mod decode;
mod encode;
use base64::Engine as _;
use crate::error::{CoreError, Result};
use crate::value::Value;
const URL_SAFE_NO_PAD: base64::engine::GeneralPurpose =
base64::engine::general_purpose::URL_SAFE_NO_PAD;
const URL_SAFE: base64::engine::GeneralPurpose = base64::engine::general_purpose::URL_SAFE;
pub fn encode_cursor(values: &[Value]) -> Result<String> {
for value in values {
check_finite(value)?;
}
let mut payload = String::new();
encode::write_array(&mut payload, values);
Ok(URL_SAFE_NO_PAD.encode(payload.as_bytes()))
}
fn check_finite(value: &Value) -> Result<()> {
match value {
Value::Float(f) if !f.is_finite() => Err(invalid("non-finite ordering key")),
Value::List(items) => items.iter().try_for_each(check_finite),
Value::Map(map) => map.values().try_for_each(check_finite),
_ => Ok(()),
}
}
pub fn decode_cursor(cursor: &str) -> Result<Vec<Value>> {
let bytes = decode::decode_base64(cursor)?;
let text = std::str::from_utf8(&bytes).map_err(|_| invalid("utf8"))?;
let parsed: serde_json::Value = serde_json::from_str(text).map_err(|_| invalid("json"))?;
let array = parsed.as_array().ok_or_else(|| invalid("not a list"))?;
array.iter().map(decode::json_to_value).collect()
}
fn invalid(reason: &str) -> CoreError {
CoreError::InvalidCursor {
reason: reason.to_owned(),
}
}
#[cfg(test)]
mod tests;