corvus_json_schema 0.1.3 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -271,12 +271,11 @@ impl<'l, 'o> SchemaCompiler<'l, 'o> {
271
271
  (r.dialect, r.vocabularies)
272
272
  };
273
273
  let legacy = dialect.is_legacy();
274
+ let grammar = crate::pattern::Grammar::of(dialect, self.options);
274
275
 
275
- if legacy {
276
- if let Some(Value::String(r)) = e.get("$ref") {
277
- // In draft 7 and earlier, $ref replaces every sibling keyword.
278
- return self.compile_ref(id, target, r);
279
- }
276
+ if legacy && let Some(Value::String(r)) = e.get("$ref") {
277
+ // In draft 7 and earlier, $ref replaces every sibling keyword.
278
+ return self.compile_ref(id, target, r);
280
279
  }
281
280
 
282
281
  let applicator = legacy || voc & vocab::APPLICATOR != 0;
@@ -295,15 +294,15 @@ impl<'l, 'o> SchemaCompiler<'l, 'o> {
295
294
  if let Some(Value::String(r)) = e.get("$ref") {
296
295
  self.compile_ref(id, target, r)?;
297
296
  }
298
- if dialect >= Dialect::Draft202012 {
299
- if let Some(Value::String(r)) = e.get("$dynamicRef") {
300
- self.compile_dynamic_ref(id, target, r, false)?;
301
- }
297
+ if dialect >= Dialect::Draft202012
298
+ && let Some(Value::String(r)) = e.get("$dynamicRef")
299
+ {
300
+ self.compile_dynamic_ref(id, target, r, false)?;
302
301
  }
303
- if dialect == Dialect::Draft201909 {
304
- if let Some(Value::String(r)) = e.get("$recursiveRef") {
305
- self.compile_dynamic_ref(id, target, r, true)?;
306
- }
302
+ if dialect == Dialect::Draft201909
303
+ && let Some(Value::String(r)) = e.get("$recursiveRef")
304
+ {
305
+ self.compile_dynamic_ref(id, target, r, true)?;
307
306
  }
308
307
 
309
308
  // Applicators.
@@ -321,18 +320,18 @@ impl<'l, 'o> SchemaCompiler<'l, 'o> {
321
320
  let c = self.child(target, v, "/not");
322
321
  self.nodes[id as usize].not = Some(c);
323
322
  }
324
- if dialect >= Dialect::Draft7 {
325
- if let Some(v) = e.get("if") {
326
- let c = self.child(target, v, "/if");
327
- self.nodes[id as usize].if_ = Some(c);
328
- if let Some(v) = e.get("then") {
329
- let c = self.child(target, v, "/then");
330
- self.nodes[id as usize].then = Some(c);
331
- }
332
- if let Some(v) = e.get("else") {
333
- let c = self.child(target, v, "/else");
334
- self.nodes[id as usize].else_ = Some(c);
335
- }
323
+ if dialect >= Dialect::Draft7
324
+ && let Some(v) = e.get("if")
325
+ {
326
+ let c = self.child(target, v, "/if");
327
+ self.nodes[id as usize].if_ = Some(c);
328
+ if let Some(v) = e.get("then") {
329
+ let c = self.child(target, v, "/then");
330
+ self.nodes[id as usize].then = Some(c);
331
+ }
332
+ if let Some(v) = e.get("else") {
333
+ let c = self.child(target, v, "/else");
334
+ self.nodes[id as usize].else_ = Some(c);
336
335
  }
337
336
  }
338
337
  if let Some(Value::Object(props)) = e.get("properties") {
@@ -346,11 +345,8 @@ impl<'l, 'o> SchemaCompiler<'l, 'o> {
346
345
  if let Some(Value::Object(pp)) = e.get("patternProperties") {
347
346
  let mut list = Vec::with_capacity(pp.len());
348
347
  for (pattern, v) in pp {
349
- let compiled = crate::pattern::compile(pattern).ok_or_else(|| {
350
- SchemaCompilationError::new(format!(
351
- "Invalid regular expression '{pattern}' in patternProperties."
352
- ))
353
- })?;
348
+ let compiled =
349
+ crate::pattern::compile(pattern, grammar).map_err(|e| e.of(pattern, "patternProperties"))?;
354
350
  let c = self.child(target, v, &format!("/patternProperties/{}", escape_pointer_token(pattern)));
355
351
  list.push(PatternProperty { pattern: compiled, node: c });
356
352
  }
@@ -384,11 +380,11 @@ impl<'l, 'o> SchemaCompiler<'l, 'o> {
384
380
  self.nodes[id as usize].prefix_items =
385
381
  self.child_array(target, e.get("prefixItems"), "prefixItems");
386
382
  }
387
- if let Some(v) = e.get("items") {
388
- if !v.is_array() {
389
- let c = self.child(target, v, &kw("items"));
390
- self.nodes[id as usize].items = Some(c);
391
- }
383
+ if let Some(v) = e.get("items")
384
+ && !v.is_array()
385
+ {
386
+ let c = self.child(target, v, &kw("items"));
387
+ self.nodes[id as usize].items = Some(c);
392
388
  }
393
389
  } else if let Some(v) = e.get("items") {
394
390
  if v.is_array() {
@@ -431,10 +427,10 @@ impl<'l, 'o> SchemaCompiler<'l, 'o> {
431
427
  n.type_mask = mask;
432
428
  n.has_type = true;
433
429
  }
434
- if dialect >= Dialect::Draft6 {
435
- if let Some(c) = e.get("const") {
436
- n.const_value = Some(c.clone());
437
- }
430
+ if dialect >= Dialect::Draft6
431
+ && let Some(c) = e.get("const")
432
+ {
433
+ n.const_value = Some(c.clone());
438
434
  }
439
435
  if let Some(Value::Array(values)) = e.get("enum") {
440
436
  n.enum_values = Some(values.clone());
@@ -450,18 +446,18 @@ impl<'l, 'o> SchemaCompiler<'l, 'o> {
450
446
  n.required_list = Some(list);
451
447
  n.required = Some(dedup);
452
448
  }
453
- if dialect >= Dialect::Draft201909 {
454
- if let Some(Value::Object(dr)) = e.get("dependentRequired") {
455
- let deps = dependencies.get_or_insert_with(Vec::new);
456
- for (name, v) in dr {
457
- if let Value::Array(list) = v {
458
- deps.push(DependencyEntry {
459
- keyword: DependencyKeyword::DependentRequired,
460
- name: name.clone(),
461
- required: Some(strings(list)),
462
- schema: None,
463
- });
464
- }
449
+ if dialect >= Dialect::Draft201909
450
+ && let Some(Value::Object(dr)) = e.get("dependentRequired")
451
+ {
452
+ let deps = dependencies.get_or_insert_with(Vec::new);
453
+ for (name, v) in dr {
454
+ if let Value::Array(list) = v {
455
+ deps.push(DependencyEntry {
456
+ keyword: DependencyKeyword::DependentRequired,
457
+ name: name.clone(),
458
+ required: Some(strings(list)),
459
+ schema: None,
460
+ });
465
461
  }
466
462
  }
467
463
  }
@@ -473,9 +469,7 @@ impl<'l, 'o> SchemaCompiler<'l, 'o> {
473
469
  n.min_length = get_uint(e.get("minLength"));
474
470
  n.max_length = get_uint(e.get("maxLength"));
475
471
  if let Some(Value::String(p)) = e.get("pattern") {
476
- n.pattern = Some(crate::pattern::compile(p).ok_or_else(|| {
477
- SchemaCompilationError::new(format!("Invalid regular expression '{p}' in pattern."))
478
- })?);
472
+ n.pattern = Some(crate::pattern::compile(p, grammar).map_err(|e| e.of(p, "pattern"))?);
479
473
  }
480
474
  if let Some(Value::Number(m)) = e.get("multipleOf") {
481
475
  n.multiple_of = Some(m.clone());
@@ -518,7 +512,7 @@ impl<'l, 'o> SchemaCompiler<'l, 'o> {
518
512
 
519
513
  if let Some(Value::String(f)) = e.get("format") {
520
514
  n.format = Some(f.clone());
521
- n.format_kind = FormatKind::of(f, dialect);
515
+ n.format_kind = FormatKind::of(f, dialect, grammar);
522
516
  n.assert_format = format_assert;
523
517
  }
524
518
 
@@ -577,17 +571,17 @@ impl<'l, 'o> SchemaCompiler<'l, 'o> {
577
571
  }
578
572
  }
579
573
  }
580
- if dialect >= Dialect::Draft201909 {
581
- if let Some(Value::Object(ds)) = e.get("dependentSchemas") {
582
- for (name, v) in ds {
583
- let c = self.child(target, v, &format!("/dependentSchemas/{}", escape_pointer_token(name)));
584
- out.push(DependencyEntry {
585
- keyword: DependencyKeyword::DependentSchemas,
586
- name: name.clone(),
587
- required: None,
588
- schema: Some(c),
589
- });
590
- }
574
+ if dialect >= Dialect::Draft201909
575
+ && let Some(Value::Object(ds)) = e.get("dependentSchemas")
576
+ {
577
+ for (name, v) in ds {
578
+ let c = self.child(target, v, &format!("/dependentSchemas/{}", escape_pointer_token(name)));
579
+ out.push(DependencyEntry {
580
+ keyword: DependencyKeyword::DependentSchemas,
581
+ name: name.clone(),
582
+ required: None,
583
+ schema: Some(c),
584
+ });
591
585
  }
592
586
  }
593
587
  out
@@ -906,15 +900,15 @@ impl<'l, 'o> SchemaCompiler<'l, 'o> {
906
900
 
907
901
  fn compute_discriminators(&mut self) {
908
902
  for i in 0..self.nodes.len() {
909
- if let Some(branches) = self.nodes[i].one_of.clone() {
910
- if branches.len() > 1 {
911
- self.nodes[i].one_of_discriminator = self.build_discriminator(&branches).map(Box::new);
912
- }
903
+ if let Some(branches) = self.nodes[i].one_of.clone()
904
+ && branches.len() > 1
905
+ {
906
+ self.nodes[i].one_of_discriminator = self.build_discriminator(&branches).map(Box::new);
913
907
  }
914
- if let Some(branches) = self.nodes[i].any_of.clone() {
915
- if branches.len() > 1 {
916
- self.nodes[i].any_of_discriminator = self.build_discriminator(&branches).map(Box::new);
917
- }
908
+ if let Some(branches) = self.nodes[i].any_of.clone()
909
+ && branches.len() > 1
910
+ {
911
+ self.nodes[i].any_of_discriminator = self.build_discriminator(&branches).map(Box::new);
918
912
  }
919
913
  }
920
914
  }
@@ -986,32 +980,32 @@ impl<'l, 'o> SchemaCompiler<'l, 'o> {
986
980
  return Class::Wildcard;
987
981
  };
988
982
  let p = self.effective_node(child);
989
- if let Some(c) = &p.const_value {
990
- if let Some(v) = primitive(c) {
991
- return Class::Positive(vec![v]);
992
- }
983
+ if let Some(c) = &p.const_value
984
+ && let Some(v) = primitive(c)
985
+ {
986
+ return Class::Positive(vec![v]);
993
987
  }
994
- if let Some(values) = &p.enum_values {
995
- if !values.is_empty() {
996
- let prims: Vec<DiscriminatorValue> = values.iter().filter_map(primitive).collect();
997
- if prims.len() == values.len() {
998
- return Class::Positive(prims);
999
- }
988
+ if let Some(values) = &p.enum_values
989
+ && !values.is_empty()
990
+ {
991
+ let prims: Vec<DiscriminatorValue> = values.iter().filter_map(primitive).collect();
992
+ if prims.len() == values.len() {
993
+ return Class::Positive(prims);
1000
994
  }
1001
995
  }
1002
- if let Some(not) = p.not {
1003
- if p.has_type && p.type_mask == type_mask::STRING {
1004
- let not = self.effective_node(not);
1005
- if let Some(values) = &not.enum_values {
1006
- if values.iter().all(Value::is_string)
1007
- && !not.has_type
1008
- && not.const_value.is_none()
1009
- && !not.has_string_keywords()
1010
- && !not.has_in_place_applicators()
1011
- {
1012
- return Class::Negative(values.iter().filter_map(primitive).collect());
1013
- }
1014
- }
996
+ if let Some(not) = p.not
997
+ && p.has_type
998
+ && p.type_mask == type_mask::STRING
999
+ {
1000
+ let not = self.effective_node(not);
1001
+ if let Some(values) = &not.enum_values
1002
+ && values.iter().all(Value::is_string)
1003
+ && !not.has_type
1004
+ && not.const_value.is_none()
1005
+ && !not.has_string_keywords()
1006
+ && !not.has_in_place_applicators()
1007
+ {
1008
+ return Class::Negative(values.iter().filter_map(primitive).collect());
1015
1009
  }
1016
1010
  }
1017
1011
  Class::Wildcard
@@ -552,9 +552,9 @@ impl<'s> Parser<'s> {
552
552
  return;
553
553
  }
554
554
  let mut kept: Vec<Node> = Vec::with_capacity(pairs.len());
555
- for p in pairs.chunks_exact(2) {
555
+ for p in pairs.as_chunks::<2>().0 {
556
556
  let name = self.key_str(&p[0]);
557
- match kept.chunks_exact(2).position(|q| self.key_str(&q[0]) == name) {
557
+ match kept.as_chunks::<2>().0.iter().position(|q| self.key_str(&q[0]) == name) {
558
558
  Some(at) => kept[2 * at + 1] = p[1],
559
559
  None => kept.extend_from_slice(p),
560
560
  }
@@ -26,7 +26,7 @@ use crate::eval::{json_equal, matches_type};
26
26
  use crate::instance::{Instance, ObjectView, View};
27
27
  use crate::node::*;
28
28
  use crate::numbers::Num;
29
- use crate::pattern::Pattern;
29
+ use crate::pattern::{Pattern, PatternBudget};
30
30
 
31
31
  const MAX_NAMES: usize = 256;
32
32
  const MAX_CONDITIONS: usize = 64;
@@ -132,11 +132,11 @@ enum TestKind {
132
132
  }
133
133
 
134
134
  impl ValueTest {
135
- fn holds<'x, I: Instance<'x>>(&self, v: I) -> bool {
135
+ fn holds<'x, I: Instance<'x>>(&self, v: I, budget: &mut PatternBudget) -> bool {
136
136
  match &self.kind {
137
137
  TestKind::Allowed(values) => values.iter().any(|a| json_equal(a, v)),
138
138
  TestKind::Pattern { pattern, requires_string } => match v.view() {
139
- View::String(s) => pattern.is_match(s),
139
+ View::String(s) => pattern.is_match(s, budget),
140
140
  _ => !requires_string,
141
141
  },
142
142
  }
@@ -696,7 +696,7 @@ pub(super) fn try_fuse(
696
696
  // A known name matching an absent pattern fails its condition whatever its value: no constant is allowed.
697
697
  for (e, name) in names.iter().enumerate() {
698
698
  for (condition, pattern) in &absent {
699
- if pattern.is_match(name) {
699
+ if pattern.is_match(name, &mut p.compile_budget.lock().unwrap_or_else(|e| e.into_inner())) {
700
700
  tests[e].push(ValueTest { condition: *condition, kind: TestKind::Allowed(Box::new([])) });
701
701
  }
702
702
  }
@@ -714,7 +714,7 @@ pub(super) fn try_fuse(
714
714
  apps.push((c as u16, app_child(schema), Some(p.fast_target[schema as usize])));
715
715
  }
716
716
  for pp in b.pattern_properties.iter().flatten() {
717
- if pp.pattern.is_match(name) {
717
+ if pp.pattern.is_match(name, &mut p.compile_budget.lock().unwrap_or_else(|e| e.into_inner())) {
718
718
  matched = true;
719
719
  apps.push((c as u16, app_child(pp.node), Some(p.fast_target[pp.node as usize])));
720
720
  }
@@ -942,7 +942,7 @@ impl Evaluator<'_, '_> {
942
942
  fn resolve_unknown<'x, I: Instance<'x>>(&mut self, c: &Contributor, name: &str, v: I) -> Option<bool> {
943
943
  let mut matched = false;
944
944
  for (pattern, child) in c.patterns.iter() {
945
- if pattern.is_match(name) {
945
+ if pattern.is_match(name, &mut self.budget) {
946
946
  matched = true;
947
947
  if !self.apply(*child, v) {
948
948
  return None;
@@ -965,7 +965,11 @@ impl Evaluator<'_, '_> {
965
965
  Some(merged) => {
966
966
  let allowed = merged.allowed(v);
967
967
  for (t, test) in entry.tests.iter().enumerate() {
968
- let holds = if merged.keyed & (1 << t) != 0 { allowed & (1 << t) != 0 } else { test.holds(v) };
968
+ let holds = if merged.keyed & (1 << t) != 0 {
969
+ allowed & (1 << t) != 0
970
+ } else {
971
+ test.holds(v, &mut self.budget)
972
+ };
969
973
  if !holds {
970
974
  pass.failed |= 1 << test.condition;
971
975
  }
@@ -973,7 +977,7 @@ impl Evaluator<'_, '_> {
973
977
  }
974
978
  None => {
975
979
  for test in entry.tests.iter() {
976
- if !test.holds(v) {
980
+ if !test.holds(v, &mut self.budget) {
977
981
  pass.failed |= 1 << test.condition;
978
982
  }
979
983
  }
@@ -1004,7 +1008,7 @@ impl Evaluator<'_, '_> {
1004
1008
  return Outcome::Done { cover, defer };
1005
1009
  }
1006
1010
  for (c, pattern) in f.absent.iter() {
1007
- if pass.failed & (1 << c) == 0 && pattern.is_match(name) {
1011
+ if pass.failed & (1 << c) == 0 && pattern.is_match(name, &mut self.budget) {
1008
1012
  pass.failed |= 1 << c;
1009
1013
  }
1010
1014
  }
@@ -21,7 +21,7 @@ use crate::instance::{ArrayView, Instance, Kind, ObjectView, View, str_eq};
21
21
  use crate::node::*;
22
22
  use crate::numbers::{Divisor, Num, cmp};
23
23
  use crate::options::FormatValidator;
24
- use crate::pattern::Pattern;
24
+ use crate::pattern::{Pattern, PatternBudget};
25
25
 
26
26
  /// Every JSON type (a node without `type`).
27
27
  const ANY: u8 = 0x7f;
@@ -790,7 +790,8 @@ fn plan_node(p: &Program, id: NodeId, n: &SchemaNode, target: &dyn Fn(NodeId) ->
790
790
  .iter()
791
791
  .map(|name| {
792
792
  let patterns = n.pattern_properties.iter().flatten().enumerate();
793
- patterns.filter(|(_, pp)| pp.pattern.is_match(name)).map(|(j, _)| j as u16).collect()
793
+ let mut budget = p.compile_budget.lock().unwrap_or_else(|e| e.into_inner());
794
+ patterns.filter(|(_, pp)| pp.pattern.is_match(name, &mut budget)).map(|(j, _)| j as u16).collect()
794
795
  })
795
796
  .collect(),
796
797
  additional: n.additional_properties.map(child),
@@ -1086,13 +1087,13 @@ impl Evaluator<'_, '_> {
1086
1087
  #[inline(always)]
1087
1088
  fn enter<'x, I: Instance<'x>>(&mut self, shape: Shape, b: &Body, x: I) -> bool {
1088
1089
  match shape {
1089
- Shape::Leaf => run_leaf(b, x),
1090
+ Shape::Leaf => run_leaf(b, x, &mut self.budget),
1090
1091
  Shape::StringEnum => match (&b.values[0], x.view()) {
1091
1092
  (Op::EnumStrings(values), View::String(s)) => values.contains(s),
1092
1093
  _ => false,
1093
1094
  },
1094
1095
  Shape::Strings => match x.view() {
1095
- View::String(s) => run_string(&b.string, s),
1096
+ View::String(s) => run_string(&b.string, s, &mut self.budget),
1096
1097
  _ => true,
1097
1098
  },
1098
1099
  Shape::Object => match x.view() {
@@ -1219,7 +1220,7 @@ impl Evaluator<'_, '_> {
1219
1220
  }
1220
1221
  let ok = match view {
1221
1222
  View::Number(n) => b.number.is_empty() || run_number(&b.number, &n),
1222
- View::String(s) => b.string.is_empty() || run_string(&b.string, s),
1223
+ View::String(s) => b.string.is_empty() || run_string(&b.string, s, &mut self.budget),
1223
1224
  View::Object(o) => b.object.as_ref().is_none_or(|plan| self.run_object(plan, o, x)),
1224
1225
  View::Array(a) => {
1225
1226
  b.array.as_ref().is_none_or(|plan| self.run_array::<I>(plan, a))
@@ -1423,7 +1424,7 @@ impl Evaluator<'_, '_> {
1423
1424
  fn visit_pattern<'x, I: Instance<'x>>(&mut self, plan: &ObjectPlan, o: I::Object) -> bool {
1424
1425
  let (pattern, c) = &plan.patterns[0];
1425
1426
  for (k, v) in o.iter() {
1426
- let applies = if pattern.is_match(k) { Some(*c) } else { plan.additional };
1427
+ let applies = if pattern.is_match(k, &mut self.budget) { Some(*c) } else { plan.additional };
1427
1428
  if let Some(c) = applies
1428
1429
  && !self.run_child(c, v)
1429
1430
  {
@@ -1455,7 +1456,7 @@ impl Evaluator<'_, '_> {
1455
1456
  }
1456
1457
  } else {
1457
1458
  for (pattern, c) in plan.patterns.iter() {
1458
- if pattern.is_match(k) {
1459
+ if pattern.is_match(k, &mut self.budget) {
1459
1460
  matched = true;
1460
1461
  if !self.run_child(*c, v) {
1461
1462
  return None;
@@ -1557,7 +1558,7 @@ fn all_of_type<'x, I: Instance<'x>>(mut items: impl Iterator<Item = I>, types: u
1557
1558
 
1558
1559
  /// A leaf's keywords: its value constraints, then those for the instance's type.
1559
1560
  #[inline]
1560
- fn run_leaf<'x, I: Instance<'x>>(b: &Body, x: I) -> bool {
1561
+ fn run_leaf<'x, I: Instance<'x>>(b: &Body, x: I, budget: &mut PatternBudget) -> bool {
1561
1562
  for op in b.values.iter() {
1562
1563
  let ok = match op {
1563
1564
  Op::Const(c) => json_equal(x, c),
@@ -1571,7 +1572,7 @@ fn run_leaf<'x, I: Instance<'x>>(b: &Body, x: I) -> bool {
1571
1572
  }
1572
1573
  match x.view() {
1573
1574
  View::Number(n) => b.number.is_empty() || run_number(&b.number, &n),
1574
- View::String(s) => b.string.is_empty() || run_string(&b.string, s),
1575
+ View::String(s) => b.string.is_empty() || run_string(&b.string, s, budget),
1575
1576
  _ => true,
1576
1577
  }
1577
1578
  }
@@ -1590,10 +1591,10 @@ fn run_number(ops: &[NumberOp], n: &Number) -> bool {
1590
1591
  }
1591
1592
 
1592
1593
  #[inline(never)]
1593
- fn run_string(ops: &[StringOp], s: &str) -> bool {
1594
+ fn run_string(ops: &[StringOp], s: &str, budget: &mut PatternBudget) -> bool {
1594
1595
  ops.iter().all(|op| match op {
1595
1596
  StringOp::Length { min, max } => length_ok(s, *min, *max),
1596
- StringOp::Pattern(p) => p.is_match(s),
1597
+ StringOp::Pattern(p) => p.is_match(s, budget),
1597
1598
  StringOp::Format(f) => f.string(s),
1598
1599
  StringOp::Content(kind) => content_ok(s, *kind),
1599
1600
  })