Skip to main content

wesley_core/domain/
ir.rs

1//! Wesley Intermediate Representation (IR).
2//!
3//! Pure semantic representation of GraphQL SDL, free from domain-specific concepts.
4
5use indexmap::IndexMap;
6use serde::{Deserialize, Serialize};
7use sha2::{Digest, Sha256};
8
9/// The root Wesley IR structure.
10#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
11pub struct WesleyIR {
12    /// IR Schema version (e.g. "1.0.0").
13    pub version: String,
14    /// Non-deterministic metadata (stripped during parity hashing).
15    #[serde(skip_serializing_if = "Option::is_none")]
16    pub metadata: Option<Metadata>,
17    /// Consolidated type definitions.
18    pub types: Vec<TypeDefinition>,
19}
20
21/// Metadata for the IR.
22#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
23#[serde(rename_all = "camelCase")]
24pub struct Metadata {
25    /// Source SDL hash.
26    pub source_hash: Option<String>,
27    /// Generation timestamp.
28    pub generated_at: Option<String>,
29    /// Compilation units involved.
30    #[serde(default, skip_serializing_if = "Vec::is_empty")]
31    pub units: Vec<UnitMeta>,
32}
33
34/// Compilation unit metadata.
35#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
36pub struct UnitMeta {
37    /// Unit ID (usually path).
38    pub id: String,
39    /// Package name.
40    pub package: String,
41    /// Unit hash.
42    pub hash: String,
43}
44
45/// A type definition (Object, Interface, etc.).
46#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
47#[serde(rename_all = "camelCase")]
48pub struct TypeDefinition {
49    /// Type name.
50    pub name: String,
51    /// Kind of type.
52    pub kind: TypeKind,
53    /// Optional description.
54    #[serde(skip_serializing_if = "Option::is_none")]
55    pub description: Option<String>,
56    /// Generic map of directives.
57    pub directives: IndexMap<String, serde_json::Value>,
58    /// Interfaces this type implements (for Objects and Interfaces).
59    #[serde(default, skip_serializing_if = "Vec::is_empty")]
60    pub implements: Vec<String>,
61    /// Fields (for Objects, Interfaces, InputObjects).
62    #[serde(default, skip_serializing_if = "Vec::is_empty")]
63    pub fields: Vec<Field>,
64    /// Enum values (for Enums).
65    #[serde(default, skip_serializing_if = "Vec::is_empty")]
66    pub enum_values: Vec<String>,
67    /// Union member type names (for Unions).
68    #[serde(default, skip_serializing_if = "Vec::is_empty")]
69    pub union_members: Vec<String>,
70}
71
72/// Kinds of GraphQL types.
73#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
74#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
75pub enum TypeKind {
76    /// Object type.
77    Object,
78    /// Interface type.
79    Interface,
80    /// Union type.
81    Union,
82    /// Enum type.
83    Enum,
84    /// Scalar type.
85    Scalar,
86    /// Input object type.
87    InputObject,
88}
89
90/// A field definition.
91#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
92#[serde(rename_all = "camelCase")]
93pub struct Field {
94    /// Field name.
95    pub name: String,
96    /// Optional description.
97    #[serde(skip_serializing_if = "Option::is_none")]
98    pub description: Option<String>,
99    /// Field type reference.
100    pub r#type: TypeReference,
101    /// Field arguments, for object and interface fields.
102    #[serde(default, skip_serializing_if = "Vec::is_empty")]
103    pub arguments: Vec<FieldArgument>,
104    /// Default value, if an input object field declares one.
105    #[serde(default, skip_serializing_if = "Option::is_none")]
106    pub default_value: Option<serde_json::Value>,
107    /// Generic map of directives.
108    pub directives: IndexMap<String, serde_json::Value>,
109}
110
111/// A field argument definition.
112#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
113#[serde(rename_all = "camelCase")]
114pub struct FieldArgument {
115    /// Argument name.
116    pub name: String,
117    /// Optional description.
118    #[serde(skip_serializing_if = "Option::is_none")]
119    pub description: Option<String>,
120    /// Argument type reference.
121    pub r#type: TypeReference,
122    /// Default value, if the schema declares one.
123    #[serde(skip_serializing_if = "Option::is_none")]
124    pub default_value: Option<serde_json::Value>,
125    /// Generic map of directives attached to the argument.
126    pub directives: IndexMap<String, serde_json::Value>,
127}
128
129/// Reference to a type with nullability and list information.
130#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
131#[serde(rename_all = "camelCase")]
132pub struct TypeReference {
133    /// Base type name.
134    pub base: String,
135    /// Whether the type is nullable.
136    pub nullable: bool,
137    /// Whether the type is a list.
138    pub is_list: bool,
139    /// Whether list items are nullable.
140    #[serde(skip_serializing_if = "Option::is_none")]
141    pub list_item_nullable: Option<bool>,
142    /// Nested list wrappers ordered from outermost to innermost.
143    #[serde(default, skip_serializing_if = "Vec::is_empty")]
144    pub list_wrappers: Vec<TypeListWrapper>,
145    /// Whether the named leaf value is nullable for nested list references.
146    #[serde(skip_serializing_if = "Option::is_none")]
147    pub leaf_nullable: Option<bool>,
148}
149
150/// One list wrapper inside a nested GraphQL type reference.
151#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
152#[serde(rename_all = "camelCase")]
153pub struct TypeListWrapper {
154    /// Whether this list wrapper is nullable.
155    pub nullable: bool,
156}
157
158/// Computes the canonical registry hash for the given IR.
159pub fn compute_registry_hash(ir: &WesleyIR) -> Result<String, serde_json::Error> {
160    let mut parity_ir = ir.clone();
161    parity_ir.metadata = None;
162
163    let json = to_canonical_json(&parity_ir)?;
164    Ok(compute_content_hash(&json))
165}
166
167/// Computes a stable SHA-256 hex hash for arbitrary UTF-8 content.
168pub fn compute_content_hash(content: &str) -> String {
169    compute_content_hash_bytes(content.as_bytes())
170}
171
172/// Computes a stable SHA-256 hex hash for arbitrary bytes.
173pub fn compute_content_hash_bytes(content: &[u8]) -> String {
174    let mut hasher = Sha256::new();
175    hasher.update(content);
176    let result = hasher.finalize();
177
178    hex::encode(result)
179}
180
181/// Serializes a value to a canonical JSON string (sorted keys, no whitespace).
182pub fn to_canonical_json<T: Serialize>(value: &T) -> Result<String, serde_json::Error> {
183    let val = serde_json::to_value(value)?;
184    let sorted_val = sort_json_value(val);
185    serde_json::to_string(&sorted_val)
186}
187
188fn sort_json_value(value: serde_json::Value) -> serde_json::Value {
189    match value {
190        serde_json::Value::Object(map) => {
191            let mut sorted_map = serde_json::Map::new();
192            let mut keys: Vec<String> = map.keys().cloned().collect();
193            keys.sort();
194            for key in keys {
195                if let Some(val) = map.get(&key) {
196                    sorted_map.insert(key, sort_json_value(val.clone()));
197                }
198            }
199            serde_json::Value::Object(sorted_map)
200        }
201        serde_json::Value::Array(arr) => {
202            serde_json::Value::Array(arr.into_iter().map(sort_json_value).collect())
203        }
204        _ => value,
205    }
206}