lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README | LICENSE

canonical.rs (4281B)


      1 #[cfg(not(feature = "std"))]
      2 use alloc::{
      3     string::{String, ToString},
      4     vec::Vec,
      5 };
      6 
      7 use serde::Serialize;
      8 use serde_json::{Map, Value};
      9 
     10 use crate::error::RadrootsReplicaEventsError;
     11 
     12 #[cfg(test)]
     13 pub(crate) mod failpoints {
     14     use std::cell::Cell;
     15 
     16     thread_local! {
     17         static FORCE_ERROR: Cell<bool> = const { Cell::new(false) };
     18     }
     19 
     20     pub(crate) fn set_error() {
     21         FORCE_ERROR.with(|flag| flag.set(true));
     22     }
     23 
     24     pub(crate) fn take_error() -> bool {
     25         FORCE_ERROR.with(|flag| {
     26             let value = flag.get();
     27             flag.set(false);
     28             value
     29         })
     30     }
     31 }
     32 
     33 pub fn canonical_json_string<T: Serialize>(
     34     value: &T,
     35 ) -> Result<String, RadrootsReplicaEventsError> {
     36     let value = serde_json::to_value(value).map_err(map_canonical_serialize_error)?;
     37     canonical_json_value(value)
     38 }
     39 
     40 fn canonical_json_value(value: Value) -> Result<String, RadrootsReplicaEventsError> {
     41     #[cfg(test)]
     42     if failpoints::take_error() {
     43         return Err(RadrootsReplicaEventsError::InvalidData(
     44             canonical_error_message(),
     45         ));
     46     }
     47     Ok(canonicalize_value(value).to_string())
     48 }
     49 
     50 fn canonical_error_message() -> String {
     51     "canonical json serialization failed".to_string()
     52 }
     53 
     54 fn map_canonical_serialize_error(_err: serde_json::Error) -> RadrootsReplicaEventsError {
     55     RadrootsReplicaEventsError::InvalidData(canonical_error_message())
     56 }
     57 
     58 fn canonicalize_value(value: Value) -> Value {
     59     match value {
     60         Value::Object(map) => canonicalize_object(map),
     61         Value::Array(values) => {
     62             let values = values
     63                 .into_iter()
     64                 .map(canonicalize_value)
     65                 .collect::<Vec<_>>();
     66             Value::Array(values)
     67         }
     68         other => other,
     69     }
     70 }
     71 
     72 fn canonicalize_object(map: Map<String, Value>) -> Value {
     73     let mut entries = map.into_iter().collect::<Vec<_>>();
     74     entries.sort_by(|a, b| a.0.cmp(&b.0));
     75     let mut ordered = Map::new();
     76     for (key, value) in entries {
     77         ordered.insert(key, canonicalize_value(value));
     78     }
     79     Value::Object(ordered)
     80 }
     81 
     82 #[cfg(test)]
     83 mod tests {
     84     use super::canonical_json_string;
     85     use serde::Serialize;
     86 
     87     #[derive(Serialize)]
     88     struct CanonicalFixture {
     89         z: u32,
     90         a: NestedFixture,
     91     }
     92 
     93     #[derive(Serialize)]
     94     struct NestedFixture {
     95         b: u32,
     96         a: u32,
     97     }
     98 
     99     #[test]
    100     fn canonical_json_string_sorts_object_keys_recursively() {
    101         let value = CanonicalFixture {
    102             z: 2,
    103             a: NestedFixture { b: 3, a: 1 },
    104         };
    105         let json = canonical_json_string(&value).expect("json");
    106         assert_eq!(json, r#"{"a":{"a":1,"b":3},"z":2}"#);
    107     }
    108 
    109     #[test]
    110     fn canonical_json_string_handles_arrays() {
    111         let json = canonical_json_string(&serde_json::json!([{"b": 2, "a": 1}])).expect("json");
    112         assert_eq!(json, r#"[{"a":1,"b":2}]"#);
    113     }
    114 
    115     #[test]
    116     fn canonical_json_string_handles_scalar_values() {
    117         let json = canonical_json_string(&"value").expect("json");
    118         assert_eq!(json, r#""value""#);
    119     }
    120 
    121     #[test]
    122     fn canonical_json_string_failpoint_returns_error() {
    123         super::failpoints::set_error();
    124         let err = canonical_json_string(&"value").expect_err("failpoint");
    125         assert!(
    126             err.to_string()
    127                 .contains("canonical json serialization failed")
    128         );
    129     }
    130 
    131     struct FlakySerialize {
    132         fail: bool,
    133     }
    134 
    135     impl Serialize for FlakySerialize {
    136         fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    137         where
    138             S: serde::Serializer,
    139         {
    140             if self.fail {
    141                 Err(serde::ser::Error::custom("always fail"))
    142             } else {
    143                 serializer.serialize_str("ok")
    144             }
    145         }
    146     }
    147 
    148     #[test]
    149     fn canonical_json_string_propagates_serialization_errors() {
    150         let ok = canonical_json_string(&FlakySerialize { fail: false }).expect("serialize ok");
    151         assert_eq!(ok, r#""ok""#);
    152 
    153         let err =
    154             canonical_json_string(&FlakySerialize { fail: true }).expect_err("serialize fail");
    155         assert!(
    156             err.to_string()
    157                 .contains("canonical json serialization failed")
    158         );
    159     }
    160 }