sbor/schema/schema_comparison/
schema_comparison_settings.rs

1use super::*;
2
3#[derive(Debug, Copy, Clone, PartialEq, Eq)]
4pub struct SchemaComparisonSettings {
5    pub(crate) completeness: SchemaComparisonCompletenessSettings,
6    pub(crate) structure: SchemaComparisonStructureSettings,
7    pub(crate) metadata: SchemaComparisonMetadataSettings,
8    pub(crate) validation: SchemaComparisonValidationSettings,
9}
10
11impl SchemaComparisonSettings {
12    /// A set of defaults intended to enforce effective equality of the schemas,
13    /// but with clear error messages if they diverge
14    pub const fn require_equality() -> Self {
15        Self {
16            completeness: SchemaComparisonCompletenessSettings::enforce_type_roots_cover_schema_disallow_new_root_types(),
17            structure: SchemaComparisonStructureSettings::require_identical_structure(),
18            metadata: SchemaComparisonMetadataSettings::require_identical_metadata(),
19            validation: SchemaComparisonValidationSettings::require_identical_validation(),
20        }
21    }
22
23    /// A set of defaults intended to capture a pretty tight definition of structural extension.
24    ///
25    /// This captures that:
26    /// * Payloads which are valid/decodable against the old schema are valid against the new schema
27    /// * Programmatic SBOR JSON is unchanged (that is, type/field/variant names are also unchanged)
28    ///
29    /// Notably:
30    /// * Type roots can be added in the compared schema, but we check that the type roots
31    ///   provided completely cover both schemas
32    /// * Types must be structurally identical on their intersection, except new enum variants can be added
33    /// * Type metadata (e.g. names) must be identical on their intersection
34    /// * Type validation must be equal or strictly weaker in the new schema
35    pub const fn allow_extension() -> Self {
36        Self {
37            completeness: SchemaComparisonCompletenessSettings::enforce_type_roots_cover_schema_allow_new_root_types(),
38            structure: SchemaComparisonStructureSettings::allow_extension(),
39            metadata: SchemaComparisonMetadataSettings::require_identical_metadata(),
40            validation: SchemaComparisonValidationSettings::allow_weakening(),
41        }
42    }
43
44    pub fn with_completeness(
45        mut self,
46        builder: impl FnOnce(
47            SchemaComparisonCompletenessSettings,
48        ) -> SchemaComparisonCompletenessSettings,
49    ) -> Self {
50        self.completeness = builder(self.completeness);
51        self
52    }
53
54    pub const fn set_completeness(
55        mut self,
56        settings: SchemaComparisonCompletenessSettings,
57    ) -> Self {
58        self.completeness = settings;
59        self
60    }
61
62    pub fn with_structure(
63        mut self,
64        builder: impl FnOnce(SchemaComparisonStructureSettings) -> SchemaComparisonStructureSettings,
65    ) -> Self {
66        self.structure = builder(self.structure);
67        self
68    }
69
70    pub const fn set_structure(mut self, settings: SchemaComparisonStructureSettings) -> Self {
71        self.structure = settings;
72        self
73    }
74
75    pub fn with_metadata(
76        mut self,
77        builder: impl FnOnce(SchemaComparisonMetadataSettings) -> SchemaComparisonMetadataSettings,
78    ) -> Self {
79        self.metadata = builder(self.metadata);
80        self
81    }
82
83    pub const fn set_metadata(mut self, settings: SchemaComparisonMetadataSettings) -> Self {
84        self.metadata = settings;
85        self
86    }
87
88    /// An easy method for a common setting change
89    pub const fn allow_all_name_changes(mut self) -> Self {
90        self.metadata = SchemaComparisonMetadataSettings::allow_all_changes();
91        self
92    }
93
94    pub fn with_validation(
95        mut self,
96        builder: impl FnOnce(SchemaComparisonValidationSettings) -> SchemaComparisonValidationSettings,
97    ) -> Self {
98        self.validation = builder(self.validation);
99        self
100    }
101
102    pub const fn set_validation(mut self, settings: SchemaComparisonValidationSettings) -> Self {
103        self.validation = settings;
104        self
105    }
106}
107
108#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
109pub struct SchemaComparisonCompletenessSettings {
110    pub(crate) allow_root_unreachable_types_in_base_schema: bool,
111    pub(crate) allow_root_unreachable_types_in_compared_schema: bool,
112    /// This is only relevant in the "multiple named roots" mode
113    pub(crate) allow_compared_to_have_more_root_types: bool,
114}
115
116impl SchemaComparisonCompletenessSettings {
117    pub const fn allow_type_roots_not_to_cover_schema() -> Self {
118        Self {
119            allow_root_unreachable_types_in_base_schema: true,
120            allow_root_unreachable_types_in_compared_schema: true,
121            allow_compared_to_have_more_root_types: true,
122        }
123    }
124
125    pub const fn enforce_type_roots_cover_schema_allow_new_root_types() -> Self {
126        Self {
127            allow_root_unreachable_types_in_base_schema: false,
128            allow_root_unreachable_types_in_compared_schema: false,
129            allow_compared_to_have_more_root_types: true,
130        }
131    }
132
133    pub const fn enforce_type_roots_cover_schema_disallow_new_root_types() -> Self {
134        Self {
135            allow_root_unreachable_types_in_base_schema: false,
136            allow_root_unreachable_types_in_compared_schema: false,
137            allow_compared_to_have_more_root_types: false,
138        }
139    }
140
141    pub const fn with_allow_root_unreachable_types_in_base_schema(mut self) -> Self {
142        self.allow_root_unreachable_types_in_base_schema = true;
143        self
144    }
145
146    pub const fn with_dont_allow_root_unreachable_types_in_base_schema(mut self) -> Self {
147        self.allow_root_unreachable_types_in_base_schema = false;
148        self
149    }
150
151    pub const fn with_allow_root_unreachable_types_in_compared_schema(mut self) -> Self {
152        self.allow_root_unreachable_types_in_compared_schema = true;
153        self
154    }
155
156    pub const fn with_dont_allow_root_unreachable_types_in_compared_schema(mut self) -> Self {
157        self.allow_root_unreachable_types_in_compared_schema = false;
158        self
159    }
160
161    pub const fn with_allow_compared_to_have_more_root_types(mut self) -> Self {
162        self.allow_compared_to_have_more_root_types = true;
163        self
164    }
165
166    pub const fn with_dont_allow_compared_to_have_more_root_types(mut self) -> Self {
167        self.allow_compared_to_have_more_root_types = false;
168        self
169    }
170}
171
172#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
173pub struct SchemaComparisonStructureSettings {
174    pub(crate) allow_new_enum_variants: bool,
175    pub(crate) allow_replacing_with_any: bool,
176}
177
178impl SchemaComparisonStructureSettings {
179    pub const fn require_identical_structure() -> Self {
180        Self {
181            allow_new_enum_variants: false,
182            allow_replacing_with_any: false,
183        }
184    }
185
186    pub const fn allow_extension() -> Self {
187        Self {
188            allow_new_enum_variants: true,
189            allow_replacing_with_any: true,
190        }
191    }
192}
193
194#[derive(Debug, Copy, Clone, PartialEq, Eq)]
195pub struct SchemaComparisonMetadataSettings {
196    pub(crate) type_name_changes: NameChangeRule,
197    pub(crate) field_name_changes: NameChangeRule,
198    pub(crate) variant_name_changes: NameChangeRule,
199}
200
201impl SchemaComparisonMetadataSettings {
202    pub const fn require_identical_metadata() -> Self {
203        Self {
204            type_name_changes: NameChangeRule::equality(),
205            field_name_changes: NameChangeRule::equality(),
206            variant_name_changes: NameChangeRule::equality(),
207        }
208    }
209
210    pub const fn allow_adding_names() -> Self {
211        Self {
212            type_name_changes: NameChangeRule::AllowAddingNames,
213            field_name_changes: NameChangeRule::AllowAddingNames,
214            variant_name_changes: NameChangeRule::AllowAddingNames,
215        }
216    }
217
218    pub const fn allow_all_changes() -> Self {
219        Self {
220            type_name_changes: NameChangeRule::AllowAllChanges,
221            field_name_changes: NameChangeRule::AllowAllChanges,
222            variant_name_changes: NameChangeRule::AllowAllChanges,
223        }
224    }
225
226    pub const fn with_type_name_changes(mut self, type_name_changes: NameChangeRule) -> Self {
227        self.type_name_changes = type_name_changes;
228        self
229    }
230    pub const fn with_field_name_changes(mut self, field_name_changes: NameChangeRule) -> Self {
231        self.field_name_changes = field_name_changes;
232        self
233    }
234    pub const fn with_variant_name_changes(mut self, variant_name_changes: NameChangeRule) -> Self {
235        self.variant_name_changes = variant_name_changes;
236        self
237    }
238
239    pub(crate) fn checks_required(&self) -> bool {
240        let everything_allowed = self.type_name_changes == NameChangeRule::AllowAllChanges
241            && self.field_name_changes == NameChangeRule::AllowAllChanges
242            && self.variant_name_changes == NameChangeRule::AllowAllChanges;
243        !everything_allowed
244    }
245}
246
247#[derive(Debug, Copy, Clone, PartialEq, Eq)]
248pub enum NameChangeRule {
249    DisallowAllChanges,
250    AllowAddingNames,
251    AllowAllChanges,
252}
253
254impl NameChangeRule {
255    pub const fn equality() -> Self {
256        Self::DisallowAllChanges
257    }
258}
259
260#[derive(Debug, Copy, Clone, PartialEq, Eq)]
261pub struct SchemaComparisonValidationSettings {
262    pub(crate) allow_validation_weakening: bool,
263}
264
265impl SchemaComparisonValidationSettings {
266    pub const fn require_identical_validation() -> Self {
267        Self {
268            allow_validation_weakening: false,
269        }
270    }
271
272    pub const fn allow_weakening() -> Self {
273        Self {
274            allow_validation_weakening: true,
275        }
276    }
277}