From 3898c43742ff74f75a063bc65636be848929f1c3 Mon Sep 17 00:00:00 2001 From: Alex Groleau Date: Tue, 3 Mar 2026 00:13:37 -0500 Subject: [PATCH] validator refactor progress --- Cargo.lock | 1 + Cargo.toml | 3 +- GEMINI.md | 25 +- src/database/enum.rs | 12 + src/{validator => database}/formats.rs | 0 src/database/mod.rs | 220 +++ src/database/page.rs | 35 + src/database/punc.rs | 20 + src/database/relation.rs | 15 + src/{validator => database}/schema.rs | 174 +- src/database/type.rs | 35 + src/jspg.rs | 29 + src/lib.rs | 105 +- src/merger/mod.rs | 9 + src/queryer/mod.rs | 9 + src/tests/fixtures.rs | 258 +-- src/validator/compiler.rs | 394 ----- src/validator/context.rs | 98 +- src/validator/mod.rs | 222 +-- src/validator/registry.rs | 50 - src/validator/rules.rs | 1008 ------------ src/validator/rules/array.rs | 119 ++ src/validator/rules/combinators.rs | 83 + src/validator/rules/conditionals.rs | 69 + src/validator/rules/core.rs | 84 + src/validator/rules/format.rs | 44 + src/validator/rules/mod.rs | 93 ++ src/validator/rules/numeric.rs | 61 + src/validator/rules/object.rs | 183 +++ src/validator/rules/polymorphism.rs | 64 + src/validator/rules/string.rs | 53 + src/validator/util.rs | 209 +-- test_err.log | 62 + tests/fixtures.rs | 258 +-- tests/fixtures/additionalProperties.json | 80 +- tests/fixtures/allOf.json | 414 +++-- tests/fixtures/anchor.json | 120 -- tests/fixtures/anyOf.json | 204 ++- ...boolean_schema.json => booleanSchema.json} | 12 +- tests/fixtures/const.json | 218 ++- tests/fixtures/contains.json | 132 +- tests/fixtures/content.json | 68 +- tests/fixtures/dependencies.json | 576 +++++++ tests/fixtures/dependentRequired.json | 220 --- tests/fixtures/dependentSchemas.json | 303 ---- tests/fixtures/dynamicRef.json | 1111 ------------- tests/fixtures/emptyString.json | 72 +- tests/fixtures/enum.json | 244 +-- tests/fixtures/exclusiveMaximum.json | 12 +- tests/fixtures/exclusiveMinimum.json | 12 +- tests/fixtures/families.json | 202 +++ tests/fixtures/format.json | 240 ++- tests/fixtures/if-then-else.json | 330 ++-- tests/fixtures/items.json | 288 ++-- tests/fixtures/masking.json | 114 +- tests/fixtures/maxContains.json | 86 +- tests/fixtures/maxItems.json | 36 +- tests/fixtures/maxLength.json | 22 +- tests/fixtures/maxProperties.json | 48 +- tests/fixtures/maximum.json | 24 +- tests/fixtures/merge.json | 150 +- tests/fixtures/minContains.json | 162 +- tests/fixtures/minItems.json | 36 +- tests/fixtures/minLength.json | 22 +- tests/fixtures/minProperties.json | 36 +- tests/fixtures/minimum.json | 22 +- tests/fixtures/multipleOf.json | 42 +- tests/fixtures/not.json | 196 ++- tests/fixtures/oneOf.json | 330 ++-- tests/fixtures/pattern.json | 22 +- tests/fixtures/patternProperties.json | 114 +- tests/fixtures/prefixItems.json | 76 +- tests/fixtures/properties.json | 294 ++-- tests/fixtures/propertyNames.json | 110 +- tests/fixtures/puncs.json | 1414 ----------------- tests/fixtures/ref.json | 1186 ++++++++------ tests/fixtures/required.json | 112 +- tests/fixtures/type.json | 146 +- tests/fixtures/typedRefs.json | 343 ++++ tests/fixtures/uniqueItems.json | 132 +- tests/lib.rs | 28 +- 81 files changed, 6331 insertions(+), 7934 deletions(-) create mode 100644 src/database/enum.rs rename src/{validator => database}/formats.rs (100%) create mode 100644 src/database/mod.rs create mode 100644 src/database/page.rs create mode 100644 src/database/punc.rs create mode 100644 src/database/relation.rs rename src/{validator => database}/schema.rs (56%) create mode 100644 src/database/type.rs create mode 100644 src/jspg.rs create mode 100644 src/merger/mod.rs create mode 100644 src/queryer/mod.rs delete mode 100644 src/validator/compiler.rs delete mode 100644 src/validator/registry.rs delete mode 100644 src/validator/rules.rs create mode 100644 src/validator/rules/array.rs create mode 100644 src/validator/rules/combinators.rs create mode 100644 src/validator/rules/conditionals.rs create mode 100644 src/validator/rules/core.rs create mode 100644 src/validator/rules/format.rs create mode 100644 src/validator/rules/mod.rs create mode 100644 src/validator/rules/numeric.rs create mode 100644 src/validator/rules/object.rs create mode 100644 src/validator/rules/polymorphism.rs create mode 100644 src/validator/rules/string.rs create mode 100644 test_err.log delete mode 100644 tests/fixtures/anchor.json rename tests/fixtures/{boolean_schema.json => booleanSchema.json} (93%) create mode 100644 tests/fixtures/dependencies.json delete mode 100644 tests/fixtures/dependentRequired.json delete mode 100644 tests/fixtures/dependentSchemas.json delete mode 100644 tests/fixtures/dynamicRef.json create mode 100644 tests/fixtures/families.json delete mode 100644 tests/fixtures/puncs.json create mode 100644 tests/fixtures/typedRefs.json diff --git a/Cargo.lock b/Cargo.lock index a92dda4..eff7952 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -817,6 +817,7 @@ dependencies = [ "chrono", "fluent-uri", "idna", + "indexmap", "json-pointer", "lazy_static", "once_cell", diff --git a/Cargo.toml b/Cargo.toml index efcacff..ee2eb26 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ percent-encoding = "2.3.2" uuid = { version = "1.20.0", features = ["v4", "serde"] } chrono = { version = "0.4.43", features = ["serde"] } json-pointer = "0.3.4" +indexmap = { version = "2.13.0", features = ["serde"] } [dev-dependencies] pgrx-tests = "0.16.1" @@ -51,4 +52,4 @@ lto = "fat" codegen-units = 1 [package.metadata.jspg] -target_draft = "draft2020-12" \ No newline at end of file +target_draft = "draft2020-12" diff --git a/GEMINI.md b/GEMINI.md index d2fdef2..15cdf04 100644 --- a/GEMINI.md +++ b/GEMINI.md @@ -69,19 +69,26 @@ Returns a debug dump of the currently cached schemas (for development/debugging) JSPG implements specific extensions to the Draft 2020-12 standard to support the Punc architecture's object-oriented needs. -### 1. Implicit Keyword Shadowing -Standard JSON Schema composition (`allOf`) is additive (Intersection), meaning constraints can only be tightened, not replaced. However, JSPG treats `$ref` differently when it appears alongside other properties to support object-oriented inheritance. +### 1. The Unified Semantic Graph & Native Inheritance +JSPG goes beyond Draft 2020-12 to natively understand Object-Oriented inheritance and polymorphism. During the `cache_json_schemas()` phase, JSPG builds a single Directed Acyclic Graph (DAG) using **only** the `$ref` keyword. Every schema that uses `$ref` establishes a parent-to-child relationship. -* **Inheritance (`$ref` + `properties`)**: When a schema uses `$ref` *and* defines its own properties, JSPG implements **Smart Merge** (or Shadowing). If a property is defined in the current schema, its constraints take precedence over the inherited constraints for that specific keyword. - * *Example*: If `Entity` defines `type: { const: "entity" }` and `Person` (which refs Entity) defines `type: { const: "person" }`, validation passes for "person". The local `const` shadows the inherited `const`. - * *Granularity*: Shadowing is per-keyword. If `Entity` defined `type: { const: "entity", minLength: 5 }`, `Person` would shadow `const` but still inherit `minLength: 5`. +Furthermore, `jspg` knows which schemas belong directly to database tables (Entities) versus which are ad-hoc API shapes. +* **Native `type` Discrimination**: For any schema that traces its ancestry back to the base `entity`, JSPG securely and implicitly manages the `type` property. You do **not** need to explicitly override `"type": {"const": "person"}` in entity subclasses. If a schema `$ref`s `organization`, JSPG automatically allows the incoming `type` to be anything in the `organization` family tree (e.g., `person`, `bot`), but rigidly truncates/masks the data structure to the requested `organization` shape. +* **Ad-Hoc Objects**: If an ad-hoc schema `$ref`s a base object but does not trace back to `entity`, standard JSON Schema rules apply (no magical `type` tracking). -* **Composition (`allOf`)**: When using `allOf`, standard intersection rules apply. No shadowing occurs; all constraints from all branches must pass. This is used for mixins or interfaces. +> [!NOTE] +> **`$ref` never creates a Union.** When you use `$ref`, you are asking for a single, concrete struct/shape. The schema's strict fields will be rigidly enforced, but the `type` property is permitted to match any valid descendant via the native discrimination. -### 2. Virtual Family References (`$family`) -To support polymorphic fields (e.g., a field that accepts any "User" type), JSPG generates virtual schemas representing type hierarchies. +### 2. Shape Polymorphism & Virtual Unions (`$family`) +To support polymorphic API contracts and deeply nested UI Unions without manually writing massive `oneOf` blocks, JSPG provides the `$family` macro. While `$ref` guarantees a single shape, `$family` asks the code generators for a true Polymorphic Union class. -* **Mechanism**: When caching types, if a type defines a `hierarchy` (e.g., `["entity", "organization", "person"]`), JSPG generates a virtual `oneOf` family containing refs to all valid descendants. These can be pointed to exclusively by using `{"$family": "organization"}`. Because `$family` is a macro-pointer that swaps in the virtual union, it **must** be used exclusively in its schema object; you cannot define other properties alongside it. +When `{"$family": "organization.light"}` is encountered, JSPG: +1. Locates the base `organization` node in the Semantic Graph. +2. Recursively walks down to find all descendants via `$ref`. +3. **Strictly Filters** the descendants using the exact dot-notation suffix requested. It will only include descendants whose `$id` matches the shape modifier (e.g., `person.light`, `user.light`). If `bot` has no `.light` shape defined, it is securely omitted from the union. +4. Generates a virtual `oneOf` array containing those precise `$ref`s. + +This cleanly separates **Database Ancestry** (managed entirely and implicitly by `$ref` for single shapes) from **Shape Variations** (managed explicitly by `$family` to build `oneOf` unions). ### 3. Strict by Default & Extensibility JSPG enforces a "Secure by Default" philosophy. All schemas are treated as if `unevaluatedProperties: false` (and `unevaluatedItems: false`) is set, unless explicitly overridden. diff --git a/src/database/enum.rs b/src/database/enum.rs new file mode 100644 index 0000000..72d9c4d --- /dev/null +++ b/src/database/enum.rs @@ -0,0 +1,12 @@ +use crate::database::schema::Schema; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, Default)] +#[serde(default)] +pub struct Enum { + pub name: String, + pub module: String, + pub source: String, + pub values: Vec, + pub schemas: Vec, +} diff --git a/src/validator/formats.rs b/src/database/formats.rs similarity index 100% rename from src/validator/formats.rs rename to src/database/formats.rs diff --git a/src/database/mod.rs b/src/database/mod.rs new file mode 100644 index 0000000..8815e6a --- /dev/null +++ b/src/database/mod.rs @@ -0,0 +1,220 @@ +pub mod r#enum; +pub mod formats; +pub mod page; +pub mod punc; +pub mod schema; +pub mod r#type; + +use crate::database::r#enum::Enum; +use crate::database::punc::Punc; +use crate::database::schema::Schema; +use crate::database::r#type::Type; +use std::collections::HashMap; + +pub struct Database { + pub enums: HashMap, + pub types: HashMap, + pub puncs: HashMap, + pub schemas: HashMap, + pub descendants: HashMap>, +} + +impl Database { + pub fn new(val: &serde_json::Value) -> Self { + let mut db = Self { + enums: HashMap::new(), + types: HashMap::new(), + puncs: HashMap::new(), + schemas: HashMap::new(), + descendants: HashMap::new(), + }; + + if let Some(arr) = val.get("enums").and_then(|v| v.as_array()) { + for item in arr { + if let Ok(def) = serde_json::from_value::(item.clone()) { + db.enums.insert(def.name.clone(), def); + } + } + } + + if let Some(arr) = val.get("types").and_then(|v| v.as_array()) { + for item in arr { + if let Ok(def) = serde_json::from_value::(item.clone()) { + db.types.insert(def.name.clone(), def); + } + } + } + + if let Some(arr) = val.get("puncs").and_then(|v| v.as_array()) { + for item in arr { + if let Ok(def) = serde_json::from_value::(item.clone()) { + db.puncs.insert(def.name.clone(), def); + } + } + } + + if let Some(arr) = val.get("schemas").and_then(|v| v.as_array()) { + for (i, item) in arr.iter().enumerate() { + if let Ok(mut schema) = serde_json::from_value::(item.clone()) { + let id = schema + .obj + .id + .clone() + .unwrap_or_else(|| format!("schema_{}", i)); + schema.obj.id = Some(id.clone()); + db.schemas.insert(id, schema); + } + } + } + + let _ = db.compile(); + db + } + + /// Organizes the graph of the database, compiling regex, format functions, and pointing schema references. + fn compile(&mut self) -> Result<(), String> { + self.collect_schemas(); + + // 1. Compile regex and formats sequentially + for schema in self.schemas.values_mut() { + schema.compile(); + } + + // 2. Compute the Unified Semantic Graph (descendants) + self.collect_descendents(); + + // 3. For any schema representing a Postgres table, cache its allowed subclasses + self.compile_allowed_types(); + + // 4. Finally, securely link all string $refs into memory pointers (Arc) + self.compile_pointers(); + + Ok(()) + } + + fn collect_schemas(&mut self) { + let mut to_insert = Vec::new(); + for (_, type_def) in &self.types { + for schema in &type_def.schemas { + if let Some(id) = &schema.obj.id { + to_insert.push((id.clone(), schema.clone())); + } + } + } + for (_, punc_def) in &self.puncs { + for schema in &punc_def.schemas { + if let Some(id) = &schema.obj.id { + to_insert.push((id.clone(), schema.clone())); + } + } + } + for (_, enum_def) in &self.enums { + for schema in &enum_def.schemas { + if let Some(id) = &schema.obj.id { + to_insert.push((id.clone(), schema.clone())); + } + } + } + for (id, schema) in to_insert { + self.schemas.insert(id, schema); + } + } + + fn collect_descendents(&mut self) { + let mut direct_children: HashMap> = HashMap::new(); + + // First pass: Find all schemas that have a $ref to another schema + let schema_ids: Vec = self.schemas.keys().cloned().collect(); + for id in schema_ids { + if let Some(ref_str) = self.schemas.get(&id).and_then(|s| s.obj.ref_string.clone()) { + if self.schemas.contains_key(&ref_str) { + direct_children.entry(ref_str).or_default().push(id.clone()); + } + } + } + + // Now compute descendants for all schemas + let mut descendants_map: HashMap> = HashMap::new(); + for key in self.schemas.keys() { + let mut descendants = Vec::new(); + let mut queue = Vec::new(); + if let Some(children) = direct_children.get(key) { + queue.extend(children.iter().cloned()); + } + + let mut visited = std::collections::HashSet::new(); + while let Some(child) = queue.pop() { + if visited.insert(child.clone()) { + descendants.push(child.clone()); + if let Some(grandchildren) = direct_children.get(&child) { + queue.extend(grandchildren.iter().cloned()); + } + } + } + descendants_map.insert(key.clone(), descendants); + } + self.descendants = descendants_map; + } + + fn compile_allowed_types(&mut self) { + // 1. Identify which types act as bases (table-backed schemas) + let mut entity_bases = HashMap::new(); + for type_def in self.types.values() { + for type_schema in &type_def.schemas { + if let Some(id) = &type_schema.obj.id { + entity_bases.insert(id.clone(), type_def.name.clone()); + } + } + } + + // 2. Compute compiled_allowed_types for all descendants of entity bases + let mut allowed_types_map: HashMap> = HashMap::new(); + for base_id in entity_bases.keys() { + allowed_types_map.insert( + base_id.clone(), + self + .descendants + .get(base_id) + .unwrap_or(&vec![]) + .iter() + .cloned() + .collect(), + ); + if let Some(descendants) = self.descendants.get(base_id) { + let set: std::collections::HashSet = descendants.iter().cloned().collect(); + for desc_id in descendants { + allowed_types_map.insert(desc_id.clone(), set.clone()); + } + } + } + + // 3. Inject types into the schemas + let schema_ids: Vec = self.schemas.keys().cloned().collect(); + for id in schema_ids { + if let Some(set) = allowed_types_map.get(&id) { + if let Some(schema) = self.schemas.get_mut(&id) { + schema.obj.compiled_allowed_types = Some(set.clone()); + } + } + } + } + + fn compile_pointers(&mut self) { + let schema_ids: Vec = self.schemas.keys().cloned().collect(); + for id in schema_ids { + let mut compiled_ref = None; + + if let Some(schema) = self.schemas.get(&id) { + if let Some(ref_str) = &schema.obj.ref_string { + if let Some(target) = self.schemas.get(ref_str) { + compiled_ref = Some(std::sync::Arc::new(target.clone())); + } + } + } + + if let Some(schema) = self.schemas.get_mut(&id) { + schema.obj.compiled_ref = compiled_ref; + } + } + } +} diff --git a/src/database/page.rs b/src/database/page.rs new file mode 100644 index 0000000..c3efcd8 --- /dev/null +++ b/src/database/page.rs @@ -0,0 +1,35 @@ +use indexmap::IndexMap; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, Default)] +#[serde(default)] +pub struct Page { + #[serde(skip_serializing_if = "Option::is_none")] + pub path: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub title: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub sidebar: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub actions: Option>, +} + +#[derive(Debug, Clone, Serialize, Deserialize, Default)] +#[serde(default)] +pub struct Sidebar { + #[serde(skip_serializing_if = "Option::is_none")] + pub category: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub priority: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize, Default)] +#[serde(default)] +pub struct Action { + #[serde(skip_serializing_if = "Option::is_none")] + pub punc: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub navigate: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub present: Option, +} diff --git a/src/database/punc.rs b/src/database/punc.rs new file mode 100644 index 0000000..6cea891 --- /dev/null +++ b/src/database/punc.rs @@ -0,0 +1,20 @@ +use crate::database::page::Page; +use crate::database::schema::Schema; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, Default)] +#[serde(default)] +pub struct Punc { + pub id: String, + pub r#type: String, + pub name: String, + pub module: String, + pub source: String, + pub description: Option, + pub public: bool, + pub form: bool, + pub get: Option, + pub page: Option, + #[serde(default)] + pub schemas: Vec, +} diff --git a/src/database/relation.rs b/src/database/relation.rs new file mode 100644 index 0000000..02c616b --- /dev/null +++ b/src/database/relation.rs @@ -0,0 +1,15 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, Default)] +#[serde(default)] +pub struct Relation { + pub id: String, + pub constraint_name: String, + pub source_type: String, + #[serde(default)] + pub source_columns: Vec, + pub destination_type: String, + #[serde(default)] + pub destination_columns: Vec, + pub prefix: Option, +} diff --git a/src/validator/schema.rs b/src/database/schema.rs similarity index 56% rename from src/validator/schema.rs rename to src/database/schema.rs index 56cae8b..109baae 100644 --- a/src/validator/schema.rs +++ b/src/database/schema.rs @@ -12,12 +12,6 @@ pub struct SchemaObject { pub id: Option, #[serde(rename = "$ref")] pub ref_string: Option, - #[serde(rename = "$anchor")] - pub anchor: Option, - #[serde(rename = "$dynamicAnchor")] - pub dynamic_anchor: Option, - #[serde(rename = "$dynamicRef")] - pub dynamic_ref: Option, /* Note: The `Ref` field in the Go struct is a pointer populated by the linker. In Rust, we might handle this differently (e.g., separate lookup or Rc/Arc), @@ -43,12 +37,6 @@ pub struct SchemaObject { // dependencies can be schema dependencies or property dependencies pub dependencies: Option>, - // Definitions (for $ref resolution) - #[serde(rename = "$defs")] - pub defs: Option>>, - #[serde(rename = "definitions")] - pub definitions: Option>>, - // Array Keywords #[serde(rename = "items")] pub items: Option>, @@ -83,10 +71,6 @@ pub struct SchemaObject { pub max_properties: Option, #[serde(rename = "propertyNames")] pub property_names: Option>, - #[serde(rename = "dependentRequired")] - pub dependent_required: Option>>, - #[serde(rename = "dependentSchemas")] - pub dependent_schemas: Option>>, // Numeric Validation pub format: Option, @@ -138,15 +122,42 @@ pub struct SchemaObject { // Compiled Fields (Hidden from JSON/Serde) #[serde(skip)] - pub compiled_format: Option, + pub compiled_ref: Option>, #[serde(skip)] - pub compiled_pattern: Option, + pub compiled_allowed_types: Option>, #[serde(skip)] - pub compiled_pattern_properties: Option)>>, + pub compiled_format: Option, #[serde(skip)] - pub compiled_registry: Option>, + pub compiled_pattern: Option, + #[serde(skip)] + pub compiled_pattern_properties: Option)>>, } +pub enum ResolvedRef<'a> { + Local(&'a Schema), + Global(&'a Schema, &'a Schema), +} + +/// Represents a compiled format validator +#[derive(Clone)] +pub enum CompiledFormat { + Func(fn(&serde_json::Value) -> Result<(), Box>), + Regex(regex::Regex), +} + +impl std::fmt::Debug for CompiledFormat { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + CompiledFormat::Func(_) => write!(f, "CompiledFormat::Func(...)"), + CompiledFormat::Regex(r) => write!(f, "CompiledFormat::Regex({:?})", r), + } + } +} + +/// A wrapper for compiled regex patterns +#[derive(Debug, Clone)] +pub struct CompiledRegex(pub regex::Regex); + #[derive(Debug, Clone, Serialize)] pub struct Schema { #[serde(flatten)] @@ -176,6 +187,129 @@ impl std::ops::DerefMut for Schema { } } +impl Schema { + pub fn resolve_ref(&self, _ref_string: &str) -> Option<&Arc> { + // This is vestigial for now. References are global pointers. We will remove this shortly. + None + } + + pub fn compile(&mut self) { + if let Some(format_str) = &self.obj.format { + if let Some(fmt) = crate::database::formats::FORMATS.get(format_str.as_str()) { + self.obj.compiled_format = Some(crate::database::schema::CompiledFormat::Func(fmt.func)); + } + } + + if let Some(pattern_str) = &self.obj.pattern { + if let Ok(re) = regex::Regex::new(pattern_str) { + self.obj.compiled_pattern = Some(crate::database::schema::CompiledRegex(re)); + } + } + + if let Some(pattern_props) = &self.obj.pattern_properties { + let mut compiled = Vec::new(); + for (k, v) in pattern_props { + if let Ok(re) = regex::Regex::new(k) { + compiled.push((crate::database::schema::CompiledRegex(re), v.clone())); + } + } + if !compiled.is_empty() { + self.obj.compiled_pattern_properties = Some(compiled); + } + } + + // Crawl children recursively to compile their internals + if let Some(props) = &mut self.obj.properties { + for (_, v) in props { + // Safe deep mutation workaround without unsafe Arc unwrap + let mut inner = (**v).clone(); + inner.compile(); + *v = Arc::new(inner); + } + } + + if let Some(arr) = &mut self.obj.prefix_items { + for v in arr.iter_mut() { + let mut inner = (**v).clone(); + inner.compile(); + *v = Arc::new(inner); + } + } + + if let Some(arr) = &mut self.obj.all_of { + for v in arr.iter_mut() { + let mut inner = (**v).clone(); + inner.compile(); + *v = Arc::new(inner); + } + } + + if let Some(arr) = &mut self.obj.any_of { + for v in arr.iter_mut() { + let mut inner = (**v).clone(); + inner.compile(); + *v = Arc::new(inner); + } + } + + if let Some(arr) = &mut self.obj.one_of { + for v in arr.iter_mut() { + let mut inner = (**v).clone(); + inner.compile(); + *v = Arc::new(inner); + } + } + + if let Some(v) = &mut self.obj.additional_properties { + let mut inner = (**v).clone(); + inner.compile(); + *v = Arc::new(inner); + } + + if let Some(v) = &mut self.obj.items { + let mut inner = (**v).clone(); + inner.compile(); + *v = Arc::new(inner); + } + + if let Some(v) = &mut self.obj.contains { + let mut inner = (**v).clone(); + inner.compile(); + *v = Arc::new(inner); + } + + if let Some(v) = &mut self.obj.property_names { + let mut inner = (**v).clone(); + inner.compile(); + *v = Arc::new(inner); + } + + if let Some(v) = &mut self.obj.not { + let mut inner = (**v).clone(); + inner.compile(); + *v = Arc::new(inner); + } + + if let Some(v) = &mut self.obj.if_ { + let mut inner = (**v).clone(); + inner.compile(); + *v = Arc::new(inner); + } + + if let Some(v) = &mut self.obj.then_ { + let mut inner = (**v).clone(); + inner.compile(); + *v = Arc::new(inner); + } + + if let Some(v) = &mut self.obj.else_ { + let mut inner = (**v).clone(); + inner.compile(); + *v = Arc::new(inner); + } + } +} + impl<'de> Deserialize<'de> for Schema { fn deserialize(deserializer: D) -> Result where diff --git a/src/database/type.rs b/src/database/type.rs new file mode 100644 index 0000000..f3d19ed --- /dev/null +++ b/src/database/type.rs @@ -0,0 +1,35 @@ +use crate::database::schema::Schema; +use serde::{Deserialize, Serialize}; +use serde_json::Value; + +#[derive(Debug, Clone, Serialize, Deserialize, Default)] +#[serde(default)] +pub struct Type { + pub id: String, + pub r#type: String, + pub name: String, + pub module: String, + pub source: String, + #[serde(default)] + pub historical: bool, + #[serde(default)] + pub sensitive: bool, + #[serde(default)] + pub ownable: bool, + pub longevity: Option, + #[serde(default)] + pub hierarchy: Vec, + pub relationship: Option, + #[serde(default)] + pub fields: Vec, + pub grouped_fields: Option, + #[serde(default)] + pub lookup_fields: Vec, + #[serde(default)] + pub null_fields: Vec, + #[serde(default)] + pub default_fields: Vec, + pub field_types: Option, + #[serde(default)] + pub schemas: Vec, +} diff --git a/src/jspg.rs b/src/jspg.rs new file mode 100644 index 0000000..e9b6056 --- /dev/null +++ b/src/jspg.rs @@ -0,0 +1,29 @@ +use crate::database::Database; +use crate::merger::Merger; +use crate::queryer::Queryer; +use crate::validator::Validator; +use std::sync::Arc; + +pub struct Jspg { + pub database: Arc, + pub validator: Validator, + pub queryer: Queryer, + pub merger: Merger, +} + +impl Jspg { + pub fn new(database_val: &serde_json::Value) -> Self { + let database_instance = Database::new(database_val); + let database = Arc::new(database_instance); + let validator = Validator::new(std::sync::Arc::new(database.schemas.clone())); + let queryer = Queryer::new(); + let merger = Merger::new(); + + Self { + database, + validator, + queryer, + merger, + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 0dc8978..f1d74be 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,7 +2,11 @@ use pgrx::*; pg_module_magic!(); +pub mod database; pub mod drop; +pub mod jspg; +pub mod merger; +pub mod queryer; pub mod validator; use serde_json::json; @@ -12,100 +16,38 @@ lazy_static::lazy_static! { // Global Atomic Swap Container: // - RwLock: To protect the SWAP of the Option. // - Option: Because it starts empty. - // - Arc: Because multiple running threads might hold the OLD validator while we swap. - // - Validator: It immutably owns the Registry. - static ref GLOBAL_VALIDATOR: RwLock>> = RwLock::new(None); + // - Arc: Because multiple running threads might hold the OLD engine while we swap. + // - Jspg: The root semantic engine encapsulating the database metadata, validator, queryer, and merger. + static ref GLOBAL_JSPG: RwLock>> = RwLock::new(None); } #[pg_extern(strict)] -pub fn cache_json_schemas(enums: JsonB, types: JsonB, puncs: JsonB) -> JsonB { - // 1 & 2. Build Registry, Families, and Wrap in Validator all in one shot - let new_validator = crate::validator::Validator::from_punc_definition( - Some(&enums.0), - Some(&types.0), - Some(&puncs.0), - ); - let new_arc = Arc::new(new_validator); +pub fn jspg_cache_database(database: JsonB) -> JsonB { + let new_jspg = crate::jspg::Jspg::new(&database.0); + let new_arc = Arc::new(new_jspg); // 3. ATOMIC SWAP { - let mut lock = GLOBAL_VALIDATOR.write().unwrap(); + let mut lock = GLOBAL_JSPG.write().unwrap(); *lock = Some(new_arc); } let drop = crate::drop::Drop::success(); JsonB(serde_json::to_value(drop).unwrap()) } - -#[pg_extern(strict, parallel_safe)] -pub fn mask_json_schema(schema_id: &str, instance: JsonB) -> JsonB { - // 1. Acquire Snapshot - let validator_arc = { - let lock = GLOBAL_VALIDATOR.read().unwrap(); - lock.clone() - }; - - // 2. Validate (Lock-Free) - if let Some(validator) = validator_arc { - // We need a mutable copy of the value to mask it - let mut mutable_instance = instance.0.clone(); - - match validator.mask(schema_id, &mut mutable_instance) { - Ok(result) => { - // If valid, return the MASKED instance - if result.is_valid() { - let drop = crate::drop::Drop::success_with_val(mutable_instance); - JsonB(serde_json::to_value(drop).unwrap()) - } else { - // If invalid, return errors (Schema Validation Errors) - let errors: Vec = result - .errors - .into_iter() - .map(|e| crate::drop::Error { - code: e.code, - message: e.message, - details: crate::drop::ErrorDetails { path: e.path }, - }) - .collect(); - let drop = crate::drop::Drop::with_errors(errors); - JsonB(serde_json::to_value(drop).unwrap()) - } - } - Err(e) => { - // Schema Not Found or other fatal error - let error = crate::drop::Error { - code: e.code, - message: e.message, - details: crate::drop::ErrorDetails { path: e.path }, - }; - let drop = crate::drop::Drop::with_errors(vec![error]); - JsonB(serde_json::to_value(drop).unwrap()) - } - } - } else { - let error = crate::drop::Error { - code: "VALIDATOR_NOT_INITIALIZED".to_string(), - message: "JSON Schemas have not been cached yet. Run cache_json_schemas()".to_string(), - details: crate::drop::ErrorDetails { - path: "".to_string(), - }, - }; - let drop = crate::drop::Drop::with_errors(vec![error]); - JsonB(serde_json::to_value(drop).unwrap()) - } -} +// `mask_json_schema` has been removed as the mask architecture is fully replaced by Spi string queries during DB interactions. #[pg_extern(strict, parallel_safe)] pub fn validate_json_schema(schema_id: &str, instance: JsonB) -> JsonB { // 1. Acquire Snapshot - let validator_arc = { - let lock = GLOBAL_VALIDATOR.read().unwrap(); + let jspg_arc = { + let lock = GLOBAL_JSPG.read().unwrap(); lock.clone() }; // 2. Validate (Lock-Free) - if let Some(validator) = validator_arc { - match validator.validate(schema_id, &instance.0) { + if let Some(engine) = jspg_arc { + match engine.validator.validate(schema_id, &instance.0) { Ok(result) => { if result.is_valid() { let drop = crate::drop::Drop::success(); @@ -137,7 +79,7 @@ pub fn validate_json_schema(schema_id: &str, instance: JsonB) -> JsonB { } else { let error = crate::drop::Error { code: "VALIDATOR_NOT_INITIALIZED".to_string(), - message: "JSON Schemas have not been cached yet. Run cache_json_schemas()".to_string(), + message: "The JSPG database has not been cached yet. Run jspg_cache_database()".to_string(), details: crate::drop::ErrorDetails { path: "".to_string(), }, @@ -149,8 +91,11 @@ pub fn validate_json_schema(schema_id: &str, instance: JsonB) -> JsonB { #[pg_extern(strict, parallel_safe)] pub fn json_schema_cached(schema_id: &str) -> bool { - if let Some(validator) = GLOBAL_VALIDATOR.read().unwrap().as_ref() { - match validator.validate(schema_id, &serde_json::Value::Null) { + if let Some(engine) = GLOBAL_JSPG.read().unwrap().as_ref() { + match engine + .validator + .validate(schema_id, &serde_json::Value::Null) + { Err(e) if e.code == "SCHEMA_NOT_FOUND" => false, _ => true, } @@ -161,7 +106,7 @@ pub fn json_schema_cached(schema_id: &str) -> bool { #[pg_extern(strict)] pub fn clear_json_schemas() -> JsonB { - let mut lock = GLOBAL_VALIDATOR.write().unwrap(); + let mut lock = GLOBAL_JSPG.write().unwrap(); *lock = None; let drop = crate::drop::Drop::success(); JsonB(serde_json::to_value(drop).unwrap()) @@ -169,8 +114,8 @@ pub fn clear_json_schemas() -> JsonB { #[pg_extern(strict, parallel_safe)] pub fn show_json_schemas() -> JsonB { - if let Some(validator) = GLOBAL_VALIDATOR.read().unwrap().as_ref() { - let mut keys = validator.get_schema_ids(); + if let Some(engine) = GLOBAL_JSPG.read().unwrap().as_ref() { + let mut keys = engine.validator.get_schema_ids(); keys.sort(); let drop = crate::drop::Drop::success_with_val(json!(keys)); JsonB(serde_json::to_value(drop).unwrap()) diff --git a/src/merger/mod.rs b/src/merger/mod.rs new file mode 100644 index 0000000..950b7e7 --- /dev/null +++ b/src/merger/mod.rs @@ -0,0 +1,9 @@ +pub struct Merger { + // To be implemented +} + +impl Merger { + pub fn new() -> Self { + Self {} + } +} diff --git a/src/queryer/mod.rs b/src/queryer/mod.rs new file mode 100644 index 0000000..b3b10ec --- /dev/null +++ b/src/queryer/mod.rs @@ -0,0 +1,9 @@ +pub struct Queryer { + // To be implemented +} + +impl Queryer { + pub fn new() -> Self { + Self {} + } +} diff --git a/src/tests/fixtures.rs b/src/tests/fixtures.rs index 93767f0..3174cd8 100644 --- a/src/tests/fixtures.rs +++ b/src/tests/fixtures.rs @@ -1,28 +1,4 @@ -#[pg_test] -fn test_anchor_0() { - let path = format!("{}/tests/fixtures/anchor.json", env!("CARGO_MANIFEST_DIR")); - crate::validator::util::run_test_file_at_index(&path, 0).unwrap(); -} - -#[pg_test] -fn test_anchor_1() { - let path = format!("{}/tests/fixtures/anchor.json", env!("CARGO_MANIFEST_DIR")); - crate::validator::util::run_test_file_at_index(&path, 1).unwrap(); -} - -#[pg_test] -fn test_anchor_2() { - let path = format!("{}/tests/fixtures/anchor.json", env!("CARGO_MANIFEST_DIR")); - crate::validator::util::run_test_file_at_index(&path, 2).unwrap(); -} - -#[pg_test] -fn test_anchor_3() { - let path = format!("{}/tests/fixtures/anchor.json", env!("CARGO_MANIFEST_DIR")); - crate::validator::util::run_test_file_at_index(&path, 3).unwrap(); -} - #[pg_test] fn test_content_0() { let path = format!("{}/tests/fixtures/content.json", env!("CARGO_MANIFEST_DIR")); @@ -107,54 +83,6 @@ fn test_min_items_2() { crate::validator::util::run_test_file_at_index(&path, 2).unwrap(); } -#[pg_test] -fn test_puncs_0() { - let path = format!("{}/tests/fixtures/puncs.json", env!("CARGO_MANIFEST_DIR")); - crate::validator::util::run_test_file_at_index(&path, 0).unwrap(); -} - -#[pg_test] -fn test_puncs_1() { - let path = format!("{}/tests/fixtures/puncs.json", env!("CARGO_MANIFEST_DIR")); - crate::validator::util::run_test_file_at_index(&path, 1).unwrap(); -} - -#[pg_test] -fn test_puncs_2() { - let path = format!("{}/tests/fixtures/puncs.json", env!("CARGO_MANIFEST_DIR")); - crate::validator::util::run_test_file_at_index(&path, 2).unwrap(); -} - -#[pg_test] -fn test_puncs_3() { - let path = format!("{}/tests/fixtures/puncs.json", env!("CARGO_MANIFEST_DIR")); - crate::validator::util::run_test_file_at_index(&path, 3).unwrap(); -} - -#[pg_test] -fn test_puncs_4() { - let path = format!("{}/tests/fixtures/puncs.json", env!("CARGO_MANIFEST_DIR")); - crate::validator::util::run_test_file_at_index(&path, 4).unwrap(); -} - -#[pg_test] -fn test_puncs_5() { - let path = format!("{}/tests/fixtures/puncs.json", env!("CARGO_MANIFEST_DIR")); - crate::validator::util::run_test_file_at_index(&path, 5).unwrap(); -} - -#[pg_test] -fn test_puncs_6() { - let path = format!("{}/tests/fixtures/puncs.json", env!("CARGO_MANIFEST_DIR")); - crate::validator::util::run_test_file_at_index(&path, 6).unwrap(); -} - -#[pg_test] -fn test_puncs_7() { - let path = format!("{}/tests/fixtures/puncs.json", env!("CARGO_MANIFEST_DIR")); - crate::validator::util::run_test_file_at_index(&path, 7).unwrap(); -} - #[pg_test] fn test_additional_properties_0() { let path = format!("{}/tests/fixtures/additionalProperties.json", env!("CARGO_MANIFEST_DIR")); @@ -347,6 +275,18 @@ fn test_any_of_9() { crate::validator::util::run_test_file_at_index(&path, 9).unwrap(); } +#[pg_test] +fn test_families_0() { + let path = format!("{}/tests/fixtures/families.json", env!("CARGO_MANIFEST_DIR")); + crate::validator::util::run_test_file_at_index(&path, 0).unwrap(); +} + +#[pg_test] +fn test_families_1() { + let path = format!("{}/tests/fixtures/families.json", env!("CARGO_MANIFEST_DIR")); + crate::validator::util::run_test_file_at_index(&path, 1).unwrap(); +} + #[pg_test] fn test_property_names_0() { let path = format!("{}/tests/fixtures/propertyNames.json", env!("CARGO_MANIFEST_DIR")); @@ -389,18 +329,6 @@ fn test_property_names_6() { crate::validator::util::run_test_file_at_index(&path, 6).unwrap(); } -#[pg_test] -fn test_boolean_schema_0() { - let path = format!("{}/tests/fixtures/boolean_schema.json", env!("CARGO_MANIFEST_DIR")); - crate::validator::util::run_test_file_at_index(&path, 0).unwrap(); -} - -#[pg_test] -fn test_boolean_schema_1() { - let path = format!("{}/tests/fixtures/boolean_schema.json", env!("CARGO_MANIFEST_DIR")); - crate::validator::util::run_test_file_at_index(&path, 1).unwrap(); -} - #[pg_test] fn test_not_0() { let path = format!("{}/tests/fixtures/not.json", env!("CARGO_MANIFEST_DIR")); @@ -569,6 +497,30 @@ fn test_items_15() { crate::validator::util::run_test_file_at_index(&path, 15).unwrap(); } +#[pg_test] +fn test_typed_refs_0() { + let path = format!("{}/tests/fixtures/typedRefs.json", env!("CARGO_MANIFEST_DIR")); + crate::validator::util::run_test_file_at_index(&path, 0).unwrap(); +} + +#[pg_test] +fn test_typed_refs_1() { + let path = format!("{}/tests/fixtures/typedRefs.json", env!("CARGO_MANIFEST_DIR")); + crate::validator::util::run_test_file_at_index(&path, 1).unwrap(); +} + +#[pg_test] +fn test_typed_refs_2() { + let path = format!("{}/tests/fixtures/typedRefs.json", env!("CARGO_MANIFEST_DIR")); + crate::validator::util::run_test_file_at_index(&path, 2).unwrap(); +} + +#[pg_test] +fn test_typed_refs_3() { + let path = format!("{}/tests/fixtures/typedRefs.json", env!("CARGO_MANIFEST_DIR")); + crate::validator::util::run_test_file_at_index(&path, 3).unwrap(); +} + #[pg_test] fn test_enum_0() { let path = format!("{}/tests/fixtures/enum.json", env!("CARGO_MANIFEST_DIR")); @@ -1013,6 +965,18 @@ fn test_one_of_12() { crate::validator::util::run_test_file_at_index(&path, 12).unwrap(); } +#[pg_test] +fn test_boolean_schema_0() { + let path = format!("{}/tests/fixtures/booleanSchema.json", env!("CARGO_MANIFEST_DIR")); + crate::validator::util::run_test_file_at_index(&path, 0).unwrap(); +} + +#[pg_test] +fn test_boolean_schema_1() { + let path = format!("{}/tests/fixtures/booleanSchema.json", env!("CARGO_MANIFEST_DIR")); + crate::validator::util::run_test_file_at_index(&path, 1).unwrap(); +} + #[pg_test] fn test_if_then_else_0() { let path = format!("{}/tests/fixtures/if-then-else.json", env!("CARGO_MANIFEST_DIR")); @@ -1960,129 +1924,3 @@ fn test_contains_8() { let path = format!("{}/tests/fixtures/contains.json", env!("CARGO_MANIFEST_DIR")); crate::validator::util::run_test_file_at_index(&path, 8).unwrap(); } - -#[pg_test] -fn test_dynamic_ref_0() { - let path = format!("{}/tests/fixtures/dynamicRef.json", env!("CARGO_MANIFEST_DIR")); - crate::validator::util::run_test_file_at_index(&path, 0).unwrap(); -} - -#[pg_test] -fn test_dynamic_ref_1() { - let path = format!("{}/tests/fixtures/dynamicRef.json", env!("CARGO_MANIFEST_DIR")); - crate::validator::util::run_test_file_at_index(&path, 1).unwrap(); -} - -#[pg_test] -fn test_dynamic_ref_2() { - let path = format!("{}/tests/fixtures/dynamicRef.json", env!("CARGO_MANIFEST_DIR")); - crate::validator::util::run_test_file_at_index(&path, 2).unwrap(); -} - -#[pg_test] -fn test_dynamic_ref_3() { - let path = format!("{}/tests/fixtures/dynamicRef.json", env!("CARGO_MANIFEST_DIR")); - crate::validator::util::run_test_file_at_index(&path, 3).unwrap(); -} - -#[pg_test] -fn test_dynamic_ref_4() { - let path = format!("{}/tests/fixtures/dynamicRef.json", env!("CARGO_MANIFEST_DIR")); - crate::validator::util::run_test_file_at_index(&path, 4).unwrap(); -} - -#[pg_test] -fn test_dynamic_ref_5() { - let path = format!("{}/tests/fixtures/dynamicRef.json", env!("CARGO_MANIFEST_DIR")); - crate::validator::util::run_test_file_at_index(&path, 5).unwrap(); -} - -#[pg_test] -fn test_dynamic_ref_6() { - let path = format!("{}/tests/fixtures/dynamicRef.json", env!("CARGO_MANIFEST_DIR")); - crate::validator::util::run_test_file_at_index(&path, 6).unwrap(); -} - -#[pg_test] -fn test_dynamic_ref_7() { - let path = format!("{}/tests/fixtures/dynamicRef.json", env!("CARGO_MANIFEST_DIR")); - crate::validator::util::run_test_file_at_index(&path, 7).unwrap(); -} - -#[pg_test] -fn test_dynamic_ref_8() { - let path = format!("{}/tests/fixtures/dynamicRef.json", env!("CARGO_MANIFEST_DIR")); - crate::validator::util::run_test_file_at_index(&path, 8).unwrap(); -} - -#[pg_test] -fn test_dynamic_ref_9() { - let path = format!("{}/tests/fixtures/dynamicRef.json", env!("CARGO_MANIFEST_DIR")); - crate::validator::util::run_test_file_at_index(&path, 9).unwrap(); -} - -#[pg_test] -fn test_dynamic_ref_10() { - let path = format!("{}/tests/fixtures/dynamicRef.json", env!("CARGO_MANIFEST_DIR")); - crate::validator::util::run_test_file_at_index(&path, 10).unwrap(); -} - -#[pg_test] -fn test_dynamic_ref_11() { - let path = format!("{}/tests/fixtures/dynamicRef.json", env!("CARGO_MANIFEST_DIR")); - crate::validator::util::run_test_file_at_index(&path, 11).unwrap(); -} - -#[pg_test] -fn test_dynamic_ref_12() { - let path = format!("{}/tests/fixtures/dynamicRef.json", env!("CARGO_MANIFEST_DIR")); - crate::validator::util::run_test_file_at_index(&path, 12).unwrap(); -} - -#[pg_test] -fn test_dynamic_ref_13() { - let path = format!("{}/tests/fixtures/dynamicRef.json", env!("CARGO_MANIFEST_DIR")); - crate::validator::util::run_test_file_at_index(&path, 13).unwrap(); -} - -#[pg_test] -fn test_dynamic_ref_14() { - let path = format!("{}/tests/fixtures/dynamicRef.json", env!("CARGO_MANIFEST_DIR")); - crate::validator::util::run_test_file_at_index(&path, 14).unwrap(); -} - -#[pg_test] -fn test_dynamic_ref_15() { - let path = format!("{}/tests/fixtures/dynamicRef.json", env!("CARGO_MANIFEST_DIR")); - crate::validator::util::run_test_file_at_index(&path, 15).unwrap(); -} - -#[pg_test] -fn test_dynamic_ref_16() { - let path = format!("{}/tests/fixtures/dynamicRef.json", env!("CARGO_MANIFEST_DIR")); - crate::validator::util::run_test_file_at_index(&path, 16).unwrap(); -} - -#[pg_test] -fn test_dynamic_ref_17() { - let path = format!("{}/tests/fixtures/dynamicRef.json", env!("CARGO_MANIFEST_DIR")); - crate::validator::util::run_test_file_at_index(&path, 17).unwrap(); -} - -#[pg_test] -fn test_dynamic_ref_18() { - let path = format!("{}/tests/fixtures/dynamicRef.json", env!("CARGO_MANIFEST_DIR")); - crate::validator::util::run_test_file_at_index(&path, 18).unwrap(); -} - -#[pg_test] -fn test_dynamic_ref_19() { - let path = format!("{}/tests/fixtures/dynamicRef.json", env!("CARGO_MANIFEST_DIR")); - crate::validator::util::run_test_file_at_index(&path, 19).unwrap(); -} - -#[pg_test] -fn test_dynamic_ref_20() { - let path = format!("{}/tests/fixtures/dynamicRef.json", env!("CARGO_MANIFEST_DIR")); - crate::validator::util::run_test_file_at_index(&path, 20).unwrap(); -} diff --git a/src/validator/compiler.rs b/src/validator/compiler.rs deleted file mode 100644 index 1a9aef9..0000000 --- a/src/validator/compiler.rs +++ /dev/null @@ -1,394 +0,0 @@ -use crate::validator::schema::Schema; -use regex::Regex; -use serde_json::Value; -// use std::collections::HashMap; -use std::error::Error; -use std::sync::Arc; - -/// Represents a compiled format validator -#[derive(Debug, Clone)] -pub enum CompiledFormat { - /// A simple function pointer validator - Func(fn(&Value) -> Result<(), Box>), - /// A regex-based validator - Regex(Regex), -} - -/// A wrapper for compiled regex patterns -#[derive(Debug, Clone)] -pub struct CompiledRegex(pub Regex); - -/// The Compiler is responsible for pre-calculating high-cost schema operations -pub struct Compiler; - -impl Compiler { - /// Internal: Compiles formats and regexes in-place - fn compile_formats_and_regexes(schema: &mut Schema) { - // 1. Compile Format - if let Some(format_str) = &schema.format { - if let Some(fmt) = crate::validator::formats::FORMATS.get(format_str.as_str()) { - schema.compiled_format = Some(CompiledFormat::Func(fmt.func)); - } - } - - // 2. Compile Pattern (regex) - if let Some(pattern_str) = &schema.pattern { - if let Ok(re) = Regex::new(pattern_str) { - schema.compiled_pattern = Some(CompiledRegex(re)); - } - } - - // 2.5 Compile Pattern Properties - if let Some(pp) = &schema.pattern_properties { - let mut compiled_pp = Vec::new(); - for (pattern, sub_schema) in pp { - if let Ok(re) = Regex::new(pattern) { - compiled_pp.push((CompiledRegex(re), sub_schema.clone())); - } else { - eprintln!( - "Invalid patternProperty regex in schema (compile time): {}", - pattern - ); - } - } - if !compiled_pp.is_empty() { - schema.compiled_pattern_properties = Some(compiled_pp); - } - } - - // 3. Recurse - Self::compile_recursive(schema); - } - - fn normalize_dependencies(schema: &mut Schema) { - if let Some(deps) = schema.dependencies.take() { - for (key, dep) in deps { - match dep { - crate::validator::schema::Dependency::Props(props) => { - schema - .dependent_required - .get_or_insert_with(std::collections::BTreeMap::new) - .insert(key, props); - } - crate::validator::schema::Dependency::Schema(sub_schema) => { - schema - .dependent_schemas - .get_or_insert_with(std::collections::BTreeMap::new) - .insert(key, sub_schema); - } - } - } - } - } - - fn compile_recursive(schema: &mut Schema) { - Self::normalize_dependencies(schema); - - // Compile self - if let Some(format_str) = &schema.format { - if let Some(fmt) = crate::validator::formats::FORMATS.get(format_str.as_str()) { - schema.compiled_format = Some(CompiledFormat::Func(fmt.func)); - } - } - if let Some(pattern_str) = &schema.pattern { - if let Ok(re) = Regex::new(pattern_str) { - schema.compiled_pattern = Some(CompiledRegex(re)); - } - } - - // Recurse - - if let Some(defs) = &mut schema.definitions { - for s in defs.values_mut() { - Self::compile_recursive(Arc::make_mut(s)); - } - } - if let Some(defs) = &mut schema.defs { - for s in defs.values_mut() { - Self::compile_recursive(Arc::make_mut(s)); - } - } - if let Some(props) = &mut schema.properties { - for s in props.values_mut() { - Self::compile_recursive(Arc::make_mut(s)); - } - } - if let Some(add_props) = &mut schema.additional_properties { - Self::compile_recursive(Arc::make_mut(add_props)); - } - - // ... Recurse logic ... - if let Some(items) = &mut schema.items { - Self::compile_recursive(Arc::make_mut(items)); - } - if let Some(prefix_items) = &mut schema.prefix_items { - for s in prefix_items { - Self::compile_recursive(Arc::make_mut(s)); - } - } - if let Some(not) = &mut schema.not { - Self::compile_recursive(Arc::make_mut(not)); - } - if let Some(all_of) = &mut schema.all_of { - for s in all_of { - Self::compile_recursive(Arc::make_mut(s)); - } - } - if let Some(any_of) = &mut schema.any_of { - for s in any_of { - Self::compile_recursive(Arc::make_mut(s)); - } - } - if let Some(one_of) = &mut schema.one_of { - for s in one_of { - Self::compile_recursive(Arc::make_mut(s)); - } - } - if let Some(s) = &mut schema.if_ { - Self::compile_recursive(Arc::make_mut(s)); - } - if let Some(s) = &mut schema.then_ { - Self::compile_recursive(Arc::make_mut(s)); - } - if let Some(s) = &mut schema.else_ { - Self::compile_recursive(Arc::make_mut(s)); - } - - if let Some(ds) = &mut schema.dependent_schemas { - for s in ds.values_mut() { - Self::compile_recursive(Arc::make_mut(s)); - } - } - if let Some(pn) = &mut schema.property_names { - Self::compile_recursive(Arc::make_mut(pn)); - } - } - - /// Recursively traverses the schema tree to build the local registry index. - fn compile_index( - schema: &Arc, - registry: &mut crate::validator::registry::Registry, - parent_base: Option, - pointer: json_pointer::JsonPointer>, - ) { - // 1. Index using Parent Base (Path from Parent) - if let Some(base) = &parent_base { - // We use the pointer's string representation (e.g., "/definitions/foo") - // and append it to the base. - let fragment = pointer.to_string(); - let ptr_uri = if fragment.is_empty() { - base.clone() - } else { - format!("{}#{}", base, fragment) - }; - registry.insert(ptr_uri, schema.clone()); - } - - // 2. Determine Current Scope... (unchanged logic) - let mut current_base = parent_base.clone(); - let mut child_pointer = pointer.clone(); - - if let Some(id) = &schema.obj.id { - let mut new_base = None; - if let Ok(_) = url::Url::parse(id) { - new_base = Some(id.clone()); - } else if let Some(base) = ¤t_base { - if let Ok(base_url) = url::Url::parse(base) { - if let Ok(joined) = base_url.join(id) { - new_base = Some(joined.to_string()); - } - } - } else { - new_base = Some(id.clone()); - } - - if let Some(base) = new_base { - // println!("DEBUG: Compiling index for path: {}", base); // Added println - registry.insert(base.clone(), schema.clone()); - current_base = Some(base); - child_pointer = json_pointer::JsonPointer::new(vec![]); // Reset - } - } - - // 3. Index by Anchor - if let Some(anchor) = &schema.obj.anchor { - if let Some(base) = ¤t_base { - let anchor_uri = format!("{}#{}", base, anchor); - registry.insert(anchor_uri, schema.clone()); - } - } - // Index by Dynamic Anchor - if let Some(d_anchor) = &schema.obj.dynamic_anchor { - if let Some(base) = ¤t_base { - let anchor_uri = format!("{}#{}", base, d_anchor); - registry.insert(anchor_uri, schema.clone()); - } - } - - // 4. Recurse (unchanged logic structure, just passing registry) - if let Some(defs) = schema.defs.as_ref().or(schema.definitions.as_ref()) { - let segment = if schema.defs.is_some() { - "$defs" - } else { - "definitions" - }; - for (key, sub_schema) in defs { - let mut sub = child_pointer.clone(); - sub.push(segment.to_string()); - let decoded_key = percent_encoding::percent_decode_str(key).decode_utf8_lossy(); - sub.push(decoded_key.to_string()); - Self::compile_index(sub_schema, registry, current_base.clone(), sub); - } - } - - if let Some(props) = &schema.properties { - for (key, sub_schema) in props { - let mut sub = child_pointer.clone(); - sub.push("properties".to_string()); - sub.push(key.to_string()); - Self::compile_index(sub_schema, registry, current_base.clone(), sub); - } - } - - if let Some(items) = &schema.items { - let mut sub = child_pointer.clone(); - sub.push("items".to_string()); - Self::compile_index(items, registry, current_base.clone(), sub); - } - - if let Some(prefix_items) = &schema.prefix_items { - for (i, sub_schema) in prefix_items.iter().enumerate() { - let mut sub = child_pointer.clone(); - sub.push("prefixItems".to_string()); - sub.push(i.to_string()); - Self::compile_index(sub_schema, registry, current_base.clone(), sub); - } - } - - if let Some(all_of) = &schema.all_of { - for (i, sub_schema) in all_of.iter().enumerate() { - let mut sub = child_pointer.clone(); - sub.push("allOf".to_string()); - sub.push(i.to_string()); - Self::compile_index(sub_schema, registry, current_base.clone(), sub); - } - } - if let Some(any_of) = &schema.any_of { - for (i, sub_schema) in any_of.iter().enumerate() { - let mut sub = child_pointer.clone(); - sub.push("anyOf".to_string()); - sub.push(i.to_string()); - Self::compile_index(sub_schema, registry, current_base.clone(), sub); - } - } - if let Some(one_of) = &schema.one_of { - for (i, sub_schema) in one_of.iter().enumerate() { - let mut sub = child_pointer.clone(); - sub.push("oneOf".to_string()); - sub.push(i.to_string()); - Self::compile_index(sub_schema, registry, current_base.clone(), sub); - } - } - - if let Some(not) = &schema.not { - let mut sub = child_pointer.clone(); - sub.push("not".to_string()); - Self::compile_index(not, registry, current_base.clone(), sub); - } - if let Some(if_) = &schema.if_ { - let mut sub = child_pointer.clone(); - sub.push("if".to_string()); - Self::compile_index(if_, registry, current_base.clone(), sub); - } - if let Some(then_) = &schema.then_ { - let mut sub = child_pointer.clone(); - sub.push("then".to_string()); - Self::compile_index(then_, registry, current_base.clone(), sub); - } - if let Some(else_) = &schema.else_ { - let mut sub = child_pointer.clone(); - sub.push("else".to_string()); - Self::compile_index(else_, registry, current_base.clone(), sub); - } - if let Some(deps) = &schema.dependent_schemas { - for (key, sub_schema) in deps { - let mut sub = child_pointer.clone(); - sub.push("dependentSchemas".to_string()); - sub.push(key.to_string()); - Self::compile_index(sub_schema, registry, current_base.clone(), sub); - } - } - if let Some(pp) = &schema.pattern_properties { - for (key, sub_schema) in pp { - let mut sub = child_pointer.clone(); - sub.push("patternProperties".to_string()); - sub.push(key.to_string()); - Self::compile_index(sub_schema, registry, current_base.clone(), sub); - } - } - if let Some(add_props) = &schema.additional_properties { - let mut sub = child_pointer.clone(); - sub.push("additionalProperties".to_string()); - Self::compile_index(add_props, registry, current_base.clone(), sub); - } - if let Some(contains) = &schema.contains { - let mut sub = child_pointer.clone(); - sub.push("contains".to_string()); - Self::compile_index(contains, registry, current_base.clone(), sub); - } - if let Some(property_names) = &schema.property_names { - let mut sub = child_pointer.clone(); - sub.push("propertyNames".to_string()); - Self::compile_index(property_names, registry, current_base.clone(), sub); - } - } - - pub fn compile(mut root_schema: Schema, root_id: Option) -> Arc { - // 1. Compile in-place (formats/regexes/normalization) - Self::compile_formats_and_regexes(&mut root_schema); - - // Apply root_id override if schema ID is missing - if let Some(rid) = &root_id { - if root_schema.obj.id.is_none() { - root_schema.obj.id = Some(rid.clone()); - } - } - - // 2. Build ID/Pointer Index - let mut registry = crate::validator::registry::Registry::new(); - - // We need a temporary Arc to satisfy compile_index recursion - // But we are modifying root_schema. - // This is tricky. compile_index takes &Arc. - // We should build the index first, THEN attach it. - - let root = Arc::new(root_schema); - - // Default base_uri to "" - let base_uri = root_id - .clone() - .or_else(|| root.obj.id.clone()) - .or(Some("".to_string())); - - Self::compile_index( - &root, - &mut registry, - base_uri, - json_pointer::JsonPointer::new(vec![]), - ); - - // Also ensure root id is indexed if present - if let Some(rid) = root_id { - registry.insert(rid, root.clone()); - } - - // Now we need to attach this registry to the root schema. - // Since root is an Arc, we might need to recreate it if we can't mutate. - // Schema struct modifications require &mut. - - let mut final_schema = Arc::try_unwrap(root).unwrap_or_else(|arc| (*arc).clone()); - final_schema.obj.compiled_registry = Some(Arc::new(registry)); - - Arc::new(final_schema) - } -} diff --git a/src/validator/context.rs b/src/validator/context.rs index eccc423..848c473 100644 --- a/src/validator/context.rs +++ b/src/validator/context.rs @@ -1,44 +1,60 @@ -use crate::validator::schema::Schema; -use crate::validator::Validator; +use crate::database::schema::Schema; use crate::validator::error::ValidationError; -use crate::validator::instance::ValidationInstance; use crate::validator::result::ValidationResult; -use std::collections::HashSet; -pub struct ValidationContext<'a, I: ValidationInstance<'a>> { - pub validator: &'a Validator, +pub struct ValidationContext<'a> { + pub schemas: &'a std::collections::HashMap, pub root: &'a Schema, pub schema: &'a Schema, - pub instance: I, + pub instance: &'a serde_json::Value, pub path: String, pub depth: usize, - pub scope: Vec, - pub overrides: HashSet, pub extensible: bool, pub reporter: bool, } -impl<'a, I: ValidationInstance<'a>> ValidationContext<'a, I> { +impl<'a> ValidationContext<'a> { + pub fn resolve_ref( + &self, + ref_string: &str, + ) -> Option<(crate::database::schema::ResolvedRef<'a>, String)> { + if let Some(local_schema_arc) = self.root.resolve_ref(ref_string) { + if ref_string.starts_with('#') { + return Some(( + crate::database::schema::ResolvedRef::Local(local_schema_arc.as_ref()), + ref_string.to_string(), + )); + } + } + + // We will replace all of this with `self.schema.compiled_ref` heavily shortly. + // For now, doing a basic map lookup to pass compilation. + if let Some(s) = self.schemas.get(ref_string) { + return Some(( + crate::database::schema::ResolvedRef::Global(s, s), + ref_string.to_string(), + )); + } + + None + } + pub fn new( - validator: &'a Validator, + schemas: &'a std::collections::HashMap, root: &'a Schema, schema: &'a Schema, - instance: I, - scope: Vec, - overrides: HashSet, + instance: &'a serde_json::Value, extensible: bool, reporter: bool, ) -> Self { let effective_extensible = schema.extensible.unwrap_or(extensible); Self { - validator, + schemas, root, schema, instance, path: String::new(), depth: 0, - scope, - overrides, extensible: effective_extensible, reporter, } @@ -47,72 +63,30 @@ impl<'a, I: ValidationInstance<'a>> ValidationContext<'a, I> { pub fn derive( &self, schema: &'a Schema, - instance: I, + instance: &'a serde_json::Value, path: &str, - scope: Vec, - overrides: HashSet, extensible: bool, reporter: bool, ) -> Self { let effective_extensible = schema.extensible.unwrap_or(extensible); Self { - validator: self.validator, + schemas: self.schemas, root: self.root, schema, instance, path: path.to_string(), depth: self.depth + 1, - scope, - overrides, extensible: effective_extensible, reporter, } } pub fn derive_for_schema(&self, schema: &'a Schema, reporter: bool) -> Self { - self.derive( - schema, - self.instance, - &self.path, - self.scope.clone(), - HashSet::new(), - self.extensible, - reporter, - ) + self.derive(schema, self.instance, &self.path, self.extensible, reporter) } pub fn validate(&self) -> Result { - let mut effective_scope = self.scope.clone(); - - if let Some(id) = &self.schema.obj.id { - let current_base = self.scope.last().map(|s| s.as_str()).unwrap_or(""); - let mut new_base = id.clone().to_string(); - if !current_base.is_empty() { - if let Ok(base_url) = url::Url::parse(current_base) { - if let Ok(joined) = base_url.join(id) { - new_base = joined.to_string(); - } - } - } - - effective_scope.push(new_base); - - let shadow = ValidationContext { - validator: self.validator, - root: self.root, - schema: self.schema, - instance: self.instance, - path: self.path.clone(), - depth: self.depth, - scope: effective_scope, - overrides: self.overrides.clone(), - extensible: self.extensible, - reporter: self.reporter, - }; - return shadow.validate_scoped(); - } - self.validate_scoped() } } diff --git a/src/validator/mod.rs b/src/validator/mod.rs index e0c80fd..9a9f0fd 100644 --- a/src/validator/mod.rs +++ b/src/validator/mod.rs @@ -1,101 +1,29 @@ -pub mod compiler; pub mod context; pub mod error; -pub mod formats; -pub mod instance; -pub mod registry; pub mod result; pub mod rules; -pub mod schema; pub mod util; pub use context::ValidationContext; pub use error::ValidationError; -pub use instance::{MutableInstance, ReadOnlyInstance}; pub use result::ValidationResult; -use crate::validator::registry::Registry; -use crate::validator::schema::Schema; use serde_json::Value; use std::collections::HashSet; -use std::sync::Arc; - -pub enum ResolvedRef<'a> { - Local(&'a Schema), - Global(&'a Schema, &'a Schema), -} pub struct Validator { - pub registry: Registry, - pub families: std::collections::HashMap>, + pub schemas: std::sync::Arc>, } impl Validator { - pub fn from_punc_definition( - enums: Option<&Value>, - types: Option<&Value>, - puncs: Option<&Value>, + pub fn new( + schemas: std::sync::Arc>, ) -> Self { - let mut registry = Registry::new(); - let mut families = std::collections::HashMap::new(); - - let mut family_map: std::collections::HashMap> = - std::collections::HashMap::new(); - - if let Some(Value::Array(arr)) = types { - for item in arr { - if let Some(name) = item.get("name").and_then(|v| v.as_str()) { - if let Some(hierarchy) = item.get("hierarchy").and_then(|v| v.as_array()) { - for ancestor in hierarchy { - if let Some(anc_str) = ancestor.as_str() { - family_map - .entry(anc_str.to_string()) - .or_default() - .insert(name.to_string()); - } - } - } - } - } - } - - for (family_name, members) in family_map { - let object_refs: Vec = members - .iter() - .map(|s| serde_json::json!({ "$ref": s })) - .collect(); - let schema_json = serde_json::json!({ - "oneOf": object_refs - }); - if let Ok(schema) = serde_json::from_value::(schema_json) { - let compiled = crate::validator::compiler::Compiler::compile(schema, None); - families.insert(family_name, compiled); - } - } - - let mut cache_items = |items_val: Option<&Value>| { - if let Some(Value::Array(arr)) = items_val { - for item in arr { - if let Some(Value::Array(schemas)) = item.get("schemas") { - for schema_val in schemas { - if let Ok(schema) = serde_json::from_value::(schema_val.clone()) { - registry.add(schema); - } - } - } - } - } - }; - - cache_items(enums); - cache_items(types); - cache_items(puncs); - - Self { registry, families } + Self { schemas } } pub fn get_schema_ids(&self) -> Vec { - self.registry.schemas.keys().cloned().collect() + self.schemas.keys().cloned().collect() } pub fn check_type(t: &str, val: &Value) -> bool { @@ -116,148 +44,14 @@ impl Validator { } } - pub fn resolve_ref<'a>( - &'a self, - root: &'a Schema, - ref_string: &str, - scope: &str, - ) -> Option<(ResolvedRef<'a>, String)> { - if ref_string.starts_with('#') { - if let Some(indexrs) = &root.obj.compiled_registry { - if let Some(s) = indexrs.schemas.get(ref_string) { - return Some((ResolvedRef::Local(s.as_ref()), ref_string.to_string())); - } - } - } - - if let Ok(base) = url::Url::parse(scope) { - if let Ok(joined) = base.join(ref_string) { - let joined_str = joined.to_string(); - if let Some(indexrs) = &root.obj.compiled_registry { - if let Some(s) = indexrs.schemas.get(&joined_str) { - return Some((ResolvedRef::Local(s.as_ref() as &Schema), joined_str)); - } - } - - if let Ok(decoded) = percent_encoding::percent_decode_str(&joined_str).decode_utf8() { - let decoded_str = decoded.to_string(); - if decoded_str != joined_str { - if let Some(indexrs) = &root.obj.compiled_registry { - if let Some(s) = indexrs.schemas.get(&decoded_str) { - return Some((ResolvedRef::Local(s.as_ref() as &Schema), decoded_str)); - } - } - } - } - - if let Some(s) = self.registry.schemas.get(&joined_str) { - return Some((ResolvedRef::Global(s.as_ref(), s.as_ref()), joined_str)); - } - } - } else { - if ref_string.starts_with('#') { - let joined_str = format!("{}{}", scope, ref_string); - - if let Some(indexrs) = &root.obj.compiled_registry { - if let Some(s) = indexrs.schemas.get(&joined_str) { - return Some((ResolvedRef::Local(s.as_ref() as &Schema), joined_str)); - } - } - - if let Ok(decoded) = percent_encoding::percent_decode_str(&joined_str).decode_utf8() { - let decoded_str = decoded.to_string(); - if decoded_str != joined_str { - if let Some(indexrs) = &root.obj.compiled_registry { - if let Some(s) = indexrs.schemas.get(&decoded_str) { - return Some((ResolvedRef::Local(s.as_ref() as &Schema), decoded_str)); - } - } - } - } - - if let Some(s) = self.registry.schemas.get(&joined_str) { - return Some((ResolvedRef::Global(s.as_ref(), s.as_ref()), joined_str)); - } - } - } - - if let Ok(parsed) = url::Url::parse(ref_string) { - let absolute = parsed.to_string(); - if let Some(indexrs) = &root.obj.compiled_registry { - if let Some(s) = indexrs.schemas.get(&absolute) { - return Some((ResolvedRef::Local(s.as_ref()), absolute)); - } - } - - let resource_base = if let Some((base, _)) = absolute.split_once('#') { - base - } else { - &absolute - }; - - if let Some(compiled) = self.registry.schemas.get(resource_base) { - if let Some(indexrs) = &compiled.obj.compiled_registry { - if let Some(s) = indexrs.schemas.get(&absolute) { - return Some((ResolvedRef::Global(compiled.as_ref(), s.as_ref()), absolute)); - } - } - } - } - - if let Some(compiled) = self.registry.schemas.get(ref_string) { - return Some(( - ResolvedRef::Global(compiled.as_ref(), compiled.as_ref()), - ref_string.to_string(), - )); - } - - None - } - pub fn validate( &self, schema_id: &str, instance: &Value, ) -> Result { - if let Some(schema) = self.registry.schemas.get(schema_id) { - let ctx = ValidationContext::new( - self, - schema, - schema, - ReadOnlyInstance(instance), - vec![], - HashSet::new(), - false, - false, - ); - ctx.validate() - } else { - Err(ValidationError { - code: "SCHEMA_NOT_FOUND".to_string(), - message: format!("Schema {} not found", schema_id), - path: "".to_string(), - }) - } - } - - pub fn mask( - &self, - schema_id: &str, - instance: &mut Value, - ) -> Result { - if let Some(schema) = self.registry.schemas.get(schema_id) { - let ctx = ValidationContext::new( - self, - schema, - schema, - MutableInstance::new(instance), - vec![], - HashSet::new(), - false, - false, - ); - let res = ctx.validate()?; - Ok(res) + if let Some(schema) = self.schemas.get(schema_id) { + let ctx = ValidationContext::new(&self.schemas, schema, schema, instance, false, false); + ctx.validate_scoped() } else { Err(ValidationError { code: "SCHEMA_NOT_FOUND".to_string(), diff --git a/src/validator/registry.rs b/src/validator/registry.rs deleted file mode 100644 index 5de2a9a..0000000 --- a/src/validator/registry.rs +++ /dev/null @@ -1,50 +0,0 @@ -use crate::validator::schema::Schema; -use lazy_static::lazy_static; -use std::collections::HashMap; -use std::sync::RwLock; - -lazy_static! { - pub static ref REGISTRY: RwLock = RwLock::new(Registry::new()); -} - -use std::sync::Arc; - -#[derive(Debug, Clone, Default)] -pub struct Registry { - pub schemas: HashMap>, -} - -impl Registry { - pub fn new() -> Self { - Registry { - schemas: HashMap::new(), - } - } - - pub fn add(&mut self, schema: crate::validator::schema::Schema) { - let id = schema - .obj - .id - .clone() - .expect("Schema must have an $id to be registered"); - let compiled = crate::validator::compiler::Compiler::compile(schema, Some(id.clone())); - self.schemas.insert(id, compiled); - } - - pub fn insert(&mut self, id: String, schema: Arc) { - // We allow overwriting for now to support re-compilation in tests/dev - self.schemas.insert(id, schema); - } - - pub fn get(&self, id: &str) -> Option> { - self.schemas.get(id).cloned() - } - - pub fn clear(&mut self) { - self.schemas.clear(); - } - - pub fn len(&self) -> usize { - self.schemas.len() - } -} diff --git a/src/validator/rules.rs b/src/validator/rules.rs deleted file mode 100644 index 9937497..0000000 --- a/src/validator/rules.rs +++ /dev/null @@ -1,1008 +0,0 @@ -use regex::Regex; -use serde_json::Value; -use std::collections::HashSet; - -use crate::validator::context::ValidationContext; -use crate::validator::error::ValidationError; -use crate::validator::instance::ValidationInstance; -use crate::validator::result::ValidationResult; -use crate::validator::{ResolvedRef, Validator}; - -impl<'a, I: ValidationInstance<'a>> ValidationContext<'a, I> { - pub(crate) fn validate_scoped(&self) -> Result { - let mut result = ValidationResult::new(); - - self.validate_depth()?; - if self.validate_always_fail(&mut result) { - return Ok(result); - } - if self.validate_family(&mut result) { - return Ok(result); - } - - if let Some(ref_res) = self.validate_refs()? { - result.merge(ref_res); - } - - self.validate_core(&mut result); - self.validate_numeric(&mut result); - self.validate_string(&mut result); - self.validate_format(&mut result); - self.validate_object(&mut result)?; - self.validate_array(&mut result)?; - self.validate_combinators(&mut result)?; - self.validate_conditionals(&mut result)?; - self.validate_extensible(&mut result); - self.validate_strictness(&mut result); - - Ok(result) - } - - fn validate_extensible(&self, result: &mut ValidationResult) { - if self.extensible { - let current = self.instance.as_value(); - if let Some(obj) = current.as_object() { - result.evaluated_keys.extend(obj.keys().cloned()); - } else if let Some(arr) = current.as_array() { - result.evaluated_indices.extend(0..arr.len()); - } - } - } - - fn validate_depth(&self) -> Result<(), ValidationError> { - if self.depth > 100 { - Err(ValidationError { - code: "RECURSION_LIMIT_EXCEEDED".to_string(), - message: "Recursion limit exceeded".to_string(), - path: self.path.to_string(), - }) - } else { - Ok(()) - } - } - - fn validate_always_fail(&self, result: &mut ValidationResult) -> bool { - if self.schema.always_fail { - result.errors.push(ValidationError { - code: "FALSE_SCHEMA".to_string(), - message: "Schema is false".to_string(), - path: self.path.to_string(), - }); - true - } else { - false - } - } - - fn validate_family(&self, result: &mut ValidationResult) -> bool { - if self.schema.family.is_some() { - let conflicts = self.schema.type_.is_some() - || self.schema.properties.is_some() - || self.schema.required.is_some() - || self.schema.additional_properties.is_some() - || self.schema.items.is_some() - || self.schema.ref_string.is_some() - || self.schema.one_of.is_some() - || self.schema.any_of.is_some() - || self.schema.all_of.is_some() - || self.schema.enum_.is_some() - || self.schema.const_.is_some(); - - if conflicts { - result.errors.push(ValidationError { - code: "INVALID_SCHEMA".to_string(), - message: "$family must be used exclusively without other constraints".to_string(), - path: self.path.to_string(), - }); - return true; - } - } - false - } - - pub(crate) fn validate_refs(&self) -> Result, ValidationError> { - let mut res = ValidationResult::new(); - let mut handled = false; - - let effective_scope = &self.scope; - let current_base_resolved = effective_scope.last().map(|s| s.as_str()).unwrap_or(""); - - // $ref - if let Some(ref ref_string) = self.schema.ref_string { - handled = true; - if ref_string == "#" { - let mut new_overrides = self.overrides.clone(); - if let Some(props) = &self.schema.properties { - new_overrides.extend(props.keys().map(|k| k.to_string())); - } - - let derived = self.derive( - self.root, - self.instance, - &self.path, - effective_scope.clone(), - new_overrides, - self.extensible, - self.reporter, - ); - res.merge(derived.validate()?); - } else { - if let Some((resolved, matched_key)) = - self - .validator - .resolve_ref(self.root, ref_string, current_base_resolved) - { - let (target_root, target_schema) = match &resolved { - ResolvedRef::Local(s) => (self.root, *s), - ResolvedRef::Global(root, s) => (*root, *s), - }; - - let resource_base = if let Some((base, _)) = matched_key.split_once('#') { - base - } else { - &matched_key - }; - - let scope_to_pass = if target_schema.obj.id.is_none() { - if !resource_base.is_empty() && resource_base != current_base_resolved { - let mut new_scope = effective_scope.clone(); - new_scope.push(resource_base.to_string()); - new_scope - } else { - effective_scope.clone() - } - } else { - effective_scope.clone() - }; - - let mut new_overrides = self.overrides.clone(); - if let Some(props) = &self.schema.properties { - new_overrides.extend(props.keys().map(|k| k.to_string())); - } - - let target_ctx = ValidationContext::new( - self.validator, - target_root, - target_schema, - self.instance, - scope_to_pass, - new_overrides, - false, - self.reporter, - ); - let mut manual_ctx = target_ctx; - manual_ctx.path = self.path.clone(); - manual_ctx.depth = self.depth + 1; - - let target_res = manual_ctx.validate()?; - res.merge(target_res); - handled = true; - } else { - res.errors.push(ValidationError { - code: "REF_RESOLUTION_FAILED".to_string(), - message: format!("Could not resolve reference '{}'", ref_string), - path: self.path.to_string(), - }); - } - } - } - - // $dynamicRef - if let Some(ref d_ref) = self.schema.obj.dynamic_ref { - handled = true; - let anchor = if let Some(idx) = d_ref.rfind('#') { - &d_ref[idx + 1..] - } else { - d_ref.as_str() - }; - - let mut resolved_target: Option<(ResolvedRef, String)> = None; - let local_resolution = self - .validator - .resolve_ref(self.root, d_ref, current_base_resolved); - - let is_bookended = if let Some((ResolvedRef::Local(s), _)) = &local_resolution { - s.obj.dynamic_anchor.as_deref() == Some(anchor) - } else { - false - }; - - if is_bookended { - for base in effective_scope.iter() { - let resource_base = if let Some((r, _)) = base.split_once('#') { - r - } else { - base - }; - let key = format!("{}#{}", resource_base, anchor); - - if let Some(indexrs) = &self.root.obj.compiled_registry { - if let Some(s) = indexrs.schemas.get(&key) { - if s.obj.dynamic_anchor.as_deref() == Some(anchor) { - resolved_target = Some((ResolvedRef::Local(s.as_ref()), key.to_string())); - break; - } - } - } - if resolved_target.is_none() { - if let Some(registry_arc) = &self.root.obj.compiled_registry { - if let Some(compiled) = registry_arc.schemas.get(resource_base) { - if let Some(indexrs) = &compiled.obj.compiled_registry { - if let Some(s) = indexrs.schemas.get(&key) { - if s.obj.dynamic_anchor.as_deref() == Some(anchor) { - resolved_target = Some(( - ResolvedRef::Global(compiled.as_ref(), s.as_ref()), - key.to_string(), - )); - break; - } - } - } - } - } else { - if let Some(compiled) = self.validator.registry.schemas.get(resource_base) { - if let Some(indexrs) = &compiled.obj.compiled_registry { - if let Some(s) = indexrs.schemas.get(&key) { - if s.obj.dynamic_anchor.as_deref() == Some(anchor) { - resolved_target = Some(( - ResolvedRef::Global(compiled.as_ref(), s.as_ref()), - key.to_string(), - )); - break; - } - } - } - } - } - } - if resolved_target.is_some() { - break; - } - } - } - - if resolved_target.is_none() { - resolved_target = local_resolution; - } - - if let Some((resolved, matched_key)) = resolved_target { - let (target_root, target_schema) = match &resolved { - ResolvedRef::Local(s) => (self.root, *s), - ResolvedRef::Global(root, s) => (*root, *s), - }; - - let resource_base = if let Some((base, _)) = matched_key.split_once('#') { - base - } else { - &matched_key - }; - - let scope_to_pass = if let Some(ref tid) = target_schema.obj.id { - let mut new_scope = effective_scope.clone(); - new_scope.push(tid.to_string()); - new_scope - } else { - if !resource_base.is_empty() && resource_base != current_base_resolved { - let mut new_scope = effective_scope.clone(); - new_scope.push(resource_base.to_string()); - new_scope - } else { - effective_scope.clone() - } - }; - - let mut new_overrides = self.overrides.clone(); - if let Some(props) = &self.schema.properties { - new_overrides.extend(props.keys().map(|k| k.to_string())); - } - - let target_ctx = ValidationContext::new( - self.validator, - target_root, - target_schema, - self.instance, - scope_to_pass, - new_overrides, - false, - self.reporter, - ); - let mut manual_ctx = target_ctx; - manual_ctx.path = self.path.clone(); - manual_ctx.depth = self.depth + 1; - - res.merge(manual_ctx.validate()?); - } else { - res.errors.push(ValidationError { - code: "REF_RESOLUTION_FAILED".to_string(), - message: format!("Could not resolve dynamic reference '{}'", d_ref), - path: self.path.to_string(), - }); - } - } - - // Family Support Map - if let Some(ref family) = self.schema.obj.family { - if let Some(family_schema) = self.validator.families.get(family) { - let derived = self.derive_for_schema(family_schema.as_ref(), true); - res.merge(derived.validate()?); - handled = true; - } else { - res.errors.push(ValidationError { - code: "FAMILY_NOT_FOUND".to_string(), - message: format!("Family '{}' not found in families map", family), - path: self.path.to_string(), - }); - handled = true; - } - } - - if handled { Ok(Some(res)) } else { Ok(None) } - } - - pub(crate) fn validate_core(&self, result: &mut ValidationResult) { - let current = self.instance.as_value(); - if let Some(ref type_) = self.schema.type_ { - match type_ { - crate::validator::schema::SchemaTypeOrArray::Single(t) => { - if !Validator::check_type(t, current) { - result.errors.push(ValidationError { - code: "INVALID_TYPE".to_string(), - message: format!("Expected type '{}'", t), - path: self.path.to_string(), - }); - } - } - crate::validator::schema::SchemaTypeOrArray::Multiple(types) => { - let mut valid = false; - for t in types { - if Validator::check_type(t, current) { - valid = true; - break; - } - } - if !valid { - result.errors.push(ValidationError { - code: "INVALID_TYPE".to_string(), - message: format!("Expected one of types {:?}", types), - path: self.path.to_string(), - }); - } - } - } - } - - if let Some(ref const_val) = self.schema.const_ { - if !crate::validator::util::equals(current, const_val) { - result.errors.push(ValidationError { - code: "CONST_VIOLATED".to_string(), - message: "Value does not match const".to_string(), - path: self.path.to_string(), - }); - } else { - if let Some(obj) = current.as_object() { - result.evaluated_keys.extend(obj.keys().cloned()); - } else if let Some(arr) = current.as_array() { - result.evaluated_indices.extend(0..arr.len()); - } - } - } - - if let Some(ref enum_vals) = self.schema.enum_ { - let mut found = false; - for val in enum_vals { - if crate::validator::util::equals(current, val) { - found = true; - break; - } - } - if !found { - result.errors.push(ValidationError { - code: "ENUM_MISMATCH".to_string(), - message: "Value is not in enum".to_string(), - path: self.path.to_string(), - }); - } else { - if let Some(obj) = current.as_object() { - result.evaluated_keys.extend(obj.keys().cloned()); - } else if let Some(arr) = current.as_array() { - result.evaluated_indices.extend(0..arr.len()); - } - } - } - } - - pub(crate) fn validate_numeric(&self, result: &mut ValidationResult) { - let current = self.instance.as_value(); - if let Some(num) = current.as_f64() { - if let Some(min) = self.schema.minimum { - if num < min { - result.errors.push(ValidationError { - code: "MINIMUM_VIOLATED".to_string(), - message: format!("Value {} < min {}", num, min), - path: self.path.to_string(), - }); - } - } - if let Some(max) = self.schema.maximum { - if num > max { - result.errors.push(ValidationError { - code: "MAXIMUM_VIOLATED".to_string(), - message: format!("Value {} > max {}", num, max), - path: self.path.to_string(), - }); - } - } - if let Some(ex_min) = self.schema.exclusive_minimum { - if num <= ex_min { - result.errors.push(ValidationError { - code: "EXCLUSIVE_MINIMUM_VIOLATED".to_string(), - message: format!("Value {} <= ex_min {}", num, ex_min), - path: self.path.to_string(), - }); - } - } - if let Some(ex_max) = self.schema.exclusive_maximum { - if num >= ex_max { - result.errors.push(ValidationError { - code: "EXCLUSIVE_MAXIMUM_VIOLATED".to_string(), - message: format!("Value {} >= ex_max {}", num, ex_max), - path: self.path.to_string(), - }); - } - } - if let Some(multiple_of) = self.schema.multiple_of { - let val: f64 = num / multiple_of; - if (val - val.round()).abs() > f64::EPSILON { - result.errors.push(ValidationError { - code: "MULTIPLE_OF_VIOLATED".to_string(), - message: format!("Value {} not multiple of {}", num, multiple_of), - path: self.path.to_string(), - }); - } - } - } - } - - pub(crate) fn validate_string(&self, result: &mut ValidationResult) { - let current = self.instance.as_value(); - if let Some(s) = current.as_str() { - if let Some(min) = self.schema.min_length { - if (s.chars().count() as f64) < min { - result.errors.push(ValidationError { - code: "MIN_LENGTH_VIOLATED".to_string(), - message: format!("Length < min {}", min), - path: self.path.to_string(), - }); - } - } - if let Some(max) = self.schema.max_length { - if (s.chars().count() as f64) > max { - result.errors.push(ValidationError { - code: "MAX_LENGTH_VIOLATED".to_string(), - message: format!("Length > max {}", max), - path: self.path.to_string(), - }); - } - } - if let Some(ref compiled_re) = self.schema.compiled_pattern { - if !compiled_re.0.is_match(s) { - result.errors.push(ValidationError { - code: "PATTERN_VIOLATED".to_string(), - message: format!("Pattern mismatch {:?}", self.schema.pattern), - path: self.path.to_string(), - }); - } - } else if let Some(ref pattern) = self.schema.pattern { - if let Ok(re) = Regex::new(pattern) { - if !re.is_match(s) { - result.errors.push(ValidationError { - code: "PATTERN_VIOLATED".to_string(), - message: format!("Pattern mismatch {}", pattern), - path: self.path.to_string(), - }); - } - } - } - } - } - - pub(crate) fn validate_format(&self, result: &mut ValidationResult) { - let current = self.instance.as_value(); - if let Some(ref compiled_fmt) = self.schema.compiled_format { - match compiled_fmt { - crate::validator::compiler::CompiledFormat::Func(f) => { - let should = if let Some(s) = current.as_str() { - !s.is_empty() - } else { - true - }; - if should { - if let Err(e) = f(current) { - result.errors.push(ValidationError { - code: "FORMAT_MISMATCH".to_string(), - message: format!("Format error: {}", e), - path: self.path.to_string(), - }); - } - } - } - crate::validator::compiler::CompiledFormat::Regex(re) => { - if let Some(s) = current.as_str() { - if !re.is_match(s) { - result.errors.push(ValidationError { - code: "FORMAT_MISMATCH".to_string(), - message: "Format regex mismatch".to_string(), - path: self.path.to_string(), - }); - } - } - } - } - } - } - - pub(crate) fn validate_object( - &self, - result: &mut ValidationResult, - ) -> Result<(), ValidationError> { - let current = self.instance.as_value(); - if let Some(obj) = current.as_object() { - if let Some(min) = self.schema.min_properties { - if (obj.len() as f64) < min { - result.errors.push(ValidationError { - code: "MIN_PROPERTIES".to_string(), - message: "Too few properties".to_string(), - path: self.path.to_string(), - }); - } - } - if let Some(max) = self.schema.max_properties { - if (obj.len() as f64) > max { - result.errors.push(ValidationError { - code: "MAX_PROPERTIES".to_string(), - message: "Too many properties".to_string(), - path: self.path.to_string(), - }); - } - } - if let Some(ref req) = self.schema.required { - for field in req { - if !obj.contains_key(field) { - result.errors.push(ValidationError { - code: "REQUIRED_FIELD_MISSING".to_string(), - message: format!("Missing {}", field), - path: format!("{}/{}", self.path, field), - }); - } - } - } - if let Some(ref dep_req) = self.schema.dependent_required { - for (key, required_keys) in dep_req { - if obj.contains_key(key) { - for req_key in required_keys { - if !obj.contains_key(req_key) { - result.errors.push(ValidationError { - code: "DEPENDENT_REQUIRED".to_string(), - message: format!("Missing dependent {}", req_key), - path: self.path.to_string(), - }); - } - } - } - } - } - if let Some(ref dep_sch) = self.schema.dependent_schemas { - for (key, sub_schema) in dep_sch { - if obj.contains_key(key) { - let derived = self.derive( - sub_schema, - self.instance, - &self.path, - self.scope.clone(), - HashSet::new(), - self.extensible, - false, - ); - result.merge(derived.validate()?); - } - } - } - - if let Some(props) = &self.schema.properties { - for (key, sub_schema) in props { - if self.overrides.contains(key) { - continue; - } - - if let Some(child_instance) = self.instance.child_at_key(key) { - let new_path = format!("{}/{}", self.path, key); - let is_ref = sub_schema.ref_string.is_some() || sub_schema.obj.dynamic_ref.is_some(); - let next_extensible = if is_ref { false } else { self.extensible }; - - let derived = self.derive( - sub_schema, - child_instance, - &new_path, - self.scope.clone(), - HashSet::new(), - next_extensible, - false, - ); - let item_res = derived.validate()?; - result.merge(item_res); - result.evaluated_keys.insert(key.to_string()); - } - } - } - - if let Some(ref compiled_pp) = self.schema.compiled_pattern_properties { - for (compiled_re, sub_schema) in compiled_pp { - for (key, _) in obj { - if compiled_re.0.is_match(key) { - if let Some(child_instance) = self.instance.child_at_key(key) { - let new_path = format!("{}/{}", self.path, key); - let is_ref = - sub_schema.ref_string.is_some() || sub_schema.obj.dynamic_ref.is_some(); - let next_extensible = if is_ref { false } else { self.extensible }; - - let derived = self.derive( - sub_schema, - child_instance, - &new_path, - self.scope.clone(), - HashSet::new(), - next_extensible, - false, - ); - let item_res = derived.validate()?; - result.merge(item_res); - result.evaluated_keys.insert(key.to_string()); - } - } - } - } - } - - if let Some(ref additional_schema) = self.schema.additional_properties { - for (key, _) in obj { - let mut locally_matched = false; - if let Some(props) = &self.schema.properties { - if props.contains_key(&key.to_string()) { - locally_matched = true; - } - } - if !locally_matched { - if let Some(ref compiled_pp) = self.schema.compiled_pattern_properties { - for (compiled_re, _) in compiled_pp { - if compiled_re.0.is_match(key) { - locally_matched = true; - break; - } - } - } - } - - if !locally_matched { - if let Some(child_instance) = self.instance.child_at_key(key) { - let new_path = format!("{}/{}", self.path, key); - let is_ref = additional_schema.ref_string.is_some() - || additional_schema.obj.dynamic_ref.is_some(); - let next_extensible = if is_ref { false } else { self.extensible }; - - let derived = self.derive( - additional_schema, - child_instance, - &new_path, - self.scope.clone(), - HashSet::new(), - next_extensible, - false, - ); - let item_res = derived.validate()?; - result.merge(item_res); - result.evaluated_keys.insert(key.to_string()); - } - } - } - } - - if let Some(ref property_names) = self.schema.property_names { - for key in obj.keys() { - let _new_path = format!("{}/propertyNames/{}", self.path, key); - let val_str = Value::String(key.to_string()); - - let ctx = ValidationContext::new( - self.validator, - self.root, - property_names, - crate::validator::ReadOnlyInstance(&val_str), - self.scope.clone(), - HashSet::new(), - self.extensible, - self.reporter, - ); - - result.merge(ctx.validate()?); - } - } - } - - if !self.extensible { - self.instance.prune_object(&result.evaluated_keys); - } - - Ok(()) - } - - pub(crate) fn validate_array( - &self, - result: &mut ValidationResult, - ) -> Result<(), ValidationError> { - let current = self.instance.as_value(); - if let Some(arr) = current.as_array() { - if let Some(min) = self.schema.min_items { - if (arr.len() as f64) < min { - result.errors.push(ValidationError { - code: "MIN_ITEMS".to_string(), - message: "Too few items".to_string(), - path: self.path.to_string(), - }); - } - } - if let Some(max) = self.schema.max_items { - if (arr.len() as f64) > max { - result.errors.push(ValidationError { - code: "MAX_ITEMS".to_string(), - message: "Too many items".to_string(), - path: self.path.to_string(), - }); - } - } - - if self.schema.unique_items.unwrap_or(false) { - let mut seen: Vec<&Value> = Vec::new(); - for item in arr { - if seen.contains(&item) { - result.errors.push(ValidationError { - code: "UNIQUE_ITEMS_VIOLATED".to_string(), - message: "Array has duplicate items".to_string(), - path: self.path.to_string(), - }); - break; - } - seen.push(item); - } - } - - if let Some(ref contains_schema) = self.schema.contains { - let mut _match_count = 0; - for i in 0..arr.len() { - if let Some(child_instance) = self.instance.child_at_index(i) { - let derived = self.derive( - contains_schema, - child_instance, - &self.path, - self.scope.clone(), - HashSet::new(), - self.extensible, - false, - ); - - let check = derived.validate()?; - if check.is_valid() { - _match_count += 1; - result.evaluated_indices.insert(i); - } - } - } - - let min = self.schema.min_contains.unwrap_or(1.0) as usize; - if _match_count < min { - result.errors.push(ValidationError { - code: "CONTAINS_VIOLATED".to_string(), - message: format!("Contains matches {} < min {}", _match_count, min), - path: self.path.to_string(), - }); - } - if let Some(max) = self.schema.max_contains { - if _match_count > max as usize { - result.errors.push(ValidationError { - code: "CONTAINS_VIOLATED".to_string(), - message: format!("Contains matches {} > max {}", _match_count, max), - path: self.path.to_string(), - }); - } - } - } - - let len = arr.len(); - let mut validation_index = 0; - - if let Some(ref prefix) = self.schema.prefix_items { - for (i, sub_schema) in prefix.iter().enumerate() { - if i < len { - let path = format!("{}/{}", self.path, i); - if let Some(child_instance) = self.instance.child_at_index(i) { - let derived = self.derive( - sub_schema, - child_instance, - &path, - self.scope.clone(), - HashSet::new(), - self.extensible, - false, - ); - let item_res = derived.validate()?; - result.merge(item_res); - result.evaluated_indices.insert(i); - validation_index += 1; - } - } - } - } - - if let Some(ref items_schema) = self.schema.items { - for i in validation_index..len { - let path = format!("{}/{}", self.path, i); - if let Some(child_instance) = self.instance.child_at_index(i) { - let derived = self.derive( - items_schema, - child_instance, - &path, - self.scope.clone(), - HashSet::new(), - self.extensible, - false, - ); - let item_res = derived.validate()?; - result.merge(item_res); - result.evaluated_indices.insert(i); - } - } - } - } - - if !self.extensible { - self.instance.prune_array(&result.evaluated_indices); - } - - Ok(()) - } - - pub(crate) fn validate_combinators( - &self, - result: &mut ValidationResult, - ) -> Result<(), ValidationError> { - if let Some(ref all_of) = self.schema.all_of { - for sub in all_of { - let derived = self.derive_for_schema(sub, true); - let res = derived.validate()?; - result.merge(res); - } - } - - if let Some(ref any_of) = self.schema.any_of { - let mut valid = false; - - for sub in any_of { - let derived = self.derive_for_schema(sub, true); - let sub_res = derived.validate()?; - if sub_res.is_valid() { - valid = true; - result.merge(sub_res); - } - } - - if !valid { - result.errors.push(ValidationError { - code: "ANY_OF_VIOLATED".to_string(), - message: "Matches none of anyOf schemas".to_string(), - path: self.path.to_string(), - }); - } - } - - if let Some(ref one_of) = self.schema.one_of { - let mut valid_count = 0; - let mut valid_res = ValidationResult::new(); - - for sub in one_of { - let derived = self.derive_for_schema(sub, true); - let sub_res = derived.validate()?; - if sub_res.is_valid() { - valid_count += 1; - valid_res = sub_res; - } - } - - if valid_count == 1 { - result.merge(valid_res); - } else if valid_count == 0 { - result.errors.push(ValidationError { - code: "ONE_OF_VIOLATED".to_string(), - message: "Matches none of oneOf schemas".to_string(), - path: self.path.to_string(), - }); - } else { - result.errors.push(ValidationError { - code: "ONE_OF_VIOLATED".to_string(), - message: format!("Matches {} of oneOf schemas (expected 1)", valid_count), - path: self.path.to_string(), - }); - } - } - - if let Some(ref not_schema) = self.schema.not { - let derived = self.derive_for_schema(not_schema, true); - let sub_res = derived.validate()?; - if sub_res.is_valid() { - result.errors.push(ValidationError { - code: "NOT_VIOLATED".to_string(), - message: "Matched 'not' schema".to_string(), - path: self.path.to_string(), - }); - } - } - - Ok(()) - } - - pub(crate) fn validate_conditionals( - &self, - result: &mut ValidationResult, - ) -> Result<(), ValidationError> { - if let Some(ref if_schema) = self.schema.if_ { - let derived_if = self.derive_for_schema(if_schema, true); - let if_res = derived_if.validate()?; - - result.evaluated_keys.extend(if_res.evaluated_keys.clone()); - result - .evaluated_indices - .extend(if_res.evaluated_indices.clone()); - - if if_res.is_valid() { - if let Some(ref then_schema) = self.schema.then_ { - let derived_then = self.derive_for_schema(then_schema, true); - result.merge(derived_then.validate()?); - } - } else { - if let Some(ref else_schema) = self.schema.else_ { - let derived_else = self.derive_for_schema(else_schema, true); - result.merge(derived_else.validate()?); - } - } - } - - Ok(()) - } - - pub(crate) fn validate_strictness(&self, result: &mut ValidationResult) { - if self.extensible || self.reporter { - return; - } - - if let Some(obj) = self.instance.as_value().as_object() { - for key in obj.keys() { - if !result.evaluated_keys.contains(key) && !self.overrides.contains(key) { - result.errors.push(ValidationError { - code: "STRICT_PROPERTY_VIOLATION".to_string(), - message: format!("Unexpected property '{}'", key), - path: format!("{}/{}", self.path, key), - }); - } - } - } - - if let Some(arr) = self.instance.as_value().as_array() { - for i in 0..arr.len() { - if !result.evaluated_indices.contains(&i) { - result.errors.push(ValidationError { - code: "STRICT_ITEM_VIOLATION".to_string(), - message: format!("Unexpected item at index {}", i), - path: format!("{}/{}", self.path, i), - }); - } - } - } - } -} diff --git a/src/validator/rules/array.rs b/src/validator/rules/array.rs new file mode 100644 index 0000000..821b97b --- /dev/null +++ b/src/validator/rules/array.rs @@ -0,0 +1,119 @@ +use serde_json::Value; +use std::collections::HashSet; + +use crate::validator::context::ValidationContext; +use crate::validator::error::ValidationError; +use crate::validator::result::ValidationResult; + +impl<'a> ValidationContext<'a> { + pub(crate) fn validate_array( + &self, + result: &mut ValidationResult, + ) -> Result { + let current = self.instance; + if let Some(arr) = current.as_array() { + if let Some(min) = self.schema.min_items { + if (arr.len() as f64) < min { + result.errors.push(ValidationError { + code: "MIN_ITEMS".to_string(), + message: "Too few items".to_string(), + path: self.path.to_string(), + }); + } + } + if let Some(max) = self.schema.max_items { + if (arr.len() as f64) > max { + result.errors.push(ValidationError { + code: "MAX_ITEMS".to_string(), + message: "Too many items".to_string(), + path: self.path.to_string(), + }); + } + } + + if self.schema.unique_items.unwrap_or(false) { + let mut seen: Vec<&Value> = Vec::new(); + for item in arr { + if seen.contains(&item) { + result.errors.push(ValidationError { + code: "UNIQUE_ITEMS_VIOLATED".to_string(), + message: "Array has duplicate items".to_string(), + path: self.path.to_string(), + }); + break; + } + seen.push(item); + } + } + + if let Some(ref contains_schema) = self.schema.contains { + let mut _match_count = 0; + for (i, child_instance) in arr.iter().enumerate() { + let derived = self.derive( + contains_schema, + child_instance, + &self.path, + self.extensible, + false, + ); + + let check = derived.validate()?; + if check.is_valid() { + _match_count += 1; + result.evaluated_indices.insert(i); + } + } + + let min = self.schema.min_contains.unwrap_or(1.0) as usize; + if _match_count < min { + result.errors.push(ValidationError { + code: "CONTAINS_VIOLATED".to_string(), + message: format!("Contains matches {} < min {}", _match_count, min), + path: self.path.to_string(), + }); + } + if let Some(max) = self.schema.max_contains { + if _match_count > max as usize { + result.errors.push(ValidationError { + code: "CONTAINS_VIOLATED".to_string(), + message: format!("Contains matches {} > max {}", _match_count, max), + path: self.path.to_string(), + }); + } + } + } + + let len = arr.len(); + let mut validation_index = 0; + + if let Some(ref prefix) = self.schema.prefix_items { + for (i, sub_schema) in prefix.iter().enumerate() { + if i < len { + let path = format!("{}/{}", self.path, i); + if let Some(child_instance) = arr.get(i) { + let derived = self.derive(sub_schema, child_instance, &path, self.extensible, false); + let item_res = derived.validate()?; + result.merge(item_res); + result.evaluated_indices.insert(i); + validation_index += 1; + } + } + } + } + + if let Some(ref items_schema) = self.schema.items { + for i in validation_index..len { + let path = format!("{}/{}", self.path, i); + if let Some(child_instance) = arr.get(i) { + let derived = self.derive(items_schema, child_instance, &path, self.extensible, false); + let item_res = derived.validate()?; + result.merge(item_res); + result.evaluated_indices.insert(i); + } + } + } + } + + Ok(true) + } +} diff --git a/src/validator/rules/combinators.rs b/src/validator/rules/combinators.rs new file mode 100644 index 0000000..8579e27 --- /dev/null +++ b/src/validator/rules/combinators.rs @@ -0,0 +1,83 @@ +use crate::validator::context::ValidationContext; +use crate::validator::error::ValidationError; +use crate::validator::result::ValidationResult; + +impl<'a> ValidationContext<'a> { + pub(crate) fn validate_combinators( + &self, + result: &mut ValidationResult, + ) -> Result { + if let Some(ref all_of) = self.schema.all_of { + for sub in all_of { + let derived = self.derive_for_schema(sub, true); + let res = derived.validate()?; + result.merge(res); + } + } + + if let Some(ref any_of) = self.schema.any_of { + let mut valid = false; + + for sub in any_of { + let derived = self.derive_for_schema(sub, true); + let sub_res = derived.validate()?; + if sub_res.is_valid() { + valid = true; + result.merge(sub_res); + } + } + + if !valid { + result.errors.push(ValidationError { + code: "ANY_OF_VIOLATED".to_string(), + message: "Matches none of anyOf schemas".to_string(), + path: self.path.to_string(), + }); + } + } + + if let Some(ref one_of) = self.schema.one_of { + let mut valid_count = 0; + let mut valid_res = ValidationResult::new(); + + for sub in one_of { + let derived = self.derive_for_schema(sub, true); + let sub_res = derived.validate()?; + if sub_res.is_valid() { + valid_count += 1; + valid_res = sub_res; + } + } + + if valid_count == 1 { + result.merge(valid_res); + } else if valid_count == 0 { + result.errors.push(ValidationError { + code: "ONE_OF_VIOLATED".to_string(), + message: "Matches none of oneOf schemas".to_string(), + path: self.path.to_string(), + }); + } else { + result.errors.push(ValidationError { + code: "ONE_OF_VIOLATED".to_string(), + message: format!("Matches {} of oneOf schemas (expected 1)", valid_count), + path: self.path.to_string(), + }); + } + } + + if let Some(ref not_schema) = self.schema.not { + let derived = self.derive_for_schema(not_schema, true); + let sub_res = derived.validate()?; + if sub_res.is_valid() { + result.errors.push(ValidationError { + code: "NOT_VIOLATED".to_string(), + message: "Matched 'not' schema".to_string(), + path: self.path.to_string(), + }); + } + } + + Ok(true) + } +} diff --git a/src/validator/rules/conditionals.rs b/src/validator/rules/conditionals.rs new file mode 100644 index 0000000..61195a7 --- /dev/null +++ b/src/validator/rules/conditionals.rs @@ -0,0 +1,69 @@ +use crate::validator::context::ValidationContext; +use crate::validator::error::ValidationError; +use crate::validator::result::ValidationResult; + +impl<'a> ValidationContext<'a> { + pub(crate) fn validate_conditionals( + &self, + result: &mut ValidationResult, + ) -> Result { + if let Some(ref if_schema) = self.schema.if_ { + let derived_if = self.derive_for_schema(if_schema, true); + let if_res = derived_if.validate()?; + + result.evaluated_keys.extend(if_res.evaluated_keys.clone()); + result + .evaluated_indices + .extend(if_res.evaluated_indices.clone()); + + if if_res.is_valid() { + if let Some(ref then_schema) = self.schema.then_ { + let derived_then = self.derive_for_schema(then_schema, true); + result.merge(derived_then.validate()?); + } + } else { + if let Some(ref else_schema) = self.schema.else_ { + let derived_else = self.derive_for_schema(else_schema, true); + result.merge(derived_else.validate()?); + } + } + } + + Ok(true) + } + + pub(crate) fn validate_strictness( + &self, + result: &mut ValidationResult, + ) -> Result { + if self.extensible || self.reporter { + return Ok(true); + } + + if let Some(obj) = self.instance.as_object() { + for key in obj.keys() { + if !result.evaluated_keys.contains(key) { + result.errors.push(ValidationError { + code: "STRICT_PROPERTY_VIOLATION".to_string(), + message: format!("Unexpected property '{}'", key), + path: format!("{}/{}", self.path, key), + }); + } + } + } + + if let Some(arr) = self.instance.as_array() { + for i in 0..arr.len() { + if !result.evaluated_indices.contains(&i) { + result.errors.push(ValidationError { + code: "STRICT_ITEM_VIOLATION".to_string(), + message: format!("Unexpected item at index {}", i), + path: format!("{}/{}", self.path, i), + }); + } + } + } + + Ok(true) + } +} diff --git a/src/validator/rules/core.rs b/src/validator/rules/core.rs new file mode 100644 index 0000000..be7126d --- /dev/null +++ b/src/validator/rules/core.rs @@ -0,0 +1,84 @@ +use crate::validator::Validator; +use crate::validator::context::ValidationContext; +use crate::validator::error::ValidationError; +use crate::validator::result::ValidationResult; + +impl<'a> ValidationContext<'a> { + pub(crate) fn validate_core( + &self, + result: &mut ValidationResult, + ) -> Result { + let current = self.instance; + + if let Some(ref type_) = self.schema.type_ { + match type_ { + crate::database::schema::SchemaTypeOrArray::Single(t) => { + if !Validator::check_type(t, current) { + result.errors.push(ValidationError { + code: "INVALID_TYPE".to_string(), + message: format!("Expected type '{}'", t), + path: self.path.to_string(), + }); + } + } + crate::database::schema::SchemaTypeOrArray::Multiple(types) => { + let mut valid = false; + for t in types { + if Validator::check_type(t, current) { + valid = true; + break; + } + } + if !valid { + result.errors.push(ValidationError { + code: "INVALID_TYPE".to_string(), + message: format!("Expected one of types {:?}", types), + path: self.path.to_string(), + }); + } + } + } + } + + if let Some(ref const_val) = self.schema.const_ { + if !crate::validator::util::equals(current, const_val) { + result.errors.push(ValidationError { + code: "CONST_VIOLATED".to_string(), + message: "Value does not match const".to_string(), + path: self.path.to_string(), + }); + } else { + if let Some(obj) = current.as_object() { + result.evaluated_keys.extend(obj.keys().cloned()); + } else if let Some(arr) = current.as_array() { + result.evaluated_indices.extend(0..arr.len()); + } + } + } + + if let Some(ref enum_vals) = self.schema.enum_ { + let mut found = false; + for val in enum_vals { + if crate::validator::util::equals(current, val) { + found = true; + break; + } + } + if !found { + result.errors.push(ValidationError { + code: "ENUM_MISMATCH".to_string(), + message: "Value is not in enum".to_string(), + path: self.path.to_string(), + }); + } else { + if let Some(obj) = current.as_object() { + result.evaluated_keys.extend(obj.keys().cloned()); + } else if let Some(arr) = current.as_array() { + result.evaluated_indices.extend(0..arr.len()); + } + } + } + + Ok(true) + } +} diff --git a/src/validator/rules/format.rs b/src/validator/rules/format.rs new file mode 100644 index 0000000..07185f8 --- /dev/null +++ b/src/validator/rules/format.rs @@ -0,0 +1,44 @@ +use crate::validator::context::ValidationContext; +use crate::validator::error::ValidationError; +use crate::validator::result::ValidationResult; + +impl<'a> ValidationContext<'a> { + pub(crate) fn validate_format( + &self, + result: &mut ValidationResult, + ) -> Result { + let current = self.instance; + if let Some(ref compiled_fmt) = self.schema.compiled_format { + match compiled_fmt { + crate::database::schema::CompiledFormat::Func(f) => { + let should = if let Some(s) = current.as_str() { + !s.is_empty() + } else { + true + }; + if should { + if let Err(e) = f(current) { + result.errors.push(ValidationError { + code: "FORMAT_MISMATCH".to_string(), + message: format!("Format error: {}", e), + path: self.path.to_string(), + }); + } + } + } + crate::database::schema::CompiledFormat::Regex(re) => { + if let Some(s) = current.as_str() { + if !re.is_match(s) { + result.errors.push(ValidationError { + code: "FORMAT_MISMATCH".to_string(), + message: "Format regex mismatch".to_string(), + path: self.path.to_string(), + }); + } + } + } + } + } + Ok(true) + } +} diff --git a/src/validator/rules/mod.rs b/src/validator/rules/mod.rs new file mode 100644 index 0000000..f10bfac --- /dev/null +++ b/src/validator/rules/mod.rs @@ -0,0 +1,93 @@ +use serde_json::Value; +use std::collections::HashSet; + +use crate::validator::context::ValidationContext; +use crate::validator::error::ValidationError; +use crate::validator::result::ValidationResult; + +pub mod array; +pub mod combinators; +pub mod conditionals; +pub mod core; +pub mod format; +pub mod numeric; +pub mod object; +pub mod polymorphism; +pub mod string; + +impl<'a> ValidationContext<'a> { + pub(crate) fn validate_scoped(&self) -> Result { + let mut result = ValidationResult::new(); + + // Structural Limits + if !self.validate_depth(&mut result)? { + return Ok(result); + } + if !self.validate_always_fail(&mut result)? { + return Ok(result); + } + if !self.validate_family(&mut result)? { + return Ok(result); + } + if !self.validate_refs(&mut result)? { + return Ok(result); + } + + // Core Type Constraints + self.validate_core(&mut result)?; + self.validate_numeric(&mut result)?; + self.validate_string(&mut result)?; + self.validate_format(&mut result)?; + + // Complex Structures + self.validate_object(&mut result)?; + self.validate_array(&mut result)?; + + // Multipliers & Conditionals + self.validate_combinators(&mut result)?; + self.validate_conditionals(&mut result)?; + + // State Tracking + self.validate_extensible(&mut result)?; + self.validate_strictness(&mut result)?; + + Ok(result) + } + + fn validate_depth(&self, _result: &mut ValidationResult) -> Result { + if self.depth > 100 { + Err(ValidationError { + code: "RECURSION_LIMIT_EXCEEDED".to_string(), + message: "Recursion limit exceeded".to_string(), + path: self.path.to_string(), + }) + } else { + Ok(true) + } + } + + fn validate_always_fail(&self, result: &mut ValidationResult) -> Result { + if self.schema.always_fail { + result.errors.push(ValidationError { + code: "FALSE_SCHEMA".to_string(), + message: "Schema is false".to_string(), + path: self.path.to_string(), + }); + // Short-circuit + Ok(false) + } else { + Ok(true) + } + } + + fn validate_extensible(&self, result: &mut ValidationResult) -> Result { + if self.extensible { + if let Some(obj) = self.instance.as_object() { + result.evaluated_keys.extend(obj.keys().cloned()); + } else if let Some(arr) = self.instance.as_array() { + result.evaluated_indices.extend(0..arr.len()); + } + } + Ok(true) + } +} diff --git a/src/validator/rules/numeric.rs b/src/validator/rules/numeric.rs new file mode 100644 index 0000000..4926a36 --- /dev/null +++ b/src/validator/rules/numeric.rs @@ -0,0 +1,61 @@ +use crate::validator::context::ValidationContext; +use crate::validator::error::ValidationError; +use crate::validator::result::ValidationResult; + +impl<'a> ValidationContext<'a> { + pub(crate) fn validate_numeric( + &self, + result: &mut ValidationResult, + ) -> Result { + let current = self.instance; + if let Some(num) = current.as_f64() { + if let Some(min) = self.schema.minimum { + if num < min { + result.errors.push(ValidationError { + code: "MINIMUM_VIOLATED".to_string(), + message: format!("Value {} < min {}", num, min), + path: self.path.to_string(), + }); + } + } + if let Some(max) = self.schema.maximum { + if num > max { + result.errors.push(ValidationError { + code: "MAXIMUM_VIOLATED".to_string(), + message: format!("Value {} > max {}", num, max), + path: self.path.to_string(), + }); + } + } + if let Some(ex_min) = self.schema.exclusive_minimum { + if num <= ex_min { + result.errors.push(ValidationError { + code: "EXCLUSIVE_MINIMUM_VIOLATED".to_string(), + message: format!("Value {} <= ex_min {}", num, ex_min), + path: self.path.to_string(), + }); + } + } + if let Some(ex_max) = self.schema.exclusive_maximum { + if num >= ex_max { + result.errors.push(ValidationError { + code: "EXCLUSIVE_MAXIMUM_VIOLATED".to_string(), + message: format!("Value {} >= ex_max {}", num, ex_max), + path: self.path.to_string(), + }); + } + } + if let Some(multiple_of) = self.schema.multiple_of { + let val: f64 = num / multiple_of; + if (val - val.round()).abs() > f64::EPSILON { + result.errors.push(ValidationError { + code: "MULTIPLE_OF_VIOLATED".to_string(), + message: format!("Value {} not multiple of {}", num, multiple_of), + path: self.path.to_string(), + }); + } + } + } + Ok(true) + } +} diff --git a/src/validator/rules/object.rs b/src/validator/rules/object.rs new file mode 100644 index 0000000..a70432b --- /dev/null +++ b/src/validator/rules/object.rs @@ -0,0 +1,183 @@ +use serde_json::Value; +use std::collections::HashSet; + +use crate::validator::context::ValidationContext; +use crate::validator::error::ValidationError; +use crate::validator::result::ValidationResult; + +impl<'a> ValidationContext<'a> { + pub(crate) fn validate_object( + &self, + result: &mut ValidationResult, + ) -> Result { + let current = self.instance; + if let Some(obj) = current.as_object() { + // Entity Bound Implicit Type Validation + if let Some(allowed_types) = &self.schema.obj.compiled_allowed_types { + if let Some(type_val) = obj.get("type") { + if let Some(type_str) = type_val.as_str() { + if allowed_types.contains(type_str) { + // Ensure it passes strict mode + result.evaluated_keys.insert("type".to_string()); + } else { + result.errors.push(ValidationError { + code: "CONST_VIOLATED".to_string(), // Aligning with original const override errors + message: format!( + "Type '{}' is not a valid descendant for this entity bound schema", + type_str + ), + path: format!("{}/type", self.path), + }); + } + } + } + } + if let Some(min) = self.schema.min_properties { + if (obj.len() as f64) < min { + result.errors.push(ValidationError { + code: "MIN_PROPERTIES".to_string(), + message: "Too few properties".to_string(), + path: self.path.to_string(), + }); + } + } + if let Some(max) = self.schema.max_properties { + if (obj.len() as f64) > max { + result.errors.push(ValidationError { + code: "MAX_PROPERTIES".to_string(), + message: "Too many properties".to_string(), + path: self.path.to_string(), + }); + } + } + if let Some(ref req) = self.schema.required { + for field in req { + if !obj.contains_key(field) { + result.errors.push(ValidationError { + code: "REQUIRED_FIELD_MISSING".to_string(), + message: format!("Missing {}", field), + path: format!("{}/{}", self.path, field), + }); + } + } + } + + if let Some(props) = &self.schema.properties { + for (key, sub_schema) in props { + if let Some(child_instance) = obj.get(key) { + let new_path = format!("{}/{}", self.path, key); + let is_ref = sub_schema.ref_string.is_some() || sub_schema.obj.compiled_ref.is_some(); + let next_extensible = if is_ref { false } else { self.extensible }; + + let derived = self.derive( + sub_schema, + child_instance, + &new_path, + next_extensible, + false, + ); + let mut item_res = derived.validate()?; + + // Entity Bound Implicit Type Interception + if key == "type" { + if let Some(allowed_types) = &self.schema.obj.compiled_allowed_types { + if let Some(instance_type) = child_instance.as_str() { + if allowed_types.contains(instance_type) { + item_res + .errors + .retain(|e| e.code != "CONST_VIOLATED" && e.code != "ENUM_VIOLATED"); + } + } + } + } + + result.merge(item_res); + result.evaluated_keys.insert(key.to_string()); + } + } + } + + if let Some(ref compiled_pp) = self.schema.compiled_pattern_properties { + for (compiled_re, sub_schema) in compiled_pp { + for (key, child_instance) in obj { + if compiled_re.0.is_match(key) { + let new_path = format!("{}/{}", self.path, key); + let is_ref = sub_schema.ref_string.is_some() || sub_schema.obj.compiled_ref.is_some(); + let next_extensible = if is_ref { false } else { self.extensible }; + + let derived = self.derive( + sub_schema, + child_instance, + &new_path, + next_extensible, + false, + ); + let item_res = derived.validate()?; + result.merge(item_res); + result.evaluated_keys.insert(key.to_string()); + } + } + } + } + + if let Some(ref additional_schema) = self.schema.additional_properties { + for (key, child_instance) in obj { + let mut locally_matched = false; + if let Some(props) = &self.schema.properties { + if props.contains_key(&key.to_string()) { + locally_matched = true; + } + } + if !locally_matched { + if let Some(ref compiled_pp) = self.schema.compiled_pattern_properties { + for (compiled_re, _) in compiled_pp { + if compiled_re.0.is_match(key) { + locally_matched = true; + break; + } + } + } + } + + if !locally_matched { + let new_path = format!("{}/{}", self.path, key); + let is_ref = additional_schema.ref_string.is_some() + || additional_schema.obj.compiled_ref.is_some(); + let next_extensible = if is_ref { false } else { self.extensible }; + + let derived = self.derive( + additional_schema, + child_instance, + &new_path, + next_extensible, + false, + ); + let item_res = derived.validate()?; + result.merge(item_res); + result.evaluated_keys.insert(key.to_string()); + } + } + } + + if let Some(ref property_names) = self.schema.property_names { + for key in obj.keys() { + let _new_path = format!("{}/propertyNames/{}", self.path, key); + let val_str = Value::String(key.to_string()); + + let ctx = ValidationContext::new( + self.schemas, + self.root, + property_names, + &val_str, + self.extensible, + self.reporter, + ); + + result.merge(ctx.validate()?); + } + } + } + + Ok(true) + } +} diff --git a/src/validator/rules/polymorphism.rs b/src/validator/rules/polymorphism.rs new file mode 100644 index 0000000..b099e70 --- /dev/null +++ b/src/validator/rules/polymorphism.rs @@ -0,0 +1,64 @@ +use crate::validator::context::ValidationContext; +use crate::validator::error::ValidationError; +use crate::validator::result::ValidationResult; + +impl<'a> ValidationContext<'a> { + pub(crate) fn validate_family( + &self, + result: &mut ValidationResult, + ) -> Result { + if self.schema.family.is_some() { + let conflicts = self.schema.type_.is_some() + || self.schema.properties.is_some() + || self.schema.required.is_some() + || self.schema.additional_properties.is_some() + || self.schema.items.is_some() + || self.schema.ref_string.is_some() + || self.schema.one_of.is_some() + || self.schema.any_of.is_some() + || self.schema.all_of.is_some() + || self.schema.enum_.is_some() + || self.schema.const_.is_some(); + + if conflicts { + result.errors.push(ValidationError { + code: "INVALID_SCHEMA".to_string(), + message: "$family must be used exclusively without other constraints".to_string(), + path: self.path.to_string(), + }); + // Short-circuit: the schema formulation is broken + return Ok(false); + } + } + + // Family specific runtime validation will go here later if needed + Ok(true) + } + + pub(crate) fn validate_refs( + &self, + result: &mut ValidationResult, + ) -> Result { + // 1. Core $ref logic fully transitioned to memory pointer resolutions. + if let Some(_ref_str) = &self.schema.ref_string { + if let Some(global_schema) = &self.schema.compiled_ref { + let mut shadow = self.derive( + global_schema, + self.instance, + &self.path, + self.extensible, + false, + ); + shadow.root = global_schema; + result.merge(shadow.validate()?); + } else { + result.errors.push(ValidationError { + code: "REF_RESOLUTION_FAILED".to_string(), + message: format!("Reference pointer was not compiled inside Database graph"), + path: self.path.to_string(), + }); + } + } + Ok(true) + } +} diff --git a/src/validator/rules/string.rs b/src/validator/rules/string.rs new file mode 100644 index 0000000..6c5a936 --- /dev/null +++ b/src/validator/rules/string.rs @@ -0,0 +1,53 @@ +use crate::validator::context::ValidationContext; +use crate::validator::error::ValidationError; +use crate::validator::result::ValidationResult; +use regex::Regex; + +impl<'a> ValidationContext<'a> { + pub(crate) fn validate_string( + &self, + result: &mut ValidationResult, + ) -> Result { + let current = self.instance; + if let Some(s) = current.as_str() { + if let Some(min) = self.schema.min_length { + if (s.chars().count() as f64) < min { + result.errors.push(ValidationError { + code: "MIN_LENGTH_VIOLATED".to_string(), + message: format!("Length < min {}", min), + path: self.path.to_string(), + }); + } + } + if let Some(max) = self.schema.max_length { + if (s.chars().count() as f64) > max { + result.errors.push(ValidationError { + code: "MAX_LENGTH_VIOLATED".to_string(), + message: format!("Length > max {}", max), + path: self.path.to_string(), + }); + } + } + if let Some(ref compiled_re) = self.schema.compiled_pattern { + if !compiled_re.0.is_match(s) { + result.errors.push(ValidationError { + code: "PATTERN_VIOLATED".to_string(), + message: format!("Pattern mismatch {:?}", self.schema.pattern), + path: self.path.to_string(), + }); + } + } else if let Some(ref pattern) = self.schema.pattern { + if let Ok(re) = Regex::new(pattern) { + if !re.is_match(s) { + result.errors.push(ValidationError { + code: "PATTERN_VIOLATED".to_string(), + message: format!("Pattern mismatch {}", pattern), + path: self.path.to_string(), + }); + } + } + } + } + Ok(true) + } +} diff --git a/src/validator/util.rs b/src/validator/util.rs index 7ea4925..64cc8b8 100644 --- a/src/validator/util.rs +++ b/src/validator/util.rs @@ -5,11 +5,7 @@ use std::fs; struct TestSuite { #[allow(dead_code)] description: String, - schema: Option, - // Support JSPG-style test suites with explicit types/enums/puncs - types: Option, - enums: Option, - puncs: Option, + database: serde_json::Value, tests: Vec, } @@ -20,9 +16,6 @@ struct TestCase { valid: bool, // Support explicit schema ID target for test case schema_id: Option, - // Expected output for masking tests - #[allow(dead_code)] - expected: Option, } // use crate::validator::registry::REGISTRY; // No longer used directly for tests! @@ -50,64 +43,34 @@ pub fn run_test_file_at_index(path: &str, index: usize) -> Result<(), String> { let group = &suite[index]; let mut failures = Vec::::new(); - // Create Validator Instance and parse enums, types, and puncs automatically - let mut validator = Validator::from_punc_definition( - group.enums.as_ref(), - group.types.as_ref(), - group.puncs.as_ref(), - ); - - // 3. Register root 'schemas' if present (generic test support) - // Some tests use a raw 'schema' or 'schemas' field at the group level - if let Some(schema_val) = &group.schema { - match serde_json::from_value::(schema_val.clone()) { - Ok(mut schema) => { - let id_clone = schema.obj.id.clone(); - if id_clone.is_some() { - validator.registry.add(schema); - } else { - // Fallback ID if none provided in schema - let id = format!("test:{}:{}", path, index); - schema.obj.id = Some(id); - validator.registry.add(schema); - } - } - Err(e) => { - eprintln!( - "DEBUG: FAILED to deserialize group schema for index {}: {}", - index, e - ); - } - } - } + let db_json = group.database.clone(); + let db = crate::database::Database::new(&db_json); + let validator = Validator::new(std::sync::Arc::new(db.schemas)); // 4. Run Tests for (_test_index, test) in group.tests.iter().enumerate() { let mut schema_id = test.schema_id.clone(); - // If no explicit schema_id, try to infer from the single schema in the group + // If no explicit schema_id, infer from the database structure if schema_id.is_none() { - if let Some(s) = &group.schema { - // If 'schema' is a single object, use its ID or "root" - if let Some(obj) = s.as_object() { - if let Some(id_val) = obj.get("$id") { - schema_id = id_val.as_str().map(|s| s.to_string()); + if let Some(schemas) = db_json.get("schemas").and_then(|v| v.as_array()) { + if let Some(first) = schemas.first() { + if let Some(id) = first.get("$id").and_then(|v| v.as_str()) { + schema_id = Some(id.to_string()); + } else { + schema_id = Some("schema_0".to_string()); } } - if schema_id.is_none() { - schema_id = Some(format!("test:{}:{}", path, index)); - } } - } - // Default to the first punc if present (for puncs.json style) - if schema_id.is_none() { - if let Some(Value::Array(puncs)) = &group.puncs { - if let Some(first_punc) = puncs.first() { - if let Some(Value::Array(schemas)) = first_punc.get("schemas") { - if let Some(first_schema) = schemas.first() { - if let Some(id) = first_schema.get("$id").and_then(|v| v.as_str()) { - schema_id = Some(id.to_string()); + if schema_id.is_none() { + if let Some(puncs) = db_json.get("puncs").and_then(|v| v.as_array()) { + if let Some(first_punc) = puncs.first() { + if let Some(schemas) = first_punc.get("schemas").and_then(|v| v.as_array()) { + if let Some(first) = schemas.first() { + if let Some(id) = first.get("$id").and_then(|v| v.as_str()) { + schema_id = Some(id.to_string()); + } } } } @@ -127,42 +90,16 @@ pub fn run_test_file_at_index(path: &str, index: usize) -> Result<(), String> { } }; - if let Some(expected) = &test.expected { - // Masking Test - let mut data_for_mask = test.data.clone(); - match validator.mask(&sid, &mut data_for_mask) { - Ok(_) => { - if !equals(&data_for_mask, expected) { - let msg = format!( - "Masking Test '{}' failed.\nExpected: {:?}\nGot: {:?}", - test.description, expected, data_for_mask - ); - eprintln!("{}", msg); - failures.push(msg); - } - } - Err(e) => { - let msg = format!( - "Masking Test '{}' failed with execution error: {:?}", - test.description, e - ); - eprintln!("{}", msg); - failures.push(msg); - } - } - } else { - // Standard Validation Test - if got_valid != test.valid { - let error_msg = match &result { - Ok(res) => format!("{:?}", res.errors), - Err(e) => format!("Execution Error: {:?}", e), - }; + if got_valid != test.valid { + let error_msg = match &result { + Ok(res) => format!("{:?}", res.errors), + Err(e) => format!("Execution Error: {:?}", e), + }; - failures.push(format!( - "[{}] Test '{}' failed. Expected: {}, Got: {}. Errors: {}", - group.description, test.description, test.valid, got_valid, error_msg - )); - } + failures.push(format!( + "[{}] Test '{}' failed. Expected: {}, Got: {}. Errors: {}", + group.description, test.description, test.valid, got_valid, error_msg + )); } } else { failures.push(format!( @@ -178,96 +115,6 @@ pub fn run_test_file_at_index(path: &str, index: usize) -> Result<(), String> { Ok(()) } -pub fn run_test_file(path: &str) -> Result<(), String> { - let content = - fs::read_to_string(path).unwrap_or_else(|_| panic!("Failed to read file: {}", path)); - let suite: Vec = serde_json::from_str(&content) - .unwrap_or_else(|e| panic!("Failed to parse JSON in {}: {}", path, e)); - - let mut failures = Vec::::new(); - for (group_index, group) in suite.into_iter().enumerate() { - // Create Validator Instance and parse enums, types, and puncs automatically - let mut validator = Validator::from_punc_definition( - group.enums.as_ref(), - group.types.as_ref(), - group.puncs.as_ref(), - ); - - let unique_id = format!("test:{}:{}", path, group_index); - - // Register main 'schema' if present (Standard style) - if let Some(ref schema_val) = group.schema { - let mut schema: crate::validator::schema::Schema = - serde_json::from_value(schema_val.clone()).expect("Failed to parse test schema"); - - // If schema has no ID, assign unique_id and use add() or manual insert? - // Compiler needs ID. Registry::add needs ID. - if schema.obj.id.is_none() { - schema.obj.id = Some(unique_id.clone()); - } - validator.registry.add(schema); - } - - for test in group.tests { - // Use explicit schema_id from test, or default to unique_id - let schema_id = test.schema_id.as_deref().unwrap_or(&unique_id).to_string(); - - let result = validator.validate(&schema_id, &test.data); - - if test.valid { - match result { - Ok(res) => { - if !res.is_valid() { - let msg = format!( - "Test failed (expected valid): {}\nSchema: {:?}\nData: {:?}\nErrors: {:?}", - test.description, - group.schema, // We might need to find the actual schema used if schema_id is custom - test.data, - res.errors - ); - eprintln!("{}", msg); - failures.push(msg); - } - } - Err(e) => { - let msg = format!( - "Test failed (expected valid) but got execution error: {}\nSchema: {:?}\nData: {:?}\nError: {:?}", - test.description, group.schema, test.data, e - ); - eprintln!("{}", msg); - failures.push(msg); - } - } - } else { - match result { - Ok(res) => { - if res.is_valid() { - let msg = format!( - "Test failed (expected invalid): {}\nSchema: {:?}\nData: {:?}", - test.description, group.schema, test.data - ); - eprintln!("{}", msg); - failures.push(msg); - } - } - Err(_) => { - // Expected invalid, got error (which implies invalid/failure), so this is PASS. - } - } - } - } - } - - if !failures.is_empty() { - return Err(format!( - "{} tests failed in file {}:\n\n{}", - failures.len(), - path, - failures.join("\n\n") - )); - } - Ok(()) -} pub fn is_integer(v: &Value) -> bool { match v { diff --git a/test_err.log b/test_err.log new file mode 100644 index 0000000..a4eb245 --- /dev/null +++ b/test_err.log @@ -0,0 +1,62 @@ + Compiling jspg v0.1.0 (/Users/awgneo/Repositories/thoughtpatterns/cellular/jspg) + Finished `test` profile [unoptimized + debuginfo] target(s) in 26.14s + Running unittests src/lib.rs (target/debug/deps/jspg-99ace086c3537f5a) + +running 1 test + Using PgConfig("pg18") and `pg_config` from /opt/homebrew/opt/postgresql@18/bin/pg_config + Building extension with features pg_test pg18 + Running command "/opt/homebrew/bin/cargo" "build" "--lib" "--features" "pg_test pg18" "--no-default-features" "--message-format=json-render-diagnostics" + Compiling jspg v0.1.0 (/Users/awgneo/Repositories/thoughtpatterns/cellular/jspg) + Finished `dev` profile [unoptimized + debuginfo] target(s) in 7.10s + Installing extension + Copying control file to /opt/homebrew/share/postgresql@18/extension/jspg.control + Copying shared library to /opt/homebrew/lib/postgresql@18/jspg.dylib + Discovered 351 SQL entities: 1 schemas (1 unique), 350 functions, 0 types, 0 enums, 0 sqls, 0 ords, 0 hashes, 0 aggregates, 0 triggers + Rebuilding pgrx_embed, in debug mode, for SQL generation with features pg_test pg18 + Compiling jspg v0.1.0 (/Users/awgneo/Repositories/thoughtpatterns/cellular/jspg) + Finished `dev` profile [unoptimized + debuginfo] target(s) in 10.63s + Writing SQL entities to /opt/homebrew/share/postgresql@18/extension/jspg--0.1.0.sql + Finished installing jspg +[2026-03-01 22:54:19.068 EST] [82952] [69a509eb.14408]: LOG: starting PostgreSQL 18.1 (Homebrew) on aarch64-apple-darwin25.2.0, compiled by Apple clang version 17.0.0 (clang-1700.6.3.2), 64-bit +[2026-03-01 22:54:19.070 EST] [82952] [69a509eb.14408]: LOG: listening on IPv6 address "::1", port 32218 +[2026-03-01 22:54:19.070 EST] [82952] [69a509eb.14408]: LOG: listening on IPv4 address "127.0.0.1", port 32218 +[2026-03-01 22:54:19.071 EST] [82952] [69a509eb.14408]: LOG: listening on Unix socket "/Users/awgneo/Repositories/thoughtpatterns/cellular/jspg/target/test-pgdata/.s.PGSQL.32218" +[2026-03-01 22:54:19.077 EST] [82958] [69a509eb.1440e]: LOG: database system was shut down at 2026-03-01 22:49:02 EST + Creating database pgrx_tests + +thread 'tests::pg_test_typed_refs_0' (29092254) panicked at /Users/awgneo/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/pgrx-tests-0.16.1/src/framework.rs:166:9: + + +Postgres Messages: +[2026-03-01 22:54:19.068 EST] [82952] [69a509eb.14408]: LOG: starting PostgreSQL 18.1 (Homebrew) on aarch64-apple-darwin25.2.0, compiled by Apple clang version 17.0.0 (clang-1700.6.3.2), 64-bit +[2026-03-01 22:54:19.070 EST] [82952] [69a509eb.14408]: LOG: listening on IPv6 address "::1", port 32218 +[2026-03-01 22:54:19.070 EST] [82952] [69a509eb.14408]: LOG: listening on IPv4 address "127.0.0.1", port 32218 +[2026-03-01 22:54:19.071 EST] [82952] [69a509eb.14408]: LOG: listening on Unix socket "/Users/awgneo/Repositories/thoughtpatterns/cellular/jspg/target/test-pgdata/.s.PGSQL.32218" +[2026-03-01 22:54:19.081 EST] [82952] [69a509eb.14408]: LOG: database system is ready to accept connections + + +Test Function Messages: +[2026-03-01 22:54:20.058 EST] [82982] [69a509ec.14426]: LOG: statement: START TRANSACTION +[2026-03-01 22:54:20.058 EST] [82982] [69a509ec.14426]: LOG: statement: SELECT "tests"."test_typed_refs_0"(); +[2026-03-01 22:54:20.062 EST] [82982] [69a509ec.14426]: ERROR: called `Result::unwrap()` on an `Err` value: "[Entity inheritance and native type discrimination] Test 'Valid person against organization schema (implicit type allowance)' failed. Expected: true, Got: false. Errors: [ValidationError { code: \"CONST_VIOLATED\", message: \"Value does not match const\", path: \"/type\" }, ValidationError { code: \"STRICT_PROPERTY_VIOLATION\", message: \"Unexpected property 'first_name'\", path: \"/first_name\" }, ValidationError { code: \"STRICT_PROPERTY_VIOLATION\", message: \"Unexpected property 'first_name'\", path: \"/first_name\" }, ValidationError { code: \"STRICT_PROPERTY_VIOLATION\", message: \"Unexpected property 'first_name'\", path: \"/first_name\" }]\n[Entity inheritance and native type discrimination] Test 'Valid organization against organization schema' failed. Expected: true, Got: false. Errors: [ValidationError { code: \"CONST_VIOLATED\", message: \"Value does not match const\", path: \"/type\" }]\n[Entity inheritance and native type discrimination] Test 'Invalid entity against organization schema (ancestor not allowed)' failed. Expected: false, Got: true. Errors: []" +[2026-03-01 22:54:20.062 EST] [82982] [69a509ec.14426]: STATEMENT: SELECT "tests"."test_typed_refs_0"(); +[2026-03-01 22:54:20.062 EST] [82982] [69a509ec.14426]: LOG: statement: ROLLBACK + + +Client Error: +called `Result::unwrap()` on an `Err` value: "[Entity inheritance and native type discrimination] Test 'Valid person against organization schema (implicit type allowance)' failed. Expected: true, Got: false. Errors: [ValidationError { code: \"CONST_VIOLATED\", message: \"Value does not match const\", path: \"/type\" }, ValidationError { code: \"STRICT_PROPERTY_VIOLATION\", message: \"Unexpected property 'first_name'\", path: \"/first_name\" }, ValidationError { code: \"STRICT_PROPERTY_VIOLATION\", message: \"Unexpected property 'first_name'\", path: \"/first_name\" }, ValidationError { code: \"STRICT_PROPERTY_VIOLATION\", message: \"Unexpected property 'first_name'\", path: \"/first_name\" }]\n[Entity inheritance and native type discrimination] Test 'Valid organization against organization schema' failed. Expected: true, Got: false. Errors: [ValidationError { code: \"CONST_VIOLATED\", message: \"Value does not match const\", path: \"/type\" }]\n[Entity inheritance and native type discrimination] Test 'Invalid entity against organization schema (ancestor not allowed)' failed. Expected: false, Got: true. Errors: []" +postgres location: fixtures.rs +rust location:  + + +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace +test tests::pg_test_typed_refs_0 ... FAILED + +failures: + +failures: + tests::pg_test_typed_refs_0 + +test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 343 filtered out; finished in 21.82s + +error: test failed, to rerun pass `--lib` diff --git a/tests/fixtures.rs b/tests/fixtures.rs index 1e2c59f..57f5574 100644 --- a/tests/fixtures.rs +++ b/tests/fixtures.rs @@ -1,29 +1,5 @@ use jspg::validator::util; -#[test] -fn test_anchor_0() { - let path = format!("{}/tests/fixtures/anchor.json", env!("CARGO_MANIFEST_DIR")); - util::run_test_file_at_index(&path, 0).unwrap(); -} - -#[test] -fn test_anchor_1() { - let path = format!("{}/tests/fixtures/anchor.json", env!("CARGO_MANIFEST_DIR")); - util::run_test_file_at_index(&path, 1).unwrap(); -} - -#[test] -fn test_anchor_2() { - let path = format!("{}/tests/fixtures/anchor.json", env!("CARGO_MANIFEST_DIR")); - util::run_test_file_at_index(&path, 2).unwrap(); -} - -#[test] -fn test_anchor_3() { - let path = format!("{}/tests/fixtures/anchor.json", env!("CARGO_MANIFEST_DIR")); - util::run_test_file_at_index(&path, 3).unwrap(); -} - #[test] fn test_content_0() { let path = format!("{}/tests/fixtures/content.json", env!("CARGO_MANIFEST_DIR")); @@ -108,54 +84,6 @@ fn test_min_items_2() { util::run_test_file_at_index(&path, 2).unwrap(); } -#[test] -fn test_puncs_0() { - let path = format!("{}/tests/fixtures/puncs.json", env!("CARGO_MANIFEST_DIR")); - util::run_test_file_at_index(&path, 0).unwrap(); -} - -#[test] -fn test_puncs_1() { - let path = format!("{}/tests/fixtures/puncs.json", env!("CARGO_MANIFEST_DIR")); - util::run_test_file_at_index(&path, 1).unwrap(); -} - -#[test] -fn test_puncs_2() { - let path = format!("{}/tests/fixtures/puncs.json", env!("CARGO_MANIFEST_DIR")); - util::run_test_file_at_index(&path, 2).unwrap(); -} - -#[test] -fn test_puncs_3() { - let path = format!("{}/tests/fixtures/puncs.json", env!("CARGO_MANIFEST_DIR")); - util::run_test_file_at_index(&path, 3).unwrap(); -} - -#[test] -fn test_puncs_4() { - let path = format!("{}/tests/fixtures/puncs.json", env!("CARGO_MANIFEST_DIR")); - util::run_test_file_at_index(&path, 4).unwrap(); -} - -#[test] -fn test_puncs_5() { - let path = format!("{}/tests/fixtures/puncs.json", env!("CARGO_MANIFEST_DIR")); - util::run_test_file_at_index(&path, 5).unwrap(); -} - -#[test] -fn test_puncs_6() { - let path = format!("{}/tests/fixtures/puncs.json", env!("CARGO_MANIFEST_DIR")); - util::run_test_file_at_index(&path, 6).unwrap(); -} - -#[test] -fn test_puncs_7() { - let path = format!("{}/tests/fixtures/puncs.json", env!("CARGO_MANIFEST_DIR")); - util::run_test_file_at_index(&path, 7).unwrap(); -} - #[test] fn test_additional_properties_0() { let path = format!("{}/tests/fixtures/additionalProperties.json", env!("CARGO_MANIFEST_DIR")); @@ -348,6 +276,18 @@ fn test_any_of_9() { util::run_test_file_at_index(&path, 9).unwrap(); } +#[test] +fn test_families_0() { + let path = format!("{}/tests/fixtures/families.json", env!("CARGO_MANIFEST_DIR")); + util::run_test_file_at_index(&path, 0).unwrap(); +} + +#[test] +fn test_families_1() { + let path = format!("{}/tests/fixtures/families.json", env!("CARGO_MANIFEST_DIR")); + util::run_test_file_at_index(&path, 1).unwrap(); +} + #[test] fn test_property_names_0() { let path = format!("{}/tests/fixtures/propertyNames.json", env!("CARGO_MANIFEST_DIR")); @@ -390,18 +330,6 @@ fn test_property_names_6() { util::run_test_file_at_index(&path, 6).unwrap(); } -#[test] -fn test_boolean_schema_0() { - let path = format!("{}/tests/fixtures/boolean_schema.json", env!("CARGO_MANIFEST_DIR")); - util::run_test_file_at_index(&path, 0).unwrap(); -} - -#[test] -fn test_boolean_schema_1() { - let path = format!("{}/tests/fixtures/boolean_schema.json", env!("CARGO_MANIFEST_DIR")); - util::run_test_file_at_index(&path, 1).unwrap(); -} - #[test] fn test_not_0() { let path = format!("{}/tests/fixtures/not.json", env!("CARGO_MANIFEST_DIR")); @@ -570,6 +498,30 @@ fn test_items_15() { util::run_test_file_at_index(&path, 15).unwrap(); } +#[test] +fn test_typed_refs_0() { + let path = format!("{}/tests/fixtures/typedRefs.json", env!("CARGO_MANIFEST_DIR")); + util::run_test_file_at_index(&path, 0).unwrap(); +} + +#[test] +fn test_typed_refs_1() { + let path = format!("{}/tests/fixtures/typedRefs.json", env!("CARGO_MANIFEST_DIR")); + util::run_test_file_at_index(&path, 1).unwrap(); +} + +#[test] +fn test_typed_refs_2() { + let path = format!("{}/tests/fixtures/typedRefs.json", env!("CARGO_MANIFEST_DIR")); + util::run_test_file_at_index(&path, 2).unwrap(); +} + +#[test] +fn test_typed_refs_3() { + let path = format!("{}/tests/fixtures/typedRefs.json", env!("CARGO_MANIFEST_DIR")); + util::run_test_file_at_index(&path, 3).unwrap(); +} + #[test] fn test_enum_0() { let path = format!("{}/tests/fixtures/enum.json", env!("CARGO_MANIFEST_DIR")); @@ -1014,6 +966,18 @@ fn test_one_of_12() { util::run_test_file_at_index(&path, 12).unwrap(); } +#[test] +fn test_boolean_schema_0() { + let path = format!("{}/tests/fixtures/booleanSchema.json", env!("CARGO_MANIFEST_DIR")); + util::run_test_file_at_index(&path, 0).unwrap(); +} + +#[test] +fn test_boolean_schema_1() { + let path = format!("{}/tests/fixtures/booleanSchema.json", env!("CARGO_MANIFEST_DIR")); + util::run_test_file_at_index(&path, 1).unwrap(); +} + #[test] fn test_if_then_else_0() { let path = format!("{}/tests/fixtures/if-then-else.json", env!("CARGO_MANIFEST_DIR")); @@ -1961,129 +1925,3 @@ fn test_contains_8() { let path = format!("{}/tests/fixtures/contains.json", env!("CARGO_MANIFEST_DIR")); util::run_test_file_at_index(&path, 8).unwrap(); } - -#[test] -fn test_dynamic_ref_0() { - let path = format!("{}/tests/fixtures/dynamicRef.json", env!("CARGO_MANIFEST_DIR")); - util::run_test_file_at_index(&path, 0).unwrap(); -} - -#[test] -fn test_dynamic_ref_1() { - let path = format!("{}/tests/fixtures/dynamicRef.json", env!("CARGO_MANIFEST_DIR")); - util::run_test_file_at_index(&path, 1).unwrap(); -} - -#[test] -fn test_dynamic_ref_2() { - let path = format!("{}/tests/fixtures/dynamicRef.json", env!("CARGO_MANIFEST_DIR")); - util::run_test_file_at_index(&path, 2).unwrap(); -} - -#[test] -fn test_dynamic_ref_3() { - let path = format!("{}/tests/fixtures/dynamicRef.json", env!("CARGO_MANIFEST_DIR")); - util::run_test_file_at_index(&path, 3).unwrap(); -} - -#[test] -fn test_dynamic_ref_4() { - let path = format!("{}/tests/fixtures/dynamicRef.json", env!("CARGO_MANIFEST_DIR")); - util::run_test_file_at_index(&path, 4).unwrap(); -} - -#[test] -fn test_dynamic_ref_5() { - let path = format!("{}/tests/fixtures/dynamicRef.json", env!("CARGO_MANIFEST_DIR")); - util::run_test_file_at_index(&path, 5).unwrap(); -} - -#[test] -fn test_dynamic_ref_6() { - let path = format!("{}/tests/fixtures/dynamicRef.json", env!("CARGO_MANIFEST_DIR")); - util::run_test_file_at_index(&path, 6).unwrap(); -} - -#[test] -fn test_dynamic_ref_7() { - let path = format!("{}/tests/fixtures/dynamicRef.json", env!("CARGO_MANIFEST_DIR")); - util::run_test_file_at_index(&path, 7).unwrap(); -} - -#[test] -fn test_dynamic_ref_8() { - let path = format!("{}/tests/fixtures/dynamicRef.json", env!("CARGO_MANIFEST_DIR")); - util::run_test_file_at_index(&path, 8).unwrap(); -} - -#[test] -fn test_dynamic_ref_9() { - let path = format!("{}/tests/fixtures/dynamicRef.json", env!("CARGO_MANIFEST_DIR")); - util::run_test_file_at_index(&path, 9).unwrap(); -} - -#[test] -fn test_dynamic_ref_10() { - let path = format!("{}/tests/fixtures/dynamicRef.json", env!("CARGO_MANIFEST_DIR")); - util::run_test_file_at_index(&path, 10).unwrap(); -} - -#[test] -fn test_dynamic_ref_11() { - let path = format!("{}/tests/fixtures/dynamicRef.json", env!("CARGO_MANIFEST_DIR")); - util::run_test_file_at_index(&path, 11).unwrap(); -} - -#[test] -fn test_dynamic_ref_12() { - let path = format!("{}/tests/fixtures/dynamicRef.json", env!("CARGO_MANIFEST_DIR")); - util::run_test_file_at_index(&path, 12).unwrap(); -} - -#[test] -fn test_dynamic_ref_13() { - let path = format!("{}/tests/fixtures/dynamicRef.json", env!("CARGO_MANIFEST_DIR")); - util::run_test_file_at_index(&path, 13).unwrap(); -} - -#[test] -fn test_dynamic_ref_14() { - let path = format!("{}/tests/fixtures/dynamicRef.json", env!("CARGO_MANIFEST_DIR")); - util::run_test_file_at_index(&path, 14).unwrap(); -} - -#[test] -fn test_dynamic_ref_15() { - let path = format!("{}/tests/fixtures/dynamicRef.json", env!("CARGO_MANIFEST_DIR")); - util::run_test_file_at_index(&path, 15).unwrap(); -} - -#[test] -fn test_dynamic_ref_16() { - let path = format!("{}/tests/fixtures/dynamicRef.json", env!("CARGO_MANIFEST_DIR")); - util::run_test_file_at_index(&path, 16).unwrap(); -} - -#[test] -fn test_dynamic_ref_17() { - let path = format!("{}/tests/fixtures/dynamicRef.json", env!("CARGO_MANIFEST_DIR")); - util::run_test_file_at_index(&path, 17).unwrap(); -} - -#[test] -fn test_dynamic_ref_18() { - let path = format!("{}/tests/fixtures/dynamicRef.json", env!("CARGO_MANIFEST_DIR")); - util::run_test_file_at_index(&path, 18).unwrap(); -} - -#[test] -fn test_dynamic_ref_19() { - let path = format!("{}/tests/fixtures/dynamicRef.json", env!("CARGO_MANIFEST_DIR")); - util::run_test_file_at_index(&path, 19).unwrap(); -} - -#[test] -fn test_dynamic_ref_20() { - let path = format!("{}/tests/fixtures/dynamicRef.json", env!("CARGO_MANIFEST_DIR")); - util::run_test_file_at_index(&path, 20).unwrap(); -} diff --git a/tests/fixtures/additionalProperties.json b/tests/fixtures/additionalProperties.json index f4d9c0c..aaedbf0 100644 --- a/tests/fixtures/additionalProperties.json +++ b/tests/fixtures/additionalProperties.json @@ -1,19 +1,23 @@ [ { "description": "additionalProperties validates properties not matched by properties", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "properties": { - "foo": { - "type": "string" - }, - "bar": { - "type": "number" + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "foo": { + "type": "string" + }, + "bar": { + "type": "number" + } + }, + "additionalProperties": { + "type": "boolean" + } } - }, - "additionalProperties": { - "type": "boolean" - } + ] }, "tests": [ { @@ -45,17 +49,21 @@ }, { "description": "extensible: true with additionalProperties still validates structure", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "properties": { - "foo": { - "type": "string" + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "foo": { + "type": "string" + } + }, + "extensible": true, + "additionalProperties": { + "type": "integer" + } } - }, - "extensible": true, - "additionalProperties": { - "type": "integer" - } + ] }, "tests": [ { @@ -79,19 +87,23 @@ }, { "description": "complex additionalProperties with object and array items", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "properties": { - "type": { - "type": "string" + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "type": { + "type": "string" + } + }, + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } } - }, - "additionalProperties": { - "type": "array", - "items": { - "type": "string" - } - } + ] }, "tests": [ { diff --git a/tests/fixtures/allOf.json b/tests/fixtures/allOf.json index 0939e4d..622fb60 100644 --- a/tests/fixtures/allOf.json +++ b/tests/fixtures/allOf.json @@ -1,27 +1,31 @@ [ { "description": "allOf", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "allOf": [ + "database": { + "schemas": [ { - "properties": { - "bar": { - "type": "integer" + "$schema": "https://json-schema.org/draft/2020-12/schema", + "allOf": [ + { + "properties": { + "bar": { + "type": "integer" + } + }, + "required": [ + "bar" + ] + }, + { + "properties": { + "foo": { + "type": "string" + } + }, + "required": [ + "foo" + ] } - }, - "required": [ - "bar" - ] - }, - { - "properties": { - "foo": { - "type": "string" - } - }, - "required": [ - "foo" ] } ] @@ -61,39 +65,43 @@ }, { "description": "allOf with base schema", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "properties": { - "bar": { - "type": "integer" - }, - "baz": {}, - "foo": { - "type": "string" - } - }, - "required": [ - "bar" - ], - "allOf": [ + "database": { + "schemas": [ { + "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { + "bar": { + "type": "integer" + }, + "baz": {}, "foo": { "type": "string" } }, "required": [ - "foo" - ] - }, - { - "properties": { - "baz": { - "type": "null" + "bar" + ], + "allOf": [ + { + "properties": { + "foo": { + "type": "string" + } + }, + "required": [ + "foo" + ] + }, + { + "properties": { + "baz": { + "type": "null" + } + }, + "required": [ + "baz" + ] } - }, - "required": [ - "baz" ] } ] @@ -143,14 +151,18 @@ }, { "description": "allOf simple types", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "allOf": [ + "database": { + "schemas": [ { - "maximum": 30 - }, - { - "minimum": 20 + "$schema": "https://json-schema.org/draft/2020-12/schema", + "allOf": [ + { + "maximum": 30 + }, + { + "minimum": 20 + } + ] } ] }, @@ -169,11 +181,15 @@ }, { "description": "allOf with boolean schemas, all true", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "allOf": [ - true, - true + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "allOf": [ + true, + true + ] + } ] }, "tests": [ @@ -186,11 +202,15 @@ }, { "description": "allOf with boolean schemas, some false", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "allOf": [ - true, - false + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "allOf": [ + true, + false + ] + } ] }, "tests": [ @@ -203,11 +223,15 @@ }, { "description": "allOf with boolean schemas, all false", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "allOf": [ - false, - false + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "allOf": [ + false, + false + ] + } ] }, "tests": [ @@ -220,10 +244,14 @@ }, { "description": "allOf with one empty schema", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "allOf": [ - {} + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "allOf": [ + {} + ] + } ] }, "tests": [ @@ -236,11 +264,15 @@ }, { "description": "allOf with two empty schemas", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "allOf": [ - {}, - {} + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "allOf": [ + {}, + {} + ] + } ] }, "tests": [ @@ -253,12 +285,16 @@ }, { "description": "allOf with the first empty schema", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "allOf": [ - {}, + "database": { + "schemas": [ { - "type": "number" + "$schema": "https://json-schema.org/draft/2020-12/schema", + "allOf": [ + {}, + { + "type": "number" + } + ] } ] }, @@ -277,13 +313,17 @@ }, { "description": "allOf with the last empty schema", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "allOf": [ + "database": { + "schemas": [ { - "type": "number" - }, - {} + "$schema": "https://json-schema.org/draft/2020-12/schema", + "allOf": [ + { + "type": "number" + }, + {} + ] + } ] }, "tests": [ @@ -301,13 +341,17 @@ }, { "description": "nested allOf, to check validation semantics", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "allOf": [ + "database": { + "schemas": [ { + "$schema": "https://json-schema.org/draft/2020-12/schema", "allOf": [ { - "type": "null" + "allOf": [ + { + "type": "null" + } + ] } ] } @@ -328,21 +372,25 @@ }, { "description": "allOf combined with anyOf, oneOf", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "allOf": [ + "database": { + "schemas": [ { - "multipleOf": 2 - } - ], - "anyOf": [ - { - "multipleOf": 3 - } - ], - "oneOf": [ - { - "multipleOf": 5 + "$schema": "https://json-schema.org/draft/2020-12/schema", + "allOf": [ + { + "multipleOf": 2 + } + ], + "anyOf": [ + { + "multipleOf": 3 + } + ], + "oneOf": [ + { + "multipleOf": 5 + } + ] } ] }, @@ -391,31 +439,35 @@ }, { "description": "extensible: true allows extra properties in allOf", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "allOf": [ + "database": { + "schemas": [ { - "properties": { - "bar": { - "type": "integer" + "$schema": "https://json-schema.org/draft/2020-12/schema", + "allOf": [ + { + "properties": { + "bar": { + "type": "integer" + } + }, + "required": [ + "bar" + ] + }, + { + "properties": { + "foo": { + "type": "string" + } + }, + "required": [ + "foo" + ] } - }, - "required": [ - "bar" - ] - }, - { - "properties": { - "foo": { - "type": "string" - } - }, - "required": [ - "foo" - ] + ], + "extensible": true } - ], - "extensible": true + ] }, "tests": [ { @@ -431,22 +483,26 @@ }, { "description": "strict by default with allOf properties", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "allOf": [ + "database": { + "schemas": [ { - "properties": { - "foo": { - "const": 1 + "$schema": "https://json-schema.org/draft/2020-12/schema", + "allOf": [ + { + "properties": { + "foo": { + "const": 1 + } + } + }, + { + "properties": { + "bar": { + "const": 2 + } + } } - } - }, - { - "properties": { - "bar": { - "const": 2 - } - } + ] } ] }, @@ -472,23 +528,27 @@ }, { "description": "allOf with nested extensible: true (partial looseness)", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "allOf": [ + "database": { + "schemas": [ { - "properties": { - "foo": { - "const": 1 + "$schema": "https://json-schema.org/draft/2020-12/schema", + "allOf": [ + { + "properties": { + "foo": { + "const": 1 + } + } + }, + { + "extensible": true, + "properties": { + "bar": { + "const": 2 + } + } } - } - }, - { - "extensible": true, - "properties": { - "bar": { - "const": 2 - } - } + ] } ] }, @@ -506,31 +566,35 @@ }, { "description": "strictness: allOf composition with strict refs", - "schema": { - "allOf": [ + "database": { + "schemas": [ { - "$ref": "#/$defs/partA" - }, - { - "$ref": "#/$defs/partB" - } - ], - "$defs": { - "partA": { - "properties": { - "id": { - "type": "string" + "allOf": [ + { + "$ref": "#/$defs/partA" + }, + { + "$ref": "#/$defs/partB" } - } - }, - "partB": { - "properties": { - "name": { - "type": "string" + ], + "$defs": { + "partA": { + "properties": { + "id": { + "type": "string" + } + } + }, + "partB": { + "properties": { + "name": { + "type": "string" + } + } } } } - } + ] }, "tests": [ { diff --git a/tests/fixtures/anchor.json b/tests/fixtures/anchor.json deleted file mode 100644 index 99143fa..0000000 --- a/tests/fixtures/anchor.json +++ /dev/null @@ -1,120 +0,0 @@ -[ - { - "description": "Location-independent identifier", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$ref": "#foo", - "$defs": { - "A": { - "$anchor": "foo", - "type": "integer" - } - } - }, - "tests": [ - { - "data": 1, - "description": "match", - "valid": true - }, - { - "data": "a", - "description": "mismatch", - "valid": false - } - ] - }, - { - "description": "Location-independent identifier with absolute URI", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$ref": "http://localhost:1234/draft2020-12/bar#foo", - "$defs": { - "A": { - "$id": "http://localhost:1234/draft2020-12/bar", - "$anchor": "foo", - "type": "integer" - } - } - }, - "tests": [ - { - "data": 1, - "description": "match", - "valid": true - }, - { - "data": "a", - "description": "mismatch", - "valid": false - } - ] - }, - { - "description": "Location-independent identifier with base URI change in subschema", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "http://localhost:1234/draft2020-12/root", - "$ref": "http://localhost:1234/draft2020-12/nested.json#foo", - "$defs": { - "A": { - "$id": "nested.json", - "$defs": { - "B": { - "$anchor": "foo", - "type": "integer" - } - } - } - } - }, - "tests": [ - { - "data": 1, - "description": "match", - "valid": true - }, - { - "data": "a", - "description": "mismatch", - "valid": false - } - ] - }, - { - "description": "same $anchor with different base uri", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "http://localhost:1234/draft2020-12/foobar", - "$defs": { - "A": { - "$id": "child1", - "allOf": [ - { - "$id": "child2", - "$anchor": "my_anchor", - "type": "number" - }, - { - "$anchor": "my_anchor", - "type": "string" - } - ] - } - }, - "$ref": "child1#my_anchor" - }, - "tests": [ - { - "description": "$ref resolves to /$defs/A/allOf/1", - "data": "a", - "valid": true - }, - { - "description": "$ref does not resolve to /$defs/A/allOf/0", - "data": 1, - "valid": false - } - ] - } -] diff --git a/tests/fixtures/anyOf.json b/tests/fixtures/anyOf.json index 17aff97..d3a17d2 100644 --- a/tests/fixtures/anyOf.json +++ b/tests/fixtures/anyOf.json @@ -1,14 +1,18 @@ [ { "description": "anyOf", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "anyOf": [ + "database": { + "schemas": [ { - "type": "integer" - }, - { - "minimum": 2 + "$schema": "https://json-schema.org/draft/2020-12/schema", + "anyOf": [ + { + "type": "integer" + }, + { + "minimum": 2 + } + ] } ] }, @@ -37,15 +41,19 @@ }, { "description": "anyOf with base schema", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "type": "string", - "anyOf": [ + "database": { + "schemas": [ { - "maxLength": 2 - }, - { - "minLength": 4 + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "string", + "anyOf": [ + { + "maxLength": 2 + }, + { + "minLength": 4 + } + ] } ] }, @@ -69,11 +77,15 @@ }, { "description": "anyOf with boolean schemas, all true", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "anyOf": [ - true, - true + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "anyOf": [ + true, + true + ] + } ] }, "tests": [ @@ -86,11 +98,15 @@ }, { "description": "anyOf with boolean schemas, some true", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "anyOf": [ - true, - false + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "anyOf": [ + true, + false + ] + } ] }, "tests": [ @@ -103,11 +119,15 @@ }, { "description": "anyOf with boolean schemas, all false", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "anyOf": [ - false, - false + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "anyOf": [ + false, + false + ] + } ] }, "tests": [ @@ -120,27 +140,31 @@ }, { "description": "anyOf complex types", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "anyOf": [ + "database": { + "schemas": [ { - "properties": { - "bar": { - "type": "integer" + "$schema": "https://json-schema.org/draft/2020-12/schema", + "anyOf": [ + { + "properties": { + "bar": { + "type": "integer" + } + }, + "required": [ + "bar" + ] + }, + { + "properties": { + "foo": { + "type": "string" + } + }, + "required": [ + "foo" + ] } - }, - "required": [ - "bar" - ] - }, - { - "properties": { - "foo": { - "type": "string" - } - }, - "required": [ - "foo" ] } ] @@ -180,13 +204,17 @@ }, { "description": "anyOf with one empty schema", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "anyOf": [ + "database": { + "schemas": [ { - "type": "number" - }, - {} + "$schema": "https://json-schema.org/draft/2020-12/schema", + "anyOf": [ + { + "type": "number" + }, + {} + ] + } ] }, "tests": [ @@ -204,13 +232,17 @@ }, { "description": "nested anyOf, to check validation semantics", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "anyOf": [ + "database": { + "schemas": [ { + "$schema": "https://json-schema.org/draft/2020-12/schema", "anyOf": [ { - "type": "null" + "anyOf": [ + { + "type": "null" + } + ] } ] } @@ -231,17 +263,21 @@ }, { "description": "extensible: true allows extra properties in anyOf", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "anyOf": [ + "database": { + "schemas": [ { - "type": "integer" - }, - { - "minimum": 2 + "$schema": "https://json-schema.org/draft/2020-12/schema", + "anyOf": [ + { + "type": "integer" + }, + { + "minimum": 2 + } + ], + "extensible": true } - ], - "extensible": true + ] }, "tests": [ { @@ -255,22 +291,26 @@ }, { "description": "strict by default with anyOf properties", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "anyOf": [ + "database": { + "schemas": [ { - "properties": { - "foo": { - "const": 1 + "$schema": "https://json-schema.org/draft/2020-12/schema", + "anyOf": [ + { + "properties": { + "foo": { + "const": 1 + } + } + }, + { + "properties": { + "bar": { + "const": 2 + } + } } - } - }, - { - "properties": { - "bar": { - "const": 2 - } - } + ] } ] }, diff --git a/tests/fixtures/boolean_schema.json b/tests/fixtures/booleanSchema.json similarity index 93% rename from tests/fixtures/boolean_schema.json rename to tests/fixtures/booleanSchema.json index faeb594..794d8f8 100644 --- a/tests/fixtures/boolean_schema.json +++ b/tests/fixtures/booleanSchema.json @@ -1,7 +1,11 @@ [ { "description": "boolean schema 'true'", - "schema": true, + "database": { + "schemas": [ + true + ] + }, "tests": [ { "description": "number is valid", @@ -56,7 +60,11 @@ }, { "description": "boolean schema 'false'", - "schema": false, + "database": { + "schemas": [ + false + ] + }, "tests": [ { "description": "number is invalid", diff --git a/tests/fixtures/const.json b/tests/fixtures/const.json index 8b81bc3..b31f5f6 100644 --- a/tests/fixtures/const.json +++ b/tests/fixtures/const.json @@ -1,9 +1,13 @@ [ { "description": "const validation", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "const": 2 + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "const": 2 + } + ] }, "tests": [ { @@ -25,16 +29,20 @@ }, { "description": "const with object", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "const": { - "foo": "bar", - "baz": "bax" - }, - "properties": { - "foo": {}, - "baz": {} - } + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "const": { + "foo": "bar", + "baz": "bax" + }, + "properties": { + "foo": {}, + "baz": {} + } + } + ] }, "tests": [ { @@ -72,11 +80,15 @@ }, { "description": "const with array", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "const": [ + "database": { + "schemas": [ { - "foo": "bar" + "$schema": "https://json-schema.org/draft/2020-12/schema", + "const": [ + { + "foo": "bar" + } + ] } ] }, @@ -110,9 +122,13 @@ }, { "description": "const with null", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "const": null + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "const": null + } + ] }, "tests": [ { @@ -129,9 +145,13 @@ }, { "description": "const with false does not match 0", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "const": false + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "const": false + } + ] }, "tests": [ { @@ -153,9 +173,13 @@ }, { "description": "const with true does not match 1", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "const": true + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "const": true + } + ] }, "tests": [ { @@ -177,10 +201,14 @@ }, { "description": "const with [false] does not match [0]", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "const": [ - false + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "const": [ + false + ] + } ] }, "tests": [ @@ -209,10 +237,14 @@ }, { "description": "const with [true] does not match [1]", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "const": [ - true + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "const": [ + true + ] + } ] }, "tests": [ @@ -241,11 +273,15 @@ }, { "description": "const with {\"a\": false} does not match {\"a\": 0}", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "const": { - "a": false - } + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "const": { + "a": false + } + } + ] }, "tests": [ { @@ -273,11 +309,15 @@ }, { "description": "const with {\"a\": true} does not match {\"a\": 1}", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "const": { - "a": true - } + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "const": { + "a": true + } + } + ] }, "tests": [ { @@ -305,9 +345,13 @@ }, { "description": "const with 0 does not match other zero-like types", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "const": 0 + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "const": 0 + } + ] }, "tests": [ { @@ -344,9 +388,13 @@ }, { "description": "const with 1 does not match true", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "const": 1 + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "const": 1 + } + ] }, "tests": [ { @@ -368,9 +416,13 @@ }, { "description": "const with -2.0 matches integer and float types", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "const": -2.0 + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "const": -2.0 + } + ] }, "tests": [ { @@ -402,9 +454,13 @@ }, { "description": "float and integers are equal up to 64-bit representation limits", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "const": 9007199254740992 + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "const": 9007199254740992 + } + ] }, "tests": [ { @@ -431,9 +487,13 @@ }, { "description": "nul characters in strings", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "const": "hello\u0000there" + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "const": "hello\u0000there" + } + ] }, "tests": [ { @@ -450,10 +510,14 @@ }, { "description": "characters with the same visual representation but different codepoint", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "const": "μ", - "$comment": "U+03BC" + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "const": "μ", + "$comment": "U+03BC" + } + ] }, "tests": [ { @@ -472,10 +536,14 @@ }, { "description": "characters with the same visual representation, but different number of codepoints", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "const": "ä", - "$comment": "U+00E4" + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "const": "ä", + "$comment": "U+00E4" + } + ] }, "tests": [ { @@ -494,12 +562,16 @@ }, { "description": "extensible: true allows extra properties in const object match", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "const": { - "a": 1 - }, - "extensible": true + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "const": { + "a": 1 + }, + "extensible": true + } + ] }, "tests": [ { diff --git a/tests/fixtures/contains.json b/tests/fixtures/contains.json index e023c05..3358e19 100644 --- a/tests/fixtures/contains.json +++ b/tests/fixtures/contains.json @@ -1,12 +1,16 @@ [ { "description": "contains keyword validation", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "contains": { - "minimum": 5 - }, - "items": true + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "contains": { + "minimum": 5 + }, + "items": true + } + ] }, "tests": [ { @@ -60,12 +64,16 @@ }, { "description": "contains keyword with const keyword", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "contains": { - "const": 5 - }, - "items": true + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "contains": { + "const": 5 + }, + "items": true + } + ] }, "tests": [ { @@ -101,9 +109,13 @@ }, { "description": "contains keyword with boolean schema true", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "contains": true + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "contains": true + } + ] }, "tests": [ { @@ -122,9 +134,13 @@ }, { "description": "contains keyword with boolean schema false", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "contains": false + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "contains": false + } + ] }, "tests": [ { @@ -148,14 +164,18 @@ }, { "description": "items + contains", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "items": { - "multipleOf": 2 - }, - "contains": { - "multipleOf": 3 - } + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "items": { + "multipleOf": 2 + }, + "contains": { + "multipleOf": 3 + } + } + ] }, "tests": [ { @@ -196,12 +216,16 @@ }, { "description": "contains with false if subschema", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "contains": { - "if": false, - "else": true - } + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "contains": { + "if": false, + "else": true + } + } + ] }, "tests": [ { @@ -220,11 +244,15 @@ }, { "description": "contains with null instance elements", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "contains": { - "type": "null" - } + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "contains": { + "type": "null" + } + } + ] }, "tests": [ { @@ -238,12 +266,16 @@ }, { "description": "extensible: true allows non-matching items in contains", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "contains": { - "const": 1 - }, - "extensible": true + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "contains": { + "const": 1 + }, + "extensible": true + } + ] }, "tests": [ { @@ -258,11 +290,15 @@ }, { "description": "strict by default: non-matching items in contains are invalid", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "contains": { - "const": 1 - } + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "contains": { + "const": 1 + } + } + ] }, "tests": [ { diff --git a/tests/fixtures/content.json b/tests/fixtures/content.json index 33017cd..e69aa95 100644 --- a/tests/fixtures/content.json +++ b/tests/fixtures/content.json @@ -1,9 +1,13 @@ [ { "description": "validation of string-encoded content based on media type", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "contentMediaType": "application/json" + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "contentMediaType": "application/json" + } + ] }, "tests": [ { @@ -25,9 +29,13 @@ }, { "description": "validation of binary string-encoding", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "contentEncoding": "base64" + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "contentEncoding": "base64" + } + ] }, "tests": [ { @@ -49,10 +57,14 @@ }, { "description": "validation of binary-encoded media type documents", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "contentMediaType": "application/json", - "contentEncoding": "base64" + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "contentMediaType": "application/json", + "contentEncoding": "base64" + } + ] }, "tests": [ { @@ -79,24 +91,28 @@ }, { "description": "validation of binary-encoded media type documents with schema", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "contentMediaType": "application/json", - "contentEncoding": "base64", - "contentSchema": { - "type": "object", - "required": [ - "foo" - ], - "properties": { - "foo": { - "type": "string" - }, - "boo": { - "type": "integer" + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "contentMediaType": "application/json", + "contentEncoding": "base64", + "contentSchema": { + "type": "object", + "required": [ + "foo" + ], + "properties": { + "foo": { + "type": "string" + }, + "boo": { + "type": "integer" + } + } } } - } + ] }, "tests": [ { diff --git a/tests/fixtures/dependencies.json b/tests/fixtures/dependencies.json new file mode 100644 index 0000000..ec19d65 --- /dev/null +++ b/tests/fixtures/dependencies.json @@ -0,0 +1,576 @@ +[ + { + "description": "single dependency (required)", + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "schema1", + "dependencies": { + "bar": [ + "foo" + ] + }, + "extensible": true + } + ] + }, + "tests": [ + { + "description": "neither", + "data": {}, + "valid": true + }, + { + "description": "nondependant", + "data": { + "foo": 1 + }, + "valid": true + }, + { + "description": "with dependency", + "data": { + "foo": 1, + "bar": 2 + }, + "valid": true + }, + { + "description": "missing dependency", + "data": { + "bar": 2 + }, + "valid": false + }, + { + "description": "ignores arrays", + "data": [ + "bar" + ], + "valid": true + }, + { + "description": "ignores strings", + "data": "foobar", + "valid": true + }, + { + "description": "ignores other non-objects", + "data": 12, + "valid": true + } + ] + }, + { + "description": "empty dependents", + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "schema2", + "dependencies": { + "bar": [] + }, + "extensible": true + } + ] + }, + "tests": [ + { + "description": "empty object", + "data": {}, + "valid": true + }, + { + "description": "object with one property", + "data": { + "bar": 2 + }, + "valid": true + }, + { + "description": "non-object is valid", + "data": 1, + "valid": true + } + ] + }, + { + "description": "multiple dependents required", + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "schema3", + "dependencies": { + "quux": [ + "foo", + "bar" + ] + }, + "extensible": true + } + ] + }, + "tests": [ + { + "description": "neither", + "data": {}, + "valid": true + }, + { + "description": "nondependants", + "data": { + "foo": 1, + "bar": 2 + }, + "valid": true + }, + { + "description": "with dependencies", + "data": { + "foo": 1, + "bar": 2, + "quux": 3 + }, + "valid": true + }, + { + "description": "missing dependency", + "data": { + "foo": 1, + "quux": 2 + }, + "valid": false + }, + { + "description": "missing other dependency", + "data": { + "bar": 1, + "quux": 2 + }, + "valid": false + }, + { + "description": "missing both dependencies", + "data": { + "quux": 1 + }, + "valid": false + } + ] + }, + { + "description": "dependencies with escaped characters", + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "schema4", + "dependencies": { + "foo\nbar": [ + "foo\rbar" + ], + "foo\"bar": [ + "foo'bar" + ] + }, + "extensible": true + } + ] + }, + "tests": [ + { + "description": "CRLF", + "data": { + "foo\nbar": 1, + "foo\rbar": 2 + }, + "valid": true + }, + { + "description": "quoted quotes", + "data": { + "foo'bar": 1, + "foo\"bar": 2 + }, + "valid": true + }, + { + "description": "CRLF missing dependent", + "data": { + "foo\nbar": 1, + "foo": 2 + }, + "valid": false + }, + { + "description": "quoted quotes missing dependent", + "data": { + "foo\"bar": 2 + }, + "valid": false + } + ] + }, + { + "description": "extensible: true allows extra properties in dependentRequired", + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "schema5", + "dependencies": { + "bar": [ + "foo" + ] + }, + "extensible": true + } + ] + }, + "tests": [ + { + "description": "extra property is valid", + "data": { + "foo": 1, + "bar": 2, + "baz": 3 + }, + "valid": true + } + ] + }, + { + "description": "single dependency (schemas, STRICT)", + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "schema_schema1", + "properties": { + "foo": true, + "bar": true + }, + "dependencies": { + "bar": { + "properties": { + "foo": { + "type": "integer" + }, + "bar": { + "type": "integer" + } + } + } + } + } + ] + }, + "tests": [ + { + "description": "valid", + "data": { + "foo": 1, + "bar": 2 + }, + "valid": true + }, + { + "description": "no dependency", + "data": { + "foo": "quux" + }, + "valid": true + }, + { + "description": "wrong type", + "data": { + "foo": "quux", + "bar": 2 + }, + "valid": false + }, + { + "description": "wrong type other", + "data": { + "foo": 2, + "bar": "quux" + }, + "valid": false + }, + { + "description": "wrong type both", + "data": { + "foo": "quux", + "bar": "quux" + }, + "valid": false + }, + { + "description": "ignores arrays (invalid in strict mode)", + "data": [ + "bar" + ], + "valid": false, + "expect_errors": [ + { + "code": "STRICT_ITEM_VIOLATION" + } + ] + }, + { + "description": "ignores strings", + "data": "foobar", + "valid": true + }, + { + "description": "ignores other non-objects", + "data": 12, + "valid": true + } + ] + }, + { + "description": "single dependency (schemas, EXTENSIBLE)", + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "schema_schema2", + "properties": { + "foo": true, + "bar": true + }, + "dependencies": { + "bar": { + "properties": { + "foo": { + "type": "integer" + }, + "bar": { + "type": "integer" + } + } + } + }, + "extensible": true + } + ] + }, + "tests": [ + { + "description": "ignores arrays (valid in extensible mode)", + "data": [ + "bar" + ], + "valid": true + } + ] + }, + { + "description": "boolean subschemas", + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "schema_schema3", + "properties": { + "foo": true, + "bar": true + }, + "dependencies": { + "foo": true, + "bar": false + } + } + ] + }, + "tests": [ + { + "description": "object with property having schema true is valid", + "data": { + "foo": 1 + }, + "valid": true + }, + { + "description": "object with property having schema false is invalid", + "data": { + "bar": 2 + }, + "valid": false + }, + { + "description": "object with both properties is invalid", + "data": { + "foo": 1, + "bar": 2 + }, + "valid": false + }, + { + "description": "empty object is valid", + "data": {}, + "valid": true + } + ] + }, + { + "description": "dependencies with escaped characters", + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "schema_schema4", + "properties": { + "foo\tbar": true, + "foo'bar": true, + "a": true, + "b": true, + "c": true + }, + "dependencies": { + "foo\tbar": { + "minProperties": 4, + "extensible": true + }, + "foo'bar": { + "required": [ + "foo\"bar" + ] + } + } + } + ] + }, + "tests": [ + { + "description": "quoted tab", + "data": { + "foo\tbar": 1, + "a": 2, + "b": 3, + "c": 4 + }, + "valid": true + }, + { + "description": "quoted quote", + "data": { + "foo'bar": { + "foo\"bar": 1 + } + }, + "valid": false + }, + { + "description": "quoted tab invalid under dependent schema", + "data": { + "foo\tbar": 1, + "a": 2 + }, + "valid": false + }, + { + "description": "quoted quote invalid under dependent schema", + "data": { + "foo'bar": 1 + }, + "valid": false + } + ] + }, + { + "description": "dependent subschema incompatible with root (STRICT)", + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "schema_schema5", + "properties": { + "foo": {}, + "baz": true + }, + "dependencies": { + "foo": { + "properties": { + "bar": {} + } + } + } + } + ] + }, + "tests": [ + { + "description": "matches root", + "data": { + "foo": 1 + }, + "valid": false + }, + { + "description": "matches dependency (invalid in strict mode - bar not allowed if foo missing)", + "data": { + "bar": 1 + }, + "valid": false, + "expect_errors": [ + { + "code": "STRICT_PROPERTY_VIOLATION" + } + ] + }, + { + "description": "matches both", + "data": { + "foo": 1, + "bar": 2 + }, + "valid": false + }, + { + "description": "no dependency", + "data": { + "baz": 1 + }, + "valid": true + } + ] + }, + { + "description": "dependent subschema incompatible with root (EXTENSIBLE)", + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "schema_schema6", + "properties": { + "foo": {}, + "baz": true + }, + "dependencies": { + "foo": { + "properties": { + "bar": {} + }, + "additionalProperties": false + } + }, + "extensible": true + } + ] + }, + "tests": [ + { + "description": "matches dependency (valid in extensible mode)", + "data": { + "bar": 1 + }, + "valid": true + } + ] + } +] \ No newline at end of file diff --git a/tests/fixtures/dependentRequired.json b/tests/fixtures/dependentRequired.json deleted file mode 100644 index 0f69918..0000000 --- a/tests/fixtures/dependentRequired.json +++ /dev/null @@ -1,220 +0,0 @@ -[ - { - "description": "single dependency", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "dependentRequired": { - "bar": [ - "foo" - ] - }, - "extensible": true - }, - "tests": [ - { - "description": "neither", - "data": {}, - "valid": true - }, - { - "description": "nondependant", - "data": { - "foo": 1 - }, - "valid": true - }, - { - "description": "with dependency", - "data": { - "foo": 1, - "bar": 2 - }, - "valid": true - }, - { - "description": "missing dependency", - "data": { - "bar": 2 - }, - "valid": false - }, - { - "description": "ignores arrays", - "data": [ - "bar" - ], - "valid": true - }, - { - "description": "ignores strings", - "data": "foobar", - "valid": true - }, - { - "description": "ignores other non-objects", - "data": 12, - "valid": true - } - ] - }, - { - "description": "empty dependents", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "dependentRequired": { - "bar": [] - }, - "extensible": true - }, - "tests": [ - { - "description": "empty object", - "data": {}, - "valid": true - }, - { - "description": "object with one property", - "data": { - "bar": 2 - }, - "valid": true - }, - { - "description": "non-object is valid", - "data": 1, - "valid": true - } - ] - }, - { - "description": "multiple dependents required", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "dependentRequired": { - "quux": [ - "foo", - "bar" - ] - }, - "extensible": true - }, - "tests": [ - { - "description": "neither", - "data": {}, - "valid": true - }, - { - "description": "nondependants", - "data": { - "foo": 1, - "bar": 2 - }, - "valid": true - }, - { - "description": "with dependencies", - "data": { - "foo": 1, - "bar": 2, - "quux": 3 - }, - "valid": true - }, - { - "description": "missing dependency", - "data": { - "foo": 1, - "quux": 2 - }, - "valid": false - }, - { - "description": "missing other dependency", - "data": { - "bar": 1, - "quux": 2 - }, - "valid": false - }, - { - "description": "missing both dependencies", - "data": { - "quux": 1 - }, - "valid": false - } - ] - }, - { - "description": "dependencies with escaped characters", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "dependentRequired": { - "foo\nbar": [ - "foo\rbar" - ], - "foo\"bar": [ - "foo'bar" - ] - }, - "extensible": true - }, - "tests": [ - { - "description": "CRLF", - "data": { - "foo\nbar": 1, - "foo\rbar": 2 - }, - "valid": true - }, - { - "description": "quoted quotes", - "data": { - "foo'bar": 1, - "foo\"bar": 2 - }, - "valid": true - }, - { - "description": "CRLF missing dependent", - "data": { - "foo\nbar": 1, - "foo": 2 - }, - "valid": false - }, - { - "description": "quoted quotes missing dependent", - "data": { - "foo\"bar": 2 - }, - "valid": false - } - ] - }, - { - "description": "extensible: true allows extra properties in dependentRequired", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "dependentRequired": { - "bar": [ - "foo" - ] - }, - "extensible": true - }, - "tests": [ - { - "description": "extra property is valid", - "data": { - "foo": 1, - "bar": 2, - "baz": 3 - }, - "valid": true - } - ] - } -] \ No newline at end of file diff --git a/tests/fixtures/dependentSchemas.json b/tests/fixtures/dependentSchemas.json deleted file mode 100644 index 448f5af..0000000 --- a/tests/fixtures/dependentSchemas.json +++ /dev/null @@ -1,303 +0,0 @@ -[ - { - "description": "single dependency (STRICT)", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "properties": { - "foo": true, - "bar": true - }, - "dependentSchemas": { - "bar": { - "properties": { - "foo": { - "type": "integer" - }, - "bar": { - "type": "integer" - } - } - } - } - }, - "tests": [ - { - "description": "valid", - "data": { - "foo": 1, - "bar": 2 - }, - "valid": true - }, - { - "description": "no dependency", - "data": { - "foo": "quux" - }, - "valid": true - }, - { - "description": "wrong type", - "data": { - "foo": "quux", - "bar": 2 - }, - "valid": false - }, - { - "description": "wrong type other", - "data": { - "foo": 2, - "bar": "quux" - }, - "valid": false - }, - { - "description": "wrong type both", - "data": { - "foo": "quux", - "bar": "quux" - }, - "valid": false - }, - { - "description": "ignores arrays (invalid in strict mode)", - "data": [ - "bar" - ], - "valid": false, - "expect_errors": [ - { - "code": "STRICT_ITEM_VIOLATION" - } - ] - }, - { - "description": "ignores strings (invalid in strict mode - wait, strings are scalars, strict only checks obj/arr)", - "data": "foobar", - "valid": true - }, - { - "description": "ignores other non-objects", - "data": 12, - "valid": true - } - ] - }, - { - "description": "single dependency (EXTENSIBLE)", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "properties": { - "foo": true, - "bar": true - }, - "dependentSchemas": { - "bar": { - "properties": { - "foo": { - "type": "integer" - }, - "bar": { - "type": "integer" - } - } - } - }, - "extensible": true - }, - "tests": [ - { - "description": "ignores arrays (valid in extensible mode)", - "data": [ - "bar" - ], - "valid": true - } - ] - }, - { - "description": "boolean subschemas", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "properties": { - "foo": true, - "bar": true - }, - "dependentSchemas": { - "foo": true, - "bar": false - } - }, - "tests": [ - { - "description": "object with property having schema true is valid", - "data": { - "foo": 1 - }, - "valid": true - }, - { - "description": "object with property having schema false is invalid", - "data": { - "bar": 2 - }, - "valid": false - }, - { - "description": "object with both properties is invalid", - "data": { - "foo": 1, - "bar": 2 - }, - "valid": false - }, - { - "description": "empty object is valid", - "data": {}, - "valid": true - } - ] - }, - { - "description": "dependencies with escaped characters", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "properties": { - "foo\tbar": true, - "foo'bar": true, - "a": true, - "b": true, - "c": true - }, - "dependentSchemas": { - "foo\tbar": { - "minProperties": 4, - "extensible": true - }, - "foo'bar": { - "required": [ - "foo\"bar" - ] - } - } - }, - "tests": [ - { - "description": "quoted tab", - "data": { - "foo\tbar": 1, - "a": 2, - "b": 3, - "c": 4 - }, - "valid": true - }, - { - "description": "quoted quote", - "data": { - "foo'bar": { - "foo\"bar": 1 - } - }, - "valid": false - }, - { - "description": "quoted tab invalid under dependent schema", - "data": { - "foo\tbar": 1, - "a": 2 - }, - "valid": false - }, - { - "description": "quoted quote invalid under dependent schema", - "data": { - "foo'bar": 1 - }, - "valid": false - } - ] - }, - { - "description": "dependent subschema incompatible with root (STRICT)", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "properties": { - "foo": {}, - "baz": true - }, - "dependentSchemas": { - "foo": { - "properties": { - "bar": {} - } - } - } - }, - "tests": [ - { - "description": "matches root", - "data": { - "foo": 1 - }, - "valid": false - }, - { - "description": "matches dependency (invalid in strict mode - bar not allowed if foo missing)", - "data": { - "bar": 1 - }, - "valid": false, - "expect_errors": [ - { - "code": "STRICT_PROPERTY_VIOLATION" - } - ] - }, - { - "description": "matches both", - "data": { - "foo": 1, - "bar": 2 - }, - "valid": false - }, - { - "description": "no dependency", - "data": { - "baz": 1 - }, - "valid": true - } - ] - }, - { - "description": "dependent subschema incompatible with root (EXTENSIBLE)", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "properties": { - "foo": {}, - "baz": true - }, - "dependentSchemas": { - "foo": { - "properties": { - "bar": {} - }, - "additionalProperties": false - } - }, - "extensible": true - }, - "tests": [ - { - "description": "matches dependency (valid in extensible mode)", - "data": { - "bar": 1 - }, - "valid": true - } - ] - } -] \ No newline at end of file diff --git a/tests/fixtures/dynamicRef.json b/tests/fixtures/dynamicRef.json deleted file mode 100644 index c533c22..0000000 --- a/tests/fixtures/dynamicRef.json +++ /dev/null @@ -1,1111 +0,0 @@ -[ - { - "description": "A $dynamicRef to a $dynamicAnchor in the same schema resource behaves like a normal $ref to an $anchor", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://test.json-schema.org/dynamicRef-dynamicAnchor-same-schema/root", - "type": "array", - "items": { - "$dynamicRef": "#items" - }, - "$defs": { - "foo": { - "$dynamicAnchor": "items", - "type": "string" - } - } - }, - "tests": [ - { - "description": "An array of strings is valid", - "data": [ - "foo", - "bar" - ], - "valid": true - }, - { - "description": "An array containing non-strings is invalid", - "data": [ - "foo", - 42 - ], - "valid": false - } - ] - }, - { - "description": "A $dynamicRef to an $anchor in the same schema resource behaves like a normal $ref to an $anchor", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://test.json-schema.org/dynamicRef-anchor-same-schema/root", - "type": "array", - "items": { - "$dynamicRef": "#items" - }, - "$defs": { - "foo": { - "$anchor": "items", - "type": "string" - } - } - }, - "tests": [ - { - "description": "An array of strings is valid", - "data": [ - "foo", - "bar" - ], - "valid": true - }, - { - "description": "An array containing non-strings is invalid", - "data": [ - "foo", - 42 - ], - "valid": false - } - ] - }, - { - "description": "A $ref to a $dynamicAnchor in the same schema resource behaves like a normal $ref to an $anchor", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://test.json-schema.org/ref-dynamicAnchor-same-schema/root", - "type": "array", - "items": { - "$ref": "#items" - }, - "$defs": { - "foo": { - "$dynamicAnchor": "items", - "type": "string" - } - } - }, - "tests": [ - { - "description": "An array of strings is valid", - "data": [ - "foo", - "bar" - ], - "valid": true - }, - { - "description": "An array containing non-strings is invalid", - "data": [ - "foo", - 42 - ], - "valid": false - } - ] - }, - { - "description": "A $dynamicRef resolves to the first $dynamicAnchor still in scope that is encountered when the schema is evaluated", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://test.json-schema.org/typical-dynamic-resolution/root", - "$ref": "list", - "$defs": { - "foo": { - "$dynamicAnchor": "items", - "type": "string" - }, - "list": { - "$id": "list", - "type": "array", - "items": { - "$dynamicRef": "#items" - }, - "$defs": { - "items": { - "$comment": "This is only needed to satisfy the bookending requirement", - "$dynamicAnchor": "items" - } - } - } - } - }, - "tests": [ - { - "description": "An array of strings is valid", - "data": [ - "foo", - "bar" - ], - "valid": true - }, - { - "description": "An array containing non-strings is invalid", - "data": [ - "foo", - 42 - ], - "valid": false - } - ] - }, - { - "description": "A $dynamicRef without anchor in fragment behaves identical to $ref", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://test.json-schema.org/dynamicRef-without-anchor/root", - "$ref": "list", - "$defs": { - "foo": { - "$dynamicAnchor": "items", - "type": "string" - }, - "list": { - "$id": "list", - "type": "array", - "items": { - "$dynamicRef": "#/$defs/items" - }, - "$defs": { - "items": { - "$comment": "This is only needed to satisfy the bookending requirement", - "$dynamicAnchor": "items", - "type": "number" - } - } - } - } - }, - "tests": [ - { - "description": "An array of strings is invalid", - "data": [ - "foo", - "bar" - ], - "valid": false - }, - { - "description": "An array of numbers is valid", - "data": [ - 24, - 42 - ], - "valid": true - } - ] - }, - { - "description": "A $dynamicRef with intermediate scopes that don't include a matching $dynamicAnchor does not affect dynamic scope resolution", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://test.json-schema.org/dynamic-resolution-with-intermediate-scopes/root", - "$ref": "intermediate-scope", - "$defs": { - "foo": { - "$dynamicAnchor": "items", - "type": "string" - }, - "intermediate-scope": { - "$id": "intermediate-scope", - "$ref": "list" - }, - "list": { - "$id": "list", - "type": "array", - "items": { - "$dynamicRef": "#items" - }, - "$defs": { - "items": { - "$comment": "This is only needed to satisfy the bookending requirement", - "$dynamicAnchor": "items" - } - } - } - } - }, - "tests": [ - { - "description": "An array of strings is valid", - "data": [ - "foo", - "bar" - ], - "valid": true - }, - { - "description": "An array containing non-strings is invalid", - "data": [ - "foo", - 42 - ], - "valid": false - } - ] - }, - { - "description": "An $anchor with the same name as a $dynamicAnchor is not used for dynamic scope resolution", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://test.json-schema.org/dynamic-resolution-ignores-anchors/root", - "$ref": "list", - "$defs": { - "foo": { - "$anchor": "items", - "type": "string" - }, - "list": { - "$id": "list", - "type": "array", - "items": { - "$dynamicRef": "#items" - }, - "$defs": { - "items": { - "$comment": "This is only needed to satisfy the bookending requirement", - "$dynamicAnchor": "items" - } - } - } - } - }, - "tests": [ - { - "description": "Any array is valid", - "data": [ - "foo", - 42 - ], - "valid": true - } - ] - }, - { - "description": "A $dynamicRef without a matching $dynamicAnchor in the same schema resource behaves like a normal $ref to $anchor", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://test.json-schema.org/dynamic-resolution-without-bookend/root", - "$ref": "list", - "$defs": { - "foo": { - "$dynamicAnchor": "items", - "type": "string" - }, - "list": { - "$id": "list", - "type": "array", - "items": { - "$dynamicRef": "#items" - }, - "$defs": { - "items": { - "$comment": "This is only needed to give the reference somewhere to resolve to when it behaves like $ref", - "$anchor": "items" - } - } - } - } - }, - "tests": [ - { - "description": "Any array is valid", - "data": [ - "foo", - 42 - ], - "valid": true - } - ] - }, - { - "description": "A $dynamicRef with a non-matching $dynamicAnchor in the same schema resource behaves like a normal $ref to $anchor", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://test.json-schema.org/unmatched-dynamic-anchor/root", - "$ref": "list", - "$defs": { - "foo": { - "$dynamicAnchor": "items", - "type": "string" - }, - "list": { - "$id": "list", - "type": "array", - "items": { - "$dynamicRef": "#items" - }, - "$defs": { - "items": { - "$comment": "This is only needed to give the reference somewhere to resolve to when it behaves like $ref", - "$anchor": "items", - "$dynamicAnchor": "foo" - } - } - } - } - }, - "tests": [ - { - "description": "Any array is valid", - "data": [ - "foo", - 42 - ], - "valid": true - } - ] - }, - { - "description": "A $dynamicRef that initially resolves to a schema with a matching $dynamicAnchor resolves to the first $dynamicAnchor in the dynamic scope", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://test.json-schema.org/relative-dynamic-reference/root", - "$dynamicAnchor": "meta", - "type": "object", - "properties": { - "foo": { - "const": "pass" - } - }, - "$ref": "extended", - "$defs": { - "extended": { - "$id": "extended", - "$dynamicAnchor": "meta", - "type": "object", - "properties": { - "bar": { - "$ref": "bar" - }, - "foo": { - "type": "string" - } - } - }, - "bar": { - "$id": "bar", - "type": "object", - "properties": { - "baz": { - "$dynamicRef": "extended#meta" - } - } - } - } - }, - "tests": [ - { - "description": "The recursive part is valid against the root", - "data": { - "foo": "pass", - "bar": { - "baz": { - "foo": "pass" - } - } - }, - "valid": true - }, - { - "description": "The recursive part is not valid against the root", - "data": { - "foo": "pass", - "bar": { - "baz": { - "foo": "fail" - } - } - }, - "valid": false - } - ] - }, - { - "description": "A $dynamicRef that initially resolves to a schema without a matching $dynamicAnchor behaves like a normal $ref to $anchor", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://test.json-schema.org/relative-dynamic-reference-without-bookend/root", - "$dynamicAnchor": "meta", - "type": "object", - "properties": { - "foo": { - "const": "pass" - } - }, - "$ref": "extended", - "$defs": { - "extended": { - "$id": "extended", - "$anchor": "meta", - "type": "object", - "properties": { - "bar": { - "$ref": "bar" - }, - "foo": { - "type": "string" - } - } - }, - "bar": { - "$id": "bar", - "type": "object", - "properties": { - "baz": { - "$dynamicRef": "extended#meta" - } - } - } - } - }, - "tests": [ - { - "description": "The recursive part doesn't need to validate against the root", - "data": { - "foo": "pass", - "bar": { - "baz": { - "foo": "fail" - } - } - }, - "valid": true - } - ] - }, - { - "description": "multiple dynamic paths to the $dynamicRef keyword", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://test.json-schema.org/dynamic-ref-with-multiple-paths/main", - "if": { - "properties": { - "kindOfList": { - "const": "numbers" - } - }, - "required": [ - "kindOfList" - ] - }, - "then": { - "$ref": "numberList" - }, - "else": { - "$ref": "stringList" - }, - "$defs": { - "genericList": { - "$id": "genericList", - "properties": { - "list": { - "items": { - "$dynamicRef": "#itemType" - } - } - }, - "$defs": { - "defaultItemType": { - "$comment": "Only needed to satisfy bookending requirement", - "$dynamicAnchor": "itemType" - } - } - }, - "numberList": { - "$id": "numberList", - "$defs": { - "itemType": { - "$dynamicAnchor": "itemType", - "type": "number" - } - }, - "$ref": "genericList" - }, - "stringList": { - "$id": "stringList", - "$defs": { - "itemType": { - "$dynamicAnchor": "itemType", - "type": "string" - } - }, - "$ref": "genericList" - } - } - }, - "tests": [ - { - "description": "number list with number values", - "data": { - "kindOfList": "numbers", - "list": [ - 1.1 - ] - }, - "valid": true - }, - { - "description": "number list with string values", - "data": { - "kindOfList": "numbers", - "list": [ - "foo" - ] - }, - "valid": false - }, - { - "description": "string list with number values", - "data": { - "kindOfList": "strings", - "list": [ - 1.1 - ] - }, - "valid": false - }, - { - "description": "string list with string values", - "data": { - "kindOfList": "strings", - "list": [ - "foo" - ] - }, - "valid": true - } - ] - }, - { - "description": "after leaving a dynamic scope, it is not used by a $dynamicRef", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://test.json-schema.org/dynamic-ref-leaving-dynamic-scope/main", - "if": { - "$id": "first_scope", - "$defs": { - "thingy": { - "$comment": "this is first_scope#thingy", - "$dynamicAnchor": "thingy", - "type": "number" - } - } - }, - "then": { - "$id": "second_scope", - "$ref": "start", - "$defs": { - "thingy": { - "$comment": "this is second_scope#thingy, the final destination of the $dynamicRef", - "$dynamicAnchor": "thingy", - "type": "null" - } - } - }, - "$defs": { - "start": { - "$comment": "this is the landing spot from $ref", - "$id": "start", - "$dynamicRef": "inner_scope#thingy" - }, - "thingy": { - "$comment": "this is the first stop for the $dynamicRef", - "$id": "inner_scope", - "$dynamicAnchor": "thingy", - "type": "string" - } - } - }, - "tests": [ - { - "description": "string matches /$defs/thingy, but the $dynamicRef does not stop here", - "data": "a string", - "valid": false - }, - { - "description": "first_scope is not in dynamic scope for the $dynamicRef", - "data": 42, - "valid": false - }, - { - "description": "/then/$defs/thingy is the final stop for the $dynamicRef", - "data": null, - "valid": true - } - ] - }, - { - "description": "strict-tree schema, guards against misspelled properties", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "http://localhost:1234/draft2020-12/strict-tree.json", - "$dynamicAnchor": "node", - "$ref": "#/$defs/tree", - "$defs": { - "tree": { - "description": "tree schema, extensible", - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "http://localhost:1234/draft2020-12/tree.json", - "$dynamicAnchor": "node", - "type": "object", - "properties": { - "data": true, - "children": { - "type": "array", - "items": { - "$dynamicRef": "#node" - } - } - } - } - } - }, - "tests": [ - { - "description": "instance with misspelled field", - "data": { - "children": [ - { - "daat": 1 - } - ] - }, - "valid": false - }, - { - "description": "instance with correct field", - "data": { - "children": [ - { - "data": 1 - } - ] - }, - "valid": true - } - ] - }, - { - "description": "tests for implementation dynamic anchor and reference link", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "http://localhost:1234/draft2020-12/strict-extendible.json", - "$ref": "#/$defs/remote_extendible", - "$defs": { - "elements": { - "$dynamicAnchor": "elements", - "properties": { - "a": true - }, - "required": [ - "a" - ] - }, - "remote_extendible": { - "description": "extendible array", - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "http://localhost:1234/draft2020-12/extendible-dynamic-ref.json", - "type": "object", - "properties": { - "elements": { - "type": "array", - "items": { - "$dynamicRef": "#elements" - } - } - }, - "required": [ - "elements" - ], - "$defs": { - "elements": { - "$dynamicAnchor": "elements" - } - } - } - } - }, - "tests": [ - { - "description": "incorrect parent schema", - "data": { - "a": true - }, - "valid": false - }, - { - "description": "incorrect extended schema", - "data": { - "elements": [ - { - "b": 1 - } - ] - }, - "valid": false - }, - { - "description": "correct extended schema", - "data": { - "elements": [ - { - "a": 1 - } - ] - }, - "valid": true - } - ] - }, - { - "description": "$ref and $dynamicAnchor are independent of order - $defs first", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "http://localhost:1234/draft2020-12/strict-extendible-allof-defs-first.json", - "$defs": { - "remote_extendible": { - "description": "extendible array", - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "http://localhost:1234/draft2020-12/extendible-dynamic-ref.json", - "type": "object", - "properties": { - "elements": { - "type": "array", - "items": { - "$dynamicRef": "#elements" - } - } - }, - "required": [ - "elements" - ], - "$defs": { - "elements": { - "$dynamicAnchor": "elements" - } - } - } - }, - "allOf": [ - { - "$ref": "#/$defs/remote_extendible" - }, - { - "$defs": { - "elements": { - "$dynamicAnchor": "elements", - "properties": { - "a": true - }, - "required": [ - "a" - ], - "additionalProperties": false - } - } - } - ] - }, - "tests": [ - { - "description": "incorrect parent schema", - "data": { - "a": true - }, - "valid": false - }, - { - "description": "incorrect extended schema", - "data": { - "elements": [ - { - "b": 1 - } - ] - }, - "valid": false - }, - { - "description": "correct extended schema", - "data": { - "elements": [ - { - "a": 1 - } - ] - }, - "valid": true - } - ] - }, - { - "description": "$ref and $dynamicAnchor are independent of order - $ref first", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "http://localhost:1234/draft2020-12/strict-extendible-allof-ref-first.json", - "$defs": { - "remote_extendible": { - "description": "extendible array", - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "http://localhost:1234/draft2020-12/extendible-dynamic-ref.json", - "type": "object", - "properties": { - "elements": { - "type": "array", - "items": { - "$dynamicRef": "#elements" - } - } - }, - "required": [ - "elements" - ], - "$defs": { - "elements": { - "$dynamicAnchor": "elements" - } - } - } - }, - "allOf": [ - { - "$defs": { - "elements": { - "$dynamicAnchor": "elements", - "properties": { - "a": true - }, - "required": [ - "a" - ], - "additionalProperties": false - } - } - }, - { - "$ref": "#/$defs/remote_extendible" - } - ] - }, - "tests": [ - { - "description": "incorrect parent schema", - "data": { - "a": true - }, - "valid": false - }, - { - "description": "incorrect extended schema", - "data": { - "elements": [ - { - "b": 1 - } - ] - }, - "valid": false - }, - { - "description": "correct extended schema", - "data": { - "elements": [ - { - "a": 1 - } - ] - }, - "valid": true - } - ] - }, - { - "description": "$ref to $dynamicRef finds detached $dynamicAnchor", - "schema": { - "$ref": "http://localhost:1234/draft2020-12/detached-dynamicref.json#/$defs/foo", - "$defs": { - "remote_detached": { - "$id": "http://localhost:1234/draft2020-12/detached-dynamicref.json", - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$defs": { - "foo": { - "$dynamicRef": "#detached" - }, - "detached": { - "$dynamicAnchor": "detached", - "type": "integer" - } - } - } - } - }, - "tests": [ - { - "description": "number is valid", - "data": 1, - "valid": true - }, - { - "description": "non-number is invalid", - "data": "a", - "valid": false - } - ] - }, - { - "description": "$dynamicRef points to a boolean schema", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$defs": { - "true": true, - "false": false - }, - "properties": { - "true": { - "$dynamicRef": "#/$defs/true" - }, - "false": { - "$dynamicRef": "#/$defs/false" - } - } - }, - "tests": [ - { - "description": "follow $dynamicRef to a true schema", - "data": { - "true": 1 - }, - "valid": true - }, - { - "description": "follow $dynamicRef to a false schema", - "data": { - "false": 1 - }, - "valid": false - } - ] - }, - { - "description": "$dynamicRef skips over intermediate resources - direct reference", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://test.json-schema.org/dynamic-ref-skips-intermediate-resource/main", - "type": "object", - "properties": { - "bar-item": { - "$ref": "item" - } - }, - "$defs": { - "bar": { - "$id": "bar", - "type": "array", - "items": { - "$ref": "item" - }, - "$defs": { - "item": { - "$id": "item", - "type": "object", - "properties": { - "content": { - "$dynamicRef": "#content" - } - }, - "$defs": { - "defaultContent": { - "$dynamicAnchor": "content", - "type": "integer" - } - } - }, - "content": { - "$dynamicAnchor": "content", - "type": "string" - } - } - } - } - }, - "tests": [ - { - "description": "integer property passes", - "data": { - "bar-item": { - "content": 42 - } - }, - "valid": true - }, - { - "description": "string property fails", - "data": { - "bar-item": { - "content": "value" - } - }, - "valid": false - } - ] - }, - { - "description": "$dynamicRef avoids the root of each schema, but scopes are still registered", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://test.json-schema.org/dynamic-ref-avoids-root-of-each-schema/base", - "$ref": "first#/$defs/stuff", - "$defs": { - "first": { - "$id": "first", - "$defs": { - "stuff": { - "$ref": "second#/$defs/stuff" - }, - "length": { - "$comment": "unused, because there is no $dynamicAnchor here", - "maxLength": 1 - } - } - }, - "second": { - "$id": "second", - "$defs": { - "stuff": { - "$ref": "third#/$defs/stuff" - }, - "length": { - "$dynamicAnchor": "length", - "maxLength": 2 - } - } - }, - "third": { - "$id": "third", - "$defs": { - "stuff": { - "$dynamicRef": "#length" - }, - "length": { - "$dynamicAnchor": "length", - "maxLength": 3 - } - } - } - } - }, - "tests": [ - { - "description": "data is sufficient for schema at second#/$defs/length", - "data": "hi", - "valid": true - }, - { - "description": "data is not sufficient for schema at second#/$defs/length", - "data": "hey", - "valid": false - } - ] - } -] \ No newline at end of file diff --git a/tests/fixtures/emptyString.json b/tests/fixtures/emptyString.json index c0a9825..45b1b4d 100644 --- a/tests/fixtures/emptyString.json +++ b/tests/fixtures/emptyString.json @@ -1,41 +1,45 @@ [ { "description": "empty string is valid for all types (except const)", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "properties": { - "obj": { - "type": "object" - }, - "arr": { - "type": "array" - }, - "str": { - "type": "string" - }, - "int": { - "type": "integer" - }, - "num": { - "type": "number" - }, - "bool": { - "type": "boolean" - }, - "nul": { - "type": "null" - }, - "fmt": { - "type": "string", - "format": "uuid" - }, - "con": { - "const": "value" - }, - "con_empty": { - "const": "" + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "obj": { + "type": "object" + }, + "arr": { + "type": "array" + }, + "str": { + "type": "string" + }, + "int": { + "type": "integer" + }, + "num": { + "type": "number" + }, + "bool": { + "type": "boolean" + }, + "nul": { + "type": "null" + }, + "fmt": { + "type": "string", + "format": "uuid" + }, + "con": { + "const": "value" + }, + "con_empty": { + "const": "" + } + } } - } + ] }, "tests": [ { diff --git a/tests/fixtures/enum.json b/tests/fixtures/enum.json index a3f541f..4273d45 100644 --- a/tests/fixtures/enum.json +++ b/tests/fixtures/enum.json @@ -1,12 +1,16 @@ [ { "description": "simple enum validation", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "enum": [ - 1, - 2, - 3 + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "enum": [ + 1, + 2, + 3 + ] + } ] }, "tests": [ @@ -24,20 +28,24 @@ }, { "description": "heterogeneous enum validation", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "enum": [ - 6, - "foo", - [], - true, + "database": { + "schemas": [ { - "foo": 12 + "$schema": "https://json-schema.org/draft/2020-12/schema", + "enum": [ + 6, + "foo", + [], + true, + { + "foo": 12 + } + ], + "properties": { + "foo": {} + } } - ], - "properties": { - "foo": {} - } + ] }, "tests": [ { @@ -76,11 +84,15 @@ }, { "description": "heterogeneous enum-with-null validation", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "enum": [ - 6, - null + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "enum": [ + 6, + null + ] + } ] }, "tests": [ @@ -103,23 +115,27 @@ }, { "description": "enums in properties", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "type": "object", - "properties": { - "foo": { - "enum": [ - "foo" - ] - }, - "bar": { - "enum": [ + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "foo": { + "enum": [ + "foo" + ] + }, + "bar": { + "enum": [ + "bar" + ] + } + }, + "required": [ "bar" ] } - }, - "required": [ - "bar" ] }, "tests": [ @@ -170,11 +186,15 @@ }, { "description": "enum with escaped characters", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "enum": [ - "foo\nbar", - "foo\rbar" + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "enum": [ + "foo\nbar", + "foo\rbar" + ] + } ] }, "tests": [ @@ -197,10 +217,14 @@ }, { "description": "enum with false does not match 0", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "enum": [ - false + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "enum": [ + false + ] + } ] }, "tests": [ @@ -223,12 +247,16 @@ }, { "description": "enum with [false] does not match [0]", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "enum": [ - [ - false - ] + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "enum": [ + [ + false + ] + ] + } ] }, "tests": [ @@ -257,10 +285,14 @@ }, { "description": "enum with true does not match 1", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "enum": [ - true + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "enum": [ + true + ] + } ] }, "tests": [ @@ -283,12 +315,16 @@ }, { "description": "enum with [true] does not match [1]", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "enum": [ - [ - true - ] + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "enum": [ + [ + true + ] + ] + } ] }, "tests": [ @@ -317,10 +353,14 @@ }, { "description": "enum with 0 does not match false", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "enum": [ - 0 + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "enum": [ + 0 + ] + } ] }, "tests": [ @@ -343,12 +383,16 @@ }, { "description": "enum with [0] does not match [false]", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "enum": [ - [ - 0 - ] + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "enum": [ + [ + 0 + ] + ] + } ] }, "tests": [ @@ -377,10 +421,14 @@ }, { "description": "enum with 1 does not match true", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "enum": [ - 1 + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "enum": [ + 1 + ] + } ] }, "tests": [ @@ -403,12 +451,16 @@ }, { "description": "enum with [1] does not match [true]", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "enum": [ - [ - 1 - ] + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "enum": [ + [ + 1 + ] + ] + } ] }, "tests": [ @@ -437,10 +489,14 @@ }, { "description": "nul characters in strings", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "enum": [ - "hello\u0000there" + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "enum": [ + "hello\u0000there" + ] + } ] }, "tests": [ @@ -458,14 +514,18 @@ }, { "description": "extensible: true allows extra properties in enum object match", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "enum": [ + "database": { + "schemas": [ { - "foo": 1 + "$schema": "https://json-schema.org/draft/2020-12/schema", + "enum": [ + { + "foo": 1 + } + ], + "extensible": true } - ], - "extensible": true + ] }, "tests": [ { diff --git a/tests/fixtures/exclusiveMaximum.json b/tests/fixtures/exclusiveMaximum.json index 05db233..9ab08fc 100644 --- a/tests/fixtures/exclusiveMaximum.json +++ b/tests/fixtures/exclusiveMaximum.json @@ -1,9 +1,13 @@ [ { "description": "exclusiveMaximum validation", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "exclusiveMaximum": 3.0 + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "exclusiveMaximum": 3.0 + } + ] }, "tests": [ { @@ -28,4 +32,4 @@ } ] } -] +] \ No newline at end of file diff --git a/tests/fixtures/exclusiveMinimum.json b/tests/fixtures/exclusiveMinimum.json index 00af9d7..3db1f68 100644 --- a/tests/fixtures/exclusiveMinimum.json +++ b/tests/fixtures/exclusiveMinimum.json @@ -1,9 +1,13 @@ [ { "description": "exclusiveMinimum validation", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "exclusiveMinimum": 1.1 + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "exclusiveMinimum": 1.1 + } + ] }, "tests": [ { @@ -28,4 +32,4 @@ } ] } -] +] \ No newline at end of file diff --git a/tests/fixtures/families.json b/tests/fixtures/families.json new file mode 100644 index 0000000..6bc294d --- /dev/null +++ b/tests/fixtures/families.json @@ -0,0 +1,202 @@ +[ + { + "description": "Entity families with dot patterns", + "database": { + "types": [ + { + "name": "entity", + "hierarchy": [ + "entity" + ], + "schemas": [ + { + "$id": "entity", + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "type": "string", + "const": "entity" + } + } + }, + { + "$id": "entity.light", + "$ref": "entity" + } + ] + }, + { + "name": "organization", + "hierarchy": [ + "entity", + "organization" + ], + "schemas": [ + { + "$id": "organization", + "$ref": "entity", + "properties": { + "name": { + "type": "string" + } + } + } + ] + }, + { + "name": "person", + "hierarchy": [ + "entity", + "organization", + "person" + ], + "schemas": [ + { + "$id": "person", + "$ref": "organization", + "properties": { + "first_name": { + "type": "string" + } + } + }, + { + "$id": "person.light", + "$ref": "person" + } + ] + } + ], + "puncs": [ + { + "name": "get_entities", + "schemas": [ + { + "$id": "get_entities.response", + "$family": "entity" + } + ] + }, + { + "name": "get_light_entities", + "schemas": [ + { + "$id": "get_light_entities.response", + "$family": "entity.light" + } + ] + } + ] + }, + "tests": [ + { + "description": "Family matches base entity", + "schema_id": "get_entities.response", + "data": { + "id": "1", + "type": "entity" + }, + "valid": true + }, + { + "description": "Family matches descendant person", + "schema_id": "get_entities.response", + "data": { + "id": "2", + "type": "person", + "name": "ACME", + "first_name": "John" + }, + "valid": true + }, + { + "description": "Dot pattern family matches entity.light", + "schema_id": "get_light_entities.response", + "data": { + "id": "3", + "type": "entity" + }, + "valid": true + }, + { + "description": "Dot pattern family matches person.light", + "schema_id": "get_light_entities.response", + "data": { + "id": "4", + "type": "person", + "name": "ACME", + "first_name": "John" + }, + "valid": true + }, + { + "description": "Dot pattern family excludes organization (missing .light schema, constraint violation)", + "schema_id": "get_light_entities.response", + "data": { + "id": "5", + "type": "organization", + "name": "ACME" + }, + "valid": false + } + ] + }, + { + "description": "Ad-hoc non-entity families (using normal json-schema object structures)", + "database": { + "puncs": [ + { + "name": "get_widgets", + "schemas": [ + { + "$id": "widget", + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "widget_type": { + "type": "string" + } + } + }, + { + "$id": "special_widget", + "$ref": "widget", + "properties": { + "special_feature": { + "type": "string" + } + } + }, + { + "$id": "get_widgets.response", + "$family": "widget" + } + ] + } + ] + }, + "tests": [ + { + "description": "Ad-hoc family does not implicitly match descendants (no magical implicit hierarchy on normal ad-hoc schemas)", + "schema_id": "get_widgets.response", + "data": { + "id": "1", + "widget_type": "special", + "special_feature": "yes" + }, + "valid": false, + "expect_errors": [ + { + "code": "FAMILY_MISMATCH", + "path": "" + } + ] + } + ] + } +] \ No newline at end of file diff --git a/tests/fixtures/format.json b/tests/fixtures/format.json index 4355048..d1eadb2 100644 --- a/tests/fixtures/format.json +++ b/tests/fixtures/format.json @@ -1,9 +1,13 @@ [ { "description": "validation of date-time strings", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "format": "date-time" + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "format": "date-time" + } + ] }, "tests": [ { @@ -140,9 +144,13 @@ }, { "description": "validation of date strings", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "format": "date" + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "format": "date" + } + ] }, "tests": [ { @@ -389,9 +397,13 @@ }, { "description": "validation of duration strings", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "format": "duration" + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "format": "duration" + } + ] }, "tests": [ { @@ -528,9 +540,13 @@ }, { "description": "\\a is not an ECMA 262 control escape", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "format": "regex" + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "format": "regex" + } + ] }, "tests": [ { @@ -542,9 +558,13 @@ }, { "description": "validation of e-mail addresses", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "format": "email" + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "format": "email" + } + ] }, "tests": [ { @@ -671,9 +691,13 @@ }, { "description": "validation of host names", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "format": "hostname" + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "format": "hostname" + } + ] }, "tests": [ { @@ -801,9 +825,13 @@ }, { "description": "validation of A-label (punycode) host names", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "format": "hostname" + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "format": "hostname" + } + ] }, "tests": [ { @@ -1030,9 +1058,13 @@ }, { "description": "validation of an internationalized e-mail addresses", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "format": "idn-email" + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "format": "idn-email" + } + ] }, "tests": [ { @@ -1089,9 +1121,13 @@ }, { "description": "validation of internationalized host names", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "format": "idn-hostname" + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "format": "idn-hostname" + } + ] }, "tests": [ { @@ -1430,9 +1466,13 @@ "quote": "Whenever dots are used as label separators, the following characters MUST be recognized as dots: U+002E (full stop), U+3002 (ideographic full stop), U+FF0E (fullwidth full stop), U+FF61(halfwidth ideographic full stop)" } ], - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "format": "idn-hostname" + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "format": "idn-hostname" + } + ] }, "tests": [ { @@ -1539,9 +1579,13 @@ }, { "description": "validation of IP addresses", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "format": "ipv4" + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "format": "ipv4" + } + ] }, "tests": [ { @@ -1629,9 +1673,13 @@ }, { "description": "validation of IPv6 addresses", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "format": "ipv6" + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "format": "ipv6" + } + ] }, "tests": [ { @@ -1838,9 +1886,13 @@ }, { "description": "validation of IRI References", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "format": "iri-reference" + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "format": "iri-reference" + } + ] }, "tests": [ { @@ -1912,9 +1964,13 @@ }, { "description": "validation of IRIs", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "format": "iri" + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "format": "iri" + } + ] }, "tests": [ { @@ -1996,9 +2052,13 @@ }, { "description": "validation of JSON-pointers (JSON String Representation)", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "format": "json-pointer" + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "format": "json-pointer" + } + ] }, "tests": [ { @@ -2195,9 +2255,13 @@ }, { "description": "validation of regular expressions", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "format": "regex" + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "format": "regex" + } + ] }, "tests": [ { @@ -2244,9 +2308,13 @@ }, { "description": "validation of Relative JSON Pointers (RJP)", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "format": "relative-json-pointer" + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "format": "relative-json-pointer" + } + ] }, "tests": [ { @@ -2343,9 +2411,13 @@ }, { "description": "validation of time strings", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "format": "time" + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "format": "time" + } + ] }, "tests": [ { @@ -2582,9 +2654,13 @@ }, { "description": "unknown format", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "format": "unknown" + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "format": "unknown" + } + ] }, "tests": [ { @@ -2626,9 +2702,13 @@ }, { "description": "validation of URI References", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "format": "uri-reference" + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "format": "uri-reference" + } + ] }, "tests": [ { @@ -2710,9 +2790,13 @@ }, { "description": "format: uri-template", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "format": "uri-template" + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "format": "uri-template" + } + ] }, "tests": [ { @@ -2769,9 +2853,13 @@ }, { "description": "validation of URIs", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "format": "uri" + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "format": "uri" + } + ] }, "tests": [ { @@ -2958,9 +3046,13 @@ }, { "description": "uuid format", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "format": "uuid" + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "format": "uuid" + } + ] }, "tests": [ { @@ -3077,9 +3169,13 @@ }, { "description": "period format", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "format": "period" + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "format": "period" + } + ] }, "tests": [ { diff --git a/tests/fixtures/if-then-else.json b/tests/fixtures/if-then-else.json index cee228a..e4f2b33 100644 --- a/tests/fixtures/if-then-else.json +++ b/tests/fixtures/if-then-else.json @@ -1,11 +1,15 @@ [ { "description": "ignore if without then or else", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "if": { - "const": 0 - } + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "if": { + "const": 0 + } + } + ] }, "tests": [ { @@ -22,11 +26,15 @@ }, { "description": "ignore then without if", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "then": { - "const": 0 - } + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "then": { + "const": 0 + } + } + ] }, "tests": [ { @@ -43,11 +51,15 @@ }, { "description": "ignore else without if", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "else": { - "const": 0 - } + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "else": { + "const": 0 + } + } + ] }, "tests": [ { @@ -64,14 +76,18 @@ }, { "description": "if and then without else", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "if": { - "exclusiveMaximum": 0 - }, - "then": { - "minimum": -10 - } + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "if": { + "exclusiveMaximum": 0 + }, + "then": { + "minimum": -10 + } + } + ] }, "tests": [ { @@ -93,14 +109,18 @@ }, { "description": "if and else without then", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "if": { - "exclusiveMaximum": 0 - }, - "else": { - "multipleOf": 2 - } + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "if": { + "exclusiveMaximum": 0 + }, + "else": { + "multipleOf": 2 + } + } + ] }, "tests": [ { @@ -122,17 +142,21 @@ }, { "description": "validate against correct branch, then vs else", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "if": { - "exclusiveMaximum": 0 - }, - "then": { - "minimum": -10 - }, - "else": { - "multipleOf": 2 - } + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "if": { + "exclusiveMaximum": 0 + }, + "then": { + "minimum": -10 + }, + "else": { + "multipleOf": 2 + } + } + ] }, "tests": [ { @@ -159,23 +183,27 @@ }, { "description": "non-interference across combined schemas", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "allOf": [ + "database": { + "schemas": [ { - "if": { - "exclusiveMaximum": 0 - } - }, - { - "then": { - "minimum": -10 - } - }, - { - "else": { - "multipleOf": 2 - } + "$schema": "https://json-schema.org/draft/2020-12/schema", + "allOf": [ + { + "if": { + "exclusiveMaximum": 0 + } + }, + { + "then": { + "minimum": -10 + } + }, + { + "else": { + "multipleOf": 2 + } + } + ] } ] }, @@ -194,15 +222,19 @@ }, { "description": "if with boolean schema true", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "if": true, - "then": { - "const": "then" - }, - "else": { - "const": "else" - } + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "if": true, + "then": { + "const": "then" + }, + "else": { + "const": "else" + } + } + ] }, "tests": [ { @@ -219,15 +251,19 @@ }, { "description": "if with boolean schema false", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "if": false, - "then": { - "const": "then" - }, - "else": { - "const": "else" - } + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "if": false, + "then": { + "const": "then" + }, + "else": { + "const": "else" + } + } + ] }, "tests": [ { @@ -244,17 +280,21 @@ }, { "description": "if appears at the end when serialized (keyword processing sequence)", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "then": { - "const": "yes" - }, - "else": { - "const": "other" - }, - "if": { - "maxLength": 4 - } + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "then": { + "const": "yes" + }, + "else": { + "const": "other" + }, + "if": { + "maxLength": 4 + } + } + ] }, "tests": [ { @@ -281,11 +321,15 @@ }, { "description": "then: false fails when condition matches", - "schema": { - "if": { - "const": 1 - }, - "then": false + "database": { + "schemas": [ + { + "if": { + "const": 1 + }, + "then": false + } + ] }, "tests": [ { @@ -302,11 +346,15 @@ }, { "description": "else: false fails when condition does not match", - "schema": { - "if": { - "const": 1 - }, - "else": false + "database": { + "schemas": [ + { + "if": { + "const": 1 + }, + "else": false + } + ] }, "tests": [ { @@ -323,29 +371,33 @@ }, { "description": "extensible: true allows extra properties in if-then-else", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "if": { - "properties": { - "foo": { - "const": 1 - } - }, - "required": [ - "foo" - ] - }, - "then": { - "properties": { - "bar": { - "const": 2 - } - }, - "required": [ - "bar" - ] - }, - "extensible": true + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "if": { + "properties": { + "foo": { + "const": 1 + } + }, + "required": [ + "foo" + ] + }, + "then": { + "properties": { + "bar": { + "const": 2 + } + }, + "required": [ + "bar" + ] + }, + "extensible": true + } + ] }, "tests": [ { @@ -361,25 +413,29 @@ }, { "description": "strict by default with if-then properties", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "if": { - "properties": { - "foo": { - "const": 1 - } - }, - "required": [ - "foo" - ] - }, - "then": { - "properties": { - "bar": { - "const": 2 + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "if": { + "properties": { + "foo": { + "const": 1 + } + }, + "required": [ + "foo" + ] + }, + "then": { + "properties": { + "bar": { + "const": 2 + } + } } } - } + ] }, "tests": [ { diff --git a/tests/fixtures/items.json b/tests/fixtures/items.json index efe5383..fc54901 100644 --- a/tests/fixtures/items.json +++ b/tests/fixtures/items.json @@ -1,11 +1,15 @@ [ { "description": "a schema given for items", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "items": { - "type": "integer" - } + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "items": { + "type": "integer" + } + } + ] }, "tests": [ { @@ -44,9 +48,13 @@ }, { "description": "items with boolean schema (true)", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "items": true + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "items": true + } + ] }, "tests": [ { @@ -67,9 +75,13 @@ }, { "description": "items with boolean schema (false)", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "items": false + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "items": false + } + ] }, "tests": [ { @@ -90,39 +102,43 @@ }, { "description": "items and subitems", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$defs": { - "item": { + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$defs": { + "item": { + "type": "array", + "items": false, + "prefixItems": [ + { + "$ref": "#/$defs/sub-item" + }, + { + "$ref": "#/$defs/sub-item" + } + ] + }, + "sub-item": { + "type": "object", + "required": [ + "foo" + ] + } + }, "type": "array", "items": false, "prefixItems": [ { - "$ref": "#/$defs/sub-item" + "$ref": "#/$defs/item" }, { - "$ref": "#/$defs/sub-item" + "$ref": "#/$defs/item" + }, + { + "$ref": "#/$defs/item" } ] - }, - "sub-item": { - "type": "object", - "required": [ - "foo" - ] - } - }, - "type": "array", - "items": false, - "prefixItems": [ - { - "$ref": "#/$defs/item" - }, - { - "$ref": "#/$defs/item" - }, - { - "$ref": "#/$defs/item" } ] }, @@ -301,21 +317,25 @@ }, { "description": "nested items", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "type": "array", - "items": { - "type": "array", - "items": { + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "array", "items": { "type": "array", "items": { - "type": "number" + "type": "array", + "items": { + "type": "array", + "items": { + "type": "number" + } + } } } } - } + ] }, "tests": [ { @@ -418,14 +438,18 @@ }, { "description": "prefixItems with no additional items allowed", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "prefixItems": [ - {}, - {}, - {} - ], - "items": false + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "prefixItems": [ + {}, + {}, + {} + ], + "items": false + } + ] }, "tests": [ { @@ -471,20 +495,24 @@ }, { "description": "items does not look in applicators, valid case", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "allOf": [ + "database": { + "schemas": [ { - "prefixItems": [ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "allOf": [ { - "minimum": 3 + "prefixItems": [ + { + "minimum": 3 + } + ] } - ] + ], + "items": { + "minimum": 5 + } } - ], - "items": { - "minimum": 5 - } + ] }, "tests": [ { @@ -507,16 +535,20 @@ }, { "description": "prefixItems validation adjusts the starting index for items", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "prefixItems": [ + "database": { + "schemas": [ { - "type": "string" + "$schema": "https://json-schema.org/draft/2020-12/schema", + "prefixItems": [ + { + "type": "string" + } + ], + "items": { + "type": "integer" + } } - ], - "items": { - "type": "integer" - } + ] }, "tests": [ { @@ -540,12 +572,16 @@ }, { "description": "items with heterogeneous array", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "prefixItems": [ - {} - ], - "items": false + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "prefixItems": [ + {} + ], + "items": false + } + ] }, "tests": [ { @@ -568,11 +604,15 @@ }, { "description": "items with null instance elements", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "items": { - "type": "null" - } + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "items": { + "type": "null" + } + } + ] }, "tests": [ { @@ -586,10 +626,14 @@ }, { "description": "extensible: true allows extra items (when items is false)", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "items": false, - "extensible": true + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "items": false, + "extensible": true + } + ] }, "tests": [ { @@ -603,12 +647,16 @@ }, { "description": "extensible: true allows extra properties for items", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "items": { - "minimum": 5 - }, - "extensible": true + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "items": { + "minimum": 5 + }, + "extensible": true + } + ] }, "tests": [ { @@ -630,10 +678,14 @@ }, { "description": "array: simple extensible array", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "type": "array", - "extensible": true + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "array", + "extensible": true + } + ] }, "tests": [ { @@ -653,10 +705,14 @@ }, { "description": "array: strict array", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "type": "array", - "extensible": false + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "array", + "extensible": false + } + ] }, "tests": [ { @@ -675,12 +731,16 @@ }, { "description": "array: items extensible", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "type": "array", - "items": { - "extensible": true - } + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "array", + "items": { + "extensible": true + } + } + ] }, "tests": [ { @@ -701,13 +761,17 @@ }, { "description": "array: items strict", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "type": "array", - "items": { - "type": "object", - "extensible": false - } + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "array", + "items": { + "type": "object", + "extensible": false + } + } + ] }, "tests": [ { diff --git a/tests/fixtures/masking.json b/tests/fixtures/masking.json index bf28817..dad4082 100644 --- a/tests/fixtures/masking.json +++ b/tests/fixtures/masking.json @@ -1,21 +1,25 @@ [ { "description": "Masking Properties", - "schema": { - "$id": "mask_properties", - "type": "object", - "properties": { - "foo": { - "type": "string" - }, - "bar": { - "type": "integer" + "database": { + "schemas": [ + { + "$id": "mask_properties", + "type": "object", + "properties": { + "foo": { + "type": "string" + }, + "bar": { + "type": "integer" + } + }, + "required": [ + "foo" + ], + "extensible": false } - }, - "required": [ - "foo" - ], - "extensible": false + ] }, "tests": [ { @@ -58,21 +62,25 @@ }, { "description": "Masking Nested Objects", - "schema": { - "$id": "mask_nested", - "type": "object", - "properties": { - "meta": { + "database": { + "schemas": [ + { + "$id": "mask_nested", "type": "object", "properties": { - "id": { - "type": "integer" + "meta": { + "type": "object", + "properties": { + "id": { + "type": "integer" + } + }, + "extensible": false } }, "extensible": false } - }, - "extensible": false + ] }, "tests": [ { @@ -95,18 +103,22 @@ }, { "description": "Masking Arrays", - "schema": { - "$id": "mask_arrays", - "type": "object", - "properties": { - "tags": { - "type": "array", - "items": { - "type": "string" - } + "database": { + "schemas": [ + { + "$id": "mask_arrays", + "type": "object", + "properties": { + "tags": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "extensible": false } - }, - "extensible": false + ] }, "tests": [ { @@ -129,23 +141,27 @@ }, { "description": "Masking Tuple Arrays (prefixItems)", - "schema": { - "$id": "mask_tuple", - "type": "object", - "properties": { - "coord": { - "type": "array", - "prefixItems": [ - { - "type": "number" - }, - { - "type": "number" + "database": { + "schemas": [ + { + "$id": "mask_tuple", + "type": "object", + "properties": { + "coord": { + "type": "array", + "prefixItems": [ + { + "type": "number" + }, + { + "type": "number" + } + ] } - ] + }, + "extensible": false } - }, - "extensible": false + ] }, "tests": [ { diff --git a/tests/fixtures/maxContains.json b/tests/fixtures/maxContains.json index 80f7ef1..e81a60a 100644 --- a/tests/fixtures/maxContains.json +++ b/tests/fixtures/maxContains.json @@ -1,10 +1,14 @@ [ { "description": "maxContains without contains is ignored", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "maxContains": 1, - "extensible": true + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "maxContains": 1, + "extensible": true + } + ] }, "tests": [ { @@ -26,13 +30,17 @@ }, { "description": "maxContains with contains", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "contains": { - "const": 1 - }, - "maxContains": 1, - "extensible": true + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "contains": { + "const": 1 + }, + "maxContains": 1, + "extensible": true + } + ] }, "tests": [ { @@ -76,13 +84,17 @@ }, { "description": "maxContains with contains, value with a decimal", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "contains": { - "const": 1 - }, - "maxContains": 1.0, - "extensible": true + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "contains": { + "const": 1 + }, + "maxContains": 1.0, + "extensible": true + } + ] }, "tests": [ { @@ -104,14 +116,18 @@ }, { "description": "minContains < maxContains", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "contains": { - "const": 1 - }, - "minContains": 1, - "maxContains": 3, - "extensible": true + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "contains": { + "const": 1 + }, + "minContains": 1, + "maxContains": 3, + "extensible": true + } + ] }, "tests": [ { @@ -141,13 +157,17 @@ }, { "description": "extensible: true allows non-matching items in maxContains", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "contains": { - "const": 1 - }, - "maxContains": 1, - "extensible": true + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "contains": { + "const": 1 + }, + "maxContains": 1, + "extensible": true + } + ] }, "tests": [ { diff --git a/tests/fixtures/maxItems.json b/tests/fixtures/maxItems.json index 3adfe00..35547e2 100644 --- a/tests/fixtures/maxItems.json +++ b/tests/fixtures/maxItems.json @@ -1,10 +1,14 @@ [ { "description": "maxItems validation", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "maxItems": 2, - "extensible": true + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "maxItems": 2, + "extensible": true + } + ] }, "tests": [ { @@ -40,10 +44,14 @@ }, { "description": "maxItems validation with a decimal", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "maxItems": 2.0, - "extensible": true + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "maxItems": 2.0, + "extensible": true + } + ] }, "tests": [ { @@ -66,10 +74,14 @@ }, { "description": "extensible: true allows extra items in maxItems (but counted)", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "maxItems": 2, - "extensible": true + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "maxItems": 2, + "extensible": true + } + ] }, "tests": [ { diff --git a/tests/fixtures/maxLength.json b/tests/fixtures/maxLength.json index 7462726..f4d55ac 100644 --- a/tests/fixtures/maxLength.json +++ b/tests/fixtures/maxLength.json @@ -1,9 +1,13 @@ [ { "description": "maxLength validation", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "maxLength": 2 + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "maxLength": 2 + } + ] }, "tests": [ { @@ -35,9 +39,13 @@ }, { "description": "maxLength validation with a decimal", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "maxLength": 2.0 + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "maxLength": 2.0 + } + ] }, "tests": [ { @@ -52,4 +60,4 @@ } ] } -] +] \ No newline at end of file diff --git a/tests/fixtures/maxProperties.json b/tests/fixtures/maxProperties.json index c078bd2..4229901 100644 --- a/tests/fixtures/maxProperties.json +++ b/tests/fixtures/maxProperties.json @@ -1,10 +1,14 @@ [ { "description": "maxProperties validation", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "maxProperties": 2, - "extensible": true + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "maxProperties": 2, + "extensible": true + } + ] }, "tests": [ { @@ -54,10 +58,14 @@ }, { "description": "maxProperties validation with a decimal", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "maxProperties": 2.0, - "extensible": true + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "maxProperties": 2.0, + "extensible": true + } + ] }, "tests": [ { @@ -80,10 +88,14 @@ }, { "description": "maxProperties = 0 means the object is empty", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "maxProperties": 0, - "extensible": true + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "maxProperties": 0, + "extensible": true + } + ] }, "tests": [ { @@ -102,10 +114,14 @@ }, { "description": "extensible: true allows extra properties in maxProperties (though maxProperties still counts them!)", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "maxProperties": 2, - "extensible": true + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "maxProperties": 2, + "extensible": true + } + ] }, "tests": [ { diff --git a/tests/fixtures/maximum.json b/tests/fixtures/maximum.json index b99a541..8643998 100644 --- a/tests/fixtures/maximum.json +++ b/tests/fixtures/maximum.json @@ -1,9 +1,13 @@ [ { "description": "maximum validation", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "maximum": 3.0 + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "maximum": 3.0 + } + ] }, "tests": [ { @@ -30,11 +34,15 @@ }, { "description": "maximum validation with unsigned integer", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "maximum": 300 + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "maximum": 300 + } + ] }, - "tests": [ + "tests": [ { "description": "below the maximum is invalid", "data": 299.97, @@ -57,4 +65,4 @@ } ] } -] +] \ No newline at end of file diff --git a/tests/fixtures/merge.json b/tests/fixtures/merge.json index f2427c7..65703b7 100644 --- a/tests/fixtures/merge.json +++ b/tests/fixtures/merge.json @@ -1,23 +1,27 @@ [ { "description": "merging: properties accumulate", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$defs": { - "base": { + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$defs": { + "base": { + "properties": { + "base_prop": { + "type": "string" + } + } + } + }, + "$ref": "#/$defs/base", "properties": { - "base_prop": { + "child_prop": { "type": "string" } } } - }, - "$ref": "#/$defs/base", - "properties": { - "child_prop": { - "type": "string" - } - } + ] }, "tests": [ { @@ -46,28 +50,32 @@ }, { "description": "merging: required fields accumulate", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$defs": { - "base": { + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$defs": { + "base": { + "properties": { + "a": { + "type": "string" + } + }, + "required": [ + "a" + ] + } + }, + "$ref": "#/$defs/base", "properties": { - "a": { + "b": { "type": "string" } }, "required": [ - "a" + "b" ] } - }, - "$ref": "#/$defs/base", - "properties": { - "b": { - "type": "string" - } - }, - "required": [ - "b" ] }, "tests": [ @@ -109,36 +117,40 @@ }, { "description": "merging: dependencies accumulate", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$defs": { - "base": { + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$defs": { + "base": { + "properties": { + "trigger": { + "type": "string" + }, + "base_dep": { + "type": "string" + } + }, + "dependencies": { + "trigger": [ + "base_dep" + ] + } + } + }, + "$ref": "#/$defs/base", "properties": { - "trigger": { - "type": "string" - }, - "base_dep": { + "child_dep": { "type": "string" } }, "dependencies": { "trigger": [ - "base_dep" + "child_dep" ] } } - }, - "$ref": "#/$defs/base", - "properties": { - "child_dep": { - "type": "string" - } - }, - "dependencies": { - "trigger": [ - "child_dep" - ] - } + ] }, "tests": [ { @@ -182,32 +194,36 @@ }, { "description": "merging: form and display do NOT merge", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$defs": { - "base": { + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$defs": { + "base": { + "properties": { + "a": { + "type": "string" + }, + "b": { + "type": "string" + } + }, + "form": [ + "a", + "b" + ] + } + }, + "$ref": "#/$defs/base", "properties": { - "a": { - "type": "string" - }, - "b": { + "c": { "type": "string" } }, "form": [ - "a", - "b" + "c" ] } - }, - "$ref": "#/$defs/base", - "properties": { - "c": { - "type": "string" - } - }, - "form": [ - "c" ] }, "tests": [ diff --git a/tests/fixtures/minContains.json b/tests/fixtures/minContains.json index 55fc308..a4a0e74 100644 --- a/tests/fixtures/minContains.json +++ b/tests/fixtures/minContains.json @@ -1,10 +1,14 @@ [ { "description": "minContains without contains is ignored", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "minContains": 1, - "extensible": true + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "minContains": 1, + "extensible": true + } + ] }, "tests": [ { @@ -23,13 +27,17 @@ }, { "description": "minContains=1 with contains", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "contains": { - "const": 1 - }, - "minContains": 1, - "extensible": true + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "contains": { + "const": 1 + }, + "minContains": 1, + "extensible": true + } + ] }, "tests": [ { @@ -71,13 +79,17 @@ }, { "description": "minContains=2 with contains", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "contains": { - "const": 1 - }, - "minContains": 2, - "extensible": true + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "contains": { + "const": 1 + }, + "minContains": 2, + "extensible": true + } + ] }, "tests": [ { @@ -130,13 +142,17 @@ }, { "description": "minContains=2 with contains with a decimal value", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "contains": { - "const": 1 - }, - "minContains": 2.0, - "extensible": true + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "contains": { + "const": 1 + }, + "minContains": 2.0, + "extensible": true + } + ] }, "tests": [ { @@ -158,14 +174,18 @@ }, { "description": "maxContains = minContains", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "contains": { - "const": 1 - }, - "maxContains": 2, - "minContains": 2, - "extensible": true + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "contains": { + "const": 1 + }, + "maxContains": 2, + "minContains": 2, + "extensible": true + } + ] }, "tests": [ { @@ -201,14 +221,18 @@ }, { "description": "maxContains < minContains", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "contains": { - "const": 1 - }, - "maxContains": 1, - "minContains": 3, - "extensible": true + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "contains": { + "const": 1 + }, + "maxContains": 1, + "minContains": 3, + "extensible": true + } + ] }, "tests": [ { @@ -244,13 +268,17 @@ }, { "description": "minContains = 0", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "contains": { - "const": 1 - }, - "minContains": 0, - "extensible": true + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "contains": { + "const": 1 + }, + "minContains": 0, + "extensible": true + } + ] }, "tests": [ { @@ -269,14 +297,18 @@ }, { "description": "minContains = 0 with maxContains", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "contains": { - "const": 1 - }, - "minContains": 0, - "maxContains": 1, - "extensible": true + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "contains": { + "const": 1 + }, + "minContains": 0, + "maxContains": 1, + "extensible": true + } + ] }, "tests": [ { @@ -303,13 +335,17 @@ }, { "description": "extensible: true allows non-matching items in minContains", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "contains": { - "const": 1 - }, - "minContains": 1, - "extensible": true + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "contains": { + "const": 1 + }, + "minContains": 1, + "extensible": true + } + ] }, "tests": [ { diff --git a/tests/fixtures/minItems.json b/tests/fixtures/minItems.json index ff49550..ec28842 100644 --- a/tests/fixtures/minItems.json +++ b/tests/fixtures/minItems.json @@ -1,10 +1,14 @@ [ { "description": "minItems validation", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "minItems": 1, - "extensible": true + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "minItems": 1, + "extensible": true + } + ] }, "tests": [ { @@ -36,10 +40,14 @@ }, { "description": "minItems validation with a decimal", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "minItems": 1.0, - "extensible": true + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "minItems": 1.0, + "extensible": true + } + ] }, "tests": [ { @@ -59,10 +67,14 @@ }, { "description": "extensible: true allows extra items in minItems", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "minItems": 1, - "extensible": true + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "minItems": 1, + "extensible": true + } + ] }, "tests": [ { diff --git a/tests/fixtures/minLength.json b/tests/fixtures/minLength.json index 5076c5a..1318eee 100644 --- a/tests/fixtures/minLength.json +++ b/tests/fixtures/minLength.json @@ -1,9 +1,13 @@ [ { "description": "minLength validation", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "minLength": 2 + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "minLength": 2 + } + ] }, "tests": [ { @@ -35,9 +39,13 @@ }, { "description": "minLength validation with a decimal", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "minLength": 2.0 + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "minLength": 2.0 + } + ] }, "tests": [ { @@ -52,4 +60,4 @@ } ] } -] +] \ No newline at end of file diff --git a/tests/fixtures/minProperties.json b/tests/fixtures/minProperties.json index 153aad7..34723a1 100644 --- a/tests/fixtures/minProperties.json +++ b/tests/fixtures/minProperties.json @@ -1,10 +1,14 @@ [ { "description": "minProperties validation", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "minProperties": 1, - "extensible": true + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "minProperties": 1, + "extensible": true + } + ] }, "tests": [ { @@ -46,10 +50,14 @@ }, { "description": "minProperties validation with a decimal", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "minProperties": 1.0, - "extensible": true + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "minProperties": 1.0, + "extensible": true + } + ] }, "tests": [ { @@ -69,10 +77,14 @@ }, { "description": "extensible: true allows extra properties in minProperties", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "minProperties": 1, - "extensible": true + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "minProperties": 1, + "extensible": true + } + ] }, "tests": [ { diff --git a/tests/fixtures/minimum.json b/tests/fixtures/minimum.json index dc44052..288aab3 100644 --- a/tests/fixtures/minimum.json +++ b/tests/fixtures/minimum.json @@ -1,9 +1,13 @@ [ { "description": "minimum validation", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "minimum": 1.1 + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "minimum": 1.1 + } + ] }, "tests": [ { @@ -30,9 +34,13 @@ }, { "description": "minimum validation with signed integer", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "minimum": -2 + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "minimum": -2 + } + ] }, "tests": [ { @@ -72,4 +80,4 @@ } ] } -] +] \ No newline at end of file diff --git a/tests/fixtures/multipleOf.json b/tests/fixtures/multipleOf.json index cddadc9..c614d7b 100644 --- a/tests/fixtures/multipleOf.json +++ b/tests/fixtures/multipleOf.json @@ -1,9 +1,13 @@ [ { "description": "by int", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "multipleOf": 2 + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "multipleOf": 2 + } + ] }, "tests": [ { @@ -25,9 +29,13 @@ }, { "description": "by number", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "multipleOf": 1.5 + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "multipleOf": 1.5 + } + ] }, "tests": [ { @@ -49,9 +57,13 @@ }, { "description": "by small number", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "multipleOf": 0.0001 + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "multipleOf": 0.0001 + } + ] }, "tests": [ { @@ -68,10 +80,14 @@ }, { "description": "small multiple of large integer", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "type": "integer", - "multipleOf": 1e-8 + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "integer", + "multipleOf": 1e-8 + } + ] }, "tests": [ { diff --git a/tests/fixtures/not.json b/tests/fixtures/not.json index 8a74e7f..33a9768 100644 --- a/tests/fixtures/not.json +++ b/tests/fixtures/not.json @@ -1,11 +1,15 @@ [ { "description": "not", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "not": { - "type": "integer" - } + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "not": { + "type": "integer" + } + } + ] }, "tests": [ { @@ -22,14 +26,18 @@ }, { "description": "not multiple types", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "not": { - "type": [ - "integer", - "boolean" - ] - } + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "not": { + "type": [ + "integer", + "boolean" + ] + } + } + ] }, "tests": [ { @@ -51,17 +59,21 @@ }, { "description": "not more complex schema", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "not": { - "type": "object", - "properties": { - "foo": { - "type": "string" - } + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "not": { + "type": "object", + "properties": { + "foo": { + "type": "string" + } + } + }, + "extensible": true } - }, - "extensible": true + ] }, "tests": [ { @@ -87,13 +99,17 @@ }, { "description": "forbidden property", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "properties": { - "foo": { - "not": {} + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "foo": { + "not": {} + } + } } - } + ] }, "tests": [ { @@ -113,9 +129,13 @@ }, { "description": "forbid everything with empty schema", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "not": {} + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "not": {} + } + ] }, "tests": [ { @@ -171,9 +191,13 @@ }, { "description": "forbid everything with boolean schema true", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "not": true + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "not": true + } + ] }, "tests": [ { @@ -229,10 +253,14 @@ }, { "description": "allow everything with boolean schema false", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "not": false, - "extensible": true + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "not": false, + "extensible": true + } + ] }, "tests": [ { @@ -288,11 +316,15 @@ }, { "description": "double negation", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "not": { - "not": {} - } + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "not": { + "not": {} + } + } + ] }, "tests": [ { @@ -304,12 +336,16 @@ }, { "description": "extensible: true allows extra properties in not", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "not": { - "type": "integer" - }, - "extensible": true + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "not": { + "type": "integer" + }, + "extensible": true + } + ] }, "tests": [ { @@ -323,11 +359,15 @@ }, { "description": "extensible: false (default) forbids extra properties in not", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "not": { - "type": "integer" - } + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "not": { + "type": "integer" + } + } + ] }, "tests": [ { @@ -341,17 +381,21 @@ }, { "description": "property next to not (extensible: true)", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "properties": { - "bar": { - "type": "string" + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "bar": { + "type": "string" + } + }, + "not": { + "type": "integer" + }, + "extensible": true } - }, - "not": { - "type": "integer" - }, - "extensible": true + ] }, "tests": [ { @@ -366,16 +410,20 @@ }, { "description": "property next to not (extensible: false)", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "properties": { - "bar": { - "type": "string" + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "bar": { + "type": "string" + } + }, + "not": { + "type": "integer" + } } - }, - "not": { - "type": "integer" - } + ] }, "tests": [ { diff --git a/tests/fixtures/oneOf.json b/tests/fixtures/oneOf.json index d8b288e..d10ce50 100644 --- a/tests/fixtures/oneOf.json +++ b/tests/fixtures/oneOf.json @@ -1,14 +1,18 @@ [ { "description": "oneOf", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "oneOf": [ + "database": { + "schemas": [ { - "type": "integer" - }, - { - "minimum": 2 + "$schema": "https://json-schema.org/draft/2020-12/schema", + "oneOf": [ + { + "type": "integer" + }, + { + "minimum": 2 + } + ] } ] }, @@ -37,15 +41,19 @@ }, { "description": "oneOf with base schema", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "type": "string", - "oneOf": [ + "database": { + "schemas": [ { - "minLength": 2 - }, - { - "maxLength": 4 + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "string", + "oneOf": [ + { + "minLength": 2 + }, + { + "maxLength": 4 + } + ] } ] }, @@ -69,12 +77,16 @@ }, { "description": "oneOf with boolean schemas, all true", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "oneOf": [ - true, - true, - true + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "oneOf": [ + true, + true, + true + ] + } ] }, "tests": [ @@ -87,12 +99,16 @@ }, { "description": "oneOf with boolean schemas, one true", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "oneOf": [ - true, - false, - false + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "oneOf": [ + true, + false, + false + ] + } ] }, "tests": [ @@ -105,12 +121,16 @@ }, { "description": "oneOf with boolean schemas, more than one true", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "oneOf": [ - true, - true, - false + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "oneOf": [ + true, + true, + false + ] + } ] }, "tests": [ @@ -123,12 +143,16 @@ }, { "description": "oneOf with boolean schemas, all false", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "oneOf": [ - false, - false, - false + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "oneOf": [ + false, + false, + false + ] + } ] }, "tests": [ @@ -141,27 +165,31 @@ }, { "description": "oneOf complex types", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "oneOf": [ + "database": { + "schemas": [ { - "properties": { - "bar": { - "type": "integer" + "$schema": "https://json-schema.org/draft/2020-12/schema", + "oneOf": [ + { + "properties": { + "bar": { + "type": "integer" + } + }, + "required": [ + "bar" + ] + }, + { + "properties": { + "foo": { + "type": "string" + } + }, + "required": [ + "foo" + ] } - }, - "required": [ - "bar" - ] - }, - { - "properties": { - "foo": { - "type": "string" - } - }, - "required": [ - "foo" ] } ] @@ -201,13 +229,17 @@ }, { "description": "oneOf with empty schema", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "oneOf": [ + "database": { + "schemas": [ { - "type": "number" - }, - {} + "$schema": "https://json-schema.org/draft/2020-12/schema", + "oneOf": [ + { + "type": "number" + }, + {} + ] + } ] }, "tests": [ @@ -225,25 +257,29 @@ }, { "description": "oneOf with required", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "type": "object", - "properties": { - "foo": true, - "bar": true, - "baz": true - }, - "oneOf": [ + "database": { + "schemas": [ { - "required": [ - "foo", - "bar" - ] - }, - { - "required": [ - "foo", - "baz" + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "foo": true, + "bar": true, + "baz": true + }, + "oneOf": [ + { + "required": [ + "foo", + "bar" + ] + }, + { + "required": [ + "foo", + "baz" + ] + } ] } ] @@ -294,21 +330,25 @@ }, { "description": "oneOf with required (extensible)", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "type": "object", - "extensible": true, - "oneOf": [ + "database": { + "schemas": [ { - "required": [ - "foo", - "bar" - ] - }, - { - "required": [ - "foo", - "baz" + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "extensible": true, + "oneOf": [ + { + "required": [ + "foo", + "bar" + ] + }, + { + "required": [ + "foo", + "baz" + ] + } ] } ] @@ -359,24 +399,28 @@ }, { "description": "oneOf with missing optional property", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "oneOf": [ + "database": { + "schemas": [ { - "properties": { - "bar": true, - "baz": true - }, - "required": [ - "bar" - ] - }, - { - "properties": { - "foo": true - }, - "required": [ - "foo" + "$schema": "https://json-schema.org/draft/2020-12/schema", + "oneOf": [ + { + "properties": { + "bar": true, + "baz": true + }, + "required": [ + "bar" + ] + }, + { + "properties": { + "foo": true + }, + "required": [ + "foo" + ] + } ] } ] @@ -415,13 +459,17 @@ }, { "description": "nested oneOf, to check validation semantics", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "oneOf": [ + "database": { + "schemas": [ { + "$schema": "https://json-schema.org/draft/2020-12/schema", "oneOf": [ { - "type": "null" + "oneOf": [ + { + "type": "null" + } + ] } ] } @@ -442,31 +490,35 @@ }, { "description": "extensible: true allows extra properties in oneOf", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "oneOf": [ + "database": { + "schemas": [ { - "properties": { - "bar": { - "type": "integer" + "$schema": "https://json-schema.org/draft/2020-12/schema", + "oneOf": [ + { + "properties": { + "bar": { + "type": "integer" + } + }, + "required": [ + "bar" + ] + }, + { + "properties": { + "foo": { + "type": "string" + } + }, + "required": [ + "foo" + ] } - }, - "required": [ - "bar" - ] - }, - { - "properties": { - "foo": { - "type": "string" - } - }, - "required": [ - "foo" - ] + ], + "extensible": true } - ], - "extensible": true + ] }, "tests": [ { diff --git a/tests/fixtures/pattern.json b/tests/fixtures/pattern.json index af0b8d8..0b731d7 100644 --- a/tests/fixtures/pattern.json +++ b/tests/fixtures/pattern.json @@ -1,9 +1,13 @@ [ { "description": "pattern validation", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "pattern": "^a*$" + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "pattern": "^a*$" + } + ] }, "tests": [ { @@ -50,9 +54,13 @@ }, { "description": "pattern is not anchored", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "pattern": "a+" + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "pattern": "a+" + } + ] }, "tests": [ { @@ -62,4 +70,4 @@ } ] } -] +] \ No newline at end of file diff --git a/tests/fixtures/patternProperties.json b/tests/fixtures/patternProperties.json index 1924e50..4a934a7 100644 --- a/tests/fixtures/patternProperties.json +++ b/tests/fixtures/patternProperties.json @@ -1,14 +1,18 @@ [ { "description": "patternProperties validates properties matching a regex", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "patternProperties": { - "f.*o": { - "type": "integer" + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "patternProperties": { + "f.*o": { + "type": "integer" + } + }, + "items": {} } - }, - "items": {} + ] }, "tests": [ { @@ -71,16 +75,20 @@ }, { "description": "multiple simultaneous patternProperties are validated", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "patternProperties": { - "a*": { - "type": "integer" - }, - "aaa*": { - "maximum": 20 + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "patternProperties": { + "a*": { + "type": "integer" + }, + "aaa*": { + "maximum": 20 + } + } } - } + ] }, "tests": [ { @@ -131,17 +139,21 @@ }, { "description": "regexes are not anchored by default and are case sensitive", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "patternProperties": { - "[0-9]{2,}": { - "type": "boolean" - }, - "X_": { - "type": "string" + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "patternProperties": { + "[0-9]{2,}": { + "type": "boolean" + }, + "X_": { + "type": "string" + } + }, + "extensible": true } - }, - "extensible": true + ] }, "tests": [ { @@ -176,12 +188,16 @@ }, { "description": "patternProperties with boolean schemas", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "patternProperties": { - "f.*": true, - "b.*": false - } + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "patternProperties": { + "f.*": true, + "b.*": false + } + } + ] }, "tests": [ { @@ -222,13 +238,17 @@ }, { "description": "patternProperties with null valued instance properties", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "patternProperties": { - "^.*bar$": { - "type": "null" + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "patternProperties": { + "^.*bar$": { + "type": "null" + } + } } - } + ] }, "tests": [ { @@ -242,14 +262,18 @@ }, { "description": "extensible: true allows extra properties NOT matching pattern", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "patternProperties": { - "f.*o": { - "type": "integer" + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "patternProperties": { + "f.*o": { + "type": "integer" + } + }, + "extensible": true } - }, - "extensible": true + ] }, "tests": [ { diff --git a/tests/fixtures/prefixItems.json b/tests/fixtures/prefixItems.json index 5271657..1a115e0 100644 --- a/tests/fixtures/prefixItems.json +++ b/tests/fixtures/prefixItems.json @@ -1,14 +1,18 @@ [ { "description": "a schema given for prefixItems", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "prefixItems": [ + "database": { + "schemas": [ { - "type": "integer" - }, - { - "type": "string" + "$schema": "https://json-schema.org/draft/2020-12/schema", + "prefixItems": [ + { + "type": "integer" + }, + { + "type": "string" + } + ] } ] }, @@ -63,11 +67,15 @@ }, { "description": "prefixItems with boolean schemas", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "prefixItems": [ - true, - false + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "prefixItems": [ + true, + false + ] + } ] }, "tests": [ @@ -95,14 +103,18 @@ }, { "description": "additional items are allowed by default", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "prefixItems": [ + "database": { + "schemas": [ { - "type": "integer" + "$schema": "https://json-schema.org/draft/2020-12/schema", + "prefixItems": [ + { + "type": "integer" + } + ], + "extensible": true } - ], - "extensible": true + ] }, "tests": [ { @@ -118,11 +130,15 @@ }, { "description": "prefixItems with null instance elements", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "prefixItems": [ + "database": { + "schemas": [ { - "type": "null" + "$schema": "https://json-schema.org/draft/2020-12/schema", + "prefixItems": [ + { + "type": "null" + } + ] } ] }, @@ -138,14 +154,18 @@ }, { "description": "extensible: true allows extra items with prefixItems", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "prefixItems": [ + "database": { + "schemas": [ { - "type": "integer" + "$schema": "https://json-schema.org/draft/2020-12/schema", + "prefixItems": [ + { + "type": "integer" + } + ], + "extensible": true } - ], - "extensible": true + ] }, "tests": [ { diff --git a/tests/fixtures/properties.json b/tests/fixtures/properties.json index 71a7544..0a092fe 100644 --- a/tests/fixtures/properties.json +++ b/tests/fixtures/properties.json @@ -1,16 +1,20 @@ [ { "description": "object properties validation", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "properties": { - "foo": { - "type": "integer" - }, - "bar": { - "type": "string" + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "foo": { + "type": "integer" + }, + "bar": { + "type": "string" + } + } } - } + ] }, "tests": [ { @@ -56,12 +60,16 @@ }, { "description": "properties with boolean schema", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "properties": { - "foo": true, - "bar": false - } + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "foo": true, + "bar": false + } + } + ] }, "tests": [ { @@ -95,28 +103,32 @@ }, { "description": "properties with escaped characters", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "properties": { - "foo\nbar": { - "type": "number" - }, - "foo\"bar": { - "type": "number" - }, - "foo\\bar": { - "type": "number" - }, - "foo\rbar": { - "type": "number" - }, - "foo\tbar": { - "type": "number" - }, - "foo\fbar": { - "type": "number" + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "foo\nbar": { + "type": "number" + }, + "foo\"bar": { + "type": "number" + }, + "foo\\bar": { + "type": "number" + }, + "foo\rbar": { + "type": "number" + }, + "foo\tbar": { + "type": "number" + }, + "foo\fbar": { + "type": "number" + } + } } - } + ] }, "tests": [ { @@ -147,13 +159,17 @@ }, { "description": "properties with null valued instance properties", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "properties": { - "foo": { - "type": "null" + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "foo": { + "type": "null" + } + } } - } + ] }, "tests": [ { @@ -168,23 +184,27 @@ { "description": "properties whose names are Javascript object property names", "comment": "Ensure JS implementations don't universally consider e.g. __proto__ to always be present in an object.", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "properties": { - "__proto__": { - "type": "number" - }, - "toString": { + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { - "length": { - "type": "string" + "__proto__": { + "type": "number" + }, + "toString": { + "properties": { + "length": { + "type": "string" + } + } + }, + "constructor": { + "type": "number" } } - }, - "constructor": { - "type": "number" } - } + ] }, "tests": [ { @@ -242,14 +262,18 @@ }, { "description": "extensible: true allows extra properties", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "properties": { - "foo": { - "type": "integer" + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "foo": { + "type": "integer" + } + }, + "extensible": true } - }, - "extensible": true + ] }, "tests": [ { @@ -264,13 +288,17 @@ }, { "description": "strict by default: extra properties invalid", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "properties": { - "foo": { - "type": "string" + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "foo": { + "type": "string" + } + } } - } + ] }, "tests": [ { @@ -285,17 +313,21 @@ }, { "description": "inheritance: nested object inherits strictness from strict parent", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "properties": { - "nested": { + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { - "foo": { - "type": "string" + "nested": { + "properties": { + "foo": { + "type": "string" + } + } } } } - } + ] }, "tests": [ { @@ -312,18 +344,22 @@ }, { "description": "override: nested object allows extra properties if extensible: true", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "properties": { - "nested": { - "extensible": true, + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { - "foo": { - "type": "string" + "nested": { + "extensible": true, + "properties": { + "foo": { + "type": "string" + } + } } } } - } + ] }, "tests": [ { @@ -340,18 +376,22 @@ }, { "description": "inheritance: nested object inherits looseness from loose parent", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "extensible": true, - "properties": { - "nested": { + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "extensible": true, "properties": { - "foo": { - "type": "string" + "nested": { + "properties": { + "foo": { + "type": "string" + } + } } } } - } + ] }, "tests": [ { @@ -368,19 +408,23 @@ }, { "description": "override: nested object enforces strictness if extensible: false", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "extensible": true, - "properties": { - "nested": { - "extensible": false, + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "extensible": true, "properties": { - "foo": { - "type": "string" + "nested": { + "extensible": false, + "properties": { + "foo": { + "type": "string" + } + } } } } - } + ] }, "tests": [ { @@ -397,20 +441,24 @@ }, { "description": "arrays: inline items inherit strictness from strict parent", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "properties": { - "list": { - "type": "array", - "items": { - "properties": { - "foo": { - "type": "string" + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "list": { + "type": "array", + "items": { + "properties": { + "foo": { + "type": "string" + } + } } } } } - } + ] }, "tests": [ { @@ -429,21 +477,25 @@ }, { "description": "arrays: inline items inherit looseness from loose parent", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "extensible": true, - "properties": { - "list": { - "type": "array", - "items": { - "properties": { - "foo": { - "type": "string" + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "extensible": true, + "properties": { + "list": { + "type": "array", + "items": { + "properties": { + "foo": { + "type": "string" + } + } } } } } - } + ] }, "tests": [ { diff --git a/tests/fixtures/propertyNames.json b/tests/fixtures/propertyNames.json index 6d14d8b..8d48dd3 100644 --- a/tests/fixtures/propertyNames.json +++ b/tests/fixtures/propertyNames.json @@ -1,12 +1,16 @@ [ { "description": "propertyNames validation", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "propertyNames": { - "maxLength": 3 - }, - "extensible": true + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "propertyNames": { + "maxLength": 3 + }, + "extensible": true + } + ] }, "tests": [ { @@ -54,12 +58,16 @@ }, { "description": "propertyNames validation with pattern", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "propertyNames": { - "pattern": "^a+$" - }, - "extensible": true + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "propertyNames": { + "pattern": "^a+$" + }, + "extensible": true + } + ] }, "tests": [ { @@ -87,10 +95,14 @@ }, { "description": "propertyNames with boolean schema true", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "propertyNames": true, - "extensible": true + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "propertyNames": true, + "extensible": true + } + ] }, "tests": [ { @@ -109,10 +121,14 @@ }, { "description": "propertyNames with boolean schema false", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "propertyNames": false, - "extensible": true + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "propertyNames": false, + "extensible": true + } + ] }, "tests": [ { @@ -131,12 +147,16 @@ }, { "description": "propertyNames with const", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "propertyNames": { - "const": "foo" - }, - "extensible": true + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "propertyNames": { + "const": "foo" + }, + "extensible": true + } + ] }, "tests": [ { @@ -162,15 +182,19 @@ }, { "description": "propertyNames with enum", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "propertyNames": { - "enum": [ - "foo", - "bar" - ] - }, - "extensible": true + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "propertyNames": { + "enum": [ + "foo", + "bar" + ] + }, + "extensible": true + } + ] }, "tests": [ { @@ -204,12 +228,16 @@ }, { "description": "extensible: true allows extra properties (checked by propertyNames)", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "propertyNames": { - "maxLength": 3 - }, - "extensible": true + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "propertyNames": { + "maxLength": 3 + }, + "extensible": true + } + ] }, "tests": [ { diff --git a/tests/fixtures/puncs.json b/tests/fixtures/puncs.json deleted file mode 100644 index df02c8e..0000000 --- a/tests/fixtures/puncs.json +++ /dev/null @@ -1,1414 +0,0 @@ -[ - { - "description": "punc-specific resolution and local refs", - "enums": [], - "types": [ - { - "name": "global_thing", - "schemas": [ - { - "$id": "global_thing", - "type": "object", - "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "type": { - "type": "string" - } - }, - "required": [ - "id", - "type" - ] - } - ] - }, - { - "name": "punc_entity", - "schemas": [ - { - "$id": "punc_entity", - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "id", - "type" - ] - } - ] - }, - { - "name": "punc_person", - "schemas": [ - { - "$id": "punc_person", - "$ref": "punc_entity", - "properties": { - "first_name": { - "type": "string", - "minLength": 1 - }, - "last_name": { - "type": "string", - "minLength": 1 - }, - "address": { - "type": "object", - "properties": { - "street": { - "type": "string" - }, - "city": { - "type": "string" - } - }, - "required": [ - "street", - "city" - ] - } - } - } - ] - } - ], - "puncs": [ - { - "name": "public_ref_test", - "public": true, - "schemas": [ - { - "$id": "public_ref_test.request", - "$ref": "punc_person" - } - ] - }, - { - "name": "private_ref_test", - "public": false, - "schemas": [ - { - "$id": "private_ref_test.request", - "$ref": "punc_person" - } - ] - }, - { - "name": "punc_with_local_ref_test", - "public": false, - "schemas": [ - { - "$id": "local_address", - "type": "object", - "properties": { - "street": { - "type": "string" - }, - "city": { - "type": "string" - } - }, - "required": [ - "street", - "city" - ] - }, - { - "$id": "punc_with_local_ref_test.request", - "$ref": "local_address" - } - ] - }, - { - "name": "punc_with_local_ref_to_global_test", - "public": false, - "schemas": [ - { - "$id": "local_user_with_thing", - "type": "object", - "properties": { - "user_name": { - "type": "string" - }, - "thing": { - "$ref": "global_thing" - } - }, - "required": [ - "user_name", - "thing" - ] - }, - { - "$id": "punc_with_local_ref_to_global_test.request", - "$ref": "local_user_with_thing" - } - ] - } - ], - "tests": [ - { - "description": "valid instance with address passes in public punc", - "schema_id": "public_ref_test.request", - "data": { - "id": "1", - "type": "person" - }, - "valid": true - }, - { - "description": "punc local ref resolution", - "schema_id": "punc_with_local_ref_test.request", - "data": { - "street": "123 Main St", - "city": "Anytown" - }, - "valid": true - }, - { - "description": "punc local ref missing requirement", - "schema_id": "punc_with_local_ref_test.request", - "data": {}, - "valid": false, - "expect_errors": [ - { - "code": "REQUIRED_FIELD_MISSING", - "path": "/city" - } - ] - }, - { - "description": "punc local ref to global type - invalid format", - "schema_id": "punc_with_local_ref_to_global_test.request", - "data": {}, - "valid": false, - "expect_errors": [ - { - "code": "FORMAT_INVALID", - "path": "/thing/id" - } - ] - } - ] - }, - { - "description": "punc refs global enum", - "enums": [ - { - "name": "status_enum", - "values": [ - "active", - "inactive" - ], - "schemas": [ - { - "$id": "status_enum", - "type": "string", - "enum": [ - "active", - "inactive" - ] - } - ] - } - ], - "puncs": [ - { - "name": "enum_test_punc", - "public": true, - "schemas": [ - { - "$id": "enum_test_punc.request", - "type": "object", - "properties": { - "status": { - "$ref": "status_enum" - } - }, - "required": [ - "status" - ] - } - ] - } - ], - "tests": [ - { - "description": "valid enum value", - "schema_id": "enum_test_punc.request", - "data": { - "status": "active" - }, - "valid": true - }, - { - "description": "invalid enum value", - "schema_id": "enum_test_punc.request", - "data": { - "status": "pending" - }, - "valid": false, - "expect_errors": [ - { - "code": "ENUM_VIOLATED", - "path": "/status" - } - ] - } - ] - }, - { - "description": "inheritance type matching and overrides", - "types": [ - { - "name": "entity", - "schemas": [ - { - "$id": "entity", - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "type": { - "type": "string" - } - } - } - ] - }, - { - "name": "job", - "schemas": [ - { - "$id": "job", - "$ref": "entity", - "properties": { - "type": { - "type": "string", - "const": "job", - "override": true - }, - "job_id": { - "type": "string" - } - } - } - ] - }, - { - "name": "super_job", - "schemas": [ - { - "$id": "super_job", - "$ref": "job", - "properties": { - "type": { - "type": "string", - "const": "super_job", - "override": true - }, - "manager_id": { - "type": "string" - } - } - }, - { - "$id": "super_job.short", - "$ref": "super_job", - "properties": { - "name": { - "type": "string", - "maxLength": 10 - } - } - } - ] - } - ], - "tests": [ - { - "description": "valid job instance", - "schema_id": "job", - "data": { - "id": "1", - "type": "job", - "name": "my job", - "job_id": "job123" - }, - "valid": true - }, - { - "description": "invalid job instance - wrong type const", - "schema_id": "job", - "data": { - "id": "1", - "type": "not_job", - "name": "my job", - "job_id": "job123" - }, - "valid": false, - "expect_errors": [ - { - "code": "CONST_VIOLATED", - "path": "/type" - } - ] - }, - { - "description": "valid super_job instance", - "schema_id": "super_job", - "data": { - "id": "1", - "type": "super_job", - "name": "my super job", - "job_id": "job123", - "manager_id": "mgr1" - }, - "valid": true - }, - { - "description": "valid super_job.short instance", - "schema_id": "super_job.short", - "data": { - "id": "1", - "type": "super_job", - "name": "short", - "job_id": "job123", - "manager_id": "mgr1" - }, - "valid": true - }, - { - "description": "invalid super_job.short - type must be super_job not job", - "schema_id": "super_job.short", - "data": { - "id": "1", - "type": "job", - "name": "short", - "job_id": "job123", - "manager_id": "mgr1" - }, - "valid": false, - "expect_errors": [ - { - "code": "CONST_VIOLATED", - "path": "/type" - } - ] - } - ] - }, - { - "description": "union type matching with const discriminators", - "types": [ - { - "name": "union_base", - "schemas": [ - { - "$id": "union_base", - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "id", - "type" - ] - } - ] - }, - { - "name": "union_a", - "schemas": [ - { - "$id": "union_a", - "$ref": "union_base", - "properties": { - "type": { - "const": "union_a", - "override": true - }, - "prop_a": { - "type": "string" - } - }, - "required": [ - "prop_a" - ] - } - ] - }, - { - "name": "union_b", - "schemas": [ - { - "$id": "union_b", - "$ref": "union_base", - "properties": { - "type": { - "const": "union_b", - "override": true - }, - "prop_b": { - "type": "number" - } - }, - "required": [ - "prop_b" - ] - } - ] - }, - { - "name": "union_c", - "schemas": [ - { - "$id": "union_c", - "base": "union_base", - "$ref": "union_base", - "properties": { - "type": { - "const": "union_c", - "override": true - }, - "prop_c": { - "type": "boolean" - } - }, - "required": [ - "prop_c" - ] - } - ] - } - ], - "puncs": [ - { - "name": "union_test", - "public": true, - "schemas": [ - { - "$id": "union_test.request", - "type": "object", - "properties": { - "union_prop": { - "oneOf": [ - { - "$ref": "union_a" - }, - { - "$ref": "union_b" - }, - { - "$ref": "union_c" - } - ] - } - } - } - ] - } - ], - "tests": [ - { - "description": "valid union variant A", - "schema_id": "union_test.request", - "data": { - "union_prop": { - "id": "123", - "type": "union_a", - "prop_a": "hello" - } - }, - "valid": true - }, - { - "description": "valid union variant B", - "schema_id": "union_test.request", - "data": { - "union_prop": { - "id": "456", - "type": "union_b", - "prop_b": 123 - } - }, - "valid": true - }, - { - "description": "invalid variant - wrong discriminator", - "schema_id": "union_test.request", - "data": { - "union_prop": { - "id": "789", - "type": "union_b", - "prop_a": "hello" - } - }, - "valid": false, - "expect_errors": [ - { - "code": "CONST_VIOLATED", - "path": "/union_prop/type" - } - ] - }, - { - "description": "valid union variant C", - "schema_id": "union_test.request", - "data": { - "union_prop": { - "id": "789", - "type": "union_c", - "prop_c": true - } - }, - "valid": true - }, - { - "description": "invalid instance - base type should fail due to override", - "schema_id": "union_test.request", - "data": { - "union_prop": { - "id": "101", - "type": "union_base", - "prop_a": "world" - } - }, - "valid": false, - "expect_errors": [ - { - "code": "CONST_VIOLATED", - "path": "/union_prop/type" - } - ] - } - ] - }, - { - "description": "nullable union validation", - "types": [ - { - "name": "thing_base", - "schemas": [ - { - "$id": "thing_base", - "type": "object", - "properties": { - "type": { - "type": "string", - "const": "thing_base" - }, - "id": { - "type": "string" - } - }, - "required": [ - "type", - "id" - ] - } - ] - }, - { - "name": "thing_a", - "schemas": [ - { - "$id": "thing_a", - "base": "thing_base", - "$ref": "thing_base", - "properties": { - "type": { - "type": "string", - "const": "thing_a", - "override": true - }, - "prop_a": { - "type": "string" - } - }, - "required": [ - "prop_a" - ] - } - ] - }, - { - "name": "thing_b", - "schemas": [ - { - "$id": "thing_b", - "base": "thing_base", - "$ref": "thing_base", - "properties": { - "type": { - "type": "string", - "const": "thing_b", - "override": true - }, - "prop_b": { - "type": "string" - } - }, - "required": [ - "prop_b" - ] - } - ] - } - ], - "puncs": [ - { - "name": "nullable_union_test", - "public": true, - "schemas": [ - { - "$id": "nullable_union_test.request", - "type": "object", - "properties": { - "nullable_prop": { - "oneOf": [ - { - "$ref": "thing_a" - }, - { - "$ref": "thing_b" - }, - { - "type": "null" - } - ] - } - } - } - ] - } - ], - "tests": [ - { - "description": "valid thing_a", - "schema_id": "nullable_union_test.request", - "data": { - "nullable_prop": { - "id": "123", - "type": "thing_a", - "prop_a": "hello" - } - }, - "valid": true - }, - { - "description": "valid thing_b", - "schema_id": "nullable_union_test.request", - "data": { - "nullable_prop": { - "id": "456", - "type": "thing_b", - "prop_b": "goodbye" - } - }, - "valid": true - }, - { - "description": "valid null", - "schema_id": "nullable_union_test.request", - "data": { - "nullable_prop": null - }, - "valid": true - }, - { - "description": "invalid base type", - "schema_id": "nullable_union_test.request", - "data": { - "nullable_prop": { - "id": "789", - "type": "thing_base", - "prop_a": "should fail" - } - }, - "valid": false, - "expect_errors": [ - { - "code": "CONST_VIOLATED", - "path": "/nullable_prop/type" - } - ] - }, - { - "description": "invalid type (string)", - "schema_id": "nullable_union_test.request", - "data": { - "nullable_prop": "not_an_object_or_null" - }, - "valid": false, - "expect_errors": [ - { - "code": "TYPE_MISMATCH", - "path": "/nullable_prop" - } - ] - } - ] - }, - { - "description": "type hierarchy descendants matching (family schemas)", - "types": [ - { - "name": "entity", - "hierarchy": [ - "entity" - ], - "schemas": [ - { - "$id": "entity", - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "type": { - "type": "string" - } - } - } - ] - }, - { - "name": "organization", - "hierarchy": [ - "entity", - "organization" - ], - "schemas": [ - { - "$id": "organization", - "$ref": "entity", - "properties": { - "type": { - "const": "organization", - "override": true - }, - "name": { - "type": "string" - } - } - } - ] - }, - { - "name": "person", - "hierarchy": [ - "entity", - "organization", - "person" - ], - "schemas": [ - { - "$id": "person", - "$ref": "organization", - "properties": { - "type": { - "const": "person", - "override": true - }, - "first_name": { - "type": "string" - } - } - } - ] - } - ], - "tests": [ - { - "description": "derived type (person) matches base schema (organization) via family ref", - "schema_id": "organization", - "data": { - "id": "p1", - "type": "person", - "name": "John", - "first_name": "John" - }, - "valid": false, - "expect_errors": [ - { - "code": "CONST_VIOLATED", - "path": "/type" - }, - { - "code": "ADDITIONAL_PROPERTIES_NOT_ALLOWED", - "path": "/first_name" - } - ] - }, - { - "description": "base type matches its own schema", - "schema_id": "organization", - "data": { - "id": "o1", - "type": "organization", - "name": "ACME" - }, - "valid": true - }, - { - "description": "ancestor type (entity) fails derived schema (organization)", - "schema_id": "organization", - "data": { - "id": "e1", - "type": "entity" - }, - "valid": false, - "expect_errors": [ - { - "code": "ENUM_VIOLATED", - "path": "/type" - } - ] - }, - { - "description": "unrelated type (job) fails derived schema (organization)", - "schema_id": "organization", - "data": { - "id": "job-1", - "type": "job", - "name": "Should Fail" - }, - "valid": false, - "expect_errors": [ - { - "code": "ENUM_VIOLATED", - "path": "/type" - } - ] - } - ] - }, - { - "description": "complex punc type matching with oneOf and nested refs", - "types": [ - { - "name": "entity", - "schemas": [ - { - "$id": "entity", - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "type": { - "type": "string" - } - } - } - ] - }, - { - "name": "job", - "schemas": [ - { - "$id": "job", - "$ref": "entity", - "properties": { - "type": { - "type": "string", - "const": "job", - "override": true - }, - "job_id": { - "type": "string" - } - } - } - ] - }, - { - "name": "super_job", - "schemas": [ - { - "$id": "super_job", - "$ref": "job", - "extensible": false, - "properties": { - "type": { - "type": "string", - "const": "super_job", - "override": true - }, - "manager_id": { - "type": "string" - } - } - }, - { - "$id": "super_job.short", - "$ref": "super_job", - "properties": { - "name": { - "type": "string", - "maxLength": 10 - } - } - } - ] - }, - { - "name": "organization", - "hierarchy": [ - "entity", - "organization" - ], - "schemas": [ - { - "$id": "organization", - "$ref": "entity", - "properties": { - "type": { - "const": "organization", - "override": true - }, - "name": { - "type": "string" - } - } - } - ] - }, - { - "name": "person", - "hierarchy": [ - "entity", - "organization", - "person" - ], - "schemas": [ - { - "$id": "person", - "$ref": "organization", - "properties": { - "type": { - "const": "person", - "override": true - }, - "first_name": { - "type": "string" - } - } - } - ] - } - ], - "puncs": [ - { - "name": "type_test_punc", - "public": true, - "schemas": [ - { - "$id": "type_test_punc.request", - "type": "object", - "properties": { - "root_job": { - "$ref": "job" - }, - "nested_or_super_job": { - "oneOf": [ - { - "$ref": "super_job" - }, - { - "type": "object", - "properties": { - "my_job": { - "$ref": "job" - } - }, - "required": [ - "my_job" - ] - } - ] - } - }, - "required": [ - "root_job", - "nested_or_super_job" - ] - } - ] - }, - { - "name": "polymorphic_org_punc", - "public": false, - "schemas": [ - { - "$id": "polymorphic_org_punc.request", - "$family": "organization" - } - ] - }, - { - "name": "strict_org_punc", - "public": false, - "schemas": [ - { - "$id": "strict_org_punc.request", - "$ref": "organization" - } - ] - }, - { - "name": "invalid_family_punc", - "public": false, - "schemas": [ - { - "$id": "invalid_family_punc.request", - "$family": "organization", - "properties": { - "extra": { - "type": "string" - } - } - } - ] - } - ], - "tests": [ - { - "description": "valid punc instance with mixed job types", - "schema_id": "type_test_punc.request", - "data": { - "root_job": { - "type": "job", - "name": "root job", - "job_id": "job456" - }, - "nested_or_super_job": { - "type": "super_job", - "name": "nested super job", - "job_id": "job789", - "manager_id": "mgr2" - } - }, - "valid": true - }, - { - "description": "invalid type at punc root ref (job expected, entity given)", - "schema_id": "type_test_punc.request", - "data": { - "root_job": { - "type": "entity", - "name": "root job", - "job_id": "job456" - }, - "nested_or_super_job": { - "type": "super_job", - "name": "nested super job", - "job_id": "job789", - "manager_id": "mgr2" - } - }, - "valid": false, - "expect_errors": [ - { - "code": "CONST_VIOLATED", - "path": "/root_job/type" - } - ] - }, - { - "description": "invalid type at punc nested ref (job expected, entity given)", - "schema_id": "type_test_punc.request", - "data": { - "root_job": { - "type": "job", - "name": "root job", - "job_id": "job456" - }, - "nested_or_super_job": { - "my_job": { - "type": "entity", - "name": "nested job" - } - } - }, - "valid": false, - "expect_errors": [ - { - "code": "CONST_VIOLATED", - "path": "/nested_or_super_job/my_job/type" - } - ] - }, - { - "description": "invalid type at punc oneOf ref (super_job expected, job given)", - "schema_id": "type_test_punc.request", - "data": { - "root_job": { - "type": "job", - "name": "root job", - "job_id": "job456" - }, - "nested_or_super_job": { - "type": "job", - "name": "nested super job", - "job_id": "job789", - "manager_id": "mgr2" - } - }, - "valid": false, - "expect_errors": [ - { - "code": "CONST_VIOLATED", - "path": "/nested_or_super_job/type" - } - ] - }, - { - "description": "valid person against organization punc (polymorphic)", - "schema_id": "polymorphic_org_punc.request", - "data": { - "id": "person-1", - "type": "person", - "name": "John Doe", - "first_name": "John" - }, - "valid": true - }, - { - "description": "valid organization against organization punc (polymorphic)", - "schema_id": "polymorphic_org_punc.request", - "data": { - "id": "org-1", - "type": "organization", - "name": "ACME Corp" - }, - "valid": true - }, - { - "description": "invalid job against organization punc (polymorphic)", - "schema_id": "polymorphic_org_punc.request", - "data": { - "id": "job-1", - "type": "job", - "name": "My Job" - }, - "valid": false, - "expect_errors": [ - { - "code": "ONE_OF_FAILED", - "path": "" - } - ] - }, - { - "description": "valid organization against strict punc", - "schema_id": "strict_org_punc.request", - "data": { - "id": "org-2", - "type": "organization", - "name": "Strict Corp" - }, - "valid": true - }, - { - "description": "invalid person against strict punc (type mismatch + extra fields)", - "schema_id": "strict_org_punc.request", - "data": { - "id": "person-2", - "type": "person", - "name": "Jane Doe", - "first_name": "Jane" - }, - "valid": false, - "expect_errors": [ - { - "code": "CONST_VIOLATED", - "path": "/type" - }, - { - "code": "ADDITIONAL_PROPERTIES_NOT_ALLOWED", - "path": "/first_name" - } - ] - }, - { - "description": "invalid schema due to family exclusivity violation", - "schema_id": "invalid_family_punc.request", - "data": { - "id": "org-2", - "type": "organization", - "name": "Strict Corp", - "extra": "value" - }, - "valid": false, - "expect_errors": [ - { - "code": "INVALID_SCHEMA", - "path": "" - } - ] - } - ] - }, - { - "description": "dependency merging across inheritance", - "types": [ - { - "name": "entity", - "schemas": [ - { - "$id": "entity", - "type": "object", - "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "type": { - "type": "string" - }, - "created_by": { - "type": "string", - "format": "uuid" - }, - "creating": { - "type": "boolean" - }, - "name": { - "type": "string" - } - }, - "required": [ - "id", - "type", - "created_by" - ], - "dependencies": { - "creating": [ - "name" - ] - } - } - ] - }, - { - "name": "user", - "schemas": [ - { - "$id": "user", - "$ref": "entity", - "properties": { - "password": { - "type": "string", - "minLength": 8 - } - }, - "dependencies": { - "creating": [ - "name" - ] - } - } - ] - }, - { - "name": "person", - "schemas": [ - { - "$id": "person", - "$ref": "user", - "properties": { - "first_name": { - "type": "string", - "minLength": 1 - }, - "last_name": { - "type": "string", - "minLength": 1 - } - }, - "dependencies": { - "creating": [ - "first_name", - "last_name" - ] - } - } - ] - } - ], - "tests": [ - { - "description": "dependency merging: fails when missing deps from ancestor and self", - "schema_id": "person", - "data": { - "id": "550e8400-e29b-41d4-a716-446655440000", - "type": "person", - "created_by": "550e8400-e29b-41d4-a716-446655440001", - "creating": true, - "password": "securepass" - }, - "valid": false, - "expect_errors": [ - { - "code": "DEPENDENCY_FAILED", - "path": "/name" - }, - { - "code": "DEPENDENCY_FAILED", - "path": "/first_name" - }, - { - "code": "DEPENDENCY_FAILED", - "path": "/last_name" - } - ] - }, - { - "description": "dependency merging: fails when missing only local dep", - "schema_id": "person", - "data": { - "id": "550e8400-e29b-41d4-a716-446655440000", - "type": "person", - "created_by": "550e8400-e29b-41d4-a716-446655440001", - "creating": true, - "password": "securepass", - "name": "John Doe", - "first_name": "John" - }, - "valid": false, - "expect_errors": [ - { - "code": "DEPENDENCY_FAILED", - "path": "/last_name" - } - ] - } - ] - } -] \ No newline at end of file diff --git a/tests/fixtures/ref.json b/tests/fixtures/ref.json index 7f5c666..48a67d0 100644 --- a/tests/fixtures/ref.json +++ b/tests/fixtures/ref.json @@ -1,14 +1,18 @@ [ { "description": "root pointer ref", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "properties": { - "foo": { - "$ref": "#" + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "foo": { + "$ref": "#" + } + }, + "additionalProperties": false } - }, - "additionalProperties": false + ] }, "tests": [ { @@ -47,16 +51,20 @@ }, { "description": "relative pointer ref to object", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "properties": { - "foo": { - "type": "integer" - }, - "bar": { - "$ref": "#/properties/foo" + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "foo": { + "type": "integer" + }, + "bar": { + "$ref": "#/properties/foo" + } + } } - } + ] }, "tests": [ { @@ -77,14 +85,18 @@ }, { "description": "relative pointer ref to array", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "prefixItems": [ + "database": { + "schemas": [ { - "type": "integer" - }, - { - "$ref": "#/prefixItems/0" + "$schema": "https://json-schema.org/draft/2020-12/schema", + "prefixItems": [ + { + "type": "integer" + }, + { + "$ref": "#/prefixItems/0" + } + ] } ] }, @@ -109,30 +121,34 @@ }, { "description": "escaped pointer ref", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$defs": { - "tilde~field": { - "type": "integer" - }, - "slash/field": { - "type": "integer" - }, - "percent%field": { - "type": "integer" + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$defs": { + "tilde~field": { + "type": "integer" + }, + "slash/field": { + "type": "integer" + }, + "percent%field": { + "type": "integer" + } + }, + "properties": { + "tilde": { + "$ref": "#/$defs/tilde~0field" + }, + "slash": { + "$ref": "#/$defs/slash~1field" + }, + "percent": { + "$ref": "#/$defs/percent%25field" + } + } } - }, - "properties": { - "tilde": { - "$ref": "#/$defs/tilde~0field" - }, - "slash": { - "$ref": "#/$defs/slash~1field" - }, - "percent": { - "$ref": "#/$defs/percent%25field" - } - } + ] }, "tests": [ { @@ -181,20 +197,24 @@ }, { "description": "nested refs", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$defs": { - "a": { - "type": "integer" - }, - "b": { - "$ref": "#/$defs/a" - }, - "c": { - "$ref": "#/$defs/b" + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$defs": { + "a": { + "type": "integer" + }, + "b": { + "$ref": "#/$defs/a" + }, + "c": { + "$ref": "#/$defs/b" + } + }, + "$ref": "#/$defs/c" } - }, - "$ref": "#/$defs/c" + ] }, "tests": [ { @@ -211,19 +231,23 @@ }, { "description": "ref applies alongside sibling keywords", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$defs": { - "reffed": { - "type": "array" + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$defs": { + "reffed": { + "type": "array" + } + }, + "properties": { + "foo": { + "$ref": "#/$defs/reffed", + "maxItems": 2 + } + } } - }, - "properties": { - "foo": { - "$ref": "#/$defs/reffed", - "maxItems": 2 - } - } + ] }, "tests": [ { @@ -255,13 +279,17 @@ }, { "description": "property named $ref that is not a reference", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "properties": { - "$ref": { - "type": "string" + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "$ref": { + "type": "string" + } + } } - } + ] }, "tests": [ { @@ -282,18 +310,22 @@ }, { "description": "property named $ref, containing an actual $ref", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "properties": { - "$ref": { - "$ref": "#/$defs/is-string" + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "$ref": { + "$ref": "#/$defs/is-string" + } + }, + "$defs": { + "is-string": { + "type": "string" + } + } } - }, - "$defs": { - "is-string": { - "type": "string" - } - } + ] }, "tests": [ { @@ -314,12 +346,16 @@ }, { "description": "$ref to boolean schema true", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$ref": "#/$defs/bool", - "$defs": { - "bool": true - } + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$ref": "#/$defs/bool", + "$defs": { + "bool": true + } + } + ] }, "tests": [ { @@ -331,12 +367,16 @@ }, { "description": "$ref to boolean schema false", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$ref": "#/$defs/bool", - "$defs": { - "bool": false - } + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$ref": "#/$defs/bool", + "$defs": { + "bool": false + } + } + ] }, "tests": [ { @@ -348,44 +388,48 @@ }, { "description": "Recursive references between schemas", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "http://localhost:1234/draft2020-12/tree", - "description": "tree of nodes", - "type": "object", - "properties": { - "meta": { - "type": "string" - }, - "nodes": { - "type": "array", - "items": { - "$ref": "node" - } - } - }, - "required": [ - "meta", - "nodes" - ], - "$defs": { - "node": { - "$id": "http://localhost:1234/draft2020-12/node", - "description": "node", + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "http://localhost:1234/draft2020-12/tree", + "description": "tree of nodes", "type": "object", "properties": { - "value": { - "type": "number" + "meta": { + "type": "string" }, - "subtree": { - "$ref": "tree" + "nodes": { + "type": "array", + "items": { + "$ref": "node" + } } }, "required": [ - "value" - ] + "meta", + "nodes" + ], + "$defs": { + "node": { + "$id": "http://localhost:1234/draft2020-12/node", + "description": "node", + "type": "object", + "properties": { + "value": { + "type": "number" + }, + "subtree": { + "$ref": "tree" + } + }, + "required": [ + "value" + ] + } + } } - } + ] }, "tests": [ { @@ -466,18 +510,22 @@ }, { "description": "refs with quote", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "properties": { - "foo\"bar": { - "$ref": "#/$defs/foo%22bar" + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "foo\"bar": { + "$ref": "#/$defs/foo%22bar" + } + }, + "$defs": { + "foo\"bar": { + "type": "number" + } + } } - }, - "$defs": { - "foo\"bar": { - "type": "number" - } - } + ] }, "tests": [ { @@ -498,17 +546,21 @@ }, { "description": "naive replacement of $ref with its destination is not correct", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "extensible": true, - "$defs": { - "a_string": { - "type": "string" - } - }, - "enum": [ + "database": { + "schemas": [ { - "$ref": "#/$defs/a_string" + "$schema": "https://json-schema.org/draft/2020-12/schema", + "extensible": true, + "$defs": { + "a_string": { + "type": "string" + } + }, + "enum": [ + { + "$ref": "#/$defs/a_string" + } + ] } ] }, @@ -536,25 +588,29 @@ }, { "description": "refs with relative uris and defs", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "http://example.com/schema-relative-uri-defs1.json", - "properties": { - "foo": { - "$id": "schema-relative-uri-defs2.json", - "$defs": { - "inner": { - "properties": { - "bar": { - "type": "string" + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "http://example.com/schema-relative-uri-defs1.json", + "properties": { + "foo": { + "$id": "schema-relative-uri-defs2.json", + "$defs": { + "inner": { + "properties": { + "bar": { + "type": "string" + } + } } - } + }, + "$ref": "#/$defs/inner" } }, - "$ref": "#/$defs/inner" + "$ref": "schema-relative-uri-defs2.json" } - }, - "$ref": "schema-relative-uri-defs2.json" + ] }, "tests": [ { @@ -591,26 +647,30 @@ }, { "description": "refs with relative uris and defs (extensible)", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "http://example.com/schema-relative-uri-defs1-ext.json", - "properties": { - "foo": { - "$id": "schema-relative-uri-defs2-ext.json", - "$defs": { - "inner": { - "extensible": true, - "properties": { - "bar": { - "type": "string" + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "http://example.com/schema-relative-uri-defs1-ext.json", + "properties": { + "foo": { + "$id": "schema-relative-uri-defs2-ext.json", + "$defs": { + "inner": { + "extensible": true, + "properties": { + "bar": { + "type": "string" + } + } } - } + }, + "$ref": "#/$defs/inner" } }, - "$ref": "#/$defs/inner" + "$ref": "schema-relative-uri-defs2-ext.json" } - }, - "$ref": "schema-relative-uri-defs2-ext.json" + ] }, "tests": [ { @@ -627,25 +687,29 @@ }, { "description": "relative refs with absolute uris and defs", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "http://example.com/schema-refs-absolute-uris-defs1.json", - "properties": { - "foo": { - "$id": "http://example.com/schema-refs-absolute-uris-defs2.json", - "$defs": { - "inner": { - "properties": { - "bar": { - "type": "string" + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "http://example.com/schema-refs-absolute-uris-defs1.json", + "properties": { + "foo": { + "$id": "http://example.com/schema-refs-absolute-uris-defs2.json", + "$defs": { + "inner": { + "properties": { + "bar": { + "type": "string" + } + } } - } + }, + "$ref": "#/$defs/inner" } }, - "$ref": "#/$defs/inner" + "$ref": "schema-refs-absolute-uris-defs2.json" } - }, - "$ref": "schema-refs-absolute-uris-defs2.json" + ] }, "tests": [ { @@ -682,25 +746,29 @@ }, { "description": "$id must be resolved against nearest parent, not just immediate parent", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "http://example.com/a.json", - "$defs": { - "x": { - "$id": "http://example.com/b/c.json", - "not": { - "$defs": { - "y": { - "$id": "d.json", - "type": "number" + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "http://example.com/a.json", + "$defs": { + "x": { + "$id": "http://example.com/b/c.json", + "not": { + "$defs": { + "y": { + "$id": "d.json", + "type": "number" + } + } } } - } - } - }, - "allOf": [ - { - "$ref": "http://example.com/b/d.json" + }, + "allOf": [ + { + "$ref": "http://example.com/b/d.json" + } + ] } ] }, @@ -719,23 +787,27 @@ }, { "description": "order of evaluation: $id and $ref", - "schema": { - "$comment": "$id must be evaluated before $ref to get the proper $ref destination", - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://example.com/draft2020-12/ref-and-id1/base.json", - "$ref": "int.json", - "$defs": { - "bigint": { - "$comment": "canonical uri: https://example.com/ref-and-id1/int.json", - "$id": "int.json", - "maximum": 10 - }, - "smallint": { - "$comment": "canonical uri: https://example.com/ref-and-id1-int.json", - "$id": "/draft2020-12/ref-and-id1-int.json", - "maximum": 2 + "database": { + "schemas": [ + { + "$comment": "$id must be evaluated before $ref to get the proper $ref destination", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://example.com/draft2020-12/ref-and-id1/base.json", + "$ref": "int.json", + "$defs": { + "bigint": { + "$comment": "canonical uri: https://example.com/ref-and-id1/int.json", + "$id": "int.json", + "maximum": 10 + }, + "smallint": { + "$comment": "canonical uri: https://example.com/ref-and-id1-int.json", + "$id": "/draft2020-12/ref-and-id1-int.json", + "maximum": 2 + } + } } - } + ] }, "tests": [ { @@ -752,24 +824,28 @@ }, { "description": "order of evaluation: $id and $anchor and $ref", - "schema": { - "$comment": "$id must be evaluated before $ref to get the proper $ref destination", - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://example.com/draft2020-12/ref-and-id2/base.json", - "$ref": "#bigint", - "$defs": { - "bigint": { - "$comment": "canonical uri: /ref-and-id2/base.json#/$defs/bigint; another valid uri for this location: /ref-and-id2/base.json#bigint", - "$anchor": "bigint", - "maximum": 10 - }, - "smallint": { - "$comment": "canonical uri: https://example.com/ref-and-id2#/$defs/smallint; another valid uri for this location: https://example.com/ref-and-id2/#bigint", - "$id": "https://example.com/draft2020-12/ref-and-id2/", - "$anchor": "bigint", - "maximum": 2 + "database": { + "schemas": [ + { + "$comment": "$id must be evaluated before $ref to get the proper $ref destination", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://example.com/draft2020-12/ref-and-id2/base.json", + "$ref": "#bigint", + "$defs": { + "bigint": { + "$comment": "canonical uri: /ref-and-id2/base.json#/$defs/bigint; another valid uri for this location: /ref-and-id2/base.json#bigint", + "$anchor": "bigint", + "maximum": 10 + }, + "smallint": { + "$comment": "canonical uri: https://example.com/ref-and-id2#/$defs/smallint; another valid uri for this location: https://example.com/ref-and-id2/#bigint", + "$id": "https://example.com/draft2020-12/ref-and-id2/", + "$anchor": "bigint", + "maximum": 2 + } + } } - } + ] }, "tests": [ { @@ -786,23 +862,27 @@ }, { "description": "order of evaluation: $id and $ref on nested schema", - "schema": { - "$comment": "$id must be evaluated before $ref to get the proper $ref destination", - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://example.com/draft2020-12/ref-and-id3/base.json", - "$ref": "nested/foo.json", - "$defs": { - "foo": { - "$comment": "canonical uri: https://example.com/draft2020-12/ref-and-id3/nested/foo.json", - "$id": "nested/foo.json", - "$ref": "./bar.json" - }, - "bar": { - "$comment": "canonical uri: https://example.com/draft2020-12/ref-and-id3/nested/bar.json", - "$id": "nested/bar.json", - "type": "number" + "database": { + "schemas": [ + { + "$comment": "$id must be evaluated before $ref to get the proper $ref destination", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://example.com/draft2020-12/ref-and-id3/base.json", + "$ref": "nested/foo.json", + "$defs": { + "foo": { + "$comment": "canonical uri: https://example.com/draft2020-12/ref-and-id3/nested/foo.json", + "$id": "nested/foo.json", + "$ref": "./bar.json" + }, + "bar": { + "$comment": "canonical uri: https://example.com/draft2020-12/ref-and-id3/nested/bar.json", + "$id": "nested/bar.json", + "type": "number" + } + } } - } + ] }, "tests": [ { @@ -819,16 +899,20 @@ }, { "description": "simple URN base URI with $ref via the URN", - "schema": { - "$comment": "URIs do not have to have HTTP(s) schemes", - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "urn:uuid:deadbeef-1234-ffff-ffff-4321feebdaed", - "minimum": 30, - "properties": { - "foo": { - "$ref": "urn:uuid:deadbeef-1234-ffff-ffff-4321feebdaed" + "database": { + "schemas": [ + { + "$comment": "URIs do not have to have HTTP(s) schemes", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "urn:uuid:deadbeef-1234-ffff-ffff-4321feebdaed", + "minimum": 30, + "properties": { + "foo": { + "$ref": "urn:uuid:deadbeef-1234-ffff-ffff-4321feebdaed" + } + } } - } + ] }, "tests": [ { @@ -849,20 +933,24 @@ }, { "description": "simple URN base URI with JSON pointer", - "schema": { - "$comment": "URIs do not have to have HTTP(s) schemes", - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "urn:uuid:deadbeef-1234-00ff-ff00-4321feebdaed", - "properties": { - "foo": { - "$ref": "#/$defs/bar" + "database": { + "schemas": [ + { + "$comment": "URIs do not have to have HTTP(s) schemes", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "urn:uuid:deadbeef-1234-00ff-ff00-4321feebdaed", + "properties": { + "foo": { + "$ref": "#/$defs/bar" + } + }, + "$defs": { + "bar": { + "type": "string" + } + } } - }, - "$defs": { - "bar": { - "type": "string" - } - } + ] }, "tests": [ { @@ -883,20 +971,24 @@ }, { "description": "URN base URI with NSS", - "schema": { - "$comment": "RFC 8141 §2.2", - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "urn:example:1/406/47452/2", - "properties": { - "foo": { - "$ref": "#/$defs/bar" + "database": { + "schemas": [ + { + "$comment": "RFC 8141 §2.2", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "urn:example:1/406/47452/2", + "properties": { + "foo": { + "$ref": "#/$defs/bar" + } + }, + "$defs": { + "bar": { + "type": "string" + } + } } - }, - "$defs": { - "bar": { - "type": "string" - } - } + ] }, "tests": [ { @@ -917,20 +1009,24 @@ }, { "description": "URN base URI with r-component", - "schema": { - "$comment": "RFC 8141 §2.3.1", - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "urn:example:foo-bar-baz-qux?+CCResolve:cc=uk", - "properties": { - "foo": { - "$ref": "#/$defs/bar" + "database": { + "schemas": [ + { + "$comment": "RFC 8141 §2.3.1", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "urn:example:foo-bar-baz-qux?+CCResolve:cc=uk", + "properties": { + "foo": { + "$ref": "#/$defs/bar" + } + }, + "$defs": { + "bar": { + "type": "string" + } + } } - }, - "$defs": { - "bar": { - "type": "string" - } - } + ] }, "tests": [ { @@ -951,20 +1047,24 @@ }, { "description": "URN base URI with q-component", - "schema": { - "$comment": "RFC 8141 §2.3.2", - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "urn:example:weather?=op=map&lat=39.56&lon=-104.85&datetime=1969-07-21T02:56:15Z", - "properties": { - "foo": { - "$ref": "#/$defs/bar" + "database": { + "schemas": [ + { + "$comment": "RFC 8141 §2.3.2", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "urn:example:weather?=op=map&lat=39.56&lon=-104.85&datetime=1969-07-21T02:56:15Z", + "properties": { + "foo": { + "$ref": "#/$defs/bar" + } + }, + "$defs": { + "bar": { + "type": "string" + } + } } - }, - "$defs": { - "bar": { - "type": "string" - } - } + ] }, "tests": [ { @@ -985,19 +1085,23 @@ }, { "description": "URN base URI with URN and JSON pointer ref", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "urn:uuid:deadbeef-1234-0000-0000-4321feebdaed", - "properties": { - "foo": { - "$ref": "urn:uuid:deadbeef-1234-0000-0000-4321feebdaed#/$defs/bar" + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "urn:uuid:deadbeef-1234-0000-0000-4321feebdaed", + "properties": { + "foo": { + "$ref": "urn:uuid:deadbeef-1234-0000-0000-4321feebdaed#/$defs/bar" + } + }, + "$defs": { + "bar": { + "type": "string" + } + } } - }, - "$defs": { - "bar": { - "type": "string" - } - } + ] }, "tests": [ { @@ -1018,20 +1122,24 @@ }, { "description": "URN base URI with URN and anchor ref", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "urn:uuid:deadbeef-1234-ff00-00ff-4321feebdaed", - "properties": { - "foo": { - "$ref": "urn:uuid:deadbeef-1234-ff00-00ff-4321feebdaed#something" + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "urn:uuid:deadbeef-1234-ff00-00ff-4321feebdaed", + "properties": { + "foo": { + "$ref": "urn:uuid:deadbeef-1234-ff00-00ff-4321feebdaed#something" + } + }, + "$defs": { + "bar": { + "$anchor": "something", + "type": "string" + } + } } - }, - "$defs": { - "bar": { - "$anchor": "something", - "type": "string" - } - } + ] }, "tests": [ { @@ -1052,20 +1160,24 @@ }, { "description": "URN ref with nested pointer ref", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$ref": "urn:uuid:deadbeef-4321-ffff-ffff-1234feebdaed", - "$defs": { - "foo": { - "$id": "urn:uuid:deadbeef-4321-ffff-ffff-1234feebdaed", + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$ref": "urn:uuid:deadbeef-4321-ffff-ffff-1234feebdaed", "$defs": { - "bar": { - "type": "string" + "foo": { + "$id": "urn:uuid:deadbeef-4321-ffff-ffff-1234feebdaed", + "$defs": { + "bar": { + "type": "string" + } + }, + "$ref": "#/$defs/bar" } - }, - "$ref": "#/$defs/bar" + } } - } + ] }, "tests": [ { @@ -1082,13 +1194,17 @@ }, { "description": "ref to if", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$ref": "http://example.com/ref/if", - "if": { - "$id": "http://example.com/ref/if", - "type": "integer" - } + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$ref": "http://example.com/ref/if", + "if": { + "$id": "http://example.com/ref/if", + "type": "integer" + } + } + ] }, "tests": [ { @@ -1105,13 +1221,17 @@ }, { "description": "ref to then", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$ref": "http://example.com/ref/then", - "then": { - "$id": "http://example.com/ref/then", - "type": "integer" - } + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$ref": "http://example.com/ref/then", + "then": { + "$id": "http://example.com/ref/then", + "type": "integer" + } + } + ] }, "tests": [ { @@ -1128,13 +1248,17 @@ }, { "description": "ref to else", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$ref": "http://example.com/ref/else", - "else": { - "$id": "http://example.com/ref/else", - "type": "integer" - } + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$ref": "http://example.com/ref/else", + "else": { + "$id": "http://example.com/ref/else", + "type": "integer" + } + } + ] }, "tests": [ { @@ -1151,20 +1275,24 @@ }, { "description": "ref with absolute-path-reference", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "http://example.com/ref/absref.json", - "$defs": { - "a": { - "$id": "http://example.com/ref/absref/foobar.json", - "type": "number" - }, - "b": { - "$id": "http://example.com/absref/foobar.json", - "type": "string" + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "http://example.com/ref/absref.json", + "$defs": { + "a": { + "$id": "http://example.com/ref/absref/foobar.json", + "type": "number" + }, + "b": { + "$id": "http://example.com/absref/foobar.json", + "type": "string" + } + }, + "$ref": "/absref/foobar.json" } - }, - "$ref": "/absref/foobar.json" + ] }, "tests": [ { @@ -1181,15 +1309,19 @@ }, { "description": "$id with file URI still resolves pointers - *nix", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "file:///folder/file.json", - "$defs": { - "foo": { - "type": "number" + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "file:///folder/file.json", + "$defs": { + "foo": { + "type": "number" + } + }, + "$ref": "#/$defs/foo" } - }, - "$ref": "#/$defs/foo" + ] }, "tests": [ { @@ -1206,15 +1338,19 @@ }, { "description": "$id with file URI still resolves pointers - windows", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "file:///c:/folder/file.json", - "$defs": { - "foo": { - "type": "number" + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "file:///c:/folder/file.json", + "$defs": { + "foo": { + "type": "number" + } + }, + "$ref": "#/$defs/foo" } - }, - "$ref": "#/$defs/foo" + ] }, "tests": [ { @@ -1231,20 +1367,24 @@ }, { "description": "empty tokens in $ref json-pointer", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$defs": { - "": { + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", "$defs": { "": { - "type": "number" + "$defs": { + "": { + "type": "number" + } + } } - } - } - }, - "allOf": [ - { - "$ref": "#/$defs//$defs/" + }, + "allOf": [ + { + "$ref": "#/$defs//$defs/" + } + ] } ] }, @@ -1263,18 +1403,22 @@ }, { "description": "$ref boundary resets to loose", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$defs": { - "target": { - "properties": { - "foo": { - "type": "string" + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$defs": { + "target": { + "properties": { + "foo": { + "type": "string" + } + } } - } + }, + "$ref": "#/$defs/target" } - }, - "$ref": "#/$defs/target" + ] }, "tests": [ { @@ -1289,19 +1433,23 @@ }, { "description": "$ref target can enforce strictness", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$defs": { - "target": { - "extensible": false, - "properties": { - "foo": { - "type": "string" + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$defs": { + "target": { + "extensible": false, + "properties": { + "foo": { + "type": "string" + } + } } - } + }, + "$ref": "#/$defs/target" } - }, - "$ref": "#/$defs/target" + ] }, "tests": [ { @@ -1316,40 +1464,44 @@ }, { "description": "strictness: boundary reset at $ref", - "schema": { - "extensible": true, - "properties": { - "inline_child": { - "properties": { - "a": { - "type": "integer" - } - } - }, - "ref_child": { - "$ref": "#/$defs/strict_node" - }, - "extensible_ref_child": { - "$ref": "#/$defs/extensible_node" - } - }, - "$defs": { - "strict_node": { - "properties": { - "b": { - "type": "integer" - } - } - }, - "extensible_node": { + "database": { + "schemas": [ + { "extensible": true, "properties": { - "c": { - "type": "integer" + "inline_child": { + "properties": { + "a": { + "type": "integer" + } + } + }, + "ref_child": { + "$ref": "#/$defs/strict_node" + }, + "extensible_ref_child": { + "$ref": "#/$defs/extensible_node" + } + }, + "$defs": { + "strict_node": { + "properties": { + "b": { + "type": "integer" + } + } + }, + "extensible_node": { + "extensible": true, + "properties": { + "c": { + "type": "integer" + } + } } } } - } + ] }, "tests": [ { @@ -1386,25 +1538,29 @@ }, { "description": "arrays: ref items inherit strictness (reset at boundary)", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "properties": { - "list": { - "type": "array", - "items": { - "$ref": "#/$defs/strict_node" - } - } - }, - "$defs": { - "strict_node": { + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { - "a": { - "type": "integer" + "list": { + "type": "array", + "items": { + "$ref": "#/$defs/strict_node" + } + } + }, + "$defs": { + "strict_node": { + "properties": { + "a": { + "type": "integer" + } + } } } } - } + ] }, "tests": [ { @@ -1423,35 +1579,39 @@ }, { "description": "implicit keyword shadowing", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$defs": { - "parent": { - "type": "object", - "properties": { - "type": { - "const": "parent" - }, - "age": { - "minimum": 10, - "maximum": 20 + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$defs": { + "parent": { + "type": "object", + "properties": { + "type": { + "const": "parent" + }, + "age": { + "minimum": 10, + "maximum": 20 + } + }, + "required": [ + "type", + "age" + ] } }, - "required": [ - "type", - "age" - ] + "$ref": "#/$defs/parent", + "properties": { + "type": { + "const": "child" + }, + "age": { + "minimum": 15 + } + } } - }, - "$ref": "#/$defs/parent", - "properties": { - "type": { - "const": "child" - }, - "age": { - "minimum": 15 - } - } + ] }, "tests": [ { diff --git a/tests/fixtures/required.json b/tests/fixtures/required.json index 95d3a72..a311b19 100644 --- a/tests/fixtures/required.json +++ b/tests/fixtures/required.json @@ -1,14 +1,18 @@ [ { "description": "required validation", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "properties": { - "foo": {}, - "bar": {} - }, - "required": [ - "foo" + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "foo": {}, + "bar": {} + }, + "required": [ + "foo" + ] + } ] }, "tests": [ @@ -55,11 +59,15 @@ }, { "description": "required default validation", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "properties": { - "foo": {} - } + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "foo": {} + } + } + ] }, "tests": [ { @@ -71,12 +79,16 @@ }, { "description": "required with empty array", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "properties": { - "foo": {} - }, - "required": [] + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "foo": {} + }, + "required": [] + } + ] }, "tests": [ { @@ -88,17 +100,21 @@ }, { "description": "required with escaped characters", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "required": [ - "foo\nbar", - "foo\"bar", - "foo\\bar", - "foo\rbar", - "foo\tbar", - "foo\fbar" - ], - "extensible": true + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "required": [ + "foo\nbar", + "foo\"bar", + "foo\\bar", + "foo\rbar", + "foo\tbar", + "foo\fbar" + ], + "extensible": true + } + ] }, "tests": [ { @@ -126,14 +142,18 @@ { "description": "required properties whose names are Javascript object property names", "comment": "Ensure JS implementations don't universally consider e.g. __proto__ to always be present in an object.", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "required": [ - "__proto__", - "toString", - "constructor" - ], - "extensible": true + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "required": [ + "__proto__", + "toString", + "constructor" + ], + "extensible": true + } + ] }, "tests": [ { @@ -191,12 +211,16 @@ }, { "description": "extensible: true allows extra properties in required", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "required": [ - "foo" - ], - "extensible": true + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "required": [ + "foo" + ], + "extensible": true + } + ] }, "tests": [ { diff --git a/tests/fixtures/type.json b/tests/fixtures/type.json index e424885..c884d8b 100644 --- a/tests/fixtures/type.json +++ b/tests/fixtures/type.json @@ -1,9 +1,13 @@ [ { "description": "integer type matches integers", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "type": "integer" + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "integer" + } + ] }, "tests": [ { @@ -55,9 +59,13 @@ }, { "description": "number type matches numbers", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "type": "number" + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "number" + } + ] }, "tests": [ { @@ -109,9 +117,13 @@ }, { "description": "string type matches strings", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "type": "string" + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "string" + } + ] }, "tests": [ { @@ -163,9 +175,13 @@ }, { "description": "object type matches objects", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "type": "object" + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object" + } + ] }, "tests": [ { @@ -207,9 +223,13 @@ }, { "description": "array type matches arrays", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "type": "array" + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "array" + } + ] }, "tests": [ { @@ -251,9 +271,13 @@ }, { "description": "boolean type matches booleans", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "type": "boolean" + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "boolean" + } + ] }, "tests": [ { @@ -310,9 +334,13 @@ }, { "description": "null type matches only the null object", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "type": "null" + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "null" + } + ] }, "tests": [ { @@ -369,11 +397,15 @@ }, { "description": "multiple types can be specified in an array", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "type": [ - "integer", - "string" + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": [ + "integer", + "string" + ] + } ] }, "tests": [ @@ -416,10 +448,14 @@ }, { "description": "type as array with one item", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "type": [ - "string" + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": [ + "string" + ] + } ] }, "tests": [ @@ -437,13 +473,17 @@ }, { "description": "type: array or object", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "type": [ - "array", - "object" - ], - "items": {} + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": [ + "array", + "object" + ], + "items": {} + } + ] }, "tests": [ { @@ -479,14 +519,18 @@ }, { "description": "type: array, object or null", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "type": [ - "array", - "object", - "null" - ], - "items": {} + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": [ + "array", + "object", + "null" + ], + "items": {} + } + ] }, "tests": [ { @@ -522,10 +566,14 @@ }, { "description": "extensible: true allows extra properties", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "type": "object", - "extensible": true + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "extensible": true + } + ] }, "tests": [ { diff --git a/tests/fixtures/typedRefs.json b/tests/fixtures/typedRefs.json new file mode 100644 index 0000000..f991d5d --- /dev/null +++ b/tests/fixtures/typedRefs.json @@ -0,0 +1,343 @@ +[ + { + "description": "Entities extending entities", + "database": { + "types": [ + { + "name": "entity", + "hierarchy": [ + "entity" + ], + "schemas": [ + { + "$id": "entity", + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "type": "string", + "const": "entity" + } + } + } + ] + }, + { + "name": "organization", + "hierarchy": [ + "entity", + "organization" + ], + "schemas": [ + { + "$id": "organization", + "$ref": "entity", + "properties": { + "name": { + "type": "string" + } + } + } + ] + }, + { + "name": "person", + "hierarchy": [ + "entity", + "organization", + "person" + ], + "schemas": [ + { + "$id": "person", + "$ref": "organization", + "properties": { + "first_name": { + "type": "string" + } + } + } + ] + } + ], + "puncs": [ + { + "name": "save_org", + "schemas": [ + { + "$id": "save_org.request", + "$ref": "organization" + } + ] + } + ] + }, + "tests": [ + { + "description": "Valid person against organization schema (implicit type allowance)", + "schema_id": "save_org.request", + "data": { + "id": "1", + "type": "person", + "name": "ACME" + }, + "valid": true + }, + { + "description": "Valid organization against organization schema", + "schema_id": "save_org.request", + "data": { + "id": "2", + "type": "organization", + "name": "ACME" + }, + "valid": true + }, + { + "description": "Invalid entity against organization schema (ancestor not allowed)", + "schema_id": "save_org.request", + "data": { + "id": "3", + "type": "entity" + }, + "valid": false + }, + { + "description": "Invalid generic type against organization schema", + "schema_id": "save_org.request", + "data": { + "id": "4", + "type": "generic_thing" + }, + "valid": false + } + ] + }, + { + "description": "Ad-hocs extending entities (still entities with type magic)", + "database": { + "types": [ + { + "name": "entity", + "hierarchy": [ + "entity" + ], + "schemas": [ + { + "$id": "entity", + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "type": "string", + "const": "entity" + } + } + } + ] + }, + { + "name": "person", + "hierarchy": [ + "entity", + "person" + ], + "schemas": [ + { + "$id": "person", + "$ref": "entity", + "properties": { + "first_name": { + "type": "string" + } + } + } + ] + } + ], + "puncs": [ + { + "name": "save_person_light", + "schemas": [ + { + "$id": "person.light", + "$ref": "person", + "extensible": false, + "properties": { + "first_name": { + "type": "string" + } + } + }, + { + "$id": "save_person_light.request", + "$ref": "person.light" + } + ] + } + ] + }, + "tests": [ + { + "description": "Valid person against person.light ad-hoc schema", + "schema_id": "save_person_light.request", + "data": { + "id": "1", + "type": "person", + "first_name": "John" + }, + "valid": true + }, + { + "description": "Invalid person against person.light (strictness violation)", + "schema_id": "save_person_light.request", + "data": { + "id": "1", + "type": "person", + "first_name": "John", + "extra": "bad" + }, + "valid": false + }, + { + "description": "Invalid entity against person.light ad-hoc schema (ancestor not allowed)", + "schema_id": "save_person_light.request", + "data": { + "id": "1", + "type": "entity", + "first_name": "John" + }, + "valid": false + } + ] + }, + { + "description": "Ad-hocs extending ad-hocs (No type property)", + "database": { + "puncs": [ + { + "name": "save_address", + "schemas": [ + { + "$id": "address", + "type": "object", + "properties": { + "street": { + "type": "string" + }, + "city": { + "type": "string" + } + } + }, + { + "$id": "us_address", + "$ref": "address", + "properties": { + "state": { + "type": "string" + }, + "zip": { + "type": "string" + } + } + }, + { + "$id": "save_address.request", + "$ref": "us_address" + } + ] + } + ] + }, + "tests": [ + { + "description": "Valid us_address", + "schema_id": "save_address.request", + "data": { + "street": "123 Main", + "city": "Anytown", + "state": "CA", + "zip": "12345" + }, + "valid": true + }, + { + "description": "Invalid base address against us_address", + "schema_id": "save_address.request", + "data": { + "street": "123 Main", + "city": "Anytown" + }, + "valid": true + } + ] + }, + { + "description": "Ad-hocs extending ad-hocs (with string type property, no magic)", + "database": { + "puncs": [ + { + "name": "save_config", + "schemas": [ + { + "$id": "config_base", + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "config_base" + }, + "setting": { + "type": "string" + } + } + }, + { + "$id": "config_advanced", + "$ref": "config_base", + "properties": { + "type": { + "type": "string", + "const": "config_advanced" + }, + "advanced_setting": { + "type": "string" + } + } + }, + { + "$id": "save_config.request", + "$ref": "config_base" + } + ] + } + ] + }, + "tests": [ + { + "description": "Valid config_base against config_base", + "schema_id": "save_config.request", + "data": { + "type": "config_base", + "setting": "on" + }, + "valid": true + }, + { + "description": "Invalid config_advanced against config_base (no type magic, const is strictly 'config_base')", + "schema_id": "save_config.request", + "data": { + "type": "config_advanced", + "setting": "on", + "advanced_setting": "off" + }, + "valid": false + } + ] + } +] \ No newline at end of file diff --git a/tests/fixtures/uniqueItems.json b/tests/fixtures/uniqueItems.json index ac4d23c..57fa866 100644 --- a/tests/fixtures/uniqueItems.json +++ b/tests/fixtures/uniqueItems.json @@ -1,10 +1,14 @@ [ { "description": "uniqueItems validation", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "uniqueItems": true, - "extensible": true + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "uniqueItems": true, + "extensible": true + } + ] }, "tests": [ { @@ -352,18 +356,22 @@ }, { "description": "uniqueItems with an array of items", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "prefixItems": [ + "database": { + "schemas": [ { - "type": "boolean" - }, - { - "type": "boolean" + "$schema": "https://json-schema.org/draft/2020-12/schema", + "prefixItems": [ + { + "type": "boolean" + }, + { + "type": "boolean" + } + ], + "uniqueItems": true, + "extensible": true } - ], - "uniqueItems": true, - "extensible": true + ] }, "tests": [ { @@ -442,18 +450,22 @@ }, { "description": "uniqueItems with an array of items and additionalItems=false", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "prefixItems": [ + "database": { + "schemas": [ { - "type": "boolean" - }, - { - "type": "boolean" + "$schema": "https://json-schema.org/draft/2020-12/schema", + "prefixItems": [ + { + "type": "boolean" + }, + { + "type": "boolean" + } + ], + "uniqueItems": true, + "items": false } - ], - "uniqueItems": true, - "items": false + ] }, "tests": [ { @@ -501,10 +513,14 @@ }, { "description": "uniqueItems=false validation", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "uniqueItems": false, - "extensible": true + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "uniqueItems": false, + "extensible": true + } + ] }, "tests": [ { @@ -683,18 +699,22 @@ }, { "description": "uniqueItems=false with an array of items", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "prefixItems": [ + "database": { + "schemas": [ { - "type": "boolean" - }, - { - "type": "boolean" + "$schema": "https://json-schema.org/draft/2020-12/schema", + "prefixItems": [ + { + "type": "boolean" + }, + { + "type": "boolean" + } + ], + "uniqueItems": false, + "extensible": true } - ], - "uniqueItems": false, - "extensible": true + ] }, "tests": [ { @@ -773,18 +793,22 @@ }, { "description": "uniqueItems=false with an array of items and additionalItems=false", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "prefixItems": [ + "database": { + "schemas": [ { - "type": "boolean" - }, - { - "type": "boolean" + "$schema": "https://json-schema.org/draft/2020-12/schema", + "prefixItems": [ + { + "type": "boolean" + }, + { + "type": "boolean" + } + ], + "uniqueItems": false, + "items": false } - ], - "uniqueItems": false, - "items": false + ] }, "tests": [ { @@ -832,10 +856,14 @@ }, { "description": "extensible: true allows extra items in uniqueItems", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "uniqueItems": true, - "extensible": true + "database": { + "schemas": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "uniqueItems": true, + "extensible": true + } + ] }, "tests": [ { diff --git a/tests/lib.rs b/tests/lib.rs index 1d5f130..6406947 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -1,4 +1,4 @@ -use jspg::*; +use ::jspg::*; use pgrx::JsonB; use serde_json::json; @@ -22,20 +22,22 @@ fn test_library_api() { ); // 2. Cache schemas - let puncs = json!([]); - let types = json!([{ - "schemas": [{ - "$id": "test_schema", - "type": "object", - "properties": { - "name": { "type": "string" } - }, - "required": ["name"] + let db_json = json!({ + "puncs": [], + "enums": [], + "types": [{ + "schemas": [{ + "$id": "test_schema", + "type": "object", + "properties": { + "name": { "type": "string" } + }, + "required": ["name"] + }] }] - }]); - let enums = json!([]); + }); - let cache_drop = cache_json_schemas(JsonB(enums), JsonB(types), JsonB(puncs)); + let cache_drop = jspg_cache_database(JsonB(db_json)); assert_eq!( cache_drop.0, json!({