1mod accepts;
2mod case_enum;
3mod child_shape;
4mod display;
5mod from_json;
6mod hashing;
7mod helpers;
8mod merge;
9mod meta;
10pub mod name;
11
12pub mod graphql;
13pub mod location;
14#[cfg(test)]
15mod tests;
16mod visitor;
17
18use std::hash::Hash;
19use std::hash::Hasher;
20use std::iter::empty;
21
22pub use accepts::ShapeMismatch;
23pub use case_enum::Error;
24pub use case_enum::ShapeCase;
25pub use helpers::OffsetRange;
26use helpers::Ref;
27use indexmap::IndexMap;
28use indexmap::IndexSet;
29use meta::ShapeMeta;
30pub use visitor::ShapeVisitor;
31
32use crate::case_enum::all::all;
33use crate::case_enum::one::one;
34use crate::location::Location;
35use crate::merge::MergeSet;
36use crate::name::Name;
37use crate::name::WeakScope;
38
39#[derive(Clone, Eq)]
82pub struct Shape {
98 case: Ref<ShapeCase>,
101
102 meta: Ref<ShapeMeta>,
110}
111
112impl PartialEq for Shape {
113 fn eq(&self, other: &Self) -> bool {
114 self.case == other.case
115 }
116}
117
118impl Hash for Shape {
119 fn hash<H: Hasher>(&self, state: &mut H) {
120 self.case.hash(state);
123 }
124}
125
126impl Shape {
127 pub(crate) fn new(case: ShapeCase, locations: impl IntoIterator<Item = Location>) -> Shape {
131 let meta = ShapeMeta::new(&case, locations, []);
132 Shape {
133 case: Ref::new(case),
134 meta: Ref::new(meta),
135 }
136 }
137
138 pub(crate) fn new_with_errors(
140 case: ShapeCase,
141 locations: impl IntoIterator<Item = Location>,
142 errors: impl IntoIterator<Item = Error>,
143 ) -> Shape {
144 let meta = ShapeMeta::new_with_errors(&case, locations, [], errors);
145 Shape {
146 case: Ref::new(case),
147 meta: Ref::new(meta),
148 }
149 }
150
151 #[must_use]
155 pub fn case(&self) -> &ShapeCase {
156 self.case.as_ref()
157 }
158
159 pub fn locations(&self) -> impl Iterator<Item = &Location> {
161 let self_locs = self.meta.locations();
162
163 let unique_locs: IndexSet<&Location> = match self.case() {
164 ShapeCase::One(shapes) => self_locs
165 .chain(shapes.iter().flat_map(|s| s.meta.locations()))
166 .collect(),
167 ShapeCase::All(shapes) => self_locs
168 .chain(shapes.iter().flat_map(|s| s.meta.locations()))
169 .collect(),
170 _ => self_locs.collect(),
171 };
172
173 unique_locs.into_iter()
174 }
175
176 pub fn names(&self) -> impl Iterator<Item = &Name> {
178 self.meta.names()
179 }
180
181 pub fn nested_base_names(&self) -> impl Iterator<Item = &str> {
182 self.meta.nested_base_names()
183 }
184
185 #[must_use]
187 pub fn bool(locations: impl IntoIterator<Item = Location>) -> Self {
188 Self::new(ShapeCase::Bool(None), locations)
189 }
190
191 #[must_use]
193 pub fn bool_value(value: bool, locations: impl IntoIterator<Item = Location>) -> Self {
194 Self::new(ShapeCase::Bool(Some(value)), locations)
195 }
196
197 #[must_use]
199 pub fn string(locations: impl IntoIterator<Item = Location>) -> Self {
200 Self::new(ShapeCase::String(None), locations)
201 }
202
203 #[must_use]
205 pub fn string_value(value: &str, locations: impl IntoIterator<Item = Location>) -> Self {
206 Self::new(ShapeCase::String(Some(value.to_string())), locations)
207 }
208
209 #[must_use]
211 pub fn int(locations: impl IntoIterator<Item = Location>) -> Self {
212 Self::new(ShapeCase::Int(None), locations)
213 }
214
215 #[must_use]
217 pub fn int_value(value: i64, locations: impl IntoIterator<Item = Location>) -> Self {
218 Self::new(ShapeCase::Int(Some(value)), locations)
219 }
220
221 #[must_use]
223 pub fn float(locations: impl IntoIterator<Item = Location>) -> Self {
224 Self::new(ShapeCase::Float, locations)
225 }
226
227 #[must_use]
229 pub fn null(locations: impl IntoIterator<Item = Location>) -> Self {
230 Self::new(ShapeCase::Null, locations)
231 }
232
233 #[must_use]
234 pub fn is_null(&self) -> bool {
235 self.case.is_null()
236 }
237
238 #[must_use]
247 pub fn name(name: &str, locations: impl IntoIterator<Item = Location>) -> Self {
248 let locations = locations.into_iter().collect::<Vec<_>>();
249 Self::new(
250 ShapeCase::Name(
251 name::Name::base(name.to_string(), locations.clone()),
252 WeakScope::none(),
253 ),
254 locations.clone(),
255 )
256 }
257
258 #[must_use]
261 pub fn empty_map() -> IndexMap<String, Self> {
262 IndexMap::new()
263 }
264
265 #[must_use]
273 pub fn empty_object(locations: impl IntoIterator<Item = Location>) -> Self {
274 Shape::new(
275 ShapeCase::Object {
276 fields: Shape::empty_map(),
277 rest: Shape::none(),
278 },
279 locations,
280 )
281 }
282
283 #[must_use]
286 pub fn object(
287 fields: IndexMap<String, Shape>,
288 rest: Shape,
289 locations: impl IntoIterator<Item = Location>,
290 ) -> Self {
291 Shape::new(ShapeCase::Object { fields, rest }, locations)
292 }
293
294 #[must_use]
297 pub fn record(
298 fields: IndexMap<String, Shape>,
299 locations: impl IntoIterator<Item = Location>,
300 ) -> Self {
301 Shape::object(fields, Shape::none(), locations)
302 }
303
304 #[must_use]
307 pub fn dict(value_shape: Shape, locations: impl IntoIterator<Item = Location>) -> Self {
308 Shape::object(Shape::empty_map(), value_shape, locations)
309 }
310
311 pub fn array(
314 prefix: impl IntoIterator<Item = Shape>,
315 tail: Shape,
316 locations: impl IntoIterator<Item = Location>,
317 ) -> Self {
318 let prefix = prefix.into_iter().collect();
319 Self::new(ShapeCase::Array { prefix, tail }, locations)
320 }
321
322 pub fn tuple(
325 shapes: impl IntoIterator<Item = Shape>,
326 locations: impl IntoIterator<Item = Location>,
327 ) -> Self {
328 Shape::array(shapes, Shape::none(), locations)
329 }
330
331 #[must_use]
334 pub fn list(of: Shape, locations: impl IntoIterator<Item = Location>) -> Self {
335 Shape::array(empty(), of, locations)
336 }
337
338 pub fn one(
344 shapes: impl IntoIterator<Item = Shape>,
345 locations: impl IntoIterator<Item = Location>,
346 ) -> Self {
347 one(shapes.into_iter(), locations.into_iter().collect())
348 }
349
350 pub fn all(
356 shapes: impl IntoIterator<Item = Shape>,
357 locations: impl IntoIterator<Item = Location>,
358 ) -> Self {
359 all(shapes.into_iter(), locations.into_iter().collect())
360 }
361
362 #[must_use]
365 pub fn unknown(locations: impl IntoIterator<Item = Location>) -> Self {
366 Self::new(ShapeCase::Unknown, locations)
367 }
368
369 #[must_use]
370 pub fn is_unknown(&self) -> bool {
371 matches!(self.case(), ShapeCase::Unknown)
372 }
373
374 #[must_use]
381 pub fn none() -> Self {
382 Self::new(ShapeCase::None, [])
383 }
384
385 #[must_use]
386 pub fn is_none(&self) -> bool {
387 self.case.is_none()
388 }
389
390 #[must_use]
393 pub fn error(
394 message: impl Into<String>,
395 locations: impl IntoIterator<Item = Location>,
396 ) -> Self {
397 let locations: Vec<_> = locations.into_iter().collect();
398 Self::new_with_errors(
399 ShapeCase::Unknown,
400 locations,
401 [Error {
402 message: message.into(),
403 }],
404 )
405 }
406
407 #[deprecated(
409 since = "0.7.0",
410 note = "use `has_errors()` for recursive check or `has_own_errors()` for own-only"
411 )]
412 #[must_use]
413 pub fn is_error(&self) -> bool {
414 self.meta.has_errors()
415 }
416
417 #[must_use]
419 pub fn has_own_errors(&self) -> bool {
420 self.meta.has_errors()
421 }
422
423 #[must_use]
425 pub fn has_errors(&self) -> bool {
426 if self.has_own_errors() {
427 return true;
428 }
429 match self.case() {
430 ShapeCase::Array { prefix, tail } => {
431 prefix.iter().any(Shape::has_errors) || tail.has_errors()
432 }
433 ShapeCase::Object { fields, rest } => {
434 fields.values().any(Shape::has_errors) || rest.has_errors()
435 }
436 ShapeCase::One(one) => one.iter().any(Shape::has_errors),
437 ShapeCase::All(all) => all.iter().any(Shape::has_errors),
438 ShapeCase::Name(_, _)
441 | ShapeCase::Bool(_)
442 | ShapeCase::String(_)
443 | ShapeCase::Int(_)
444 | ShapeCase::Float
445 | ShapeCase::Null
446 | ShapeCase::Unknown
447 | ShapeCase::None => false,
448 }
449 }
450
451 pub fn own_errors(&self) -> impl Iterator<Item = &Error> {
453 self.meta.errors()
454 }
455
456 #[must_use]
461 pub fn errors(&self) -> Vec<&Error> {
462 let mut errors: Vec<&Error> = self.own_errors().collect();
463
464 match self.case() {
465 ShapeCase::Array { prefix, tail } => {
466 for child in prefix {
467 errors.extend(child.errors());
468 }
469 errors.extend(tail.errors());
470 }
471 ShapeCase::Object { fields, rest } => {
472 for child in fields.values() {
473 errors.extend(child.errors());
474 }
475 errors.extend(rest.errors());
476 }
477 ShapeCase::One(one) => {
478 for child in one.iter() {
479 errors.extend(child.errors());
480 }
481 }
482 ShapeCase::All(all) => {
483 for child in all.iter() {
484 errors.extend(child.errors());
485 }
486 }
487 ShapeCase::Name(_, _)
490 | ShapeCase::Bool(_)
491 | ShapeCase::String(_)
492 | ShapeCase::Int(_)
493 | ShapeCase::Float
494 | ShapeCase::Null
495 | ShapeCase::Unknown
496 | ShapeCase::None => {}
497 }
498
499 errors
500 }
501
502 #[must_use]
506 pub fn error_with_partial(
507 message: impl Into<String>,
508 partial: Shape,
509 locations: impl IntoIterator<Item = Location>,
510 ) -> Self {
511 partial
512 .with_error(Error {
513 message: message.into(),
514 })
515 .with_locations(locations.into_iter().collect::<Vec<_>>().iter())
516 }
517
518 #[must_use]
520 pub fn with_error(mut self, error: Error) -> Self {
521 Ref::make_mut(&mut self.meta).add_error(error);
522 self
523 }
524
525 #[must_use]
527 pub fn with_locations<'a>(mut self, locations: impl IntoIterator<Item = &'a Location>) -> Self {
528 for loc in locations {
529 if !self.meta.has_location(loc) {
530 Ref::make_mut(&mut self.meta).add_location(loc);
531 }
532 }
533 self
534 }
535}
536
537#[cfg(test)]
538mod test_errors {
539 use super::*;
540
541 #[test]
542 fn error_shape_has_error() {
543 let error_shape = Shape::error("Expected a string", []);
544 let errors = error_shape.errors();
545 assert_eq!(errors.len(), 1);
546 assert_eq!(errors[0].message, "Expected a string");
547 assert!(error_shape.has_errors());
548 }
549
550 #[test]
551 fn error_with_partial_preserves_shape() {
552 let error_shape = Shape::error_with_partial("Parse failed", Shape::bool([]), []);
553 assert!(matches!(error_shape.case(), ShapeCase::Bool(None)));
555 assert!(error_shape.has_errors());
556 let errors = error_shape.errors();
557 assert_eq!(errors.len(), 1);
558 assert_eq!(errors[0].message, "Parse failed");
559 }
560
561 #[test]
562 fn multiple_errors_can_attach() {
563 let shape = Shape::bool([])
564 .with_error(Error {
565 message: "First error".to_string(),
566 })
567 .with_error(Error {
568 message: "Second error".to_string(),
569 });
570 let errors = shape.errors();
571 assert_eq!(errors.len(), 2);
572 assert_eq!(errors[0].message, "First error");
573 assert_eq!(errors[1].message, "Second error");
574 }
575
576 #[test]
577 fn plain_shapes_have_no_errors() {
578 let int_shape = Shape::int([]);
579 assert!(!int_shape.has_errors());
580 assert_eq!(int_shape.errors().len(), 0);
581 }
582
583 #[test]
584 fn errors_collects_from_nested_shapes() {
585 let error_element = Shape::string([]).with_error(Error {
587 message: "nested error".to_string(),
588 });
589 let array = Shape::array([], error_element.clone(), []);
590
591 assert!(!array.has_own_errors());
593 assert!(array.own_errors().next().is_none());
594
595 let all = array.errors();
597 assert_eq!(all.len(), 1);
598 assert_eq!(all[0].message, "nested error");
599
600 assert!(array.has_errors());
602 }
603
604 #[test]
605 fn errors_collects_from_object_fields() {
606 let error_field = Shape::int([]).with_error(Error {
607 message: "field error".to_string(),
608 });
609 let mut fields = Shape::empty_map();
610 fields.insert("count".to_string(), error_field);
611 let obj = Shape::object(fields, Shape::none(), []);
612
613 assert!(!obj.has_own_errors());
615 assert!(obj.has_errors());
616
617 let all = obj.errors();
618 assert_eq!(all.len(), 1);
619 assert_eq!(all[0].message, "field error");
620 }
621
622 #[test]
623 fn errors_collects_from_multiple_levels() {
624 let deep_error = Shape::bool([]).with_error(Error {
626 message: "deep".to_string(),
627 });
628 let array_with_error = Shape::array([], deep_error, []).with_error(Error {
629 message: "middle".to_string(),
630 });
631 let mut fields = Shape::empty_map();
632 fields.insert("items".to_string(), array_with_error);
633 let obj = Shape::object(fields, Shape::none(), []).with_error(Error {
634 message: "top".to_string(),
635 });
636
637 let all = obj.errors();
638 assert_eq!(all.len(), 3);
639 assert_eq!(all[0].message, "top");
641 assert_eq!(all[1].message, "middle");
642 assert_eq!(all[2].message, "deep");
643 }
644
645 #[test]
646 fn has_errors_short_circuits() {
647 let clean_shape = Shape::int([]);
649 assert!(!clean_shape.has_errors());
650
651 let error_shape = Shape::error("oops", []);
652 assert!(error_shape.has_errors());
653 }
654
655 #[test]
656 fn errors_and_names_single_each() {
657 let shape = Shape::string([])
659 .with_error(Error {
660 message: "invalid value".to_string(),
661 })
662 .with_base_name("MyString", []);
663
664 assert!(shape.has_errors());
665 assert_eq!(shape.errors().len(), 1);
666 assert!(shape.has_base_name("MyString"));
667
668 assert_eq!(shape.pretty_print(), r#"String (err "invalid value")"#);
669 assert_eq!(shape.pretty_print_without_errors(), "String");
670 assert_eq!(
671 shape.pretty_print_with_names(),
672 r#"String (err "invalid value") (aka MyString)"#
673 );
674 }
675
676 #[test]
677 fn multiple_errors_no_names() {
678 let shape = Shape::int([])
679 .with_error(Error {
680 message: "first error".to_string(),
681 })
682 .with_error(Error {
683 message: "second error".to_string(),
684 });
685
686 assert!(shape.has_errors());
687 assert_eq!(shape.errors().len(), 2);
688 assert_eq!(shape.names().count(), 0);
689
690 assert_eq!(
691 shape.pretty_print(),
692 r#"Int (err "first error", "second error")"#
693 );
694 assert_eq!(shape.pretty_print_without_errors(), "Int");
695 assert_eq!(
696 shape.pretty_print_with_names(),
697 r#"Int (err "first error", "second error")"#
698 );
699 }
700
701 #[test]
702 fn no_errors_multiple_names() {
703 let shape = Shape::int([])
707 .with_base_name("Count", [])
708 .with_base_name("Total", []);
709
710 assert!(!shape.has_errors());
711 assert!(shape.has_base_name("Count"));
712 assert!(shape.has_base_name("Total"));
713
714 assert_eq!(shape.pretty_print(), "Int");
715 assert_eq!(shape.pretty_print_without_errors(), "Int");
716 let with_names = shape.pretty_print_with_names();
718 assert!(with_names.contains("(aka"));
719 assert!(with_names.contains("Count"));
720 assert!(with_names.contains("Total"));
721 }
722
723 #[test]
724 fn multiple_errors_multiple_names() {
725 let shape = Shape::bool([])
726 .with_error(Error {
727 message: "err1".to_string(),
728 })
729 .with_error(Error {
730 message: "err2".to_string(),
731 })
732 .with_base_name("Flag", [])
733 .with_base_name("Toggle", []);
734
735 assert!(shape.has_errors());
736 assert_eq!(shape.errors().len(), 2);
737 assert!(shape.has_base_name("Flag"));
738 assert!(shape.has_base_name("Toggle"));
739
740 assert_eq!(shape.pretty_print(), r#"Bool (err "err1", "err2")"#);
742
743 assert_eq!(shape.pretty_print_without_errors(), "Bool");
745
746 let with_names = shape.pretty_print_with_names();
748 assert!(with_names.starts_with(r#"Bool (err "err1", "err2") (aka "#));
749 assert!(with_names.contains("Flag"));
750 assert!(with_names.contains("Toggle"));
751 }
752
753 #[test]
754 fn nested_errors_and_names() {
755 let field_with_error = Shape::string([])
757 .with_error(Error {
758 message: "field error".to_string(),
759 })
760 .with_base_name("FieldName", []);
761
762 let mut fields = Shape::empty_map();
763 fields.insert("field".to_string(), field_with_error);
764 let obj = Shape::object(fields, Shape::none(), [])
765 .with_error(Error {
766 message: "object error".to_string(),
767 })
768 .with_base_name("MyObject", []);
769
770 assert!(obj.has_own_errors());
772 assert!(obj.has_errors());
773 assert_eq!(obj.errors().len(), 2);
775
776 let pp = obj.pretty_print();
778 assert!(pp.contains(r#"(err "object error")"#));
779 assert!(pp.contains(r#"(err "field error")"#));
780
781 let with_names = obj.pretty_print_with_names();
783 assert!(with_names.contains("MyObject"));
784 assert!(with_names.contains("FieldName"));
785 }
786}