1use eure_document::document::NodeId;
8use eure_document::parse::ParseError;
9use eure_document::path::EurePath;
10use eure_document::value::ObjectKey;
11use thiserror::Error;
12
13use crate::SchemaNodeId;
14
15#[derive(Debug, Clone, Error, PartialEq)]
24pub enum ValidatorError {
25 #[error("undefined type reference: {name}")]
27 UndefinedTypeReference { name: String },
28
29 #[error("invalid variant tag '{tag}': {reason}")]
31 InvalidVariantTag { tag: String, reason: String },
32
33 #[error("conflicting variant tags: $variant = {explicit}, repr = {repr}")]
35 ConflictingVariantTags { explicit: String, repr: String },
36
37 #[error("cross-schema reference not supported: {namespace}.{name}")]
39 CrossSchemaReference { namespace: String, name: String },
40
41 #[error("parse error: {0}")]
43 DocumentParseError(#[from] ParseError),
44
45 #[error("inner errors propagated")]
47 InnerErrorsPropagated,
48}
49
50impl ValidatorError {
51 pub fn as_parse_error(&self) -> Option<&ParseError> {
53 match self {
54 ValidatorError::DocumentParseError(e) => Some(e),
55 _ => None,
56 }
57 }
58}
59
60#[derive(Debug, Clone, PartialEq)]
83pub struct BestVariantMatch {
84 pub variant_name: String,
86 pub error: Box<ValidationError>,
88 pub all_errors: Vec<ValidationError>,
90 pub depth: usize,
92 pub error_count: usize,
94}
95
96#[derive(Debug, Clone, Error, PartialEq)]
105pub enum ValidationError {
106 #[error("Type mismatch: expected {expected}, got {actual} at path {path}")]
107 TypeMismatch {
108 expected: String,
109 actual: String,
110 path: EurePath,
111 node_id: NodeId,
112 schema_node_id: SchemaNodeId,
113 },
114
115 #[error("Missing required field '{field}' at path {path}")]
116 MissingRequiredField {
117 field: String,
118 path: EurePath,
119 node_id: NodeId,
120 schema_node_id: SchemaNodeId,
121 },
122
123 #[error("Unknown field '{field}' at path {path}")]
124 UnknownField {
125 field: String,
126 path: EurePath,
127 node_id: NodeId,
128 schema_node_id: SchemaNodeId,
129 },
130
131 #[error("Value {value} is out of range at path {path}")]
132 OutOfRange {
133 value: String,
134 path: EurePath,
135 node_id: NodeId,
136 schema_node_id: SchemaNodeId,
137 },
138
139 #[error("String length {length} is out of bounds at path {path}")]
140 StringLengthOutOfBounds {
141 length: usize,
142 min: Option<u32>,
143 max: Option<u32>,
144 path: EurePath,
145 node_id: NodeId,
146 schema_node_id: SchemaNodeId,
147 },
148
149 #[error("String does not match pattern '{pattern}' at path {path}")]
150 PatternMismatch {
151 pattern: String,
152 path: EurePath,
153 node_id: NodeId,
154 schema_node_id: SchemaNodeId,
155 },
156
157 #[error("Array length {length} is out of bounds at path {path}")]
158 ArrayLengthOutOfBounds {
159 length: usize,
160 min: Option<u32>,
161 max: Option<u32>,
162 path: EurePath,
163 node_id: NodeId,
164 schema_node_id: SchemaNodeId,
165 },
166
167 #[error("Map size {size} is out of bounds at path {path}")]
168 MapSizeOutOfBounds {
169 size: usize,
170 min: Option<u32>,
171 max: Option<u32>,
172 path: EurePath,
173 node_id: NodeId,
174 schema_node_id: SchemaNodeId,
175 },
176
177 #[error("Tuple length mismatch: expected {expected}, got {actual} at path {path}")]
178 TupleLengthMismatch {
179 expected: usize,
180 actual: usize,
181 path: EurePath,
182 node_id: NodeId,
183 schema_node_id: SchemaNodeId,
184 },
185
186 #[error("Array elements must be unique at path {path}")]
187 ArrayNotUnique {
188 path: EurePath,
189 node_id: NodeId,
190 schema_node_id: SchemaNodeId,
191 },
192
193 #[error("Array must contain required element at path {path}")]
194 ArrayMissingContains {
195 path: EurePath,
196 node_id: NodeId,
197 schema_node_id: SchemaNodeId,
198 },
199
200 #[error("{}", format_no_variant_matched(path, best_match))]
209 NoVariantMatched {
210 path: EurePath,
211 best_match: Option<Box<BestVariantMatch>>,
213 node_id: NodeId,
214 schema_node_id: SchemaNodeId,
215 },
216
217 #[error("Multiple variants matched for union at path {path}: {variants:?}")]
218 AmbiguousUnion {
219 path: EurePath,
220 variants: Vec<String>,
221 node_id: NodeId,
222 schema_node_id: SchemaNodeId,
223 },
224
225 #[error("Invalid variant tag '{tag}' at path {path}")]
226 InvalidVariantTag {
227 tag: String,
228 path: EurePath,
229 node_id: NodeId,
230 schema_node_id: SchemaNodeId,
231 },
232
233 #[error("Conflicting variant tags: $variant = {explicit}, repr = {repr} at path {path}")]
234 ConflictingVariantTags {
235 explicit: String,
236 repr: String,
237 path: EurePath,
238 node_id: NodeId,
239 schema_node_id: SchemaNodeId,
240 },
241
242 #[error("Variant '{variant}' requires explicit $variant tag at path {path}")]
243 RequiresExplicitVariant {
244 variant: String,
245 path: EurePath,
246 node_id: NodeId,
247 schema_node_id: SchemaNodeId,
248 },
249
250 #[error("Literal value mismatch at path {path}")]
251 LiteralMismatch {
252 expected: String,
253 actual: String,
254 path: EurePath,
255 node_id: NodeId,
256 schema_node_id: SchemaNodeId,
257 },
258
259 #[error("Language mismatch: expected {expected}, got {actual} at path {path}")]
260 LanguageMismatch {
261 expected: String,
262 actual: String,
263 path: EurePath,
264 node_id: NodeId,
265 schema_node_id: SchemaNodeId,
266 },
267
268 #[error("Invalid key type at path {path}")]
269 InvalidKeyType {
270 key: ObjectKey,
272 path: EurePath,
273 node_id: NodeId,
274 schema_node_id: SchemaNodeId,
275 },
276
277 #[error("Integer not a multiple of {divisor} at path {path}")]
278 NotMultipleOf {
279 divisor: String,
280 path: EurePath,
281 node_id: NodeId,
282 schema_node_id: SchemaNodeId,
283 },
284
285 #[error("Undefined type reference '{name}' at path {path}")]
286 UndefinedTypeReference {
287 name: String,
288 path: EurePath,
289 node_id: NodeId,
290 schema_node_id: SchemaNodeId,
291 },
292
293 #[error("Missing required extension '{extension}' at path {path}")]
294 MissingRequiredExtension {
295 extension: String,
296 path: EurePath,
297 node_id: NodeId,
298 schema_node_id: SchemaNodeId,
299 },
300
301 #[error("{}", format_parse_error(path, error))]
304 ParseError {
305 path: EurePath,
306 node_id: NodeId,
307 schema_node_id: SchemaNodeId,
308 error: eure_document::parse::ParseError,
309 },
310}
311
312fn format_parse_error(path: &EurePath, error: &eure_document::parse::ParseError) -> String {
314 use eure_document::parse::ParseErrorKind;
315 match &error.kind {
316 ParseErrorKind::UnknownVariant(name) => {
317 format!("Invalid variant tag '{name}' at path {path}")
318 }
319 ParseErrorKind::ConflictingVariantTags { explicit, repr } => {
320 format!("Conflicting variant tags: $variant = {explicit}, repr = {repr} at path {path}")
321 }
322 ParseErrorKind::InvalidVariantType(kind) => {
323 format!("$variant must be a string, got {kind:?} at path {path}")
324 }
325 ParseErrorKind::InvalidVariantPath(path_str) => {
326 format!("Invalid $variant path syntax: '{path_str}' at path {path}")
327 }
328 _ => format!("{} at path {}", error.kind, path),
330 }
331}
332
333fn format_no_variant_matched(
335 path: &EurePath,
336 best_match: &Option<Box<BestVariantMatch>>,
337) -> String {
338 match best_match {
339 Some(best) => {
340 let mut msg = format!(
341 "No variant matched for union at path {path}, most close variant is '{}': {}",
342 best.variant_name, best.error
343 );
344 if best.all_errors.len() > 1 {
345 msg.push_str(&format!(" (and {} more errors)", best.all_errors.len() - 1));
346 }
347 msg
348 }
349 None => format!("No variant matched for union at path {path}"),
350 }
351}
352
353impl ValidationError {
354 pub fn node_ids(&self) -> (NodeId, SchemaNodeId) {
356 match self {
357 Self::TypeMismatch {
358 node_id,
359 schema_node_id,
360 ..
361 }
362 | Self::MissingRequiredField {
363 node_id,
364 schema_node_id,
365 ..
366 }
367 | Self::UnknownField {
368 node_id,
369 schema_node_id,
370 ..
371 }
372 | Self::OutOfRange {
373 node_id,
374 schema_node_id,
375 ..
376 }
377 | Self::StringLengthOutOfBounds {
378 node_id,
379 schema_node_id,
380 ..
381 }
382 | Self::PatternMismatch {
383 node_id,
384 schema_node_id,
385 ..
386 }
387 | Self::ArrayLengthOutOfBounds {
388 node_id,
389 schema_node_id,
390 ..
391 }
392 | Self::MapSizeOutOfBounds {
393 node_id,
394 schema_node_id,
395 ..
396 }
397 | Self::TupleLengthMismatch {
398 node_id,
399 schema_node_id,
400 ..
401 }
402 | Self::ArrayNotUnique {
403 node_id,
404 schema_node_id,
405 ..
406 }
407 | Self::ArrayMissingContains {
408 node_id,
409 schema_node_id,
410 ..
411 }
412 | Self::NoVariantMatched {
413 node_id,
414 schema_node_id,
415 ..
416 }
417 | Self::AmbiguousUnion {
418 node_id,
419 schema_node_id,
420 ..
421 }
422 | Self::InvalidVariantTag {
423 node_id,
424 schema_node_id,
425 ..
426 }
427 | Self::ConflictingVariantTags {
428 node_id,
429 schema_node_id,
430 ..
431 }
432 | Self::RequiresExplicitVariant {
433 node_id,
434 schema_node_id,
435 ..
436 }
437 | Self::LiteralMismatch {
438 node_id,
439 schema_node_id,
440 ..
441 }
442 | Self::LanguageMismatch {
443 node_id,
444 schema_node_id,
445 ..
446 }
447 | Self::InvalidKeyType {
448 node_id,
449 schema_node_id,
450 ..
451 }
452 | Self::NotMultipleOf {
453 node_id,
454 schema_node_id,
455 ..
456 }
457 | Self::UndefinedTypeReference {
458 node_id,
459 schema_node_id,
460 ..
461 }
462 | Self::MissingRequiredExtension {
463 node_id,
464 schema_node_id,
465 ..
466 }
467 | Self::ParseError {
468 node_id,
469 schema_node_id,
470 ..
471 } => (*node_id, *schema_node_id),
472 }
473 }
474
475 pub fn depth(&self) -> usize {
480 match self {
481 Self::TypeMismatch { path, .. }
482 | Self::MissingRequiredField { path, .. }
483 | Self::UnknownField { path, .. }
484 | Self::OutOfRange { path, .. }
485 | Self::StringLengthOutOfBounds { path, .. }
486 | Self::PatternMismatch { path, .. }
487 | Self::ArrayLengthOutOfBounds { path, .. }
488 | Self::MapSizeOutOfBounds { path, .. }
489 | Self::TupleLengthMismatch { path, .. }
490 | Self::ArrayNotUnique { path, .. }
491 | Self::ArrayMissingContains { path, .. }
492 | Self::NoVariantMatched { path, .. }
493 | Self::AmbiguousUnion { path, .. }
494 | Self::InvalidVariantTag { path, .. }
495 | Self::ConflictingVariantTags { path, .. }
496 | Self::RequiresExplicitVariant { path, .. }
497 | Self::LiteralMismatch { path, .. }
498 | Self::LanguageMismatch { path, .. }
499 | Self::InvalidKeyType { path, .. }
500 | Self::NotMultipleOf { path, .. }
501 | Self::UndefinedTypeReference { path, .. }
502 | Self::MissingRequiredExtension { path, .. }
503 | Self::ParseError { path, .. } => path.0.len(),
504 }
505 }
506
507 pub fn priority_score(&self) -> u8 {
512 match self {
513 Self::MissingRequiredField { .. } => 90,
514 Self::TypeMismatch { .. } => 80,
515 Self::TupleLengthMismatch { .. } => 70,
516 Self::LiteralMismatch { .. } => 70,
517 Self::InvalidVariantTag { .. } => 65,
518 Self::NoVariantMatched { .. } => 60, Self::UnknownField { .. } => 50,
520 Self::MissingRequiredExtension { .. } => 50,
521 Self::ParseError { .. } => 40, Self::OutOfRange { .. } => 30,
523 Self::StringLengthOutOfBounds { .. } => 30,
524 Self::PatternMismatch { .. } => 30,
525 Self::ArrayLengthOutOfBounds { .. } => 30,
526 Self::MapSizeOutOfBounds { .. } => 30,
527 Self::NotMultipleOf { .. } => 30,
528 Self::ArrayNotUnique { .. } => 25,
529 Self::ArrayMissingContains { .. } => 25,
530 Self::InvalidKeyType { .. } => 20,
531 Self::LanguageMismatch { .. } => 20,
532 Self::AmbiguousUnion { .. } => 0, Self::ConflictingVariantTags { .. } => 0, Self::UndefinedTypeReference { .. } => 0, Self::RequiresExplicitVariant { .. } => 0, }
537 }
538}
539
540#[derive(Debug, Clone, PartialEq)]
546pub enum ValidationWarning {
547 UnknownExtension { name: String, path: EurePath },
549 DeprecatedField { field: String, path: EurePath },
551}