1use super::{MessageSchema, SchemaField};
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
9pub enum CompatibilityMode {
10 None,
12
13 #[default]
15 Backward,
16
17 Forward,
19
20 Full,
22
23 Strict,
25}
26
27#[derive(Debug, Clone, Serialize, Deserialize)]
29pub enum SchemaEvolution {
30 FieldAdded {
32 name: String,
33 field_type: String,
34 required: bool,
35 default_value: Option<serde_json::Value>,
36 },
37
38 FieldRemoved { name: String },
40
41 FieldMadeOptional { name: String },
43
44 FieldMadeRequired {
46 name: String,
47 default_value: serde_json::Value,
48 },
49
50 FieldTypeChanged {
52 name: String,
53 old_type: String,
54 new_type: String,
55 },
56
57 FieldRenamed { old_name: String, new_name: String },
59}
60
61#[derive(Debug, Clone, Serialize, Deserialize)]
63pub struct CompatibilityCheck {
64 pub compatible: bool,
66
67 pub incompatibilities: Vec<String>,
69
70 pub evolutions: Vec<SchemaEvolution>,
72
73 pub warnings: Vec<String>,
75}
76
77impl CompatibilityCheck {
78 pub fn check(old: &MessageSchema, new: &MessageSchema, mode: CompatibilityMode) -> Self {
80 let mut incompatibilities = Vec::new();
81 let mut evolutions = Vec::new();
82 let mut warnings = Vec::new();
83
84 let old_fields: HashMap<String, &SchemaField> = old.fields.iter().map(|f| (f.name.clone(), f)).collect();
86 let new_fields: HashMap<String, &SchemaField> = new.fields.iter().map(|f| (f.name.clone(), f)).collect();
87
88 match mode {
89 CompatibilityMode::None => {
90 return Self {
92 compatible: true,
93 incompatibilities,
94 evolutions,
95 warnings,
96 };
97 }
98 CompatibilityMode::Strict => {
99 if old.fields.len() != new.fields.len() {
101 incompatibilities.push("Field count changed".to_string());
102 }
103 for (name, old_field) in &old_fields {
104 if let Some(new_field) = new_fields.get(name) {
105 if old_field.field_type != new_field.field_type {
106 incompatibilities.push(format!("Field '{}' type changed", name));
107 }
108 if old_field.required != new_field.required {
109 incompatibilities.push(format!("Field '{}' required status changed", name));
110 }
111 } else {
112 incompatibilities.push(format!("Field '{}' removed", name));
113 }
114 }
115 for name in new_fields.keys() {
116 if !old_fields.contains_key(name) {
117 incompatibilities.push(format!("Field '{}' added", name));
118 }
119 }
120 }
121 CompatibilityMode::Backward => {
122 for (name, old_field) in &old_fields {
130 if let Some(new_field) = new_fields.get(name) {
131 if old_field.field_type != new_field.field_type {
133 incompatibilities.push(format!(
134 "Field '{}' type changed from {} to {}",
135 name, old_field.field_type, new_field.field_type
136 ));
137 evolutions.push(SchemaEvolution::FieldTypeChanged {
138 name: name.clone(),
139 old_type: old_field.field_type.clone(),
140 new_type: new_field.field_type.clone(),
141 });
142 }
143
144 if old_field.required && !new_field.required {
145 evolutions.push(SchemaEvolution::FieldMadeOptional { name: name.clone() });
147 } else if !old_field.required && new_field.required {
148 incompatibilities.push(format!("Field '{}' made required (was optional)", name));
150 evolutions.push(SchemaEvolution::FieldMadeRequired {
151 name: name.clone(),
152 default_value: serde_json::Value::Null,
153 });
154 }
155 } else {
156 if old_field.required {
158 incompatibilities.push(format!("Required field '{}' removed", name));
159 } else {
160 warnings.push(format!("Optional field '{}' removed", name));
161 }
162 evolutions.push(SchemaEvolution::FieldRemoved { name: name.clone() });
163 }
164 }
165
166 for (name, new_field) in &new_fields {
168 if !old_fields.contains_key(name) {
169 if new_field.required {
170 incompatibilities.push(format!("Required field '{}' added", name));
171 } else {
172 warnings.push(format!("Optional field '{}' added", name));
173 }
174 evolutions.push(SchemaEvolution::FieldAdded {
175 name: name.clone(),
176 field_type: new_field.field_type.clone(),
177 required: new_field.required,
178 default_value: None,
179 });
180 }
181 }
182 }
183 CompatibilityMode::Forward => {
184 for (name, old_field) in &old_fields {
192 if let Some(new_field) = new_fields.get(name) {
193 if old_field.field_type != new_field.field_type {
194 incompatibilities.push(format!("Field '{}' type changed", name));
195 evolutions.push(SchemaEvolution::FieldTypeChanged {
196 name: name.clone(),
197 old_type: old_field.field_type.clone(),
198 new_type: new_field.field_type.clone(),
199 });
200 }
201 } else if old_field.required {
202 incompatibilities.push(format!("Required field '{}' removed", name));
203 evolutions.push(SchemaEvolution::FieldRemoved { name: name.clone() });
204 }
205 }
206
207 for (name, new_field) in &new_fields {
208 if !old_fields.contains_key(name) && new_field.required {
209 incompatibilities.push(format!("Required field '{}' added", name));
210 evolutions.push(SchemaEvolution::FieldAdded {
211 name: name.clone(),
212 field_type: new_field.field_type.clone(),
213 required: new_field.required,
214 default_value: None,
215 });
216 }
217 }
218 }
219 CompatibilityMode::Full => {
220 for (name, old_field) in &old_fields {
226 if let Some(new_field) = new_fields.get(name) {
227 if old_field.field_type != new_field.field_type {
228 incompatibilities.push(format!("Field '{}' type changed", name));
229 }
230 if old_field.required != new_field.required {
231 incompatibilities.push(format!("Field '{}' required status changed", name));
232 }
233 } else if old_field.required {
234 incompatibilities.push(format!("Required field '{}' removed", name));
235 }
236 }
237
238 for (name, new_field) in &new_fields {
239 if !old_fields.contains_key(name) && new_field.required {
240 incompatibilities.push(format!("Required field '{}' added", name));
241 }
242 }
243 }
244 }
245
246 Self {
247 compatible: incompatibilities.is_empty(),
248 incompatibilities,
249 evolutions,
250 warnings,
251 }
252 }
253}