corvus_json_schema 0.1.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.
Files changed (33) hide show
  1. checksums.yaml +7 -0
  2. data/Cargo.lock +390 -0
  3. data/Cargo.toml +8 -0
  4. data/LICENSE +201 -0
  5. data/README.md +111 -0
  6. data/VERSIONHISTORY.md +7 -0
  7. data/ext/corvus_json_schema/Cargo.toml +18 -0
  8. data/ext/corvus_json_schema/extconf.rb +6 -0
  9. data/ext/corvus_json_schema/rustfmt.toml +2 -0
  10. data/ext/corvus_json_schema/src/lib.rs +643 -0
  11. data/ext/corvus_json_schema/vendor/corvus-json-schema/Cargo.toml +35 -0
  12. data/ext/corvus_json_schema/vendor/corvus-json-schema/LICENSE +201 -0
  13. data/ext/corvus_json_schema/vendor/corvus-json-schema/README.md +139 -0
  14. data/ext/corvus_json_schema/vendor/corvus-json-schema/src/compiler.rs +1118 -0
  15. data/ext/corvus_json_schema/vendor/corvus-json-schema/src/dialect.rs +108 -0
  16. data/ext/corvus_json_schema/vendor/corvus-json-schema/src/document.rs +905 -0
  17. data/ext/corvus_json_schema/vendor/corvus-json-schema/src/eval/plan/fused.rs +1187 -0
  18. data/ext/corvus_json_schema/vendor/corvus-json-schema/src/eval/plan.rs +1728 -0
  19. data/ext/corvus_json_schema/vendor/corvus-json-schema/src/eval.rs +1451 -0
  20. data/ext/corvus_json_schema/vendor/corvus-json-schema/src/formats.rs +1021 -0
  21. data/ext/corvus_json_schema/vendor/corvus-json-schema/src/instance.rs +228 -0
  22. data/ext/corvus_json_schema/vendor/corvus-json-schema/src/lib.rs +189 -0
  23. data/ext/corvus_json_schema/vendor/corvus-json-schema/src/loader.rs +403 -0
  24. data/ext/corvus_json_schema/vendor/corvus-json-schema/src/metaschemas.rs +29 -0
  25. data/ext/corvus_json_schema/vendor/corvus-json-schema/src/node.rs +417 -0
  26. data/ext/corvus_json_schema/vendor/corvus-json-schema/src/numbers.rs +244 -0
  27. data/ext/corvus_json_schema/vendor/corvus-json-schema/src/options.rs +108 -0
  28. data/ext/corvus_json_schema/vendor/corvus-json-schema/src/pattern.rs +1591 -0
  29. data/ext/corvus_json_schema/vendor/corvus-json-schema/src/results.rs +316 -0
  30. data/ext/corvus_json_schema/vendor/corvus-json-schema/src/uri.rs +274 -0
  31. data/lib/corvus_json_schema/version.rb +5 -0
  32. data/lib/corvus_json_schema.rb +65 -0
  33. metadata +94 -0
@@ -0,0 +1,1118 @@
1
+ //! Compiles loaded schema documents into a `SchemaNode` graph and runs the compile-time analyses.
2
+ //! A port of `Corvus.Text.Json.RuntimeEvaluator.Compilation.SchemaCompiler` (via the TypeScript port's `compiler.ts`).
3
+
4
+ use std::collections::HashMap;
5
+
6
+ use serde_json::{Map, Value};
7
+
8
+ use crate::dialect::{Dialect, vocab};
9
+ use crate::formats::FormatKind;
10
+ use crate::loader::{SchemaLoader, SchemaTarget, addr};
11
+ use crate::node::*;
12
+ use crate::options::{CompileOptions, SchemaCompilationError};
13
+ use crate::uri::{decode_fragment, escape_pointer_token, split};
14
+
15
+ /// Keywords `SchemaCompiler.CompileNode` handles itself; anything else is an unknown keyword (an annotation from
16
+ /// 2019-09).
17
+ const KNOWN_KEYWORDS: &[&str] = &[
18
+ "if",
19
+ "not",
20
+ "type",
21
+ "enum",
22
+ "$ref",
23
+ "then",
24
+ "else",
25
+ "const",
26
+ "items",
27
+ "allOf",
28
+ "anyOf",
29
+ "oneOf",
30
+ "title",
31
+ "$defs",
32
+ "format",
33
+ "pattern",
34
+ "maximum",
35
+ "minimum",
36
+ "default",
37
+ "$schema",
38
+ "$anchor",
39
+ "required",
40
+ "contains",
41
+ "maxItems",
42
+ "minItems",
43
+ "examples",
44
+ "readOnly",
45
+ "$comment",
46
+ "maxLength",
47
+ "minLength",
48
+ "writeOnly",
49
+ "properties",
50
+ "multipleOf",
51
+ "deprecated",
52
+ "uniqueItems",
53
+ "prefixItems",
54
+ "minContains",
55
+ "maxContains",
56
+ "description",
57
+ "$vocabulary",
58
+ "definitions",
59
+ "$dynamicRef",
60
+ "dependencies",
61
+ "propertyNames",
62
+ "maxProperties",
63
+ "minProperties",
64
+ "contentSchema",
65
+ "$recursiveRef",
66
+ "$dynamicAnchor",
67
+ "contentEncoding",
68
+ "additionalItems",
69
+ "exclusiveMaximum",
70
+ "exclusiveMinimum",
71
+ "unevaluatedItems",
72
+ "contentMediaType",
73
+ "$recursiveAnchor",
74
+ "dependentSchemas",
75
+ "patternProperties",
76
+ "dependentRequired",
77
+ "additionalProperties",
78
+ "unevaluatedProperties",
79
+ "id",
80
+ "$id",
81
+ ];
82
+
83
+ struct PendingDynamicRef {
84
+ node_id: NodeId,
85
+ anchor: String,
86
+ is_recursive: bool,
87
+ initial_target: SchemaTarget,
88
+ seen_resources: Vec<bool>,
89
+ candidates: Vec<(u32, NodeId)>,
90
+ }
91
+
92
+ /// What a node's annotations are computed from, resolved on the first evaluation with a collector.
93
+ #[derive(Clone, Copy)]
94
+ pub(crate) struct AnnotationSource {
95
+ pub document: u32,
96
+ pub vocab: u32,
97
+ pub content: bool,
98
+ }
99
+
100
+ /// The compiled program: the node graph, its entry node and whether it maintains a dynamic scope.
101
+ pub(crate) struct CompiledSchema {
102
+ pub nodes: Vec<SchemaNode>,
103
+ pub root: NodeId,
104
+ pub uses_dynamic_scope: bool,
105
+ /// The loaded documents (kept for the annotation keywords, which only results collection needs). Boxed so
106
+ /// each root keeps its address: schema values are identified by address.
107
+ #[allow(clippy::vec_box)]
108
+ pub documents: Vec<Box<Value>>,
109
+ pub annotation_sources: Vec<Option<AnnotationSource>>,
110
+ }
111
+
112
+ pub(crate) struct SchemaCompiler<'l, 'o> {
113
+ loader: &'l mut SchemaLoader<'o>,
114
+ options: &'o CompileOptions,
115
+ nodes: Vec<SchemaNode>,
116
+ targets: Vec<SchemaTarget>,
117
+ /// The compiled node of each schema value, by (document, address).
118
+ node_of: HashMap<(u32, usize), NodeId>,
119
+ worklist_head: usize,
120
+ pending_dynamic_refs: Vec<PendingDynamicRef>,
121
+ annotation_sources: Vec<Option<AnnotationSource>>,
122
+ entry_node: NodeId,
123
+ }
124
+
125
+ fn get_uint(v: Option<&Value>) -> Option<u64> {
126
+ match v {
127
+ Some(Value::Number(n)) => {
128
+ if let Some(u) = n.as_u64() {
129
+ Some(u)
130
+ } else {
131
+ let f = n.as_f64()?;
132
+ (f >= 0.0 && f.fract() == 0.0 && f < 1.8e19).then_some(f as u64)
133
+ }
134
+ }
135
+ _ => None,
136
+ }
137
+ }
138
+
139
+ fn type_mask_of(name: &Value) -> u8 {
140
+ match name.as_str() {
141
+ Some("null") => type_mask::NULL,
142
+ Some("boolean") => type_mask::BOOLEAN,
143
+ Some("object") => type_mask::OBJECT,
144
+ Some("array") => type_mask::ARRAY,
145
+ Some("number") => type_mask::NUMBER,
146
+ Some("string") => type_mask::STRING,
147
+ Some("integer") => type_mask::INTEGER,
148
+ _ => 0,
149
+ }
150
+ }
151
+
152
+ fn strings(v: &[Value]) -> Vec<String> {
153
+ v.iter().filter_map(|x| x.as_str().map(String::from)).collect()
154
+ }
155
+
156
+ impl<'l, 'o> SchemaCompiler<'l, 'o> {
157
+ pub fn compile(schema: Value, options: &'o CompileOptions) -> Result<CompiledSchema, SchemaCompilationError> {
158
+ let mut loader = SchemaLoader::new(options);
159
+ let root_resource = loader.load_root(schema, options.base_uri.as_deref())?;
160
+ Self::compile_loaded(loader, root_resource, options)
161
+ }
162
+
163
+ pub fn compile_from_uri(uri: &str, options: &'o CompileOptions) -> Result<CompiledSchema, SchemaCompilationError> {
164
+ let mut loader = SchemaLoader::new(options);
165
+ let root_resource = loader.load_root_from_uri(uri)?;
166
+ Self::compile_loaded(loader, root_resource, options)
167
+ }
168
+
169
+ fn compile_loaded(
170
+ mut loader: SchemaLoader<'o>,
171
+ root_resource: u32,
172
+ options: &'o CompileOptions,
173
+ ) -> Result<CompiledSchema, SchemaCompilationError> {
174
+ let mut target = loader.root_target(root_resource);
175
+ if let Some(entry) = &options.entry_point {
176
+ target = loader
177
+ .try_resolve_reference(root_resource, entry)?
178
+ .ok_or_else(|| SchemaCompilationError::new(format!("Unable to resolve the entry point '{entry}'.")))?;
179
+ }
180
+ let mut compiler = SchemaCompiler {
181
+ loader: &mut loader,
182
+ options,
183
+ nodes: Vec::new(),
184
+ targets: Vec::new(),
185
+ node_of: HashMap::new(),
186
+ worklist_head: 0,
187
+ pending_dynamic_refs: Vec::new(),
188
+ annotation_sources: Vec::new(),
189
+ entry_node: 0,
190
+ };
191
+ compiler.entry_node = compiler.get_node(&target);
192
+ compiler.compile_all()?;
193
+ let uses_dynamic_scope = compiler.nodes.iter().any(|n| n.dynamic_ref.is_some());
194
+ compiler.analyse();
195
+ let root = compiler.entry_node;
196
+ let nodes = std::mem::take(&mut compiler.nodes);
197
+ let annotation_sources = std::mem::take(&mut compiler.annotation_sources);
198
+ drop(compiler);
199
+ let documents = loader.documents.into_iter().map(|d| d.root).collect();
200
+ Ok(CompiledSchema { nodes, root, uses_dynamic_scope, documents, annotation_sources })
201
+ }
202
+
203
+ fn get_node(&mut self, target: &SchemaTarget) -> NodeId {
204
+ let key = (target.document, target.value as usize);
205
+ if let Some(&id) = self.node_of.get(&key) {
206
+ return id;
207
+ }
208
+ let id = self.nodes.len() as NodeId;
209
+ let resource = &self.loader.resources[target.resource as usize];
210
+ self.nodes.push(SchemaNode::new(target.resource, resource.dialect, target.pointer.clone()));
211
+ self.targets.push(target.clone());
212
+ self.annotation_sources.push(None);
213
+ self.node_of.insert(key, id);
214
+ id
215
+ }
216
+
217
+ fn compile_all(&mut self) -> Result<(), SchemaCompilationError> {
218
+ loop {
219
+ while self.worklist_head < self.nodes.len() {
220
+ let id = self.worklist_head;
221
+ self.worklist_head += 1;
222
+ let target = self.targets[id].clone();
223
+ self.compile_node(id as NodeId, &target)?;
224
+ }
225
+ if self.pending_dynamic_refs.is_empty()
226
+ || (!self.expand_dynamic_refs() && self.worklist_head == self.nodes.len())
227
+ {
228
+ break;
229
+ }
230
+ }
231
+ // Most schemas have no dynamic reference: skip (and so never compile) the finalisation.
232
+ if !self.pending_dynamic_refs.is_empty() {
233
+ self.finalize_dynamic_refs()?;
234
+ }
235
+ Ok(())
236
+ }
237
+
238
+ fn child(&mut self, parent: &SchemaTarget, value: &Value, relative: &str) -> NodeId {
239
+ let pointer = format!("{}{}", parent.pointer, relative);
240
+ let resource = self.loader.resource_of(parent.document, value).unwrap_or(parent.resource);
241
+ self.get_node(&SchemaTarget { document: parent.document, pointer, value: value as *const Value, resource })
242
+ }
243
+
244
+ fn child_array(&mut self, parent: &SchemaTarget, value: Option<&Value>, keyword: &str) -> Option<Vec<NodeId>> {
245
+ let Some(Value::Array(items)) = value else {
246
+ return None;
247
+ };
248
+ Some(items.iter().enumerate().map(|(i, v)| self.child(parent, v, &format!("/{keyword}/{i}"))).collect())
249
+ }
250
+
251
+ fn compile_node(&mut self, id: NodeId, target: &SchemaTarget) -> Result<(), SchemaCompilationError> {
252
+ let element = target.value();
253
+ let e = match element {
254
+ Value::Bool(true) => {
255
+ self.nodes[id as usize].always_true = true;
256
+ return Ok(());
257
+ }
258
+ Value::Bool(false) => {
259
+ self.nodes[id as usize].always_false = true;
260
+ return Ok(());
261
+ }
262
+ Value::Object(map) => map,
263
+ _ => {
264
+ self.nodes[id as usize].always_true = true;
265
+ return Ok(());
266
+ }
267
+ };
268
+
269
+ let (dialect, voc) = {
270
+ let r = &self.loader.resources[target.resource as usize];
271
+ (r.dialect, r.vocabularies)
272
+ };
273
+ let legacy = dialect.is_legacy();
274
+
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
+ }
280
+ }
281
+
282
+ let applicator = legacy || voc & vocab::APPLICATOR != 0;
283
+ let validation = legacy || voc & vocab::VALIDATION != 0;
284
+ let unevaluated = if dialect == Dialect::Draft201909 { applicator } else { voc & vocab::UNEVALUATED != 0 };
285
+ let content = legacy || voc & vocab::CONTENT != 0;
286
+ let format_assert = self
287
+ .options
288
+ .assert_format
289
+ .unwrap_or((legacy && self.options.assert_format_in_legacy_drafts) || voc & vocab::FORMAT_ASSERTION != 0);
290
+ let kw = |name: &str| format!("/{}", escape_pointer_token(name));
291
+
292
+ let mut dependencies: Option<Vec<DependencyEntry>> = None;
293
+
294
+ // References.
295
+ if let Some(Value::String(r)) = e.get("$ref") {
296
+ self.compile_ref(id, target, r)?;
297
+ }
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
+ }
302
+ }
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
+ }
307
+ }
308
+
309
+ // Applicators.
310
+ if applicator {
311
+ if e.contains_key("allOf") {
312
+ self.nodes[id as usize].all_of = self.child_array(target, e.get("allOf"), "allOf");
313
+ }
314
+ if e.contains_key("anyOf") {
315
+ self.nodes[id as usize].any_of = self.child_array(target, e.get("anyOf"), "anyOf");
316
+ }
317
+ if e.contains_key("oneOf") {
318
+ self.nodes[id as usize].one_of = self.child_array(target, e.get("oneOf"), "oneOf");
319
+ }
320
+ if let Some(v) = e.get("not") {
321
+ let c = self.child(target, v, "/not");
322
+ self.nodes[id as usize].not = Some(c);
323
+ }
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
+ }
336
+ }
337
+ }
338
+ if let Some(Value::Object(props)) = e.get("properties") {
339
+ let mut list = Vec::with_capacity(props.len());
340
+ for (name, v) in props {
341
+ let c = self.child(target, v, &format!("/properties/{}", escape_pointer_token(name)));
342
+ list.push((name.clone(), c));
343
+ }
344
+ self.nodes[id as usize].properties = Some(list);
345
+ }
346
+ if let Some(Value::Object(pp)) = e.get("patternProperties") {
347
+ let mut list = Vec::with_capacity(pp.len());
348
+ 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
+ })?;
354
+ let c = self.child(target, v, &format!("/patternProperties/{}", escape_pointer_token(pattern)));
355
+ list.push(PatternProperty { pattern: compiled, node: c });
356
+ }
357
+ self.nodes[id as usize].pattern_properties = Some(list);
358
+ }
359
+ if let Some(v) = e.get("additionalProperties") {
360
+ let c = self.child(target, v, &kw("additionalProperties"));
361
+ self.nodes[id as usize].additional_properties = Some(c);
362
+ }
363
+ if dialect >= Dialect::Draft6 {
364
+ if let Some(v) = e.get("propertyNames") {
365
+ let c = self.child(target, v, &kw("propertyNames"));
366
+ self.nodes[id as usize].property_names = Some(c);
367
+ }
368
+ if let Some(v) = e.get("contains") {
369
+ let c = self.child(target, v, &kw("contains"));
370
+ self.nodes[id as usize].contains = Some(c);
371
+ }
372
+ }
373
+
374
+ // "dependencies" is honoured in every dialect: in 2019-09+ it is an optional compatibility keyword.
375
+ if matches!(e.get("dependencies"), Some(Value::Object(_)))
376
+ || (dialect >= Dialect::Draft201909 && matches!(e.get("dependentSchemas"), Some(Value::Object(_))))
377
+ {
378
+ dependencies = Some(self.compile_dependency_schemas(target, e, dialect));
379
+ }
380
+
381
+ // Array applicators.
382
+ if dialect >= Dialect::Draft202012 {
383
+ if let Some(Value::Array(_)) = e.get("prefixItems") {
384
+ self.nodes[id as usize].prefix_items =
385
+ self.child_array(target, e.get("prefixItems"), "prefixItems");
386
+ }
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
+ }
392
+ }
393
+ } else if let Some(v) = e.get("items") {
394
+ if v.is_array() {
395
+ let p = self.child_array(target, Some(v), "items");
396
+ let n = &mut self.nodes[id as usize];
397
+ n.prefix_items = p;
398
+ n.prefix_keyword = "items";
399
+ if let Some(a) = e.get("additionalItems") {
400
+ let c = self.child(target, a, "/additionalItems");
401
+ let n = &mut self.nodes[id as usize];
402
+ n.items = Some(c);
403
+ n.items_keyword = "additionalItems";
404
+ }
405
+ } else {
406
+ let c = self.child(target, v, &kw("items"));
407
+ self.nodes[id as usize].items = Some(c);
408
+ }
409
+ }
410
+ self.nodes[id as usize].contains_marks_evaluated = dialect >= Dialect::Draft202012;
411
+ }
412
+
413
+ if unevaluated && dialect >= Dialect::Draft201909 {
414
+ if let Some(v) = e.get("unevaluatedProperties") {
415
+ let c = self.child(target, v, &kw("unevaluatedProperties"));
416
+ self.nodes[id as usize].unevaluated_properties = Some(c);
417
+ }
418
+ if let Some(v) = e.get("unevaluatedItems") {
419
+ let c = self.child(target, v, &kw("unevaluatedItems"));
420
+ self.nodes[id as usize].unevaluated_items = Some(c);
421
+ }
422
+ }
423
+
424
+ let n = &mut self.nodes[id as usize];
425
+ if validation {
426
+ if let Some(t) = e.get("type") {
427
+ let mask = match t {
428
+ Value::Array(list) => list.iter().fold(0, |m, x| m | type_mask_of(x)),
429
+ other => type_mask_of(other),
430
+ };
431
+ n.type_mask = mask;
432
+ n.has_type = true;
433
+ }
434
+ if dialect >= Dialect::Draft6 {
435
+ if let Some(c) = e.get("const") {
436
+ n.const_value = Some(c.clone());
437
+ }
438
+ }
439
+ if let Some(Value::Array(values)) = e.get("enum") {
440
+ n.enum_values = Some(values.clone());
441
+ }
442
+ if let Some(Value::Array(req)) = e.get("required") {
443
+ let list = strings(req);
444
+ let mut dedup: Vec<String> = Vec::with_capacity(list.len());
445
+ for r in &list {
446
+ if !dedup.contains(r) {
447
+ dedup.push(r.clone());
448
+ }
449
+ }
450
+ n.required_list = Some(list);
451
+ n.required = Some(dedup);
452
+ }
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
+ }
465
+ }
466
+ }
467
+ }
468
+ n.min_properties = get_uint(e.get("minProperties"));
469
+ n.max_properties = get_uint(e.get("maxProperties"));
470
+ n.min_items = get_uint(e.get("minItems"));
471
+ n.max_items = get_uint(e.get("maxItems"));
472
+ n.unique_items = e.get("uniqueItems") == Some(&Value::Bool(true));
473
+ n.min_length = get_uint(e.get("minLength"));
474
+ n.max_length = get_uint(e.get("maxLength"));
475
+ 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
+ })?);
479
+ }
480
+ if let Some(Value::Number(m)) = e.get("multipleOf") {
481
+ n.multiple_of = Some(m.clone());
482
+ }
483
+ let num = |k: &str| match e.get(k) {
484
+ Some(Value::Number(x)) => Some(x.clone()),
485
+ _ => None,
486
+ };
487
+ if dialect == Dialect::Draft4 {
488
+ // Draft 4: exclusiveMaximum/exclusiveMinimum are booleans that make maximum/minimum exclusive.
489
+ if let Some(max) = num("maximum") {
490
+ if e.get("exclusiveMaximum") == Some(&Value::Bool(true)) {
491
+ n.exclusive_maximum = Some(max);
492
+ } else {
493
+ n.maximum = Some(max);
494
+ }
495
+ }
496
+ if let Some(min) = num("minimum") {
497
+ if e.get("exclusiveMinimum") == Some(&Value::Bool(true)) {
498
+ n.exclusive_minimum = Some(min);
499
+ } else {
500
+ n.minimum = Some(min);
501
+ }
502
+ }
503
+ } else {
504
+ n.maximum = num("maximum");
505
+ n.minimum = num("minimum");
506
+ n.exclusive_maximum = num("exclusiveMaximum");
507
+ n.exclusive_minimum = num("exclusiveMinimum");
508
+ }
509
+ if dialect >= Dialect::Draft201909 && n.contains.is_some() {
510
+ if e.contains_key("minContains") {
511
+ n.min_contains = get_uint(e.get("minContains")).unwrap_or(1);
512
+ }
513
+ if e.contains_key("maxContains") {
514
+ n.max_contains = get_uint(e.get("maxContains"));
515
+ }
516
+ }
517
+ }
518
+
519
+ if let Some(Value::String(f)) = e.get("format") {
520
+ n.format = Some(f.clone());
521
+ n.format_kind = FormatKind::of(f, dialect);
522
+ n.assert_format = format_assert;
523
+ }
524
+
525
+ // Content keywords are asserted only in draft 7 (and annotations elsewhere).
526
+ if content
527
+ && dialect >= Dialect::Draft7
528
+ && (e.contains_key("contentEncoding") || e.contains_key("contentMediaType"))
529
+ {
530
+ let base64 = e.get("contentEncoding").and_then(Value::as_str) == Some("base64");
531
+ let json = e.get("contentMediaType").and_then(Value::as_str) == Some("application/json");
532
+ n.content = match (base64, json) {
533
+ (true, true) => ContentKind::Base64Json,
534
+ (true, false) => ContentKind::Base64,
535
+ (false, true) => ContentKind::Json,
536
+ (false, false) => ContentKind::None,
537
+ };
538
+ n.assert_content =
539
+ dialect == Dialect::Draft7 && self.options.assert_content && n.content != ContentKind::None;
540
+ }
541
+
542
+ if let Some(deps) = &mut dependencies {
543
+ // In the order the three keywords appear in the schema, as SchemaCompiler.CompileNode meets them.
544
+ let order = |k: DependencyKeyword| e.keys().position(|x| x == k.name()).unwrap_or(usize::MAX);
545
+ deps.sort_by_key(|d| order(d.keyword));
546
+ }
547
+ n.dependencies = dependencies;
548
+ self.annotation_sources[id as usize] =
549
+ Some(AnnotationSource { document: target.document, vocab: voc, content });
550
+ Ok(())
551
+ }
552
+
553
+ fn compile_dependency_schemas(
554
+ &mut self,
555
+ target: &SchemaTarget,
556
+ e: &Map<String, Value>,
557
+ dialect: Dialect,
558
+ ) -> Vec<DependencyEntry> {
559
+ let mut out = Vec::new();
560
+ if let Some(Value::Object(deps)) = e.get("dependencies") {
561
+ for (name, v) in deps {
562
+ if let Value::Array(list) = v {
563
+ out.push(DependencyEntry {
564
+ keyword: DependencyKeyword::Dependencies,
565
+ name: name.clone(),
566
+ required: Some(strings(list)),
567
+ schema: None,
568
+ });
569
+ } else {
570
+ let c = self.child(target, v, &format!("/dependencies/{}", escape_pointer_token(name)));
571
+ out.push(DependencyEntry {
572
+ keyword: DependencyKeyword::Dependencies,
573
+ name: name.clone(),
574
+ required: None,
575
+ schema: Some(c),
576
+ });
577
+ }
578
+ }
579
+ }
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
+ }
591
+ }
592
+ }
593
+ out
594
+ }
595
+
596
+ fn resolve_or_fail(
597
+ &mut self,
598
+ target: &SchemaTarget,
599
+ reference: &str,
600
+ ) -> Result<SchemaTarget, SchemaCompilationError> {
601
+ self.loader.try_resolve_reference(target.resource, reference)?.ok_or_else(|| {
602
+ SchemaCompilationError::new(format!(
603
+ "Unable to resolve reference '{reference}' from '{}'.",
604
+ self.loader.resources[target.resource as usize].uri
605
+ ))
606
+ })
607
+ }
608
+
609
+ fn compile_ref(
610
+ &mut self,
611
+ id: NodeId,
612
+ target: &SchemaTarget,
613
+ reference: &str,
614
+ ) -> Result<(), SchemaCompilationError> {
615
+ let resolved = self.resolve_or_fail(target, reference)?;
616
+ let r = self.get_node(&resolved);
617
+ self.nodes[id as usize].ref_ = Some(r);
618
+ Ok(())
619
+ }
620
+
621
+ fn compile_dynamic_ref(
622
+ &mut self,
623
+ id: NodeId,
624
+ target: &SchemaTarget,
625
+ reference: &str,
626
+ is_recursive: bool,
627
+ ) -> Result<(), SchemaCompilationError> {
628
+ let resolved = self.resolve_or_fail(target, reference)?;
629
+ let fragment = decode_fragment(split(reference).1);
630
+ let res = &self.loader.resources[resolved.resource as usize];
631
+ let dynamic = if is_recursive {
632
+ res.recursive_anchor && resolved.pointer == res.root_pointer
633
+ } else {
634
+ !fragment.is_empty()
635
+ && !fragment.starts_with('/')
636
+ && res.dynamic_anchors.get(&fragment) == Some(&resolved.pointer)
637
+ };
638
+ let keyword = if is_recursive { StaticDynamicKeyword::RecursiveRef } else { StaticDynamicKeyword::DynamicRef };
639
+ if !dynamic {
640
+ // A static reference, kept apart from any sibling $ref (both apply).
641
+ let t = self.get_node(&resolved);
642
+ let n = &mut self.nodes[id as usize];
643
+ n.static_dynamic_ref = Some(t);
644
+ n.static_dynamic_keyword = keyword;
645
+ return Ok(());
646
+ }
647
+ self.pending_dynamic_refs.push(PendingDynamicRef {
648
+ node_id: id,
649
+ anchor: fragment,
650
+ is_recursive,
651
+ initial_target: resolved,
652
+ seen_resources: Vec::new(),
653
+ candidates: Vec::new(),
654
+ });
655
+ Ok(())
656
+ }
657
+
658
+ fn expand_dynamic_refs(&mut self) -> bool {
659
+ let mut added = false;
660
+ for p in 0..self.pending_dynamic_refs.len() {
661
+ let resource_count = self.loader.resources.len();
662
+ for resource in 0..resource_count {
663
+ let pending = &mut self.pending_dynamic_refs[p];
664
+ if pending.seen_resources.len() <= resource {
665
+ pending.seen_resources.resize(resource + 1, false);
666
+ }
667
+ if pending.seen_resources[resource] {
668
+ continue;
669
+ }
670
+ pending.seen_resources[resource] = true;
671
+ let r = &self.loader.resources[resource];
672
+ let pointer = if pending.is_recursive {
673
+ if !r.recursive_anchor {
674
+ continue;
675
+ }
676
+ r.root_pointer.clone()
677
+ } else {
678
+ match r.dynamic_anchors.get(&pending.anchor) {
679
+ Some(p) => p.clone(),
680
+ None => continue,
681
+ }
682
+ };
683
+ let t = self.target_in(resource as u32, &pointer);
684
+ let before = self.nodes.len();
685
+ let node_id = self.get_node(&t);
686
+ added |= self.nodes.len() != before;
687
+ self.pending_dynamic_refs[p].candidates.push((resource as u32, node_id));
688
+ }
689
+ }
690
+ added
691
+ }
692
+
693
+ fn target_in(&self, resource: u32, pointer: &str) -> SchemaTarget {
694
+ let r = &self.loader.resources[resource as usize];
695
+ let fragment = if pointer == r.root_pointer { "" } else { &pointer[r.root_pointer.len()..] };
696
+ self.loader.try_resolve_fragment(resource, fragment).unwrap_or_else(|| self.loader.root_target(resource))
697
+ }
698
+
699
+ fn finalize_dynamic_refs(&mut self) -> Result<(), SchemaCompilationError> {
700
+ let mut reachable: Option<Vec<bool>> = None;
701
+ let pending = std::mem::take(&mut self.pending_dynamic_refs);
702
+ for p in &pending {
703
+ let fallback = self.get_node(&p.initial_target);
704
+ let keyword =
705
+ if p.is_recursive { StaticDynamicKeyword::RecursiveRef } else { StaticDynamicKeyword::DynamicRef };
706
+ let set_static = |this: &mut Self, t: NodeId| {
707
+ let n = &mut this.nodes[p.node_id as usize];
708
+ n.static_dynamic_ref = Some(t);
709
+ n.static_dynamic_keyword = keyword;
710
+ };
711
+ if p.candidates.len() <= 1 {
712
+ // Only the initial target's resource defines the anchor: resolution is static.
713
+ set_static(self, fallback);
714
+ continue;
715
+ }
716
+ // The dynamic scope is searched outermost-first and its outermost entry is always the resource
717
+ // evaluation started in. When the entry resource defines the anchor, that target is the answer on every
718
+ // path.
719
+ if reachable.is_none() {
720
+ reachable = Some(self.compute_reachability(&pending));
721
+ }
722
+ if !reachable.as_ref().unwrap()[p.node_id as usize] {
723
+ set_static(self, fallback);
724
+ continue;
725
+ }
726
+ let entry_resource = self.nodes[self.entry_node as usize].resource_id;
727
+ if let Some(&(_, uniform)) = p.candidates.iter().find(|(r, _)| *r == entry_resource) {
728
+ set_static(self, uniform);
729
+ continue;
730
+ }
731
+ self.nodes[p.node_id as usize].dynamic_ref = Some(Box::new(DynamicRefTarget {
732
+ is_recursive: p.is_recursive,
733
+ fallback,
734
+ by_resource: p.candidates.clone(),
735
+ }));
736
+ }
737
+ while self.worklist_head < self.nodes.len() {
738
+ let id = self.worklist_head;
739
+ self.worklist_head += 1;
740
+ let target = self.targets[id].clone();
741
+ self.compile_node(id as NodeId, &target)?;
742
+ }
743
+ Ok(())
744
+ }
745
+
746
+ /// Nodes reachable from the entry, counting every candidate of a pending dynamic reference as a child.
747
+ fn compute_reachability(&mut self, pending: &[PendingDynamicRef]) -> Vec<bool> {
748
+ let mut extra: HashMap<NodeId, Vec<NodeId>> = HashMap::new();
749
+ for p in pending {
750
+ let init = self.get_node(&p.initial_target);
751
+ let list = extra.entry(p.node_id).or_default();
752
+ list.push(init);
753
+ list.extend(p.candidates.iter().map(|&(_, n)| n));
754
+ }
755
+ let mut reached = vec![false; self.nodes.len()];
756
+ let mut stack = vec![self.entry_node];
757
+ reached[self.entry_node as usize] = true;
758
+ while let Some(id) = stack.pop() {
759
+ let mut children = self.nodes[id as usize].children();
760
+ if let Some(e) = extra.get(&id) {
761
+ children.extend_from_slice(e);
762
+ }
763
+ for c in children {
764
+ if (c as usize) < reached.len() && !reached[c as usize] {
765
+ reached[c as usize] = true;
766
+ stack.push(c);
767
+ }
768
+ }
769
+ }
770
+ reached
771
+ }
772
+
773
+ // ------------------------------------------------------------------------------------------------------------
774
+ // Analyses
775
+
776
+ fn analyse(&mut self) {
777
+ let count = self.nodes.len();
778
+ let mut edges: Vec<Vec<NodeId>> = Vec::with_capacity(count);
779
+ let mut in_place = false;
780
+ let mut branches = false;
781
+ for n in &self.nodes {
782
+ let e = n.in_place_children(true);
783
+ in_place |= !e.is_empty();
784
+ branches |= n.one_of.is_some() || n.any_of.is_some();
785
+ edges.push(e);
786
+ }
787
+ self.compute_marking(if in_place { Some(&edges) } else { None });
788
+ if in_place {
789
+ self.compute_in_place_cycles(&edges);
790
+ }
791
+ if branches {
792
+ self.compute_discriminators();
793
+ }
794
+ }
795
+
796
+ /// Which nodes can contribute evaluated-property/item annotations: a node marks if it has the keywords itself or
797
+ /// any in-place child (not counting `not`) marks.
798
+ fn compute_marking(&mut self, edges: Option<&Vec<Vec<NodeId>>>) {
799
+ for n in &mut self.nodes {
800
+ n.marks_properties = n.properties.is_some()
801
+ || n.pattern_properties.is_some()
802
+ || n.additional_properties.is_some()
803
+ || n.unevaluated_properties.is_some();
804
+ n.marks_items = n.prefix_items.is_some()
805
+ || n.items.is_some()
806
+ || (n.contains.is_some() && n.contains_marks_evaluated)
807
+ || n.unevaluated_items.is_some();
808
+ }
809
+ let Some(edges) = edges else {
810
+ return;
811
+ };
812
+ let count = self.nodes.len();
813
+ let mut parents: Vec<Vec<NodeId>> = vec![Vec::new(); count];
814
+ for (i, edge) in edges.iter().enumerate() {
815
+ // `not` does not contribute annotations: nodes with one use the edges without it.
816
+ let children =
817
+ if self.nodes[i].not.is_some() { self.nodes[i].in_place_children(false) } else { edge.clone() };
818
+ for c in children {
819
+ parents[c as usize].push(i as NodeId);
820
+ }
821
+ }
822
+ for which in 0..2 {
823
+ let marks = |n: &SchemaNode| if which == 0 { n.marks_properties } else { n.marks_items };
824
+ let mut work: Vec<NodeId> = (0..count).filter(|&i| marks(&self.nodes[i])).map(|i| i as NodeId).collect();
825
+ while let Some(w) = work.pop() {
826
+ for &p in &parents[w as usize] {
827
+ let n = &mut self.nodes[p as usize];
828
+ let flag = if which == 0 { &mut n.marks_properties } else { &mut n.marks_items };
829
+ if !*flag {
830
+ *flag = true;
831
+ work.push(p);
832
+ }
833
+ }
834
+ }
835
+ }
836
+ }
837
+
838
+ /// Marks nodes on a cycle of in-place applicators (iterative Tarjan), the only ones that need a depth guard.
839
+ fn compute_in_place_cycles(&mut self, edges: &[Vec<NodeId>]) {
840
+ let count = self.nodes.len();
841
+ let mut index = vec![-1i32; count];
842
+ let mut low = vec![0i32; count];
843
+ let mut on_stack = vec![false; count];
844
+ let mut stack: Vec<usize> = Vec::new();
845
+ let mut next = 0i32;
846
+ for start in 0..count {
847
+ if index[start] >= 0 {
848
+ continue;
849
+ }
850
+ let mut work: Vec<(usize, usize)> = vec![(start, 0)];
851
+ index[start] = next;
852
+ low[start] = next;
853
+ next += 1;
854
+ stack.push(start);
855
+ on_stack[start] = true;
856
+ while let Some(&mut (v, ref mut ei)) = work.last_mut() {
857
+ if *ei < edges[v].len() {
858
+ let w = edges[v][*ei] as usize;
859
+ *ei += 1;
860
+ if index[w] < 0 {
861
+ index[w] = next;
862
+ low[w] = next;
863
+ next += 1;
864
+ stack.push(w);
865
+ on_stack[w] = true;
866
+ work.push((w, 0));
867
+ } else if on_stack[w] {
868
+ low[v] = low[v].min(index[w]);
869
+ }
870
+ } else {
871
+ work.pop();
872
+ if let Some(&(parent, _)) = work.last() {
873
+ low[parent] = low[parent].min(low[v]);
874
+ }
875
+ if low[v] == index[v] {
876
+ let mut component = Vec::new();
877
+ loop {
878
+ let w = stack.pop().unwrap();
879
+ on_stack[w] = false;
880
+ component.push(w);
881
+ if w == v {
882
+ break;
883
+ }
884
+ }
885
+ if component.len() > 1 || edges[v].contains(&(v as NodeId)) {
886
+ for c in component {
887
+ self.nodes[c].in_place_cycle = true;
888
+ }
889
+ }
890
+ }
891
+ }
892
+ }
893
+ }
894
+ }
895
+
896
+ /// Follows pure `$ref` nodes to the node that carries constraints.
897
+ fn effective_node(&self, id: NodeId) -> &SchemaNode {
898
+ let mut node = &self.nodes[id as usize];
899
+ let mut hops = 0;
900
+ while hops < 16 && node.is_pure_ref() {
901
+ node = &self.nodes[node.ref_.unwrap() as usize];
902
+ hops += 1;
903
+ }
904
+ node
905
+ }
906
+
907
+ fn compute_discriminators(&mut self) {
908
+ 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
+ }
913
+ }
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
+ }
918
+ }
919
+ }
920
+ }
921
+
922
+ /// Classifies branches by the constraint their `properties[X]` places on the value (positive: const/enum of
923
+ /// primitives; negative: string not in an enum; wildcard: anything else), and builds the value -> branches table.
924
+ fn build_discriminator(&self, branches: &[NodeId]) -> Option<Discriminator> {
925
+ let mut candidates: Vec<String> = Vec::new();
926
+ for &b in branches {
927
+ let eff = self.effective_node(b);
928
+ let Some(props) = &eff.properties else {
929
+ continue;
930
+ };
931
+ for (name, _) in props {
932
+ if !matches!(self.classify(eff, name), Class::Wildcard) {
933
+ candidates.push(name.clone());
934
+ }
935
+ }
936
+ break;
937
+ }
938
+ for name in candidates {
939
+ let classes: Vec<Class> = branches.iter().map(|&b| self.classify(self.effective_node(b), &name)).collect();
940
+ if classes.iter().filter(|c| !matches!(c, Class::Wildcard)).count() < 2 {
941
+ continue;
942
+ }
943
+ let mut values: Vec<DiscriminatorValue> = Vec::new();
944
+ for c in &classes {
945
+ for v in c.set() {
946
+ if !values.iter().any(|x| x.same(v)) {
947
+ values.push(v.clone());
948
+ }
949
+ }
950
+ }
951
+ let known = values
952
+ .into_iter()
953
+ .map(|value| {
954
+ let selected: Vec<u32> = classes
955
+ .iter()
956
+ .enumerate()
957
+ .filter(|(_, c)| {
958
+ let contains = c.set().iter().any(|x| x.same(&value));
959
+ match c {
960
+ Class::Positive(_) => contains,
961
+ Class::Negative(_) => !contains,
962
+ Class::Wildcard => true,
963
+ }
964
+ })
965
+ .map(|(i, _)| i as u32)
966
+ .collect();
967
+ (value, selected)
968
+ })
969
+ .collect();
970
+ let unknown = classes
971
+ .iter()
972
+ .enumerate()
973
+ .filter(|(_, c)| !matches!(c, Class::Positive(_)))
974
+ .map(|(i, _)| i as u32)
975
+ .collect();
976
+ let all_require =
977
+ branches.iter().all(|&b| self.effective_node(b).required.as_ref().is_some_and(|r| r.contains(&name)));
978
+ return Some(Discriminator { property: name, known, unknown, all_require });
979
+ }
980
+ None
981
+ }
982
+
983
+ fn classify(&self, branch: &SchemaNode, name: &str) -> Class {
984
+ let Some(child) = branch.properties.as_ref().and_then(|p| p.iter().find(|(n, _)| n == name).map(|&(_, c)| c))
985
+ else {
986
+ return Class::Wildcard;
987
+ };
988
+ 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
+ }
993
+ }
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
+ }
1000
+ }
1001
+ }
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
+ }
1015
+ }
1016
+ }
1017
+ Class::Wildcard
1018
+ }
1019
+ }
1020
+
1021
+ enum Class {
1022
+ Positive(Vec<DiscriminatorValue>),
1023
+ Negative(Vec<DiscriminatorValue>),
1024
+ Wildcard,
1025
+ }
1026
+
1027
+ impl Class {
1028
+ fn set(&self) -> &[DiscriminatorValue] {
1029
+ match self {
1030
+ Class::Positive(s) | Class::Negative(s) => s,
1031
+ Class::Wildcard => &[],
1032
+ }
1033
+ }
1034
+ }
1035
+
1036
+ fn primitive(v: &Value) -> Option<DiscriminatorValue> {
1037
+ match v {
1038
+ Value::String(s) => Some(DiscriminatorValue::String(s.clone())),
1039
+ Value::Number(n) => Some(DiscriminatorValue::Number(n.clone())),
1040
+ Value::Bool(b) => Some(DiscriminatorValue::Bool(*b)),
1041
+ Value::Null => Some(DiscriminatorValue::Null),
1042
+ _ => None,
1043
+ }
1044
+ }
1045
+
1046
+ /// The annotation keywords of a schema object, in the order `SchemaCompiler.CompileNode` records them.
1047
+ pub(crate) fn collect_annotations(
1048
+ e: &Map<String, Value>,
1049
+ dialect: Dialect,
1050
+ voc: u32,
1051
+ content: bool,
1052
+ assert_format_set: bool,
1053
+ ) -> Option<Vec<AnnotationEntry>> {
1054
+ let legacy = dialect.is_legacy();
1055
+ let meta_data = legacy || voc & vocab::META_DATA != 0;
1056
+ let format_annotate =
1057
+ legacy || voc & (vocab::FORMAT_ANNOTATION | vocab::FORMAT_ASSERTION) != 0 || assert_format_set;
1058
+ let mut out = Vec::new();
1059
+ let mut add = |keyword: &str, strings_only: bool| {
1060
+ out.push(AnnotationEntry { keyword: keyword.to_string(), value: e[keyword].clone(), strings_only });
1061
+ };
1062
+ for name in e.keys() {
1063
+ match name.as_str() {
1064
+ "title" | "description" | "default" => {
1065
+ if meta_data {
1066
+ add(name, false)
1067
+ }
1068
+ }
1069
+ "examples" => {
1070
+ if meta_data && dialect >= Dialect::Draft6 {
1071
+ add(name, false)
1072
+ }
1073
+ }
1074
+ "readOnly" | "writeOnly" => {
1075
+ if meta_data && dialect >= Dialect::Draft7 {
1076
+ add(name, false)
1077
+ }
1078
+ }
1079
+ "deprecated" => {
1080
+ if meta_data && dialect >= Dialect::Draft201909 {
1081
+ add(name, false)
1082
+ }
1083
+ }
1084
+ "format" => {
1085
+ if e["format"].is_string() && format_annotate {
1086
+ add(name, false)
1087
+ }
1088
+ }
1089
+ _ => {
1090
+ // Unknown keywords are collected as annotations from 2019-09 onwards.
1091
+ if dialect >= Dialect::Draft201909 && !KNOWN_KEYWORDS.contains(&name.as_str()) {
1092
+ add(name, false)
1093
+ }
1094
+ }
1095
+ }
1096
+ }
1097
+ if content
1098
+ && dialect >= Dialect::Draft7
1099
+ && (e.contains_key("contentEncoding") || e.contains_key("contentMediaType") || e.contains_key("contentSchema"))
1100
+ {
1101
+ if e.contains_key("contentEncoding") {
1102
+ add("contentEncoding", true);
1103
+ }
1104
+ if e.contains_key("contentMediaType") {
1105
+ add("contentMediaType", true);
1106
+ // contentSchema is only meaningful alongside contentMediaType.
1107
+ if e.contains_key("contentSchema") && dialect >= Dialect::Draft201909 {
1108
+ add("contentSchema", true);
1109
+ }
1110
+ }
1111
+ }
1112
+ (!out.is_empty()).then_some(out)
1113
+ }
1114
+
1115
+ #[allow(dead_code)]
1116
+ pub(crate) fn value_addr(v: &Value) -> usize {
1117
+ addr(v)
1118
+ }