use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64};
#[must_use]
pub fn encode_edge_cursor(pk: i64) -> String {
BASE64.encode(pk.to_string())
}
#[must_use]
pub fn decode_edge_cursor(cursor: &str) -> Option<i64> {
let bytes = BASE64.decode(cursor).ok()?;
let s = std::str::from_utf8(&bytes).ok()?;
s.parse::<i64>().ok()
}
#[must_use]
pub fn encode_uuid_cursor(uuid: &str) -> String {
BASE64.encode(uuid)
}
#[must_use]
pub fn decode_uuid_cursor(cursor: &str) -> Option<String> {
let bytes = BASE64.decode(cursor).ok()?;
std::str::from_utf8(&bytes).ok().map(str::to_owned)
}
#[must_use]
pub fn encode_node_id(type_name: &str, uuid: &str) -> String {
BASE64.encode(format!("{type_name}:{uuid}"))
}
#[must_use]
pub fn decode_node_id(id: &str) -> Option<(String, String)> {
let bytes = BASE64.decode(id).ok()?;
let s = std::str::from_utf8(&bytes).ok()?;
let (type_name, uuid) = s.split_once(':')?;
if type_name.is_empty() || uuid.is_empty() {
return None;
}
Some((type_name.to_string(), uuid.to_string()))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_edge_cursor_roundtrip() {
for pk in [0_i64, 1, 42, 999_999, i64::MAX] {
let cursor = encode_edge_cursor(pk);
assert_eq!(decode_edge_cursor(&cursor), Some(pk));
}
}
#[test]
fn test_edge_cursor_negative_pk() {
let cursor = encode_edge_cursor(-1);
assert_eq!(decode_edge_cursor(&cursor), Some(-1));
}
#[test]
fn test_edge_cursor_i64_min_roundtrips() {
let cursor = encode_edge_cursor(i64::MIN);
assert_eq!(
decode_edge_cursor(&cursor),
Some(i64::MIN),
"i64::MIN must roundtrip through encode/decode"
);
}
#[test]
fn test_edge_cursor_negative_max_roundtrips() {
let cursor = encode_edge_cursor(-i64::MAX);
assert_eq!(decode_edge_cursor(&cursor), Some(-i64::MAX));
}
#[test]
fn test_edge_cursor_invalid() {
assert_eq!(decode_edge_cursor("!!!not-base64"), None);
assert_eq!(decode_edge_cursor(""), None);
let bad = BASE64.encode("not-a-number");
assert_eq!(decode_edge_cursor(&bad), None);
}
#[test]
fn test_node_id_roundtrip() {
let uuid = "550e8400-e29b-41d4-a716-446655440000";
let id = encode_node_id("User", uuid);
let decoded = decode_node_id(&id);
assert_eq!(decoded, Some(("User".to_string(), uuid.to_string())));
}
#[test]
fn test_node_id_various_types() {
for type_name in ["User", "BlogPost", "OrderItem"] {
let uuid = "00000000-0000-0000-0000-000000000001";
let id = encode_node_id(type_name, uuid);
let decoded = decode_node_id(&id);
assert_eq!(decoded.as_ref().map(|(t, _)| t.as_str()), Some(type_name));
assert_eq!(decoded.as_ref().map(|(_, u)| u.as_str()), Some(uuid));
}
}
#[test]
fn test_node_id_invalid() {
assert_eq!(decode_node_id("!!!not-base64"), None);
assert_eq!(decode_node_id(""), None);
let no_colon = BASE64.encode("UserMissingColon");
assert_eq!(decode_node_id(&no_colon), None);
}
#[test]
fn test_edge_cursor_is_base64() {
let cursor = encode_edge_cursor(42);
BASE64
.decode(&cursor)
.unwrap_or_else(|e| panic!("expected valid base64 edge cursor: {e}"));
}
#[test]
fn test_node_id_is_base64() {
let id = encode_node_id("User", "some-uuid");
BASE64
.decode(&id)
.unwrap_or_else(|e| panic!("expected valid base64 node ID: {e}"));
}
}