Skip to main content

didbot_policy/
json.rs

1//! JSON text that does not depend on the order a client wrote its keys in.
2
3use serde_json::Value;
4
5/// The compact JSON text of `value`, with every object's keys sorted.
6///
7/// `serde_json` writes an object's keys in the order they arrived when any
8/// crate in the build enables its `preserve_order` feature, and
9/// `cedar-policy` does. Text that is hashed, published as a digest, or
10/// matched by a policy goes through here instead, so reordering keys
11/// changes none of them.
12#[must_use]
13pub fn sorted_json(value: &Value) -> String {
14    sorted(value).to_string()
15}
16
17fn sorted(value: &Value) -> Value {
18    match value {
19        Value::Object(map) => {
20            let mut entries: Vec<(&String, &Value)> = map.iter().collect();
21            entries.sort_unstable_by(|a, b| a.0.cmp(b.0));
22            Value::Object(
23                entries
24                    .into_iter()
25                    .map(|(key, item)| (key.clone(), sorted(item)))
26                    .collect(),
27            )
28        }
29        Value::Array(items) => Value::Array(items.iter().map(sorted).collect()),
30        other => other.clone(),
31    }
32}
33
34#[cfg(test)]
35mod tests {
36    use super::*;
37
38    /// Two texts that differ only in key order, nested included, come out
39    /// as one.
40    #[test]
41    fn key_order_does_not_reach_the_text() {
42        let a: Value = serde_json::from_str(r#"{"b":1,"a":{"d":[{"f":1,"e":2}],"c":2}}"#).unwrap();
43        let b: Value = serde_json::from_str(r#"{"a":{"c":2,"d":[{"e":2,"f":1}]},"b":1}"#).unwrap();
44        assert_eq!(
45            sorted_json(&a),
46            r#"{"a":{"c":2,"d":[{"e":2,"f":1}]},"b":1}"#
47        );
48        assert_eq!(sorted_json(&a), sorted_json(&b));
49    }
50}