What's the best way to implement a validation that checks if two fields are the same? #714
-
Hi, AFAICT, neither the keyword nor the format validators have access to the entire input, so I'm wondering what the best way to do this is. For example, given this input:
I'd like to validate that Thanks! |
Beta Was this translation helpful? Give feedback.
Replies: 2 comments 1 reply
-
Hi @fgsch I think you could make a custom keyword for this: {
"type": "object",
"properties": {...}
"same-values": ["foo", "qux"]
} Something like this would work: use jsonschema::{
paths::{LazyLocation, Location},
Keyword, ValidationError,
};
use serde_json::{json, Map, Value};
struct SameValueValidator {
keys: Vec<String>
};
impl Keyword for SameValueValidator {
fn validate<'i>(
&self,
instance: &'i Value,
location: &LazyLocation,
) -> Result<(), ValidationError<'i>> {
// TODO: Some better checking with what exact values are different?
if !self.is_valid(instance) {
Err(ValidationError::custom(
Location::new(),
location.into(),
instance,
"Values are not the same",
))
} else {
Ok(())
}
}
fn is_valid(&self, instance: &Value) -> bool {
// TODO: it could be with a better type checking
let first = instance.get(&self.keys[0]);
self.keys.iter().all(|key| instance.get(key) == first)
}
}
fn same_value_validator_factory<'a>(
_: &'a Map<String, Value>,
value: &'a Value,
path: Location,
) -> Result<Box<dyn Keyword>, ValidationError<'a>> {
if let Value::Array(values) = value {
// TODO: Check that there are at least 2 values
let keys = Vec::new();
for value in values {
if Value::String(value) = value {
keys.push(value.clone())
} else {
todo!("Another validation error on non-string value")
}
}
Ok(Box::new(SameValueValidator {keys}))
} else {
Err(ValidationError::custom(
Location::new(),
path,
value,
"The 'same-values' keyword must be an array",
))
}
} Please, let me know if it is sufficient for your usecase. Python bindings don't have an API for defining keywords just yet, but there are no blockers to add it. |
Beta Was this translation helpful? Give feedback.
-
Hi, IIUC, the trick here is using the keyword at the root level. That way, you have access to the whole input. I initially tried:
But I guess it is not possible this way (and maybe it doesn't make sense?). This should be sufficient for my case. I'm using Rust so the lack of Python support is not a problem for me. |
Beta Was this translation helpful? Give feedback.
Hi @fgsch
I think you could make a custom keyword for this:
Something like this would work: