1use std::collections::HashMap;
4use std::fmt;
5use std::sync::Arc;
6use std::time::Duration;
7
8use serde_json::Value;
9
10use crate::dialect::Dialect;
11
12pub type DocumentResolver = Arc<dyn Fn(&str) -> Option<Value> + Send + Sync>;
14
15pub type FormatValidator = Arc<dyn Fn(&str) -> bool + Send + Sync>;
17
18#[derive(Clone)]
20pub struct CompileOptions {
21 pub default_dialect: Dialect,
23 pub assert_format: Option<bool>,
26 pub assert_format_in_legacy_drafts: bool,
28 pub assert_content: bool,
30 pub formats: HashMap<String, FormatValidator>,
32 pub resolve_document: Option<DocumentResolver>,
34 pub base_uri: Option<String>,
36 pub entry_point: Option<String>,
38 pub max_depth: u32,
40 pub pattern_match_limit: Option<Duration>,
51}
52
53pub const DEFAULT_PATTERN_MATCH_LIMIT: Duration = Duration::from_secs(1);
56
57impl Default for CompileOptions {
58 fn default() -> Self {
59 CompileOptions {
60 default_dialect: Dialect::Draft202012,
61 assert_format: None,
62 assert_format_in_legacy_drafts: false,
63 assert_content: true,
64 formats: HashMap::new(),
65 resolve_document: None,
66 base_uri: None,
67 entry_point: None,
68 max_depth: 128,
69 pattern_match_limit: Some(DEFAULT_PATTERN_MATCH_LIMIT),
70 }
71 }
72}
73
74impl fmt::Debug for CompileOptions {
75 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
76 f.debug_struct("CompileOptions")
77 .field("default_dialect", &self.default_dialect)
78 .field("assert_format", &self.assert_format)
79 .field("assert_format_in_legacy_drafts", &self.assert_format_in_legacy_drafts)
80 .field("assert_content", &self.assert_content)
81 .field("formats", &self.formats.keys().collect::<Vec<_>>())
82 .field("resolve_document", &self.resolve_document.is_some())
83 .field("base_uri", &self.base_uri)
84 .field("entry_point", &self.entry_point)
85 .field("max_depth", &self.max_depth)
86 .field("pattern_match_limit", &self.pattern_match_limit)
87 .finish()
88 }
89}
90
91#[derive(Clone, Debug, PartialEq, Eq)]
94pub struct SchemaCompilationError {
95 message: String,
96 kind: SchemaCompilationErrorKind,
97}
98
99#[derive(Clone, Debug, PartialEq, Eq)]
101#[non_exhaustive]
102pub enum SchemaCompilationErrorKind {
103 Other,
105 InvalidPattern,
107 PatternTooLarge(PatternTooLargeError),
110 PatternMatchLimit(PatternMatchLimitError),
113}
114
115impl SchemaCompilationError {
116 pub(crate) fn new(message: impl Into<String>) -> Self {
117 SchemaCompilationError { message: message.into(), kind: SchemaCompilationErrorKind::Other }
118 }
119
120 pub(crate) fn of_kind(message: impl Into<String>, kind: SchemaCompilationErrorKind) -> Self {
121 SchemaCompilationError { message: message.into(), kind }
122 }
123
124 pub fn message(&self) -> &str {
126 &self.message
127 }
128
129 pub fn kind(&self) -> &SchemaCompilationErrorKind {
131 &self.kind
132 }
133}
134
135impl fmt::Display for SchemaCompilationError {
136 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
137 f.write_str(&self.message)
138 }
139}
140
141impl std::error::Error for SchemaCompilationError {}
142
143#[derive(Clone, Debug, PartialEq, Eq)]
145pub struct PatternTooLargeError {
146 pattern: String,
147 keyword: &'static str,
148 limit: &'static str,
149 value: usize,
150 reason: String,
151}
152
153impl PatternTooLargeError {
154 pub(crate) fn new(pattern: &str, keyword: &'static str, limit: &'static str, value: usize, reason: String) -> Self {
155 PatternTooLargeError { pattern: pattern.to_string(), keyword, limit, value, reason }
156 }
157
158 pub fn pattern(&self) -> &str {
160 &self.pattern
161 }
162
163 pub fn keyword(&self) -> &str {
165 self.keyword
166 }
167
168 pub fn limit(&self) -> &str {
171 self.limit
172 }
173
174 pub fn value(&self) -> usize {
176 self.value
177 }
178}
179
180impl fmt::Display for PatternTooLargeError {
181 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
182 write!(
183 f,
184 "The regular expression {} in {} is too large to read as an ECMA-262 regular expression: {}.",
185 crate::pattern::shown_pattern(&self.pattern),
186 self.keyword,
187 self.reason
188 )
189 }
190}
191
192impl std::error::Error for PatternTooLargeError {}
193
194#[derive(Clone, Debug, PartialEq, Eq)]
197pub struct PatternMatchLimitError {
198 pattern: String,
199 limit: Duration,
200 memory: bool,
201}
202
203impl PatternMatchLimitError {
204 pub(crate) fn new(pattern: &str, limit: Duration, memory: bool) -> Self {
205 PatternMatchLimitError { pattern: pattern.to_string(), limit, memory }
206 }
207
208 pub fn pattern(&self) -> &str {
210 &self.pattern
211 }
212
213 pub fn limit(&self) -> Duration {
215 self.limit
216 }
217
218 pub fn is_memory(&self) -> bool {
221 self.memory
222 }
223}
224
225impl fmt::Display for PatternMatchLimitError {
226 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
227 let pattern = crate::pattern::shown_pattern(&self.pattern);
228 if self.memory {
229 write!(
230 f,
231 "A match of the regular expression {pattern} was abandoned: it needed more than the limit of \
232 1,048,576 backtracking entries."
233 )
234 } else {
235 write!(
236 f,
237 "A match of the regular expression {pattern} was abandoned: it took longer than the limit of {:?}.",
238 self.limit
239 )
240 }
241 }
242}
243
244impl std::error::Error for PatternMatchLimitError {}
245
246#[derive(Clone, Copy, Debug, PartialEq, Eq)]
256pub struct SchemaEvaluationDepthError;
257
258impl fmt::Display for SchemaEvaluationDepthError {
259 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
260 f.write_str(
261 "The validation was abandoned at a limit: the schema recursed in place beyond the maximum depth, or a \
262 match of a regular expression reached its limit. The try_ functions say which.",
263 )
264 }
265}
266
267impl std::error::Error for SchemaEvaluationDepthError {}
268
269#[derive(Clone, Debug, PartialEq, Eq)]
271#[non_exhaustive]
272pub enum SchemaEvaluationError {
273 InvalidJson(crate::JsonParseError),
275 DepthExceeded(SchemaEvaluationDepthError),
277 PatternMatchLimit(PatternMatchLimitError),
279}
280
281impl fmt::Display for SchemaEvaluationError {
282 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
283 match self {
284 SchemaEvaluationError::InvalidJson(e) => write!(f, "invalid JSON: {e}"),
285 SchemaEvaluationError::DepthExceeded(_) => {
288 f.write_str("The schema recursed in place beyond the maximum depth.")
289 }
290 SchemaEvaluationError::PatternMatchLimit(e) => e.fmt(f),
291 }
292 }
293}
294
295impl std::error::Error for SchemaEvaluationError {
296 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
297 match self {
298 SchemaEvaluationError::InvalidJson(e) => Some(e),
299 SchemaEvaluationError::DepthExceeded(e) => Some(e),
300 SchemaEvaluationError::PatternMatchLimit(e) => Some(e),
301 }
302 }
303}