hypercall_api/upstash/
codec.rs1use anyhow::{anyhow, Result};
2use std::io::{Cursor, Read};
3
4pub const UPSTASH_SNAPSHOT_KEY_VERSION: &str = "v2";
5const LATEST_SUFFIX: &str = ":latest";
6const BROTLI_BUFFER_BYTES: usize = 4096;
7const BROTLI_LGWIN: u32 = 22;
8
9const BROTLI_QUALITY: u32 = 4;
11
12pub fn version_upstash_key(key: &str) -> Result<String> {
13 let key = key.trim();
14 if key.is_empty() {
15 return Err(anyhow!("Upstash snapshot key must not be empty"));
16 }
17
18 if key.contains(&format!(":{}:", UPSTASH_SNAPSHOT_KEY_VERSION))
19 || key.ends_with(&format!(":{}", UPSTASH_SNAPSHOT_KEY_VERSION))
20 {
21 return Ok(key.to_string());
22 }
23
24 if let Some(prefix) = key.strip_suffix(LATEST_SUFFIX) {
25 return Ok(format!(
26 "{}:{}{}",
27 prefix, UPSTASH_SNAPSHOT_KEY_VERSION, LATEST_SUFFIX
28 ));
29 }
30
31 Ok(format!("{}:{}", key, UPSTASH_SNAPSHOT_KEY_VERSION))
32}
33
34pub fn compress_snapshot_payload(payload: &[u8]) -> Result<Vec<u8>> {
35 let mut compressed = Vec::new();
36 let mut reader = brotli::CompressorReader::new(
37 Cursor::new(payload),
38 BROTLI_BUFFER_BYTES,
39 BROTLI_QUALITY,
40 BROTLI_LGWIN,
41 );
42 reader
43 .read_to_end(&mut compressed)
44 .map_err(|error| anyhow!("Failed to Brotli-compress Upstash payload: {}", error))?;
45 Ok(compressed)
46}
47
48#[cfg(test)]
49mod tests {
50 use super::{compress_snapshot_payload, version_upstash_key};
51 use std::io::{Cursor, Read};
52
53 #[test]
54 fn version_upstash_key_versions_latest_suffix() {
55 assert_eq!(
56 version_upstash_key("markets:snapshot:slim:latest").unwrap(),
57 "markets:snapshot:slim:v2:latest"
58 );
59 }
60
61 #[test]
62 fn version_upstash_key_leaves_existing_version_untouched() {
63 assert_eq!(
64 version_upstash_key("options-summary:snapshot:v2:latest").unwrap(),
65 "options-summary:snapshot:v2:latest"
66 );
67 }
68
69 #[test]
70 fn compress_snapshot_payload_round_trips() {
71 let payload = br#"{"schema_version":1,"built_at_ms":0,"payload":{"success":true}}"#;
72 let compressed = compress_snapshot_payload(payload).unwrap();
73
74 let mut decompressed = Vec::new();
75 let mut reader = brotli::Decompressor::new(Cursor::new(compressed), 4096);
76 reader.read_to_end(&mut decompressed).unwrap();
77
78 assert_eq!(decompressed, payload);
79 }
80}