northroot_proof_engine/
data_shape.rs1use std::collections::BTreeMap;
8use thiserror::Error;
9
10use crate::hash::compute_sha256;
11use crate::types::Sha256;
12use crate::validation::validate_sha256_format;
13
14#[derive(Debug, Error, PartialEq, Eq)]
16pub enum DataShapeError {
17 #[error("serialization error: {0}")]
19 Serialization(String),
20
21 #[error("invalid hash format: {0}")]
23 InvalidFormat(String),
24}
25
26pub fn compute_data_shape_hash(
34 schema: &serde_json::Value,
35 sketches: Option<&serde_json::Value>,
36) -> Result<Sha256, DataShapeError> {
37 let mut map = BTreeMap::new();
38 map.insert("schema", schema.clone());
39
40 if let Some(sketches_val) = sketches {
41 map.insert("sketches", sketches_val.clone());
42 }
43
44 let canonical = serde_json::to_string(&serde_json::Value::Object(
45 map.into_iter().map(|(k, v)| (k.to_string(), v)).collect(),
46 ))
47 .map_err(|e| DataShapeError::Serialization(e.to_string()))?;
48
49 Ok(compute_sha256(&canonical))
50}
51
52pub fn validate_data_shape_hash(hash: &str) -> Result<(), DataShapeError> {
56 validate_sha256_format(hash).map_err(|e| DataShapeError::InvalidFormat(e.to_string()))
57}
58
59#[cfg(test)]
60mod tests {
61 use super::*;
62
63 #[test]
64 fn test_compute_data_shape_hash_schema_only() {
65 let schema = serde_json::json!({
66 "type": "object",
67 "properties": {
68 "name": {"type": "string"},
69 "age": {"type": "number"}
70 }
71 });
72
73 let hash1 = compute_data_shape_hash(&schema, None).unwrap();
74 let hash2 = compute_data_shape_hash(&schema, None).unwrap();
75 assert_eq!(hash1, hash2, "Must be deterministic");
76 assert!(hash1.starts_with("sha256:"));
77 }
78
79 #[test]
80 fn test_compute_data_shape_hash_with_sketches() {
81 let schema = serde_json::json!({
82 "type": "object",
83 "properties": {
84 "name": {"type": "string"}
85 }
86 });
87
88 let sketches = serde_json::json!({
89 "cardinality": 1000,
90 "null_count": 5
91 });
92
93 let hash1 = compute_data_shape_hash(&schema, Some(&sketches)).unwrap();
94 let hash2 = compute_data_shape_hash(&schema, Some(&sketches)).unwrap();
95 assert_eq!(hash1, hash2, "Must be deterministic");
96
97 let hash_no_sketches = compute_data_shape_hash(&schema, None).unwrap();
99 assert_ne!(hash1, hash_no_sketches, "Sketches should affect hash");
100 }
101
102 #[test]
103 fn test_validate_data_shape_hash() {
104 let valid = "sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
105 assert!(validate_data_shape_hash(valid).is_ok());
106
107 let invalid = "invalid";
108 assert!(validate_data_shape_hash(invalid).is_err());
109 }
110}