Skip to content

Commit 6a6166f

Browse files
committed
feat: support custom ids and $defs from 2020-12
1 parent 1a3229a commit 6a6166f

File tree

3 files changed

+158
-12
lines changed

3 files changed

+158
-12
lines changed

src/diff_walker.rs

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,27 @@ use schemars::schema::{
77
};
88
use serde_json::Value;
99

10+
use crate::resolver::Resolver;
1011
use crate::{Change, ChangeKind, Error, JsonSchemaType, Range};
1112

1213
pub struct DiffWalker<F: FnMut(Change)> {
1314
pub cb: F,
1415
pub lhs_root: RootSchema,
1516
pub rhs_root: RootSchema,
17+
lhs_resolver: Resolver,
18+
rhs_resolver: Resolver,
1619
}
1720

1821
impl<F: FnMut(Change)> DiffWalker<F> {
1922
pub fn new(cb: F, lhs_root: RootSchema, rhs_root: RootSchema) -> Self {
23+
let lhs_resolver = Resolver::for_schema(&lhs_root);
24+
let rhs_resolver = Resolver::for_schema(&rhs_root);
2025
Self {
2126
cb,
2227
lhs_root,
2328
rhs_root,
29+
lhs_resolver,
30+
rhs_resolver,
2431
}
2532
}
2633

@@ -349,28 +356,19 @@ impl<F: FnMut(Change)> DiffWalker<F> {
349356
Ok(())
350357
}
351358

352-
fn resolve_ref<'a>(root_schema: &'a RootSchema, reference: &str) -> Option<&'a Schema> {
353-
if let Some(definition_name) = reference.strip_prefix("#/definitions/") {
354-
let schema_object = root_schema.definitions.get(definition_name)?;
355-
Some(schema_object)
356-
} else {
357-
None
358-
}
359-
}
360-
361359
fn resolve_references(
362-
&mut self,
360+
&self,
363361
lhs: &mut SchemaObject,
364362
rhs: &mut SchemaObject,
365363
) -> Result<(), Error> {
366364
if let Some(ref reference) = lhs.reference {
367-
if let Some(lhs_inner) = Self::resolve_ref(&self.lhs_root, reference) {
365+
if let Some(lhs_inner) = self.lhs_resolver.resolve(&self.lhs_root, reference) {
368366
*lhs = lhs_inner.clone().into_object();
369367
}
370368
}
371369

372370
if let Some(ref reference) = rhs.reference {
373-
if let Some(rhs_inner) = Self::resolve_ref(&self.rhs_root, reference) {
371+
if let Some(rhs_inner) = self.rhs_resolver.resolve(&self.rhs_root, reference) {
374372
*rhs = rhs_inner.clone().into_object();
375373
}
376374
}

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use serde_json::Value;
66
use thiserror::Error;
77

88
mod diff_walker;
9+
mod resolver;
910
mod types;
1011

1112
pub use types::*;

src/resolver.rs

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
use std::collections::BTreeMap;
2+
3+
use schemars::schema::{RootSchema, Schema, SchemaObject};
4+
5+
pub struct Resolver {
6+
ref_lookup: BTreeMap<String, String>,
7+
}
8+
9+
impl Resolver {
10+
pub fn for_schema(root: &RootSchema) -> Self {
11+
let mut ref_lookup = BTreeMap::new();
12+
13+
for (key, schema) in &root.definitions {
14+
if let Some(id) = schema.get_schema_id() {
15+
ref_lookup.insert(id.to_owned(), key.clone());
16+
}
17+
18+
if let Some(root_id) = root.schema.get_schema_id() {
19+
ref_lookup.insert(format!("{root_id}#/definitions/{key}"), key.clone());
20+
ref_lookup.insert(format!("{root_id}#/$defs/{key}"), key.clone());
21+
}
22+
23+
ref_lookup.insert(format!("#/definitions/{key}"), key.clone());
24+
ref_lookup.insert(format!("#/$defs/{key}"), key.clone());
25+
}
26+
27+
Self { ref_lookup }
28+
}
29+
30+
/// Resolves a reference.
31+
///
32+
/// `root` must be the same schema that was used to construct the resolver.
33+
/// This is not checked.
34+
pub fn resolve<'a>(&self, root: &'a RootSchema, reference: &str) -> Option<&'a Schema> {
35+
let key = self.ref_lookup.get(reference)?;
36+
root.definitions.get(key)
37+
}
38+
}
39+
40+
trait MayHaveSchemaId {
41+
fn get_schema_id(&self) -> Option<&str>;
42+
}
43+
44+
impl MayHaveSchemaId for SchemaObject {
45+
fn get_schema_id(&self) -> Option<&str> {
46+
self.metadata
47+
.as_ref()
48+
.and_then(|m| m.id.as_ref())
49+
.map(|id| id.as_str())
50+
}
51+
}
52+
53+
impl MayHaveSchemaId for Schema {
54+
fn get_schema_id(&self) -> Option<&str> {
55+
match self {
56+
Schema::Object(schema_obj) => schema_obj.get_schema_id(),
57+
Schema::Bool(_) => None,
58+
}
59+
}
60+
}
61+
62+
#[cfg(test)]
63+
mod tests {
64+
use super::*;
65+
66+
#[test]
67+
fn draft7_definitions() {
68+
let root: RootSchema = serde_json::from_str(
69+
r#"{
70+
"definitions": {
71+
"A": {}
72+
}
73+
}"#,
74+
)
75+
.unwrap();
76+
let resolver = Resolver::for_schema(&root);
77+
78+
let resolved = resolver.resolve(&root, "#/definitions/A");
79+
assert!(resolved.is_some());
80+
81+
let resolved = resolver.resolve(&root, "#/definitions/not-there");
82+
assert!(resolved.is_none());
83+
}
84+
85+
#[test]
86+
fn draft7_root_has_id() {
87+
let root: RootSchema = serde_json::from_str(
88+
r#"{
89+
"$id": "urn:uuid:e773a2e8-d746-4dc6-9480-0bba5ff33504",
90+
"definitions": {
91+
"A": {}
92+
}
93+
}"#,
94+
)
95+
.unwrap();
96+
let resolver = Resolver::for_schema(&root);
97+
98+
let resolved = resolver.resolve(&root, "#/definitions/A");
99+
assert!(resolved.is_some());
100+
let resolved = resolver.resolve(
101+
&root,
102+
"urn:uuid:e773a2e8-d746-4dc6-9480-0bba5ff33504#/definitions/A",
103+
);
104+
assert!(resolved.is_some());
105+
}
106+
107+
#[test]
108+
fn draft7_definition_has_id() {
109+
let root: RootSchema = serde_json::from_str(
110+
r#"{
111+
"definitions": {
112+
"A": {
113+
"$id": "some-id"
114+
}
115+
}
116+
}"#,
117+
)
118+
.unwrap();
119+
let resolver = Resolver::for_schema(&root);
120+
121+
let resolved = resolver.resolve(&root, "some-id");
122+
assert!(resolved.is_some());
123+
assert_eq!(resolved, resolver.resolve(&root, "#/definitions/A"))
124+
}
125+
126+
#[test]
127+
fn draft2020_12_defs() {
128+
let root: RootSchema = serde_json::from_str(
129+
r#"{
130+
"$defs": {
131+
"A": {
132+
"$id": "some-id"
133+
}
134+
}
135+
}"#,
136+
)
137+
.unwrap();
138+
let resolver = Resolver::for_schema(&root);
139+
140+
let resolved = resolver.resolve(&root, "#/$defs/A");
141+
assert!(resolved.is_some());
142+
assert_eq!(resolved, resolver.resolve(&root, "some-id"));
143+
144+
let resolved = resolver.resolve(&root, "#/$defs/not-there");
145+
assert!(resolved.is_none());
146+
}
147+
}

0 commit comments

Comments
 (0)