Skip to content

Commit 259b70a

Browse files
authored
ref: Rewrite the crate and add support for references (#10)
1 parent e94e30b commit 259b70a

File tree

5 files changed

+588
-470
lines changed

5 files changed

+588
-470
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ path = "src/bin/main.rs"
2121
[dependencies]
2222
anyhow = { version = "1.0.70", optional = true }
2323
clap = { version = "4.1.13", features = ["std", "derive"], default_features = false, optional = true }
24-
jsonref = "0.4.0"
24+
schemars = "0.8.12"
2525
serde = "1.0.158"
2626
serde_json = "1.0.94"
2727
thiserror = "1.0.40"

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,11 @@ assert_eq!(
3535
vec![
3636
Change {
3737
path: "".to_owned(),
38-
change: ChangeKind::TypeRemove { removed: SimpleJsonSchemaType::String }
38+
change: ChangeKind::TypeRemove { removed: JsonSchemaType::String }
3939
},
4040
Change {
4141
path: "".to_owned(),
42-
change: ChangeKind::TypeAdd { added: SimpleJsonSchemaType::Boolean }
42+
change: ChangeKind::TypeAdd { added: JsonSchemaType::Boolean }
4343
}
4444
]
4545
);

src/diff_walker.rs

Lines changed: 372 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,372 @@
1+
use std::collections::BTreeSet;
2+
3+
use schemars::schema::{InstanceType, RootSchema, Schema, SchemaObject, SingleOrVec};
4+
use serde_json::Value;
5+
6+
use crate::{Change, ChangeKind, Error, JsonSchemaType};
7+
8+
pub struct DiffWalker {
9+
pub changes: Vec<Change>,
10+
pub lhs_root: RootSchema,
11+
pub rhs_root: RootSchema,
12+
}
13+
14+
impl DiffWalker {
15+
fn diff_any_of(
16+
&mut self,
17+
json_path: &str,
18+
lhs: &mut SchemaObject,
19+
rhs: &mut SchemaObject,
20+
) -> Result<(), Error> {
21+
// hack to get a stable order for anyOf. serde_json::Value does not impl Hash or Ord, so we
22+
// can't use a set.
23+
if let (Some(lhs_any_of), Some(rhs_any_of)) =
24+
(&mut lhs.subschemas().any_of, &mut rhs.subschemas().any_of)
25+
{
26+
lhs_any_of.sort_by_cached_key(|x| format!("{x:?}"));
27+
rhs_any_of.sort_by_cached_key(|x| format!("{x:?}"));
28+
29+
for (i, (lhs_inner, rhs_inner)) in
30+
lhs_any_of.iter_mut().zip(rhs_any_of.iter_mut()).enumerate()
31+
{
32+
let new_path = format!("{json_path}.<anyOf:{i}>");
33+
self.diff(
34+
&new_path,
35+
&mut lhs_inner.clone().into_object(),
36+
&mut rhs_inner.clone().into_object(),
37+
)?;
38+
}
39+
}
40+
41+
Ok(())
42+
}
43+
44+
fn diff_instance_types(
45+
&mut self,
46+
json_path: &str,
47+
lhs: &mut SchemaObject,
48+
rhs: &mut SchemaObject,
49+
) {
50+
let lhs_ty = lhs.effective_type().into_set();
51+
let rhs_ty = rhs.effective_type().into_set();
52+
53+
for removed in lhs_ty.difference(&rhs_ty) {
54+
self.changes.push(Change {
55+
path: json_path.to_owned(),
56+
change: ChangeKind::TypeRemove {
57+
removed: removed.clone(),
58+
},
59+
});
60+
}
61+
62+
for added in rhs_ty.difference(&lhs_ty) {
63+
self.changes.push(Change {
64+
path: json_path.to_owned(),
65+
change: ChangeKind::TypeAdd {
66+
added: added.clone(),
67+
},
68+
});
69+
}
70+
}
71+
72+
fn diff_properties(
73+
&mut self,
74+
json_path: &str,
75+
lhs: &mut SchemaObject,
76+
rhs: &mut SchemaObject,
77+
) -> Result<(), Error> {
78+
let lhs_props: BTreeSet<_> = lhs.object().properties.keys().cloned().collect();
79+
let rhs_props: BTreeSet<_> = rhs.object().properties.keys().cloned().collect();
80+
81+
let lhs_additional_properties = lhs
82+
.object()
83+
.additional_properties
84+
.as_ref()
85+
.map_or(true, |x| x.clone().into_object().is_true());
86+
87+
for removed in lhs_props.difference(&rhs_props) {
88+
self.changes.push(Change {
89+
path: json_path.to_owned(),
90+
change: ChangeKind::PropertyRemove {
91+
lhs_additional_properties,
92+
removed: removed.clone(),
93+
},
94+
});
95+
}
96+
97+
for added in rhs_props.difference(&lhs_props) {
98+
self.changes.push(Change {
99+
path: json_path.to_owned(),
100+
change: ChangeKind::PropertyAdd {
101+
lhs_additional_properties,
102+
added: added.clone(),
103+
},
104+
});
105+
}
106+
107+
for common in rhs_props.intersection(&lhs_props) {
108+
let lhs_child = lhs.object().properties.get(common.as_str()).unwrap();
109+
let rhs_child = rhs.object().properties.get(common.as_str()).unwrap();
110+
111+
let new_path = format!("{json_path}.{common}");
112+
self.diff(
113+
&new_path,
114+
&mut lhs_child.clone().into_object(),
115+
&mut rhs_child.clone().into_object(),
116+
)?;
117+
}
118+
119+
Ok(())
120+
}
121+
122+
fn diff_additional_properties(
123+
&mut self,
124+
json_path: &str,
125+
lhs: &mut SchemaObject,
126+
rhs: &mut SchemaObject,
127+
) -> Result<(), Error> {
128+
if let (Some(ref lhs_additional_properties), Some(ref rhs_additional_properties)) = (
129+
&lhs.object().additional_properties,
130+
&rhs.object().additional_properties,
131+
) {
132+
if rhs_additional_properties != lhs_additional_properties {
133+
let new_path = format!("{json_path}.<additionalProperties>");
134+
135+
self.diff(
136+
&new_path,
137+
&mut lhs_additional_properties.clone().into_object(),
138+
&mut rhs_additional_properties.clone().into_object(),
139+
)?;
140+
}
141+
}
142+
143+
Ok(())
144+
}
145+
146+
fn diff_array_items(
147+
&mut self,
148+
json_path: &str,
149+
lhs: &mut SchemaObject,
150+
rhs: &mut SchemaObject,
151+
) -> Result<(), Error> {
152+
match (&lhs.array().items, &rhs.array().items) {
153+
(Some(SingleOrVec::Vec(lhs_items)), Some(SingleOrVec::Vec(rhs_items))) => {
154+
if lhs_items.len() != rhs_items.len() {
155+
self.changes.push(Change {
156+
path: json_path.to_owned(),
157+
change: ChangeKind::TupleChange {
158+
new_length: rhs_items.len(),
159+
},
160+
});
161+
}
162+
163+
for (i, (lhs_inner, rhs_inner)) in
164+
lhs_items.iter().zip(rhs_items.iter()).enumerate()
165+
{
166+
let new_path = format!("{json_path}.{i}");
167+
self.diff(
168+
&new_path,
169+
&mut lhs_inner.clone().into_object(),
170+
&mut rhs_inner.clone().into_object(),
171+
)?;
172+
}
173+
}
174+
(Some(SingleOrVec::Single(lhs_inner)), Some(SingleOrVec::Single(rhs_inner))) => {
175+
let new_path = format!("{json_path}.?");
176+
self.diff(
177+
&new_path,
178+
&mut lhs_inner.clone().into_object(),
179+
&mut rhs_inner.clone().into_object(),
180+
)?;
181+
}
182+
(Some(SingleOrVec::Single(lhs_inner)), Some(SingleOrVec::Vec(rhs_items))) => {
183+
self.changes.push(Change {
184+
path: json_path.to_owned(),
185+
change: ChangeKind::ArrayToTuple {
186+
new_length: rhs_items.len(),
187+
},
188+
});
189+
190+
for (i, rhs_inner) in rhs_items.iter().enumerate() {
191+
let new_path = format!("{json_path}.{i}");
192+
self.diff(
193+
&new_path,
194+
&mut lhs_inner.clone().into_object(),
195+
&mut rhs_inner.clone().into_object(),
196+
)?;
197+
}
198+
}
199+
(Some(SingleOrVec::Vec(lhs_items)), Some(SingleOrVec::Single(rhs_inner))) => {
200+
self.changes.push(Change {
201+
path: json_path.to_owned(),
202+
change: ChangeKind::TupleToArray {
203+
old_length: lhs_items.len(),
204+
},
205+
});
206+
207+
for (i, lhs_inner) in lhs_items.iter().enumerate() {
208+
let new_path = format!("{json_path}.{i}");
209+
self.diff(
210+
&new_path,
211+
&mut lhs_inner.clone().into_object(),
212+
&mut rhs_inner.clone().into_object(),
213+
)?;
214+
}
215+
}
216+
(None, None) => (),
217+
218+
#[cfg(not(test))]
219+
_ => (),
220+
#[cfg(test)]
221+
(x, y) => todo!("{:?} {:?}", x, y),
222+
}
223+
224+
Ok(())
225+
}
226+
227+
fn resolve_ref<'a>(root_schema: &'a RootSchema, reference: &str) -> Option<&'a Schema> {
228+
if let Some(definition_name) = reference.strip_prefix("#/definitions/") {
229+
let schema_object = root_schema.definitions.get(definition_name)?;
230+
Some(schema_object)
231+
} else {
232+
None
233+
}
234+
}
235+
236+
fn resolve_references(
237+
&mut self,
238+
lhs: &mut SchemaObject,
239+
rhs: &mut SchemaObject,
240+
) -> Result<(), Error> {
241+
if let Some(ref reference) = lhs.reference {
242+
if let Some(lhs_inner) = Self::resolve_ref(&self.lhs_root, reference) {
243+
*lhs = lhs_inner.clone().into_object();
244+
}
245+
}
246+
247+
if let Some(ref reference) = rhs.reference {
248+
if let Some(rhs_inner) = Self::resolve_ref(&self.rhs_root, reference) {
249+
*rhs = rhs_inner.clone().into_object();
250+
}
251+
}
252+
253+
Ok(())
254+
}
255+
256+
pub fn diff(
257+
&mut self,
258+
json_path: &str,
259+
lhs: &mut SchemaObject,
260+
rhs: &mut SchemaObject,
261+
) -> Result<(), Error> {
262+
self.resolve_references(lhs, rhs)?;
263+
self.diff_any_of(json_path, lhs, rhs)?;
264+
self.diff_instance_types(json_path, lhs, rhs);
265+
self.diff_properties(json_path, lhs, rhs)?;
266+
self.diff_additional_properties(json_path, lhs, rhs)?;
267+
self.diff_array_items(json_path, lhs, rhs)?;
268+
Ok(())
269+
}
270+
}
271+
272+
trait JsonSchemaExt {
273+
fn is_true(&self) -> bool;
274+
fn effective_type(&mut self) -> InternalJsonSchemaType;
275+
}
276+
277+
impl JsonSchemaExt for SchemaObject {
278+
fn is_true(&self) -> bool {
279+
*self == SchemaObject::default()
280+
}
281+
282+
fn effective_type(&mut self) -> InternalJsonSchemaType {
283+
if let Some(ref ty) = self.instance_type {
284+
match ty {
285+
SingleOrVec::Single(ty) => schemars_to_own(**ty).into(),
286+
SingleOrVec::Vec(tys) => InternalJsonSchemaType::Multiple(
287+
tys.iter().copied().map(schemars_to_own).collect(),
288+
),
289+
}
290+
} else if let Some(ref constant) = self.const_value {
291+
serde_value_to_own(constant).into()
292+
} else if !self.object().properties.is_empty() {
293+
JsonSchemaType::Object.into()
294+
} else if self
295+
.subschemas()
296+
.not
297+
.as_ref()
298+
.map_or(false, |x| x.clone().into_object().is_true())
299+
{
300+
InternalJsonSchemaType::Never
301+
} else {
302+
InternalJsonSchemaType::Any
303+
}
304+
}
305+
}
306+
307+
#[derive(Clone, Ord, Eq, PartialEq, PartialOrd, Debug)]
308+
enum InternalJsonSchemaType {
309+
Simple(JsonSchemaType),
310+
Any,
311+
Never,
312+
Multiple(Vec<JsonSchemaType>),
313+
}
314+
315+
impl From<JsonSchemaType> for InternalJsonSchemaType {
316+
fn from(other: JsonSchemaType) -> Self {
317+
InternalJsonSchemaType::Simple(other)
318+
}
319+
}
320+
321+
impl InternalJsonSchemaType {
322+
fn into_set(self) -> BTreeSet<JsonSchemaType> {
323+
self.explode().into_iter().collect()
324+
}
325+
326+
fn explode(self) -> Vec<JsonSchemaType> {
327+
match self {
328+
Self::Simple(JsonSchemaType::Number) => {
329+
vec![JsonSchemaType::Integer, JsonSchemaType::Number]
330+
}
331+
Self::Any => vec![
332+
JsonSchemaType::String,
333+
JsonSchemaType::Number,
334+
JsonSchemaType::Integer,
335+
JsonSchemaType::Object,
336+
JsonSchemaType::Array,
337+
JsonSchemaType::Boolean,
338+
JsonSchemaType::Null,
339+
],
340+
Self::Never => vec![],
341+
Self::Simple(x) => vec![x],
342+
Self::Multiple(xs) => xs
343+
.into_iter()
344+
.map(InternalJsonSchemaType::from)
345+
.flat_map(Self::explode)
346+
.collect(),
347+
}
348+
}
349+
}
350+
351+
fn serde_value_to_own(val: &Value) -> JsonSchemaType {
352+
match val {
353+
Value::Number(_) => JsonSchemaType::Number,
354+
Value::Null => JsonSchemaType::Null,
355+
Value::String(_) => JsonSchemaType::String,
356+
Value::Bool(_) => JsonSchemaType::Boolean,
357+
Value::Array(_) => JsonSchemaType::Array,
358+
Value::Object(_) => JsonSchemaType::Object,
359+
}
360+
}
361+
362+
fn schemars_to_own(other: InstanceType) -> JsonSchemaType {
363+
match other {
364+
InstanceType::Null => JsonSchemaType::Null,
365+
InstanceType::Boolean => JsonSchemaType::Boolean,
366+
InstanceType::Object => JsonSchemaType::Object,
367+
InstanceType::Array => JsonSchemaType::Array,
368+
InstanceType::Number => JsonSchemaType::Number,
369+
InstanceType::String => JsonSchemaType::String,
370+
InstanceType::Integer => JsonSchemaType::Integer,
371+
}
372+
}

0 commit comments

Comments
 (0)