use base64::engine::general_purpose::URL_SAFE_NO_PAD;
use base64::Engine;
use crate::errors::GraphDDBError;
use crate::value::{cursor_json, Value};
pub fn encode_cursor(last_evaluated_key: &Value) -> Result<String, GraphDDBError> {
let payload = cursor_json(last_evaluated_key)?;
Ok(URL_SAFE_NO_PAD.encode(payload.as_bytes()))
}
pub fn decode_cursor(cursor: &str) -> Result<serde_json::Value, GraphDDBError> {
let bytes = URL_SAFE_NO_PAD
.decode(cursor.as_bytes())
.map_err(|e| GraphDDBError::new(format!("invalid base64url cursor: {e}")))?;
let text = String::from_utf8(bytes)
.map_err(|e| GraphDDBError::new(format!("cursor is not valid UTF-8: {e}")))?;
serde_json::from_str(&text)
.map_err(|e| GraphDDBError::new(format!("cursor did not decode to JSON: {e}")))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::value::indexmap_shim::IndexMap;
#[test]
fn round_trip_and_no_padding() {
let mut m = IndexMap::new();
m.insert("PK".to_string(), Value::S("GROUP#g1".to_string()));
m.insert("SK".to_string(), Value::S("USER#u1".to_string()));
let key = Value::M(m);
let c = encode_cursor(&key).unwrap();
assert!(!c.contains('='), "cursor must be padless: {c}");
let decoded = decode_cursor(&c).unwrap();
assert_eq!(decoded["PK"], "GROUP#g1");
assert_eq!(decoded["SK"], "USER#u1");
}
#[test]
fn golden_byte_shape() {
let mut m = IndexMap::new();
m.insert("PK".to_string(), Value::S("P".to_string()));
m.insert("SK".to_string(), Value::S("S".to_string()));
let c = encode_cursor(&Value::M(m)).unwrap();
assert_eq!(c, "eyJQSyI6IlAiLCJTSyI6IlMifQ");
}
}