use std::collections::BTreeMap;
use thiserror::Error;
use crate::hash::compute_sha256;
use crate::types::Sha256;
use crate::validation::validate_sha256_format;
#[derive(Debug, Error, PartialEq, Eq)]
pub enum DataShapeError {
#[error("serialization error: {0}")]
Serialization(String),
#[error("invalid hash format: {0}")]
InvalidFormat(String),
}
pub fn compute_data_shape_hash(
schema: &serde_json::Value,
sketches: Option<&serde_json::Value>,
) -> Result<Sha256, DataShapeError> {
let mut map = BTreeMap::new();
map.insert("schema", schema.clone());
if let Some(sketches_val) = sketches {
map.insert("sketches", sketches_val.clone());
}
let canonical = serde_json::to_string(&serde_json::Value::Object(
map.into_iter().map(|(k, v)| (k.to_string(), v)).collect(),
))
.map_err(|e| DataShapeError::Serialization(e.to_string()))?;
Ok(compute_sha256(&canonical))
}
pub fn validate_data_shape_hash(hash: &str) -> Result<(), DataShapeError> {
validate_sha256_format(hash).map_err(|e| DataShapeError::InvalidFormat(e.to_string()))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_compute_data_shape_hash_schema_only() {
let schema = serde_json::json!({
"type": "object",
"properties": {
"name": {"type": "string"},
"age": {"type": "number"}
}
});
let hash1 = compute_data_shape_hash(&schema, None).unwrap();
let hash2 = compute_data_shape_hash(&schema, None).unwrap();
assert_eq!(hash1, hash2, "Must be deterministic");
assert!(hash1.starts_with("sha256:"));
}
#[test]
fn test_compute_data_shape_hash_with_sketches() {
let schema = serde_json::json!({
"type": "object",
"properties": {
"name": {"type": "string"}
}
});
let sketches = serde_json::json!({
"cardinality": 1000,
"null_count": 5
});
let hash1 = compute_data_shape_hash(&schema, Some(&sketches)).unwrap();
let hash2 = compute_data_shape_hash(&schema, Some(&sketches)).unwrap();
assert_eq!(hash1, hash2, "Must be deterministic");
let hash_no_sketches = compute_data_shape_hash(&schema, None).unwrap();
assert_ne!(hash1, hash_no_sketches, "Sketches should affect hash");
}
#[test]
fn test_validate_data_shape_hash() {
let valid = "sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
assert!(validate_data_shape_hash(valid).is_ok());
let invalid = "invalid";
assert!(validate_data_shape_hash(invalid).is_err());
}
}