1use std::collections::BTreeMap;
16
17use serde::{Deserialize, Serialize};
18
19use crate::domain::ownership::Sha256;
20
21#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
26#[serde(untagged)]
27pub enum Value {
28 Null,
30 Bool(bool),
32 Int(i64),
34 Text(String),
36 List(Vec<Self>),
38 Map(BTreeMap<String, Self>),
40}
41
42impl Value {
43 #[must_use]
45 pub fn map<I: IntoIterator<Item = (&'static str, Self)>>(pairs: I) -> Self {
46 Self::Map(
47 pairs
48 .into_iter()
49 .map(|(key, value)| (key.to_string(), value))
50 .collect(),
51 )
52 }
53
54 #[must_use]
56 pub fn text(value: impl Into<String>) -> Self {
57 Self::Text(value.into())
58 }
59
60 #[must_use]
62 pub fn maybe(value: Option<impl Into<String>>) -> Self {
63 value.map_or(Self::Null, |held| Self::Text(held.into()))
64 }
65}
66
67#[must_use]
73pub fn canonical(value: &Value) -> String {
74 let mut out = String::new();
75 encode(value, &mut out);
76 out
77}
78
79fn encode(value: &Value, out: &mut String) {
80 match value {
81 Value::Null => out.push_str("null"),
82 Value::Bool(true) => out.push_str("true"),
83 Value::Bool(false) => out.push_str("false"),
84 Value::Int(number) => out.push_str(&number.to_string()),
85 Value::Text(text) => encode_string(text, out),
86 Value::List(items) => {
87 out.push('[');
88 for (index, item) in items.iter().enumerate() {
89 if index > 0 {
90 out.push(',');
91 }
92 encode(item, out);
93 }
94 out.push(']');
95 }
96 Value::Map(entries) => {
97 let mut keys: Vec<&String> = entries.keys().collect();
98 keys.sort_by_key(|left| utf16_units(left));
99 out.push('{');
100 for (index, key) in keys.iter().enumerate() {
101 if index > 0 {
102 out.push(',');
103 }
104 encode_string(key, out);
105 out.push(':');
106 if let Some(held) = entries.get(*key) {
107 encode(held, out);
108 }
109 }
110 out.push('}');
111 }
112 }
113}
114
115fn utf16_units(text: &str) -> Vec<u16> {
116 text.encode_utf16().collect()
117}
118
119fn encode_string(text: &str, out: &mut String) {
125 out.push('"');
126 for character in text.chars() {
127 match character {
128 '"' => out.push_str("\\\""),
129 '\\' => out.push_str("\\\\"),
130 '\u{8}' => out.push_str("\\b"),
131 '\u{c}' => out.push_str("\\f"),
132 '\n' => out.push_str("\\n"),
133 '\r' => out.push_str("\\r"),
134 '\t' => out.push_str("\\t"),
135 control if control < '\u{20}' => {
136 use std::fmt::Write as _;
137 let _ = write!(out, "\\u{:04x}", control as u32);
138 }
139 ordinary => out.push(ordinary),
140 }
141 }
142 out.push('"');
143}
144
145#[must_use]
147pub fn fingerprint(value: &Value) -> Sha256 {
148 Sha256::of(canonical(value).as_bytes())
149}
150
151#[cfg(test)]
152mod tests {
153 #![allow(
154 clippy::unwrap_used,
155 reason = "a test panics as its failure signal, not as control flow"
156 )]
157
158 use super::*;
159
160 #[test]
161 fn the_scalar_forms_encode_as_the_rfc_writes_them() {
162 assert_eq!(canonical(&Value::Null), "null");
163 assert_eq!(canonical(&Value::Bool(true)), "true");
164 assert_eq!(canonical(&Value::Bool(false)), "false");
165 assert_eq!(canonical(&Value::Int(0)), "0");
166 assert_eq!(canonical(&Value::Int(-1)), "-1");
167 assert_eq!(
168 canonical(&Value::Int(9_007_199_254_740_991)),
169 "9007199254740991"
170 );
171 }
172
173 #[test]
177 fn strings_escape_exactly_what_the_rfc_escapes() {
178 assert_eq!(canonical(&Value::text("")), "\"\"");
179 assert_eq!(canonical(&Value::text("a\"b")), "\"a\\\"b\"");
180 assert_eq!(canonical(&Value::text("a\\b")), "\"a\\\\b\"");
181 assert_eq!(
182 canonical(&Value::text("\u{8}\u{c}\n\r\t")),
183 "\"\\b\\f\\n\\r\\t\""
184 );
185 assert_eq!(canonical(&Value::text("\u{1}")), "\"\\u0001\"");
186 assert_eq!(canonical(&Value::text("\u{7f}")), "\"\u{7f}\"");
187 assert_eq!(canonical(&Value::text("é")), "\"é\"");
188 assert_eq!(canonical(&Value::text("😀")), "\"😀\"");
189 }
190
191 #[test]
195 fn keys_sort_by_utf16_code_unit() {
196 let held = Value::map([("\u{fb33}", Value::Int(1)), ("😀", Value::Int(2))]);
197 assert_eq!(canonical(&held), "{\"😀\":2,\"\u{fb33}\":1}");
200 }
201
202 #[test]
203 fn map_insertion_order_cannot_change_the_digest() {
204 let one = Value::map([
205 ("b", Value::Int(2)),
206 ("a", Value::Int(1)),
207 ("c", Value::text("three")),
208 ]);
209 let two = Value::map([
210 ("c", Value::text("three")),
211 ("a", Value::Int(1)),
212 ("b", Value::Int(2)),
213 ]);
214 assert_eq!(canonical(&one), canonical(&two));
215 assert_eq!(fingerprint(&one), fingerprint(&two));
216 assert_eq!(canonical(&one), "{\"a\":1,\"b\":2,\"c\":\"three\"}");
217 }
218
219 #[test]
220 fn list_order_does_change_the_digest() {
221 let one = Value::List(vec![Value::Int(1), Value::Int(2)]);
222 let two = Value::List(vec![Value::Int(2), Value::Int(1)]);
223 assert_ne!(fingerprint(&one), fingerprint(&two));
224 }
225
226 #[test]
227 fn nesting_encodes_without_whitespace() {
228 let held = Value::map([(
229 "outer",
230 Value::map([("inner", Value::List(vec![Value::Null]))]),
231 )]);
232 assert_eq!(canonical(&held), "{\"outer\":{\"inner\":[null]}}");
233 }
234
235 #[test]
236 fn a_maybe_value_is_null_where_there_is_nothing() {
237 assert_eq!(canonical(&Value::maybe(None::<String>)), "null");
238 assert_eq!(canonical(&Value::maybe(Some("x"))), "\"x\"");
239 }
240}