use indexmap::IndexMap;
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct WesleyIR {
pub version: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<Metadata>,
pub types: Vec<TypeDefinition>,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct Metadata {
pub source_hash: Option<String>,
pub generated_at: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub units: Vec<UnitMeta>,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct UnitMeta {
pub id: String,
pub package: String,
pub hash: String,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct TypeDefinition {
pub name: String,
pub kind: TypeKind,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
pub directives: IndexMap<String, serde_json::Value>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub implements: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub fields: Vec<Field>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub enum_values: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub union_members: Vec<String>,
}
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum TypeKind {
Object,
Interface,
Union,
Enum,
Scalar,
InputObject,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct Field {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
pub r#type: TypeReference,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub arguments: Vec<FieldArgument>,
pub directives: IndexMap<String, serde_json::Value>,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct FieldArgument {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
pub r#type: TypeReference,
#[serde(skip_serializing_if = "Option::is_none")]
pub default_value: Option<serde_json::Value>,
pub directives: IndexMap<String, serde_json::Value>,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct TypeReference {
pub base: String,
pub nullable: bool,
pub is_list: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub list_item_nullable: Option<bool>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub list_wrappers: Vec<TypeListWrapper>,
#[serde(skip_serializing_if = "Option::is_none")]
pub leaf_nullable: Option<bool>,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct TypeListWrapper {
pub nullable: bool,
}
pub fn compute_registry_hash(ir: &WesleyIR) -> Result<String, serde_json::Error> {
let mut parity_ir = ir.clone();
parity_ir.metadata = None;
let json = to_canonical_json(&parity_ir)?;
Ok(compute_content_hash(&json))
}
pub fn compute_content_hash(content: &str) -> String {
let mut hasher = Sha256::new();
hasher.update(content.as_bytes());
let result = hasher.finalize();
hex::encode(result)
}
pub fn to_canonical_json<T: Serialize>(value: &T) -> Result<String, serde_json::Error> {
let val = serde_json::to_value(value)?;
let sorted_val = sort_json_value(val);
serde_json::to_string(&sorted_val)
}
fn sort_json_value(value: serde_json::Value) -> serde_json::Value {
match value {
serde_json::Value::Object(map) => {
let mut sorted_map = serde_json::Map::new();
let mut keys: Vec<String> = map.keys().cloned().collect();
keys.sort();
for key in keys {
if let Some(val) = map.get(&key) {
sorted_map.insert(key, sort_json_value(val.clone()));
}
}
serde_json::Value::Object(sorted_map)
}
serde_json::Value::Array(arr) => {
serde_json::Value::Array(arr.into_iter().map(sort_json_value).collect())
}
_ => value,
}
}