1use std::sync::OnceLock;
6
7use bincode::{Decode, Encode};
8use fluent_uri::Uri;
9use rustc_hash::FxHashMap;
10
11use crate::diagnostics::{Diagnostic, UnsupportedReason};
12use crate::error::{CompileError, ErrorCode, Stage};
13use crate::primitives::NodeId;
14use crate::wire::SchemaIRWire;
15
16pub const IR_VERSION: u16 = 6;
18
19pub(crate) const MAX_NODES: usize = 1 << 18;
23pub(crate) const MAX_STRINGS_BYTES: usize = 8 * 1024 * 1024;
24pub(crate) const MAX_LITERALS: usize = 1 << 18;
25pub(crate) const MAX_LITERAL_STR_BYTES: usize = 8 * 1024 * 1024;
27pub(crate) const MAX_PROPS: usize = 1 << 18;
28pub(crate) const MAX_DEPENDENT_REQUIRED_PAIRS: usize = 1 << 18;
29pub(crate) const MAX_REQUIRED_WORDS: usize = 1 << 18;
30pub(crate) const MAX_DIAGNOSTICS: usize = 1 << 16;
31pub(crate) const MAX_NODE_REFS: usize = 1 << 18;
32pub(crate) const MAX_RESOURCES: usize = 1 << 16;
33pub(crate) const MAX_ANCHORS: usize = 1 << 18;
34
35pub(crate) fn literal_str_bytes(lits: &[ScalarLit]) -> usize {
38 lits.iter()
39 .map(|l| match l {
40 ScalarLit::Str(s) | ScalarLit::Json(s) => s.len(),
41 _ => 0,
42 })
43 .sum()
44}
45
46macro_rules! slice_ref {
48 ($(#[$m:meta])* $name:ident) => {
49 $(#[$m])*
50 #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default, Encode, Decode)]
51 pub struct $name {
52 pub off: u32,
54 pub len: u32,
56 }
57 };
58}
59
60slice_ref!(
61 StrRef
63);
64slice_ref!(
65 LitSlice
67);
68slice_ref!(
69 PropSlice
71);
72slice_ref!(
73 DependentRequiredSlice
75);
76slice_ref!(
77 BitSlice
80);
81slice_ref!(
82 RefSlice
84);
85slice_ref!(
86 AnchorSlice
88);
89
90#[repr(transparent)]
92#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default, Encode, Decode)]
93pub struct ResourceId(pub(crate) u32);
94
95impl ResourceId {
96 #[must_use]
98 pub const fn get(self) -> u32 {
99 self.0
100 }
101}
102
103#[repr(transparent)]
105#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default, Encode, Decode)]
106pub struct AnchorId(pub(crate) u32);
107
108impl AnchorId {
109 #[must_use]
111 pub const fn get(self) -> u32 {
112 self.0
113 }
114}
115
116#[derive(Clone, PartialEq, Eq, Hash, Debug, Encode, Decode)]
118pub struct ResourceRecord {
119 pub canonical_uri: Option<StrRef>,
121 pub root: NodeId,
123 pub static_anchors: AnchorSlice,
125 pub dynamic_anchors: AnchorSlice,
127}
128
129#[derive(Clone, PartialEq, Eq, Hash, Debug, Encode, Decode)]
131pub struct AnchorRecord {
132 pub resource: ResourceId,
134 pub name: StrRef,
136 pub target: NodeId,
138}
139
140pub(crate) struct ResourceInput {
142 pub(crate) canonical_uri: Option<String>,
143 pub(crate) root: NodeId,
144 pub(crate) static_anchors: Vec<(String, NodeId)>,
145 pub(crate) dynamic_anchors: Vec<(String, NodeId)>,
146}
147
148#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Encode, Decode)]
150pub enum ScalarLit {
151 Null,
153 Bool(bool),
155 Int(i64),
157 Str(String),
159 Json(String),
162}
163
164#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Encode, Decode)]
167pub enum Charset {
168 Utf8Any = 0,
170 AsciiPrintableNoQuoteBackslash = 1,
172 Utf8CountedCodepoints = 2,
174}
175
176#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Encode, Decode)]
178pub enum ObjectClosure {
179 RejectOpenObjects = 0,
181 AssumeClosedProfile = 1,
183 Forbidden = 2,
185 AllowOpenProfile = 3,
187}
188
189#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Encode, Decode)]
191pub enum AdditionalPolicy {
192 Forbid,
194 AllowAny,
196 Schema(NodeId),
198 Open,
201}
202
203#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Encode, Decode)]
205pub enum UnevaluatedKind {
206 Properties,
208 Items,
210}
211
212#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Encode, Decode)]
215pub enum ItemsPolicy {
216 Schema(NodeId),
218 AllowAny,
221}
222
223#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Encode, Decode)]
225pub enum ContainsPolicy {
226 Never,
228 Always,
230 Schema(NodeId),
232}
233
234#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Encode, Decode)]
237pub struct ContainsConstraint {
238 pub policy: ContainsPolicy,
240 pub min: u32,
242 pub max: Option<u32>,
244}
245
246#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Encode, Decode)]
248pub enum UnsupportedPolicy {
249 RejectAtCompile = 0,
251}
252
253#[derive(Clone, PartialEq, Eq, Hash, Debug, Encode, Decode)]
256pub struct CompileOptions {
257 pub object_closure: ObjectClosure,
259 pub format_assertion: bool,
261 pub unsupported_policy: UnsupportedPolicy,
263 pub max_diagnostics: u32,
265}
266
267impl Default for CompileOptions {
268 fn default() -> Self {
269 Self {
270 object_closure: ObjectClosure::AllowOpenProfile,
271 format_assertion: false,
272 unsupported_policy: UnsupportedPolicy::RejectAtCompile,
273 max_diagnostics: 8192,
274 }
275 }
276}
277
278pub(crate) const MAX_SAFE_SHUFFLE_FIELDS: usize = 4;
283pub(crate) const MAX_UNROLLED_CODEPOINT_LEN: u32 = 256;
286pub(crate) const MAX_UNROLLED_ARRAY_ITEMS: u32 = 64;
289pub(crate) const MAX_UNROLLED_ENUM: usize = 128;
292#[non_exhaustive]
294#[derive(Clone, PartialEq, Eq, Hash, Debug, Encode, Decode)]
295pub enum Node {
296 Null,
298 Boolean,
300 Never,
303 StringConst {
305 value: StrRef,
307 },
308 StringPattern {
310 regex: StrRef,
312 min_len: Option<u32>,
314 max_len: Option<u32>,
316 charset: Charset,
318 },
319 Integer {
321 minimum: Option<i64>,
323 maximum: Option<i64>,
325 multiple_of: Option<u64>,
327 },
328 Number {
331 integer_only: bool,
333 minimum: Option<(i128, u32, bool)>,
336 maximum: Option<(i128, u32, bool)>,
338 multiple_of: Option<(u64, u32)>,
340 },
341 LexicalNumber {
343 regex: StrRef,
345 },
346 Enum {
348 values: LitSlice,
350 },
351 Array {
353 items: ItemsPolicy,
355 min_items: u32,
357 max_items: Option<u32>,
359 unique_items: bool,
362 contains: Option<ContainsConstraint>,
364 items_annotates: bool,
367 },
368 Tuple {
371 prefix: RefSlice,
373 tail: Option<NodeId>,
375 min_items: u32,
377 max_items: Option<u32>,
379 unique_items: bool,
381 tail_annotates: bool,
383 contains: Option<ContainsConstraint>,
385 },
386 Object {
388 fields: PropSlice,
390 required: BitSlice,
392 closure: ObjectClosure,
394 dependent: PropSlice,
397 dependent_required: DependentRequiredSlice,
399 },
400 OpenObject {
403 known: PropSlice,
405 known_required: BitSlice,
407 patterns: PropSlice,
409 additional: AdditionalPolicy,
411 property_names: Option<NodeId>,
413 min_properties: Option<u32>,
415 max_properties: Option<u32>,
417 dependent: PropSlice,
419 dependent_required: DependentRequiredSlice,
421 },
422 Union {
424 branches: RefSlice,
426 },
427 Intersection {
429 branches: RefSlice,
431 },
432 ExactlyOne {
434 branches: RefSlice,
436 },
437 Not {
440 inner: NodeId,
442 },
443 Ref {
446 def: u32,
448 },
449 DynamicRef {
451 initial_target: NodeId,
453 anchor: AnchorId,
455 },
456 Unevaluated {
459 kind: UnevaluatedKind,
461 scope: NodeId,
463 unevaluated: NodeId,
465 },
466 Unsupported {
468 keyword: StrRef,
470 reason: UnsupportedReason,
472 },
473}
474
475#[derive(Clone)]
477pub struct SchemaIR {
478 ir_version: u16,
479 root: NodeId,
480 nodes: Vec<Node>,
481 strings: Vec<u8>,
482 literals: Vec<ScalarLit>,
483 props: Vec<(StrRef, NodeId)>,
484 dependent_required_pairs: Vec<DependentRequiredPair>,
485 required_bits: Vec<u64>,
486 node_refs: Vec<NodeId>,
487 def_targets: Vec<NodeId>,
488 retrieval_uri: Option<StrRef>,
489 resources: Vec<ResourceRecord>,
490 anchors: Vec<AnchorRecord>,
491 node_resources: Vec<ResourceId>,
492 options: CompileOptions,
493 diagnostics: Vec<Diagnostic>,
494 cached_hash: OnceLock<[u8; 32]>,
495 route_table: OnceLock<crate::routing::RouteTable>,
496}
497
498#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Encode, Decode)]
500pub struct DependentRequiredPair {
501 pub trigger: StrRef,
503 pub required: StrRef,
505}
506
507impl PartialEq for SchemaIR {
508 fn eq(&self, other: &Self) -> bool {
513 self.ir_version == other.ir_version
514 && self.root == other.root
515 && self.nodes == other.nodes
516 && self.strings == other.strings
517 && self.literals == other.literals
518 && self.props == other.props
519 && self.dependent_required_pairs == other.dependent_required_pairs
520 && self.required_bits == other.required_bits
521 && self.node_refs == other.node_refs
522 && self.def_targets == other.def_targets
523 && self.retrieval_uri == other.retrieval_uri
524 && self.resources == other.resources
525 && self.anchors == other.anchors
526 && self.node_resources == other.node_resources
527 && self.options == other.options
528 && self.diagnostics == other.diagnostics
529 }
530}
531
532impl Eq for SchemaIR {}
533
534impl std::fmt::Debug for SchemaIR {
535 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
536 f.debug_struct("SchemaIR")
537 .field("ir_version", &self.ir_version)
538 .field("root", &self.root)
539 .field("nodes", &self.nodes)
540 .field("resources", &self.resources)
541 .field("diagnostics", &self.diagnostics.len())
542 .finish_non_exhaustive()
543 }
544}
545
546impl SchemaIR {
547 #[must_use]
549 pub fn ir_version(&self) -> u16 {
550 self.ir_version
551 }
552
553 #[must_use]
555 pub fn root(&self) -> NodeId {
556 self.root
557 }
558
559 #[must_use]
561 pub fn node(&self, id: NodeId) -> Option<&Node> {
562 self.nodes.get(id.get() as usize)
563 }
564
565 pub fn nodes(&self) -> impl ExactSizeIterator<Item = &Node> {
567 self.nodes.iter()
568 }
569
570 #[must_use]
572 pub fn node_count(&self) -> usize {
573 self.nodes.len()
574 }
575
576 #[must_use]
578 pub fn retrieval_uri(&self) -> Option<&str> {
579 self.retrieval_uri.and_then(|uri| self.str_at(uri))
580 }
581
582 #[must_use]
584 pub fn resources(&self) -> &[ResourceRecord] {
585 &self.resources
586 }
587
588 #[must_use]
590 pub fn anchors(&self) -> &[AnchorRecord] {
591 &self.anchors
592 }
593
594 #[must_use]
596 pub fn node_resource(&self, node: NodeId) -> Option<ResourceId> {
597 self.node_resources.get(node.get() as usize).copied()
598 }
599
600 pub fn diagnostics(&self) -> impl ExactSizeIterator<Item = &Diagnostic> {
602 self.diagnostics.iter()
603 }
604
605 pub(crate) fn is_unrolled_codepoint_string(&self, node: &Node) -> bool {
608 let Node::StringPattern {
609 regex,
610 min_len,
611 max_len,
612 charset: Charset::Utf8CountedCodepoints,
613 } = node
614 else {
615 return false;
616 };
617 let long = max_len.is_some_and(|n| n > MAX_UNROLLED_CODEPOINT_LEN)
618 || min_len.is_some_and(|n| n > MAX_UNROLLED_CODEPOINT_LEN);
619 long && self.str_at(*regex) == Some(crate::frontend::JSON_STRING_CODEPOINT)
620 }
621
622 pub(crate) fn is_user_pattern(&self, node: &Node) -> bool {
625 let Node::StringPattern {
626 regex,
627 charset: Charset::Utf8Any,
628 ..
629 } = node
630 else {
631 return false;
632 };
633 self.str_at(*regex) != Some(crate::frontend::JSON_STRING_BODY)
634 }
635
636 pub(crate) fn is_large_string_enum(&self, node: &Node) -> bool {
639 let Node::Enum { values } = node else {
640 return false;
641 };
642 let Some(lits) = self.lits_at(*values) else {
643 return false;
644 };
645 lits.len() > MAX_UNROLLED_ENUM && lits.iter().all(|l| matches!(l, ScalarLit::Str(_)))
646 }
647
648 #[must_use]
652 pub fn requires_structured_backend(&self) -> bool {
653 self.route_table()
654 .get(self.root)
655 .is_none_or(|route| route.kind != crate::routing::ExecutionKind::Regular)
656 }
657
658 #[must_use]
660 pub fn route_table(&self) -> &crate::routing::RouteTable {
661 self.route_table
662 .get_or_init(|| crate::routing::analyze(self))
663 }
664
665 #[must_use]
667 pub fn options(&self) -> &CompileOptions {
668 &self.options
669 }
670
671 #[must_use]
673 pub fn str_at(&self, r: StrRef) -> Option<&str> {
674 let end = (r.off as usize).checked_add(r.len as usize)?;
675 let bytes = self.strings.get(r.off as usize..end)?;
676 std::str::from_utf8(bytes).ok()
677 }
678
679 #[must_use]
681 pub fn lits_at(&self, r: LitSlice) -> Option<&[ScalarLit]> {
682 let end = (r.off as usize).checked_add(r.len as usize)?;
683 self.literals.get(r.off as usize..end)
684 }
685
686 #[must_use]
688 pub fn props_at(&self, r: PropSlice) -> Option<&[(StrRef, NodeId)]> {
689 let end = (r.off as usize).checked_add(r.len as usize)?;
690 self.props.get(r.off as usize..end)
691 }
692
693 #[must_use]
695 pub fn dependent_required_at(
696 &self,
697 r: DependentRequiredSlice,
698 ) -> Option<&[DependentRequiredPair]> {
699 let end = (r.off as usize).checked_add(r.len as usize)?;
700 self.dependent_required_pairs.get(r.off as usize..end)
701 }
702
703 #[must_use]
705 pub fn def_target(&self, def: u32) -> Option<NodeId> {
706 self.def_targets.get(def as usize).copied()
707 }
708
709 #[must_use]
711 pub fn refs_at(&self, r: RefSlice) -> Option<&[NodeId]> {
712 let end = (r.off as usize).checked_add(r.len as usize)?;
713 self.node_refs.get(r.off as usize..end)
714 }
715
716 #[must_use]
718 pub fn is_required(&self, r: BitSlice, idx: u32) -> bool {
719 if idx >= r.len {
720 return false;
721 }
722 let word = r.off as usize + (idx / 64) as usize;
723 self.required_bits
724 .get(word)
725 .is_some_and(|w| (w >> (idx % 64)) & 1 == 1)
726 }
727
728 #[must_use]
731 pub fn canonical_hash(&self) -> [u8; 32] {
732 *self.cached_hash.get_or_init(|| self.compute_hash())
733 }
734
735 #[must_use]
739 pub fn same_content_address(&self, other: &Self) -> bool {
740 self.canonical_hash() == other.canonical_hash()
741 }
742
743 #[must_use]
745 pub fn to_wire(&self) -> Vec<u8> {
746 crate::wire::encode(self)
747 }
748
749 pub fn from_wire(bytes: &[u8]) -> Result<Self, CompileError> {
751 crate::wire::decode(bytes)
752 }
753
754 pub(crate) fn to_wire_struct(&self) -> SchemaIRWire {
756 SchemaIRWire {
757 ir_version: self.ir_version,
758 root: self.root,
759 nodes: self.nodes.clone(),
760 strings: self.strings.clone(),
761 literals: self.literals.clone(),
762 props: self.props.clone(),
763 dependent_required_pairs: self.dependent_required_pairs.clone(),
764 required_bits: self.required_bits.clone(),
765 node_refs: self.node_refs.clone(),
766 def_targets: self.def_targets.clone(),
767 retrieval_uri: self.retrieval_uri,
768 resources: self.resources.clone(),
769 anchors: self.anchors.clone(),
770 node_resources: self.node_resources.clone(),
771 options: self.options.clone(),
772 diagnostics: self.diagnostics.clone(),
773 }
774 }
775
776 pub(crate) fn assemble(wire: SchemaIRWire) -> Result<Self, CompileError> {
779 let ir = Self {
780 ir_version: wire.ir_version,
781 root: wire.root,
782 nodes: wire.nodes,
783 strings: wire.strings,
784 literals: wire.literals,
785 props: wire.props,
786 dependent_required_pairs: wire.dependent_required_pairs,
787 required_bits: wire.required_bits,
788 node_refs: wire.node_refs,
789 def_targets: wire.def_targets,
790 retrieval_uri: wire.retrieval_uri,
791 resources: wire.resources,
792 anchors: wire.anchors,
793 node_resources: wire.node_resources,
794 options: wire.options,
795 diagnostics: wire.diagnostics,
796 cached_hash: OnceLock::new(),
797 route_table: OnceLock::new(),
798 };
799 ir.validate()?;
800 Ok(ir)
801 }
802
803 fn validate(&self) -> Result<(), CompileError> {
806 if self.ir_version != IR_VERSION {
807 return Err(err(
808 ErrorCode::ArtifactMismatch,
809 "ir_version does not match",
810 ));
811 }
812 if self.options.max_diagnostics == 0 {
813 return Err(malformed("max_diagnostics must be at least one"));
814 }
815 if self.root.get() as usize >= self.nodes.len() {
816 return Err(malformed("root node id out of range"));
817 }
818 if literal_str_bytes(&self.literals) > MAX_LITERAL_STR_BYTES {
819 return Err(limit("literal string bytes"));
820 }
821 if self.dependent_required_pairs.len() > MAX_DEPENDENT_REQUIRED_PAIRS {
822 return Err(malformed("dependentRequired pair count exceeds cap"));
823 }
824 self.validate_resources()?;
825 for (i, node) in self.nodes.iter().enumerate() {
826 let parent = u32::try_from(i).map_err(|_| limit("node index"))?;
827 self.validate_node(node, parent)?;
828 }
829 for target in &self.def_targets {
830 if target.get() as usize >= self.nodes.len() {
831 return Err(malformed("def target node id out of range"));
832 }
833 }
834 self.validate_diagnostics()?;
835 Ok(())
836 }
837
838 #[doc(hidden)]
840 #[cfg(feature = "bench-internals")]
841 pub fn validate_for_bench(&self) -> Result<(), CompileError> {
842 self.validate()
843 }
844
845 fn validate_diagnostics(&self) -> Result<(), CompileError> {
846 if self.diagnostics.len() as u64 > u64::from(self.options.max_diagnostics) {
847 return Err(CompileError::new(
848 ErrorCode::TooManyDiagnostics,
849 Stage::L2,
850 "more diagnostics than the configured cap",
851 ));
852 }
853 for d in &self.diagnostics {
854 let node = self
855 .nodes
856 .get(d.node.get() as usize)
857 .ok_or_else(|| malformed("diagnostic references a node out of range"))?;
858 let Node::Unsupported { keyword, reason } = node else {
859 return Err(malformed("a diagnostic must point at an Unsupported node"));
860 };
861 if *keyword != d.keyword {
862 return Err(malformed("diagnostic keyword disagrees with its node"));
863 }
864 if *reason != d.reason {
865 return Err(malformed("diagnostic reason disagrees with its node"));
866 }
867 if self.str_at(d.keyword).is_none() || self.str_at(d.json_pointer).is_none() {
868 return Err(malformed("diagnostic string reference out of range"));
869 }
870 if let Some(span) = d.source_span {
871 if span.start > span.end {
872 return Err(malformed("diagnostic span start is past its end"));
873 }
874 }
875 }
876 Ok(())
877 }
878
879 fn validate_node(&self, node: &Node, parent: u32) -> Result<(), CompileError> {
880 let child_ok = |c: NodeId| c.get() < parent;
881 match node {
882 Node::Null | Node::Boolean | Node::Never => Ok(()),
883 Node::StringConst { value } => {
884 self.str_at(*value)
885 .ok_or_else(|| malformed("string const reference out of range"))?;
886 Ok(())
887 }
888 Node::StringPattern {
889 regex,
890 min_len,
891 max_len,
892 charset,
893 } => {
894 let re = self
895 .str_at(*regex)
896 .ok_or_else(|| malformed("pattern reference out of range"))?;
897 if *charset == Charset::Utf8Any && re != crate::frontend::JSON_STRING_BODY {
898 validate_search_pattern(re)?;
899 } else {
900 validate_pattern(re)?;
901 }
902 if let (Some(lo), Some(hi)) = (min_len, max_len) {
903 if hi < lo {
904 return Err(malformed("string maxLength is less than minLength"));
905 }
906 }
907 Ok(())
908 }
909 Node::Integer {
910 minimum,
911 maximum,
912 multiple_of,
913 } => {
914 if let (Some(lo), Some(hi)) = (minimum, maximum) {
915 if hi < lo {
916 return Err(malformed("integer maximum is less than minimum"));
917 }
918 }
919 if *multiple_of == Some(0) {
920 return Err(malformed("multipleOf must be a positive integer"));
921 }
922 Ok(())
923 }
924 Node::Number {
925 integer_only: _,
926 minimum,
927 maximum,
928 multiple_of,
929 } => {
930 if let (Some((lo, ls, lo_excl)), Some((hi, hs, hi_excl))) = (minimum, maximum) {
931 let ord = decimal_cmp((*lo, *ls), (*hi, *hs));
932 if ord == std::cmp::Ordering::Greater
933 || (ord == std::cmp::Ordering::Equal && (*lo_excl || *hi_excl))
934 {
935 return Err(malformed("number maximum is not above minimum"));
936 }
937 }
938 if let Some((coef, _)) = multiple_of {
939 if *coef == 0 {
940 return Err(malformed("multipleOf must be positive"));
941 }
942 }
943 Ok(())
944 }
945 Node::LexicalNumber { regex } => {
946 let regex = self
947 .str_at(*regex)
948 .ok_or_else(|| malformed("lexical number pattern out of range"))?;
949 validate_pattern(regex)
950 }
951 Node::Enum { values } => {
952 let lits = self
953 .lits_at(*values)
954 .ok_or_else(|| malformed("enum literal run out of range"))?;
955 if lits.is_empty() {
956 return Err(malformed("an enum must have at least one value"));
957 }
958 for pair in lits.windows(2) {
959 if pair[0] >= pair[1] {
960 return Err(malformed(
961 "enum values must be canonically sorted and unique",
962 ));
963 }
964 }
965 Ok(())
966 }
967 Node::Array {
968 items,
969 min_items,
970 max_items,
971 ..
972 } => {
973 if let ItemsPolicy::Schema(id) = items {
974 if !child_ok(*id) {
975 return Err(malformed("array item is not a back-edge"));
976 }
977 }
978 if let Some(max) = max_items {
979 if max < min_items {
980 return Err(malformed("array maxItems is less than minItems"));
981 }
982 }
983 Ok(())
984 }
985 Node::Tuple {
986 prefix,
987 tail,
988 min_items,
989 max_items,
990 ..
991 } => {
992 let ids = self
993 .refs_at(*prefix)
994 .ok_or_else(|| malformed("tuple prefix run out of range"))?;
995 if ids.is_empty() {
996 return Err(malformed("a tuple must have at least one prefix position"));
997 }
998 for id in ids {
999 if !child_ok(*id) {
1000 return Err(malformed("tuple prefix element is not a back-edge"));
1001 }
1002 }
1003 if let Some(t) = tail {
1004 if !child_ok(*t) {
1005 return Err(malformed("tuple tail is not a back-edge"));
1006 }
1007 }
1008 if let Some(max) = max_items {
1009 if max < min_items {
1010 return Err(malformed("tuple maxItems is less than minItems"));
1011 }
1012 }
1013 if tail.is_none() && *min_items > prefix.len {
1016 return Err(malformed("closed tuple minItems exceeds the prefix length"));
1017 }
1018 Ok(())
1019 }
1020 Node::Object {
1021 fields,
1022 required,
1023 dependent,
1024 dependent_required,
1025 ..
1026 } => {
1027 let props = self
1028 .props_at(*fields)
1029 .ok_or_else(|| malformed("object field run out of range"))?;
1030 let mut seen: FxHashMap<&str, ()> = FxHashMap::default();
1031 seen.reserve(props.len());
1032 for (name, child) in props {
1033 let key = self
1034 .str_at(*name)
1035 .ok_or_else(|| malformed("object field name out of range"))?;
1036 if seen.insert(key, ()).is_some() {
1037 return Err(malformed("duplicate object key"));
1038 }
1039 if !child_ok(*child) {
1040 return Err(malformed("object field value is not a back-edge"));
1041 }
1042 }
1043 self.validate_required(*required, props.len())?;
1044 self.validate_dependent_props(*dependent, child_ok)?;
1045 self.validate_dependent_required(*dependent_required)
1046 }
1047 Node::OpenObject {
1048 known,
1049 known_required,
1050 patterns,
1051 additional,
1052 property_names,
1053 min_properties,
1054 max_properties,
1055 dependent,
1056 dependent_required,
1057 } => {
1058 let known_props = self
1059 .props_at(*known)
1060 .ok_or_else(|| malformed("open object known props out of range"))?;
1061 let mut seen: FxHashMap<&str, ()> = FxHashMap::default();
1062 seen.reserve(known_props.len());
1063 for (name, child) in known_props {
1064 let key = self
1065 .str_at(*name)
1066 .ok_or_else(|| malformed("open object field name out of range"))?;
1067 if seen.insert(key, ()).is_some() {
1068 return Err(malformed("duplicate object key"));
1069 }
1070 if !child_ok(*child) {
1071 return Err(malformed("open object field value is not a back-edge"));
1072 }
1073 }
1074 self.validate_required(*known_required, known_props.len())?;
1075 let pattern_props = self
1076 .props_at(*patterns)
1077 .ok_or_else(|| malformed("open object pattern props out of range"))?;
1078 for (regex, child) in pattern_props {
1079 let re = self
1080 .str_at(*regex)
1081 .ok_or_else(|| malformed("open object pattern regex out of range"))?;
1082 validate_search_pattern(re)?;
1083 if !child_ok(*child) {
1084 return Err(malformed("open object pattern value is not a back-edge"));
1085 }
1086 }
1087 if let AdditionalPolicy::Schema(id) = additional {
1088 if !child_ok(*id) {
1089 return Err(malformed(
1090 "open object additional schema is not a back-edge",
1091 ));
1092 }
1093 }
1094 if let Some(pn) = property_names {
1095 if !child_ok(*pn) {
1096 return Err(malformed(
1097 "open object propertyNames schema is not a back-edge",
1098 ));
1099 }
1100 }
1101 if let (Some(lo), Some(hi)) = (min_properties, max_properties) {
1102 if hi < lo {
1103 return Err(malformed(
1104 "open object maxProperties is less than minProperties",
1105 ));
1106 }
1107 }
1108 self.validate_dependent_props(*dependent, child_ok)?;
1109 self.validate_dependent_required(*dependent_required)
1110 }
1111 Node::Union { branches } => {
1112 let ids = self
1113 .refs_at(*branches)
1114 .ok_or_else(|| malformed("union branch run out of range"))?;
1115 if ids.is_empty() {
1116 return Err(malformed("a type union must have at least one branch"));
1117 }
1118 for id in ids {
1119 if !child_ok(*id) {
1120 return Err(malformed("union branch is not a back-edge"));
1121 }
1122 }
1123 Ok(())
1124 }
1125 Node::Intersection { branches } => {
1126 let ids = self
1127 .refs_at(*branches)
1128 .ok_or_else(|| malformed("intersection branch run out of range"))?;
1129 if ids.is_empty() {
1130 return Err(malformed("an intersection must have at least one branch"));
1131 }
1132 for id in ids {
1133 if !child_ok(*id) {
1134 return Err(malformed("intersection branch is not a back-edge"));
1135 }
1136 }
1137 Ok(())
1138 }
1139 Node::ExactlyOne { branches } => {
1140 let ids = self
1141 .refs_at(*branches)
1142 .ok_or_else(|| malformed("exactly-one branch run out of range"))?;
1143 if ids.is_empty() {
1144 return Err(malformed("an exactly-one must have at least one branch"));
1145 }
1146 for id in ids {
1147 if !child_ok(*id) {
1148 return Err(malformed("exactly-one branch is not a back-edge"));
1149 }
1150 }
1151 Ok(())
1152 }
1153 Node::Not { inner } => {
1154 if !child_ok(*inner) {
1155 return Err(malformed("not target is not a back-edge"));
1156 }
1157 Ok(())
1158 }
1159 Node::Ref { def } => {
1160 if (*def as usize) >= self.def_targets.len() {
1161 return Err(malformed("ref names a def slot out of range"));
1162 }
1163 Ok(())
1164 }
1165 Node::DynamicRef {
1166 initial_target,
1167 anchor,
1168 } => {
1169 if initial_target.get() as usize >= self.nodes.len() {
1170 return Err(malformed("dynamic reference target is out of range"));
1171 }
1172 let record = self
1173 .anchors
1174 .get(anchor.get() as usize)
1175 .ok_or_else(|| malformed("dynamic reference anchor is out of range"))?;
1176 let resource = self
1177 .resources
1178 .get(record.resource.get() as usize)
1179 .ok_or_else(|| {
1180 malformed("dynamic reference anchor resource is out of range")
1181 })?;
1182 let dynamic_start = resource.dynamic_anchors.off as usize;
1183 let dynamic_end = dynamic_start
1184 .checked_add(resource.dynamic_anchors.len as usize)
1185 .ok_or_else(|| malformed("dynamic anchor run overflows"))?;
1186 let anchor_index = anchor.get() as usize;
1187 if !(dynamic_start..dynamic_end).contains(&anchor_index) {
1188 return Err(malformed(
1189 "dynamic reference names an anchor that is not dynamic",
1190 ));
1191 }
1192 let consistent = record.target == *initial_target
1193 || matches!(
1194 self.node(*initial_target),
1195 Some(Node::Ref { def }) if self.def_target(*def) == Some(record.target)
1196 );
1197 if !consistent {
1198 return Err(malformed(
1199 "dynamic reference target disagrees with its initial anchor",
1200 ));
1201 }
1202 Ok(())
1203 }
1204 Node::Unevaluated {
1205 scope, unevaluated, ..
1206 } => {
1207 if !child_ok(*scope) || !child_ok(*unevaluated) {
1208 return Err(malformed("unevaluated child is not a back-edge"));
1209 }
1210 Ok(())
1211 }
1212 Node::Unsupported { keyword, .. } => {
1213 self.str_at(*keyword)
1214 .ok_or_else(|| malformed("unsupported keyword reference out of range"))?;
1215 Ok(())
1216 }
1217 }
1218 }
1219
1220 fn validate_resources(&self) -> Result<(), CompileError> {
1221 if self.resources.is_empty() || self.resources.len() > MAX_RESOURCES {
1222 return Err(malformed("resource table is empty or exceeds its cap"));
1223 }
1224 if self.anchors.len() > MAX_ANCHORS {
1225 return Err(malformed("anchor table exceeds its cap"));
1226 }
1227 if self.node_resources.len() != self.nodes.len() {
1228 return Err(malformed("node-to-resource table length mismatch"));
1229 }
1230 if let Some(uri) = self.retrieval_uri {
1231 let uri = self
1232 .str_at(uri)
1233 .ok_or_else(|| malformed("retrieval URI string run is invalid"))?;
1234 validate_canonical_uri(uri)?;
1235 }
1236 for resource in &self.node_resources {
1237 if resource.get() as usize >= self.resources.len() {
1238 return Err(malformed("node is assigned to an invalid resource"));
1239 }
1240 }
1241 let mut previous_uri: Option<&str> = None;
1242 let mut anonymous_seen = false;
1243 let mut anchor_cursor = 0usize;
1244 for (index, resource) in self.resources.iter().enumerate() {
1245 if resource.root.get() as usize >= self.nodes.len() {
1246 return Err(malformed("resource root is out of range"));
1247 }
1248 let resource_id =
1249 ResourceId(u32::try_from(index).map_err(|_| limit("resource index"))?);
1250 if self.node_resource(resource.root) != Some(resource_id) {
1251 return Err(malformed("resource root has an inconsistent owner"));
1252 }
1253 match resource.canonical_uri {
1254 None => {
1255 if anonymous_seen || previous_uri.is_some() {
1256 return Err(malformed("anonymous resource ordering is not canonical"));
1257 }
1258 anonymous_seen = true;
1259 }
1260 Some(uri) => {
1261 let uri = self
1262 .str_at(uri)
1263 .ok_or_else(|| malformed("resource URI string run is invalid"))?;
1264 validate_canonical_uri(uri)?;
1265 if previous_uri.is_some_and(|previous| previous >= uri) {
1266 return Err(malformed("resource URIs are duplicate or not canonical"));
1267 }
1268 previous_uri = Some(uri);
1269 }
1270 }
1271 let mut names = rustc_hash::FxHashSet::default();
1272 for run in [resource.static_anchors, resource.dynamic_anchors] {
1273 if usize::try_from(run.off).map_err(|_| malformed("anchor run offset"))?
1274 != anchor_cursor
1275 {
1276 return Err(malformed("anchor runs are not in canonical order"));
1277 }
1278 let anchors = self.validate_anchor_run(run, resource_id)?;
1279 anchor_cursor = anchor_cursor
1280 .checked_add(anchors.len())
1281 .ok_or_else(|| malformed("anchor run overflows"))?;
1282 names
1283 .try_reserve(anchors.len())
1284 .map_err(|_| limit("anchor validation set"))?;
1285 for anchor in anchors {
1286 let name = self
1287 .str_at(anchor.name)
1288 .ok_or_else(|| malformed("anchor name string run is invalid"))?;
1289 if !names.insert(name) {
1290 return Err(malformed(
1291 "static and dynamic anchors collide inside one resource",
1292 ));
1293 }
1294 }
1295 }
1296 }
1297 if anchor_cursor != self.anchors.len() {
1298 return Err(malformed("anchor table has unclaimed records"));
1299 }
1300 Ok(())
1301 }
1302
1303 fn validate_anchor_run(
1304 &self,
1305 run: AnchorSlice,
1306 resource: ResourceId,
1307 ) -> Result<&[AnchorRecord], CompileError> {
1308 let start = usize::try_from(run.off).map_err(|_| malformed("anchor run offset"))?;
1309 let end = start
1310 .checked_add(usize::try_from(run.len).map_err(|_| malformed("anchor run length"))?)
1311 .ok_or_else(|| malformed("anchor run overflows"))?;
1312 let anchors = self
1313 .anchors
1314 .get(start..end)
1315 .ok_or_else(|| malformed("anchor run is out of range"))?;
1316 let mut previous_name: Option<&str> = None;
1317 for anchor in anchors {
1318 if anchor.resource != resource {
1319 return Err(malformed("anchor run contains another resource"));
1320 }
1321 if anchor.target.get() as usize >= self.nodes.len()
1322 || self.node_resource(anchor.target) != Some(resource)
1323 {
1324 return Err(malformed("anchor target or resource is invalid"));
1325 }
1326 let name = self
1327 .str_at(anchor.name)
1328 .ok_or_else(|| malformed("anchor name string run is invalid"))?;
1329 if previous_name.is_some_and(|previous| previous >= name) {
1330 return Err(malformed("anchor names are duplicate or not canonical"));
1331 }
1332 previous_name = Some(name);
1333 }
1334 Ok(anchors)
1335 }
1336
1337 fn validate_required(
1338 &self,
1339 required: BitSlice,
1340 field_count: usize,
1341 ) -> Result<(), CompileError> {
1342 if required.len as usize != field_count {
1343 return Err(malformed(
1344 "required bitset width does not match field count",
1345 ));
1346 }
1347 let words = field_count.div_ceil(64);
1348 let end = (required.off as usize)
1349 .checked_add(words)
1350 .ok_or_else(|| malformed("required bitset overflows"))?;
1351 let slice = self
1352 .required_bits
1353 .get(required.off as usize..end)
1354 .ok_or_else(|| malformed("required bitset out of range"))?;
1355 if field_count % 64 != 0 {
1357 if let Some(last) = slice.last() {
1358 let used = field_count % 64;
1359 if last >> used != 0 {
1360 return Err(malformed("required bitset has non-zero padding bits"));
1361 }
1362 }
1363 }
1364 Ok(())
1365 }
1366
1367 fn validate_dependent_props(
1369 &self,
1370 dependent: PropSlice,
1371 child_ok: impl Fn(NodeId) -> bool,
1372 ) -> Result<(), CompileError> {
1373 let props = self
1374 .props_at(dependent)
1375 .ok_or_else(|| malformed("dependentSchemas prop run out of range"))?;
1376 let mut seen: FxHashMap<&str, ()> = FxHashMap::default();
1377 seen.reserve(props.len());
1378 for (name, child) in props {
1379 let key = self
1380 .str_at(*name)
1381 .ok_or_else(|| malformed("dependentSchemas key out of range"))?;
1382 if seen.insert(key, ()).is_some() {
1383 return Err(malformed("duplicate dependentSchemas key"));
1384 }
1385 if !child_ok(*child) {
1386 return Err(malformed("dependentSchemas value is not a back-edge"));
1387 }
1388 }
1389 Ok(())
1390 }
1391
1392 fn validate_dependent_required(
1393 &self,
1394 dependent: DependentRequiredSlice,
1395 ) -> Result<(), CompileError> {
1396 let pairs = self
1397 .dependent_required_at(dependent)
1398 .ok_or_else(|| malformed("dependentRequired pair run out of range"))?;
1399 if pairs.len() > MAX_DEPENDENT_REQUIRED_PAIRS {
1400 return Err(malformed("dependentRequired pair count exceeds cap"));
1401 }
1402 let mut seen = rustc_hash::FxHashSet::default();
1403 seen.try_reserve(pairs.len())
1404 .map_err(|_| limit("dependentRequired validation set"))?;
1405 for pair in pairs {
1406 let trigger = self
1407 .str_at(pair.trigger)
1408 .ok_or_else(|| malformed("dependentRequired trigger out of range"))?;
1409 let required = self
1410 .str_at(pair.required)
1411 .ok_or_else(|| malformed("dependentRequired required name out of range"))?;
1412 if !seen.insert((trigger, required)) {
1413 return Err(malformed("duplicate dependentRequired pair"));
1414 }
1415 }
1416 Ok(())
1417 }
1418
1419 fn compute_hash(&self) -> [u8; 32] {
1420 let mut digests: Vec<[u8; 32]> = Vec::with_capacity(self.nodes.len());
1421 for node in &self.nodes {
1422 digests.push(self.node_digest(node, &digests));
1423 }
1424 let mut top = blake3::Hasher::new();
1425 top.update(b"MFIR-ir-v");
1426 top.update(&self.ir_version.to_le_bytes());
1427 top.update(&digests[self.root.get() as usize]);
1428 top.update(&(self.def_targets.len() as u64).to_le_bytes());
1431 for target in &self.def_targets {
1432 top.update(&digests[target.get() as usize]);
1433 }
1434 top.update(&[self.options.object_closure as u8]);
1435 top.update(&[u8::from(self.options.format_assertion)]);
1436 top.update(&[self.options.unsupported_policy as u8]);
1437 top.update(&self.options.max_diagnostics.to_le_bytes());
1438 match self.retrieval_uri() {
1439 None => {
1440 top.update(&[0u8]);
1441 }
1442 Some(uri) => {
1443 top.update(&[1u8]);
1444 feed_bytes(&mut top, uri.as_bytes())
1445 }
1446 };
1447 top.update(&(self.resources.len() as u64).to_le_bytes());
1448 for (index, resource) in self.resources.iter().enumerate() {
1449 match resource.canonical_uri.and_then(|uri| self.str_at(uri)) {
1450 None => {
1451 top.update(&[0u8]);
1452 }
1453 Some(uri) => {
1454 top.update(&[1u8]);
1455 feed_bytes(&mut top, uri.as_bytes())
1456 }
1457 };
1458 top.update(&resource.root.get().to_le_bytes());
1459 let resource_id = ResourceId(u32::try_from(index).unwrap_or(u32::MAX));
1460 for (dynamic, run) in [
1461 (false, resource.static_anchors),
1462 (true, resource.dynamic_anchors),
1463 ] {
1464 top.update(&[u8::from(dynamic)]);
1465 let start = run.off as usize;
1466 let end = start.saturating_add(run.len as usize);
1467 let anchors = self.anchors.get(start..end).unwrap_or(&[]);
1468 top.update(&(anchors.len() as u64).to_le_bytes());
1469 for anchor in anchors {
1470 top.update(&resource_id.get().to_le_bytes());
1471 feed_bytes(&mut top, self.str_at(anchor.name).unwrap_or("").as_bytes());
1472 top.update(&anchor.target.get().to_le_bytes());
1473 }
1474 }
1475 }
1476 top.update(&(self.node_resources.len() as u64).to_le_bytes());
1477 for resource in &self.node_resources {
1478 top.update(&resource.get().to_le_bytes());
1479 }
1480 *top.finalize().as_bytes()
1481 }
1482
1483 fn node_digest(&self, node: &Node, digests: &[[u8; 32]]) -> [u8; 32] {
1484 let mut h = blake3::Hasher::new();
1485 match node {
1486 Node::Null => h.update(&[0u8]),
1487 Node::Boolean => h.update(&[1u8]),
1488 Node::Never => h.update(&[16u8]),
1489 Node::StringConst { value } => {
1490 h.update(&[2u8]);
1491 feed_bytes(&mut h, self.str_at(*value).unwrap_or("").as_bytes());
1492 &mut h
1493 }
1494 Node::StringPattern {
1495 regex,
1496 min_len,
1497 max_len,
1498 charset,
1499 } => {
1500 h.update(&[3u8]);
1501 feed_bytes(&mut h, self.str_at(*regex).unwrap_or("").as_bytes());
1502 feed_opt_u32(&mut h, *min_len);
1503 feed_opt_u32(&mut h, *max_len);
1504 h.update(&[charset_tag(*charset)]);
1505 &mut h
1506 }
1507 Node::Integer {
1508 minimum,
1509 maximum,
1510 multiple_of,
1511 } => {
1512 h.update(&[4u8]);
1513 feed_opt_i64(&mut h, *minimum);
1514 feed_opt_i64(&mut h, *maximum);
1515 feed_opt_u64(&mut h, *multiple_of);
1516 &mut h
1517 }
1518 Node::Number {
1519 integer_only,
1520 minimum,
1521 maximum,
1522 multiple_of,
1523 } => {
1524 h.update(&[12u8]);
1525 h.update(&[u8::from(*integer_only)]);
1526 feed_opt_decimal_bound(&mut h, *minimum);
1527 feed_opt_decimal_bound(&mut h, *maximum);
1528 match multiple_of {
1529 None => h.update(&[0u8]),
1530 Some((coef, exp)) => h
1531 .update(&[1u8])
1532 .update(&coef.to_le_bytes())
1533 .update(&exp.to_le_bytes()),
1534 };
1535 &mut h
1536 }
1537 Node::LexicalNumber { regex } => {
1538 h.update(&[17u8]);
1539 feed_bytes(&mut h, self.str_at(*regex).unwrap_or("").as_bytes());
1540 &mut h
1541 }
1542 Node::Enum { values } => {
1543 h.update(&[5u8]);
1544 let lits = self.lits_at(*values).unwrap_or(&[]);
1545 h.update(&(lits.len() as u64).to_le_bytes());
1546 for lit in lits {
1547 feed_bytes(&mut h, &lit_bytes(lit));
1548 }
1549 &mut h
1550 }
1551 Node::Array {
1552 items,
1553 min_items,
1554 max_items,
1555 unique_items,
1556 contains,
1557 items_annotates,
1558 } => {
1559 h.update(&[6u8]);
1560 match items {
1561 ItemsPolicy::Schema(id) => {
1562 h.update(&[0u8]);
1563 h.update(&digests[id.get() as usize]);
1564 }
1565 ItemsPolicy::AllowAny => {
1566 h.update(&[1u8]);
1567 }
1568 }
1569 h.update(&min_items.to_le_bytes());
1570 feed_opt_u32(&mut h, *max_items);
1571 h.update(&[u8::from(*unique_items)]);
1572 feed_contains(&mut h, contains, digests);
1573 h.update(&[u8::from(*items_annotates)]);
1574 &mut h
1575 }
1576 Node::Tuple {
1577 prefix,
1578 tail,
1579 min_items,
1580 max_items,
1581 unique_items,
1582 tail_annotates,
1583 contains,
1584 } => {
1585 h.update(&[13u8]);
1586 let ids = self.refs_at(*prefix).unwrap_or(&[]);
1587 h.update(&(ids.len() as u64).to_le_bytes());
1588 for id in ids {
1589 h.update(&digests[id.get() as usize]);
1590 }
1591 match tail {
1592 None => h.update(&[0u8]),
1593 Some(t) => h.update(&[1u8]).update(&digests[t.get() as usize]),
1594 };
1595 h.update(&min_items.to_le_bytes());
1596 feed_opt_u32(&mut h, *max_items);
1597 h.update(&[u8::from(*unique_items)]);
1598 feed_contains(&mut h, contains, digests);
1599 h.update(&[u8::from(*tail_annotates)]);
1600 &mut h
1601 }
1602 Node::Object {
1603 fields,
1604 required,
1605 closure,
1606 dependent,
1607 dependent_required,
1608 } => {
1609 h.update(&[7u8]);
1610 let props = self.props_at(*fields).unwrap_or(&[]);
1611 h.update(&(props.len() as u64).to_le_bytes());
1612 for ((name, child), idx) in props.iter().zip(0u32..) {
1614 feed_bytes(&mut h, self.str_at(*name).unwrap_or("").as_bytes());
1615 h.update(&digests[child.get() as usize]);
1616 h.update(&[u8::from(self.is_required(*required, idx))]);
1617 }
1618 h.update(&[closure_tag(*closure)]);
1619 self.feed_dependent(&mut h, *dependent, digests);
1620 self.feed_dependent_required(&mut h, *dependent_required);
1621 &mut h
1622 }
1623 Node::OpenObject {
1624 known,
1625 known_required,
1626 patterns,
1627 additional,
1628 property_names,
1629 min_properties,
1630 max_properties,
1631 dependent,
1632 dependent_required,
1633 } => {
1634 h.update(&[14u8]);
1635 let kp = self.props_at(*known).unwrap_or(&[]);
1636 h.update(&(kp.len() as u64).to_le_bytes());
1637 for ((name, child), idx) in kp.iter().zip(0u32..) {
1638 feed_bytes(&mut h, self.str_at(*name).unwrap_or("").as_bytes());
1639 h.update(&digests[child.get() as usize]);
1640 h.update(&[u8::from(self.is_required(*known_required, idx))]);
1641 }
1642 let pp = self.props_at(*patterns).unwrap_or(&[]);
1643 h.update(&(pp.len() as u64).to_le_bytes());
1644 for (regex, child) in pp {
1645 feed_bytes(&mut h, self.str_at(*regex).unwrap_or("").as_bytes());
1646 h.update(&digests[child.get() as usize]);
1647 }
1648 match additional {
1649 AdditionalPolicy::Forbid => h.update(&[0u8]),
1650 AdditionalPolicy::AllowAny => h.update(&[1u8]),
1651 AdditionalPolicy::Schema(id) => {
1652 h.update(&[2u8]).update(&digests[id.get() as usize])
1653 }
1654 AdditionalPolicy::Open => h.update(&[3u8]),
1655 };
1656 match property_names {
1657 None => h.update(&[0u8]),
1658 Some(id) => h.update(&[1u8]).update(&digests[id.get() as usize]),
1659 };
1660 feed_opt_u32(&mut h, *min_properties);
1661 feed_opt_u32(&mut h, *max_properties);
1662 self.feed_dependent(&mut h, *dependent, digests);
1663 self.feed_dependent_required(&mut h, *dependent_required);
1664 &mut h
1665 }
1666 Node::Union { branches } => {
1667 h.update(&[9u8]);
1668 let ids = self.refs_at(*branches).unwrap_or(&[]);
1669 h.update(&(ids.len() as u64).to_le_bytes());
1670 for id in ids {
1671 h.update(&digests[id.get() as usize]);
1672 }
1673 &mut h
1674 }
1675 Node::Intersection { branches } => {
1676 h.update(&[10u8]);
1677 let ids = self.refs_at(*branches).unwrap_or(&[]);
1678 h.update(&(ids.len() as u64).to_le_bytes());
1679 for id in ids {
1680 h.update(&digests[id.get() as usize]);
1681 }
1682 &mut h
1683 }
1684 Node::ExactlyOne { branches } => {
1685 h.update(&[11u8]);
1686 let ids = self.refs_at(*branches).unwrap_or(&[]);
1687 h.update(&(ids.len() as u64).to_le_bytes());
1688 for id in ids {
1689 h.update(&digests[id.get() as usize]);
1690 }
1691 &mut h
1692 }
1693 Node::Not { inner } => {
1694 h.update(&[15u8]);
1695 h.update(&digests[inner.get() as usize]);
1696 &mut h
1697 }
1698 Node::Ref { def } => {
1699 h.update(&[17u8]);
1700 h.update(&def.to_le_bytes());
1701 &mut h
1702 }
1703 Node::DynamicRef {
1704 initial_target,
1705 anchor,
1706 } => {
1707 h.update(&[19u8]);
1708 h.update(&initial_target.get().to_le_bytes());
1709 h.update(&anchor.get().to_le_bytes());
1710 &mut h
1711 }
1712 Node::Unevaluated {
1713 kind,
1714 scope,
1715 unevaluated,
1716 } => {
1717 h.update(&[18u8]);
1718 h.update(&[*kind as u8]);
1719 h.update(&digests[scope.get() as usize]);
1720 h.update(&digests[unevaluated.get() as usize]);
1721 &mut h
1722 }
1723 Node::Unsupported { keyword, reason } => {
1724 h.update(&[8u8]);
1725 feed_bytes(&mut h, self.str_at(*keyword).unwrap_or("").as_bytes());
1726 h.update(&(*reason as u32).to_le_bytes());
1727 &mut h
1728 }
1729 };
1730 *h.finalize().as_bytes()
1731 }
1732
1733 fn feed_dependent(&self, h: &mut blake3::Hasher, dependent: PropSlice, digests: &[[u8; 32]]) {
1736 let props = self.props_at(dependent).unwrap_or(&[]);
1737 h.update(&(props.len() as u64).to_le_bytes());
1738 for (name, child) in props {
1739 feed_bytes(h, self.str_at(*name).unwrap_or("").as_bytes());
1740 h.update(&digests[child.get() as usize]);
1741 }
1742 }
1743
1744 fn feed_dependent_required(&self, h: &mut blake3::Hasher, dependent: DependentRequiredSlice) {
1745 let mut pairs: Vec<(&[u8], &[u8])> = self
1746 .dependent_required_at(dependent)
1747 .unwrap_or(&[])
1748 .iter()
1749 .map(|pair| {
1750 (
1751 self.str_at(pair.trigger).unwrap_or("").as_bytes(),
1752 self.str_at(pair.required).unwrap_or("").as_bytes(),
1753 )
1754 })
1755 .collect();
1756 pairs.sort_unstable();
1757 h.update(&(pairs.len() as u64).to_le_bytes());
1758 for (trigger, required) in pairs {
1759 feed_bytes(h, trigger);
1760 feed_bytes(h, required);
1761 }
1762 }
1763}
1764
1765fn feed_bytes(h: &mut blake3::Hasher, bytes: &[u8]) {
1766 h.update(&(bytes.len() as u64).to_le_bytes());
1767 h.update(bytes);
1768}
1769
1770fn feed_opt_u32(h: &mut blake3::Hasher, v: Option<u32>) {
1771 match v {
1772 None => h.update(&[0u8]),
1773 Some(x) => h.update(&[1u8]).update(&x.to_le_bytes()),
1774 };
1775}
1776
1777fn feed_opt_i64(h: &mut blake3::Hasher, v: Option<i64>) {
1778 match v {
1779 None => h.update(&[0u8]),
1780 Some(x) => h.update(&[1u8]).update(&x.to_le_bytes()),
1781 };
1782}
1783
1784fn feed_opt_decimal_bound(h: &mut blake3::Hasher, v: Option<(i128, u32, bool)>) {
1785 match v {
1786 None => h.update(&[0u8]),
1787 Some((x, scale, excl)) => h
1788 .update(&[1u8])
1789 .update(&x.to_le_bytes())
1790 .update(&scale.to_le_bytes())
1791 .update(&[u8::from(excl)]),
1792 };
1793}
1794
1795pub(crate) fn decimal_cmp(a: (i128, u32), b: (i128, u32)) -> std::cmp::Ordering {
1797 use std::cmp::Ordering;
1798 if a.0 == 0 || b.0 == 0 || a.0.is_negative() != b.0.is_negative() {
1799 return a.0.signum().cmp(&b.0.signum());
1800 }
1801 let (a_digits, a_len) = decimal_digits(a.0.unsigned_abs());
1802 let (b_digits, b_len) = decimal_digits(b.0.unsigned_abs());
1803 let a_exponent = i64::try_from(a_len).unwrap_or(i64::MAX) - i64::from(a.1);
1804 let b_exponent = i64::try_from(b_len).unwrap_or(i64::MAX) - i64::from(b.1);
1805 let mut magnitude = a_exponent.cmp(&b_exponent);
1806 if magnitude == Ordering::Equal {
1807 for index in 0..a_len.max(b_len) {
1808 magnitude = a_digits[index].cmp(&b_digits[index]);
1809 if magnitude != Ordering::Equal {
1810 break;
1811 }
1812 }
1813 }
1814 if a.0.is_negative() {
1815 magnitude.reverse()
1816 } else {
1817 magnitude
1818 }
1819}
1820
1821fn decimal_digits(mut value: u128) -> ([u8; 39], usize) {
1822 let mut reverse = [0u8; 39];
1823 let mut length = 0;
1824 while value > 0 {
1825 reverse[length] = u8::try_from(value % 10).unwrap_or(0);
1826 value /= 10;
1827 length += 1;
1828 }
1829 let mut digits = [0u8; 39];
1830 for index in 0..length {
1831 digits[index] = reverse[length - index - 1];
1832 }
1833 (digits, length)
1834}
1835
1836fn feed_contains(
1837 h: &mut blake3::Hasher,
1838 contains: &Option<ContainsConstraint>,
1839 digests: &[[u8; 32]],
1840) {
1841 match contains {
1842 None => {
1843 h.update(&[0u8]);
1844 }
1845 Some(c) => {
1846 h.update(&[1u8]);
1847 match c.policy {
1848 ContainsPolicy::Never => h.update(&[0u8]),
1849 ContainsPolicy::Always => h.update(&[1u8]),
1850 ContainsPolicy::Schema(id) => h.update(&[2u8]).update(&digests[id.get() as usize]),
1851 };
1852 h.update(&c.min.to_le_bytes());
1853 feed_opt_u32(h, c.max);
1854 }
1855 }
1856}
1857
1858fn feed_opt_u64(h: &mut blake3::Hasher, v: Option<u64>) {
1859 match v {
1860 None => h.update(&[0u8]),
1861 Some(x) => h.update(&[1u8]).update(&x.to_le_bytes()),
1862 };
1863}
1864
1865fn charset_tag(c: Charset) -> u8 {
1866 match c {
1867 Charset::Utf8Any => 0,
1868 Charset::AsciiPrintableNoQuoteBackslash => 1,
1869 Charset::Utf8CountedCodepoints => 2,
1870 }
1871}
1872
1873fn closure_tag(c: ObjectClosure) -> u8 {
1874 match c {
1875 ObjectClosure::RejectOpenObjects => 0,
1876 ObjectClosure::AssumeClosedProfile => 1,
1877 ObjectClosure::Forbidden => 2,
1878 ObjectClosure::AllowOpenProfile => 3,
1879 }
1880}
1881
1882fn lit_bytes(lit: &ScalarLit) -> Vec<u8> {
1883 match lit {
1884 ScalarLit::Null => vec![0u8],
1885 ScalarLit::Bool(b) => vec![1u8, u8::from(*b)],
1886 ScalarLit::Int(i) => {
1887 let mut v = vec![2u8];
1888 v.extend_from_slice(&i.to_le_bytes());
1889 v
1890 }
1891 ScalarLit::Str(s) => {
1892 let mut v = vec![3u8];
1893 v.extend_from_slice(s.as_bytes());
1894 v
1895 }
1896 ScalarLit::Json(s) => {
1897 let mut v = vec![4u8];
1898 v.extend_from_slice(s.as_bytes());
1899 v
1900 }
1901 }
1902}
1903
1904fn validate_pattern(regex: &str) -> Result<(), CompileError> {
1906 if has_anchor_outside_brackets(regex.as_bytes()) {
1907 return Err(unsupported("explicit anchors are not allowed"));
1908 }
1909 validate_pattern_body(regex, false)
1910}
1911
1912pub(crate) fn validate_search_pattern(regex: &str) -> Result<(), CompileError> {
1915 validate_pattern_body(regex, true)
1916}
1917
1918fn validate_pattern_body(regex: &str, allow_leading_assertions: bool) -> Result<(), CompileError> {
1919 use crate::frontend::regex::{analyze_compiled_pattern, PatternAnalysis};
1920
1921 match analyze_compiled_pattern(regex) {
1922 PatternAnalysis::Ordinary => Ok(()),
1923 PatternAnalysis::Leading(_) if allow_leading_assertions => Ok(()),
1924 PatternAnalysis::Leading(_) => {
1925 Err(unsupported("unsupported regex construct").with_observed("look-ahead"))
1926 }
1927 PatternAnalysis::Unsupported(what) => {
1928 Err(unsupported("unsupported regex construct").with_observed(what))
1929 }
1930 PatternAnalysis::Malformed(what) => {
1931 Err(malformed("malformed regex syntax").with_observed(what))
1932 }
1933 }
1934}
1935
1936pub(crate) fn has_anchor_outside_brackets(bytes: &[u8]) -> bool {
1939 let mut in_bracket = false;
1940 let mut i = 0;
1941 while i < bytes.len() {
1942 match bytes[i] {
1943 b'\\' => i += 1,
1944 b'[' if !in_bracket => in_bracket = true,
1945 b']' if in_bracket => in_bracket = false,
1946 b'^' | b'$' if !in_bracket => return true,
1947 _ => {}
1948 }
1949 i += 1;
1950 }
1951 false
1952}
1953
1954fn err(code: ErrorCode, message: &'static str) -> CompileError {
1955 CompileError::new(code, Stage::L2, message)
1956}
1957
1958fn malformed(message: &'static str) -> CompileError {
1959 CompileError::new(ErrorCode::Malformed, Stage::L2, message)
1960}
1961
1962fn validate_canonical_uri(value: &str) -> Result<(), CompileError> {
1963 let uri = Uri::parse(value).map_err(|_| malformed("canonical resource URI is invalid"))?;
1964 if uri.fragment().is_some() || uri.normalize().as_str() != value {
1965 return Err(malformed(
1966 "resource URI is not fragmentless canonical RFC 3986 form",
1967 ));
1968 }
1969 Ok(())
1970}
1971
1972fn unsupported(message: &'static str) -> CompileError {
1973 CompileError::new(ErrorCode::Unsupported, Stage::L2, message)
1974}
1975
1976pub struct Builder {
1979 nodes: Vec<Node>,
1980 strings: Vec<u8>,
1981 literals: Vec<ScalarLit>,
1982 props: Vec<(StrRef, NodeId)>,
1983 dependent_required_pairs: Vec<DependentRequiredPair>,
1984 required_bits: Vec<u64>,
1985 node_refs: Vec<NodeId>,
1986 def_targets: Vec<Option<NodeId>>,
1987 diagnostics: Vec<Diagnostic>,
1988 literal_str_bytes: usize,
1989 node_intern: FxHashMap<(ResourceId, Node), NodeId>,
1990 node_resources: Vec<ResourceId>,
1991 current_resource: ResourceId,
1992 str_intern: FxHashMap<String, StrRef>,
1993 lit_intern: FxHashMap<Vec<ScalarLit>, LitSlice>,
1994 prop_intern: FxHashMap<Vec<(StrRef, NodeId)>, PropSlice>,
1995 dependent_required_intern: FxHashMap<Vec<DependentRequiredPair>, DependentRequiredSlice>,
1996 bit_intern: FxHashMap<Vec<u64>, BitSlice>,
1997 ref_intern: FxHashMap<Vec<NodeId>, RefSlice>,
1998 options: CompileOptions,
1999}
2000
2001impl Builder {
2002 #[must_use]
2004 pub fn new(options: CompileOptions) -> Self {
2005 Self {
2006 nodes: Vec::new(),
2007 strings: Vec::new(),
2008 literals: Vec::new(),
2009 props: Vec::new(),
2010 dependent_required_pairs: Vec::new(),
2011 required_bits: Vec::new(),
2012 node_refs: Vec::new(),
2013 def_targets: Vec::new(),
2014 diagnostics: Vec::new(),
2015 literal_str_bytes: 0,
2016 node_intern: FxHashMap::default(),
2017 node_resources: Vec::new(),
2018 current_resource: ResourceId(0),
2019 str_intern: FxHashMap::default(),
2020 lit_intern: FxHashMap::default(),
2021 prop_intern: FxHashMap::default(),
2022 dependent_required_intern: FxHashMap::default(),
2023 bit_intern: FxHashMap::default(),
2024 ref_intern: FxHashMap::default(),
2025 options,
2026 }
2027 }
2028
2029 fn intern_str(&mut self, s: &str) -> Result<StrRef, CompileError> {
2031 if let Some(r) = self.str_intern.get(s) {
2032 return Ok(*r);
2033 }
2034 if self
2035 .strings
2036 .len()
2037 .checked_add(s.len())
2038 .is_none_or(|n| n > MAX_STRINGS_BYTES)
2039 {
2040 return Err(limit("strings blob cap"));
2041 }
2042 let off = u32::try_from(self.strings.len()).map_err(|_| limit("strings blob"))?;
2043 let len = u32::try_from(s.len()).map_err(|_| limit("string length"))?;
2044 self.strings.extend_from_slice(s.as_bytes());
2045 let r = StrRef { off, len };
2046 self.str_intern.insert(s.to_owned(), r);
2047 Ok(r)
2048 }
2049
2050 fn intern_lits(&mut self, lits: Vec<ScalarLit>) -> Result<LitSlice, CompileError> {
2051 if let Some(r) = self.lit_intern.get(&lits) {
2052 return Ok(*r);
2053 }
2054 if self
2055 .literals
2056 .len()
2057 .checked_add(lits.len())
2058 .is_none_or(|n| n > MAX_LITERALS)
2059 {
2060 return Err(limit("literals cap"));
2061 }
2062 if self
2063 .literal_str_bytes
2064 .checked_add(literal_str_bytes(&lits))
2065 .is_none_or(|n| n > MAX_LITERAL_STR_BYTES)
2066 {
2067 return Err(limit("literal string bytes cap"));
2068 }
2069 self.literal_str_bytes += literal_str_bytes(&lits);
2070 let off = u32::try_from(self.literals.len()).map_err(|_| limit("literals blob"))?;
2071 let len = u32::try_from(lits.len()).map_err(|_| limit("literal run"))?;
2072 self.literals.extend_from_slice(&lits);
2073 let r = LitSlice { off, len };
2074 self.lit_intern.insert(lits, r);
2075 Ok(r)
2076 }
2077
2078 fn intern_props(&mut self, pairs: Vec<(StrRef, NodeId)>) -> Result<PropSlice, CompileError> {
2079 if let Some(r) = self.prop_intern.get(&pairs) {
2080 return Ok(*r);
2081 }
2082 if self
2083 .props
2084 .len()
2085 .checked_add(pairs.len())
2086 .is_none_or(|n| n > MAX_PROPS)
2087 {
2088 return Err(limit("props cap"));
2089 }
2090 let off = u32::try_from(self.props.len()).map_err(|_| limit("props blob"))?;
2091 let len = u32::try_from(pairs.len()).map_err(|_| limit("prop run"))?;
2092 self.props.extend_from_slice(&pairs);
2093 let r = PropSlice { off, len };
2094 self.prop_intern.insert(pairs, r);
2095 Ok(r)
2096 }
2097
2098 fn intern_dependent_required(
2103 &mut self,
2104 mut groups: Vec<(String, Vec<String>)>,
2105 ) -> Result<DependentRequiredSlice, CompileError> {
2106 let total_edges = groups
2107 .iter()
2108 .try_fold(0usize, |total, (_, required)| {
2109 total.checked_add(required.len())
2110 })
2111 .ok_or_else(|| limit("dependentRequired pair count"))?;
2112 if total_edges > MAX_DEPENDENT_REQUIRED_PAIRS {
2113 return Err(limit("dependentRequired pair count"));
2114 }
2115 groups.sort_unstable_by(|(a, _), (b, _)| a.cmp(b));
2116 if groups.windows(2).any(|pair| pair[0].0 == pair[1].0) {
2117 return Err(malformed("duplicate dependentRequired trigger"));
2118 }
2119 for (_, required) in &mut groups {
2120 required.sort_unstable();
2121 if required.windows(2).any(|pair| pair[0] == pair[1]) {
2122 return Err(malformed("duplicate dependentRequired pair"));
2123 }
2124 }
2125 let mut refs = Vec::new();
2126 refs.try_reserve_exact(total_edges)
2127 .map_err(|_| limit("dependentRequired pair arena"))?;
2128 for (trigger, required) in &groups {
2129 let trigger_ref = self.intern_str(trigger)?;
2130 for name in required {
2131 refs.push(DependentRequiredPair {
2132 trigger: trigger_ref,
2133 required: self.intern_str(name)?,
2134 });
2135 }
2136 }
2137 if let Some(r) = self.dependent_required_intern.get(&refs) {
2138 return Ok(*r);
2139 }
2140 if self
2141 .dependent_required_pairs
2142 .len()
2143 .checked_add(refs.len())
2144 .is_none_or(|n| n > MAX_DEPENDENT_REQUIRED_PAIRS)
2145 {
2146 return Err(limit("dependentRequired pair arena"));
2147 }
2148 let off = u32::try_from(self.dependent_required_pairs.len())
2149 .map_err(|_| limit("dependentRequired pair arena"))?;
2150 let len = u32::try_from(refs.len()).map_err(|_| limit("dependentRequired pair run"))?;
2151 self.dependent_required_pairs
2152 .try_reserve_exact(refs.len())
2153 .map_err(|_| limit("dependentRequired pair arena"))?;
2154 self.dependent_required_pairs.extend_from_slice(&refs);
2155 let slice = DependentRequiredSlice { off, len };
2156 self.dependent_required_intern.insert(refs, slice);
2157 Ok(slice)
2158 }
2159
2160 fn intern_bits(&mut self, words: Vec<u64>, field_count: u32) -> Result<BitSlice, CompileError> {
2161 if let Some(r) = self.bit_intern.get(&words) {
2162 return Ok(BitSlice {
2163 off: r.off,
2164 len: field_count,
2165 });
2166 }
2167 if self
2168 .required_bits
2169 .len()
2170 .checked_add(words.len())
2171 .is_none_or(|n| n > MAX_REQUIRED_WORDS)
2172 {
2173 return Err(limit("required words cap"));
2174 }
2175 let off = u32::try_from(self.required_bits.len()).map_err(|_| limit("required blob"))?;
2176 self.required_bits.extend_from_slice(&words);
2177 let r = BitSlice {
2178 off,
2179 len: field_count,
2180 };
2181 self.bit_intern.insert(words, r);
2182 Ok(r)
2183 }
2184
2185 fn intern_refs(&mut self, refs: Vec<NodeId>) -> Result<RefSlice, CompileError> {
2186 if let Some(r) = self.ref_intern.get(&refs) {
2187 return Ok(*r);
2188 }
2189 if self
2190 .node_refs
2191 .len()
2192 .checked_add(refs.len())
2193 .is_none_or(|n| n > MAX_NODE_REFS)
2194 {
2195 return Err(limit("node refs cap"));
2196 }
2197 let off = u32::try_from(self.node_refs.len()).map_err(|_| limit("node refs blob"))?;
2198 let len = u32::try_from(refs.len()).map_err(|_| limit("node ref run"))?;
2199 self.node_refs.extend_from_slice(&refs);
2200 let r = RefSlice { off, len };
2201 self.ref_intern.insert(refs, r);
2202 Ok(r)
2203 }
2204
2205 fn intern_node(&mut self, node: Node) -> Result<NodeId, CompileError> {
2206 if let Some(id) = self.node_intern.get(&(self.current_resource, node.clone())) {
2207 return Ok(*id);
2208 }
2209 if self.nodes.len() >= MAX_NODES {
2210 return Err(limit("node cap"));
2211 }
2212 let id = NodeId::try_from(self.nodes.len()).map_err(|_| limit("node count"))?;
2213 self.nodes.push(node.clone());
2214 self.node_resources.push(self.current_resource);
2215 self.node_intern.insert((self.current_resource, node), id);
2216 Ok(id)
2217 }
2218
2219 pub(crate) fn set_current_resource(&mut self, resource: ResourceId) -> ResourceId {
2220 std::mem::replace(&mut self.current_resource, resource)
2221 }
2222
2223 pub fn null(&mut self) -> Result<NodeId, CompileError> {
2225 self.intern_node(Node::Null)
2226 }
2227
2228 pub fn boolean(&mut self) -> Result<NodeId, CompileError> {
2230 self.intern_node(Node::Boolean)
2231 }
2232
2233 pub fn never(&mut self) -> Result<NodeId, CompileError> {
2235 self.intern_node(Node::Never)
2236 }
2237
2238 pub fn string_const(&mut self, value: &str) -> Result<NodeId, CompileError> {
2240 let value = self.intern_str(value)?;
2241 self.intern_node(Node::StringConst { value })
2242 }
2243
2244 pub fn string_pattern(
2246 &mut self,
2247 regex: &str,
2248 min_len: Option<u32>,
2249 max_len: Option<u32>,
2250 charset: Charset,
2251 ) -> Result<NodeId, CompileError> {
2252 let regex = self.intern_str(regex)?;
2253 self.intern_node(Node::StringPattern {
2254 regex,
2255 min_len,
2256 max_len,
2257 charset,
2258 })
2259 }
2260
2261 pub fn integer(
2263 &mut self,
2264 minimum: Option<i64>,
2265 maximum: Option<i64>,
2266 ) -> Result<NodeId, CompileError> {
2267 self.integer_multiple_of(minimum, maximum, None)
2268 }
2269
2270 pub fn integer_multiple_of(
2272 &mut self,
2273 minimum: Option<i64>,
2274 maximum: Option<i64>,
2275 multiple_of: Option<u64>,
2276 ) -> Result<NodeId, CompileError> {
2277 if multiple_of == Some(0) {
2278 return Err(malformed("multipleOf must be a positive integer"));
2279 }
2280 self.intern_node(Node::Integer {
2281 minimum,
2282 maximum,
2283 multiple_of,
2284 })
2285 }
2286
2287 pub fn number(&mut self) -> Result<NodeId, CompileError> {
2289 self.number_range(None, None, None)
2290 }
2291
2292 pub fn number_range(
2295 &mut self,
2296 minimum: Option<(i128, u32, bool)>,
2297 maximum: Option<(i128, u32, bool)>,
2298 multiple_of: Option<(u64, u32)>,
2299 ) -> Result<NodeId, CompileError> {
2300 self.intern_node(Node::Number {
2301 integer_only: false,
2302 minimum,
2303 maximum,
2304 multiple_of,
2305 })
2306 }
2307
2308 pub fn integer_number_range(
2310 &mut self,
2311 minimum: Option<(i128, u32, bool)>,
2312 maximum: Option<(i128, u32, bool)>,
2313 multiple_of: Option<(u64, u32)>,
2314 ) -> Result<NodeId, CompileError> {
2315 self.intern_node(Node::Number {
2316 integer_only: true,
2317 minimum,
2318 maximum,
2319 multiple_of,
2320 })
2321 }
2322
2323 pub fn lexical_number(&mut self, regex: &str) -> Result<NodeId, CompileError> {
2325 validate_pattern(regex)?;
2326 let regex = self.intern_str(regex)?;
2327 self.intern_node(Node::LexicalNumber { regex })
2328 }
2329
2330 pub fn enum_values(&mut self, mut values: Vec<ScalarLit>) -> Result<NodeId, CompileError> {
2332 if values.is_empty() {
2333 return Err(malformed("an enum must have at least one value"));
2334 }
2335 values.sort();
2336 if values.windows(2).any(|w| w[0] == w[1]) {
2337 return Err(malformed("duplicate enum value"));
2338 }
2339 let values = self.intern_lits(values)?;
2340 self.intern_node(Node::Enum { values })
2341 }
2342
2343 pub fn array(
2345 &mut self,
2346 items: NodeId,
2347 min_items: u32,
2348 max_items: Option<u32>,
2349 ) -> Result<NodeId, CompileError> {
2350 self.array_unique(items, min_items, max_items, false)
2351 }
2352
2353 pub fn array_unique(
2355 &mut self,
2356 items: NodeId,
2357 min_items: u32,
2358 max_items: Option<u32>,
2359 unique_items: bool,
2360 ) -> Result<NodeId, CompileError> {
2361 self.array_full(
2362 ItemsPolicy::Schema(items),
2363 min_items,
2364 max_items,
2365 unique_items,
2366 None,
2367 true,
2368 )
2369 }
2370
2371 pub fn array_open(
2373 &mut self,
2374 min_items: u32,
2375 max_items: Option<u32>,
2376 unique_items: bool,
2377 ) -> Result<NodeId, CompileError> {
2378 self.array_full(
2379 ItemsPolicy::AllowAny,
2380 min_items,
2381 max_items,
2382 unique_items,
2383 None,
2384 true,
2385 )
2386 }
2387
2388 #[allow(clippy::too_many_arguments)]
2391 pub fn array_full(
2392 &mut self,
2393 items: ItemsPolicy,
2394 min_items: u32,
2395 max_items: Option<u32>,
2396 unique_items: bool,
2397 contains: Option<ContainsConstraint>,
2398 items_annotates: bool,
2399 ) -> Result<NodeId, CompileError> {
2400 self.intern_node(Node::Array {
2401 items,
2402 min_items,
2403 max_items,
2404 unique_items,
2405 contains,
2406 items_annotates,
2407 })
2408 }
2409
2410 pub fn tuple(
2413 &mut self,
2414 prefix: Vec<NodeId>,
2415 tail: Option<NodeId>,
2416 min_items: u32,
2417 max_items: Option<u32>,
2418 ) -> Result<NodeId, CompileError> {
2419 self.tuple_unique(prefix, tail, min_items, max_items, false)
2420 }
2421
2422 pub fn tuple_unique(
2424 &mut self,
2425 prefix: Vec<NodeId>,
2426 tail: Option<NodeId>,
2427 min_items: u32,
2428 max_items: Option<u32>,
2429 unique_items: bool,
2430 ) -> Result<NodeId, CompileError> {
2431 self.tuple_full(prefix, tail, min_items, max_items, unique_items, None, true)
2432 }
2433
2434 #[allow(clippy::too_many_arguments)]
2437 pub fn tuple_full(
2438 &mut self,
2439 prefix: Vec<NodeId>,
2440 tail: Option<NodeId>,
2441 min_items: u32,
2442 max_items: Option<u32>,
2443 unique_items: bool,
2444 contains: Option<ContainsConstraint>,
2445 tail_annotates: bool,
2446 ) -> Result<NodeId, CompileError> {
2447 if prefix.is_empty() {
2448 return Err(malformed("a tuple must have at least one prefix position"));
2449 }
2450 let prefix = self.intern_refs(prefix)?;
2451 self.intern_node(Node::Tuple {
2452 prefix,
2453 tail,
2454 min_items,
2455 max_items,
2456 unique_items,
2457 tail_annotates,
2458 contains,
2459 })
2460 }
2461
2462 pub fn union_of(&mut self, branches: Vec<NodeId>) -> Result<NodeId, CompileError> {
2465 if branches.is_empty() {
2466 return Err(malformed("a type union must have at least one branch"));
2467 }
2468 if branches.len() == 1 {
2469 return Ok(branches[0]);
2470 }
2471 let branches = self.intern_refs(branches)?;
2472 self.intern_node(Node::Union { branches })
2473 }
2474
2475 pub fn alloc_def_slot(&mut self) -> Result<u32, CompileError> {
2478 let slot = u32::try_from(self.def_targets.len()).map_err(|_| limit("def slot count"))?;
2479 if self.def_targets.len() >= MAX_NODES {
2480 return Err(limit("def slot cap"));
2481 }
2482 self.def_targets.push(None);
2483 Ok(slot)
2484 }
2485
2486 pub fn set_def_target(&mut self, slot: u32, body: NodeId) -> Result<(), CompileError> {
2489 let entry = self
2490 .def_targets
2491 .get_mut(slot as usize)
2492 .ok_or_else(|| malformed("def slot index out of range"))?;
2493 if entry.replace(body).is_some() {
2494 return Err(malformed("def slot already assigned"));
2495 }
2496 Ok(())
2497 }
2498
2499 pub fn ref_node(&mut self, def: u32) -> Result<NodeId, CompileError> {
2501 self.intern_node(Node::Ref { def })
2502 }
2503
2504 pub fn dynamic_ref(
2506 &mut self,
2507 initial_target: NodeId,
2508 anchor: AnchorId,
2509 ) -> Result<NodeId, CompileError> {
2510 self.intern_node(Node::DynamicRef {
2511 initial_target,
2512 anchor,
2513 })
2514 }
2515
2516 pub fn unevaluated(
2519 &mut self,
2520 kind: UnevaluatedKind,
2521 scope: NodeId,
2522 unevaluated: NodeId,
2523 ) -> Result<NodeId, CompileError> {
2524 self.intern_node(Node::Unevaluated {
2525 kind,
2526 scope,
2527 unevaluated,
2528 })
2529 }
2530
2531 pub fn intersection_of(&mut self, branches: Vec<NodeId>) -> Result<NodeId, CompileError> {
2534 if branches.is_empty() {
2535 return Err(malformed("an intersection must have at least one branch"));
2536 }
2537 if branches.len() == 1 {
2538 return Ok(branches[0]);
2539 }
2540 let branches = self.intern_refs(branches)?;
2541 self.intern_node(Node::Intersection { branches })
2542 }
2543
2544 pub fn exactly_one_of(&mut self, branches: Vec<NodeId>) -> Result<NodeId, CompileError> {
2547 if branches.is_empty() {
2548 return Err(malformed("an exactly-one must have at least one branch"));
2549 }
2550 if branches.len() == 1 {
2551 return Ok(branches[0]);
2552 }
2553 let branches = self.intern_refs(branches)?;
2554 self.intern_node(Node::ExactlyOne { branches })
2555 }
2556
2557 pub fn not(&mut self, inner: NodeId) -> Result<NodeId, CompileError> {
2559 self.intern_node(Node::Not { inner })
2560 }
2561
2562 fn intern_named_fields(
2565 &mut self,
2566 fields: Vec<(String, NodeId)>,
2567 required: &[bool],
2568 ) -> Result<(PropSlice, BitSlice), CompileError> {
2569 if required.len() != fields.len() {
2570 return Err(malformed("required flags do not align with fields"));
2571 }
2572 let mut names: FxHashMap<&str, ()> = FxHashMap::default();
2573 names.reserve(fields.len());
2574 for (name, _) in &fields {
2575 if names.insert(name.as_str(), ()).is_some() {
2576 return Err(malformed("duplicate object key"));
2577 }
2578 }
2579 let mut pairs = Vec::with_capacity(fields.len());
2580 for (name, child) in &fields {
2581 let name = self.intern_str(name)?;
2582 pairs.push((name, *child));
2583 }
2584 let field_count = u32::try_from(fields.len()).map_err(|_| limit("object field count"))?;
2585 let words = fields.len().div_ceil(64);
2586 let mut bits = vec![0u64; words];
2587 for (i, &req) in required.iter().enumerate() {
2588 if req {
2589 bits[i / 64] |= 1u64 << (i % 64);
2590 }
2591 }
2592 let fields = self.intern_props(pairs)?;
2593 let required = self.intern_bits(bits, field_count)?;
2594 Ok((fields, required))
2595 }
2596
2597 pub fn object(
2600 &mut self,
2601 fields: Vec<(String, NodeId)>,
2602 required: &[bool],
2603 closure: ObjectClosure,
2604 ) -> Result<NodeId, CompileError> {
2605 self.object_full(fields, required, closure, Vec::new(), Vec::new())
2606 }
2607
2608 pub fn object_full(
2611 &mut self,
2612 fields: Vec<(String, NodeId)>,
2613 required: &[bool],
2614 closure: ObjectClosure,
2615 dependent: Vec<(String, NodeId)>,
2616 dependent_required: Vec<(String, Vec<String>)>,
2617 ) -> Result<NodeId, CompileError> {
2618 let (fields, required) = self.intern_named_fields(fields, required)?;
2619 let dependent = self.intern_dependent(dependent)?;
2620 let dependent_required = self.intern_dependent_required(dependent_required)?;
2621 self.intern_node(Node::Object {
2622 fields,
2623 required,
2624 closure,
2625 dependent,
2626 dependent_required,
2627 })
2628 }
2629
2630 fn intern_dependent(
2631 &mut self,
2632 dependent: Vec<(String, NodeId)>,
2633 ) -> Result<PropSlice, CompileError> {
2634 let mut pairs = Vec::with_capacity(dependent.len());
2635 for (name, child) in dependent {
2636 let name_ref = self.intern_str(&name)?;
2637 pairs.push((name_ref, child));
2638 }
2639 self.intern_props(pairs)
2640 }
2641
2642 #[allow(clippy::too_many_arguments)]
2645 pub fn open_object(
2646 &mut self,
2647 known: Vec<(String, NodeId)>,
2648 known_required: &[bool],
2649 patterns: Vec<(String, NodeId)>,
2650 additional: AdditionalPolicy,
2651 property_names: Option<NodeId>,
2652 min_properties: Option<u32>,
2653 max_properties: Option<u32>,
2654 ) -> Result<NodeId, CompileError> {
2655 self.open_object_full(
2656 known,
2657 known_required,
2658 patterns,
2659 additional,
2660 property_names,
2661 min_properties,
2662 max_properties,
2663 Vec::new(),
2664 Vec::new(),
2665 )
2666 }
2667
2668 #[allow(clippy::too_many_arguments)]
2671 pub fn open_object_full(
2672 &mut self,
2673 known: Vec<(String, NodeId)>,
2674 known_required: &[bool],
2675 patterns: Vec<(String, NodeId)>,
2676 additional: AdditionalPolicy,
2677 property_names: Option<NodeId>,
2678 min_properties: Option<u32>,
2679 max_properties: Option<u32>,
2680 dependent: Vec<(String, NodeId)>,
2681 dependent_required: Vec<(String, Vec<String>)>,
2682 ) -> Result<NodeId, CompileError> {
2683 if let (Some(lo), Some(hi)) = (min_properties, max_properties) {
2684 if hi < lo {
2685 return Err(malformed("maxProperties is less than minProperties"));
2686 }
2687 }
2688 let (known, known_required) = self.intern_named_fields(known, known_required)?;
2689 let mut pattern_pairs = Vec::with_capacity(patterns.len());
2690 for (regex, child) in &patterns {
2691 let regex_ref = self.intern_str(regex)?;
2692 pattern_pairs.push((regex_ref, *child));
2693 }
2694 let patterns = self.intern_props(pattern_pairs)?;
2695 let dependent = self.intern_dependent(dependent)?;
2696 let dependent_required = self.intern_dependent_required(dependent_required)?;
2697 self.intern_node(Node::OpenObject {
2698 known,
2699 known_required,
2700 patterns,
2701 additional,
2702 property_names,
2703 min_properties,
2704 max_properties,
2705 dependent,
2706 dependent_required,
2707 })
2708 }
2709
2710 pub fn unsupported(
2713 &mut self,
2714 keyword: &str,
2715 reason: UnsupportedReason,
2716 json_pointer: &str,
2717 ) -> Result<NodeId, CompileError> {
2718 if self.diagnostics.len() >= MAX_DIAGNOSTICS
2719 || self.diagnostics.len() as u64 >= u64::from(self.options.max_diagnostics)
2720 {
2721 return Err(CompileError::new(
2722 ErrorCode::TooManyDiagnostics,
2723 Stage::L2,
2724 "diagnostics buffer is full",
2725 ));
2726 }
2727 let keyword_ref = self.intern_str(keyword)?;
2728 let pointer_ref = self.intern_str(json_pointer)?;
2729 let node = self.intern_node(Node::Unsupported {
2730 keyword: keyword_ref,
2731 reason,
2732 })?;
2733 self.diagnostics.push(Diagnostic {
2734 node,
2735 keyword: keyword_ref,
2736 json_pointer: pointer_ref,
2737 source_span: None,
2738 reason,
2739 });
2740 Ok(node)
2741 }
2742
2743 pub fn finish(self, root: NodeId) -> Result<SchemaIR, CompileError> {
2745 self.finish_with_resources(
2746 root,
2747 None,
2748 vec![ResourceInput {
2749 canonical_uri: None,
2750 root,
2751 static_anchors: Vec::new(),
2752 dynamic_anchors: Vec::new(),
2753 }],
2754 )
2755 }
2756
2757 pub(crate) fn finish_with_resources(
2758 mut self,
2759 root: NodeId,
2760 retrieval_uri: Option<String>,
2761 resource_inputs: Vec<ResourceInput>,
2762 ) -> Result<SchemaIR, CompileError> {
2763 let retrieval_uri = retrieval_uri
2764 .as_deref()
2765 .map(|uri| self.intern_str(uri))
2766 .transpose()?;
2767 let mut resources = Vec::new();
2768 resources
2769 .try_reserve_exact(resource_inputs.len())
2770 .map_err(|_| limit("resource allocation"))?;
2771 let mut anchors = Vec::new();
2772 for (index, input) in resource_inputs.into_iter().enumerate() {
2773 let resource = ResourceId(u32::try_from(index).map_err(|_| limit("resource index"))?);
2774 let canonical_uri = input
2775 .canonical_uri
2776 .as_deref()
2777 .map(|uri| self.intern_str(uri))
2778 .transpose()?;
2779 let static_off = u32::try_from(anchors.len()).map_err(|_| limit("anchor offset"))?;
2780 for (name, target) in input.static_anchors {
2781 let name = self.intern_str(&name)?;
2782 anchors.push(AnchorRecord {
2783 resource,
2784 name,
2785 target,
2786 });
2787 }
2788 let static_len = u32::try_from(anchors.len())
2789 .map_err(|_| limit("anchor count"))?
2790 .checked_sub(static_off)
2791 .ok_or_else(|| limit("anchor run"))?;
2792 let dynamic_off = u32::try_from(anchors.len()).map_err(|_| limit("anchor offset"))?;
2793 for (name, target) in input.dynamic_anchors {
2794 let name = self.intern_str(&name)?;
2795 anchors.push(AnchorRecord {
2796 resource,
2797 name,
2798 target,
2799 });
2800 }
2801 let dynamic_len = u32::try_from(anchors.len())
2802 .map_err(|_| limit("anchor count"))?
2803 .checked_sub(dynamic_off)
2804 .ok_or_else(|| limit("anchor run"))?;
2805 resources.push(ResourceRecord {
2806 canonical_uri,
2807 root: input.root,
2808 static_anchors: AnchorSlice {
2809 off: static_off,
2810 len: static_len,
2811 },
2812 dynamic_anchors: AnchorSlice {
2813 off: dynamic_off,
2814 len: dynamic_len,
2815 },
2816 });
2817 }
2818 let def_targets = self
2819 .def_targets
2820 .into_iter()
2821 .map(|t| t.ok_or_else(|| malformed("def slot never assigned a target")))
2822 .collect::<Result<Vec<NodeId>, CompileError>>()?;
2823 SchemaIR::assemble(SchemaIRWire {
2824 ir_version: IR_VERSION,
2825 root,
2826 nodes: self.nodes,
2827 strings: self.strings,
2828 literals: self.literals,
2829 props: self.props,
2830 dependent_required_pairs: self.dependent_required_pairs,
2831 required_bits: self.required_bits,
2832 node_refs: self.node_refs,
2833 def_targets,
2834 retrieval_uri,
2835 resources,
2836 anchors,
2837 node_resources: self.node_resources,
2838 options: self.options,
2839 diagnostics: self.diagnostics,
2840 })
2841 }
2842}
2843
2844fn limit(what: &'static str) -> CompileError {
2845 CompileError::new(ErrorCode::InternalLimitExceeded, Stage::L2, what)
2846}
2847
2848#[cfg(test)]
2849mod tests {
2850 use super::*;
2851
2852 fn opts() -> CompileOptions {
2853 CompileOptions::default()
2854 }
2855
2856 #[test]
2857 fn identical_subschemas_share_one_node_id() {
2858 let mut b = Builder::new(opts());
2859 let a = b.boolean().unwrap();
2860 let c = b.boolean().unwrap();
2861 assert_eq!(a, c, "boolean is interned once");
2862 let obj = b
2863 .object(
2864 vec![("x".into(), a), ("y".into(), c)],
2865 &[false, false],
2866 ObjectClosure::Forbidden,
2867 )
2868 .unwrap();
2869 let ir = b.finish(obj).unwrap();
2870 assert_eq!(ir.node_count(), 2);
2872 }
2873
2874 #[test]
2875 fn finish_rejects_an_unfilled_def_slot() {
2876 let mut b = Builder::new(opts());
2877 let slot = b.alloc_def_slot().unwrap();
2878 let root = b.ref_node(slot).unwrap();
2879 assert!(b.finish(root).is_err());
2880 }
2881
2882 #[test]
2883 fn set_def_target_rejects_a_second_assignment_to_the_same_slot() {
2884 let mut b = Builder::new(opts());
2885 let slot = b.alloc_def_slot().unwrap();
2886 let a = b.boolean().unwrap();
2887 let c = b.boolean().unwrap();
2888 b.set_def_target(slot, a).unwrap();
2889 assert!(b.set_def_target(slot, c).is_err());
2890 }
2891
2892 #[test]
2893 fn set_def_target_rejects_an_out_of_range_slot() {
2894 let mut b = Builder::new(opts());
2895 let a = b.boolean().unwrap();
2896 assert!(b.set_def_target(0, a).is_err());
2897 }
2898
2899 #[test]
2900 fn object_with_few_fields_but_heavy_bounded_strings_needs_structured_backend() {
2901 let mut b = Builder::new(opts());
2902 let a = b
2903 .string_pattern("(?:.)", None, Some(100), Charset::Utf8CountedCodepoints)
2904 .unwrap();
2905 let c = b
2906 .string_pattern("(?:.)", None, Some(2048), Charset::Utf8CountedCodepoints)
2907 .unwrap();
2908 let obj = b
2909 .object(
2910 vec![("a".into(), a), ("c".into(), c)],
2911 &[false, false],
2912 ObjectClosure::Forbidden,
2913 )
2914 .unwrap();
2915 let ir = b.finish(obj).unwrap();
2916 assert!(
2917 ir.requires_structured_backend(),
2918 "2 fields, well under MAX_SAFE_SHUFFLE_FIELDS, but weight 100+2048 exceeds the cap"
2919 );
2920 }
2921
2922 #[test]
2923 fn three_fields_with_a_255_scalar_string_exceed_the_shuffle_edge_bound() {
2924 let mut b = Builder::new(opts());
2925 let label = b
2926 .string_pattern("(?:.)", Some(2), Some(255), Charset::Utf8CountedCodepoints)
2927 .unwrap();
2928 let boolean = b.boolean().unwrap();
2929 let obj = b
2930 .object(
2931 vec![
2932 ("category".into(), label),
2933 ("generate_entities".into(), boolean),
2934 ("label".into(), label),
2935 ],
2936 &[false, false, true],
2937 ObjectClosure::Forbidden,
2938 )
2939 .unwrap();
2940 let ir = b.finish(obj).unwrap();
2941 assert!(ir.requires_structured_backend());
2942 }
2943
2944 #[test]
2945 fn large_minimum_array_needs_structured_backend_even_without_a_maximum() {
2946 let mut b = Builder::new(opts());
2947 let item = b.null().unwrap();
2948 let array = b.array(item, MAX_UNROLLED_ARRAY_ITEMS + 1, None).unwrap();
2949 let ir = b.finish(array).unwrap();
2950 assert!(ir.requires_structured_backend());
2951 }
2952
2953 #[test]
2954 fn object_with_small_bounded_strings_stays_on_the_fast_dfa_path() {
2955 let mut b = Builder::new(opts());
2956 let a = b
2957 .string_pattern("(?:.)", None, Some(50), Charset::Utf8CountedCodepoints)
2958 .unwrap();
2959 let c = b
2960 .string_pattern("(?:.)", None, Some(50), Charset::Utf8CountedCodepoints)
2961 .unwrap();
2962 let obj = b
2963 .object(
2964 vec![("a".into(), a), ("c".into(), c)],
2965 &[false, false],
2966 ObjectClosure::Forbidden,
2967 )
2968 .unwrap();
2969 let ir = b.finish(obj).unwrap();
2970 assert!(!ir.requires_structured_backend());
2971 }
2972
2973 #[test]
2974 fn single_optional_heavy_bounded_string_routes_before_shuffle_construction() {
2975 let mut b = Builder::new(opts());
2976 let a = b
2977 .string_pattern("(?:.)", None, Some(4096), Charset::Utf8CountedCodepoints)
2978 .unwrap();
2979 let obj = b
2980 .object(vec![("a".into(), a)], &[false], ObjectClosure::Forbidden)
2981 .unwrap();
2982 let ir = b.finish(obj).unwrap();
2983 assert!(ir.requires_structured_backend());
2984 }
2985
2986 #[test]
2987 fn heavy_bounded_string_wrapped_in_a_nullable_union_still_counts_its_weight() {
2988 let mut b = Builder::new(opts());
2989 let null = b.null().unwrap();
2990 let big = b
2991 .string_pattern("(?:.)", None, Some(2048), Charset::Utf8CountedCodepoints)
2992 .unwrap();
2993 let nullable_big = b.union_of(vec![null, big]).unwrap();
2994 let small = b
2995 .string_pattern("(?:.)", None, Some(100), Charset::Utf8CountedCodepoints)
2996 .unwrap();
2997 let obj = b
2998 .object(
2999 vec![("a".into(), nullable_big), ("b".into(), small)],
3000 &[false, false],
3001 ObjectClosure::Forbidden,
3002 )
3003 .unwrap();
3004 let ir = b.finish(obj).unwrap();
3005 assert!(
3006 ir.requires_structured_backend(),
3007 "type:[X,null] must not hide a field's real weight from the shuffle pre-check"
3008 );
3009 }
3010
3011 #[test]
3012 fn distinct_node_count_never_exceeds_occurrence_count() {
3013 let mut b = Builder::new(opts());
3014 let inner = b.boolean().unwrap();
3015 let arr = b.array(inner, 0, None).unwrap();
3016 let ir = b.finish(arr).unwrap();
3017 assert!(ir.node_count() <= 2);
3018 }
3019
3020 #[test]
3021 fn two_builders_different_insertion_order_hash_equal() {
3022 let mut b1 = Builder::new(opts());
3023 let x = b1.boolean().unwrap();
3024 let y = b1.null().unwrap();
3025 let o1 = b1
3026 .object(
3027 vec![("a".into(), x), ("b".into(), y)],
3028 &[true, false],
3029 ObjectClosure::Forbidden,
3030 )
3031 .unwrap();
3032 let ir1 = b1.finish(o1).unwrap();
3033
3034 let mut b2 = Builder::new(opts());
3035 let y = b2.null().unwrap();
3036 let x = b2.boolean().unwrap();
3037 let o2 = b2
3038 .object(
3039 vec![("a".into(), x), ("b".into(), y)],
3040 &[true, false],
3041 ObjectClosure::Forbidden,
3042 )
3043 .unwrap();
3044 let ir2 = b2.finish(o2).unwrap();
3045
3046 assert_eq!(ir1.canonical_hash(), ir2.canonical_hash());
3047 }
3048
3049 #[test]
3050 fn different_property_order_changes_the_hash() {
3051 let mut b1 = Builder::new(opts());
3052 let (x, y) = (b1.boolean().unwrap(), b1.null().unwrap());
3053 let o1 = b1
3054 .object(
3055 vec![("a".into(), x), ("b".into(), y)],
3056 &[false, false],
3057 ObjectClosure::Forbidden,
3058 )
3059 .unwrap();
3060 let ir1 = b1.finish(o1).unwrap();
3061
3062 let mut b2 = Builder::new(opts());
3063 let (x, y) = (b2.boolean().unwrap(), b2.null().unwrap());
3064 let o2 = b2
3065 .object(
3066 vec![("b".into(), y), ("a".into(), x)],
3067 &[false, false],
3068 ObjectClosure::Forbidden,
3069 )
3070 .unwrap();
3071 let ir2 = b2.finish(o2).unwrap();
3072
3073 assert_ne!(ir1.canonical_hash(), ir2.canonical_hash());
3074 }
3075
3076 #[test]
3077 fn enum_order_does_not_change_the_hash() {
3078 let mut b1 = Builder::new(opts());
3079 let e1 = b1
3080 .enum_values(vec![ScalarLit::Int(1), ScalarLit::Int(2)])
3081 .unwrap();
3082 let ir1 = b1.finish(e1).unwrap();
3083 let mut b2 = Builder::new(opts());
3084 let e2 = b2
3085 .enum_values(vec![ScalarLit::Int(2), ScalarLit::Int(1)])
3086 .unwrap();
3087 let ir2 = b2.finish(e2).unwrap();
3088 assert_eq!(ir1.canonical_hash(), ir2.canonical_hash());
3089 }
3090
3091 #[test]
3092 fn required_flag_change_changes_the_hash() {
3093 let mk = |req: &[bool]| {
3094 let mut b = Builder::new(opts());
3095 let x = b.boolean().unwrap();
3096 let o = b
3097 .object(vec![("a".into(), x)], req, ObjectClosure::Forbidden)
3098 .unwrap();
3099 b.finish(o).unwrap().canonical_hash()
3100 };
3101 assert_ne!(mk(&[true]), mk(&[false]));
3102 }
3103
3104 #[test]
3105 fn length_prefix_prevents_concatenation_collision() {
3106 let mut b1 = Builder::new(opts());
3107 let e1 = b1
3108 .enum_values(vec![
3109 ScalarLit::Str("ab".into()),
3110 ScalarLit::Str("c".into()),
3111 ])
3112 .unwrap();
3113 let ir1 = b1.finish(e1).unwrap();
3114 let mut b2 = Builder::new(opts());
3115 let e2 = b2
3116 .enum_values(vec![
3117 ScalarLit::Str("a".into()),
3118 ScalarLit::Str("bc".into()),
3119 ])
3120 .unwrap();
3121 let ir2 = b2.finish(e2).unwrap();
3122 assert_ne!(ir1.canonical_hash(), ir2.canonical_hash());
3123 }
3124
3125 #[test]
3126 fn enum_rejects_duplicate_value() {
3127 let mut b = Builder::new(opts());
3128 let e = b.enum_values(vec![ScalarLit::Int(1), ScalarLit::Int(1)]);
3129 assert_eq!(e.unwrap_err().code, ErrorCode::Malformed);
3130 }
3131
3132 #[test]
3133 fn object_rejects_duplicate_key() {
3134 let mut b = Builder::new(opts());
3135 let x = b.boolean().unwrap();
3136 let y = b.null().unwrap();
3137 let e = b.object(
3138 vec![("a".into(), x), ("a".into(), y)],
3139 &[false, false],
3140 ObjectClosure::Forbidden,
3141 );
3142 assert_eq!(e.unwrap_err().code, ErrorCode::Malformed);
3143 }
3144
3145 #[test]
3146 fn array_max_below_min_is_rejected_at_finish() {
3147 let mut b = Builder::new(opts());
3148 let inner = b.boolean().unwrap();
3149 let arr = b.array(inner, 3, Some(2)).unwrap();
3150 assert_eq!(b.finish(arr).unwrap_err().code, ErrorCode::Malformed);
3151 }
3152
3153 #[test]
3154 fn unsupported_records_a_diagnostic_per_occurrence() {
3155 let mut b = Builder::new(opts());
3156 let u1 = b
3157 .unsupported("anyOf", UnsupportedReason::UnionType, "/anyOf")
3158 .unwrap();
3159 let u2 = b
3160 .unsupported("anyOf", UnsupportedReason::UnionType, "/properties/x/anyOf")
3161 .unwrap();
3162 assert_eq!(u1, u2, "same keyword+reason interns to one node");
3163 let ir = b.finish(u1).unwrap();
3164 assert_eq!(
3165 ir.diagnostics().len(),
3166 2,
3167 "each occurrence keeps its own pointer"
3168 );
3169 let pointers: Vec<&str> = ir
3170 .diagnostics()
3171 .map(|d| ir.str_at(d.json_pointer).unwrap())
3172 .collect();
3173 assert_eq!(pointers, ["/anyOf", "/properties/x/anyOf"]);
3174 }
3175
3176 #[test]
3177 fn diagnostics_are_bounded() {
3178 let mut options = opts();
3179 options.max_diagnostics = 2;
3180 let mut b = Builder::new(options);
3181 b.unsupported("not", UnsupportedReason::Complement, "/a")
3182 .unwrap();
3183 b.unsupported("not", UnsupportedReason::Complement, "/b")
3184 .unwrap();
3185 let e = b.unsupported("not", UnsupportedReason::Complement, "/c");
3186 assert_eq!(e.unwrap_err().code, ErrorCode::TooManyDiagnostics);
3187 }
3188
3189 #[test]
3190 fn default_max_diagnostics_absorbs_a_real_world_scale_schema_without_hard_failing() {
3191 let mut b = Builder::new(CompileOptions::default());
3193 for i in 0..5000u32 {
3194 b.unsupported("not", UnsupportedReason::Complement, &format!("/{i}"))
3195 .unwrap();
3196 }
3197 let last = b
3198 .unsupported("not", UnsupportedReason::Complement, "/last")
3199 .unwrap();
3200 let ir = b.finish(last).unwrap();
3201 assert_eq!(ir.diagnostics().len(), 5001);
3202 }
3203
3204 #[test]
3205 fn canonical_hash_ignores_diagnostic_pointer() {
3206 let mk = |ptr: &str| {
3207 let mut b = Builder::new(opts());
3208 let u = b
3209 .unsupported("not", UnsupportedReason::Complement, ptr)
3210 .unwrap();
3211 b.finish(u).unwrap().canonical_hash()
3212 };
3213 assert_eq!(mk("/a"), mk("/properties/deeply/nested/b"));
3214 }
3215
3216 #[test]
3217 fn integer_bounds_reject_empty_range_at_finish() {
3218 let mut b = Builder::new(opts());
3219 let n = b.integer(Some(5), Some(1)).unwrap();
3220 assert_eq!(b.finish(n).unwrap_err().code, ErrorCode::Malformed);
3221 }
3222
3223 #[test]
3224 fn object_closure_change_changes_the_hash() {
3225 let mk = |closure| {
3226 let mut b = Builder::new(opts());
3227 let x = b.boolean().unwrap();
3228 let o = b.object(vec![("a".into(), x)], &[false], closure).unwrap();
3229 b.finish(o).unwrap().canonical_hash()
3230 };
3231 assert_ne!(
3232 mk(ObjectClosure::Forbidden),
3233 mk(ObjectClosure::AssumeClosedProfile)
3234 );
3235 }
3236
3237 #[test]
3238 fn compile_options_are_part_of_the_program4_hash() {
3239 let mk = |cap| {
3240 let mut o = opts();
3241 o.max_diagnostics = cap;
3242 let mut b = Builder::new(o);
3243 let n = b.boolean().unwrap();
3244 b.finish(n).unwrap().canonical_hash()
3245 };
3246 assert_ne!(mk(8), mk(64));
3247 }
3248
3249 #[test]
3250 fn all_compile_options_change_the_program4_hash() {
3251 let mk = |f: &dyn Fn(&mut CompileOptions)| {
3252 let mut o = opts();
3253 f(&mut o);
3254 let mut b = Builder::new(o);
3255 let n = b.boolean().unwrap();
3256 b.finish(n).unwrap().canonical_hash()
3257 };
3258 let base = mk(&|_| {});
3259 assert_ne!(base, mk(&|o| o.max_diagnostics = 8));
3260 assert_ne!(base, mk(&|o| o.format_assertion = true));
3261 }
3262
3263 #[test]
3264 fn same_content_address_holds_across_build_order() {
3265 let mut b1 = Builder::new(opts());
3266 let x = b1.boolean().unwrap();
3267 let y = b1.null().unwrap();
3268 let o1 = b1
3269 .object(
3270 vec![("a".into(), x), ("b".into(), y)],
3271 &[true, false],
3272 ObjectClosure::Forbidden,
3273 )
3274 .unwrap();
3275 let ir1 = b1.finish(o1).unwrap();
3276
3277 let mut b2 = Builder::new(opts());
3278 let y = b2.null().unwrap();
3279 let x = b2.boolean().unwrap();
3280 let o2 = b2
3281 .object(
3282 vec![("a".into(), x), ("b".into(), y)],
3283 &[true, false],
3284 ObjectClosure::Forbidden,
3285 )
3286 .unwrap();
3287 let ir2 = b2.finish(o2).unwrap();
3288
3289 assert!(ir1.same_content_address(&ir2));
3290 }
3291
3292 #[test]
3293 fn object_field_counts_around_word_boundaries_build_and_read_back() {
3294 for n in [0usize, 1, 63, 64, 65, 130] {
3295 let mut b = Builder::new(opts());
3296 let mut fields = Vec::with_capacity(n);
3297 let mut required = Vec::with_capacity(n);
3298 for i in 0..n {
3299 let child = b.boolean().unwrap();
3300 fields.push((format!("f{i}"), child));
3301 required.push(i % 2 == 0);
3302 }
3303 let o = b
3304 .object(fields, &required, ObjectClosure::Forbidden)
3305 .unwrap();
3306 let ir = b.finish(o).unwrap();
3307 let Node::Object { required: bits, .. } = ir.node(o).unwrap() else {
3308 panic!("object");
3309 };
3310 for i in 0..n {
3311 let idx = u32::try_from(i).unwrap();
3312 assert_eq!(ir.is_required(*bits, idx), i % 2 == 0, "field {i} of {n}");
3313 }
3314 }
3315 }
3316
3317 #[test]
3318 fn integer_and_enum_carry_i64_extremes() {
3319 let mut b = Builder::new(opts());
3320 let n = b.integer(Some(i64::MIN), Some(i64::MAX)).unwrap();
3321 let ir = b.finish(n).unwrap();
3322 assert_eq!(SchemaIR::from_wire(&ir.to_wire()).unwrap(), ir);
3323
3324 let mut b = Builder::new(opts());
3325 let e = b
3326 .enum_values(vec![ScalarLit::Int(i64::MIN), ScalarLit::Int(i64::MAX)])
3327 .unwrap();
3328 let ir = b.finish(e).unwrap();
3329 assert_eq!(SchemaIR::from_wire(&ir.to_wire()).unwrap(), ir);
3330 }
3331
3332 #[test]
3333 fn literal_string_bytes_at_the_cap_build_and_round_trip() {
3334 let mut b = Builder::new(opts());
3335 let big = "a".repeat(MAX_LITERAL_STR_BYTES);
3336 let e = b.enum_values(vec![ScalarLit::Str(big)]).unwrap();
3337 let ir = b.finish(e).unwrap();
3338 assert_eq!(SchemaIR::from_wire(&ir.to_wire()).unwrap(), ir);
3339 }
3340
3341 #[test]
3342 fn a_single_over_cap_literal_string_is_rejected_at_build() {
3343 let mut b = Builder::new(opts());
3344 let too_big = "a".repeat(MAX_LITERAL_STR_BYTES + 1);
3345 let e = b.enum_values(vec![ScalarLit::Str(too_big)]);
3346 assert_eq!(e.unwrap_err().code, ErrorCode::InternalLimitExceeded);
3347 }
3348
3349 #[test]
3350 fn repeated_large_literals_cannot_bypass_the_aggregate_cap() {
3351 let half = MAX_LITERAL_STR_BYTES / 2;
3352 let mut b = Builder::new(opts());
3353 b.enum_values(vec![ScalarLit::Str("a".repeat(half))])
3355 .unwrap();
3356 let second = b.enum_values(vec![ScalarLit::Str("b".repeat(half + 1))]);
3357 assert_eq!(second.unwrap_err().code, ErrorCode::InternalLimitExceeded);
3358 }
3359
3360 #[test]
3361 fn tuple_node_round_trips_and_rejects_a_closed_min_past_the_prefix() {
3362 let mut b = Builder::new(opts());
3363 let x = b.boolean().unwrap();
3364 let y = b.null().unwrap();
3365 let node = b.tuple(vec![x, y], Some(x), 1, Some(4)).unwrap();
3366 let ir = b.finish(node).unwrap();
3367 assert_eq!(SchemaIR::from_wire(&ir.to_wire()).unwrap(), ir);
3368
3369 let mut b = Builder::new(opts());
3372 let x = b.boolean().unwrap();
3373 let node = b.tuple(vec![x], None, 3, None).unwrap();
3374 assert_eq!(b.finish(node).unwrap_err().code, ErrorCode::Malformed);
3375 }
3376
3377 #[test]
3378 fn empty_tuple_prefix_is_rejected_at_build() {
3379 let mut b = Builder::new(opts());
3380 assert_eq!(
3381 b.tuple(vec![], None, 0, None).unwrap_err().code,
3382 ErrorCode::Malformed
3383 );
3384 }
3385
3386 #[test]
3387 fn tuple_and_array_hash_differently_even_with_one_prefix_element() {
3388 let mk_tuple = || {
3389 let mut b = Builder::new(opts());
3390 let x = b.boolean().unwrap();
3391 let n = b.tuple(vec![x], Some(x), 0, None).unwrap();
3392 b.finish(n).unwrap().canonical_hash()
3393 };
3394 let mk_array = || {
3395 let mut b = Builder::new(opts());
3396 let x = b.boolean().unwrap();
3397 let n = b.array(x, 0, None).unwrap();
3398 b.finish(n).unwrap().canonical_hash()
3399 };
3400 assert_ne!(mk_tuple(), mk_array());
3401 }
3402
3403 #[test]
3404 fn deeply_nested_and_repeated_subtrees_build_via_arena_iteration() {
3405 let mut b = Builder::new(opts());
3406 let mut node = b.boolean().unwrap();
3407 for _ in 0..5_000 {
3408 node = b.array(node, 0, None).unwrap();
3409 }
3410 let ir = b.finish(node).unwrap();
3411 assert_eq!(ir.node_count(), 5_001);
3412 let _ = ir.canonical_hash();
3413 }
3414
3415 #[test]
3416 fn open_object_rejects_duplicate_known_keys() {
3417 let mut b = Builder::new(opts());
3418 let x = b.boolean().unwrap();
3419 let err = b
3420 .open_object(
3421 vec![("a".into(), x), ("a".into(), x)],
3422 &[false, false],
3423 Vec::new(),
3424 AdditionalPolicy::Forbid,
3425 None,
3426 None,
3427 None,
3428 )
3429 .unwrap_err();
3430 assert_eq!(err.code, ErrorCode::Malformed);
3431 }
3432
3433 #[test]
3434 fn open_object_rejects_max_properties_below_min_properties() {
3435 let mut b = Builder::new(opts());
3436 let err = b
3437 .open_object(
3438 Vec::new(),
3439 &[],
3440 Vec::new(),
3441 AdditionalPolicy::AllowAny,
3442 None,
3443 Some(5),
3444 Some(2),
3445 )
3446 .unwrap_err();
3447 assert_eq!(err.code, ErrorCode::Malformed);
3448 }
3449
3450 #[test]
3451 fn open_object_additional_policy_changes_the_hash() {
3452 let mk = |policy: AdditionalPolicy| {
3453 let mut b = Builder::new(opts());
3454 let n = b
3455 .open_object(Vec::new(), &[], Vec::new(), policy, None, None, None)
3456 .unwrap();
3457 b.finish(n).unwrap().canonical_hash()
3458 };
3459 let forbid = mk(AdditionalPolicy::Forbid);
3460 let allow_any = mk(AdditionalPolicy::AllowAny);
3461 assert_ne!(forbid, allow_any);
3462
3463 let mut b = Builder::new(opts());
3464 let s = b.boolean().unwrap();
3465 let schema_hash = {
3466 let n = b
3467 .open_object(
3468 Vec::new(),
3469 &[],
3470 Vec::new(),
3471 AdditionalPolicy::Schema(s),
3472 None,
3473 None,
3474 None,
3475 )
3476 .unwrap();
3477 b.finish(n).unwrap().canonical_hash()
3478 };
3479 assert_ne!(schema_hash, forbid);
3480 assert_ne!(schema_hash, allow_any);
3481 }
3482
3483 #[test]
3484 fn open_object_and_closed_object_hash_differently_for_the_same_fields() {
3485 let mk_open = || {
3486 let mut b = Builder::new(opts());
3487 let x = b.boolean().unwrap();
3488 let n = b
3489 .open_object(
3490 vec![("a".into(), x)],
3491 &[true],
3492 Vec::new(),
3493 AdditionalPolicy::Forbid,
3494 None,
3495 None,
3496 None,
3497 )
3498 .unwrap();
3499 b.finish(n).unwrap().canonical_hash()
3500 };
3501 let mk_closed = || {
3502 let mut b = Builder::new(opts());
3503 let x = b.boolean().unwrap();
3504 let n = b
3505 .object(vec![("a".into(), x)], &[true], ObjectClosure::Forbidden)
3506 .unwrap();
3507 b.finish(n).unwrap().canonical_hash()
3508 };
3509 assert_ne!(mk_open(), mk_closed());
3510 }
3511
3512 #[test]
3513 fn open_object_pattern_and_property_names_round_trip_through_wire() {
3514 let mut b = Builder::new(opts());
3515 let known_val = b.boolean().unwrap();
3516 let pattern_val = b.integer(None, None).unwrap();
3517 let names_schema = b
3518 .string_pattern("[a-z]+", None, None, Charset::Utf8Any)
3519 .unwrap();
3520 let n = b
3521 .open_object(
3522 vec![("a".into(), known_val)],
3523 &[true],
3524 vec![("^x_".into(), pattern_val)],
3525 AdditionalPolicy::AllowAny,
3526 Some(names_schema),
3527 Some(1),
3528 Some(10),
3529 )
3530 .unwrap();
3531 let ir = b.finish(n).unwrap();
3532 let bytes = ir.to_wire();
3533 let decoded = SchemaIR::from_wire(&bytes).unwrap();
3534 assert_eq!(ir.canonical_hash(), decoded.canonical_hash());
3535 let Node::OpenObject {
3536 min_properties,
3537 max_properties,
3538 property_names,
3539 ..
3540 } = decoded.node(decoded.root()).unwrap()
3541 else {
3542 panic!("expected OpenObject");
3543 };
3544 assert_eq!(*min_properties, Some(1));
3545 assert_eq!(*max_properties, Some(10));
3546 assert!(property_names.is_some());
3547 }
3548
3549 #[test]
3550 fn dependent_required_canonical_hash_is_unordered_and_wire_stable() {
3551 let options = CompileOptions::default();
3552 let first = crate::frontend::schema_to_ir(
3553 r#"{"dependentRequired":{"a":["b","c"],"x":["y"]}}"#,
3554 options.clone(),
3555 )
3556 .unwrap();
3557 let second = crate::frontend::schema_to_ir(
3558 r#"{"dependentRequired":{"x":["y"],"a":["c","b"]}}"#,
3559 options,
3560 )
3561 .unwrap();
3562 assert_eq!(IR_VERSION, 6);
3563 assert_eq!(first.canonical_hash(), second.canonical_hash());
3564 let encoded = first.to_wire();
3565 let decoded = SchemaIR::from_wire(&encoded).unwrap();
3566 assert_eq!(decoded, first);
3567 assert_eq!(decoded.canonical_hash(), first.canonical_hash());
3568 let mut v2 = encoded;
3569 v2[6..8].copy_from_slice(&2u16.to_le_bytes());
3570 assert_eq!(
3571 SchemaIR::from_wire(&v2).unwrap_err().code,
3572 ErrorCode::Malformed
3573 );
3574 }
3575
3576 #[test]
3577 fn decimal_comparison_is_exact_at_coefficient_and_scale_limits() {
3578 use std::cmp::Ordering;
3579
3580 assert_eq!(decimal_cmp((1, u32::MAX), (2, u32::MAX)), Ordering::Less);
3581 assert_eq!(
3582 decimal_cmp((-1, u32::MAX), (-2, u32::MAX)),
3583 Ordering::Greater
3584 );
3585 assert_eq!(decimal_cmp((10, 2), (1, 1)), Ordering::Equal);
3586 assert_eq!(decimal_cmp((i128::MAX, 38), (1, 0)), Ordering::Greater);
3587 assert_eq!(decimal_cmp((i128::MIN, 38), (-1, 0)), Ordering::Less);
3588 }
3589
3590 #[test]
3591 fn forged_dependent_required_arena_is_rejected_without_panicking() {
3592 let valid = crate::frontend::schema_to_ir(
3593 r#"{"type":"object","additionalProperties":true,"dependentRequired":{"a":["b"]}}"#,
3594 CompileOptions::default(),
3595 )
3596 .unwrap();
3597 let root = valid.root().get() as usize;
3598 let mut bad_slice = valid.to_wire_struct();
3599 match &mut bad_slice.nodes[root] {
3600 Node::OpenObject {
3601 dependent_required, ..
3602 }
3603 | Node::Object {
3604 dependent_required, ..
3605 } => dependent_required.off = u32::MAX,
3606 _ => panic!("expected object branch"),
3607 }
3608 assert_eq!(
3609 SchemaIR::assemble(bad_slice).unwrap_err().code,
3610 ErrorCode::Malformed
3611 );
3612
3613 for (off, len) in [(0, u32::MAX), (u32::MAX, u32::MAX)] {
3614 let mut bad_range = valid.to_wire_struct();
3615 match &mut bad_range.nodes[root] {
3616 Node::OpenObject {
3617 dependent_required, ..
3618 }
3619 | Node::Object {
3620 dependent_required, ..
3621 } => {
3622 dependent_required.off = off;
3623 dependent_required.len = len;
3624 }
3625 _ => unreachable!(),
3626 }
3627 assert_eq!(
3628 SchemaIR::assemble(bad_range).unwrap_err().code,
3629 ErrorCode::Malformed
3630 );
3631 }
3632
3633 for trigger in [true, false] {
3634 let mut bad_name = valid.to_wire_struct();
3635 let bad = StrRef {
3636 off: u32::MAX,
3637 len: 1,
3638 };
3639 if trigger {
3640 bad_name.dependent_required_pairs[0].trigger = bad;
3641 } else {
3642 bad_name.dependent_required_pairs[0].required = bad;
3643 }
3644 assert_eq!(
3645 SchemaIR::assemble(bad_name).unwrap_err().code,
3646 ErrorCode::Malformed
3647 );
3648 }
3649
3650 let mut duplicate = valid.to_wire_struct();
3651 duplicate
3652 .dependent_required_pairs
3653 .push(duplicate.dependent_required_pairs[0]);
3654 match &mut duplicate.nodes[root] {
3655 Node::OpenObject {
3656 dependent_required, ..
3657 }
3658 | Node::Object {
3659 dependent_required, ..
3660 } => dependent_required.len = 2,
3661 _ => unreachable!(),
3662 }
3663 assert_eq!(
3664 SchemaIR::assemble(duplicate).unwrap_err().code,
3665 ErrorCode::Malformed
3666 );
3667
3668 let mut over_cap = valid.to_wire_struct();
3669 over_cap.dependent_required_pairs =
3670 vec![over_cap.dependent_required_pairs[0]; MAX_DEPENDENT_REQUIRED_PAIRS + 1];
3671 assert_eq!(
3672 SchemaIR::assemble(over_cap).unwrap_err().code,
3673 ErrorCode::Malformed
3674 );
3675 }
3676}