Compare commits

...

7 Commits

60 changed files with 4009 additions and 2286 deletions

BIN
.DS_Store vendored

Binary file not shown.

View File

@ -28,7 +28,7 @@ These functions operate on the global `GLOBAL_JSPG` engine instance and provide
* `jspg_setup(database jsonb) -> jsonb`: Initializes the engine. Deserializes the full database schema registry (types, enums, puncs, relations) from Postgres and compiles them into memory atomically.
* `jspg_teardown() -> jsonb`: Clears the current session's engine instance from `GLOBAL_JSPG`, resetting the cache.
* `jspg_schemas() -> jsonb`: Exports the fully compiled AST snapshot (including all inherited dependencies) out of `GLOBAL_JSPG` into standard JSON Schema representations.
* `jspg_database() -> jsonb`: Exports the fully compiled snapshot of the database registry (including Types, Puncs, Enums, and Relations) out of `GLOBAL_JSPG` into standard JSON Schema representations.
---
@ -36,6 +36,17 @@ These functions operate on the global `GLOBAL_JSPG` engine instance and provide
JSPG augments standard JSON Schema 2020-12 to provide an opinionated, strict, and highly ergonomic Object-Oriented paradigm. Developers defining Punc Data Models should follow these conventions.
### Realms (Topological Boundaries)
JSPG strictly organizes schemas into three distinct topological boundaries called **Realms** to prevent cross-contamination and ensure secure API generation:
* **Type Realm (`database.types`)**: Represents physical Postgres tables or structural JSONB bubbles. Table-backed entities here are strictly evaluated for their `type` or `kind` discriminators if they possess polymorphic variations.
* **Punc Realm (`database.puncs`)**: Represents API endpoint Contracts (functions). Contains strictly `.request` and `.response` shapes. These cannot be inherited by standard data models.
* **Enum Realm (`database.enums`)**: Represents simple restricted value lists. Handled universally across all lookups.
The core execution engines natively enforce these boundaries:
* **Validator**: Routes dynamically using a single schema key, transparently switching domains to validate Punc requests/responses from the `Punc` realm, or raw instance payloads from the `Type` realm.
* **Merger**: Strictly bounded to the `Type` Realm. It is philosophically impossible and mathematically illegal to attempt to UPSERT an API endpoint.
* **Queryer**: Routes recursively. Safely evaluates API boundary inputs directly from the `Punc` realm, while tracing underlying table targets back through the `Type` realm to physically compile SQL `SELECT` statements.
### Types of Types
* **Table-Backed (Entity Types)**: Primarily defined in root `types` schemas. These represent physical Postgres tables.
* They are implicitly registered in the Global Registry using their precise key name mapped from the database compilation phase.
@ -45,12 +56,14 @@ JSPG augments standard JSON Schema 2020-12 to provide an opinionated, strict, an
* **Global Schema Registration**: Roots must be attached to the top-level keys mapped from the `types`, `enums`, or `puncs` database tables.
* They can re-use the standard `type` discriminator locally for `oneOf` polymorphism without conflicting with global Postgres Table constraints.
### Discriminators & The Dot Convention (A.B)
In Punc, polymorphic targets like explicit tagged unions or STI (Single Table Inheritance) rely on discriminators. Because Punc favors universal consistency, a schema's data contract must be explicit and mathematically identical regardless of the routing context an endpoint consumes it through.
### Discriminators & The `<Variant>.<Base>` Convention
In Punc, polymorphic targets like explicit tagged unions or STI (Single Table Inheritance) rely on discriminators. The system heavily leverages a standard `<Variant>.<Base>` dot-notation to enforce topological boundaries deterministically.
**The 2-Tier Paradigm**: The system inherently prevents "God Tables" by restricting routing to exactly two dimensions, guaranteeing absolute $O(1)$ lookups without ambiguity:
1. **Vertical Routing (`type`)**: Identifies the specific Postgres Table lineage (e.g. `person` vs `organization`).
2. **Horizontal Routing (`kind.type`)**: Natively evaluates Single Table Inheritance. The runtime dynamically concatenates `$kind.$type` to yield the namespace-protected schema key (e.g. `light.person`), maintaining collision-free schema registration.
**The 2-Tier Paradigm**: The system prevents "God Tables" by restricting routing to exactly two dimensions, guaranteeing absolute $O(1)$ lookups without ambiguity:
1. **Base (Vertical Routing)**: Represents the core physical lineage or foundational structural boundary. For entities, this is the table `type` (e.g. `person` or `widget`). For composed schemas, this is the root structural archetype (e.g., `filter`).
2. **Variant (Horizontal Routing)**: Represents the specific contextual projection or runtime mutation applied to the Base. For STI entities, this is the `kind` (e.g., `light`, `heavy`, `stock`). For composed filters, the variant identifies the entity it targets (e.g., `person`, `invoice`).
When an object is evaluated for STI polymorphism, the runtime natively extracts its `$kind` and `$type` values, dynamically concatenating them as `<Variant>.<Base>` (e.g. `light.person` or `stock.widget`) to yield the namespace-protected schema key.
Therefore, any schema that participates in polymorphic discrimination MUST explicitly define its discriminator properties natively inside its `properties` block. However, to stay DRY and maintain flexible APIs, you **DO NOT** need to hardcode `const` values, nor should you add them to your `required` array. The Punc engine treats `type` and `kind` as **magic properties**.
@ -80,6 +93,7 @@ Punc completely abandons the standard JSON Schema `$ref` keyword. Instead, it ov
* **Implicit Keyword Shadowing**: Unlike standard JSON Schema inheritance, local property definitions natively override and shadow inherited properties.
* **Primitive Array Shorthand (Optionality)**: The `type` array syntax is heavily optimized for nullable fields. Defining `"type": ["budget", "null"]` natively builds a nullable strict, generating `Budget? budget;` in Dart. You can freely mix primitives like `["string", "number", "null"]`.
* **Strict Array Constraint**: To explicitly prevent mathematically ambiguous Multiple Inheritance, a `type` array is strictly constrained to at most **ONE** Custom Object Pointer. Defining `"type": ["person", "organization"]` will intentionally trigger a fatal database compilation error natively instructing developers to build a proper tagged union (`oneOf`) instead.
* **Dynamic Type Bindings (`"$sibling.[suffix]"`)**: If a `type` string begins with a `$` (e.g., `"type": "$kind.filter"`), the JSPG engine treats it as a Dynamic Pointer. During compile time, it safely defers boundary checks. During runtime validation, the engine dynamically reads the literal string value of the referenced sibling property (`kind`) on the *current parent JSON object*, evaluates the substitution (e.g., `"person.filter"`), and instantly routes execution to that schema in $O(1)$ time. This enables incredibly powerful dynamic JSONB shapes (like a generic `filter` column inside a `search` table) without forcing downstream code generators to build unmaintainable unions.
### Polymorphism (`family` and `oneOf`)
Polymorphism is how an object boundary can dynamically take on entirely different shapes based on the payload provided at runtime. Punc utilizes the static database metadata generated from Postgres (`db.types`) to enforce these boundaries deterministically, rather than relying on ambiguous tree-traversals.
@ -92,7 +106,7 @@ Polymorphism is how an object boundary can dynamically take on entirely differen
* **Scenario B: Prefixed Tables (Vertical Projection)**
* *Setup*: `{ "family": "light.organization" }`
* *Execution*: The engine sees the prefix `light.` and base `organization`. It queries `db.types.get("organization").variations` and dynamically prepends the prefix to discover the relevant UI schemas.
* *Options*: `person` -> `light.person`, `organization` -> `light.organization`. (If a projection like `light.bot` does not exist in `db.schemas`, it is safely ignored).
* *Options*: `person` -> `light.person`, `organization` -> `light.organization`. (If a projection like `light.bot` does not exist in the Type Registry, it is safely ignored).
* **Scenario C: Single Table Inheritance (Horizontal Routing)**
* *Setup*: `{ "family": "widget" }` (Where `widget` is a table type but has no external variations).
* *Execution*: The engine queries `db.types.get("widget").variations` and finds only `["widget"]`. Since it lacks table inheritance, it is treated as STI. The engine scans the specific, confined `schemas` array directly under `db.types.get("widget")` for any registered key terminating in the base `.widget` (e.g., `stock.widget`). The `family` automatically uses `kind` as the discriminator.
@ -171,7 +185,7 @@ When compiling nested object graphs or arrays, the JSPG engine must dynamically
### Subschema Promotion
To seamlessly support deeply nested Object and Array structures, JSPG aggressively promotes them to standalone topological entities during the database compilation phase.
* **Path Generation:** While evaluating a unified graph originating from a base `types`, `enums`, or `puncs` key, the compiler tracks its exact path descent into nested objects and arrays. It dynamically calculates a localized alias string by appending a `/` pathing syntax (e.g., `base_schema_key/nested/path`) representing exactly its structural constraints.
* **Promotion:** This nested subschema chunk is mathematically elevated to its own independent key in the `db.schemas` cache registry using its full path. This guarantees that $O(1)$ WebSockets or isolated queries can natively target any arbitrary nested sub-object of a massive database topology directly without recursively re-parsing its parent's AST block every read. Note that you cannot use the `type` attribute to statically inherit from these automatically promoted subschemas.
* **Promotion:** This nested subschema chunk is mathematically elevated to an independent subschema entry natively within its parent's internal scope (e.g. inside `db.types.get("base").schemas`) using its full path. This guarantees that $O(1)$ WebSockets or isolated queries can natively target any arbitrary nested sub-object of a massive database topology directly without recursively re-parsing its parent's AST block every read. Note that you cannot use the `type` attribute to statically inherit from these automatically promoted subschemas.
* **Primitive Confinement:** Purely scalar or primitive branches (like `oneOf: [{type: "string"}, {type: "null"}]`) bypass global topological promotion. They are evaluated directly within the execution engine via isolated Tuple Indexes to explicitly protect the global DB Registry and Go Mixer from memory bloat.
---

View File

@ -2,21 +2,26 @@
{
"description": "additionalProperties validates properties not matched by properties",
"database": {
"schemas": {
"schema1": {
"properties": {
"foo": {
"type": "string"
},
"bar": {
"type": "number"
"types": [
{
"name": "schema1",
"schemas": {
"schema1": {
"properties": {
"foo": {
"type": "string"
},
"bar": {
"type": "number"
}
},
"additionalProperties": {
"type": "boolean"
}
}
},
"additionalProperties": {
"type": "boolean"
}
}
}
]
},
"tests": [
{
@ -61,19 +66,24 @@
{
"description": "extensible: true with additionalProperties still validates structure",
"database": {
"schemas": {
"additionalProperties_1_0": {
"properties": {
"foo": {
"type": "string"
"types": [
{
"name": "additionalProperties_1_0",
"schemas": {
"additionalProperties_1_0": {
"properties": {
"foo": {
"type": "string"
}
},
"extensible": true,
"additionalProperties": {
"type": "integer"
}
}
},
"extensible": true,
"additionalProperties": {
"type": "integer"
}
}
}
]
},
"tests": [
{
@ -106,21 +116,26 @@
{
"description": "complex additionalProperties with object and array items",
"database": {
"schemas": {
"schema3": {
"properties": {
"type": {
"type": "string"
}
},
"additionalProperties": {
"type": "array",
"items": {
"type": "string"
"types": [
{
"name": "schema3",
"schemas": {
"schema3": {
"properties": {
"type": {
"type": "string"
}
},
"additionalProperties": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
}
}
]
},
"tests": [
{

View File

@ -2,9 +2,14 @@
{
"description": "boolean schema 'true'",
"database": {
"schemas": {
"booleanSchema_0_0": {}
}
"types": [
{
"name": "booleanSchema_0_0",
"schemas": {
"booleanSchema_0_0": {}
}
}
]
},
"tests": [
{
@ -97,11 +102,16 @@
{
"description": "boolean schema 'false'",
"database": {
"schemas": {
"booleanSchema_1_0": {
"not": {}
"types": [
{
"name": "booleanSchema_1_0",
"schemas": {
"booleanSchema_1_0": {
"not": {}
}
}
}
}
]
},
"tests": [
{

View File

@ -2,218 +2,238 @@
{
"description": "Multi-Paradigm Declarative Cases",
"database": {
"schemas": {
"parallel_rules": {
"type": "object",
"properties": {
"status": {
"type": "string"
},
"kind": {
"type": "string"
}
},
"cases": [
{
"when": {
"properties": {
"status": {
"const": "unverified"
}
"types": [
{
"name": "parallel_rules",
"schemas": {
"parallel_rules": {
"type": "object",
"properties": {
"status": {
"type": "string"
},
"required": [
"status"
]
"kind": {
"type": "string"
}
},
"then": {
"properties": {
"amount_1": {
"type": "number"
"cases": [
{
"when": {
"properties": {
"status": {
"const": "unverified"
}
},
"required": [
"status"
]
},
"amount_2": {
"type": "number"
}
},
"required": [
"amount_1",
"amount_2"
]
}
},
{
"when": {
"properties": {
"kind": {
"const": "credit"
}
},
"required": [
"kind"
]
},
"then": {
"properties": {
"cvv": {
"type": "number"
}
},
"required": [
"cvv"
]
}
}
]
},
"mutually_exclusive": {
"type": "object",
"properties": {
"type": {
"type": "string"
}
},
"cases": [
{
"when": {
"properties": {
"type": {
"const": "A"
}
},
"required": [
"type"
]
},
"then": {
"properties": {
"field_a": {
"type": "number"
}
},
"required": [
"field_a"
]
}
},
{
"when": {
"properties": {
"type": {
"const": "B"
}
},
"required": [
"type"
]
},
"then": {
"properties": {
"field_b": {
"type": "number"
}
},
"required": [
"field_b"
]
},
"else": {
"properties": {
"fallback_b": {
"type": "number"
}
},
"required": [
"fallback_b"
]
}
}
]
},
"nested_fallbacks": {
"type": "object",
"properties": {
"tier": {
"type": "string"
}
},
"cases": [
{
"when": {
"properties": {
"tier": {
"const": "1"
}
},
"required": [
"tier"
]
},
"then": {
"properties": {
"basic": {
"type": "number"
}
},
"required": [
"basic"
]
},
"else": {
"cases": [
{
"when": {
"properties": {
"tier": {
"const": "2"
}
"then": {
"properties": {
"amount_1": {
"type": "number"
},
"required": [
"tier"
]
"amount_2": {
"type": "number"
}
},
"then": {
"properties": {
"standard": {
"type": "number"
}
},
"required": [
"standard"
]
},
"else": {
"properties": {
"premium": {
"type": "number"
}
},
"required": [
"premium"
]
}
}
]
}
}
]
},
"missing_when": {
"type": "object",
"cases": [
{
"else": {
"properties": {
"unconditional": {
"type": "number"
"required": [
"amount_1",
"amount_2"
]
}
},
"required": [
"unconditional"
]
}
{
"when": {
"properties": {
"kind": {
"const": "credit"
}
},
"required": [
"kind"
]
},
"then": {
"properties": {
"cvv": {
"type": "number"
}
},
"required": [
"cvv"
]
}
}
]
}
]
}
},
{
"name": "mutually_exclusive",
"schemas": {
"mutually_exclusive": {
"type": "object",
"properties": {
"type": {
"type": "string"
}
},
"cases": [
{
"when": {
"properties": {
"type": {
"const": "A"
}
},
"required": [
"type"
]
},
"then": {
"properties": {
"field_a": {
"type": "number"
}
},
"required": [
"field_a"
]
}
},
{
"when": {
"properties": {
"type": {
"const": "B"
}
},
"required": [
"type"
]
},
"then": {
"properties": {
"field_b": {
"type": "number"
}
},
"required": [
"field_b"
]
},
"else": {
"properties": {
"fallback_b": {
"type": "number"
}
},
"required": [
"fallback_b"
]
}
}
]
}
}
},
{
"name": "nested_fallbacks",
"schemas": {
"nested_fallbacks": {
"type": "object",
"properties": {
"tier": {
"type": "string"
}
},
"cases": [
{
"when": {
"properties": {
"tier": {
"const": "1"
}
},
"required": [
"tier"
]
},
"then": {
"properties": {
"basic": {
"type": "number"
}
},
"required": [
"basic"
]
},
"else": {
"cases": [
{
"when": {
"properties": {
"tier": {
"const": "2"
}
},
"required": [
"tier"
]
},
"then": {
"properties": {
"standard": {
"type": "number"
}
},
"required": [
"standard"
]
},
"else": {
"properties": {
"premium": {
"type": "number"
}
},
"required": [
"premium"
]
}
}
]
}
}
]
}
}
},
{
"name": "missing_when",
"schemas": {
"missing_when": {
"type": "object",
"cases": [
{
"else": {
"properties": {
"unconditional": {
"type": "number"
}
},
"required": [
"unconditional"
]
}
}
]
}
}
}
}
]
},
"tests": [
{

View File

@ -2,11 +2,16 @@
{
"description": "const validation",
"database": {
"schemas": {
"const_0_0": {
"const": 2
"types": [
{
"name": "const_0_0",
"schemas": {
"const_0_0": {
"const": 2
}
}
}
}
]
},
"tests": [
{
@ -41,18 +46,23 @@
{
"description": "const with object",
"database": {
"schemas": {
"const_1_0": {
"const": {
"foo": "bar",
"baz": "bax"
},
"properties": {
"foo": {},
"baz": {}
"types": [
{
"name": "const_1_0",
"schemas": {
"const_1_0": {
"const": {
"foo": "bar",
"baz": "bax"
},
"properties": {
"foo": {},
"baz": {}
}
}
}
}
}
]
},
"tests": [
{
@ -107,15 +117,20 @@
{
"description": "const with array",
"database": {
"schemas": {
"const_2_0": {
"const": [
{
"foo": "bar"
"types": [
{
"name": "const_2_0",
"schemas": {
"const_2_0": {
"const": [
{
"foo": "bar"
}
]
}
]
}
}
}
]
},
"tests": [
{
@ -160,11 +175,16 @@
{
"description": "const with null",
"database": {
"schemas": {
"const_3_0": {
"const": null
"types": [
{
"name": "const_3_0",
"schemas": {
"const_3_0": {
"const": null
}
}
}
}
]
},
"tests": [
{
@ -190,11 +210,16 @@
{
"description": "const with false does not match 0",
"database": {
"schemas": {
"const_4_0": {
"const": false
"types": [
{
"name": "const_4_0",
"schemas": {
"const_4_0": {
"const": false
}
}
}
}
]
},
"tests": [
{
@ -229,11 +254,16 @@
{
"description": "const with true does not match 1",
"database": {
"schemas": {
"const_5_0": {
"const": true
"types": [
{
"name": "const_5_0",
"schemas": {
"const_5_0": {
"const": true
}
}
}
}
]
},
"tests": [
{
@ -268,13 +298,18 @@
{
"description": "const with [false] does not match [0]",
"database": {
"schemas": {
"const_6_0": {
"const": [
false
]
"types": [
{
"name": "const_6_0",
"schemas": {
"const_6_0": {
"const": [
false
]
}
}
}
}
]
},
"tests": [
{
@ -315,13 +350,18 @@
{
"description": "const with [true] does not match [1]",
"database": {
"schemas": {
"const_7_0": {
"const": [
true
]
"types": [
{
"name": "const_7_0",
"schemas": {
"const_7_0": {
"const": [
true
]
}
}
}
}
]
},
"tests": [
{
@ -362,13 +402,18 @@
{
"description": "const with {\"a\": false} does not match {\"a\": 0}",
"database": {
"schemas": {
"const_8_0": {
"const": {
"a": false
"types": [
{
"name": "const_8_0",
"schemas": {
"const_8_0": {
"const": {
"a": false
}
}
}
}
}
]
},
"tests": [
{
@ -409,13 +454,18 @@
{
"description": "const with {\"a\": true} does not match {\"a\": 1}",
"database": {
"schemas": {
"const_9_0": {
"const": {
"a": true
"types": [
{
"name": "const_9_0",
"schemas": {
"const_9_0": {
"const": {
"a": true
}
}
}
}
}
]
},
"tests": [
{
@ -456,11 +506,16 @@
{
"description": "const with 0 does not match other zero-like types",
"database": {
"schemas": {
"const_10_0": {
"const": 0
"types": [
{
"name": "const_10_0",
"schemas": {
"const_10_0": {
"const": 0
}
}
}
}
]
},
"tests": [
{
@ -522,11 +577,16 @@
{
"description": "const with 1 does not match true",
"database": {
"schemas": {
"const_11_0": {
"const": 1
"types": [
{
"name": "const_11_0",
"schemas": {
"const_11_0": {
"const": 1
}
}
}
}
]
},
"tests": [
{
@ -561,11 +621,16 @@
{
"description": "const with -2.0 matches integer and float types",
"database": {
"schemas": {
"const_12_0": {
"const": -2
"types": [
{
"name": "const_12_0",
"schemas": {
"const_12_0": {
"const": -2
}
}
}
}
]
},
"tests": [
{
@ -618,11 +683,16 @@
{
"description": "float and integers are equal up to 64-bit representation limits",
"database": {
"schemas": {
"const_13_0": {
"const": 9007199254740992
"types": [
{
"name": "const_13_0",
"schemas": {
"const_13_0": {
"const": 9007199254740992
}
}
}
}
]
},
"tests": [
{
@ -666,11 +736,16 @@
{
"description": "nul characters in strings",
"database": {
"schemas": {
"const_14_0": {
"const": "hello\u0000there"
"types": [
{
"name": "const_14_0",
"schemas": {
"const_14_0": {
"const": "hello\u0000there"
}
}
}
}
]
},
"tests": [
{
@ -696,17 +771,22 @@
{
"description": "characters with the same visual representation but different codepoint",
"database": {
"schemas": {
"const_15_0": {
"const": "μ",
"$comment": "U+03BC"
"types": [
{
"name": "const_15_0",
"schemas": {
"const_15_0": {
"const": "\u03bc",
"$comment": "U+03BC"
}
}
}
}
]
},
"tests": [
{
"description": "character uses the same codepoint",
"data": "μ",
"data": "\u03bc",
"comment": "U+03BC",
"schema_id": "const_15_0",
"action": "validate",
@ -716,7 +796,7 @@
},
{
"description": "character looks the same but uses a different codepoint",
"data": "µ",
"data": "\u00b5",
"comment": "U+00B5",
"schema_id": "const_15_0",
"action": "validate",
@ -729,17 +809,22 @@
{
"description": "characters with the same visual representation, but different number of codepoints",
"database": {
"schemas": {
"const_16_0": {
"const": "ä",
"$comment": "U+00E4"
"types": [
{
"name": "const_16_0",
"schemas": {
"const_16_0": {
"const": "\u00e4",
"$comment": "U+00E4"
}
}
}
}
]
},
"tests": [
{
"description": "character uses the same codepoint",
"data": "ä",
"data": "\u00e4",
"comment": "U+00E4",
"schema_id": "const_16_0",
"action": "validate",
@ -749,7 +834,7 @@
},
{
"description": "character looks the same but uses combining marks",
"data": "ä",
"data": "a\u0308",
"comment": "a, U+0308",
"schema_id": "const_16_0",
"action": "validate",
@ -762,14 +847,19 @@
{
"description": "extensible: true allows extra properties in const object match",
"database": {
"schemas": {
"const_17_0": {
"const": {
"a": 1
},
"extensible": true
"types": [
{
"name": "const_17_0",
"schemas": {
"const_17_0": {
"const": {
"a": 1
},
"extensible": true
}
}
}
}
]
},
"tests": [
{

View File

@ -2,14 +2,19 @@
{
"description": "contains keyword validation",
"database": {
"schemas": {
"contains_0_0": {
"contains": {
"minimum": 5
},
"items": true
"types": [
{
"name": "contains_0_0",
"schemas": {
"contains_0_0": {
"contains": {
"minimum": 5
},
"items": true
}
}
}
}
]
},
"tests": [
{
@ -88,14 +93,19 @@
{
"description": "contains keyword with const keyword",
"database": {
"schemas": {
"contains_1_0": {
"contains": {
"const": 5
},
"items": true
"types": [
{
"name": "contains_1_0",
"schemas": {
"contains_1_0": {
"contains": {
"const": 5
},
"items": true
}
}
}
}
]
},
"tests": [
{
@ -144,11 +154,16 @@
{
"description": "contains keyword with boolean schema true",
"database": {
"schemas": {
"contains_2_0": {
"contains": true
"types": [
{
"name": "contains_2_0",
"schemas": {
"contains_2_0": {
"contains": true
}
}
}
}
]
},
"tests": [
{
@ -176,11 +191,16 @@
{
"description": "contains keyword with boolean schema false",
"database": {
"schemas": {
"contains_3_0": {
"contains": false
"types": [
{
"name": "contains_3_0",
"schemas": {
"contains_3_0": {
"contains": false
}
}
}
}
]
},
"tests": [
{
@ -217,16 +237,21 @@
{
"description": "items + contains",
"database": {
"schemas": {
"contains_4_0": {
"items": {
"multipleOf": 2
},
"contains": {
"multipleOf": 3
"types": [
{
"name": "contains_4_0",
"schemas": {
"contains_4_0": {
"items": {
"multipleOf": 2
},
"contains": {
"multipleOf": 3
}
}
}
}
}
]
},
"tests": [
{
@ -284,14 +309,19 @@
{
"description": "contains with false if subschema",
"database": {
"schemas": {
"contains_5_0": {
"contains": {
"if": false,
"else": true
"types": [
{
"name": "contains_5_0",
"schemas": {
"contains_5_0": {
"contains": {
"if": false,
"else": true
}
}
}
}
}
]
},
"tests": [
{
@ -319,13 +349,18 @@
{
"description": "contains with null instance elements",
"database": {
"schemas": {
"contains_6_0": {
"contains": {
"type": "null"
"types": [
{
"name": "contains_6_0",
"schemas": {
"contains_6_0": {
"contains": {
"type": "null"
}
}
}
}
}
]
},
"tests": [
{
@ -344,14 +379,19 @@
{
"description": "extensible: true allows non-matching items in contains",
"database": {
"schemas": {
"contains_7_0": {
"contains": {
"const": 1
},
"extensible": true
"types": [
{
"name": "contains_7_0",
"schemas": {
"contains_7_0": {
"contains": {
"const": 1
},
"extensible": true
}
}
}
}
]
},
"tests": [
{
@ -371,13 +411,18 @@
{
"description": "strict by default: non-matching items in contains are invalid",
"database": {
"schemas": {
"contains_8_0": {
"contains": {
"const": 1
"types": [
{
"name": "contains_8_0",
"schemas": {
"contains_8_0": {
"contains": {
"const": 1
}
}
}
}
}
]
},
"tests": [
{

View File

@ -2,11 +2,16 @@
{
"description": "validation of string-encoded content based on media type",
"database": {
"schemas": {
"content_0_0": {
"contentMediaType": "application/json"
"types": [
{
"name": "content_0_0",
"schemas": {
"content_0_0": {
"contentMediaType": "application/json"
}
}
}
}
]
},
"tests": [
{
@ -41,11 +46,16 @@
{
"description": "validation of binary string-encoding",
"database": {
"schemas": {
"content_1_0": {
"contentEncoding": "base64"
"types": [
{
"name": "content_1_0",
"schemas": {
"content_1_0": {
"contentEncoding": "base64"
}
}
}
}
]
},
"tests": [
{
@ -80,12 +90,17 @@
{
"description": "validation of binary-encoded media type documents",
"database": {
"schemas": {
"content_2_0": {
"contentMediaType": "application/json",
"contentEncoding": "base64"
"types": [
{
"name": "content_2_0",
"schemas": {
"content_2_0": {
"contentMediaType": "application/json",
"contentEncoding": "base64"
}
}
}
}
]
},
"tests": [
{
@ -129,26 +144,31 @@
{
"description": "validation of binary-encoded media type documents with schema",
"database": {
"schemas": {
"content_3_0": {
"contentMediaType": "application/json",
"contentEncoding": "base64",
"contentSchema": {
"type": "object",
"required": [
"foo"
],
"properties": {
"foo": {
"type": "string"
},
"boo": {
"type": "integer"
"types": [
{
"name": "content_3_0",
"schemas": {
"content_3_0": {
"contentMediaType": "application/json",
"contentEncoding": "base64",
"contentSchema": {
"type": "object",
"required": [
"foo"
],
"properties": {
"foo": {
"type": "string"
},
"boo": {
"type": "integer"
}
}
}
}
}
}
}
]
},
"tests": [
{

View File

@ -2,17 +2,22 @@
{
"description": "single dependency (required)",
"database": {
"schemas": {
"schema1": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"dependencies": {
"bar": [
"foo"
]
},
"extensible": true
"types": [
{
"name": "schema1",
"schemas": {
"schema1": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"dependencies": {
"bar": [
"foo"
]
},
"extensible": true
}
}
}
}
]
},
"tests": [
{
@ -92,15 +97,20 @@
{
"description": "empty dependents",
"database": {
"schemas": {
"schema2": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"dependencies": {
"bar": []
},
"extensible": true
"types": [
{
"name": "schema2",
"schemas": {
"schema2": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"dependencies": {
"bar": []
},
"extensible": true
}
}
}
}
]
},
"tests": [
{
@ -137,18 +147,23 @@
{
"description": "multiple dependents required",
"database": {
"schemas": {
"schema3": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"dependencies": {
"quux": [
"foo",
"bar"
]
},
"extensible": true
"types": [
{
"name": "schema3",
"schemas": {
"schema3": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"dependencies": {
"quux": [
"foo",
"bar"
]
},
"extensible": true
}
}
}
}
]
},
"tests": [
{
@ -225,20 +240,25 @@
{
"description": "dependencies with escaped characters",
"database": {
"schemas": {
"schema4": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"dependencies": {
"foo\nbar": [
"foo\rbar"
],
"foo\"bar": [
"foo'bar"
]
},
"extensible": true
"types": [
{
"name": "schema4",
"schemas": {
"schema4": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"dependencies": {
"foo\nbar": [
"foo\rbar"
],
"foo\"bar": [
"foo'bar"
]
},
"extensible": true
}
}
}
}
]
},
"tests": [
{
@ -293,17 +313,22 @@
{
"description": "extensible: true allows extra properties in dependentRequired",
"database": {
"schemas": {
"schema5": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"dependencies": {
"bar": [
"foo"
]
},
"extensible": true
"types": [
{
"name": "schema5",
"schemas": {
"schema5": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"dependencies": {
"bar": [
"foo"
]
},
"extensible": true
}
}
}
}
]
},
"tests": [
{
@ -324,27 +349,32 @@
{
"description": "single dependency (schemas, STRICT)",
"database": {
"schemas": {
"schema_schema1": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"foo": true,
"bar": true
},
"dependencies": {
"bar": {
"types": [
{
"name": "schema_schema1",
"schemas": {
"schema_schema1": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"foo": {
"type": "integer"
},
"foo": true,
"bar": true
},
"dependencies": {
"bar": {
"type": "integer"
"properties": {
"foo": {
"type": "integer"
},
"bar": {
"type": "integer"
}
}
}
}
}
}
}
}
]
},
"tests": [
{
@ -445,28 +475,33 @@
{
"description": "single dependency (schemas, EXTENSIBLE)",
"database": {
"schemas": {
"schema_schema2": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"foo": true,
"bar": true
},
"dependencies": {
"bar": {
"types": [
{
"name": "schema_schema2",
"schemas": {
"schema_schema2": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"foo": {
"type": "integer"
},
"foo": true,
"bar": true
},
"dependencies": {
"bar": {
"type": "integer"
"properties": {
"foo": {
"type": "integer"
},
"bar": {
"type": "integer"
}
}
}
}
},
"extensible": true
}
},
"extensible": true
}
}
}
]
},
"tests": [
{
@ -485,19 +520,24 @@
{
"description": "boolean subschemas",
"database": {
"schemas": {
"schema_schema3": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"foo": true,
"bar": true
},
"dependencies": {
"foo": true,
"bar": false
"types": [
{
"name": "schema_schema3",
"schemas": {
"schema_schema3": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"foo": true,
"bar": true
},
"dependencies": {
"foo": true,
"bar": false
}
}
}
}
}
]
},
"tests": [
{
@ -548,29 +588,34 @@
{
"description": "dependencies with escaped characters",
"database": {
"schemas": {
"schema_schema4": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"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"
]
"types": [
{
"name": "schema_schema4",
"schemas": {
"schema_schema4": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"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": [
{
@ -628,22 +673,27 @@
{
"description": "dependent subschema incompatible with root (STRICT)",
"database": {
"schemas": {
"schema_schema5": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"foo": {},
"baz": true
},
"dependencies": {
"foo": {
"types": [
{
"name": "schema_schema5",
"schemas": {
"schema_schema5": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"bar": {}
"foo": {},
"baz": true
},
"dependencies": {
"foo": {
"properties": {
"bar": {}
}
}
}
}
}
}
}
]
},
"tests": [
{
@ -701,24 +751,29 @@
{
"description": "dependent subschema incompatible with root (EXTENSIBLE)",
"database": {
"schemas": {
"schema_schema6": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"foo": {},
"baz": true
},
"dependencies": {
"foo": {
"types": [
{
"name": "schema_schema6",
"schemas": {
"schema_schema6": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"bar": {}
"foo": {},
"baz": true
},
"additionalProperties": false
"dependencies": {
"foo": {
"properties": {
"bar": {}
},
"additionalProperties": false
}
},
"extensible": true
}
},
"extensible": true
}
}
}
]
},
"tests": [
{

155
fixtures/dynamicType.json Normal file
View File

@ -0,0 +1,155 @@
[
{
"description": "Dynamic type binding ($sibling.suffix) validation",
"database": {
"types": [
{
"name": "person",
"schemas": {
"person.filter": {
"properties": {
"age": {
"type": "integer"
}
}
}
}
},
{
"name": "widget",
"schemas": {
"widget.filter": {
"properties": {
"weight": {
"type": "integer"
}
}
}
}
},
{
"name": "search",
"schemas": {
"search": {
"properties": {
"kind": {
"type": "string"
},
"filter": {
"type": "$kind.filter"
}
}
}
}
}
]
},
"tests": [
{
"description": "Valid person filter payload",
"data": {
"kind": "person",
"filter": {
"age": 30
}
},
"schema_id": "search",
"action": "validate",
"expect": {
"success": true
}
},
{
"description": "Invalid person filter payload (fails constraint)",
"data": {
"kind": "person",
"filter": {
"age": "thirty"
}
},
"schema_id": "search",
"action": "validate",
"expect": {
"success": false,
"errors": [
{
"code": "INVALID_TYPE",
"details": {
"path": "filter/age"
}
}
]
}
},
{
"description": "Valid widget filter payload",
"data": {
"kind": "widget",
"filter": {
"weight": 500
}
},
"schema_id": "search",
"action": "validate",
"expect": {
"success": true
}
},
{
"description": "Fails resolution if kind doesn't match an existing schema",
"data": {
"kind": "unknown",
"filter": {
"weight": 500
}
},
"schema_id": "search",
"action": "validate",
"expect": {
"success": false,
"errors": [
{
"code": "DYNAMIC_TYPE_RESOLUTION_FAILED",
"details": {
"path": "filter"
}
},
{
"code": "STRICT_PROPERTY_VIOLATION",
"details": {
"path": "filter/weight"
}
}
]
}
},
{
"description": "Fails resolution if discriminator is missing",
"data": {
"filter": {
"weight": 500
}
},
"schema_id": "search",
"action": "validate",
"expect": {
"success": false,
"errors": [
{
"code": "DYNAMIC_TYPE_RESOLUTION_FAILED",
"details": {
"path": "filter"
}
},
{
"code": "STRICT_PROPERTY_VIOLATION",
"details": {
"path": "filter/weight"
}
}
]
}
}
]
}
]

View File

@ -2,43 +2,48 @@
{
"description": "empty string is valid for all types (except const)",
"database": {
"schemas": {
"emptyString_0_0": {
"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": ""
"types": [
{
"name": "emptyString_0_0",
"schemas": {
"emptyString_0_0": {
"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": [
{

View File

@ -2,15 +2,20 @@
{
"description": "simple enum validation",
"database": {
"schemas": {
"enum_0_0": {
"enum": [
1,
2,
3
]
"types": [
{
"name": "enum_0_0",
"schemas": {
"enum_0_0": {
"enum": [
1,
2,
3
]
}
}
}
}
]
},
"tests": [
{
@ -36,22 +41,27 @@
{
"description": "heterogeneous enum validation",
"database": {
"schemas": {
"enum_1_0": {
"enum": [
6,
"foo",
[],
true,
{
"foo": 12
"types": [
{
"name": "enum_1_0",
"schemas": {
"enum_1_0": {
"enum": [
6,
"foo",
[],
true,
{
"foo": 12
}
],
"properties": {
"foo": {}
}
}
],
"properties": {
"foo": {}
}
}
}
]
},
"tests": [
{
@ -111,14 +121,19 @@
{
"description": "heterogeneous enum-with-null validation",
"database": {
"schemas": {
"enum_2_0": {
"enum": [
6,
null
]
"types": [
{
"name": "enum_2_0",
"schemas": {
"enum_2_0": {
"enum": [
6,
null
]
}
}
}
}
]
},
"tests": [
{
@ -153,26 +168,31 @@
{
"description": "enums in properties",
"database": {
"schemas": {
"enum_3_0": {
"type": "object",
"properties": {
"foo": {
"enum": [
"foo"
]
},
"bar": {
"enum": [
"types": [
{
"name": "enum_3_0",
"schemas": {
"enum_3_0": {
"type": "object",
"properties": {
"foo": {
"enum": [
"foo"
]
},
"bar": {
"enum": [
"bar"
]
}
},
"required": [
"bar"
]
}
},
"required": [
"bar"
]
}
}
}
]
},
"tests": [
{
@ -247,14 +267,19 @@
{
"description": "enum with escaped characters",
"database": {
"schemas": {
"enum_4_0": {
"enum": [
"foo\nbar",
"foo\rbar"
]
"types": [
{
"name": "enum_4_0",
"schemas": {
"enum_4_0": {
"enum": [
"foo\nbar",
"foo\rbar"
]
}
}
}
}
]
},
"tests": [
{
@ -289,13 +314,18 @@
{
"description": "enum with false does not match 0",
"database": {
"schemas": {
"enum_5_0": {
"enum": [
false
]
"types": [
{
"name": "enum_5_0",
"schemas": {
"enum_5_0": {
"enum": [
false
]
}
}
}
}
]
},
"tests": [
{
@ -330,15 +360,20 @@
{
"description": "enum with [false] does not match [0]",
"database": {
"schemas": {
"enum_6_0": {
"enum": [
[
false
]
]
"types": [
{
"name": "enum_6_0",
"schemas": {
"enum_6_0": {
"enum": [
[
false
]
]
}
}
}
}
]
},
"tests": [
{
@ -379,13 +414,18 @@
{
"description": "enum with true does not match 1",
"database": {
"schemas": {
"enum_7_0": {
"enum": [
true
]
"types": [
{
"name": "enum_7_0",
"schemas": {
"enum_7_0": {
"enum": [
true
]
}
}
}
}
]
},
"tests": [
{
@ -420,15 +460,20 @@
{
"description": "enum with [true] does not match [1]",
"database": {
"schemas": {
"enum_8_0": {
"enum": [
[
true
]
]
"types": [
{
"name": "enum_8_0",
"schemas": {
"enum_8_0": {
"enum": [
[
true
]
]
}
}
}
}
]
},
"tests": [
{
@ -469,13 +514,18 @@
{
"description": "enum with 0 does not match false",
"database": {
"schemas": {
"enum_9_0": {
"enum": [
0
]
"types": [
{
"name": "enum_9_0",
"schemas": {
"enum_9_0": {
"enum": [
0
]
}
}
}
}
]
},
"tests": [
{
@ -510,15 +560,20 @@
{
"description": "enum with [0] does not match [false]",
"database": {
"schemas": {
"enum_10_0": {
"enum": [
[
0
]
]
"types": [
{
"name": "enum_10_0",
"schemas": {
"enum_10_0": {
"enum": [
[
0
]
]
}
}
}
}
]
},
"tests": [
{
@ -559,13 +614,18 @@
{
"description": "enum with 1 does not match true",
"database": {
"schemas": {
"enum_11_0": {
"enum": [
1
]
"types": [
{
"name": "enum_11_0",
"schemas": {
"enum_11_0": {
"enum": [
1
]
}
}
}
}
]
},
"tests": [
{
@ -600,15 +660,20 @@
{
"description": "enum with [1] does not match [true]",
"database": {
"schemas": {
"enum_12_0": {
"enum": [
[
1
]
]
"types": [
{
"name": "enum_12_0",
"schemas": {
"enum_12_0": {
"enum": [
[
1
]
]
}
}
}
}
]
},
"tests": [
{
@ -649,13 +714,18 @@
{
"description": "nul characters in strings",
"database": {
"schemas": {
"enum_13_0": {
"enum": [
"hello\u0000there"
]
"types": [
{
"name": "enum_13_0",
"schemas": {
"enum_13_0": {
"enum": [
"hello\u0000there"
]
}
}
}
}
]
},
"tests": [
{
@ -681,16 +751,21 @@
{
"description": "extensible: true allows extra properties in enum object match",
"database": {
"schemas": {
"enum_14_0": {
"enum": [
{
"foo": 1
"types": [
{
"name": "enum_14_0",
"schemas": {
"enum_14_0": {
"enum": [
{
"foo": 1
}
],
"extensible": true
}
],
"extensible": true
}
}
}
]
},
"tests": [
{

View File

@ -2,11 +2,16 @@
{
"description": "exclusiveMaximum validation",
"database": {
"schemas": {
"exclusiveMaximum_0_0": {
"exclusiveMaximum": 3
"types": [
{
"name": "exclusiveMaximum_0_0",
"schemas": {
"exclusiveMaximum_0_0": {
"exclusiveMaximum": 3
}
}
}
}
]
},
"tests": [
{

View File

@ -2,11 +2,16 @@
{
"description": "exclusiveMinimum validation",
"database": {
"schemas": {
"exclusiveMinimum_0_0": {
"exclusiveMinimum": 1.1
"types": [
{
"name": "exclusiveMinimum_0_0",
"schemas": {
"exclusiveMinimum_0_0": {
"exclusiveMinimum": 1.1
}
}
}
}
]
},
"tests": [
{

View File

@ -107,17 +107,17 @@
"search": {
"type": "object",
"properties": {
"kind": {
"type": "string"
},
"name": {
"type": "string"
},
"filter": {
"type": "filter"
"type": "$kind.filter"
}
}
},
"filter": {
"type": "object"
},
"condition": {
"type": "object",
"properties": {
@ -172,14 +172,50 @@
"schemas": {
"person": {},
"person.filter": {
"type": "filter",
"type": "object",
"compiledPropertyNames": [
"$and",
"$or",
"age",
"billing_address",
"birth_date",
"first_name"
],
"properties": {
"$and": {
"type": [
"array",
"null"
],
"items": {
"compiledPropertyNames": [
"$and",
"$or",
"age",
"billing_address",
"birth_date",
"first_name"
],
"type": "person.filter"
}
},
"$or": {
"type": [
"array",
"null"
],
"items": {
"compiledPropertyNames": [
"$and",
"$or",
"age",
"billing_address",
"birth_date",
"first_name"
],
"type": "person.filter"
}
},
"first_name": {
"type": [
"string.condition",
@ -208,11 +244,41 @@
},
"address": {},
"address.filter": {
"type": "filter",
"type": "object",
"compiledPropertyNames": [
"$and",
"$or",
"city"
],
"properties": {
"$and": {
"type": [
"array",
"null"
],
"items": {
"compiledPropertyNames": [
"$and",
"$or",
"city"
],
"type": "address.filter"
}
},
"$or": {
"type": [
"array",
"null"
],
"items": {
"compiledPropertyNames": [
"$and",
"$or",
"city"
],
"type": "address.filter"
}
},
"city": {
"type": [
"string.condition",
@ -221,22 +287,62 @@
}
}
},
"filter": {},
"condition": {},
"string.condition": {},
"integer.condition": {},
"date.condition": {},
"search": {},
"search.filter": {
"type": "filter",
"type": "object",
"compiledPropertyNames": [
"$and",
"$or",
"filter",
"kind",
"name"
],
"properties": {
"$and": {
"type": [
"array",
"null"
],
"items": {
"compiledPropertyNames": [
"$and",
"$or",
"filter",
"kind",
"name"
],
"type": "search.filter"
}
},
"$or": {
"type": [
"array",
"null"
],
"items": {
"compiledPropertyNames": [
"$and",
"$or",
"filter",
"kind",
"name"
],
"type": "search.filter"
}
},
"filter": {
"type": [
"filter.filter",
"$kind.filter.filter",
"null"
]
},
"kind": {
"type": [
"string.condition",
"null"
]
},

File diff suppressed because it is too large Load Diff

View File

@ -2,13 +2,18 @@
{
"description": "a schema given for items",
"database": {
"schemas": {
"items_0_0": {
"items": {
"type": "integer"
"types": [
{
"name": "items_0_0",
"schemas": {
"items_0_0": {
"items": {
"type": "integer"
}
}
}
}
}
]
},
"tests": [
{
@ -64,11 +69,16 @@
{
"description": "items with boolean schema (true)",
"database": {
"schemas": {
"items_1_0": {
"items": true
"types": [
{
"name": "items_1_0",
"schemas": {
"items_1_0": {
"items": true
}
}
}
}
]
},
"tests": [
{
@ -98,11 +108,16 @@
{
"description": "items with boolean schema (false)",
"database": {
"schemas": {
"items_2_0": {
"items": false
"types": [
{
"name": "items_2_0",
"schemas": {
"items_2_0": {
"items": false
}
}
}
}
]
},
"tests": [
{
@ -132,41 +147,56 @@
{
"description": "items and subitems",
"database": {
"schemas": {
"items_3_0": {
"type": "array",
"items": false,
"prefixItems": [
{
"type": "item"
},
{
"type": "item"
},
{
"type": "item"
"types": [
{
"name": "items_3_0",
"schemas": {
"items_3_0": {
"type": "array",
"items": false,
"prefixItems": [
{
"type": "item"
},
{
"type": "item"
},
{
"type": "item"
}
]
}
]
}
},
"item": {
"type": "array",
"items": false,
"prefixItems": [
{
"type": "sub-item"
},
{
"type": "sub-item"
{
"name": "item",
"schemas": {
"item": {
"type": "array",
"items": false,
"prefixItems": [
{
"type": "sub-item"
},
{
"type": "sub-item"
}
]
}
]
}
},
"sub-item": {
"type": "object",
"required": [
"foo"
]
{
"name": "sub-item",
"schemas": {
"sub-item": {
"type": "object",
"required": [
"foo"
]
}
}
}
}
]
},
"tests": [
{
@ -368,23 +398,28 @@
{
"description": "nested items",
"database": {
"schemas": {
"items_4_0": {
"type": "array",
"items": {
"type": "array",
"items": {
"types": [
{
"name": "items_4_0",
"schemas": {
"items_4_0": {
"type": "array",
"items": {
"type": "array",
"items": {
"type": "number"
"type": "array",
"items": {
"type": "array",
"items": {
"type": "number"
}
}
}
}
}
}
}
}
]
},
"tests": [
{
@ -500,16 +535,21 @@
{
"description": "prefixItems with no additional items allowed",
"database": {
"schemas": {
"items_5_0": {
"prefixItems": [
{},
{},
{}
],
"items": false
"types": [
{
"name": "items_5_0",
"schemas": {
"items_5_0": {
"prefixItems": [
{},
{},
{}
],
"items": false
}
}
}
}
]
},
"tests": [
{
@ -576,22 +616,27 @@
{
"description": "items does not look in applicators, valid case",
"database": {
"schemas": {
"items_6_0": {
"allOf": [
{
"prefixItems": [
"types": [
{
"name": "items_6_0",
"schemas": {
"items_6_0": {
"allOf": [
{
"minimum": 3
"prefixItems": [
{
"minimum": 3
}
]
}
]
],
"items": {
"minimum": 5
}
}
],
"items": {
"minimum": 5
}
}
}
]
},
"tests": [
{
@ -623,18 +668,23 @@
{
"description": "prefixItems validation adjusts the starting index for items",
"database": {
"schemas": {
"items_7_0": {
"prefixItems": [
{
"type": "string"
"types": [
{
"name": "items_7_0",
"schemas": {
"items_7_0": {
"prefixItems": [
{
"type": "string"
}
],
"items": {
"type": "integer"
}
}
],
"items": {
"type": "integer"
}
}
}
]
},
"tests": [
{
@ -667,14 +717,19 @@
{
"description": "items with heterogeneous array",
"database": {
"schemas": {
"items_8_0": {
"prefixItems": [
{}
],
"items": false
"types": [
{
"name": "items_8_0",
"schemas": {
"items_8_0": {
"prefixItems": [
{}
],
"items": false
}
}
}
}
]
},
"tests": [
{
@ -706,13 +761,18 @@
{
"description": "items with null instance elements",
"database": {
"schemas": {
"items_9_0": {
"items": {
"type": "null"
"types": [
{
"name": "items_9_0",
"schemas": {
"items_9_0": {
"items": {
"type": "null"
}
}
}
}
}
]
},
"tests": [
{
@ -731,12 +791,17 @@
{
"description": "extensible: true allows extra items (when items is false)",
"database": {
"schemas": {
"items_10_0": {
"items": false,
"extensible": true
"types": [
{
"name": "items_10_0",
"schemas": {
"items_10_0": {
"items": false,
"extensible": true
}
}
}
}
]
},
"tests": [
{
@ -755,14 +820,19 @@
{
"description": "extensible: true allows extra properties for items",
"database": {
"schemas": {
"items_11_0": {
"items": {
"minimum": 5
},
"extensible": true
"types": [
{
"name": "items_11_0",
"schemas": {
"items_11_0": {
"items": {
"minimum": 5
},
"extensible": true
}
}
}
}
]
},
"tests": [
{
@ -793,12 +863,17 @@
{
"description": "array: simple extensible array",
"database": {
"schemas": {
"items_12_0": {
"type": "array",
"extensible": true
"types": [
{
"name": "items_12_0",
"schemas": {
"items_12_0": {
"type": "array",
"extensible": true
}
}
}
}
]
},
"tests": [
{
@ -827,12 +902,17 @@
{
"description": "array: strict array",
"database": {
"schemas": {
"items_13_0": {
"type": "array",
"extensible": false
"types": [
{
"name": "items_13_0",
"schemas": {
"items_13_0": {
"type": "array",
"extensible": false
}
}
}
}
]
},
"tests": [
{
@ -860,14 +940,19 @@
{
"description": "array: items extensible",
"database": {
"schemas": {
"items_14_0": {
"type": "array",
"items": {
"extensible": true
"types": [
{
"name": "items_14_0",
"schemas": {
"items_14_0": {
"type": "array",
"items": {
"extensible": true
}
}
}
}
}
]
},
"tests": [
{
@ -897,15 +982,20 @@
{
"description": "array: items strict",
"database": {
"schemas": {
"items_15_0": {
"type": "array",
"items": {
"type": "object",
"extensible": false
"types": [
{
"name": "items_15_0",
"schemas": {
"items_15_0": {
"type": "array",
"items": {
"type": "object",
"extensible": false
}
}
}
}
}
]
},
"tests": [
{

View File

@ -2,12 +2,17 @@
{
"description": "maxContains without contains is ignored",
"database": {
"schemas": {
"maxContains_0_0": {
"maxContains": 1,
"extensible": true
"types": [
{
"name": "maxContains_0_0",
"schemas": {
"maxContains_0_0": {
"maxContains": 1,
"extensible": true
}
}
}
}
]
},
"tests": [
{
@ -38,15 +43,20 @@
{
"description": "maxContains with contains",
"database": {
"schemas": {
"maxContains_1_0": {
"contains": {
"const": 1
},
"maxContains": 1,
"extensible": true
"types": [
{
"name": "maxContains_1_0",
"schemas": {
"maxContains_1_0": {
"contains": {
"const": 1
},
"maxContains": 1,
"extensible": true
}
}
}
}
]
},
"tests": [
{
@ -111,15 +121,20 @@
{
"description": "maxContains with contains, value with a decimal",
"database": {
"schemas": {
"maxContains_2_0": {
"contains": {
"const": 1
},
"maxContains": 1,
"extensible": true
"types": [
{
"name": "maxContains_2_0",
"schemas": {
"maxContains_2_0": {
"contains": {
"const": 1
},
"maxContains": 1,
"extensible": true
}
}
}
}
]
},
"tests": [
{
@ -150,16 +165,21 @@
{
"description": "minContains < maxContains",
"database": {
"schemas": {
"maxContains_3_0": {
"contains": {
"const": 1
},
"minContains": 1,
"maxContains": 3,
"extensible": true
"types": [
{
"name": "maxContains_3_0",
"schemas": {
"maxContains_3_0": {
"contains": {
"const": 1
},
"minContains": 1,
"maxContains": 3,
"extensible": true
}
}
}
}
]
},
"tests": [
{
@ -202,15 +222,20 @@
{
"description": "extensible: true allows non-matching items in maxContains",
"database": {
"schemas": {
"maxContains_4_0": {
"contains": {
"const": 1
},
"maxContains": 1,
"extensible": true
"types": [
{
"name": "maxContains_4_0",
"schemas": {
"maxContains_4_0": {
"contains": {
"const": 1
},
"maxContains": 1,
"extensible": true
}
}
}
}
]
},
"tests": [
{

View File

@ -2,12 +2,17 @@
{
"description": "maxItems validation",
"database": {
"schemas": {
"maxItems_0_0": {
"maxItems": 2,
"extensible": true
"types": [
{
"name": "maxItems_0_0",
"schemas": {
"maxItems_0_0": {
"maxItems": 2,
"extensible": true
}
}
}
}
]
},
"tests": [
{
@ -60,12 +65,17 @@
{
"description": "maxItems validation with a decimal",
"database": {
"schemas": {
"maxItems_1_0": {
"maxItems": 2,
"extensible": true
"types": [
{
"name": "maxItems_1_0",
"schemas": {
"maxItems_1_0": {
"maxItems": 2,
"extensible": true
}
}
}
}
]
},
"tests": [
{
@ -97,12 +107,17 @@
{
"description": "extensible: true allows extra items in maxItems (but counted)",
"database": {
"schemas": {
"maxItems_2_0": {
"maxItems": 2,
"extensible": true
"types": [
{
"name": "maxItems_2_0",
"schemas": {
"maxItems_2_0": {
"maxItems": 2,
"extensible": true
}
}
}
}
]
},
"tests": [
{

View File

@ -2,11 +2,16 @@
{
"description": "maxLength validation",
"database": {
"schemas": {
"maxLength_0_0": {
"maxLength": 2
"types": [
{
"name": "maxLength_0_0",
"schemas": {
"maxLength_0_0": {
"maxLength": 2
}
}
}
}
]
},
"tests": [
{
@ -47,7 +52,7 @@
},
{
"description": "two graphemes is long enough",
"data": "💩💩",
"data": "\ud83d\udca9\ud83d\udca9",
"schema_id": "maxLength_0_0",
"action": "validate",
"expect": {
@ -59,11 +64,16 @@
{
"description": "maxLength validation with a decimal",
"database": {
"schemas": {
"maxLength_1_0": {
"maxLength": 2
"types": [
{
"name": "maxLength_1_0",
"schemas": {
"maxLength_1_0": {
"maxLength": 2
}
}
}
}
]
},
"tests": [
{

View File

@ -2,12 +2,17 @@
{
"description": "maxProperties validation",
"database": {
"schemas": {
"maxProperties_0_0": {
"maxProperties": 2,
"extensible": true
"types": [
{
"name": "maxProperties_0_0",
"schemas": {
"maxProperties_0_0": {
"maxProperties": 2,
"extensible": true
}
}
}
}
]
},
"tests": [
{
@ -82,12 +87,17 @@
{
"description": "maxProperties validation with a decimal",
"database": {
"schemas": {
"maxProperties_1_0": {
"maxProperties": 2,
"extensible": true
"types": [
{
"name": "maxProperties_1_0",
"schemas": {
"maxProperties_1_0": {
"maxProperties": 2,
"extensible": true
}
}
}
}
]
},
"tests": [
{
@ -119,12 +129,17 @@
{
"description": "maxProperties = 0 means the object is empty",
"database": {
"schemas": {
"maxProperties_2_0": {
"maxProperties": 0,
"extensible": true
"types": [
{
"name": "maxProperties_2_0",
"schemas": {
"maxProperties_2_0": {
"maxProperties": 0,
"extensible": true
}
}
}
}
]
},
"tests": [
{
@ -152,12 +167,17 @@
{
"description": "extensible: true allows extra properties in maxProperties (though maxProperties still counts them!)",
"database": {
"schemas": {
"maxProperties_3_0": {
"maxProperties": 2,
"extensible": true
"types": [
{
"name": "maxProperties_3_0",
"schemas": {
"maxProperties_3_0": {
"maxProperties": 2,
"extensible": true
}
}
}
}
]
},
"tests": [
{

View File

@ -2,11 +2,16 @@
{
"description": "maximum validation",
"database": {
"schemas": {
"maximum_0_0": {
"maximum": 3
"types": [
{
"name": "maximum_0_0",
"schemas": {
"maximum_0_0": {
"maximum": 3
}
}
}
}
]
},
"tests": [
{
@ -50,11 +55,16 @@
{
"description": "maximum validation with unsigned integer",
"database": {
"schemas": {
"maximum_1_0": {
"maximum": 300
"types": [
{
"name": "maximum_1_0",
"schemas": {
"maximum_1_0": {
"maximum": 300
}
}
}
}
]
},
"tests": [
{

View File

@ -2,23 +2,33 @@
{
"description": "merging: properties accumulate",
"database": {
"schemas": {
"base_0": {
"properties": {
"base_prop": {
"type": "string"
"types": [
{
"name": "base_0",
"schemas": {
"base_0": {
"properties": {
"base_prop": {
"type": "string"
}
}
}
}
},
"merge_0_0": {
"type": "base_0",
"properties": {
"child_prop": {
"type": "string"
{
"name": "merge_0_0",
"schemas": {
"merge_0_0": {
"type": "base_0",
"properties": {
"child_prop": {
"type": "string"
}
}
}
}
}
}
]
},
"tests": [
{
@ -58,29 +68,39 @@
{
"description": "merging: required fields accumulate",
"database": {
"schemas": {
"base_1": {
"properties": {
"a": {
"type": "string"
"types": [
{
"name": "base_1",
"schemas": {
"base_1": {
"properties": {
"a": {
"type": "string"
}
},
"required": [
"a"
]
}
},
"required": [
"a"
]
}
},
"merge_1_0": {
"type": "base_1",
"properties": {
"b": {
"type": "string"
{
"name": "merge_1_0",
"schemas": {
"merge_1_0": {
"type": "base_1",
"properties": {
"b": {
"type": "string"
}
},
"required": [
"b"
]
}
},
"required": [
"b"
]
}
}
}
]
},
"tests": [
{
@ -138,36 +158,46 @@
{
"description": "merging: dependencies accumulate",
"database": {
"schemas": {
"base_2": {
"properties": {
"trigger": {
"type": "string"
},
"base_dep": {
"type": "string"
"types": [
{
"name": "base_2",
"schemas": {
"base_2": {
"properties": {
"trigger": {
"type": "string"
},
"base_dep": {
"type": "string"
}
},
"dependencies": {
"trigger": [
"base_dep"
]
}
}
},
"dependencies": {
"trigger": [
"base_dep"
]
}
},
"merge_2_0": {
"type": "base_2",
"properties": {
"child_dep": {
"type": "string"
{
"name": "merge_2_0",
"schemas": {
"merge_2_0": {
"type": "base_2",
"properties": {
"child_dep": {
"type": "string"
}
},
"dependencies": {
"trigger": [
"child_dep"
]
}
}
},
"dependencies": {
"trigger": [
"child_dep"
]
}
}
}
]
},
"tests": [
{
@ -228,33 +258,43 @@
{
"description": "merging: form and display do NOT merge",
"database": {
"schemas": {
"base_3": {
"properties": {
"a": {
"type": "string"
},
"b": {
"type": "string"
"types": [
{
"name": "base_3",
"schemas": {
"base_3": {
"properties": {
"a": {
"type": "string"
},
"b": {
"type": "string"
}
},
"form": [
"a",
"b"
]
}
},
"form": [
"a",
"b"
]
}
},
"merge_3_0": {
"type": "base_3",
"properties": {
"c": {
"type": "string"
{
"name": "merge_3_0",
"schemas": {
"merge_3_0": {
"type": "base_3",
"properties": {
"c": {
"type": "string"
}
},
"form": [
"c"
]
}
},
"form": [
"c"
]
}
}
}
]
},
"tests": [
{

View File

@ -2,12 +2,17 @@
{
"description": "minContains without contains is ignored",
"database": {
"schemas": {
"minContains_0_0": {
"minContains": 1,
"extensible": true
"types": [
{
"name": "minContains_0_0",
"schemas": {
"minContains_0_0": {
"minContains": 1,
"extensible": true
}
}
}
}
]
},
"tests": [
{
@ -35,15 +40,20 @@
{
"description": "minContains=1 with contains",
"database": {
"schemas": {
"minContains_1_0": {
"contains": {
"const": 1
},
"minContains": 1,
"extensible": true
"types": [
{
"name": "minContains_1_0",
"schemas": {
"minContains_1_0": {
"contains": {
"const": 1
},
"minContains": 1,
"extensible": true
}
}
}
}
]
},
"tests": [
{
@ -106,15 +116,20 @@
{
"description": "minContains=2 with contains",
"database": {
"schemas": {
"minContains_2_0": {
"contains": {
"const": 1
},
"minContains": 2,
"extensible": true
"types": [
{
"name": "minContains_2_0",
"schemas": {
"minContains_2_0": {
"contains": {
"const": 1
},
"minContains": 2,
"extensible": true
}
}
}
}
]
},
"tests": [
{
@ -192,15 +207,20 @@
{
"description": "minContains=2 with contains with a decimal value",
"database": {
"schemas": {
"minContains_3_0": {
"contains": {
"const": 1
},
"minContains": 2,
"extensible": true
"types": [
{
"name": "minContains_3_0",
"schemas": {
"minContains_3_0": {
"contains": {
"const": 1
},
"minContains": 2,
"extensible": true
}
}
}
}
]
},
"tests": [
{
@ -231,16 +251,21 @@
{
"description": "maxContains = minContains",
"database": {
"schemas": {
"minContains_4_0": {
"contains": {
"const": 1
},
"maxContains": 2,
"minContains": 2,
"extensible": true
"types": [
{
"name": "minContains_4_0",
"schemas": {
"minContains_4_0": {
"contains": {
"const": 1
},
"maxContains": 2,
"minContains": 2,
"extensible": true
}
}
}
}
]
},
"tests": [
{
@ -293,16 +318,21 @@
{
"description": "maxContains < minContains",
"database": {
"schemas": {
"minContains_5_0": {
"contains": {
"const": 1
},
"maxContains": 1,
"minContains": 3,
"extensible": true
"types": [
{
"name": "minContains_5_0",
"schemas": {
"minContains_5_0": {
"contains": {
"const": 1
},
"maxContains": 1,
"minContains": 3,
"extensible": true
}
}
}
}
]
},
"tests": [
{
@ -355,15 +385,20 @@
{
"description": "minContains = 0",
"database": {
"schemas": {
"minContains_6_0": {
"contains": {
"const": 1
},
"minContains": 0,
"extensible": true
"types": [
{
"name": "minContains_6_0",
"schemas": {
"minContains_6_0": {
"contains": {
"const": 1
},
"minContains": 0,
"extensible": true
}
}
}
}
]
},
"tests": [
{
@ -391,16 +426,21 @@
{
"description": "minContains = 0 with maxContains",
"database": {
"schemas": {
"minContains_7_0": {
"contains": {
"const": 1
},
"minContains": 0,
"maxContains": 1,
"extensible": true
"types": [
{
"name": "minContains_7_0",
"schemas": {
"minContains_7_0": {
"contains": {
"const": 1
},
"minContains": 0,
"maxContains": 1,
"extensible": true
}
}
}
}
]
},
"tests": [
{
@ -440,15 +480,20 @@
{
"description": "extensible: true allows non-matching items in minContains",
"database": {
"schemas": {
"minContains_8_0": {
"contains": {
"const": 1
},
"minContains": 1,
"extensible": true
"types": [
{
"name": "minContains_8_0",
"schemas": {
"minContains_8_0": {
"contains": {
"const": 1
},
"minContains": 1,
"extensible": true
}
}
}
}
]
},
"tests": [
{

View File

@ -2,12 +2,17 @@
{
"description": "minItems validation",
"database": {
"schemas": {
"minItems_0_0": {
"minItems": 1,
"extensible": true
"types": [
{
"name": "minItems_0_0",
"schemas": {
"minItems_0_0": {
"minItems": 1,
"extensible": true
}
}
}
}
]
},
"tests": [
{
@ -56,12 +61,17 @@
{
"description": "minItems validation with a decimal",
"database": {
"schemas": {
"minItems_1_0": {
"minItems": 1,
"extensible": true
"types": [
{
"name": "minItems_1_0",
"schemas": {
"minItems_1_0": {
"minItems": 1,
"extensible": true
}
}
}
}
]
},
"tests": [
{
@ -90,12 +100,17 @@
{
"description": "extensible: true allows extra items in minItems",
"database": {
"schemas": {
"minItems_2_0": {
"minItems": 1,
"extensible": true
"types": [
{
"name": "minItems_2_0",
"schemas": {
"minItems_2_0": {
"minItems": 1,
"extensible": true
}
}
}
}
]
},
"tests": [
{

View File

@ -2,11 +2,16 @@
{
"description": "minLength validation",
"database": {
"schemas": {
"minLength_0_0": {
"minLength": 2
"types": [
{
"name": "minLength_0_0",
"schemas": {
"minLength_0_0": {
"minLength": 2
}
}
}
}
]
},
"tests": [
{
@ -47,7 +52,7 @@
},
{
"description": "one grapheme is not long enough",
"data": "💩",
"data": "\ud83d\udca9",
"schema_id": "minLength_0_0",
"action": "validate",
"expect": {
@ -59,11 +64,16 @@
{
"description": "minLength validation with a decimal",
"database": {
"schemas": {
"minLength_1_0": {
"minLength": 2
"types": [
{
"name": "minLength_1_0",
"schemas": {
"minLength_1_0": {
"minLength": 2
}
}
}
}
]
},
"tests": [
{

View File

@ -2,12 +2,17 @@
{
"description": "minProperties validation",
"database": {
"schemas": {
"minProperties_0_0": {
"minProperties": 1,
"extensible": true
"types": [
{
"name": "minProperties_0_0",
"schemas": {
"minProperties_0_0": {
"minProperties": 1,
"extensible": true
}
}
}
}
]
},
"tests": [
{
@ -74,12 +79,17 @@
{
"description": "minProperties validation with a decimal",
"database": {
"schemas": {
"minProperties_1_0": {
"minProperties": 1,
"extensible": true
"types": [
{
"name": "minProperties_1_0",
"schemas": {
"minProperties_1_0": {
"minProperties": 1,
"extensible": true
}
}
}
}
]
},
"tests": [
{
@ -108,12 +118,17 @@
{
"description": "extensible: true allows extra properties in minProperties",
"database": {
"schemas": {
"minProperties_2_0": {
"minProperties": 1,
"extensible": true
"types": [
{
"name": "minProperties_2_0",
"schemas": {
"minProperties_2_0": {
"minProperties": 1,
"extensible": true
}
}
}
}
]
},
"tests": [
{

View File

@ -2,11 +2,16 @@
{
"description": "minimum validation",
"database": {
"schemas": {
"minimum_0_0": {
"minimum": 1.1
"types": [
{
"name": "minimum_0_0",
"schemas": {
"minimum_0_0": {
"minimum": 1.1
}
}
}
}
]
},
"tests": [
{
@ -50,11 +55,16 @@
{
"description": "minimum validation with signed integer",
"database": {
"schemas": {
"minimum_1_0": {
"minimum": -2
"types": [
{
"name": "minimum_1_0",
"schemas": {
"minimum_1_0": {
"minimum": -2
}
}
}
}
]
},
"tests": [
{

View File

@ -2,11 +2,16 @@
{
"description": "by int",
"database": {
"schemas": {
"multipleOf_0_0": {
"multipleOf": 2
"types": [
{
"name": "multipleOf_0_0",
"schemas": {
"multipleOf_0_0": {
"multipleOf": 2
}
}
}
}
]
},
"tests": [
{
@ -41,11 +46,16 @@
{
"description": "by number",
"database": {
"schemas": {
"multipleOf_1_0": {
"multipleOf": 1.5
"types": [
{
"name": "multipleOf_1_0",
"schemas": {
"multipleOf_1_0": {
"multipleOf": 1.5
}
}
}
}
]
},
"tests": [
{
@ -80,11 +90,16 @@
{
"description": "by small number",
"database": {
"schemas": {
"multipleOf_2_0": {
"multipleOf": 0.0001
"types": [
{
"name": "multipleOf_2_0",
"schemas": {
"multipleOf_2_0": {
"multipleOf": 0.0001
}
}
}
}
]
},
"tests": [
{
@ -110,12 +125,17 @@
{
"description": "small multiple of large integer",
"database": {
"schemas": {
"multipleOf_3_0": {
"type": "integer",
"multipleOf": 1e-08
"types": [
{
"name": "multipleOf_3_0",
"schemas": {
"multipleOf_3_0": {
"type": "integer",
"multipleOf": 1e-08
}
}
}
}
]
},
"tests": [
{

View File

@ -2,13 +2,18 @@
{
"description": "not",
"database": {
"schemas": {
"not_0_0": {
"not": {
"type": "integer"
"types": [
{
"name": "not_0_0",
"schemas": {
"not_0_0": {
"not": {
"type": "integer"
}
}
}
}
}
]
},
"tests": [
{
@ -34,16 +39,21 @@
{
"description": "not multiple types",
"database": {
"schemas": {
"not_1_0": {
"not": {
"type": [
"integer",
"boolean"
]
"types": [
{
"name": "not_1_0",
"schemas": {
"not_1_0": {
"not": {
"type": [
"integer",
"boolean"
]
}
}
}
}
}
]
},
"tests": [
{
@ -78,19 +88,24 @@
{
"description": "not more complex schema",
"database": {
"schemas": {
"not_2_0": {
"not": {
"type": "object",
"properties": {
"foo": {
"type": "string"
}
"types": [
{
"name": "not_2_0",
"schemas": {
"not_2_0": {
"not": {
"type": "object",
"properties": {
"foo": {
"type": "string"
}
}
},
"extensible": true
}
},
"extensible": true
}
}
}
]
},
"tests": [
{
@ -129,15 +144,20 @@
{
"description": "forbidden property",
"database": {
"schemas": {
"not_3_0": {
"properties": {
"foo": {
"not": {}
"types": [
{
"name": "not_3_0",
"schemas": {
"not_3_0": {
"properties": {
"foo": {
"not": {}
}
}
}
}
}
}
]
},
"tests": [
{
@ -166,11 +186,16 @@
{
"description": "forbid everything with empty schema",
"database": {
"schemas": {
"not_4_0": {
"not": {}
"types": [
{
"name": "not_4_0",
"schemas": {
"not_4_0": {
"not": {}
}
}
}
}
]
},
"tests": [
{
@ -263,11 +288,16 @@
{
"description": "forbid everything with boolean schema true",
"database": {
"schemas": {
"not_5_0": {
"not": true
"types": [
{
"name": "not_5_0",
"schemas": {
"not_5_0": {
"not": true
}
}
}
}
]
},
"tests": [
{
@ -360,12 +390,17 @@
{
"description": "allow everything with boolean schema false",
"database": {
"schemas": {
"not_6_0": {
"not": false,
"extensible": true
"types": [
{
"name": "not_6_0",
"schemas": {
"not_6_0": {
"not": false,
"extensible": true
}
}
}
}
]
},
"tests": [
{
@ -458,13 +493,18 @@
{
"description": "double negation",
"database": {
"schemas": {
"not_7_0": {
"not": {
"not": {}
"types": [
{
"name": "not_7_0",
"schemas": {
"not_7_0": {
"not": {
"not": {}
}
}
}
}
}
]
},
"tests": [
{
@ -481,14 +521,19 @@
{
"description": "extensible: true allows extra properties in not",
"database": {
"schemas": {
"not_8_0": {
"not": {
"type": "integer"
},
"extensible": true
"types": [
{
"name": "not_8_0",
"schemas": {
"not_8_0": {
"not": {
"type": "integer"
},
"extensible": true
}
}
}
}
]
},
"tests": [
{
@ -507,13 +552,18 @@
{
"description": "extensible: false (default) forbids extra properties in not",
"database": {
"schemas": {
"not_9_0": {
"not": {
"type": "integer"
"types": [
{
"name": "not_9_0",
"schemas": {
"not_9_0": {
"not": {
"type": "integer"
}
}
}
}
}
]
},
"tests": [
{
@ -532,19 +582,24 @@
{
"description": "property next to not (extensible: true)",
"database": {
"schemas": {
"not_10_0": {
"properties": {
"bar": {
"type": "string"
"types": [
{
"name": "not_10_0",
"schemas": {
"not_10_0": {
"properties": {
"bar": {
"type": "string"
}
},
"not": {
"type": "integer"
},
"extensible": true
}
},
"not": {
"type": "integer"
},
"extensible": true
}
}
}
]
},
"tests": [
{
@ -564,18 +619,23 @@
{
"description": "property next to not (extensible: false)",
"database": {
"schemas": {
"not_11_0": {
"properties": {
"bar": {
"type": "string"
"types": [
{
"name": "not_11_0",
"schemas": {
"not_11_0": {
"properties": {
"bar": {
"type": "string"
}
},
"not": {
"type": "integer"
}
}
},
"not": {
"type": "integer"
}
}
}
]
},
"tests": [
{

View File

@ -2,27 +2,37 @@
{
"description": "Strict Inheritance",
"database": {
"schemas": {
"parent_type": {
"type": "object",
"properties": {
"a": {
"type": "integer"
"types": [
{
"name": "parent_type",
"schemas": {
"parent_type": {
"type": "object",
"properties": {
"a": {
"type": "integer"
}
},
"required": [
"a"
]
}
},
"required": [
"a"
]
}
},
"child_type": {
"type": "parent_type",
"properties": {
"b": {
"type": "integer"
{
"name": "child_type",
"schemas": {
"child_type": {
"type": "parent_type",
"properties": {
"b": {
"type": "integer"
}
}
}
}
}
}
]
},
"tests": [
{
@ -66,26 +76,36 @@
{
"description": "Local Shadowing (Composition & Proxies)",
"database": {
"schemas": {
"budget": {
"type": "object",
"properties": {
"max": {
"type": "integer",
"maximum": 100
"types": [
{
"name": "budget",
"schemas": {
"budget": {
"type": "object",
"properties": {
"max": {
"type": "integer",
"maximum": 100
}
}
}
}
},
"custom_budget": {
"type": "budget",
"properties": {
"max": {
"type": "integer",
"maximum": 50
{
"name": "custom_budget",
"schemas": {
"custom_budget": {
"type": "budget",
"properties": {
"max": {
"type": "integer",
"maximum": 50
}
}
}
}
}
}
]
},
"tests": [
{
@ -115,30 +135,40 @@
{
"description": "Primitive Array Shorthand (Optionality)",
"database": {
"schemas": {
"invoice": {
"type": "object",
"properties": {
"amount": {
"type": "integer"
}
},
"required": [
"amount"
]
},
"request": {
"type": "object",
"properties": {
"inv": {
"type": [
"invoice",
"null"
"types": [
{
"name": "invoice",
"schemas": {
"invoice": {
"type": "object",
"properties": {
"amount": {
"type": "integer"
}
},
"required": [
"amount"
]
}
}
},
{
"name": "request",
"schemas": {
"request": {
"type": "object",
"properties": {
"inv": {
"type": [
"invoice",
"null"
]
}
}
}
}
}
}
]
},
"tests": [
{

View File

@ -2,63 +2,68 @@
{
"description": "Hybrid Array Pathing",
"database": {
"schemas": {
"hybrid_pathing": {
"type": "object",
"properties": {
"primitives": {
"type": "array",
"items": {
"type": "string"
}
},
"ad_hoc_objects": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"types": [
{
"name": "hybrid_pathing",
"schemas": {
"hybrid_pathing": {
"type": "object",
"properties": {
"primitives": {
"type": "array",
"items": {
"type": "string"
}
},
"required": [
"name"
]
}
},
"entities": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"value": {
"type": "number",
"minimum": 10
"ad_hoc_objects": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string"
}
},
"required": [
"name"
]
}
}
}
},
"deep_entities": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"nested": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"flag": {
"type": "boolean"
},
"entities": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"value": {
"type": "number",
"minimum": 10
}
}
}
},
"deep_entities": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"nested": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"flag": {
"type": "boolean"
}
}
}
}
}
@ -68,7 +73,7 @@
}
}
}
}
]
},
"tests": [
{
@ -227,26 +232,31 @@
{
"description": "Ad-Hoc Union Pathing",
"database": {
"schemas": {
"ad_hoc_pathing": {
"type": "object",
"properties": {
"metadata_bubbles": {
"type": "array",
"items": {
"oneOf": [
{
"type": "string"
},
{
"type": "integer"
"types": [
{
"name": "ad_hoc_pathing",
"schemas": {
"ad_hoc_pathing": {
"type": "object",
"properties": {
"metadata_bubbles": {
"type": "array",
"items": {
"oneOf": [
{
"type": "string"
},
{
"type": "integer"
}
]
}
]
}
}
}
}
}
}
]
},
"tests": [
{
@ -277,9 +287,22 @@
{
"description": "Polymorphic Family Pathing",
"database": {
"relations": [
{
"id": "11111111-1111-1111-1111-111111111111",
"type": "relation",
"constraint": "fk_family_pathing_table_families_widget",
"source_type": "widget",
"source_columns": ["family_pathing_id"],
"destination_type": "family_pathing",
"destination_columns": ["id"],
"prefix": "table_families"
}
],
"types": [
{
"name": "widget",
"hierarchy": ["widget"],
"variations": [
"widget"
],
@ -322,21 +345,25 @@
]
}
}
}
],
"schemas": {
"family_pathing": {
"type": "object",
"properties": {
"table_families": {
"type": "array",
"items": {
"family": "widget"
},
{
"name": "family_pathing",
"hierarchy": ["family_pathing"],
"schemas": {
"family_pathing": {
"type": "object",
"properties": {
"table_families": {
"type": "array",
"items": {
"family": "widget"
}
}
}
}
}
}
}
]
},
"tests": [
{

View File

@ -2,11 +2,16 @@
{
"description": "pattern validation",
"database": {
"schemas": {
"pattern_0_0": {
"pattern": "^a*$"
"types": [
{
"name": "pattern_0_0",
"schemas": {
"pattern_0_0": {
"pattern": "^a*$"
}
}
}
}
]
},
"tests": [
{
@ -86,11 +91,16 @@
{
"description": "pattern is not anchored",
"database": {
"schemas": {
"pattern_1_0": {
"pattern": "a+"
"types": [
{
"name": "pattern_1_0",
"schemas": {
"pattern_1_0": {
"pattern": "a+"
}
}
}
}
]
},
"tests": [
{

View File

@ -2,16 +2,21 @@
{
"description": "patternProperties validates properties matching a regex",
"database": {
"schemas": {
"patternProperties_0_0": {
"patternProperties": {
"f.*o": {
"type": "integer"
"types": [
{
"name": "patternProperties_0_0",
"schemas": {
"patternProperties_0_0": {
"patternProperties": {
"f.*o": {
"type": "integer"
}
},
"items": {}
}
},
"items": {}
}
}
}
]
},
"tests": [
{
@ -107,18 +112,23 @@
{
"description": "multiple simultaneous patternProperties are validated",
"database": {
"schemas": {
"patternProperties_1_0": {
"patternProperties": {
"a*": {
"type": "integer"
},
"aaa*": {
"maximum": 20
"types": [
{
"name": "patternProperties_1_0",
"schemas": {
"patternProperties_1_0": {
"patternProperties": {
"a*": {
"type": "integer"
},
"aaa*": {
"maximum": 20
}
}
}
}
}
}
]
},
"tests": [
{
@ -194,19 +204,24 @@
{
"description": "regexes are not anchored by default and are case sensitive",
"database": {
"schemas": {
"patternProperties_2_0": {
"patternProperties": {
"[0-9]{2,}": {
"type": "boolean"
},
"X_": {
"type": "string"
"types": [
{
"name": "patternProperties_2_0",
"schemas": {
"patternProperties_2_0": {
"patternProperties": {
"[0-9]{2,}": {
"type": "boolean"
},
"X_": {
"type": "string"
}
},
"extensible": true
}
},
"extensible": true
}
}
}
]
},
"tests": [
{
@ -258,14 +273,19 @@
{
"description": "patternProperties with boolean schemas",
"database": {
"schemas": {
"patternProperties_3_0": {
"patternProperties": {
"f.*": true,
"b.*": false
"types": [
{
"name": "patternProperties_3_0",
"schemas": {
"patternProperties_3_0": {
"patternProperties": {
"f.*": true,
"b.*": false
}
}
}
}
}
]
},
"tests": [
{
@ -327,15 +347,20 @@
{
"description": "patternProperties with null valued instance properties",
"database": {
"schemas": {
"patternProperties_4_0": {
"patternProperties": {
"^.*bar$": {
"type": "null"
"types": [
{
"name": "patternProperties_4_0",
"schemas": {
"patternProperties_4_0": {
"patternProperties": {
"^.*bar$": {
"type": "null"
}
}
}
}
}
}
]
},
"tests": [
{
@ -354,16 +379,21 @@
{
"description": "extensible: true allows extra properties NOT matching pattern",
"database": {
"schemas": {
"patternProperties_5_0": {
"patternProperties": {
"f.*o": {
"type": "integer"
"types": [
{
"name": "patternProperties_5_0",
"schemas": {
"patternProperties_5_0": {
"patternProperties": {
"f.*o": {
"type": "integer"
}
},
"extensible": true
}
},
"extensible": true
}
}
}
]
},
"tests": [
{

View File

@ -73,13 +73,16 @@
}
}
}
},
{
"name": "family_entity",
"schemas": {
"family_entity": {
"family": "entity"
}
}
}
],
"schemas": {
"family_entity": {
"family": "entity"
}
}
]
},
"tests": [
{
@ -222,13 +225,16 @@
"type": "light.entity"
}
}
},
{
"name": "family_light_org",
"schemas": {
"family_light_org": {
"family": "light.organization"
}
}
}
],
"schemas": {
"family_light_org": {
"family": "light.organization"
}
}
]
},
"tests": [
{
@ -315,16 +321,24 @@
}
}
}
}
],
"schemas": {
"family_widget": {
"family": "widget"
},
"family_stock_widget": {
"family": "stock.widget"
{
"name": "family_widget",
"schemas": {
"family_widget": {
"family": "widget"
}
}
},
{
"name": "family_stock_widget",
"schemas": {
"family_stock_widget": {
"family": "stock.widget"
}
}
}
}
]
},
"tests": [
{
@ -463,20 +477,23 @@
]
}
}
}
],
"schemas": {
"oneOf_union": {
"oneOf": [
{
"type": "full.person"
},
{
"type": "full.bot"
},
{
"name": "oneOf_union",
"schemas": {
"oneOf_union": {
"oneOf": [
{
"type": "full.person"
},
{
"type": "full.bot"
}
]
}
]
}
}
}
]
},
"tests": [
{
@ -559,48 +576,58 @@
{
"description": "JSONB Field Bubble oneOf Discrimination (Promoted IDs)",
"database": {
"schemas": {
"metadata": {
"type": "object",
"properties": {
"type": {
"type": "string"
"types": [
{
"name": "metadata",
"schemas": {
"metadata": {
"type": "object",
"properties": {
"type": {
"type": "string"
}
}
},
"invoice.metadata": {
"type": "metadata",
"properties": {
"invoice_id": {
"type": "integer"
}
},
"required": [
"invoice_id"
]
},
"payment.metadata": {
"type": "metadata",
"properties": {
"payment_id": {
"type": "integer"
}
},
"required": [
"payment_id"
]
}
}
},
"invoice.metadata": {
"type": "metadata",
"properties": {
"invoice_id": {
"type": "integer"
{
"name": "oneOf_bubble",
"schemas": {
"oneOf_bubble": {
"oneOf": [
{
"type": "invoice.metadata"
},
{
"type": "payment.metadata"
}
]
}
},
"required": [
"invoice_id"
]
},
"payment.metadata": {
"type": "metadata",
"properties": {
"payment_id": {
"type": "integer"
}
},
"required": [
"payment_id"
]
},
"oneOf_bubble": {
"oneOf": [
{
"type": "invoice.metadata"
},
{
"type": "payment.metadata"
}
]
}
}
}
]
},
"tests": [
{
@ -675,16 +702,24 @@
}
}
}
}
],
"schemas": {
"stock_widget_validation": {
"type": "stock.widget"
},
"projected_widget_validation": {
"type": "projected.widget"
{
"name": "stock_widget_validation",
"schemas": {
"stock_widget_validation": {
"type": "stock.widget"
}
}
},
{
"name": "projected_widget_validation",
"schemas": {
"projected_widget_validation": {
"type": "projected.widget"
}
}
}
}
]
},
"tests": [
{

View File

@ -2,18 +2,23 @@
{
"description": "a schema given for prefixItems",
"database": {
"schemas": {
"prefixItems_0_0": {
"prefixItems": [
{
"type": "integer"
},
{
"type": "string"
"types": [
{
"name": "prefixItems_0_0",
"schemas": {
"prefixItems_0_0": {
"prefixItems": [
{
"type": "integer"
},
{
"type": "string"
}
]
}
]
}
}
}
]
},
"tests": [
{
@ -91,14 +96,19 @@
{
"description": "prefixItems with boolean schemas",
"database": {
"schemas": {
"prefixItems_1_0": {
"prefixItems": [
true,
false
]
"types": [
{
"name": "prefixItems_1_0",
"schemas": {
"prefixItems_1_0": {
"prefixItems": [
true,
false
]
}
}
}
}
]
},
"tests": [
{
@ -138,16 +148,21 @@
{
"description": "additional items are allowed by default",
"database": {
"schemas": {
"prefixItems_2_0": {
"prefixItems": [
{
"type": "integer"
"types": [
{
"name": "prefixItems_2_0",
"schemas": {
"prefixItems_2_0": {
"prefixItems": [
{
"type": "integer"
}
],
"extensible": true
}
],
"extensible": true
}
}
}
]
},
"tests": [
{
@ -168,15 +183,20 @@
{
"description": "prefixItems with null instance elements",
"database": {
"schemas": {
"prefixItems_3_0": {
"prefixItems": [
{
"type": "null"
"types": [
{
"name": "prefixItems_3_0",
"schemas": {
"prefixItems_3_0": {
"prefixItems": [
{
"type": "null"
}
]
}
]
}
}
}
]
},
"tests": [
{
@ -195,16 +215,21 @@
{
"description": "extensible: true allows extra items with prefixItems",
"database": {
"schemas": {
"prefixItems_4_0": {
"prefixItems": [
{
"type": "integer"
"types": [
{
"name": "prefixItems_4_0",
"schemas": {
"prefixItems_4_0": {
"prefixItems": [
{
"type": "integer"
}
],
"extensible": true
}
],
"extensible": true
}
}
}
]
},
"tests": [
{

View File

@ -2,11 +2,16 @@
{
"description": "integer type matches integers",
"database": {
"schemas": {
"type_0_0": {
"type": "integer"
"types": [
{
"name": "type_0_0",
"schemas": {
"type_0_0": {
"type": "integer"
}
}
}
}
]
},
"tests": [
{
@ -95,11 +100,16 @@
{
"description": "number type matches numbers",
"database": {
"schemas": {
"type_1_0": {
"type": "number"
"types": [
{
"name": "type_1_0",
"schemas": {
"type_1_0": {
"type": "number"
}
}
}
}
]
},
"tests": [
{
@ -188,11 +198,16 @@
{
"description": "string type matches strings",
"database": {
"schemas": {
"type_2_0": {
"type": "string"
"types": [
{
"name": "type_2_0",
"schemas": {
"type_2_0": {
"type": "string"
}
}
}
}
]
},
"tests": [
{
@ -281,11 +296,16 @@
{
"description": "object type matches objects",
"database": {
"schemas": {
"type_3_0": {
"type": "object"
"types": [
{
"name": "type_3_0",
"schemas": {
"type_3_0": {
"type": "object"
}
}
}
}
]
},
"tests": [
{
@ -356,11 +376,16 @@
{
"description": "array type matches arrays",
"database": {
"schemas": {
"type_4_0": {
"type": "array"
"types": [
{
"name": "type_4_0",
"schemas": {
"type_4_0": {
"type": "array"
}
}
}
}
]
},
"tests": [
{
@ -431,11 +456,16 @@
{
"description": "boolean type matches booleans",
"database": {
"schemas": {
"type_5_0": {
"type": "boolean"
"types": [
{
"name": "type_5_0",
"schemas": {
"type_5_0": {
"type": "boolean"
}
}
}
}
]
},
"tests": [
{
@ -533,11 +563,16 @@
{
"description": "null type matches only the null object",
"database": {
"schemas": {
"type_6_0": {
"type": "null"
"types": [
{
"name": "type_6_0",
"schemas": {
"type_6_0": {
"type": "null"
}
}
}
}
]
},
"tests": [
{
@ -635,14 +670,19 @@
{
"description": "multiple types can be specified in an array",
"database": {
"schemas": {
"type_7_0": {
"type": [
"integer",
"string"
]
"types": [
{
"name": "type_7_0",
"schemas": {
"type_7_0": {
"type": [
"integer",
"string"
]
}
}
}
}
]
},
"tests": [
{
@ -713,13 +753,18 @@
{
"description": "type as array with one item",
"database": {
"schemas": {
"type_8_0": {
"type": [
"string"
]
"types": [
{
"name": "type_8_0",
"schemas": {
"type_8_0": {
"type": [
"string"
]
}
}
}
}
]
},
"tests": [
{
@ -745,15 +790,20 @@
{
"description": "type: array or object",
"database": {
"schemas": {
"type_9_0": {
"type": [
"array",
"object"
],
"items": {}
"types": [
{
"name": "type_9_0",
"schemas": {
"type_9_0": {
"type": [
"array",
"object"
],
"items": {}
}
}
}
}
]
},
"tests": [
{
@ -810,16 +860,21 @@
{
"description": "type: array, object or null",
"database": {
"schemas": {
"type_10_0": {
"type": [
"array",
"object",
"null"
],
"items": {}
"types": [
{
"name": "type_10_0",
"schemas": {
"type_10_0": {
"type": [
"array",
"object",
"null"
],
"items": {}
}
}
}
}
]
},
"tests": [
{
@ -876,12 +931,17 @@
{
"description": "extensible: true allows extra properties",
"database": {
"schemas": {
"type_11_0": {
"type": "object",
"extensible": true
"types": [
{
"name": "type_11_0",
"schemas": {
"type_11_0": {
"type": "object",
"extensible": true
}
}
}
}
]
},
"tests": [
{

View File

@ -2,18 +2,23 @@
{
"description": "object properties validation",
"database": {
"schemas": {
"properties_0_0": {
"properties": {
"foo": {
"type": "integer"
},
"bar": {
"type": "string"
"types": [
{
"name": "properties_0_0",
"schemas": {
"properties_0_0": {
"properties": {
"foo": {
"type": "integer"
},
"bar": {
"type": "string"
}
}
}
}
}
}
]
},
"tests": [
{
@ -84,14 +89,19 @@
{
"description": "properties with boolean schema",
"database": {
"schemas": {
"properties_1_0": {
"properties": {
"foo": true,
"bar": false
"types": [
{
"name": "properties_1_0",
"schemas": {
"properties_1_0": {
"properties": {
"foo": true,
"bar": false
}
}
}
}
}
]
},
"tests": [
{
@ -142,30 +152,35 @@
{
"description": "properties with escaped characters",
"database": {
"schemas": {
"properties_2_0": {
"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"
"types": [
{
"name": "properties_2_0",
"schemas": {
"properties_2_0": {
"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": [
{
@ -205,15 +220,20 @@
{
"description": "properties with null valued instance properties",
"database": {
"schemas": {
"properties_3_0": {
"properties": {
"foo": {
"type": "null"
"types": [
{
"name": "properties_3_0",
"schemas": {
"properties_3_0": {
"properties": {
"foo": {
"type": "null"
}
}
}
}
}
}
]
},
"tests": [
{
@ -233,25 +253,30 @@
"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.",
"database": {
"schemas": {
"properties_4_0": {
"properties": {
"__proto__": {
"type": "number"
},
"toString": {
"types": [
{
"name": "properties_4_0",
"schemas": {
"properties_4_0": {
"properties": {
"length": {
"type": "string"
"__proto__": {
"type": "number"
},
"toString": {
"properties": {
"length": {
"type": "string"
}
}
},
"constructor": {
"type": "number"
}
}
},
"constructor": {
"type": "number"
}
}
}
}
]
},
"tests": [
{
@ -338,16 +363,21 @@
{
"description": "extensible: true allows extra properties",
"database": {
"schemas": {
"properties_5_0": {
"properties": {
"foo": {
"type": "integer"
"types": [
{
"name": "properties_5_0",
"schemas": {
"properties_5_0": {
"properties": {
"foo": {
"type": "integer"
}
},
"extensible": true
}
},
"extensible": true
}
}
}
]
},
"tests": [
{
@ -367,15 +397,20 @@
{
"description": "strict by default: extra properties invalid",
"database": {
"schemas": {
"properties_6_0": {
"properties": {
"foo": {
"type": "string"
"types": [
{
"name": "properties_6_0",
"schemas": {
"properties_6_0": {
"properties": {
"foo": {
"type": "string"
}
}
}
}
}
}
]
},
"tests": [
{
@ -395,19 +430,24 @@
{
"description": "inheritance: nested object inherits strictness from strict parent",
"database": {
"schemas": {
"properties_7_0": {
"properties": {
"nested": {
"types": [
{
"name": "properties_7_0",
"schemas": {
"properties_7_0": {
"properties": {
"foo": {
"type": "string"
"nested": {
"properties": {
"foo": {
"type": "string"
}
}
}
}
}
}
}
}
]
},
"tests": [
{
@ -429,20 +469,25 @@
{
"description": "override: nested object allows extra properties if extensible: true",
"database": {
"schemas": {
"properties_8_0": {
"properties": {
"nested": {
"extensible": true,
"types": [
{
"name": "properties_8_0",
"schemas": {
"properties_8_0": {
"properties": {
"foo": {
"type": "string"
"nested": {
"extensible": true,
"properties": {
"foo": {
"type": "string"
}
}
}
}
}
}
}
}
]
},
"tests": [
{
@ -464,20 +509,25 @@
{
"description": "inheritance: nested object inherits looseness from loose parent",
"database": {
"schemas": {
"properties_9_0": {
"extensible": true,
"properties": {
"nested": {
"types": [
{
"name": "properties_9_0",
"schemas": {
"properties_9_0": {
"extensible": true,
"properties": {
"foo": {
"type": "string"
"nested": {
"properties": {
"foo": {
"type": "string"
}
}
}
}
}
}
}
}
]
},
"tests": [
{
@ -499,21 +549,26 @@
{
"description": "override: nested object enforces strictness if extensible: false",
"database": {
"schemas": {
"properties_10_0": {
"extensible": true,
"properties": {
"nested": {
"extensible": false,
"types": [
{
"name": "properties_10_0",
"schemas": {
"properties_10_0": {
"extensible": true,
"properties": {
"foo": {
"type": "string"
"nested": {
"extensible": false,
"properties": {
"foo": {
"type": "string"
}
}
}
}
}
}
}
}
]
},
"tests": [
{
@ -535,22 +590,27 @@
{
"description": "arrays: inline items inherit strictness from strict parent",
"database": {
"schemas": {
"properties_11_0": {
"properties": {
"list": {
"type": "array",
"items": {
"properties": {
"foo": {
"type": "string"
"types": [
{
"name": "properties_11_0",
"schemas": {
"properties_11_0": {
"properties": {
"list": {
"type": "array",
"items": {
"properties": {
"foo": {
"type": "string"
}
}
}
}
}
}
}
}
}
]
},
"tests": [
{
@ -574,23 +634,28 @@
{
"description": "arrays: inline items inherit looseness from loose parent",
"database": {
"schemas": {
"properties_12_0": {
"extensible": true,
"properties": {
"list": {
"type": "array",
"items": {
"properties": {
"foo": {
"type": "string"
"types": [
{
"name": "properties_12_0",
"schemas": {
"properties_12_0": {
"extensible": true,
"properties": {
"list": {
"type": "array",
"items": {
"properties": {
"foo": {
"type": "string"
}
}
}
}
}
}
}
}
}
]
},
"tests": [
{

View File

@ -2,14 +2,19 @@
{
"description": "propertyNames validation",
"database": {
"schemas": {
"propertyNames_0_0": {
"propertyNames": {
"maxLength": 3
},
"extensible": true
"types": [
{
"name": "propertyNames_0_0",
"schemas": {
"propertyNames_0_0": {
"propertyNames": {
"maxLength": 3
},
"extensible": true
}
}
}
}
]
},
"tests": [
{
@ -82,14 +87,19 @@
{
"description": "propertyNames validation with pattern",
"database": {
"schemas": {
"propertyNames_1_0": {
"propertyNames": {
"pattern": "^a+$"
},
"extensible": true
"types": [
{
"name": "propertyNames_1_0",
"schemas": {
"propertyNames_1_0": {
"propertyNames": {
"pattern": "^a+$"
},
"extensible": true
}
}
}
}
]
},
"tests": [
{
@ -130,12 +140,17 @@
{
"description": "propertyNames with boolean schema true",
"database": {
"schemas": {
"propertyNames_2_0": {
"propertyNames": true,
"extensible": true
"types": [
{
"name": "propertyNames_2_0",
"schemas": {
"propertyNames_2_0": {
"propertyNames": true,
"extensible": true
}
}
}
}
]
},
"tests": [
{
@ -163,12 +178,17 @@
{
"description": "propertyNames with boolean schema false",
"database": {
"schemas": {
"propertyNames_3_0": {
"propertyNames": false,
"extensible": true
"types": [
{
"name": "propertyNames_3_0",
"schemas": {
"propertyNames_3_0": {
"propertyNames": false,
"extensible": true
}
}
}
}
]
},
"tests": [
{
@ -196,14 +216,19 @@
{
"description": "propertyNames with const",
"database": {
"schemas": {
"propertyNames_4_0": {
"propertyNames": {
"const": "foo"
},
"extensible": true
"types": [
{
"name": "propertyNames_4_0",
"schemas": {
"propertyNames_4_0": {
"propertyNames": {
"const": "foo"
},
"extensible": true
}
}
}
}
]
},
"tests": [
{
@ -242,17 +267,22 @@
{
"description": "propertyNames with enum",
"database": {
"schemas": {
"propertyNames_5_0": {
"propertyNames": {
"enum": [
"foo",
"bar"
]
},
"extensible": true
"types": [
{
"name": "propertyNames_5_0",
"schemas": {
"propertyNames_5_0": {
"propertyNames": {
"enum": [
"foo",
"bar"
]
},
"extensible": true
}
}
}
}
]
},
"tests": [
{
@ -303,14 +333,19 @@
{
"description": "extensible: true allows extra properties (checked by propertyNames)",
"database": {
"schemas": {
"propertyNames_6_0": {
"propertyNames": {
"maxLength": 3
},
"extensible": true
"types": [
{
"name": "propertyNames_6_0",
"schemas": {
"propertyNames_6_0": {
"propertyNames": {
"maxLength": 3
},
"extensible": true
}
}
}
}
]
},
"tests": [
{

View File

@ -2,17 +2,22 @@
{
"description": "required validation",
"database": {
"schemas": {
"required_0_0": {
"properties": {
"foo": {},
"bar": {}
},
"required": [
"foo"
]
"types": [
{
"name": "required_0_0",
"schemas": {
"required_0_0": {
"properties": {
"foo": {},
"bar": {}
},
"required": [
"foo"
]
}
}
}
}
]
},
"tests": [
{
@ -87,13 +92,18 @@
{
"description": "required default validation",
"database": {
"schemas": {
"required_1_0": {
"properties": {
"foo": {}
"types": [
{
"name": "required_1_0",
"schemas": {
"required_1_0": {
"properties": {
"foo": {}
}
}
}
}
}
]
},
"tests": [
{
@ -110,14 +120,19 @@
{
"description": "required with empty array",
"database": {
"schemas": {
"required_2_0": {
"properties": {
"foo": {}
},
"required": []
"types": [
{
"name": "required_2_0",
"schemas": {
"required_2_0": {
"properties": {
"foo": {}
},
"required": []
}
}
}
}
]
},
"tests": [
{
@ -134,19 +149,24 @@
{
"description": "required with escaped characters",
"database": {
"schemas": {
"required_3_0": {
"required": [
"foo\nbar",
"foo\"bar",
"foo\\bar",
"foo\rbar",
"foo\tbar",
"foo\fbar"
],
"extensible": true
"types": [
{
"name": "required_3_0",
"schemas": {
"required_3_0": {
"required": [
"foo\nbar",
"foo\"bar",
"foo\\bar",
"foo\rbar",
"foo\tbar",
"foo\fbar"
],
"extensible": true
}
}
}
}
]
},
"tests": [
{
@ -183,16 +203,21 @@
"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.",
"database": {
"schemas": {
"required_4_0": {
"required": [
"__proto__",
"toString",
"constructor"
],
"extensible": true
"types": [
{
"name": "required_4_0",
"schemas": {
"required_4_0": {
"required": [
"__proto__",
"toString",
"constructor"
],
"extensible": true
}
}
}
}
]
},
"tests": [
{
@ -279,14 +304,19 @@
{
"description": "extensible: true allows extra properties in required",
"database": {
"schemas": {
"required_5_0": {
"required": [
"foo"
],
"extensible": true
"types": [
{
"name": "required_5_0",
"schemas": {
"required_5_0": {
"required": [
"foo"
],
"extensible": true
}
}
}
}
]
},
"tests": [
{

View File

@ -2,12 +2,17 @@
{
"description": "uniqueItems validation",
"database": {
"schemas": {
"uniqueItems_0_0": {
"uniqueItems": true,
"extensible": true
"types": [
{
"name": "uniqueItems_0_0",
"schemas": {
"uniqueItems_0_0": {
"uniqueItems": true,
"extensible": true
}
}
}
}
]
},
"tests": [
{
@ -468,20 +473,25 @@
{
"description": "uniqueItems with an array of items",
"database": {
"schemas": {
"uniqueItems_1_0": {
"prefixItems": [
{
"type": "boolean"
},
{
"type": "boolean"
"types": [
{
"name": "uniqueItems_1_0",
"schemas": {
"uniqueItems_1_0": {
"prefixItems": [
{
"type": "boolean"
},
{
"type": "boolean"
}
],
"uniqueItems": true,
"extensible": true
}
],
"uniqueItems": true,
"extensible": true
}
}
}
]
},
"tests": [
{
@ -593,20 +603,25 @@
{
"description": "uniqueItems with an array of items and additionalItems=false",
"database": {
"schemas": {
"uniqueItems_2_0": {
"prefixItems": [
{
"type": "boolean"
},
{
"type": "boolean"
"types": [
{
"name": "uniqueItems_2_0",
"schemas": {
"uniqueItems_2_0": {
"prefixItems": [
{
"type": "boolean"
},
{
"type": "boolean"
}
],
"uniqueItems": true,
"items": false
}
],
"uniqueItems": true,
"items": false
}
}
}
]
},
"tests": [
{
@ -675,12 +690,17 @@
{
"description": "uniqueItems=false validation",
"database": {
"schemas": {
"uniqueItems_3_0": {
"uniqueItems": false,
"extensible": true
"types": [
{
"name": "uniqueItems_3_0",
"schemas": {
"uniqueItems_3_0": {
"uniqueItems": false,
"extensible": true
}
}
}
}
]
},
"tests": [
{
@ -920,20 +940,25 @@
{
"description": "uniqueItems=false with an array of items",
"database": {
"schemas": {
"uniqueItems_4_0": {
"prefixItems": [
{
"type": "boolean"
},
{
"type": "boolean"
"types": [
{
"name": "uniqueItems_4_0",
"schemas": {
"uniqueItems_4_0": {
"prefixItems": [
{
"type": "boolean"
},
{
"type": "boolean"
}
],
"uniqueItems": false,
"extensible": true
}
],
"uniqueItems": false,
"extensible": true
}
}
}
]
},
"tests": [
{
@ -1045,20 +1070,25 @@
{
"description": "uniqueItems=false with an array of items and additionalItems=false",
"database": {
"schemas": {
"uniqueItems_5_0": {
"prefixItems": [
{
"type": "boolean"
},
{
"type": "boolean"
"types": [
{
"name": "uniqueItems_5_0",
"schemas": {
"uniqueItems_5_0": {
"prefixItems": [
{
"type": "boolean"
},
{
"type": "boolean"
}
],
"uniqueItems": false,
"items": false
}
],
"uniqueItems": false,
"items": false
}
}
}
]
},
"tests": [
{
@ -1127,12 +1157,17 @@
{
"description": "extensible: true allows extra items in uniqueItems",
"database": {
"schemas": {
"uniqueItems_6_0": {
"uniqueItems": true,
"extensible": true
"types": [
{
"name": "uniqueItems_6_0",
"schemas": {
"uniqueItems_6_0": {
"uniqueItems": true,
"extensible": true
}
}
}
}
]
},
"tests": [
{

View File

@ -8,7 +8,7 @@ impl Schema {
pub fn compile_filter(
&self,
_db: &Database,
_root_id: &str,
root_id: &str,
_errors: &mut Vec<crate::drop::Error>,
) -> Option<Schema> {
if let Some(props) = self.obj.compiled_properties.get() {
@ -31,9 +31,51 @@ impl Schema {
}
if !filter_props.is_empty() {
let root_filter_type = format!("{}.filter", root_id);
let mut and_obj = SchemaObject::default();
and_obj.type_ = Some(SchemaTypeOrArray::Multiple(vec![
"array".to_string(),
"null".to_string(),
]));
and_obj.items = Some(Arc::new(Schema {
obj: SchemaObject {
type_: Some(SchemaTypeOrArray::Single(root_filter_type.clone())),
..Default::default()
},
always_fail: false,
}));
filter_props.insert(
"$and".to_string(),
Arc::new(Schema {
obj: and_obj,
always_fail: false,
}),
);
let mut or_obj = SchemaObject::default();
or_obj.type_ = Some(SchemaTypeOrArray::Multiple(vec![
"array".to_string(),
"null".to_string(),
]));
or_obj.items = Some(Arc::new(Schema {
obj: SchemaObject {
type_: Some(SchemaTypeOrArray::Single(root_filter_type.clone())),
..Default::default()
},
always_fail: false,
}));
filter_props.insert(
"$or".to_string(),
Arc::new(Schema {
obj: or_obj,
always_fail: false,
}),
);
let mut wrapper_obj = SchemaObject::default();
// Conceptually link this directly into the STI lineage of the base `filter` object
wrapper_obj.type_ = Some(SchemaTypeOrArray::Single("filter".to_string()));
// Filters are just plain objects containing conditions, no inheritance required
wrapper_obj.type_ = Some(SchemaTypeOrArray::Single("object".to_string()));
wrapper_obj.properties = Some(filter_props);
return Some(Schema {

View File

@ -51,8 +51,8 @@ impl Schema {
// 1. Resolve INHERITANCE dependencies first
if let Some(crate::database::object::SchemaTypeOrArray::Single(t)) = &self.obj.type_ {
if !crate::database::object::is_primitive_type(t) {
if let Some(parent) = db.schemas.get(t) {
if !crate::database::object::is_primitive_type(t) && !t.starts_with('$') {
if let Some(parent) = db.schemas.get(t).cloned() {
parent.as_ref().compile(db, t, t.clone(), errors);
if let Some(p_props) = parent.obj.compiled_properties.get() {
props.extend(p_props.clone());
@ -85,8 +85,8 @@ impl Schema {
}
for t in types {
if !crate::database::object::is_primitive_type(t) {
if let Some(parent) = db.schemas.get(t) {
if !crate::database::object::is_primitive_type(t) && !t.starts_with('$') {
if let Some(parent) = db.schemas.get(t).cloned() {
parent.as_ref().compile(db, t, t.clone(), errors);
}
}

View File

@ -12,7 +12,10 @@ impl Schema {
let mut strategy = String::new();
if let Some(family) = &self.obj.family {
// Formalize the <Variant>.<Base> topology
// family_base extracts the 'Base' (e.g. 'widget', 'person')
let family_base = family.split('.').next_back().unwrap_or(family).to_string();
// family_prefix extracts the 'Variant' (e.g. 'stock', 'light')
let family_prefix = family
.strip_suffix(&family_base)
.unwrap_or("")
@ -29,7 +32,7 @@ impl Schema {
format!("{}.{}", family_prefix, var)
};
if db.schemas.contains_key(&target_id) {
if db.schemas.get(&target_id).is_some() {
options.insert(var.to_string(), (None, Some(target_id)));
}
}

View File

@ -10,8 +10,6 @@ pub mod relation;
pub mod schema;
pub mod r#type;
// External mock exports inside the executor sub-folder
use r#enum::Enum;
use executors::DatabaseExecutor;
@ -157,26 +155,6 @@ impl Database {
}
}
if let Some(map) = val.get("schemas").and_then(|v| v.as_object()) {
for (key, item) in map.iter() {
match serde_json::from_value::<Schema>(item.clone()) {
Ok(schema) => {
db.schemas.insert(key.clone(), Arc::new(schema));
}
Err(e) => {
errors.push(crate::drop::Error {
code: "DATABASE_SCHEMA_PARSE_FAILED".to_string(),
message: format!("Failed to parse database schema key '{}': {}", key, e),
details: crate::drop::ErrorDetails {
context: Some(serde_json::json!(key)),
..Default::default()
},
});
}
}
}
}
db.compile(&mut errors);
let drop = if errors.is_empty() {
crate::drop::Drop::success()
@ -213,30 +191,32 @@ impl Database {
}
pub fn compile(&mut self, errors: &mut Vec<crate::drop::Error>) {
// Collect existing schemas patched in the databse
let mut harvested = Vec::new();
for (id, schema_arc) in &self.schemas {
crate::database::schema::Schema::collect_schemas(
schema_arc,
id,
id.clone(),
&mut harvested,
errors,
);
}
for (id, schema_arc) in harvested {
self.schemas.insert(id, schema_arc);
}
self.collect_schemas(errors);
// Mathematically evaluate all property inheritances, formats, schemas, and foreign key edges topographically over OnceLocks
for (id, schema_arc) in &self.schemas {
// First compile pass initializes exact structural root_id mapping to resolve DB constraints
let root_id = id.split('/').next().unwrap_or(id);
schema_arc
.as_ref()
.compile(self, root_id, id.clone(), errors);
// Formally evaluate properties with strict 3-pass Ordered Graph execution natively
for (_, enum_def) in &self.enums {
for (schema_id, schema_arc) in &enum_def.schemas {
let root_id = schema_id.split('/').next().unwrap_or(schema_id);
schema_arc
.as_ref()
.compile(self, root_id, schema_id.clone(), errors);
}
}
for (_, type_def) in &self.types {
for (schema_id, schema_arc) in &type_def.schemas {
let root_id = schema_id.split('/').next().unwrap_or(schema_id);
schema_arc
.as_ref()
.compile(self, root_id, schema_id.clone(), errors);
}
}
for (_, punc_def) in &self.puncs {
for (schema_id, schema_arc) in &punc_def.schemas {
let root_id = schema_id.split('/').next().unwrap_or(schema_id);
schema_arc
.as_ref()
.compile(self, root_id, schema_id.clone(), errors);
}
}
// Phase 2: Synthesize Composed Filter References
@ -260,7 +240,7 @@ impl Database {
let mut filter_ids = Vec::new();
for (type_name, id, filter_arc) in filter_schemas {
filter_ids.push(id.clone());
filter_ids.push((type_name.clone(), id.clone()));
self.schemas.insert(id.clone(), filter_arc.clone());
if let Some(t) = self.types.get_mut(&type_name) {
t.schemas.insert(id, filter_arc);
@ -268,8 +248,13 @@ impl Database {
}
// Now actively compile the newly injected filters to lock all nested compose references natively
for id in filter_ids {
if let Some(filter_arc) = self.schemas.get(&id).cloned() {
for (type_name, id) in filter_ids {
if let Some(filter_arc) = self
.types
.get(&type_name)
.and_then(|t| t.schemas.get(&id))
.cloned()
{
let root_id = id.split('/').next().unwrap_or(&id);
filter_arc
.as_ref()
@ -282,13 +267,12 @@ impl Database {
let mut type_insert = Vec::new();
let mut punc_insert = Vec::new();
let mut enum_insert = Vec::new();
let mut global_insert = Vec::new();
// Pass 1: Extract all Schemas structurally off top level definitions into the master registry.
// Validate every node recursively via string filters natively!
for (type_name, type_def) in &self.types {
for (id, schema_arc) in &type_def.schemas {
global_insert.push((id.clone(), Arc::clone(schema_arc)));
self.schemas.insert(id.clone(), Arc::clone(schema_arc));
let mut local_insert = Vec::new();
crate::database::schema::Schema::collect_schemas(
schema_arc,
@ -299,14 +283,13 @@ impl Database {
);
for entry in &local_insert {
type_insert.push((type_name.clone(), entry.0.clone(), Arc::clone(&entry.1)));
global_insert.push((entry.0.clone(), Arc::clone(&entry.1)));
}
}
}
for (punc_name, punc_def) in &self.puncs {
for (id, schema_arc) in &punc_def.schemas {
global_insert.push((id.clone(), Arc::clone(schema_arc)));
self.schemas.insert(id.clone(), Arc::clone(schema_arc));
let mut local_insert = Vec::new();
crate::database::schema::Schema::collect_schemas(
schema_arc,
@ -317,14 +300,13 @@ impl Database {
);
for entry in &local_insert {
punc_insert.push((punc_name.clone(), entry.0.clone(), Arc::clone(&entry.1)));
global_insert.push((entry.0.clone(), Arc::clone(&entry.1)));
}
}
}
for (enum_name, enum_def) in &self.enums {
for (id, schema_arc) in &enum_def.schemas {
global_insert.push((id.clone(), Arc::clone(schema_arc)));
self.schemas.insert(id.clone(), Arc::clone(schema_arc));
let mut local_insert = Vec::new();
crate::database::schema::Schema::collect_schemas(
schema_arc,
@ -335,34 +317,32 @@ impl Database {
);
for entry in &local_insert {
enum_insert.push((enum_name.clone(), entry.0.clone(), Arc::clone(&entry.1)));
global_insert.push((entry.0.clone(), Arc::clone(&entry.1)));
}
}
}
// Apply global inserts
for (id, schema_arc) in global_insert {
self.schemas.insert(id, schema_arc);
}
// Apply local scopes
// Apply local scopes and global schema map
for (origin_name, id, schema_arc) in type_insert {
self.schemas.insert(id.clone(), schema_arc.clone());
if let Some(t) = self.types.get_mut(&origin_name) {
t.schemas.insert(id, schema_arc);
}
}
for (origin_name, id, schema_arc) in punc_insert {
self.schemas.insert(id.clone(), schema_arc.clone());
if let Some(p) = self.puncs.get_mut(&origin_name) {
p.schemas.insert(id, schema_arc);
}
}
for (origin_name, id, schema_arc) in enum_insert {
self.schemas.insert(id.clone(), schema_arc.clone());
if let Some(e) = self.enums.get_mut(&origin_name) {
e.schemas.insert(id, schema_arc);
}
}
}
/// Inspects the Postgres pg_constraint relations catalog to securely identify
/// the precise Foreign Key connecting a parent and child hierarchy path.
pub fn resolve_relation<'a>(

View File

@ -22,6 +22,27 @@ impl std::ops::DerefMut for Schema {
}
}
impl Schema {
/// Returns true if the schema acts purely as a type pointer (composition without overriding constraints)
pub fn is_proxy(&self) -> bool {
self.obj.properties.is_none()
&& self.obj.pattern_properties.is_none()
&& self.obj.additional_properties.is_none()
&& self.obj.required.is_none()
&& self.obj.dependencies.is_none()
&& self.obj.items.is_none()
&& self.obj.prefix_items.is_none()
&& self.obj.contains.is_none()
&& self.obj.format.is_none()
&& self.obj.enum_.is_none()
&& self.obj.const_.is_none()
&& self.obj.cases.is_none()
&& self.obj.one_of.is_none()
&& self.obj.not.is_none()
&& self.obj.family.is_none()
}
}
impl<'de> Deserialize<'de> for Schema {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where

View File

@ -25,7 +25,7 @@ impl Merger {
let mut notifications_queue = Vec::new();
let target_schema = match self.db.schemas.get(schema_id) {
Some(s) => Arc::clone(s),
Some(s) => Arc::clone(&s),
None => {
return crate::drop::Drop::with_errors(vec![crate::drop::Error {
code: "MERGE_FAILED".to_string(),
@ -144,30 +144,49 @@ impl Merger {
if let Some(v) = val {
if let Some((idx_opt, target_id_opt)) = options.get(v) {
if let Some(target_id) = target_id_opt {
if let Some(target_schema) = self.db.schemas.get(target_id) {
schema = Arc::clone(target_schema);
if let Some(target_schema) =
self.db.schemas.get(target_id)
{
schema = target_schema.clone();
} else {
return Err(format!("Polymorphic mapped target '{}' not found in database registry", target_id));
return Err(format!(
"Polymorphic mapped target '{}' not found in database registry",
target_id
));
}
} else if let Some(idx) = idx_opt {
if let Some(target_schema) = schema.obj.one_of.as_ref().and_then(|options| options.get(*idx)) {
schema = Arc::clone(target_schema);
if let Some(target_schema) = schema
.obj
.one_of
.as_ref()
.and_then(|options| options.get(*idx))
{
schema = Arc::clone(target_schema);
} else {
return Err(format!("Polymorphic index target '{}' not found in local oneOf array", idx));
return Err(format!(
"Polymorphic index target '{}' not found in local oneOf array",
idx
));
}
} else {
return Err(format!("Polymorphic mapped target has no path"));
}
} else {
return Err(format!("Polymorphic discriminator {}='{}' matched no compiled options", disc, v));
return Err(format!(
"Polymorphic discriminator {}='{}' matched no compiled options",
disc, v
));
}
} else {
return Err(format!("Polymorphic merging failed: missing required discriminator '{}'", disc));
return Err(format!(
"Polymorphic merging failed: missing required discriminator '{}'",
disc
));
}
}
}
self.merge_object(schema, map, notifications)
},
}
_ => Err("Invalid merge payload: root must be an Object or Array".to_string()),
}
}

View File

@ -28,9 +28,10 @@ impl<'a> Compiler<'a> {
.db
.schemas
.get(schema_id)
.cloned()
.ok_or_else(|| format!("Schema not found: {}", schema_id))?;
let target_schema = std::sync::Arc::clone(schema);
let target_schema = schema;
let mut compiler = Compiler {
db: &self.db,
@ -151,9 +152,9 @@ impl<'a> Compiler<'a> {
if let Some(crate::database::object::SchemaTypeOrArray::Single(t)) = &node.schema.obj.type_ {
if !crate::database::object::is_primitive_type(t) {
// If it's just an ad-hoc struct ref, we should resolve it
if let Some(target_schema) = self.db.schemas.get(t) {
if let Some(target_schema) = self.db.schemas.get(t).cloned() {
let mut ref_node = node.clone();
ref_node.schema = Arc::clone(target_schema);
ref_node.schema = target_schema.clone();
ref_node.schema_id = Some(t.clone());
return self.compile_node(ref_node);
}
@ -306,9 +307,9 @@ impl<'a> Compiler<'a> {
for (disc_val, (idx_opt, target_id_opt)) in options {
if let Some(target_id) = target_id_opt {
if let Some(target_schema) = self.db.schemas.get(target_id) {
if let Some(target_schema) = self.db.schemas.get(target_id).cloned() {
let mut child_node = node.clone();
child_node.schema = Arc::clone(target_schema);
child_node.schema = target_schema.clone();
child_node.schema_id = Some(target_id.clone());
child_node.is_polymorphic_branch = true;

View File

@ -1247,6 +1247,36 @@ fn test_const_17_1() {
crate::tests::runner::run_test_case(&path, 17, 1).unwrap();
}
#[test]
fn test_dynamic_type_0_0() {
let path = format!("{}/fixtures/dynamicType.json", env!("CARGO_MANIFEST_DIR"));
crate::tests::runner::run_test_case(&path, 0, 0).unwrap();
}
#[test]
fn test_dynamic_type_0_1() {
let path = format!("{}/fixtures/dynamicType.json", env!("CARGO_MANIFEST_DIR"));
crate::tests::runner::run_test_case(&path, 0, 1).unwrap();
}
#[test]
fn test_dynamic_type_0_2() {
let path = format!("{}/fixtures/dynamicType.json", env!("CARGO_MANIFEST_DIR"));
crate::tests::runner::run_test_case(&path, 0, 2).unwrap();
}
#[test]
fn test_dynamic_type_0_3() {
let path = format!("{}/fixtures/dynamicType.json", env!("CARGO_MANIFEST_DIR"));
crate::tests::runner::run_test_case(&path, 0, 3).unwrap();
}
#[test]
fn test_dynamic_type_0_4() {
let path = format!("{}/fixtures/dynamicType.json", env!("CARGO_MANIFEST_DIR"));
crate::tests::runner::run_test_case(&path, 0, 4).unwrap();
}
#[test]
fn test_property_names_0_0() {
let path = format!("{}/fixtures/propertyNames.json", env!("CARGO_MANIFEST_DIR"));

View File

@ -140,13 +140,27 @@ fn test_library_api() {
"type": "object"
},
"source_schema.filter": {
"compiledPropertyNames": ["name", "target", "type"],
"compiledPropertyNames": ["$and", "$or", "name", "target", "type"],
"properties": {
"$and": {
"type": ["array", "null"],
"items": {
"compiledPropertyNames": ["$and", "$or", "name", "target", "type"],
"type": "source_schema.filter"
}
},
"$or": {
"type": ["array", "null"],
"items": {
"compiledPropertyNames": ["$and", "$or", "name", "target", "type"],
"type": "source_schema.filter"
}
},
"name": { "type": ["string.condition", "null"] },
"target": { "type": ["target_schema.filter", "null"] },
"type": { "type": ["string.condition", "null"] }
},
"type": "filter"
"type": "object"
}
},
"sensitive": false,
@ -179,11 +193,25 @@ fn test_library_api() {
"type": "object"
},
"target_schema.filter": {
"compiledPropertyNames": ["value"],
"compiledPropertyNames": ["$and", "$or", "value"],
"properties": {
"$and": {
"type": ["array", "null"],
"items": {
"compiledPropertyNames": ["$and", "$or", "value"],
"type": "target_schema.filter"
}
},
"$or": {
"type": ["array", "null"],
"items": {
"compiledPropertyNames": ["$and", "$or", "value"],
"type": "target_schema.filter"
}
},
"value": { "type": ["number.condition", "null"] }
},
"type": "filter"
"type": "object"
}
},
"sensitive": false,

View File

@ -64,12 +64,6 @@ impl Case {
let validator = Validator::new(db);
let schema_id = &self.schema_id;
if !validator.db.schemas.contains_key(schema_id) {
return Err(format!(
"Missing Schema: Cannot find schema ID '{}'",
schema_id
));
}
let test_data = self.data.clone().unwrap_or(Value::Null);
let result = validator.validate(schema_id, &test_data);

View File

@ -5,7 +5,16 @@ impl Expect {
pub fn assert_schemas(&self, db: &Arc<crate::database::Database>) -> Result<(), String> {
if let Some(expected_map) = &self.schemas {
// Collect actual schemas and sort
let mut actual: Vec<String> = db.schemas.keys().cloned().collect();
let mut actual: Vec<String> = Vec::new();
for type_def in db.types.values() {
actual.extend(type_def.schemas.keys().cloned());
}
for punc_def in db.puncs.values() {
actual.extend(punc_def.schemas.keys().cloned());
}
for enum_def in db.enums.values() {
actual.extend(enum_def.schemas.keys().cloned());
}
actual.sort();
// Collect expected schemas and sort
@ -26,7 +35,7 @@ impl Expect {
if expected_val.is_object() && expected_val.as_object().unwrap().is_empty() {
continue; // A `{}` means we just wanted to test it was collected/promoted, skip deep match
}
let actual_ast = db.schemas.get(key).unwrap();
let actual_ast = db.schemas.get(key).cloned().unwrap();
let actual_val = serde_json::to_value(actual_ast).unwrap();
if actual_val != *expected_val {

View File

@ -15,6 +15,7 @@ pub struct ValidationContext<'a> {
pub extensible: bool,
pub reporter: bool,
pub overrides: HashSet<String>,
pub parent: Option<&'a serde_json::Value>,
}
impl<'a> ValidationContext<'a> {
@ -38,6 +39,7 @@ impl<'a> ValidationContext<'a> {
extensible: effective_extensible,
reporter,
overrides,
parent: None,
}
}
@ -57,6 +59,7 @@ impl<'a> ValidationContext<'a> {
overrides: HashSet<String>,
extensible: bool,
reporter: bool,
parent_instance: Option<&'a serde_json::Value>,
) -> Self {
let effective_extensible = schema.extensible.unwrap_or(extensible);
@ -70,6 +73,7 @@ impl<'a> ValidationContext<'a> {
extensible: effective_extensible,
reporter,
overrides,
parent: parent_instance,
}
}
@ -81,6 +85,7 @@ impl<'a> ValidationContext<'a> {
HashSet::new(),
self.extensible,
reporter,
self.parent,
)
}

View File

@ -23,10 +23,6 @@ impl Validator {
Self { db }
}
pub fn get_schema_ids(&self) -> Vec<String> {
self.db.schemas.keys().cloned().collect()
}
pub fn check_type(t: &str, val: &Value) -> bool {
if let Value::String(s) = val
&& s.is_empty()
@ -46,11 +42,13 @@ impl Validator {
}
pub fn validate(&self, schema_id: &str, instance: &Value) -> crate::drop::Drop {
if let Some(schema) = self.db.schemas.get(schema_id) {
let schema_opt = self.db.schemas.get(schema_id);
if let Some(schema) = schema_opt {
let ctx = ValidationContext::new(
&self.db,
schema,
schema,
&schema,
&schema,
instance,
HashSet::new(),
false,

View File

@ -57,6 +57,7 @@ impl<'a> ValidationContext<'a> {
HashSet::new(),
self.extensible,
false,
Some(self.instance),
);
let check = derived.validate()?;
@ -108,6 +109,7 @@ impl<'a> ValidationContext<'a> {
HashSet::new(),
self.extensible,
false,
Some(self.instance),
);
let item_res = derived.validate()?;
result.merge(item_res);
@ -137,6 +139,7 @@ impl<'a> ValidationContext<'a> {
HashSet::new(),
self.extensible,
false,
Some(self.instance),
);
let item_res = derived.validate()?;
result.merge(item_res);

View File

@ -12,6 +12,7 @@ pub mod numeric;
pub mod object;
pub mod polymorphism;
pub mod string;
pub mod r#type;
pub mod util;
impl<'a> ValidationContext<'a> {
@ -28,7 +29,7 @@ impl<'a> ValidationContext<'a> {
if !self.validate_family(&mut result)? {
return Ok(result);
}
if !self.validate_type_inheritance(&mut result)? {
if !self.validate_type(&mut result)? {
return Ok(result);
}

View File

@ -23,10 +23,13 @@ impl<'a> ValidationContext<'a> {
// Entity implicit type validation
if let Some(ref schema_identifier_str) = schema_identifier {
// We decompose identity string routing inherently
let expected_type = schema_identifier_str.split('.').last().unwrap_or(schema_identifier_str);
let expected_type = schema_identifier_str
.split('.')
.last()
.unwrap_or(schema_identifier_str);
// Check if the identifier represents a registered global database entity boundary mathematically
if let Some(type_def) = self.db.types.get(expected_type) {
if let Some(type_def) = self.db.types.get(expected_type).filter(|t| !t.variations.is_empty()) {
if let Some(type_val) = obj.get("type") {
if let Some(type_str) = type_val.as_str() {
if type_def.variations.contains(type_str) {
@ -47,21 +50,28 @@ impl<'a> ValidationContext<'a> {
// Because it's a global entity target, the payload must structurally provide a discriminator natively
result.errors.push(ValidationError {
code: "MISSING_TYPE".to_string(),
message: format!("Schema mechanically requires type discrimination '{}'", expected_type),
message: format!(
"Schema mechanically requires type discrimination '{}'",
expected_type
),
path: self.path.clone(), // Empty boundary
});
}
// If the target mathematically declares a horizontal structural STI variation natively
if schema_identifier_str.contains('.') {
let requires_kind = self.schema.compiled_properties.get()
.map_or(false, |p| p.contains_key("kind"));
let requires_kind = self
.schema
.compiled_properties
.get()
.map_or(false, |p| p.contains_key("kind"));
if requires_kind {
if obj.get("kind").is_none() {
result.errors.push(ValidationError {
code: "MISSING_KIND".to_string(),
message: "Schema mechanically requires horizontal kind discrimination".to_string(),
message: "Schema mechanically requires horizontal kind discrimination"
.to_string(),
path: self.path.clone(),
});
} else {
@ -74,20 +84,20 @@ impl<'a> ValidationContext<'a> {
// Because they lack manual type property descriptors, we natively shield "type" and "kind" keys from
// triggering additionalProperty violations natively IF they precisely correspond to their fast-path boundaries
if let Some(type_val) = obj.get("type") {
if let Some(type_str) = type_val.as_str() {
if type_str == expected_type {
result.evaluated_keys.insert("type".to_string());
}
}
if let Some(type_str) = type_val.as_str() {
if type_str == expected_type {
result.evaluated_keys.insert("type".to_string());
}
}
}
if let Some(kind_val) = obj.get("kind") {
if let Some((kind_str, _)) = schema_identifier_str.rsplit_once('.') {
if let Some(actual_kind) = kind_val.as_str() {
if actual_kind == kind_str {
result.evaluated_keys.insert("kind".to_string());
}
if let Some((kind_str, _)) = schema_identifier_str.rsplit_once('.') {
if let Some(actual_kind) = kind_val.as_str() {
if actual_kind == kind_str {
result.evaluated_keys.insert("kind".to_string());
}
}
}
}
}
}
}
@ -167,7 +177,9 @@ impl<'a> ValidationContext<'a> {
if let Some(child_instance) = obj.get(key) {
let new_path = self.join_path(key);
let is_ref = match &sub_schema.type_ {
Some(crate::database::object::SchemaTypeOrArray::Single(t)) => !crate::database::object::is_primitive_type(t),
Some(crate::database::object::SchemaTypeOrArray::Single(t)) => {
!crate::database::object::is_primitive_type(t)
}
_ => false,
};
let next_extensible = if is_ref { false } else { self.extensible };
@ -179,11 +191,10 @@ impl<'a> ValidationContext<'a> {
HashSet::new(),
next_extensible,
false,
Some(self.instance),
);
let item_res = derived.validate()?;
result.merge(item_res);
result.evaluated_keys.insert(key.to_string());
}
@ -196,7 +207,9 @@ impl<'a> ValidationContext<'a> {
if compiled_re.0.is_match(key) {
let new_path = self.join_path(key);
let is_ref = match &sub_schema.type_ {
Some(crate::database::object::SchemaTypeOrArray::Single(t)) => !crate::database::object::is_primitive_type(t),
Some(crate::database::object::SchemaTypeOrArray::Single(t)) => {
!crate::database::object::is_primitive_type(t)
}
_ => false,
};
let next_extensible = if is_ref { false } else { self.extensible };
@ -208,6 +221,7 @@ impl<'a> ValidationContext<'a> {
HashSet::new(),
next_extensible,
false,
Some(self.instance),
);
let item_res = derived.validate()?;
result.merge(item_res);
@ -225,7 +239,8 @@ impl<'a> ValidationContext<'a> {
{
locally_matched = true;
}
if !locally_matched && let Some(compiled_pp) = self.schema.compiled_pattern_properties.get()
if !locally_matched
&& let Some(compiled_pp) = self.schema.compiled_pattern_properties.get()
{
for (compiled_re, _) in compiled_pp {
if compiled_re.0.is_match(key) {
@ -238,7 +253,9 @@ impl<'a> ValidationContext<'a> {
if !locally_matched {
let new_path = self.join_path(key);
let is_ref = match &additional_schema.type_ {
Some(crate::database::object::SchemaTypeOrArray::Single(t)) => !crate::database::object::is_primitive_type(t),
Some(crate::database::object::SchemaTypeOrArray::Single(t)) => {
!crate::database::object::is_primitive_type(t)
}
_ => false,
};
let next_extensible = if is_ref { false } else { self.extensible };
@ -250,6 +267,7 @@ impl<'a> ValidationContext<'a> {
HashSet::new(),
next_extensible,
false,
Some(self.instance),
);
let item_res = derived.validate()?;
result.merge(item_res);

View File

@ -100,7 +100,7 @@ impl<'a> ValidationContext<'a> {
if let Some((idx_opt, target_id_opt)) = options.get(&val) {
if let Some(target_id) = target_id_opt {
if let Some(target_schema) = self.db.schemas.get(target_id) {
let derived = self.derive_for_schema(target_schema.as_ref(), false);
let derived = self.derive_for_schema(target_schema, false);
let sub_res = derived.validate()?;
let is_valid = sub_res.is_valid();
result.merge(sub_res);
@ -176,78 +176,4 @@ impl<'a> ValidationContext<'a> {
return Ok(false);
}
}
pub(crate) fn validate_type_inheritance(
&self,
result: &mut ValidationResult,
) -> Result<bool, ValidationError> {
// Core inheritance logic replaces legacy routing
let payload_primitive = match self.instance {
serde_json::Value::Null => "null",
serde_json::Value::Bool(_) => "boolean",
serde_json::Value::Number(n) => {
if n.is_i64() || n.is_u64() {
"integer"
} else {
"number"
}
}
serde_json::Value::String(_) => "string",
serde_json::Value::Array(_) => "array",
serde_json::Value::Object(_) => "object",
};
let mut custom_types = Vec::new();
match &self.schema.type_ {
Some(crate::database::object::SchemaTypeOrArray::Single(t)) => {
if !crate::database::object::is_primitive_type(t) {
custom_types.push(t.clone());
}
}
Some(crate::database::object::SchemaTypeOrArray::Multiple(arr)) => {
if arr.contains(&payload_primitive.to_string())
|| (payload_primitive == "integer" && arr.contains(&"number".to_string()))
{
// It natively matched a primitive in the array options, skip forcing custom proxy fallback
} else {
for t in arr {
if !crate::database::object::is_primitive_type(t) {
custom_types.push(t.clone());
}
}
}
}
None => {}
}
for t in custom_types {
if let Some(global_schema) = self.db.schemas.get(&t) {
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 mut shadow = self.derive(
global_schema,
self.instance,
&self.path,
new_overrides,
self.extensible,
true, // Reporter mode
);
shadow.root = global_schema;
result.merge(shadow.validate()?);
} else {
result.errors.push(ValidationError {
code: "INHERITANCE_RESOLUTION_FAILED".to_string(),
message: format!(
"Inherited entity pointer '{}' was not found in schema registry",
t
),
path: self.path.to_string(),
});
}
}
Ok(true)
}
}

138
src/validator/rules/type.rs Normal file
View File

@ -0,0 +1,138 @@
use crate::validator::context::ValidationContext;
use crate::validator::error::ValidationError;
use crate::validator::result::ValidationResult;
impl<'a> ValidationContext<'a> {
pub(crate) fn validate_type(
&self,
result: &mut ValidationResult,
) -> Result<bool, ValidationError> {
let payload_primitive = match self.instance {
serde_json::Value::Null => "null",
serde_json::Value::Bool(_) => "boolean",
serde_json::Value::Number(n) => {
if n.is_i64() || n.is_u64() {
"integer"
} else {
"number"
}
}
serde_json::Value::String(_) => "string",
serde_json::Value::Array(_) => "array",
serde_json::Value::Object(_) => "object",
};
let mut custom_types = Vec::new();
match &self.schema.type_ {
Some(crate::database::object::SchemaTypeOrArray::Single(t)) => {
if !crate::database::object::is_primitive_type(t) {
custom_types.push(t.clone());
}
}
Some(crate::database::object::SchemaTypeOrArray::Multiple(arr)) => {
if arr.contains(&payload_primitive.to_string())
|| (payload_primitive == "integer" && arr.contains(&"number".to_string()))
{
// It natively matched a primitive in the array options, skip forcing custom proxy fallback
} else {
for t in arr {
if !crate::database::object::is_primitive_type(t) {
custom_types.push(t.clone());
}
}
}
}
None => {}
}
for t in custom_types {
let mut target_id = t.clone();
// 1. DYNAMIC TYPE (Composition)
if t.starts_with('$') {
let parts: Vec<&str> = t.split('.').collect();
let var_name = &parts[0][1..]; // Remove the $ prefix
let suffix = if parts.len() > 1 {
format!(".{}", parts[1..].join("."))
} else {
String::new()
};
let mut resolved = false;
if let Some(parent) = self.parent {
if let Some(obj) = parent.as_object() {
if let Some(val) = obj.get(var_name) {
if let Some(str_val) = val.as_str() {
target_id = format!("{}{}", str_val, suffix);
resolved = true;
}
}
}
}
if !resolved {
result.errors.push(ValidationError {
code: "DYNAMIC_TYPE_RESOLUTION_FAILED".to_string(),
message: format!(
"Dynamic type pointer '{}' could not resolve discriminator property '{}' on parent instance",
t, var_name
),
path: self.path.to_string(),
});
continue;
}
}
// 2. Fetch and apply
if let Some(global_schema) = self.db.schemas.get(&target_id) {
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 mut shadow = self.derive(
&global_schema,
self.instance,
&self.path,
new_overrides,
self.extensible,
true, // Reporter mode
self.parent,
);
shadow.root = &global_schema;
result.merge(shadow.validate()?);
} else {
// 3. Error handling pathways
if t.starts_with('$') {
result.errors.push(ValidationError {
code: "DYNAMIC_TYPE_RESOLUTION_FAILED".to_string(),
message: format!(
"Resolved dynamic type pointer '{}' was not found in schema registry",
target_id
),
path: self.path.to_string(),
});
} else if self.schema.is_proxy() {
result.errors.push(ValidationError {
code: "PROXY_TYPE_RESOLUTION_FAILED".to_string(),
message: format!(
"Composed proxy entity pointer '{}' was not found in schema registry",
target_id
),
path: self.path.to_string(),
});
} else {
result.errors.push(ValidationError {
code: "INHERITANCE_RESOLUTION_FAILED".to_string(),
message: format!(
"Inherited entity pointer '{}' was not found in schema registry",
target_id
),
path: self.path.to_string(),
});
}
}
}
Ok(true)
}
}

View File

@ -1 +1 @@
1.0.125
1.0.127