Skip to content

Commit 9ef7b4c

Browse files
committed
chore: Naive path tracking
Signed-off-by: Dmitry Dygalo <dmitry@dygalo.dev>
1 parent a030dcb commit 9ef7b4c

5 files changed

Lines changed: 39 additions & 12 deletions

File tree

src/error.rs

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,20 +19,40 @@ impl error::Error for SchemaError {}
1919
#[derive(Debug)]
2020
pub struct ValidationError {
2121
message: String,
22+
location: Vec<String>,
2223
}
2324

2425
impl ValidationError {
2526
/// Create new validation error.
26-
pub fn new(message: impl Into<String>) -> Self {
27+
pub fn new(
28+
message: impl Into<String>,
29+
location: impl Iterator<Item = impl Into<String>>,
30+
) -> Self {
2731
Self {
2832
message: message.into(),
33+
location: location.map(Into::into).collect(),
2934
}
3035
}
36+
/// JSON Pointer to the location of the error.
37+
pub fn location_pointer(&self) -> String {
38+
let mut pointer = String::new();
39+
for segment in &self.location {
40+
pointer.push('/');
41+
pointer.push_str(segment);
42+
}
43+
pointer
44+
}
3145
}
3246

3347
impl fmt::Display for ValidationError {
3448
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
35-
f.write_str(&self.message)
49+
f.write_str(&self.message)?;
50+
f.write_str(" at ")?;
51+
for segment in self.location.iter() {
52+
f.write_str("/")?;
53+
f.write_str(segment)?;
54+
}
55+
Ok(())
3656
}
3757
}
3858

src/keywords/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,5 @@ pub(crate) use properties::Properties;
99
pub(crate) use type_::Type;
1010

1111
pub(crate) trait Node {
12-
fn validate(&self, instance: &Value) -> Result<(), ValidationError>;
12+
fn validate(&self, instance: &Value, path: Vec<&str>) -> Result<(), ValidationError>;
1313
}

src/keywords/properties.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,13 @@ impl Properties {
2020
}
2121

2222
impl Node for Properties {
23-
fn validate(&self, instance: &Value) -> Result<(), ValidationError> {
23+
fn validate(&self, instance: &Value, path: Vec<&str>) -> Result<(), ValidationError> {
2424
if let Value::Object(object) = instance {
2525
for (key, value) in &self.properties {
26-
if let Some(instance) = object.get(key) {
27-
value.validate(instance)?;
26+
if let Some((key, instance)) = object.get_key_value(key) {
27+
let mut path = path.clone();
28+
path.push(key);
29+
value.validate(instance, path)?;
2830
}
2931
}
3032
}

src/keywords/type_.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ impl Type {
2525
}
2626

2727
impl Node for Type {
28-
fn validate(&self, instance: &Value) -> Result<(), ValidationError> {
28+
fn validate(&self, instance: &Value, path: Vec<&str>) -> Result<(), ValidationError> {
2929
match (self, instance) {
3030
(Type::Array, Value::Array(_))
3131
| (Type::Null, Value::Null)
@@ -34,9 +34,10 @@ impl Node for Type {
3434
| (Type::Object, Value::Object(_))
3535
| (Type::String, Value::String(_)) => Ok(()),
3636
(Type::Integer, Value::Number(n)) if n.is_i64() || n.is_u64() => Ok(()),
37-
_ => Err(ValidationError::new(format!(
38-
"{instance} is not of type '{self}'"
39-
))),
37+
_ => Err(ValidationError::new(
38+
format!("{instance} is not of type '{self}'"),
39+
path.into_iter(),
40+
)),
4041
}
4142
}
4243
}

src/lib.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ impl Validator {
116116
///
117117
/// Returns `ValidationError` if the input instance is not valid under the given validator.
118118
pub fn validate(&self, instance: &Value) -> Result<(), ValidationError> {
119-
self.node.validate(instance)
119+
self.node.validate(instance, vec![])
120120
}
121121
}
122122

@@ -154,6 +154,10 @@ mod tests {
154154
}
155155
});
156156
let error = validate(&instance, &json!({})).expect_err("Should fail");
157-
assert_eq!(error.to_string(), "1 is not of type 'string'");
157+
assert_eq!(
158+
error.to_string(),
159+
"1 is not of type 'string' at /inner/inner/inner/inner/another"
160+
);
161+
assert_eq!(instance.pointer(&error.location_pointer()), Some(&json!(1)));
158162
}
159163
}

0 commit comments

Comments
 (0)