Compare commits

...

8 Commits

Author SHA1 Message Date
8aa15873b0 version: 1.0.166 2026-07-03 19:24:16 -04:00
0d282cc930 filter synthesis: compile named non-table value types structurally
A property typed as a named value type (a schema-only config object like
an operating-hours schedule) previously got a dangling {type}.filter
reference — no filter is ever synthesized for a non-table-backed schema,
so the whole parent filter failed downstream (PROXY_TYPE_RESOLUTION_FAILED;
the punc generator emitted an empty filter type).

Naming a value type is a reuse choice, not a semantics choice: it now
compiles structurally into the parent filter, exactly like an inline
object, recursively (including array items). Table-backed boundaries keep
the lazy {type}.filter reference. A named type with no compilable
structure is omitted instead of dangling.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 19:23:54 -04:00
581fc8e0c0 flow update 2026-07-03 01:33:00 -04:00
6f0bff8dc7 version: 1.0.165 2026-07-02 23:43:33 -04:00
99c69e27ab minor dict improvements and flow update 2026-07-02 23:43:23 -04:00
e490e0d844 version: 1.0.164 2026-06-23 20:17:35 -04:00
e026e82f65 test(jspg): rename items fixture to array and resolve unused HashMap warning
- Rename fixtures/items.json to fixtures/array.json to better reflect array testing constraints.
- Update reference paths in src/tests/fixtures.rs and across other fixture JSON files.
- Remove unused HashMap import in src/validator/rules/dict.rs to resolve the compiler warning.
2026-06-23 20:17:03 -04:00
a1fb9ef650 jspg checkpoint 2026-06-23 19:48:38 -04:00
50 changed files with 1725 additions and 5178 deletions

View File

@ -47,7 +47,22 @@ The core execution engines natively enforce these boundaries:
* **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
### Basic and Structural Types
JSPG models data using a simplified subset of standard JSON Schema types, categorized into primitives and structural types:
* **Primitives**:
* `string`: Standard string, supporting standard keywords (e.g., `pattern`, `minLength`, `maxLength`) and custom formats (`uuid`, `date-time`, `email`). Empty strings (`""`) are leniently treated as "present but unset".
* `number` & `integer`: Numeric and integer values (e.g., supporting `minimum`, `maximum`).
* `boolean`: Boolean `true` or `false` values.
* `null`: Represents database `NULL` or unset fields.
* **Structural Types**:
* `object`: Represents a structured object with a fixed list of static `properties`. Objects are **strict by default** (raising `STRICT_PROPERTY_VIOLATION` for properties not defined in the schema) unless marked with `"extensible": true`.
* `array`: A homogeneous, one-dimensional collection where every element is validated against the `items` schema. Positional/tuple array validation is not supported, and array strictness checks do not apply.
* `dict`: A dynamic key-value map. It utilizes the `keys` keyword to validate key strings (typically via `pattern`) and the `items` keyword to validate the values. It completely replaces the legacy `additionalProperties` and `patternProperties` keywords. Extensibility is implicitly managed via dynamic keys, so strictness checking does not apply.
### Types of Types (Complex & Referential Types)
Beyond basic primitives and structures, JSPG maps schemas to their storage and database-referential boundaries:
* **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.
* The schema conceptually requires a `type` discriminator at runtime so the engine knows what physical variation to interact with.
@ -162,9 +177,11 @@ It evaluates as an **Independent Declarative Rules Engine**. Every `Case` block
```
### Strict by Default & Extensibility
* **Strictness**: By default, any property not explicitly defined in the schema causes a validation error (effectively enforcing `additionalProperties: false` globally).
* **Extensibility (`extensible: true`)**: To allow a free-for-all of undefined properties, schemas must explicitly declare `"extensible": true`.
* **Structured Additional Properties**: If `additionalProperties: {...}` is defined as a schema, arbitrary keys are allowed so long as their values match the defined type constraint.
* **Strictness**: Strictness is a first-class feature for object schemas. By default, any property present in the JSON instance that is not explicitly defined in the schema's `properties` (or inherited, etc.) causes a `STRICT_PROPERTY_VIOLATION` validation error.
* **Extensibility (`extensible: true`)**: This keyword is valid **only** at the `type: "object"` level. By setting `"extensible": true`, you bypass the strictness checking on the object, allowing arbitrary undefined properties to pass.
* **Type: dict and type: array**: These types are dynamic/homogeneous:
* **`dict`**: Defined with `keys` and `items` schemas. Its keys and values are dynamic, so strictness checking does not apply. `"extensible"` is not applicable at the `dict` level.
* **`array`**: Homogeneous collection of items matching the `items` schema. Validation is homogeneous, so strictness checking does not apply. `"extensible"` is not applicable at the `array` level (tuple-like `prefixItems` are removed).
* **Inheritance Boundaries**: Strictness resets when crossing non-primitive `type` boundaries. A schema extending a strict parent remains strict unless it explicitly overrides with `"extensible": true`.
### Format Leniency for Empty Strings
@ -206,7 +223,7 @@ Traits are reusable, non-generating schema fragments used to share properties an
}
```
* **Resolution and Merging**: During `Database::new()`, includes are resolved and merged at the raw JSON level:
* **`properties` / `patternProperties`**: Map keys from the host schema override/shadow included traits.
* **`properties`**: Map keys from the host schema override/shadow included traits.
* **`required` / `display`**: Lists are merged and deduped.
* **`dependencies`**: Merged by combining and deduping lists.
* **Scalars / Arrays / Items**: Host definitions completely override included traits.

View File

@ -1,246 +0,0 @@
[
{
"description": "additionalProperties validates properties not matched by properties",
"database": {
"types": [
{
"name": "schema1",
"schemas": {
"schema1": {
"properties": {
"foo": {
"type": "string"
},
"bar": {
"type": "number"
}
},
"additionalProperties": {
"type": "boolean"
}
}
}
}
]
},
"tests": [
{
"description": "defined properties are valid",
"data": {
"foo": "value",
"bar": 123
},
"schema_id": "schema1",
"action": "validate",
"expect": {
"success": true
}
},
{
"description": "additional property matching schema is valid",
"data": {
"foo": "value",
"is_active": true,
"hidden": false
},
"schema_id": "schema1",
"action": "validate",
"expect": {
"success": true
}
},
{
"description": "additional property not matching schema is invalid",
"data": {
"foo": "value",
"is_active": 1
},
"schema_id": "schema1",
"action": "validate",
"expect": {
"success": false,
"errors": [
{
"code": "INVALID_TYPE",
"values": {
"expected": "boolean"
},
"details": {
"path": "is_active",
"schema": "schema1"
}
}
]
}
}
]
},
{
"description": "extensible: true with additionalProperties still validates structure",
"database": {
"types": [
{
"name": "additionalProperties_1_0",
"schemas": {
"additionalProperties_1_0": {
"properties": {
"foo": {
"type": "string"
}
},
"extensible": true,
"additionalProperties": {
"type": "integer"
}
}
}
}
]
},
"tests": [
{
"description": "additional property matching schema is valid",
"data": {
"foo": "hello",
"count": 5,
"age": 42
},
"schema_id": "additionalProperties_1_0",
"action": "validate",
"expect": {
"success": true
}
},
{
"description": "additional property not matching schema is invalid despite extensible: true",
"data": {
"foo": "hello",
"count": "five"
},
"schema_id": "additionalProperties_1_0",
"action": "validate",
"expect": {
"success": false,
"errors": [
{
"code": "INVALID_TYPE",
"values": {
"expected": "integer"
},
"details": {
"path": "count",
"schema": "additionalProperties_1_0"
}
}
]
}
}
]
},
{
"description": "complex additionalProperties with object and array items",
"database": {
"types": [
{
"name": "schema3",
"schemas": {
"schema3": {
"properties": {
"type": {
"type": "string"
}
},
"additionalProperties": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
}
]
},
"tests": [
{
"description": "valid array of strings",
"data": {
"type": "my_type",
"group_a": [
"field1",
"field2"
],
"group_b": [
"field3"
]
},
"schema_id": "schema3",
"action": "validate",
"expect": {
"success": true
}
},
{
"description": "invalid array of integers",
"data": {
"type": "my_type",
"group_a": [
1,
2
]
},
"schema_id": "schema3",
"action": "validate",
"expect": {
"success": false,
"errors": [
{
"code": "INVALID_TYPE",
"values": {
"expected": "string"
},
"details": {
"path": "group_a/0",
"schema": "schema3"
}
},
{
"code": "INVALID_TYPE",
"values": {
"expected": "string"
},
"details": {
"path": "group_a/1",
"schema": "schema3"
}
}
]
}
},
{
"description": "invalid non-array type",
"data": {
"type": "my_type",
"group_a": "field1"
},
"schema_id": "schema3",
"action": "validate",
"expect": {
"success": false,
"errors": [
{
"code": "INVALID_TYPE",
"values": {
"expected": "array"
},
"details": {
"path": "group_a",
"schema": "schema3"
}
}
]
}
}
]
}
]

590
fixtures/array.json Normal file
View File

@ -0,0 +1,590 @@
[
{
"description": "array type without items allows any items",
"database": {
"types": [
{
"name": "array_0_0",
"schemas": {
"array_0_0": {
"type": "array"
}
}
}
]
},
"tests": [
{
"description": "empty array is valid",
"data": [],
"schema_id": "array_0_0",
"action": "validate",
"expect": {
"success": true
}
},
{
"description": "array with items is valid",
"data": [
1,
"foo"
],
"schema_id": "array_0_0",
"action": "validate",
"expect": {
"success": true
}
},
{
"description": "array with arbitrary objects is valid since items is not constrained",
"data": [
{
"extra_prop": 1
}
],
"schema_id": "array_0_0",
"action": "validate",
"expect": {
"success": true
}
}
]
},
{
"description": "items with boolean schema (true)",
"database": {
"types": [
{
"name": "array_1_0",
"schemas": {
"array_1_0": {
"items": true
}
}
}
]
},
"tests": [
{
"description": "any array is valid",
"data": [
1,
"foo",
true
],
"schema_id": "array_1_0",
"action": "validate",
"expect": {
"success": true
}
}
]
},
{
"description": "items with boolean schema (false)",
"database": {
"types": [
{
"name": "array_2_0",
"schemas": {
"array_2_0": {
"items": false
}
}
}
]
},
"tests": [
{
"description": "empty array is valid",
"data": [],
"schema_id": "array_2_0",
"action": "validate",
"expect": {
"success": true
}
},
{
"description": "any non-empty array is invalid",
"data": [
1,
"foo"
],
"schema_id": "array_2_0",
"action": "validate",
"expect": {
"success": false,
"errors": [
{
"code": "FALSE_SCHEMA",
"details": {
"path": "0",
"schema": "array_2_0"
}
},
{
"code": "FALSE_SCHEMA",
"details": {
"path": "1",
"schema": "array_2_0"
}
}
]
}
}
]
},
{
"description": "a schema given for items",
"database": {
"types": [
{
"name": "array_3_0",
"schemas": {
"array_3_0": {
"items": {
"type": "integer"
}
}
}
}
]
},
"tests": [
{
"description": "valid items",
"data": [
1,
2,
3
],
"schema_id": "array_3_0",
"action": "validate",
"expect": {
"success": true
}
},
{
"description": "wrong type of items",
"data": [
1,
"x"
],
"schema_id": "array_3_0",
"action": "validate",
"expect": {
"success": false,
"errors": [
{
"code": "INVALID_TYPE",
"values": {
"expected": "integer"
},
"details": {
"path": "1",
"schema": "array_3_0"
}
}
]
}
},
{
"description": "non-arrays are invalid",
"data": {
"foo": "bar"
},
"schema_id": "array_3_0",
"action": "validate",
"expect": {
"success": false,
"errors": [
{
"code": "STRICT_PROPERTY_VIOLATION",
"values": {
"property_name": "foo"
},
"details": {
"path": "foo",
"schema": "array_3_0"
}
}
]
}
},
{
"description": "JavaScript pseudo-arrays are invalid",
"data": {
"0": "invalid",
"length": 1
},
"schema_id": "array_3_0",
"action": "validate",
"expect": {
"success": false,
"errors": [
{
"code": "STRICT_PROPERTY_VIOLATION",
"values": {
"property_name": "0"
},
"details": {
"path": "0",
"schema": "array_3_0"
}
},
{
"code": "STRICT_PROPERTY_VIOLATION",
"values": {
"property_name": "length"
},
"details": {
"path": "length",
"schema": "array_3_0"
}
}
]
}
},
{
"description": "primitive strings are valid since items is ignored on primitives",
"data": "hello",
"schema_id": "array_3_0",
"action": "validate",
"expect": {
"success": true
}
}
]
},
{
"description": "nested items",
"database": {
"types": [
{
"name": "array_4_0",
"schemas": {
"array_4_0": {
"type": "array",
"items": {
"type": "array",
"items": {
"type": "array",
"items": {
"type": "array",
"items": {
"type": "number"
}
}
}
}
}
}
}
]
},
"tests": [
{
"description": "valid nested array",
"data": [
[
[
[
1
]
],
[
[
2
],
[
3
]
]
],
[
[
[
4
],
[
5
],
[
6
]
]
]
],
"schema_id": "array_4_0",
"action": "validate",
"expect": {
"success": true
}
},
{
"description": "nested array with invalid type",
"data": [
[
[
[
"1"
]
],
[
[
2
],
[
3
]
]
],
[
[
[
4
],
[
5
],
[
6
]
]
]
],
"schema_id": "array_4_0",
"action": "validate",
"expect": {
"success": false,
"errors": [
{
"code": "INVALID_TYPE",
"values": {
"expected": "number"
},
"details": {
"path": "0/0/0/0",
"schema": "array_4_0"
}
}
]
}
},
{
"description": "not deep enough",
"data": [
[
[
1
],
[
2
],
[
3
]
],
[
[
4
],
[
5
],
[
6
]
]
],
"schema_id": "array_4_0",
"action": "validate",
"expect": {
"success": false,
"errors": [
{
"code": "INVALID_TYPE",
"values": {
"expected": "array"
},
"details": {
"path": "0/0/0",
"schema": "array_4_0"
}
},
{
"code": "INVALID_TYPE",
"values": {
"expected": "array"
},
"details": {
"path": "0/1/0",
"schema": "array_4_0"
}
},
{
"code": "INVALID_TYPE",
"values": {
"expected": "array"
},
"details": {
"path": "0/2/0",
"schema": "array_4_0"
}
},
{
"code": "INVALID_TYPE",
"values": {
"expected": "array"
},
"details": {
"path": "1/0/0",
"schema": "array_4_0"
}
},
{
"code": "INVALID_TYPE",
"values": {
"expected": "array"
},
"details": {
"path": "1/1/0",
"schema": "array_4_0"
}
},
{
"code": "INVALID_TYPE",
"values": {
"expected": "array"
},
"details": {
"path": "1/2/0",
"schema": "array_4_0"
}
}
]
}
}
]
},
{
"description": "items with null instance elements",
"database": {
"types": [
{
"name": "array_5_0",
"schemas": {
"array_5_0": {
"items": {
"type": "null"
}
}
}
}
]
},
"tests": [
{
"description": "allows null elements",
"data": [
null
],
"schema_id": "array_5_0",
"action": "validate",
"expect": {
"success": true
}
}
]
},
{
"description": "array: items extensible",
"database": {
"types": [
{
"name": "array_6_0",
"schemas": {
"array_6_0": {
"type": "array",
"items": {
"extensible": true
}
}
}
}
]
},
"tests": [
{
"description": "array with items is valid (items explicitly allowed to be anything extensible)",
"data": [
1,
"foo",
{}
],
"schema_id": "array_6_0",
"action": "validate",
"expect": {
"success": true
}
}
]
},
{
"description": "array: items strict",
"database": {
"types": [
{
"name": "array_7_0",
"schemas": {
"array_7_0": {
"type": "array",
"items": {
"type": "object",
"extensible": false
}
}
}
}
]
},
"tests": [
{
"description": "array with strict object items is valid",
"data": [
{}
],
"schema_id": "array_7_0",
"action": "validate",
"expect": {
"success": true
}
},
{
"description": "array with invalid strict object items (extra property)",
"data": [
{
"extra": 1
}
],
"schema_id": "array_7_0",
"action": "validate",
"expect": {
"success": false,
"errors": [
{
"code": "STRICT_PROPERTY_VIOLATION",
"values": {
"property_name": "extra"
},
"details": {
"path": "0/extra",
"schema": "array_7_0"
}
}
]
}
}
]
}
]

View File

@ -268,16 +268,6 @@
"path": "",
"schema": "booleanSchema_1_0"
}
},
{
"code": "STRICT_ITEM_VIOLATION",
"values": {
"index": "0"
},
"details": {
"path": "0",
"schema": "booleanSchema_1_0"
}
}
]
}

View File

@ -156,26 +156,6 @@
"path": "",
"schema": "const_1_0"
}
},
{
"code": "STRICT_ITEM_VIOLATION",
"values": {
"index": "0"
},
"details": {
"path": "0",
"schema": "const_1_0"
}
},
{
"code": "STRICT_ITEM_VIOLATION",
"values": {
"index": "1"
},
"details": {
"path": "1",
"schema": "const_1_0"
}
}
]
}
@ -233,16 +213,6 @@
"path": "",
"schema": "const_2_0"
}
},
{
"code": "STRICT_ITEM_VIOLATION",
"values": {
"index": "0"
},
"details": {
"path": "0",
"schema": "const_2_0"
}
}
]
}
@ -268,36 +238,6 @@
"path": "",
"schema": "const_2_0"
}
},
{
"code": "STRICT_ITEM_VIOLATION",
"values": {
"index": "0"
},
"details": {
"path": "0",
"schema": "const_2_0"
}
},
{
"code": "STRICT_ITEM_VIOLATION",
"values": {
"index": "1"
},
"details": {
"path": "1",
"schema": "const_2_0"
}
},
{
"code": "STRICT_ITEM_VIOLATION",
"values": {
"index": "2"
},
"details": {
"path": "2",
"schema": "const_2_0"
}
}
]
}
@ -534,16 +474,6 @@
"path": "",
"schema": "const_6_0"
}
},
{
"code": "STRICT_ITEM_VIOLATION",
"values": {
"index": "0"
},
"details": {
"path": "0",
"schema": "const_6_0"
}
}
]
}
@ -567,16 +497,6 @@
"path": "",
"schema": "const_6_0"
}
},
{
"code": "STRICT_ITEM_VIOLATION",
"values": {
"index": "0"
},
"details": {
"path": "0",
"schema": "const_6_0"
}
}
]
}
@ -630,16 +550,6 @@
"path": "",
"schema": "const_7_0"
}
},
{
"code": "STRICT_ITEM_VIOLATION",
"values": {
"index": "0"
},
"details": {
"path": "0",
"schema": "const_7_0"
}
}
]
}
@ -663,16 +573,6 @@
"path": "",
"schema": "const_7_0"
}
},
{
"code": "STRICT_ITEM_VIOLATION",
"values": {
"index": "0"
},
"details": {
"path": "0",
"schema": "const_7_0"
}
}
]
}

View File

@ -177,8 +177,8 @@
{
"code": "CONTAINS_VIOLATED",
"values": {
"count": "0",
"limit": "1"
"limit": "1",
"count": "0"
},
"details": {
"path": "",
@ -227,8 +227,8 @@
{
"code": "CONTAINS_VIOLATED",
"values": {
"limit": "1",
"count": "0"
"count": "0",
"limit": "1"
},
"details": {
"path": "",
@ -275,16 +275,6 @@
"path": "",
"schema": "contains_3_0"
}
},
{
"code": "STRICT_ITEM_VIOLATION",
"values": {
"index": "0"
},
"details": {
"path": "0",
"schema": "contains_3_0"
}
}
]
}
@ -383,8 +373,8 @@
{
"code": "MULTIPLE_OF_VIOLATED",
"values": {
"value": "3",
"multiple_of": "2"
"multiple_of": "2",
"value": "3"
},
"details": {
"path": "0",
@ -394,8 +384,8 @@
{
"code": "MULTIPLE_OF_VIOLATED",
"values": {
"multiple_of": "2",
"value": "9"
"value": "9",
"multiple_of": "2"
},
"details": {
"path": "2",
@ -431,8 +421,8 @@
{
"code": "CONTAINS_VIOLATED",
"values": {
"limit": "1",
"count": "0"
"count": "0",
"limit": "1"
},
"details": {
"path": "",
@ -599,7 +589,7 @@
},
"tests": [
{
"description": "extra items cause failure",
"description": "extra items do not cause failure",
"data": [
1,
2
@ -607,19 +597,7 @@
"schema_id": "contains_8_0",
"action": "validate",
"expect": {
"success": false,
"errors": [
{
"code": "STRICT_ITEM_VIOLATION",
"values": {
"index": "1"
},
"details": {
"path": "1",
"schema": "contains_8_0"
}
}
]
"success": true
}
},
{

View File

@ -151,9 +151,9 @@
{
"code": "EDGE_MISSING",
"values": {
"child_type": "child",
"property_name": "children",
"parent_type": "parent"
"parent_type": "parent",
"child_type": "child"
},
"details": {
"path": "full.parent/children",
@ -390,9 +390,9 @@
{
"code": "AMBIGUOUS_TYPE_RELATIONS",
"values": {
"parent_type": "actor",
"property_name": "ambiguous_edge",
"child_type": "junction",
"property_name": "ambiguous_edge"
"parent_type": "actor"
},
"details": {
"path": "full.actor/ambiguous_edge",

View File

@ -227,8 +227,8 @@
{
"code": "DEPENDENCY_MISSING",
"values": {
"property_name": "quux",
"required_property": "bar"
"required_property": "bar",
"property_name": "quux"
},
"details": {
"path": "",
@ -276,8 +276,8 @@
{
"code": "DEPENDENCY_MISSING",
"values": {
"property_name": "quux",
"required_property": "foo"
"required_property": "foo",
"property_name": "quux"
},
"details": {
"path": "",
@ -287,8 +287,8 @@
{
"code": "DEPENDENCY_MISSING",
"values": {
"property_name": "quux",
"required_property": "bar"
"required_property": "bar",
"property_name": "quux"
},
"details": {
"path": "",
@ -579,19 +579,7 @@
"schema_id": "schema_schema1",
"action": "validate",
"expect": {
"success": false,
"errors": [
{
"code": "STRICT_ITEM_VIOLATION",
"values": {
"index": "0"
},
"details": {
"path": "0",
"schema": "schema_schema1"
}
}
]
"success": true
}
},
{

284
fixtures/dict.json Normal file
View File

@ -0,0 +1,284 @@
[
{
"description": "basic dict type validation",
"database": {
"types": [
{
"name": "dict_basic",
"schemas": {
"dict_basic": {
"type": "dict"
}
}
}
]
},
"tests": [
{
"description": "valid empty dict",
"data": {},
"schema_id": "dict_basic",
"action": "validate",
"expect": {
"success": true
}
},
{
"description": "valid simple dict",
"data": {
"key1": "value1",
"key2": 123,
"key3": true
},
"schema_id": "dict_basic",
"action": "validate",
"expect": {
"success": true
}
},
{
"description": "invalid type - array is not dict",
"data": [
"a",
"b"
],
"schema_id": "dict_basic",
"action": "validate",
"expect": {
"success": false,
"errors": [
{
"code": "INVALID_TYPE",
"values": {
"expected": "dict"
},
"details": {
"path": "",
"schema": "dict_basic"
}
}
]
}
},
{
"description": "invalid type - string is not dict",
"data": "not a dict",
"schema_id": "dict_basic",
"action": "validate",
"expect": {
"success": false,
"errors": [
{
"code": "INVALID_TYPE",
"values": {
"expected": "dict"
},
"details": {
"path": "",
"schema": "dict_basic"
}
}
]
}
}
]
},
{
"description": "dict keys validation",
"database": {
"types": [
{
"name": "dict_keys",
"schemas": {
"dict_keys": {
"type": "dict",
"keys": {
"type": "string",
"pattern": "^[a-z]{3}$"
}
}
}
}
]
},
"tests": [
{
"description": "valid 3-letter lowercase keys",
"data": {
"abc": 1,
"xyz": "test"
},
"schema_id": "dict_keys",
"action": "validate",
"expect": {
"success": true
}
},
{
"description": "invalid key format",
"data": {
"abc": 1,
"abcd": 2
},
"schema_id": "dict_keys",
"action": "validate",
"expect": {
"success": false,
"errors": [
{
"code": "PATTERN_VIOLATED",
"values": {
"value": "abcd",
"pattern": "^[a-z]{3}$"
},
"details": {
"path": "keys/abcd",
"schema": "dict_keys"
}
}
]
}
}
]
},
{
"description": "dict items (values) validation",
"database": {
"types": [
{
"name": "dict_items",
"schemas": {
"dict_items": {
"type": "dict",
"items": {
"type": "integer"
}
}
}
}
]
},
"tests": [
{
"description": "valid integer values",
"data": {
"a": 1,
"b": 100
},
"schema_id": "dict_items",
"action": "validate",
"expect": {
"success": true
}
},
{
"description": "invalid value type",
"data": {
"a": 1,
"b": "string value"
},
"schema_id": "dict_items",
"action": "validate",
"expect": {
"success": false,
"errors": [
{
"code": "INVALID_TYPE",
"values": {
"expected": "integer"
},
"details": {
"path": "b",
"schema": "dict_items"
}
}
]
}
}
]
},
{
"description": "dict keys and items validation combined",
"database": {
"types": [
{
"name": "dict_combined",
"schemas": {
"dict_combined": {
"type": "dict",
"keys": {
"type": "string",
"pattern": "^[0-9]+$"
},
"items": {
"type": "boolean"
}
}
}
}
]
},
"tests": [
{
"description": "valid numeric keys and boolean values",
"data": {
"123": true,
"456": false
},
"schema_id": "dict_combined",
"action": "validate",
"expect": {
"success": true
}
},
{
"description": "invalid key and valid value",
"data": {
"123": true,
"abc": false
},
"schema_id": "dict_combined",
"action": "validate",
"expect": {
"success": false,
"errors": [
{
"code": "PATTERN_VIOLATED",
"values": {
"pattern": "^[0-9]+$",
"value": "abc"
},
"details": {
"path": "keys/abc",
"schema": "dict_combined"
}
}
]
}
},
{
"description": "valid key and invalid value",
"data": {
"123": "not a boolean"
},
"schema_id": "dict_combined",
"action": "validate",
"expect": {
"success": false,
"errors": [
{
"code": "INVALID_TYPE",
"values": {
"expected": "boolean"
},
"details": {
"path": "123",
"schema": "dict_combined"
}
}
]
}
}
]
}
]

View File

@ -164,8 +164,8 @@
{
"code": "DYNAMIC_TYPE_RESOLUTION_FAILED",
"values": {
"discriminator": "kind",
"pointer": "$kind.filter"
"pointer": "$kind.filter",
"discriminator": "kind"
},
"details": {
"path": "filter",

View File

@ -560,16 +560,6 @@
"path": "",
"schema": "enum_6_0"
}
},
{
"code": "STRICT_ITEM_VIOLATION",
"values": {
"index": "0"
},
"details": {
"path": "0",
"schema": "enum_6_0"
}
}
]
}
@ -593,16 +583,6 @@
"path": "",
"schema": "enum_6_0"
}
},
{
"code": "STRICT_ITEM_VIOLATION",
"values": {
"index": "0"
},
"details": {
"path": "0",
"schema": "enum_6_0"
}
}
]
}
@ -728,16 +708,6 @@
"path": "",
"schema": "enum_8_0"
}
},
{
"code": "STRICT_ITEM_VIOLATION",
"values": {
"index": "0"
},
"details": {
"path": "0",
"schema": "enum_8_0"
}
}
]
}
@ -761,16 +731,6 @@
"path": "",
"schema": "enum_8_0"
}
},
{
"code": "STRICT_ITEM_VIOLATION",
"values": {
"index": "0"
},
"details": {
"path": "0",
"schema": "enum_8_0"
}
}
]
}
@ -873,16 +833,6 @@
"path": "",
"schema": "enum_10_0"
}
},
{
"code": "STRICT_ITEM_VIOLATION",
"values": {
"index": "0"
},
"details": {
"path": "0",
"schema": "enum_10_0"
}
}
]
}
@ -1007,16 +957,6 @@
"path": "",
"schema": "enum_12_0"
}
},
{
"code": "STRICT_ITEM_VIOLATION",
"values": {
"index": "0"
},
"details": {
"path": "0",
"schema": "enum_12_0"
}
}
]
}

View File

@ -34,8 +34,8 @@
{
"code": "EXCLUSIVE_MAXIMUM_VIOLATED",
"values": {
"value": "3",
"limit": "3"
"limit": "3",
"value": "3"
},
"details": {
"path": "",
@ -56,8 +56,8 @@
{
"code": "EXCLUSIVE_MAXIMUM_VIOLATED",
"values": {
"value": "3.5",
"limit": "3"
"limit": "3",
"value": "3.5"
},
"details": {
"path": "",

View File

@ -56,8 +56,8 @@
{
"code": "EXCLUSIVE_MINIMUM_VIOLATED",
"values": {
"limit": "1.1",
"value": "0.6"
"value": "0.6",
"limit": "1.1"
},
"details": {
"path": "",

View File

@ -87,6 +87,34 @@
"type": "string"
}
}
},
"schedule": {
"type": [
"opening_hours",
"null"
]
}
}
},
"season": {
"type": "object",
"properties": {
"label": {
"type": "string"
}
}
},
"opening_hours": {
"type": "object",
"properties": {
"open": {
"type": "string"
},
"seasons": {
"type": "array",
"items": {
"type": "season"
}
}
}
}
@ -262,6 +290,7 @@
"uuid_field",
"tags",
"ad_hoc",
"schedule",
"$and",
"$or"
],
@ -277,6 +306,7 @@
"uuid_field",
"tags",
"ad_hoc",
"schedule",
"$and",
"$or"
],
@ -298,6 +328,7 @@
"uuid_field",
"tags",
"ad_hoc",
"schedule",
"$and",
"$or"
],
@ -366,6 +397,41 @@
"string.condition",
"null"
]
},
"schedule": {
"type": [
"filter",
"null"
],
"compiledPropertyNames": [
"open",
"seasons"
],
"properties": {
"open": {
"type": [
"string.condition",
"null"
]
},
"seasons": {
"type": [
"filter",
"null"
],
"compiledPropertyNames": [
"label"
],
"properties": {
"label": {
"type": [
"string.condition",
"null"
]
}
}
}
}
}
},
"type": "filter"
@ -483,10 +549,12 @@
]
}
}
}
},
"opening_hours": {},
"season": {}
}
}
}
]
}
]
]

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -70,8 +70,8 @@
{
"code": "CONTAINS_VIOLATED",
"values": {
"count": "0",
"limit": "1"
"limit": "1",
"count": "0"
},
"details": {
"path": "",
@ -106,8 +106,8 @@
{
"code": "CONTAINS_VIOLATED",
"values": {
"count": "2",
"limit": "1"
"limit": "1",
"count": "2"
},
"details": {
"path": "",
@ -284,8 +284,8 @@
{
"code": "CONTAINS_VIOLATED",
"values": {
"count": "4",
"limit": "3"
"limit": "3",
"count": "4"
},
"details": {
"path": "",

View File

@ -7,8 +7,7 @@
"name": "maxItems_0_0",
"schemas": {
"maxItems_0_0": {
"maxItems": 2,
"extensible": true
"maxItems": 2
}
}
}
@ -53,8 +52,8 @@
{
"code": "MAX_ITEMS_VIOLATED",
"values": {
"count": "3",
"limit": "2"
"limit": "2",
"count": "3"
},
"details": {
"path": "",
@ -83,8 +82,7 @@
"name": "maxItems_1_0",
"schemas": {
"maxItems_1_0": {
"maxItems": 2,
"extensible": true
"maxItems": 2
}
}
}
@ -117,8 +115,8 @@
{
"code": "MAX_ITEMS_VIOLATED",
"values": {
"limit": "2",
"count": "3"
"count": "3",
"limit": "2"
},
"details": {
"path": "",
@ -138,8 +136,7 @@
"name": "maxItems_2_0",
"schemas": {
"maxItems_2_0": {
"maxItems": 2,
"extensible": true
"maxItems": 2
}
}
}
@ -161,8 +158,8 @@
{
"code": "MAX_ITEMS_VIOLATED",
"values": {
"limit": "2",
"count": "3"
"count": "3",
"limit": "2"
},
"details": {
"path": "",

View File

@ -43,8 +43,8 @@
{
"code": "MAXIMUM_VIOLATED",
"values": {
"value": "3.5",
"limit": "3"
"limit": "3",
"value": "3.5"
},
"details": {
"path": "",

View File

@ -239,8 +239,8 @@
{
"code": "DEPENDENCY_MISSING",
"values": {
"property_name": "trigger",
"required_property": "base_dep"
"required_property": "base_dep",
"property_name": "trigger"
},
"details": {
"path": "",

View File

@ -67,8 +67,8 @@
{
"code": "CONTAINS_VIOLATED",
"values": {
"count": "0",
"limit": "1"
"limit": "1",
"count": "0"
},
"details": {
"path": "",
@ -91,8 +91,8 @@
{
"code": "CONTAINS_VIOLATED",
"values": {
"count": "0",
"limit": "1"
"limit": "1",
"count": "0"
},
"details": {
"path": "",
@ -169,8 +169,8 @@
{
"code": "CONTAINS_VIOLATED",
"values": {
"count": "0",
"limit": "2"
"limit": "2",
"count": "0"
},
"details": {
"path": "",
@ -193,8 +193,8 @@
{
"code": "CONTAINS_VIOLATED",
"values": {
"limit": "2",
"count": "1"
"count": "1",
"limit": "2"
},
"details": {
"path": "",
@ -301,8 +301,8 @@
{
"code": "CONTAINS_VIOLATED",
"values": {
"limit": "2",
"count": "1"
"count": "1",
"limit": "2"
},
"details": {
"path": "",
@ -381,8 +381,8 @@
{
"code": "CONTAINS_VIOLATED",
"values": {
"count": "1",
"limit": "2"
"limit": "2",
"count": "1"
},
"details": {
"path": "",
@ -463,8 +463,8 @@
{
"code": "CONTAINS_VIOLATED",
"values": {
"limit": "3",
"count": "0"
"count": "0",
"limit": "3"
},
"details": {
"path": "",
@ -513,8 +513,8 @@
{
"code": "CONTAINS_VIOLATED",
"values": {
"limit": "1",
"count": "3"
"count": "3",
"limit": "1"
},
"details": {
"path": "",
@ -657,8 +657,8 @@
{
"code": "CONTAINS_VIOLATED",
"values": {
"limit": "1",
"count": "2"
"count": "2",
"limit": "1"
},
"details": {
"path": "",

View File

@ -7,8 +7,7 @@
"name": "minItems_0_0",
"schemas": {
"minItems_0_0": {
"minItems": 1,
"extensible": true
"minItems": 1
}
}
}
@ -49,8 +48,8 @@
{
"code": "MIN_ITEMS_VIOLATED",
"values": {
"count": "0",
"limit": "1"
"limit": "1",
"count": "0"
},
"details": {
"path": "",
@ -79,8 +78,7 @@
"name": "minItems_1_0",
"schemas": {
"minItems_1_0": {
"minItems": 1,
"extensible": true
"minItems": 1
}
}
}
@ -110,8 +108,8 @@
{
"code": "MIN_ITEMS_VIOLATED",
"values": {
"limit": "1",
"count": "0"
"count": "0",
"limit": "1"
},
"details": {
"path": "",

View File

@ -43,8 +43,8 @@
{
"code": "MIN_LENGTH_VIOLATED",
"values": {
"count": "1",
"limit": "2"
"limit": "2",
"count": "1"
},
"details": {
"path": "",
@ -74,8 +74,8 @@
{
"code": "MIN_LENGTH_VIOLATED",
"values": {
"limit": "2",
"count": "1"
"count": "1",
"limit": "2"
},
"details": {
"path": "",

View File

@ -128,8 +128,8 @@
{
"code": "MIN_PROPERTIES_VIOLATED",
"values": {
"limit": "1",
"count": "0"
"count": "0",
"limit": "1"
},
"details": {
"path": "",

View File

@ -127,8 +127,8 @@
{
"code": "MINIMUM_VIOLATED",
"values": {
"limit": "-2",
"value": "-2.0001"
"value": "-2.0001",
"limit": "-2"
},
"details": {
"path": "",

View File

@ -148,8 +148,8 @@
{
"code": "MULTIPLE_OF_VIOLATED",
"values": {
"multiple_of": "0.0001",
"value": "0.00751"
"value": "0.00751",
"multiple_of": "0.0001"
},
"details": {
"path": "",

View File

@ -407,16 +407,6 @@
"path": "",
"schema": "not_4_0"
}
},
{
"code": "STRICT_ITEM_VIOLATION",
"values": {
"index": "0"
},
"details": {
"path": "0",
"schema": "not_4_0"
}
}
]
}
@ -610,16 +600,6 @@
"path": "",
"schema": "not_5_0"
}
},
{
"code": "STRICT_ITEM_VIOLATION",
"values": {
"index": "0"
},
"details": {
"path": "0",
"schema": "not_5_0"
}
}
]
}

View File

@ -34,7 +34,8 @@
{
"code": "PATTERN_VIOLATED",
"values": {
"pattern": "^a*$"
"pattern": "^a*$",
"value": "abc"
},
"details": {
"path": "",

View File

@ -1,580 +0,0 @@
[
{
"description": "patternProperties validates properties matching a regex",
"database": {
"types": [
{
"name": "patternProperties_0_0",
"schemas": {
"patternProperties_0_0": {
"patternProperties": {
"f.*o": {
"type": "integer"
}
},
"items": {}
}
}
}
]
},
"tests": [
{
"description": "a single valid match is valid",
"data": {
"foo": 1
},
"schema_id": "patternProperties_0_0",
"action": "validate",
"expect": {
"success": true
}
},
{
"description": "multiple valid matches is valid",
"data": {
"foo": 1,
"foooooo": 2
},
"schema_id": "patternProperties_0_0",
"action": "validate",
"expect": {
"success": true
}
},
{
"description": "a single invalid match is invalid",
"data": {
"foo": "bar",
"fooooo": 2
},
"schema_id": "patternProperties_0_0",
"action": "validate",
"expect": {
"success": false,
"errors": [
{
"code": "INVALID_TYPE",
"values": {
"expected": "integer"
},
"details": {
"path": "foo",
"schema": "patternProperties_0_0"
}
}
]
}
},
{
"description": "multiple invalid matches is invalid",
"data": {
"foo": "bar",
"foooooo": "baz"
},
"schema_id": "patternProperties_0_0",
"action": "validate",
"expect": {
"success": false,
"errors": [
{
"code": "INVALID_TYPE",
"values": {
"expected": "integer"
},
"details": {
"path": "foo",
"schema": "patternProperties_0_0"
}
},
{
"code": "INVALID_TYPE",
"values": {
"expected": "integer"
},
"details": {
"path": "foooooo",
"schema": "patternProperties_0_0"
}
}
]
}
},
{
"description": "ignores arrays",
"data": [
"foo"
],
"schema_id": "patternProperties_0_0",
"action": "validate",
"expect": {
"success": true
}
},
{
"description": "ignores strings",
"data": "foo",
"schema_id": "patternProperties_0_0",
"action": "validate",
"expect": {
"success": true
}
},
{
"description": "ignores other non-objects",
"data": 12,
"schema_id": "patternProperties_0_0",
"action": "validate",
"expect": {
"success": true
}
},
{
"description": "extra property not matching pattern is INVALID (strict by default)",
"data": {
"foo": 1,
"extra": 2
},
"schema_id": "patternProperties_0_0",
"action": "validate",
"expect": {
"success": false,
"errors": [
{
"code": "STRICT_PROPERTY_VIOLATION",
"values": {
"property_name": "extra"
},
"details": {
"path": "extra",
"schema": "patternProperties_0_0"
}
}
]
}
}
]
},
{
"description": "multiple simultaneous patternProperties are validated",
"database": {
"types": [
{
"name": "patternProperties_1_0",
"schemas": {
"patternProperties_1_0": {
"patternProperties": {
"a*": {
"type": "integer"
},
"aaa*": {
"maximum": 20
}
}
}
}
}
]
},
"tests": [
{
"description": "a single valid match is valid",
"data": {
"a": 21
},
"schema_id": "patternProperties_1_0",
"action": "validate",
"expect": {
"success": true
}
},
{
"description": "a simultaneous match is valid",
"data": {
"aaaa": 18
},
"schema_id": "patternProperties_1_0",
"action": "validate",
"expect": {
"success": true
}
},
{
"description": "multiple matches is valid",
"data": {
"a": 21,
"aaaa": 18
},
"schema_id": "patternProperties_1_0",
"action": "validate",
"expect": {
"success": true
}
},
{
"description": "an invalid due to one is invalid",
"data": {
"a": "bar"
},
"schema_id": "patternProperties_1_0",
"action": "validate",
"expect": {
"success": false,
"errors": [
{
"code": "INVALID_TYPE",
"values": {
"expected": "integer"
},
"details": {
"path": "a",
"schema": "patternProperties_1_0"
}
}
]
}
},
{
"description": "an invalid due to the other is invalid",
"data": {
"aaaa": 31
},
"schema_id": "patternProperties_1_0",
"action": "validate",
"expect": {
"success": false,
"errors": [
{
"code": "MAXIMUM_VIOLATED",
"values": {
"value": "31",
"limit": "20"
},
"details": {
"path": "aaaa",
"schema": "patternProperties_1_0"
}
}
]
}
},
{
"description": "an invalid due to both is invalid",
"data": {
"aaa": "foo",
"aaaa": 31
},
"schema_id": "patternProperties_1_0",
"action": "validate",
"expect": {
"success": false,
"errors": [
{
"code": "INVALID_TYPE",
"values": {
"expected": "integer"
},
"details": {
"path": "aaa",
"schema": "patternProperties_1_0"
}
},
{
"code": "MAXIMUM_VIOLATED",
"values": {
"limit": "20",
"value": "31"
},
"details": {
"path": "aaaa",
"schema": "patternProperties_1_0"
}
}
]
}
}
]
},
{
"description": "regexes are not anchored by default and are case sensitive",
"database": {
"types": [
{
"name": "patternProperties_2_0",
"schemas": {
"patternProperties_2_0": {
"patternProperties": {
"[0-9]{2,}": {
"type": "boolean"
},
"X_": {
"type": "string"
}
},
"extensible": true
}
}
}
]
},
"tests": [
{
"description": "non recognized members are ignored",
"data": {
"answer 1": "42"
},
"schema_id": "patternProperties_2_0",
"action": "validate",
"expect": {
"success": true
}
},
{
"description": "recognized members are accounted for",
"data": {
"a31b": null
},
"schema_id": "patternProperties_2_0",
"action": "validate",
"expect": {
"success": false,
"errors": [
{
"code": "INVALID_TYPE",
"values": {
"expected": "boolean"
},
"details": {
"path": "a31b",
"schema": "patternProperties_2_0"
}
}
]
}
},
{
"description": "regexes are case sensitive",
"data": {
"a_x_3": 3
},
"schema_id": "patternProperties_2_0",
"action": "validate",
"expect": {
"success": true
}
},
{
"description": "regexes are case sensitive, 2",
"data": {
"a_X_3": 3
},
"schema_id": "patternProperties_2_0",
"action": "validate",
"expect": {
"success": false,
"errors": [
{
"code": "INVALID_TYPE",
"values": {
"expected": "string"
},
"details": {
"path": "a_X_3",
"schema": "patternProperties_2_0"
}
}
]
}
}
]
},
{
"description": "patternProperties with boolean schemas",
"database": {
"types": [
{
"name": "patternProperties_3_0",
"schemas": {
"patternProperties_3_0": {
"patternProperties": {
"f.*": true,
"b.*": false
}
}
}
}
]
},
"tests": [
{
"description": "object with property matching schema true is valid",
"data": {
"foo": 1
},
"schema_id": "patternProperties_3_0",
"action": "validate",
"expect": {
"success": true
}
},
{
"description": "object with property matching schema false is invalid",
"data": {
"bar": 2
},
"schema_id": "patternProperties_3_0",
"action": "validate",
"expect": {
"success": false,
"errors": [
{
"code": "FALSE_SCHEMA",
"details": {
"path": "bar",
"schema": "patternProperties_3_0"
}
}
]
}
},
{
"description": "object with both properties is invalid",
"data": {
"foo": 1,
"bar": 2
},
"schema_id": "patternProperties_3_0",
"action": "validate",
"expect": {
"success": false,
"errors": [
{
"code": "FALSE_SCHEMA",
"details": {
"path": "bar",
"schema": "patternProperties_3_0"
}
}
]
}
},
{
"description": "object with a property matching both true and false is invalid",
"data": {
"foobar": 1
},
"schema_id": "patternProperties_3_0",
"action": "validate",
"expect": {
"success": false,
"errors": [
{
"code": "FALSE_SCHEMA",
"details": {
"path": "foobar",
"schema": "patternProperties_3_0"
}
}
]
}
},
{
"description": "empty object is valid",
"data": {},
"schema_id": "patternProperties_3_0",
"action": "validate",
"expect": {
"success": true
}
}
]
},
{
"description": "patternProperties with null valued instance properties",
"database": {
"types": [
{
"name": "patternProperties_4_0",
"schemas": {
"patternProperties_4_0": {
"patternProperties": {
"^.*bar$": {
"type": "null"
}
}
}
}
}
]
},
"tests": [
{
"description": "allows null values",
"data": {
"foobar": null
},
"schema_id": "patternProperties_4_0",
"action": "validate",
"expect": {
"success": true
}
}
]
},
{
"description": "extensible: true allows extra properties NOT matching pattern",
"database": {
"types": [
{
"name": "patternProperties_5_0",
"schemas": {
"patternProperties_5_0": {
"patternProperties": {
"f.*o": {
"type": "integer"
}
},
"extensible": true
}
}
}
]
},
"tests": [
{
"description": "extra property not matching pattern is valid",
"data": {
"bar": 1
},
"schema_id": "patternProperties_5_0",
"action": "validate",
"expect": {
"success": true
}
},
{
"description": "property matching pattern MUST still be valid",
"data": {
"foo": "invalid string"
},
"schema_id": "patternProperties_5_0",
"action": "validate",
"expect": {
"success": false,
"errors": [
{
"code": "INVALID_TYPE",
"values": {
"expected": "integer"
},
"details": {
"path": "foo",
"schema": "patternProperties_5_0"
}
}
]
}
}
]
}
]

View File

@ -1,324 +0,0 @@
[
{
"description": "a schema given for prefixItems",
"database": {
"types": [
{
"name": "prefixItems_0_0",
"schemas": {
"prefixItems_0_0": {
"prefixItems": [
{
"type": "integer"
},
{
"type": "string"
}
]
}
}
}
]
},
"tests": [
{
"description": "correct types",
"data": [
1,
"foo"
],
"schema_id": "prefixItems_0_0",
"action": "validate",
"expect": {
"success": true
}
},
{
"description": "wrong types",
"data": [
"foo",
1
],
"schema_id": "prefixItems_0_0",
"action": "validate",
"expect": {
"success": false,
"errors": [
{
"code": "INVALID_TYPE",
"values": {
"expected": "integer"
},
"details": {
"path": "0",
"schema": "prefixItems_0_0"
}
},
{
"code": "INVALID_TYPE",
"values": {
"expected": "string"
},
"details": {
"path": "1",
"schema": "prefixItems_0_0"
}
}
]
}
},
{
"description": "incomplete array of items",
"data": [
1
],
"schema_id": "prefixItems_0_0",
"action": "validate",
"expect": {
"success": true
}
},
{
"description": "array with additional items (invalid due to strictness)",
"data": [
1,
"foo",
true
],
"schema_id": "prefixItems_0_0",
"action": "validate",
"expect": {
"success": false,
"errors": [
{
"code": "STRICT_ITEM_VIOLATION",
"values": {
"index": "2"
},
"details": {
"path": "2",
"schema": "prefixItems_0_0"
}
}
]
}
},
{
"description": "empty array",
"data": [],
"schema_id": "prefixItems_0_0",
"action": "validate",
"expect": {
"success": true
}
},
{
"description": "JavaScript pseudo-array is valid (invalid due to strict object validation)",
"data": {
"0": "invalid",
"1": "valid",
"length": 2
},
"schema_id": "prefixItems_0_0",
"action": "validate",
"expect": {
"success": false,
"errors": [
{
"code": "STRICT_PROPERTY_VIOLATION",
"values": {
"property_name": "0"
},
"details": {
"path": "0",
"schema": "prefixItems_0_0"
}
},
{
"code": "STRICT_PROPERTY_VIOLATION",
"values": {
"property_name": "1"
},
"details": {
"path": "1",
"schema": "prefixItems_0_0"
}
},
{
"code": "STRICT_PROPERTY_VIOLATION",
"values": {
"property_name": "length"
},
"details": {
"path": "length",
"schema": "prefixItems_0_0"
}
}
]
}
}
]
},
{
"description": "prefixItems with boolean schemas",
"database": {
"types": [
{
"name": "prefixItems_1_0",
"schemas": {
"prefixItems_1_0": {
"prefixItems": [
true,
false
]
}
}
}
]
},
"tests": [
{
"description": "array with one item is valid",
"data": [
1
],
"schema_id": "prefixItems_1_0",
"action": "validate",
"expect": {
"success": true
}
},
{
"description": "array with two items is invalid",
"data": [
1,
"foo"
],
"schema_id": "prefixItems_1_0",
"action": "validate",
"expect": {
"success": false,
"errors": [
{
"code": "FALSE_SCHEMA",
"details": {
"path": "1",
"schema": "prefixItems_1_0"
}
}
]
}
},
{
"description": "empty array is valid",
"data": [],
"schema_id": "prefixItems_1_0",
"action": "validate",
"expect": {
"success": true
}
}
]
},
{
"description": "additional items are allowed by default",
"database": {
"types": [
{
"name": "prefixItems_2_0",
"schemas": {
"prefixItems_2_0": {
"prefixItems": [
{
"type": "integer"
}
],
"extensible": true
}
}
}
]
},
"tests": [
{
"description": "only the first item is validated",
"data": [
1,
"foo",
false
],
"schema_id": "prefixItems_2_0",
"action": "validate",
"expect": {
"success": true
}
}
]
},
{
"description": "prefixItems with null instance elements",
"database": {
"types": [
{
"name": "prefixItems_3_0",
"schemas": {
"prefixItems_3_0": {
"prefixItems": [
{
"type": "null"
}
]
}
}
}
]
},
"tests": [
{
"description": "allows null elements",
"data": [
null
],
"schema_id": "prefixItems_3_0",
"action": "validate",
"expect": {
"success": true
}
}
]
},
{
"description": "extensible: true allows extra items with prefixItems",
"database": {
"types": [
{
"name": "prefixItems_4_0",
"schemas": {
"prefixItems_4_0": {
"prefixItems": [
{
"type": "integer"
}
],
"extensible": true
}
}
}
]
},
"tests": [
{
"description": "extra item is valid",
"data": [
1,
"foo"
],
"schema_id": "prefixItems_4_0",
"action": "validate",
"expect": {
"success": true
}
}
]
}
]

View File

@ -489,16 +489,6 @@
"path": "constructor",
"schema": "properties_4_0"
}
},
{
"code": "STRICT_PROPERTY_VIOLATION",
"values": {
"property_name": "length"
},
"details": {
"path": "constructor/length",
"schema": "properties_4_0"
}
}
]
}

View File

@ -43,8 +43,8 @@
{
"code": "MAX_LENGTH_VIOLATED",
"values": {
"count": "6",
"limit": "3"
"limit": "3",
"count": "6"
},
"details": {
"path": "",
@ -141,7 +141,8 @@
{
"code": "PATTERN_VIOLATED",
"values": {
"pattern": "^a+$"
"pattern": "^a+$",
"value": "aaA"
},
"details": {
"path": "",
@ -430,8 +431,8 @@
{
"code": "MAX_LENGTH_VIOLATED",
"values": {
"limit": "3",
"count": "6"
"count": "6",
"limit": "3"
},
"details": {
"path": "",

View File

@ -139,8 +139,8 @@
{
"code": "MAX_LENGTH_VIOLATED",
"values": {
"limit": "5",
"count": "26"
"count": "26",
"limit": "5"
},
"details": {
"path": "email",

View File

@ -316,536 +316,6 @@
}
]
}
},
{
"description": "non-unique array of more than two arrays is invalid",
"data": [
[
"foo"
],
[
"bar"
],
[
"foo"
]
],
"schema_id": "uniqueItems_0_0",
"action": "validate",
"expect": {
"success": false,
"errors": [
{
"code": "UNIQUE_ITEMS_VIOLATED",
"details": {
"path": "",
"schema": "uniqueItems_0_0"
}
}
]
}
},
{
"description": "1 and true are unique",
"data": [
1,
true
],
"schema_id": "uniqueItems_0_0",
"action": "validate",
"expect": {
"success": true
}
},
{
"description": "0 and false are unique",
"data": [
0,
false
],
"schema_id": "uniqueItems_0_0",
"action": "validate",
"expect": {
"success": true
}
},
{
"description": "[1] and [true] are unique",
"data": [
[
1
],
[
true
]
],
"schema_id": "uniqueItems_0_0",
"action": "validate",
"expect": {
"success": true
}
},
{
"description": "[0] and [false] are unique",
"data": [
[
0
],
[
false
]
],
"schema_id": "uniqueItems_0_0",
"action": "validate",
"expect": {
"success": true
}
},
{
"description": "nested [1] and [true] are unique",
"data": [
[
[
1
],
"foo"
],
[
[
true
],
"foo"
]
],
"schema_id": "uniqueItems_0_0",
"action": "validate",
"expect": {
"success": true
}
},
{
"description": "nested [0] and [false] are unique",
"data": [
[
[
0
],
"foo"
],
[
[
false
],
"foo"
]
],
"schema_id": "uniqueItems_0_0",
"action": "validate",
"expect": {
"success": true
}
},
{
"description": "unique heterogeneous types are valid",
"data": [
{},
[
1
],
true,
null,
1,
"{}"
],
"schema_id": "uniqueItems_0_0",
"action": "validate",
"expect": {
"success": true
}
},
{
"description": "non-unique heterogeneous types are invalid",
"data": [
{},
[
1
],
true,
null,
{},
1
],
"schema_id": "uniqueItems_0_0",
"action": "validate",
"expect": {
"success": false,
"errors": [
{
"code": "UNIQUE_ITEMS_VIOLATED",
"details": {
"path": "",
"schema": "uniqueItems_0_0"
}
}
]
}
},
{
"description": "different objects are unique",
"data": [
{
"a": 1,
"b": 2
},
{
"a": 2,
"b": 1
}
],
"schema_id": "uniqueItems_0_0",
"action": "validate",
"expect": {
"success": true
}
},
{
"description": "objects are non-unique despite key order",
"data": [
{
"a": 1,
"b": 2
},
{
"b": 2,
"a": 1
}
],
"schema_id": "uniqueItems_0_0",
"action": "validate",
"expect": {
"success": false,
"errors": [
{
"code": "UNIQUE_ITEMS_VIOLATED",
"details": {
"path": "",
"schema": "uniqueItems_0_0"
}
}
]
}
},
{
"description": "{\"a\": false} and {\"a\": 0} are unique",
"data": [
{
"a": false
},
{
"a": 0
}
],
"schema_id": "uniqueItems_0_0",
"action": "validate",
"expect": {
"success": true
}
},
{
"description": "{\"a\": true} and {\"a\": 1} are unique",
"data": [
{
"a": true
},
{
"a": 1
}
],
"schema_id": "uniqueItems_0_0",
"action": "validate",
"expect": {
"success": true
}
}
]
},
{
"description": "uniqueItems with an array of items",
"database": {
"types": [
{
"name": "uniqueItems_1_0",
"schemas": {
"uniqueItems_1_0": {
"prefixItems": [
{
"type": "boolean"
},
{
"type": "boolean"
}
],
"uniqueItems": true,
"extensible": true
}
}
}
]
},
"tests": [
{
"description": "[false, true] from items array is valid",
"data": [
false,
true
],
"schema_id": "uniqueItems_1_0",
"action": "validate",
"expect": {
"success": true
}
},
{
"description": "[true, false] from items array is valid",
"data": [
true,
false
],
"schema_id": "uniqueItems_1_0",
"action": "validate",
"expect": {
"success": true
}
},
{
"description": "[false, false] from items array is not valid",
"data": [
false,
false
],
"schema_id": "uniqueItems_1_0",
"action": "validate",
"expect": {
"success": false,
"errors": [
{
"code": "UNIQUE_ITEMS_VIOLATED",
"details": {
"path": "",
"schema": "uniqueItems_1_0"
}
}
]
}
},
{
"description": "[true, true] from items array is not valid",
"data": [
true,
true
],
"schema_id": "uniqueItems_1_0",
"action": "validate",
"expect": {
"success": false,
"errors": [
{
"code": "UNIQUE_ITEMS_VIOLATED",
"details": {
"path": "",
"schema": "uniqueItems_1_0"
}
}
]
}
},
{
"description": "unique array extended from [false, true] is valid",
"data": [
false,
true,
"foo",
"bar"
],
"schema_id": "uniqueItems_1_0",
"action": "validate",
"expect": {
"success": true
}
},
{
"description": "unique array extended from [true, false] is valid",
"data": [
true,
false,
"foo",
"bar"
],
"schema_id": "uniqueItems_1_0",
"action": "validate",
"expect": {
"success": true
}
},
{
"description": "non-unique array extended from [false, true] is not valid",
"data": [
false,
true,
"foo",
"foo"
],
"schema_id": "uniqueItems_1_0",
"action": "validate",
"expect": {
"success": false,
"errors": [
{
"code": "UNIQUE_ITEMS_VIOLATED",
"details": {
"path": "",
"schema": "uniqueItems_1_0"
}
}
]
}
},
{
"description": "non-unique array extended from [true, false] is not valid",
"data": [
true,
false,
"foo",
"foo"
],
"schema_id": "uniqueItems_1_0",
"action": "validate",
"expect": {
"success": false,
"errors": [
{
"code": "UNIQUE_ITEMS_VIOLATED",
"details": {
"path": "",
"schema": "uniqueItems_1_0"
}
}
]
}
}
]
},
{
"description": "uniqueItems with an array of items and additionalItems=false",
"database": {
"types": [
{
"name": "uniqueItems_2_0",
"schemas": {
"uniqueItems_2_0": {
"prefixItems": [
{
"type": "boolean"
},
{
"type": "boolean"
}
],
"uniqueItems": true,
"items": false
}
}
}
]
},
"tests": [
{
"description": "[false, true] from items array is valid",
"data": [
false,
true
],
"schema_id": "uniqueItems_2_0",
"action": "validate",
"expect": {
"success": true
}
},
{
"description": "[true, false] from items array is valid",
"data": [
true,
false
],
"schema_id": "uniqueItems_2_0",
"action": "validate",
"expect": {
"success": true
}
},
{
"description": "[false, false] from items array is not valid",
"data": [
false,
false
],
"schema_id": "uniqueItems_2_0",
"action": "validate",
"expect": {
"success": false,
"errors": [
{
"code": "UNIQUE_ITEMS_VIOLATED",
"details": {
"path": "",
"schema": "uniqueItems_2_0"
}
}
]
}
},
{
"description": "[true, true] from items array is not valid",
"data": [
true,
true
],
"schema_id": "uniqueItems_2_0",
"action": "validate",
"expect": {
"success": false,
"errors": [
{
"code": "UNIQUE_ITEMS_VIOLATED",
"details": {
"path": "",
"schema": "uniqueItems_2_0"
}
}
]
}
},
{
"description": "extra items are invalid even if unique",
"data": [
false,
true,
null
],
"schema_id": "uniqueItems_2_0",
"action": "validate",
"expect": {
"success": false,
"errors": [
{
"code": "FALSE_SCHEMA",
"details": {
"path": "2",
"schema": "uniqueItems_2_0"
}
}
]
}
}
]
},
@ -1099,232 +569,6 @@
}
]
},
{
"description": "uniqueItems=false with an array of items",
"database": {
"types": [
{
"name": "uniqueItems_4_0",
"schemas": {
"uniqueItems_4_0": {
"prefixItems": [
{
"type": "boolean"
},
{
"type": "boolean"
}
],
"uniqueItems": false,
"extensible": true
}
}
}
]
},
"tests": [
{
"description": "[false, true] from items array is valid",
"data": [
false,
true
],
"schema_id": "uniqueItems_4_0",
"action": "validate",
"expect": {
"success": true
}
},
{
"description": "[true, false] from items array is valid",
"data": [
true,
false
],
"schema_id": "uniqueItems_4_0",
"action": "validate",
"expect": {
"success": true
}
},
{
"description": "[false, false] from items array is valid",
"data": [
false,
false
],
"schema_id": "uniqueItems_4_0",
"action": "validate",
"expect": {
"success": true
}
},
{
"description": "[true, true] from items array is valid",
"data": [
true,
true
],
"schema_id": "uniqueItems_4_0",
"action": "validate",
"expect": {
"success": true
}
},
{
"description": "unique array extended from [false, true] is valid",
"data": [
false,
true,
"foo",
"bar"
],
"schema_id": "uniqueItems_4_0",
"action": "validate",
"expect": {
"success": true
}
},
{
"description": "unique array extended from [true, false] is valid",
"data": [
true,
false,
"foo",
"bar"
],
"schema_id": "uniqueItems_4_0",
"action": "validate",
"expect": {
"success": true
}
},
{
"description": "non-unique array extended from [false, true] is valid",
"data": [
false,
true,
"foo",
"foo"
],
"schema_id": "uniqueItems_4_0",
"action": "validate",
"expect": {
"success": true
}
},
{
"description": "non-unique array extended from [true, false] is valid",
"data": [
true,
false,
"foo",
"foo"
],
"schema_id": "uniqueItems_4_0",
"action": "validate",
"expect": {
"success": true
}
}
]
},
{
"description": "uniqueItems=false with an array of items and additionalItems=false",
"database": {
"types": [
{
"name": "uniqueItems_5_0",
"schemas": {
"uniqueItems_5_0": {
"prefixItems": [
{
"type": "boolean"
},
{
"type": "boolean"
}
],
"uniqueItems": false,
"items": false
}
}
}
]
},
"tests": [
{
"description": "[false, true] from items array is valid",
"data": [
false,
true
],
"schema_id": "uniqueItems_5_0",
"action": "validate",
"expect": {
"success": true
}
},
{
"description": "[true, false] from items array is valid",
"data": [
true,
false
],
"schema_id": "uniqueItems_5_0",
"action": "validate",
"expect": {
"success": true
}
},
{
"description": "[false, false] from items array is valid",
"data": [
false,
false
],
"schema_id": "uniqueItems_5_0",
"action": "validate",
"expect": {
"success": true
}
},
{
"description": "[true, true] from items array is valid",
"data": [
true,
true
],
"schema_id": "uniqueItems_5_0",
"action": "validate",
"expect": {
"success": true
}
},
{
"description": "extra items are invalid even if unique",
"data": [
false,
true,
null
],
"schema_id": "uniqueItems_5_0",
"action": "validate",
"expect": {
"success": false,
"errors": [
{
"code": "FALSE_SCHEMA",
"details": {
"path": "2",
"schema": "uniqueItems_5_0"
}
}
]
}
}
]
},
{
"description": "extensible: true allows extra items in uniqueItems",
"database": {

View File

@ -25,9 +25,9 @@ impl Schema {
("identifier".to_string(), id.to_string()),
])),
details: ErrorDetails {
path: Some(path.to_string()),
schema: Some(root_id.to_string()),
..Default::default()
path: Some(path.to_string()),
schema: Some(root_id.to_string()),
..Default::default()
},
});
return;
@ -84,13 +84,6 @@ impl Schema {
}
}
if let Some(pattern_props) = &schema_arc.obj.pattern_properties {
for (k, v) in pattern_props.iter() {
let next_path = format!("{}/{}", path, k);
Self::collect_schemas(v, root_id, next_path, to_insert, errors);
}
}
let mut map_arr = |arr: &Vec<Arc<Schema>>, sub: &str| {
for (i, v) in arr.iter().enumerate() {
Self::collect_schemas(
@ -103,10 +96,6 @@ impl Schema {
}
};
if let Some(arr) = &schema_arc.obj.prefix_items {
map_arr(arr, "prefixItems");
}
if let Some(arr) = &schema_arc.obj.one_of {
map_arr(arr, "oneOf");
}
@ -123,11 +112,6 @@ impl Schema {
}
};
map_opt(
&schema_arc.obj.additional_properties,
false,
"additionalProperties",
);
map_opt(&schema_arc.obj.items, true, "items");
map_opt(&schema_arc.obj.not, false, "not");
map_opt(&schema_arc.obj.contains, false, "contains");

View File

@ -26,10 +26,14 @@ impl Schema {
if let Some(items) = &child.obj.items {
if !items.is_proxy() {
structural_filter = items.compile_filter(_db, "", _errors);
} else if let Some(target) = Self::value_type_target(items, _db) {
structural_filter = target.compile_filter(_db, "", _errors);
}
}
} else if !child.is_proxy() {
structural_filter = child.compile_filter(_db, "", _errors);
} else if let Some(target) = Self::value_type_target(child, _db) {
structural_filter = target.compile_filter(_db, "", _errors);
}
if let Some(mut inline_schema) = structural_filter {
@ -117,6 +121,37 @@ impl Schema {
None
}
/// Resolves a pure type-pointer schema to a named non-table value type's own schema —
/// a reusable, schema-only object (e.g. an operating-hours config). Naming a value type
/// is a reuse choice, not a semantics choice: it filters structurally, exactly like an
/// inline object. Table-backed boundaries keep the lazy {type}.filter reference instead.
fn value_type_target<'a>(schema: &Arc<Schema>, db: &'a Database) -> Option<&'a Arc<Schema>> {
let t = match &schema.obj.type_ {
Some(SchemaTypeOrArray::Single(t)) => Some(t.as_str()),
Some(SchemaTypeOrArray::Multiple(types)) => {
types.iter().find(|t| *t != "null").map(|s| s.as_str())
}
None => None,
}?;
if matches!(
t,
"string" | "integer" | "number" | "boolean" | "object" | "array" | "null"
) {
return None;
}
if t.ends_with(".condition") || t.ends_with(".filter") {
return None;
}
if db.enums.contains_key(t) {
return None;
}
let base = t.split('.').next_back().unwrap_or(t);
if db.types.contains_key(base) {
return None;
}
db.schemas.get(t)
}
fn resolve_filter_type(schema: &Arc<Schema>, db: &Database) -> Option<Vec<String>> {
if let Some(type_) = &schema.obj.type_ {
match type_ {
@ -151,6 +186,7 @@ impl Schema {
"number" => Some(vec!["number.condition".to_string()]),
"boolean" => Some(vec!["boolean.condition".to_string()]),
"object" => None, // Inline structures are ignored in Composed References
"dict" => None, // Dynamic dictionary maps are ignored in Composed References
"array" => {
if let Some(items) = &schema.obj.items {
return Self::resolve_filter_type(items, db);
@ -164,8 +200,16 @@ impl Schema {
} else if db.enums.contains_key(custom) {
Some(vec![format!("{}.condition", custom)])
} else {
// Assume anything else is a Relational cross-boundary that already has its own .filter dynamically built
Some(vec![format!("{}.filter", custom)])
// A Relational cross-boundary gets a reference to the target's dynamically built
// .filter — but only a table-backed boundary has one. A named non-table value type
// compiles structurally upstream (value_type_target); reaching here means it had
// no compilable structure — omit it rather than emit a dangling .filter reference.
let base = custom.split('.').next_back().unwrap_or(custom);
if db.types.contains_key(base) {
Some(vec![format!("{}.filter", custom)])
} else {
None
}
}
}
}

View File

@ -39,18 +39,6 @@ impl Schema {
}
}
if let Some(pattern_props) = &self.obj.pattern_properties {
let mut compiled = Vec::new();
for (k, v) in pattern_props {
if let Ok(re) = regex::Regex::new(k) {
compiled.push((crate::database::object::CompiledRegex(re), v.clone()));
}
}
if !compiled.is_empty() {
let _ = self.obj.compiled_pattern_properties.set(compiled);
}
}
let mut props = IndexMap::new();
// 1. Resolve INHERITANCE dependencies first
@ -78,10 +66,10 @@ impl Schema {
code: "MULTIPLE_INHERITANCE_PROHIBITED".to_string(),
values: Some(HashMap::from([("types".to_string(), types.join(", "))])),
details: ErrorDetails {
path: Some(path.clone()),
schema: Some(root_id.to_string()),
..Default::default()
}
path: Some(path.clone()),
schema: Some(root_id.to_string()),
..Default::default()
},
});
}
@ -137,35 +125,21 @@ impl Schema {
child.compile(db, root_id, format!("{}/{}", path, k), errors);
}
}
if let Some(items) = &self.obj.items {
items.compile(db, root_id, format!("{}/items", path), errors);
}
if let Some(pattern_props) = &self.obj.pattern_properties {
for (k, child) in pattern_props {
child.compile(db, root_id, format!("{}/{}", path, k), errors);
}
}
if let Some(additional_props) = &self.obj.additional_properties {
additional_props.compile(
db,
root_id,
format!("{}/additionalProperties", path),
errors,
);
}
if let Some(one_of) = &self.obj.one_of {
for (i, child) in one_of.iter().enumerate() {
child.compile(db, root_id, format!("{}/oneOf/{}", path, i), errors);
}
}
if let Some(arr) = &self.obj.prefix_items {
for (i, child) in arr.iter().enumerate() {
child.compile(db, root_id, format!("{}/prefixItems/{}", path, i), errors);
}
}
if let Some(child) = &self.obj.not {
child.compile(db, root_id, format!("{}/not", path), errors);
}
if let Some(child) = &self.obj.contains {
child.compile(db, root_id, format!("{}/contains", path), errors);
}

View File

@ -81,7 +81,6 @@ fn resolve_in_place(
let mut merged_required = HashSet::new();
let mut merged_display = HashSet::new();
let mut merged_dependencies = serde_json::Map::new();
let mut merged_pattern_props = serde_json::Map::new();
// Read current values first to let host override included properties
if let Some(req) = current.get("required").and_then(|v| v.as_array()) {
@ -103,11 +102,7 @@ fn resolve_in_place(
merged_dependencies.insert(k.clone(), v.clone());
}
}
if let Some(pat_props) = current.get("patternProperties").and_then(|v| v.as_object()) {
for (k, v) in pat_props {
merged_pattern_props.insert(k.clone(), v.clone());
}
}
if let Some(props) = current.get("properties").and_then(|v| v.as_object()) {
for (k, v) in props {
merged_props.insert(k.clone(), v.clone());
@ -119,7 +114,10 @@ fn resolve_in_place(
if visited.contains(inc_name) {
errors.push(Error {
code: "CIRCULAR_INCLUDE_DETECTED".to_string(),
values: Some(HashMap::from([("include".to_string(), inc_name.to_string())])),
values: Some(HashMap::from([(
"include".to_string(),
inc_name.to_string(),
)])),
details: ErrorDetails {
schema: Some(schema_id.to_string()),
path: Some(path.to_string()),
@ -156,18 +154,6 @@ fn resolve_in_place(
}
}
// Merge patternProperties (host overrides trait)
if let Some(target_pat_props) = resolved_target
.get("patternProperties")
.and_then(|v| v.as_object())
{
for (k, v) in target_pat_props {
if !merged_pattern_props.contains_key(k) {
merged_pattern_props.insert(k.clone(), v.clone());
}
}
}
// Merge required
if let Some(target_req) = resolved_target.get("required").and_then(|v| v.as_array()) {
for r in target_req {
@ -218,7 +204,6 @@ fn resolve_in_place(
if let Some(obj) = current.as_object_mut() {
for (k, v) in resolved_target.as_object().unwrap() {
if k != "properties"
&& k != "patternProperties"
&& k != "required"
&& k != "display"
&& k != "dependencies"
@ -233,7 +218,10 @@ fn resolve_in_place(
} else {
errors.push(Error {
code: "TRAIT_NOT_FOUND".to_string(),
values: Some(HashMap::from([("include".to_string(), inc_name.to_string())])),
values: Some(HashMap::from([(
"include".to_string(),
inc_name.to_string(),
)])),
details: ErrorDetails {
schema: Some(schema_id.to_string()),
path: Some(path.to_string()),
@ -248,12 +236,7 @@ fn resolve_in_place(
if !merged_props.is_empty() {
obj.insert("properties".to_string(), Value::Object(merged_props));
}
if !merged_pattern_props.is_empty() {
obj.insert(
"patternProperties".to_string(),
Value::Object(merged_pattern_props),
);
}
if !merged_required.is_empty() {
let mut req_vec: Vec<Value> = merged_required.into_iter().map(Value::String).collect();
req_vec.sort_by(|a, b| a.as_str().unwrap().cmp(b.as_str().unwrap()));
@ -289,22 +272,7 @@ fn resolve_in_place(
);
}
}
if let Some(pat_props) = obj
.get_mut("patternProperties")
.and_then(|v| v.as_object_mut())
{
for (k, v) in pat_props {
resolve_in_place(
v,
traits,
schemas,
errors,
schema_id,
&format!("{}/{}", path, k),
visited,
);
}
}
if let Some(items) = obj.get_mut("items") {
resolve_in_place(
items,
@ -316,30 +284,6 @@ fn resolve_in_place(
visited,
);
}
if let Some(prefix_items) = obj.get_mut("prefixItems").and_then(|v| v.as_array_mut()) {
for (i, v) in prefix_items.iter_mut().enumerate() {
resolve_in_place(
v,
traits,
schemas,
errors,
schema_id,
&format!("{}/prefixItems/{}", path, i),
visited,
);
}
}
if let Some(additional_props) = obj.get_mut("additionalProperties") {
resolve_in_place(
additional_props,
traits,
schemas,
errors,
schema_id,
&format!("{}/additionalProperties", path),
visited,
);
}
if let Some(one_of) = obj.get_mut("oneOf").and_then(|v| v.as_array_mut()) {
for (i, v) in one_of.iter_mut().enumerate() {
resolve_in_place(

View File

@ -32,12 +32,10 @@ pub struct SchemaObject {
// Object Keywords
#[serde(skip_serializing_if = "Option::is_none")]
pub properties: Option<IndexMap<String, Arc<Schema>>>,
#[serde(rename = "patternProperties")]
#[serde(rename = "keys")]
#[serde(skip_serializing_if = "Option::is_none")]
pub pattern_properties: Option<IndexMap<String, Arc<Schema>>>,
#[serde(rename = "additionalProperties")]
#[serde(skip_serializing_if = "Option::is_none")]
pub additional_properties: Option<Arc<Schema>>,
pub keys: Option<Arc<Schema>>,
#[serde(rename = "family")]
#[serde(skip_serializing_if = "Option::is_none")]
pub family: Option<String>,
@ -53,9 +51,6 @@ pub struct SchemaObject {
#[serde(rename = "items")]
#[serde(skip_serializing_if = "Option::is_none")]
pub items: Option<Arc<Schema>>,
#[serde(rename = "prefixItems")]
#[serde(skip_serializing_if = "Option::is_none")]
pub prefix_items: Option<Vec<Arc<Schema>>>,
// String Validation
#[serde(rename = "minLength")]
@ -189,8 +184,6 @@ pub struct SchemaObject {
pub compiled_format: OnceLock<CompiledFormat>,
#[serde(skip)]
pub compiled_pattern: OnceLock<CompiledRegex>,
#[serde(skip)]
pub compiled_pattern_properties: OnceLock<Vec<(CompiledRegex, Arc<Schema>)>>,
}
/// Represents a compiled format validator
@ -263,7 +256,7 @@ where
pub fn is_primitive_type(t: &str) -> bool {
matches!(
t,
"string" | "number" | "integer" | "boolean" | "object" | "array" | "null"
"string" | "number" | "integer" | "boolean" | "object" | "array" | "null" | "dict"
)
}

View File

@ -26,12 +26,10 @@ 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.keys.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()
@ -68,12 +66,10 @@ impl<'de> Deserialize<'de> for Schema {
// restrict additional properties natively
let is_empty = obj.type_.is_none()
&& obj.properties.is_none()
&& obj.pattern_properties.is_none()
&& obj.additional_properties.is_none()
&& obj.keys.is_none()
&& obj.required.is_none()
&& obj.dependencies.is_none()
&& obj.items.is_none()
&& obj.prefix_items.is_none()
&& obj.contains.is_none()
&& obj.format.is_none()
&& obj.enum_.is_none()

File diff suppressed because it is too large Load Diff

View File

@ -37,6 +37,7 @@ impl Validator {
"number" => val.is_number(),
"integer" => is_integer(val),
"object" => val.is_object(),
"dict" => val.is_object(),
"array" => val.is_array(),
_ => true,
}

View File

@ -99,42 +99,10 @@ impl<'a> ValidationContext<'a> {
}
let len = arr.len();
let mut validation_index = 0;
if let Some(ref prefix) = self.schema.prefix_items {
for (i, sub_schema) in prefix.iter().enumerate() {
if i < len {
if let Some(child_instance) = arr.get(i) {
let mut item_path = self.join_path(&i.to_string());
let is_topological = sub_schema.obj.requires_uuid_path(self.db);
if is_topological {
if let Some(obj) = child_instance.as_object() {
if let Some(id_str) = obj.get("id").and_then(|v| v.as_str()) {
item_path = self.join_path(id_str);
}
}
}
let derived = self.derive(
sub_schema,
child_instance,
&item_path,
HashSet::new(),
self.extensible,
false,
Some(self.instance),
);
let item_res = derived.validate()?;
result.merge(item_res);
result.evaluated_indices.insert(i);
validation_index += 1;
}
}
}
}
if let Some(ref items_schema) = self.schema.items {
let is_topological = items_schema.obj.requires_uuid_path(self.db);
for i in validation_index..len {
for i in 0..len {
if let Some(child_instance) = arr.get(i) {
let mut item_path = self.join_path(&i.to_string());
if is_topological {

View File

@ -0,0 +1,76 @@
use std::collections::HashSet;
use serde_json::Value;
use crate::validator::context::ValidationContext;
use crate::validator::error::ValidationError;
use crate::validator::result::ValidationResult;
impl<'a> ValidationContext<'a> {
pub(crate) fn validate_dict(
&self,
result: &mut ValidationResult,
) -> Result<bool, ValidationError> {
let current = self.instance;
if let Some(obj) = current.as_object() {
let is_dict = match &self.schema.type_ {
Some(crate::database::object::SchemaTypeOrArray::Single(t)) => t == "dict",
Some(crate::database::object::SchemaTypeOrArray::Multiple(types)) => types.contains(&"dict".to_string()),
None => false,
};
if is_dict {
// Mark all keys as evaluated since a dictionary's keys are all dynamic parameters, not structured properties
result.evaluated_keys.extend(obj.keys().cloned());
// Validate keys
if let Some(ref keys_schema) = self.schema.keys {
for key in obj.keys() {
let _new_path = self.join_path(&format!("keys/{}", key));
let val_str = Value::String(key.to_string());
let ctx = self.derive(
keys_schema,
&val_str,
&_new_path,
HashSet::new(),
self.extensible,
self.reporter,
None,
);
result.merge(ctx.validate()?);
}
}
// Validate items (values)
if let Some(ref items_schema) = self.schema.items {
for (key, child_instance) in obj {
let new_path = self.join_path(key);
let is_ref = match &items_schema.type_ {
Some(crate::database::object::SchemaTypeOrArray::Single(ref_t)) => {
!crate::database::object::is_primitive_type(ref_t)
}
_ => false,
};
let next_extensible = if is_ref { false } else { self.extensible };
let derived = self.derive(
items_schema,
child_instance,
&new_path,
HashSet::new(),
next_extensible,
false,
Some(self.instance),
);
let item_res = derived.validate()?;
result.merge(item_res);
}
}
}
}
Ok(true)
}
}

View File

@ -1,5 +1,7 @@
use std::collections::HashMap;
use crate::database::object::{is_primitive_type, SchemaTypeOrArray};
use crate::database::schema::Schema;
use crate::validator::context::ValidationContext;
use crate::validator::error::ValidationError;
use crate::validator::result::ValidationResult;
@ -16,6 +18,49 @@ impl<'a> ValidationContext<'a> {
Ok(true)
}
pub(crate) fn schema_expects_primitive(&self, schema: &Schema, primitive: &str) -> bool {
match &schema.type_ {
None => true, // Any type is allowed
Some(SchemaTypeOrArray::Single(t)) => {
if t == primitive {
true
} else if !is_primitive_type(t) {
if primitive == "object" {
true
} else {
// Custom type - resolve recursively
if let Some(parent) = self.db.schemas.get(t) {
self.schema_expects_primitive(parent, primitive)
} else {
false
}
}
} else {
false
}
}
Some(SchemaTypeOrArray::Multiple(types)) => {
types.iter().any(|t| {
if t == primitive {
true
} else if !is_primitive_type(t) {
if primitive == "object" {
true
} else {
if let Some(parent) = self.db.schemas.get(t) {
self.schema_expects_primitive(parent, primitive)
} else {
false
}
}
} else {
false
}
})
}
}
}
pub(crate) fn validate_strictness(
&self,
result: &mut ValidationResult,
@ -24,38 +69,18 @@ impl<'a> ValidationContext<'a> {
return Ok(true);
}
if let Some(obj) = self.instance.as_object() {
for key in obj.keys() {
if !result.evaluated_keys.contains(key) && !self.overrides.contains(key) {
result.errors.push(ValidationError {
code: "STRICT_PROPERTY_VIOLATION".to_string(),
values: Some(HashMap::from([
("property_name".to_string(), key.to_string()),
])),
path: self.join_path(key),
});
}
}
}
if let Some(arr) = self.instance.as_array() {
for i in 0..arr.len() {
if !result.evaluated_indices.contains(&i) {
let mut item_path = self.join_path(&i.to_string());
if let Some(child_instance) = arr.get(i) {
if let Some(obj) = child_instance.as_object() {
if let Some(id_str) = obj.get("id").and_then(|v| v.as_str()) {
item_path = self.join_path(id_str);
}
}
if self.schema_expects_primitive(self.schema, "object") || self.schema_expects_primitive(self.schema, "dict") {
if let Some(obj) = self.instance.as_object() {
for key in obj.keys() {
if !result.evaluated_keys.contains(key) && !self.overrides.contains(key) {
result.errors.push(ValidationError {
code: "STRICT_PROPERTY_VIOLATION".to_string(),
values: Some(HashMap::from([
("property_name".to_string(), key.to_string()),
])),
path: self.join_path(key),
});
}
result.errors.push(ValidationError {
code: "STRICT_ITEM_VIOLATION".to_string(),
values: Some(HashMap::from([
("index".to_string(), i.to_string()),
])),
path: item_path,
});
}
}
}

View File

@ -6,6 +6,7 @@ use std::collections::HashMap;
pub mod array;
pub mod cases;
pub mod core;
pub mod dict;
pub mod extensible;
pub mod format;
pub mod not;
@ -43,6 +44,7 @@ impl<'a> ValidationContext<'a> {
// Complex Structures
self.validate_object(&mut result)?;
self.validate_array(&mut result)?;
self.validate_dict(&mut result)?;
// Multipliers & Conditionals
if !self.validate_one_of(&mut result)? {

View File

@ -211,80 +211,8 @@ impl<'a> ValidationContext<'a> {
}
}
if let Some(compiled_pp) = self.schema.compiled_pattern_properties.get() {
for (compiled_re, sub_schema) in compiled_pp {
for (key, child_instance) in obj {
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)
}
_ => false,
};
let next_extensible = if is_ref { false } else { self.extensible };
let derived = self.derive(
sub_schema,
child_instance,
&new_path,
HashSet::new(),
next_extensible,
false,
Some(self.instance),
);
let item_res = derived.validate()?;
result.merge(item_res);
result.evaluated_keys.insert(key.to_string());
}
}
}
}
if let Some(ref additional_schema) = self.schema.additional_properties {
for (key, child_instance) in obj {
let mut locally_matched = false;
if let Some(props) = &self.schema.properties
&& props.contains_key(&key.to_string())
{
locally_matched = true;
}
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) {
locally_matched = true;
break;
}
}
}
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)
}
_ => false,
};
let next_extensible = if is_ref { false } else { self.extensible };
let derived = self.derive(
additional_schema,
child_instance,
&new_path,
HashSet::new(),
next_extensible,
false,
Some(self.instance),
);
let item_res = derived.validate()?;
result.merge(item_res);
result.evaluated_keys.insert(key.to_string());
}
}
}
if let Some(ref property_names) = self.schema.property_names {
for key in obj.keys() {

View File

@ -14,7 +14,6 @@ impl<'a> ValidationContext<'a> {
let conflicts = self.schema.type_.is_some()
|| self.schema.properties.is_some()
|| self.schema.required.is_some()
|| self.schema.additional_properties.is_some()
|| self.schema.items.is_some()
|| self.schema.cases.is_some()
|| self.schema.one_of.is_some()

View File

@ -42,6 +42,7 @@ impl<'a> ValidationContext<'a> {
code: "PATTERN_VIOLATED".to_string(),
values: Some(HashMap::from([
("pattern".to_string(), self.schema.pattern.clone().unwrap_or_default()),
("value".to_string(), s.to_string()),
])),
path: self.path.to_string(),
});
@ -54,6 +55,7 @@ impl<'a> ValidationContext<'a> {
code: "PATTERN_VIOLATED".to_string(),
values: Some(HashMap::from([
("pattern".to_string(), pattern.to_string()),
("value".to_string(), s.to_string()),
])),
path: self.path.to_string(),
});

View File

@ -1 +1 @@
1.0.163
1.0.166