nixfleet_canonicalize/
lib.rs1#![allow(clippy::doc_lazy_continuation)]
2use anyhow::{Context, Result};
7use serde::Serialize;
8use sha2::Digest;
9
10pub fn canonicalize(input: &str) -> Result<String> {
12 let value: serde_json::Value =
13 serde_json::from_str(input).context("input is not valid JSON")?;
14 serde_jcs::to_string(&value).context("JCS canonicalization failed")
15}
16
17pub fn sha256_jcs_hex<T: Serialize>(value: &T) -> Result<String> {
19 let canonical = serde_jcs::to_vec(value).context("JCS canonicalization failed")?;
20 let digest = sha2::Sha256::digest(&canonical);
21 Ok(hex::encode(digest))
22}
23
24#[cfg(test)]
25mod tests {
26 use super::*;
27
28 #[test]
29 fn sha256_jcs_hex_string_value_is_stable() {
30 let a = sha256_jcs_hex(&"hello").unwrap();
31 let b = sha256_jcs_hex(&"hello").unwrap();
32 assert_eq!(a, b);
33 assert_eq!(a.len(), 64);
34 }
35
36 #[test]
37 fn sha256_jcs_hex_struct_value_is_stable() {
38 #[derive(Serialize)]
39 struct S<'a> {
40 host: &'a str,
41 count: u32,
42 }
43 let a = sha256_jcs_hex(&S {
44 host: "host-02",
45 count: 7,
46 })
47 .unwrap();
48 let b = sha256_jcs_hex(&S {
49 host: "host-02",
50 count: 7,
51 })
52 .unwrap();
53 assert_eq!(a, b);
54 }
55
56 #[test]
57 fn sha256_jcs_hex_empty_string_is_distinct_from_other_input() {
58 let empty = sha256_jcs_hex(&"").unwrap();
59 let nonempty = sha256_jcs_hex(&"x").unwrap();
60 assert_ne!(empty, nonempty);
61 assert_eq!(empty.len(), 64);
62 }
63}