rusty_schema_diff/analyzer/
json_schema.rs1use crate::analyzer::{SchemaAnalyzer, SchemaChange, ChangeType};
7use crate::{Schema, CompatibilityReport, MigrationPlan, ValidationResult};
8use crate::error::Result;
9use serde_json::Value;
10use std::collections::HashMap;
11
12pub struct JsonSchemaAnalyzer;
14
15impl SchemaAnalyzer for JsonSchemaAnalyzer {
16 fn analyze_compatibility(&self, old: &Schema, new: &Schema) -> Result<CompatibilityReport> {
27 let old_schema: Value = serde_json::from_str(&old.content)?;
28 let new_schema: Value = serde_json::from_str(&new.content)?;
29
30 let mut changes = Vec::new();
31 self.compare_schemas(&old_schema, &new_schema, "", &mut changes);
32
33 let compatibility_score = self.calculate_compatibility_score(&changes);
34 let is_compatible = compatibility_score >= 80;
35
36 Ok(CompatibilityReport {
37 changes,
38 compatibility_score,
39 is_compatible,
40 issues: vec![], metadata: Default::default(),
42 })
43 }
44
45 fn generate_migration_path(&self, old: &Schema, new: &Schema) -> Result<MigrationPlan> {
56 let mut changes = Vec::new();
57 let old_schema: Value = serde_json::from_str(&old.content)?;
58 let new_schema: Value = serde_json::from_str(&new.content)?;
59
60 self.compare_schemas(&old_schema, &new_schema, "", &mut changes);
61
62 Ok(MigrationPlan::new(
63 old.version.to_string(),
64 new.version.to_string(),
65 changes,
66 ))
67 }
68
69 fn validate_changes(&self, _changes: &[SchemaChange]) -> Result<ValidationResult> {
70 Ok(ValidationResult {
71 is_valid: true,
72 errors: Vec::new(),
73 context: HashMap::new(),
74 })
75 }
76}
77
78impl JsonSchemaAnalyzer {
79 fn compare_schemas(&self, old: &Value, new: &Value, path: &str, changes: &mut Vec<SchemaChange>) {
81 match (old, new) {
82 (Value::Object(old_obj), Value::Object(new_obj)) => {
83 self.compare_objects(old_obj, new_obj, path, changes);
84 }
85 (Value::Array(old_arr), Value::Array(new_arr)) => {
86 self.compare_arrays(old_arr, new_arr, path, changes);
87 }
88 _ if old != new => {
89 let mut metadata = HashMap::new();
90 metadata.insert("old_value".to_string(), old.to_string());
91 metadata.insert("new_value".to_string(), new.to_string());
92
93 changes.push(SchemaChange::new(
94 ChangeType::Modification,
95 path.to_string(),
96 format!("Value changed from {:?} to {:?}", old, new),
97 metadata,
98 ));
99 }
100 _ => {}
101 }
102 }
103
104 fn calculate_compatibility_score(&self, changes: &[SchemaChange]) -> u8 {
105 let base_score: u8 = 100;
106 let mut deductions: u8 = 0;
107
108 for change in changes {
109 match change.change_type {
110 ChangeType::Addition => deductions = deductions.saturating_add(5),
111 ChangeType::Removal => deductions = deductions.saturating_add(20),
112 ChangeType::Modification => deductions = deductions.saturating_add(10),
113 ChangeType::Rename => deductions = deductions.saturating_add(8),
114 }
115 }
116
117 base_score.saturating_sub(deductions)
118 }
119
120 #[allow(dead_code)]
121 fn detect_schema_changes(&self, path: &str, old_schema: &Value, new_schema: &Value, changes: &mut Vec<SchemaChange>) {
122 match (old_schema, new_schema) {
123 (Value::Object(old_obj), Value::Object(new_obj)) => {
124 for (key, old_value) in old_obj {
126 if let Some(new_value) = new_obj.get(key) {
127 if old_value != new_value {
128 let mut metadata = HashMap::new();
129 metadata.insert("property".to_string(), key.clone());
130
131 changes.push(SchemaChange::new(
132 ChangeType::Modification,
133 format!("{}/{}", path, key),
134 format!("Property '{}' was modified", key),
135 metadata,
136 ));
137 }
138 } else {
139 let mut metadata = HashMap::new();
140 metadata.insert("property".to_string(), key.clone());
141
142 changes.push(SchemaChange::new(
143 ChangeType::Removal,
144 format!("{}/{}", path, key),
145 format!("Property '{}' was removed", key),
146 metadata,
147 ));
148 }
149 }
150
151 for key in new_obj.keys() {
153 if !old_obj.contains_key(key) {
154 let mut metadata = HashMap::new();
155 metadata.insert("property".to_string(), key.clone());
156
157 changes.push(SchemaChange::new(
158 ChangeType::Addition,
159 format!("{}/{}", path, key),
160 format!("New property '{}' was added", key),
161 metadata,
162 ));
163 }
164 }
165 }
166 (old_val, new_val) if old_val != new_val => {
167 let mut metadata = HashMap::new();
168 metadata.insert("old_value".to_string(), old_val.to_string());
169 metadata.insert("new_value".to_string(), new_val.to_string());
170
171 changes.push(SchemaChange::new(
172 ChangeType::Modification,
173 path.to_string(),
174 format!("Value changed from {:?} to {:?}", old_val, new_val),
175 metadata,
176 ));
177 }
178 _ => {}
179 }
180 }
181
182 fn compare_objects(&self, old_obj: &serde_json::Map<String, Value>, new_obj: &serde_json::Map<String, Value>, path: &str, changes: &mut Vec<SchemaChange>) {
183 for (key, old_value) in old_obj {
185 if let Some(new_value) = new_obj.get(key) {
186 self.compare_schemas(old_value, new_value, &format!("{}/{}", path, key), changes);
187 } else {
188 let mut metadata = HashMap::new();
189 metadata.insert("property".to_string(), key.clone());
190
191 changes.push(SchemaChange::new(
192 ChangeType::Removal,
193 format!("{}/{}", path, key),
194 format!("Property '{}' was removed", key),
195 metadata,
196 ));
197 }
198 }
199
200 for key in new_obj.keys() {
202 if !old_obj.contains_key(key) {
203 let mut metadata = HashMap::new();
204 metadata.insert("property".to_string(), key.clone());
205
206 changes.push(SchemaChange::new(
207 ChangeType::Addition,
208 format!("{}/{}", path, key),
209 format!("New property '{}' was added", key),
210 metadata,
211 ));
212 }
213 }
214 }
215
216 fn compare_arrays(&self, old_arr: &[Value], new_arr: &[Value], path: &str, changes: &mut Vec<SchemaChange>) {
217 if old_arr.len() != new_arr.len() {
218 let mut metadata = HashMap::new();
219 metadata.insert("old_length".to_string(), old_arr.len().to_string());
220 metadata.insert("new_length".to_string(), new_arr.len().to_string());
221
222 changes.push(SchemaChange::new(
223 ChangeType::Modification,
224 path.to_string(),
225 format!("Array length changed from {} to {}", old_arr.len(), new_arr.len()),
226 metadata,
227 ));
228 }
229
230 for (i, (old_value, new_value)) in old_arr.iter().zip(new_arr.iter()).enumerate() {
231 self.compare_schemas(old_value, new_value, &format!("{}/{}", path, i), changes);
232 }
233 }
234}