ferrin_schema/
validation.rs1use std::fmt;
9
10#[cfg(feature = "json-schema-validation")]
11use serde_json::Value;
12
13#[derive(Debug, Clone, PartialEq, Eq)]
15pub struct ValidationIssue {
16 pub path: String,
18 pub message: String,
20}
21
22#[derive(Debug, Clone, PartialEq, Eq, Default)]
24pub struct ValidationIssues {
25 pub issues: Vec<ValidationIssue>,
27}
28
29impl ValidationIssues {
30 #[must_use]
32 pub fn new(issues: Vec<ValidationIssue>) -> Self {
33 Self { issues }
34 }
35
36 #[must_use]
38 pub fn message(message: impl Into<String>) -> Self {
39 Self {
40 issues: vec![ValidationIssue {
41 path: String::new(),
42 message: message.into(),
43 }],
44 }
45 }
46
47 #[must_use]
49 pub fn is_empty(&self) -> bool {
50 self.issues.is_empty()
51 }
52}
53
54impl fmt::Display for ValidationIssues {
55 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
56 match self.issues.as_slice() {
57 [] => f.write_str("validation failed"),
58 [single] if single.path.is_empty() => f.write_str(&single.message),
59 [single] => write!(f, "{}: {}", single.path, single.message),
60 many => {
61 write!(f, "{} validation issues:", many.len())?;
62 for issue in many {
63 if issue.path.is_empty() {
64 write!(f, " [{}]", issue.message)?;
65 } else {
66 write!(f, " [{}: {}]", issue.path, issue.message)?;
67 }
68 }
69 Ok(())
70 }
71 }
72 }
73}
74
75impl std::error::Error for ValidationIssues {}
76
77#[cfg(feature = "json-schema-validation")]
79#[derive(Debug)]
80pub struct Validator {
81 inner: jsonschema::Validator,
82}
83
84#[cfg(feature = "json-schema-validation")]
85impl Validator {
86 pub fn compile(schema: &Value) -> Result<Self, crate::SchemaError> {
93 let options = if schema.get("$schema").is_some() {
94 jsonschema::options()
95 } else {
96 jsonschema::draft7::options()
97 };
98 let inner = options
99 .build(schema)
100 .map_err(|error| crate::SchemaError::InvalidSchema {
101 message: error.to_string(),
102 })?;
103 Ok(Self { inner })
104 }
105
106 #[must_use]
108 pub fn is_valid(&self, value: &Value) -> bool {
109 self.inner.is_valid(value)
110 }
111
112 #[must_use]
114 pub fn issues(&self, value: &Value) -> ValidationIssues {
115 let issues = self
116 .inner
117 .iter_errors(value)
118 .map(|error| ValidationIssue {
119 path: error.instance_path().to_string(),
120 message: error.to_string(),
121 })
122 .collect();
123 ValidationIssues::new(issues)
124 }
125
126 pub fn validate(&self, value: &Value) -> Result<(), ValidationIssues> {
133 let issues = self.issues(value);
134 if issues.is_empty() {
135 Ok(())
136 } else {
137 Err(issues)
138 }
139 }
140}