1#[cfg(feature = "typed-ids")]
10pub use stack_ids::ContentDigest as Digest;
11
12#[cfg(feature = "typed-ids")]
13impl From<stack_ids::DigestError> for crate::error::PolyKvError {
14 fn from(e: stack_ids::DigestError) -> Self {
15 crate::error::PolyKvError::Internal(format!("digest error: {:?}", e))
16 }
17}
18
19#[cfg(feature = "typed-ids")]
24pub fn compute_json<T: serde::Serialize>(value: &T) -> crate::error::Result<Digest> {
25 Digest::compute_json(value).map_err(crate::error::PolyKvError::from)
26}
27
28#[cfg(feature = "typed-ids")]
30pub fn compute(data: &[u8]) -> Digest {
31 Digest::compute(data)
32}
33
34#[cfg(feature = "typed-ids")]
36pub fn compute_str(data: &str) -> Digest {
37 Digest::compute_str(data)
38}
39
40#[cfg(not(feature = "typed-ids"))]
43#[derive(
44 Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, serde::Serialize, serde::Deserialize,
45)]
46#[serde(transparent)]
47pub struct Digest(pub String);
48
49#[cfg(not(feature = "typed-ids"))]
50impl Digest {
51 pub fn compute(data: &[u8]) -> Self {
52 Self(blake3::hash(data).to_hex().to_string())
53 }
54
55 pub fn compute_str(data: &str) -> Self {
56 Self::compute(data.as_bytes())
57 }
58
59 pub fn compute_json<T: serde::Serialize>(value: &T) -> crate::error::Result<Self> {
60 internal_compute_json(value)
61 }
62
63 pub fn hex(&self) -> &str {
64 &self.0
65 }
66
67 pub fn from_hex_unchecked(hex: impl Into<String>) -> Self {
68 Self(hex.into())
69 }
70}
71
72#[cfg(not(feature = "typed-ids"))]
74pub fn compute_json<T: serde::Serialize>(value: &T) -> crate::error::Result<Digest> {
75 internal_compute_json(value)
76}
77
78#[cfg(not(feature = "typed-ids"))]
79pub fn compute(data: &[u8]) -> Digest {
80 Digest::compute(data)
81}
82
83#[cfg(not(feature = "typed-ids"))]
84pub fn compute_str(data: &str) -> Digest {
85 Digest::compute_str(data)
86}
87
88#[cfg(not(feature = "typed-ids"))]
89fn internal_compute_json<T: serde::Serialize>(value: &T) -> crate::error::Result<Digest> {
90 let json = serde_json::to_string(value)
91 .map_err(|e| crate::error::PolyKvError::Internal(format!("digest json: {}", e)))?;
92 let val: serde_json::Value = serde_json::from_str(&json)
94 .map_err(|e| crate::error::PolyKvError::Internal(format!("digest parse: {}", e)))?;
95 let canonical = canonicalize_json_value(val);
96 let canonical_str = serde_json::to_string(&canonical)
97 .map_err(|e| crate::error::PolyKvError::Internal(format!("digest canonicalize: {}", e)))?;
98 Ok(Digest(
99 blake3::hash(canonical_str.as_bytes()).to_hex().to_string(),
100 ))
101}
102
103#[cfg(not(feature = "typed-ids"))]
104fn canonicalize_json_value(value: serde_json::Value) -> serde_json::Value {
105 match value {
106 serde_json::Value::Object(map) => {
107 let mut entries: Vec<(String, serde_json::Value)> = map
108 .into_iter()
109 .map(|(k, v)| (k, canonicalize_json_value(v)))
110 .collect();
111 entries.sort_by(|a, b| a.0.cmp(&b.0));
112 let mut ordered = serde_json::Map::new();
113 for (k, v) in entries {
114 ordered.insert(k, v);
115 }
116 serde_json::Value::Object(ordered)
117 }
118 serde_json::Value::Array(items) => {
119 serde_json::Value::Array(items.into_iter().map(canonicalize_json_value).collect())
120 }
121 other => other,
122 }
123}