Skip to content

Commit c695a3d

Browse files
Fix(validation): Ensure coercion on schemas with unresolved $ref (#25)
Co-authored-by: gmuloc <gmulocher@arista.com>
1 parent 6bc774d commit c695a3d

File tree

1 file changed

+111
-8
lines changed

1 file changed

+111
-8
lines changed

rust/validation/src/coercion.rs

Lines changed: 111 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22
// Use of this source code is governed by the Apache License 2.0
33
// that can be found in the LICENSE file.
44

5-
use avdschema::{any::AnySchema, boolean::Bool, dict::Dict, int::Int, list::List, str::Str};
5+
use avdschema::delegate_anyschema_method;
6+
use avdschema::{
7+
any::AnySchema, boolean::Bool, dict::Dict, int::Int, list::List, resolve_ref, str::Str,
8+
};
69
use serde_json::Value;
710

811
use crate::{context::Context, feedback::CoercionNote};
@@ -19,9 +22,11 @@ where
1922
///
2023
/// TODO: Decide whether we should limit this to only coerce according to `convert_types`.
2124
fn coerce(&self, input: &mut Value, ctx: &mut Context);
25+
fn coerce_ref(&self, input: &mut Value, ctx: &mut Context);
2226
}
2327
impl Coercion for Bool {
2428
fn coerce(&self, _input: &mut Value, _ctx: &mut Context) {}
29+
fn coerce_ref(&self, _input: &mut Value, _ctx: &mut Context) {}
2530
}
2631
impl Coercion for Dict {
2732
fn coerce(&self, input: &mut Value, ctx: &mut Context) {
@@ -49,6 +54,18 @@ impl Coercion for Dict {
4954
}
5055
}
5156
}
57+
self.coerce_ref(input, ctx);
58+
}
59+
60+
fn coerce_ref(&self, input: &mut Value, ctx: &mut Context) {
61+
if let Some(ref_) = self.base.schema_ref.as_ref() {
62+
// Ignoring not being able to resolve the schema.
63+
// Ignoring a wrong schema type at the ref. Since coercion is infallible.
64+
// TODO: What to do?
65+
if let Ok(AnySchema::Dict(ref_schema)) = resolve_ref(ref_, ctx.store) {
66+
ref_schema.coerce(input, ctx);
67+
}
68+
}
5269
}
5370
}
5471

@@ -97,6 +114,18 @@ impl Coercion for Int {
97114
if let Some(value) = value {
98115
_ = core::mem::replace(input, value.into());
99116
}
117+
self.coerce_ref(input, ctx);
118+
}
119+
120+
fn coerce_ref(&self, input: &mut Value, ctx: &mut Context) {
121+
if let Some(ref_) = self.base.schema_ref.as_ref() {
122+
// Ignoring not being able to resolve the schema.
123+
// Ignoring a wrong schema type at the ref. Since coercion is infallible.
124+
// TODO: What to do?
125+
if let Ok(AnySchema::Int(ref_schema)) = resolve_ref(ref_, ctx.store) {
126+
ref_schema.coerce(input, ctx);
127+
}
128+
}
100129
}
101130
}
102131
impl Coercion for List {
@@ -110,6 +139,18 @@ impl Coercion for List {
110139
ctx.state.path.pop();
111140
}
112141
}
142+
self.coerce_ref(input, ctx);
143+
}
144+
145+
fn coerce_ref(&self, input: &mut Value, ctx: &mut Context) {
146+
if let Some(ref_) = self.base.schema_ref.as_ref() {
147+
// Ignoring not being able to resolve the schema.
148+
// Ignoring a wrong schema type at the ref. Since coercion is infallible.
149+
// TODO: What to do?
150+
if let Ok(AnySchema::List(ref_schema)) = resolve_ref(ref_, ctx.store) {
151+
ref_schema.coerce(input, ctx);
152+
}
153+
}
113154
}
114155
}
115156
impl Coercion for Str {
@@ -163,16 +204,78 @@ impl Coercion for Str {
163204
if let Some(value) = value.as_deref() {
164205
_ = core::mem::replace(input, value.into());
165206
}
207+
self.coerce_ref(input, ctx);
208+
}
209+
210+
fn coerce_ref(&self, input: &mut Value, ctx: &mut Context) {
211+
if let Some(ref_) = self.base.schema_ref.as_ref() {
212+
// Ignoring not being able to resolve the schema.
213+
// Ignoring a wrong schema type at the ref. Since coercion is infallible.
214+
// TODO: What to do?
215+
if let Ok(AnySchema::Str(ref_schema)) = resolve_ref(ref_, ctx.store) {
216+
ref_schema.coerce(input, ctx);
217+
}
218+
}
166219
}
167220
}
168221
impl Coercion for AnySchema {
169222
fn coerce(&self, input: &mut Value, ctx: &mut Context) {
170-
match self {
171-
Self::Bool(schema) => schema.coerce(input, ctx),
172-
Self::Int(schema) => schema.coerce(input, ctx),
173-
Self::Str(schema) => schema.coerce(input, ctx),
174-
Self::List(schema) => schema.coerce(input, ctx),
175-
Self::Dict(schema) => schema.coerce(input, ctx),
176-
}
223+
delegate_anyschema_method!(self, coerce, input, ctx)
224+
}
225+
226+
fn coerce_ref(&self, input: &mut Value, ctx: &mut Context) {
227+
delegate_anyschema_method!(self, coerce_ref, input, ctx)
228+
}
229+
}
230+
231+
#[cfg(test)]
232+
mod tests {
233+
use avdschema::base::Base;
234+
use ordermap::OrderMap;
235+
236+
use super::*;
237+
use crate::Validation as _;
238+
use crate::context::{Configuration, Context};
239+
use crate::feedback::{CoercionNote, Feedback};
240+
use crate::validation::test_utils::get_test_store;
241+
242+
#[test]
243+
fn validate_coecion_with_ref_ok() {
244+
let schema = Dict {
245+
keys: Some(OrderMap::from_iter([(
246+
"foo".into(),
247+
Dict {
248+
base: Base {
249+
// Using ref to the root of the schema since such refs will not get resolved.
250+
schema_ref: Some("eos_cli_config_gen#".into()),
251+
..Default::default()
252+
},
253+
..Default::default()
254+
}
255+
.into(),
256+
)])),
257+
..Default::default()
258+
};
259+
let mut input = serde_json::json!({ "foo": {"key1": 123} });
260+
let store = get_test_store();
261+
let configuration = Configuration {
262+
return_coercion_infos: true,
263+
..Default::default()
264+
};
265+
let mut ctx = Context::new(&store, Some(&configuration));
266+
schema.coerce(&mut input, &mut ctx);
267+
schema.validate_value(&input, &mut ctx);
268+
assert!(ctx.result.errors.is_empty());
269+
assert_eq!(
270+
ctx.result.infos,
271+
vec![Feedback {
272+
path: vec!["foo".into(), "key1".into()].into(),
273+
issue: CoercionNote {
274+
found: 123.into(),
275+
made: "123".into()
276+
}
277+
.into()
278+
},]
279+
)
177280
}
178281
}

0 commit comments

Comments
 (0)