Compare commits

..

21 Commits

Author SHA1 Message Date
95600551a7 version: 1.0.172 2026-07-07 12:45:00 -04:00
c18a8c1561 remove icon from sidebar 2026-07-07 12:44:49 -04:00
8ed588ca61 version: 1.0.171 2026-07-07 12:42:56 -04:00
edd0dd4763 jspg cleanup 2026-07-07 12:42:05 -04:00
abf1d12e43 test: mock matcher understands UNION existence reads; revive dead update-path mocks
parse_and_match_mocks matched mock.type against only the first FROM table —
always 'entity' for hierarchy existence reads — so every merger update-case
mock was silently inert and those cases snapshotted the create path. Now each
UNION arm is matched independently (a mock matching any arm returns once,
mirroring UNION dedup) and the type check accepts any FROM/JOIN table.

Regenerated the five revived cases (their snapshots now show real update /
no-op replace / archive semantics) and added the two previously untestable
probe-disagreement cases: same-row dedup (updates, no error) and id-vs-lookup
conflict (TOO_MANY_LOOKUP_ROWS).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 14:05:21 -04:00
a885713563 version: 1.0.170 2026-07-06 13:05:16 -04:00
d9b4f417f6 merger existence read: UNION the id and lk-lookup probes instead of OR-ing them across the hierarchy join — the OR is un-indexable and full-scans the subtype table on every child merge (one scan per relationship-edge write; surfaced by Castleberry onboarding); each UNION arm uses its own index (pk / lk_) and dedup preserves TOO_MANY_LOOKUP_ROWS semantics
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 20:52:29 -04:00
eae39f92d6 version: 1.0.169 2026-07-04 18:27:57 -04:00
2895f06074 page.personal is an opaque filter template (the 'mine' predicate), not a punc name — same read, scope-derived filter
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 18:27:51 -04:00
c1c16bc814 version: 1.0.168 2026-07-04 18:21:36 -04:00
5885552192 database: page carries 'personal' (the personal-scope sibling punc) and sidebar carries 'icon' — previously stripped by the whitelist
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 18:21:30 -04:00
dc033296d7 version: 1.0.167 2026-07-03 20:04:28 -04:00
03a871bc1a filter synthesis: named value types get their own synthesized, referenced filter
Supersedes the inline-structural approach (0d282cc): inlining erased the value
type's name, so identical nested shapes (weekly_hours at two paths) generated
duplicate leaf types downstream and Dart barrel exports collided. Now a named
non-table value type's filter is synthesized ONCE (like table-backed boundaries)
and property references point at it — mirroring how the entity side generates
one class per named type. Same filter-by-fields capability; laziness also
removes the structural-recursion hazard. A named type with no compilable
structure still gets no filter and is omitted rather than dangled.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 20:04:16 -04:00
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
58 changed files with 1808 additions and 5317 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

@ -72,8 +72,8 @@
{
"code": "CONTAINS_VIOLATED",
"values": {
"limit": "1",
"count": "0"
"count": "0",
"limit": "1"
},
"details": {
"path": "",
@ -268,23 +268,13 @@
{
"code": "CONTAINS_VIOLATED",
"values": {
"count": "0",
"limit": "1"
"limit": "1",
"count": "0"
},
"details": {
"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",
@ -431,8 +421,8 @@
{
"code": "CONTAINS_VIOLATED",
"values": {
"limit": "1",
"count": "0"
"count": "0",
"limit": "1"
},
"details": {
"path": "",
@ -453,8 +443,8 @@
{
"code": "MULTIPLE_OF_VIOLATED",
"values": {
"multiple_of": "2",
"value": "5"
"value": "5",
"multiple_of": "2"
},
"details": {
"path": "1",
@ -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

@ -62,8 +62,8 @@
"code": "EDGE_MISSING",
"values": {
"parent_type": "org",
"child_type": "user",
"property_name": "missing_users"
"property_name": "missing_users",
"child_type": "user"
},
"details": {
"path": "full.org/missing_users",
@ -152,8 +152,8 @@
"code": "EDGE_MISSING",
"values": {
"child_type": "child",
"property_name": "children",
"parent_type": "parent"
"parent_type": "parent",
"property_name": "children"
},
"details": {
"path": "full.parent/children",
@ -254,9 +254,9 @@
{
"code": "AMBIGUOUS_TYPE_RELATIONS",
"values": {
"parent_type": "invoice",
"child_type": "activity",
"property_name": "activities",
"parent_type": "invoice"
"property_name": "activities"
},
"details": {
"path": "full.invoice/activities",
@ -470,8 +470,8 @@
{
"code": "DATABASE_TYPE_PARSE_FAILED",
"values": {
"type": "failure",
"reason": "invalid type: sequence, expected a string"
"reason": "invalid type: sequence, expected a string",
"type": "failure"
},
"details": {
"context": "failure"

View File

@ -65,8 +65,8 @@
{
"code": "DEPENDENCY_MISSING",
"values": {
"required_property": "foo",
"property_name": "bar"
"property_name": "bar",
"required_property": "foo"
},
"details": {
"path": "",
@ -227,8 +227,8 @@
{
"code": "DEPENDENCY_MISSING",
"values": {
"property_name": "quux",
"required_property": "bar"
"required_property": "bar",
"property_name": "quux"
},
"details": {
"path": "",
@ -362,8 +362,8 @@
{
"code": "DEPENDENCY_MISSING",
"values": {
"required_property": "foo\rbar",
"property_name": "foo\nbar"
"property_name": "foo\nbar",
"required_property": "foo\rbar"
},
"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
}
},
{
@ -841,8 +829,8 @@
{
"code": "MIN_PROPERTIES_VIOLATED",
"values": {
"count": "2",
"limit": "4"
"limit": "4",
"count": "2"
},
"details": {
"path": "",

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": {
"pattern": "^[a-z]{3}$",
"value": "abcd"
},
"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": "",

View File

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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -106,8 +106,8 @@
{
"code": "CONTAINS_VIOLATED",
"values": {
"count": "2",
"limit": "1"
"limit": "1",
"count": "2"
},
"details": {
"path": "",
@ -144,8 +144,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": "MAX_LENGTH_VIOLATED",
"values": {
"count": "3",
"limit": "2"
"limit": "2",
"count": "3"
},
"details": {
"path": "",
@ -109,8 +109,8 @@
{
"code": "MAX_LENGTH_VIOLATED",
"values": {
"count": "3",
"limit": "2"
"limit": "2",
"count": "3"
},
"details": {
"path": "",

View File

@ -139,8 +139,8 @@
{
"code": "MAX_PROPERTIES_VIOLATED",
"values": {
"count": "3",
"limit": "2"
"limit": "2",
"count": "3"
},
"details": {
"path": "",
@ -190,8 +190,8 @@
{
"code": "MAX_PROPERTIES_VIOLATED",
"values": {
"count": "1",
"limit": "0"
"limit": "0",
"count": "1"
},
"details": {
"path": "",
@ -234,8 +234,8 @@
{
"code": "MAX_PROPERTIES_VIOLATED",
"values": {
"limit": "2",
"count": "3"
"count": "3",
"limit": "2"
},
"details": {
"path": "",

View File

@ -118,8 +118,8 @@
{
"code": "MAXIMUM_VIOLATED",
"values": {
"value": "300.5",
"limit": "300"
"limit": "300",
"value": "300.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": "",
@ -264,8 +264,8 @@
{
"code": "DEPENDENCY_MISSING",
"values": {
"required_property": "child_dep",
"property_name": "trigger"
"property_name": "trigger",
"required_property": "child_dep"
},
"details": {
"path": "",

View File

@ -1310,9 +1310,9 @@
[
"(SELECT to_jsonb(t1.*) || to_jsonb(t2.*) || to_jsonb(t3.*) || to_jsonb(t4.*)",
"FROM agreego.\"entity\" t1",
"JOIN agreego.\"organization\" t2 ON ",
"JOIN agreego.\"user\" t3 ON ",
"JOIN agreego.\"person\" t4 ON ",
"LEFT JOIN agreego.\"organization\" t2 ON t2.id = t1.id",
"LEFT JOIN agreego.\"user\" t3 ON t3.id = t1.id",
"LEFT JOIN agreego.\"person\" t4 ON t4.id = t1.id",
"WHERE",
" (\"first_name\" = 'LookupFirst'",
" AND \"last_name\" = 'LookupLast'",
@ -1463,12 +1463,18 @@
[
"(SELECT to_jsonb(t1.*) || to_jsonb(t2.*) || to_jsonb(t3.*) || to_jsonb(t4.*)",
"FROM agreego.\"entity\" t1",
"JOIN agreego.\"organization\" t2 ON ",
"JOIN agreego.\"user\" t3 ON ",
"JOIN agreego.\"person\" t4 ON ",
"LEFT JOIN agreego.\"organization\" t2 ON t2.id = t1.id",
"LEFT JOIN agreego.\"user\" t3 ON t3.id = t1.id",
"LEFT JOIN agreego.\"person\" t4 ON t4.id = t1.id",
"WHERE",
" t1.id = '{{uuid:data.id}}'",
" OR (\"first_name\" = 'LookupFirst'",
"UNION SELECT to_jsonb(t1.*) || to_jsonb(t2.*) || to_jsonb(t3.*) || to_jsonb(t4.*)",
"FROM agreego.\"entity\" t1",
"LEFT JOIN agreego.\"organization\" t2 ON t2.id = t1.id",
"LEFT JOIN agreego.\"user\" t3 ON t3.id = t1.id",
"LEFT JOIN agreego.\"person\" t4 ON t4.id = t1.id",
"WHERE",
" (\"first_name\" = 'LookupFirst'",
" AND \"last_name\" = 'LookupLast'",
" AND \"date_of_birth\" = '{{timestamp}}'",
" AND \"pronouns\" = 'they/them'))"
@ -1616,12 +1622,18 @@
[
"(SELECT to_jsonb(t1.*) || to_jsonb(t2.*) || to_jsonb(t3.*) || to_jsonb(t4.*)",
"FROM agreego.\"entity\" t1",
"JOIN agreego.\"organization\" t2 ON ",
"JOIN agreego.\"user\" t3 ON ",
"JOIN agreego.\"person\" t4 ON ",
"LEFT JOIN agreego.\"organization\" t2 ON t2.id = t1.id",
"LEFT JOIN agreego.\"user\" t3 ON t3.id = t1.id",
"LEFT JOIN agreego.\"person\" t4 ON t4.id = t1.id",
"WHERE",
" t1.id = '{{uuid:data.id}}'",
" OR (\"first_name\" = 'LookupFirst'",
"UNION SELECT to_jsonb(t1.*) || to_jsonb(t2.*) || to_jsonb(t3.*) || to_jsonb(t4.*)",
"FROM agreego.\"entity\" t1",
"LEFT JOIN agreego.\"organization\" t2 ON t2.id = t1.id",
"LEFT JOIN agreego.\"user\" t3 ON t3.id = t1.id",
"LEFT JOIN agreego.\"person\" t4 ON t4.id = t1.id",
"WHERE",
" (\"first_name\" = 'LookupFirst'",
" AND \"last_name\" = 'LookupLast'",
" AND \"date_of_birth\" = '{{timestamp}}'",
" AND \"pronouns\" = 'they/them'))"
@ -1759,9 +1771,9 @@
[
"(SELECT to_jsonb(t1.*) || to_jsonb(t2.*) || to_jsonb(t3.*) || to_jsonb(t4.*)",
"FROM agreego.\"entity\" t1",
"JOIN agreego.\"organization\" t2 ON ",
"JOIN agreego.\"user\" t3 ON ",
"JOIN agreego.\"person\" t4 ON ",
"LEFT JOIN agreego.\"organization\" t2 ON t2.id = t1.id",
"LEFT JOIN agreego.\"user\" t3 ON t3.id = t1.id",
"LEFT JOIN agreego.\"person\" t4 ON t4.id = t1.id",
"WHERE",
" t1.id = '{{uuid:mocks.0.id}}')"
],
@ -1882,9 +1894,9 @@
[
"(SELECT to_jsonb(t1.*) || to_jsonb(t2.*) || to_jsonb(t3.*) || to_jsonb(t4.*)",
"FROM agreego.\"entity\" t1",
"JOIN agreego.\"organization\" t2 ON ",
"JOIN agreego.\"user\" t3 ON ",
"JOIN agreego.\"person\" t4 ON ",
"LEFT JOIN agreego.\"organization\" t2 ON t2.id = t1.id",
"LEFT JOIN agreego.\"user\" t3 ON t3.id = t1.id",
"LEFT JOIN agreego.\"person\" t4 ON t4.id = t1.id",
"WHERE",
" t1.id = '123')"
],
@ -2221,10 +2233,14 @@
[
"(SELECT to_jsonb(t1.*) || to_jsonb(t2.*)",
"FROM agreego.\"entity\" t1",
"JOIN agreego.\"order\" t2 ON ",
"LEFT JOIN agreego.\"order\" t2 ON t2.id = t1.id",
"WHERE",
" t1.id = 'abc'",
" OR (\"id\" = 'abc'))"
"UNION SELECT to_jsonb(t1.*) || to_jsonb(t2.*)",
"FROM agreego.\"entity\" t1",
"LEFT JOIN agreego.\"order\" t2 ON t2.id = t1.id",
"WHERE",
" (\"id\" = 'abc'))"
],
[
"INSERT INTO agreego.\"entity\" (",
@ -3024,9 +3040,9 @@
[
"(SELECT to_jsonb(t1.*) || to_jsonb(t2.*) || to_jsonb(t3.*) || to_jsonb(t4.*)",
"FROM agreego.\"entity\" t1",
"JOIN agreego.\"organization\" t2 ON ",
"JOIN agreego.\"user\" t3 ON ",
"JOIN agreego.\"person\" t4 ON ",
"LEFT JOIN agreego.\"organization\" t2 ON t2.id = t1.id",
"LEFT JOIN agreego.\"user\" t3 ON t3.id = t1.id",
"LEFT JOIN agreego.\"person\" t4 ON t4.id = t1.id",
"WHERE",
" t1.id = 'abc-archived')"
],
@ -3377,7 +3393,7 @@
[
"(SELECT to_jsonb(t1.*) || to_jsonb(t2.*)",
"FROM agreego.\"entity\" t1",
"JOIN agreego.\"order_line\" t2 ON ",
"LEFT JOIN agreego.\"order_line\" t2 ON t2.id = t1.id",
"WHERE",
" t1.id = '{{uuid:data.lines.0.id}}')"
],
@ -3501,10 +3517,14 @@
[
"(SELECT to_jsonb(t1.*) || to_jsonb(t2.*)",
"FROM agreego.\"entity\" t1",
"JOIN agreego.\"invoice\" t2 ON ",
"LEFT JOIN agreego.\"invoice\" t2 ON t2.id = t1.id",
"WHERE",
" t1.id = '{{uuid:data.id}}'",
" OR (\"id\" = '{{uuid:data.id}}'))"
"UNION SELECT to_jsonb(t1.*) || to_jsonb(t2.*)",
"FROM agreego.\"entity\" t1",
"LEFT JOIN agreego.\"invoice\" t2 ON t2.id = t1.id",
"WHERE",
" (\"id\" = '{{uuid:data.id}}'))"
],
[
"INSERT INTO agreego.\"entity\" (",
@ -3618,7 +3638,7 @@
[
"(SELECT to_jsonb(t1.*) || to_jsonb(t2.*)",
"FROM agreego.\"entity\" t1",
"JOIN agreego.\"account\" t2 ON ",
"LEFT JOIN agreego.\"account\" t2 ON t2.id = t1.id",
"WHERE",
" t1.id = '{{uuid:data.id}}')"
],

View File

@ -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": "",
@ -218,8 +218,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": "",
@ -357,8 +357,8 @@
{
"code": "CONTAINS_VIOLATED",
"values": {
"count": "0",
"limit": "2"
"limit": "2",
"count": "0"
},
"details": {
"path": "",
@ -407,8 +407,8 @@
{
"code": "CONTAINS_VIOLATED",
"values": {
"limit": "2",
"count": "3"
"count": "3",
"limit": "2"
},
"details": {
"path": "",
@ -487,8 +487,8 @@
{
"code": "CONTAINS_VIOLATED",
"values": {
"count": "1",
"limit": "3"
"limit": "3",
"count": "1"
},
"details": {
"path": "",
@ -538,8 +538,8 @@
{
"code": "CONTAINS_VIOLATED",
"values": {
"limit": "3",
"count": "2"
"count": "2",
"limit": "3"
},
"details": {
"path": "",
@ -549,8 +549,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
}
}
}
@ -79,8 +78,7 @@
"name": "minItems_1_0",
"schemas": {
"minItems_1_0": {
"minItems": 1,
"extensible": true
"minItems": 1
}
}
}

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

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

View File

@ -100,8 +100,8 @@
{
"code": "MULTIPLE_OF_VIOLATED",
"values": {
"multiple_of": "1.5",
"value": "35"
"value": "35",
"multiple_of": "1.5"
},
"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

@ -156,8 +156,8 @@
{
"code": "MAXIMUM_VIOLATED",
"values": {
"value": "60",
"limit": "50"
"limit": "50",
"value": "60"
},
"details": {
"path": "max",

View File

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

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

@ -148,8 +148,8 @@
{
"code": "NO_FAMILY_MATCH",
"values": {
"discriminator": "type",
"value": "alien"
"value": "alien",
"discriminator": "type"
},
"details": {
"path": "",
@ -284,8 +284,8 @@
{
"code": "NO_FAMILY_MATCH",
"values": {
"discriminator": "type",
"value": "bot"
"value": "bot",
"discriminator": "type"
},
"details": {
"path": "",

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

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

View File

@ -1293,8 +1293,8 @@
" 'age', person_3.age",
" )",
" FROM agreego.entity entity_1",
" JOIN agreego.organization organization_2 ON organization_2.id = entity_1.id",
" JOIN agreego.person person_3 ON person_3.id = organization_2.id",
" JOIN agreego.organization organization_2",
" JOIN agreego.person person_3",
" WHERE",
" NOT entity_1.archived",
"))))"
@ -1336,15 +1336,15 @@
" 'number', phone_number_8.number",
" )",
" FROM agreego.entity entity_7",
" JOIN agreego.phone_number phone_number_8 ON phone_number_8.id = entity_7.id",
" JOIN agreego.phone_number phone_number_8",
" WHERE",
" NOT entity_7.archived",
" AND relationship_5.target_id = entity_7.id",
" )",
" )), '[]'::jsonb)",
" FROM agreego.entity entity_4",
" JOIN agreego.relationship relationship_5 ON relationship_5.id = entity_4.id",
" JOIN agreego.contact contact_6 ON contact_6.id = relationship_5.id",
" JOIN agreego.relationship relationship_5",
" JOIN agreego.contact contact_6",
" WHERE",
" NOT entity_4.archived",
" AND relationship_5.target_type = 'phone_number'",
@ -1366,15 +1366,15 @@
" 'address', email_address_13.address",
" )",
" FROM agreego.entity entity_12",
" JOIN agreego.email_address email_address_13 ON email_address_13.id = entity_12.id",
" JOIN agreego.email_address email_address_13",
" WHERE",
" NOT entity_12.archived",
" AND relationship_10.target_id = entity_12.id",
" )",
" )), '[]'::jsonb)",
" FROM agreego.entity entity_9",
" JOIN agreego.relationship relationship_10 ON relationship_10.id = entity_9.id",
" JOIN agreego.contact contact_11 ON contact_11.id = relationship_10.id",
" JOIN agreego.relationship relationship_10",
" JOIN agreego.contact contact_11",
" WHERE",
" NOT entity_9.archived",
" AND relationship_10.target_type = 'email_address'",
@ -1396,15 +1396,15 @@
" 'city', address_18.city",
" )",
" FROM agreego.entity entity_17",
" JOIN agreego.address address_18 ON address_18.id = entity_17.id",
" JOIN agreego.address address_18",
" WHERE",
" NOT entity_17.archived",
" AND relationship_15.target_id = entity_17.id",
" )",
" )), '[]'::jsonb)",
" FROM agreego.entity entity_14",
" JOIN agreego.relationship relationship_15 ON relationship_15.id = entity_14.id",
" JOIN agreego.contact contact_16 ON contact_16.id = relationship_15.id",
" JOIN agreego.relationship relationship_15",
" JOIN agreego.contact contact_16",
" WHERE",
" NOT entity_14.archived",
" AND relationship_15.target_type = 'address'",
@ -1427,7 +1427,7 @@
" 'number', phone_number_23.number",
" )",
" FROM agreego.entity entity_22",
" JOIN agreego.phone_number phone_number_23 ON phone_number_23.id = entity_22.id",
" JOIN agreego.phone_number phone_number_23",
" WHERE",
" NOT entity_22.archived",
" AND relationship_20.target_id = entity_22.id",
@ -1441,7 +1441,7 @@
" 'address', email_address_25.address",
" )",
" FROM agreego.entity entity_24",
" JOIN agreego.email_address email_address_25 ON email_address_25.id = entity_24.id",
" JOIN agreego.email_address email_address_25",
" WHERE",
" NOT entity_24.archived",
" AND relationship_20.target_id = entity_24.id",
@ -1455,7 +1455,7 @@
" 'city', address_27.city",
" )",
" FROM agreego.entity entity_26",
" JOIN agreego.address address_27 ON address_27.id = entity_26.id",
" JOIN agreego.address address_27",
" WHERE",
" NOT entity_26.archived",
" AND relationship_20.target_id = entity_26.id",
@ -1464,16 +1464,16 @@
" END",
" )), '[]'::jsonb)",
" FROM agreego.entity entity_19",
" JOIN agreego.relationship relationship_20 ON relationship_20.id = entity_19.id",
" JOIN agreego.contact contact_21 ON contact_21.id = relationship_20.id",
" JOIN agreego.relationship relationship_20",
" JOIN agreego.contact contact_21",
" WHERE",
" NOT entity_19.archived",
" AND relationship_20.source_id = entity_1.id",
" )",
" )",
" FROM agreego.entity entity_1",
" JOIN agreego.organization organization_2 ON organization_2.id = entity_1.id",
" JOIN agreego.person person_3 ON person_3.id = organization_2.id",
" JOIN agreego.organization organization_2",
" JOIN agreego.person person_3",
" WHERE",
" NOT entity_1.archived",
"))))"
@ -1586,7 +1586,7 @@
" 'number', phone_number_8.number",
" )",
" FROM agreego.entity entity_7",
" JOIN agreego.phone_number phone_number_8 ON phone_number_8.id = entity_7.id",
" JOIN agreego.phone_number phone_number_8",
" WHERE",
" NOT entity_7.archived",
" AND phone_number_8.number ILIKE $32 #>> '{}'",
@ -1594,8 +1594,8 @@
" )",
" )), '[]'::jsonb)",
" FROM agreego.entity entity_4",
" JOIN agreego.relationship relationship_5 ON relationship_5.id = entity_4.id",
" JOIN agreego.contact contact_6 ON contact_6.id = relationship_5.id",
" JOIN agreego.relationship relationship_5",
" JOIN agreego.contact contact_6",
" WHERE",
" NOT entity_4.archived",
" AND relationship_5.target_type = 'phone_number'",
@ -1617,15 +1617,15 @@
" 'address', email_address_13.address",
" )",
" FROM agreego.entity entity_12",
" JOIN agreego.email_address email_address_13 ON email_address_13.id = entity_12.id",
" JOIN agreego.email_address email_address_13",
" WHERE",
" NOT entity_12.archived",
" AND relationship_10.target_id = entity_12.id",
" )",
" )), '[]'::jsonb)",
" FROM agreego.entity entity_9",
" JOIN agreego.relationship relationship_10 ON relationship_10.id = entity_9.id",
" JOIN agreego.contact contact_11 ON contact_11.id = relationship_10.id",
" JOIN agreego.relationship relationship_10",
" JOIN agreego.contact contact_11",
" WHERE",
" NOT entity_9.archived",
" AND relationship_10.target_type = 'email_address'",
@ -1647,15 +1647,15 @@
" 'city', address_18.city",
" )",
" FROM agreego.entity entity_17",
" JOIN agreego.address address_18 ON address_18.id = entity_17.id",
" JOIN agreego.address address_18",
" WHERE",
" NOT entity_17.archived",
" AND relationship_15.target_id = entity_17.id",
" )",
" )), '[]'::jsonb)",
" FROM agreego.entity entity_14",
" JOIN agreego.relationship relationship_15 ON relationship_15.id = entity_14.id",
" JOIN agreego.contact contact_16 ON contact_16.id = relationship_15.id",
" JOIN agreego.relationship relationship_15",
" JOIN agreego.contact contact_16",
" WHERE",
" NOT entity_14.archived",
" AND relationship_15.target_type = 'address'",
@ -1678,7 +1678,7 @@
" 'number', phone_number_23.number",
" )",
" FROM agreego.entity entity_22",
" JOIN agreego.phone_number phone_number_23 ON phone_number_23.id = entity_22.id",
" JOIN agreego.phone_number phone_number_23",
" WHERE",
" NOT entity_22.archived",
" AND relationship_20.target_id = entity_22.id",
@ -1692,7 +1692,7 @@
" 'address', email_address_25.address",
" )",
" FROM agreego.entity entity_24",
" JOIN agreego.email_address email_address_25 ON email_address_25.id = entity_24.id",
" JOIN agreego.email_address email_address_25",
" WHERE",
" NOT entity_24.archived",
" AND relationship_20.target_id = entity_24.id",
@ -1706,7 +1706,7 @@
" 'city', address_27.city",
" )",
" FROM agreego.entity entity_26",
" JOIN agreego.address address_27 ON address_27.id = entity_26.id",
" JOIN agreego.address address_27",
" WHERE",
" NOT entity_26.archived",
" AND relationship_20.target_id = entity_26.id",
@ -1715,8 +1715,8 @@
" END",
" )), '[]'::jsonb)",
" FROM agreego.entity entity_19",
" JOIN agreego.relationship relationship_20 ON relationship_20.id = entity_19.id",
" JOIN agreego.contact contact_21 ON contact_21.id = relationship_20.id",
" JOIN agreego.relationship relationship_20",
" JOIN agreego.contact contact_21",
" WHERE",
" NOT entity_19.archived",
" AND contact_21.is_primary = ($11 #>> '{}')::BOOLEAN",
@ -1724,8 +1724,8 @@
" )",
" )",
" FROM agreego.entity entity_1",
" JOIN agreego.organization organization_2 ON organization_2.id = entity_1.id",
" JOIN agreego.person person_3 ON person_3.id = organization_2.id",
" JOIN agreego.organization organization_2",
" JOIN agreego.person person_3",
" WHERE",
" NOT entity_1.archived",
" AND person_3.age = ($1 #>> '{}')::NUMERIC",
@ -1787,15 +1787,15 @@
" 'address', email_address_5.address",
" )",
" FROM agreego.entity entity_4",
" JOIN agreego.email_address email_address_5 ON email_address_5.id = entity_4.id",
" JOIN agreego.email_address email_address_5",
" WHERE",
" NOT entity_4.archived",
" AND relationship_2.target_id = entity_4.id",
" )",
" )), '[]'::jsonb)",
" FROM agreego.entity entity_1",
" JOIN agreego.relationship relationship_2 ON relationship_2.id = entity_1.id",
" JOIN agreego.contact contact_3 ON contact_3.id = relationship_2.id",
" JOIN agreego.relationship relationship_2",
" JOIN agreego.contact contact_3",
" WHERE",
" NOT entity_1.archived",
" AND relationship_2.target_type = 'email_address'",
@ -1832,8 +1832,8 @@
" 'age', person_5.age",
" )",
" FROM agreego.entity entity_3",
" JOIN agreego.organization organization_4 ON organization_4.id = entity_3.id",
" JOIN agreego.person person_5 ON person_5.id = organization_4.id",
" JOIN agreego.organization organization_4",
" JOIN agreego.person person_5",
" WHERE",
" NOT entity_3.archived",
" AND order_2.customer_id = person_5.id",
@ -1849,14 +1849,14 @@
" 'price', order_line_7.price",
" )), '[]'::jsonb)",
" FROM agreego.entity entity_6",
" JOIN agreego.order_line order_line_7 ON order_line_7.id = entity_6.id",
" JOIN agreego.order_line order_line_7",
" WHERE",
" NOT entity_6.archived",
" AND order_line_7.order_id = order_2.id",
" )",
" )",
" FROM agreego.entity entity_1",
" JOIN agreego.order order_2 ON order_2.id = entity_1.id",
" JOIN agreego.order order_2",
" WHERE",
" NOT entity_1.archived",
"))))"
@ -1881,7 +1881,7 @@
" 'name', organization_2.name",
" )",
" FROM agreego.entity entity_1",
" JOIN agreego.organization organization_2 ON organization_2.id = entity_1.id",
" JOIN agreego.organization organization_2",
" WHERE",
" NOT entity_1.archived",
"))))"
@ -1910,8 +1910,8 @@
" 'role', bot_5.role",
" )",
" FROM agreego.entity entity_3",
" JOIN agreego.organization organization_4 ON organization_4.id = entity_3.id",
" JOIN agreego.bot bot_5 ON bot_5.id = organization_4.id",
" JOIN agreego.organization organization_4",
" JOIN agreego.bot bot_5",
" WHERE",
" NOT entity_3.archived",
" AND entity_3.id = entity_1.id",
@ -1925,7 +1925,7 @@
" 'name', organization_7.name",
" )",
" FROM agreego.entity entity_6",
" JOIN agreego.organization organization_7 ON organization_7.id = entity_6.id",
" JOIN agreego.organization organization_7",
" WHERE",
" NOT entity_6.archived",
" AND entity_6.id = entity_1.id",
@ -1942,8 +1942,8 @@
" 'age', person_10.age",
" )",
" FROM agreego.entity entity_8",
" JOIN agreego.organization organization_9 ON organization_9.id = entity_8.id",
" JOIN agreego.person person_10 ON person_10.id = organization_9.id",
" JOIN agreego.organization organization_9",
" JOIN agreego.person person_10",
" WHERE",
" NOT entity_8.archived",
" AND entity_8.id = entity_1.id",
@ -1951,7 +1951,7 @@
" ELSE NULL",
" END), '[]'::jsonb)",
" FROM agreego.entity entity_1",
" JOIN agreego.organization organization_2 ON organization_2.id = entity_1.id",
" JOIN agreego.organization organization_2",
" WHERE",
" NOT entity_1.archived",
"))))"
@ -1979,8 +1979,8 @@
" 'token', bot_5.token",
" )",
" FROM agreego.entity entity_3",
" JOIN agreego.organization organization_4 ON organization_4.id = entity_3.id",
" JOIN agreego.bot bot_5 ON bot_5.id = organization_4.id",
" JOIN agreego.organization organization_4",
" JOIN agreego.bot bot_5",
" WHERE",
" NOT entity_3.archived",
" AND entity_3.id = entity_1.id",
@ -1996,8 +1996,8 @@
" 'last_name', person_8.last_name",
" )",
" FROM agreego.entity entity_6",
" JOIN agreego.organization organization_7 ON organization_7.id = entity_6.id",
" JOIN agreego.person person_8 ON person_8.id = organization_7.id",
" JOIN agreego.organization organization_7",
" JOIN agreego.person person_8",
" WHERE",
" NOT entity_6.archived",
" AND entity_6.id = entity_1.id",
@ -2005,7 +2005,7 @@
" ELSE NULL",
" END",
" FROM agreego.entity entity_1",
" JOIN agreego.organization organization_2 ON organization_2.id = entity_1.id",
" JOIN agreego.organization organization_2",
" WHERE",
" NOT entity_1.archived",
"))))"
@ -2049,15 +2049,15 @@
" 'number', phone_number_10.number",
" )",
" FROM agreego.entity entity_9",
" JOIN agreego.phone_number phone_number_10 ON phone_number_10.id = entity_9.id",
" JOIN agreego.phone_number phone_number_10",
" WHERE",
" NOT entity_9.archived",
" AND relationship_7.target_id = entity_9.id",
" )",
" )), '[]'::jsonb)",
" FROM agreego.entity entity_6",
" JOIN agreego.relationship relationship_7 ON relationship_7.id = entity_6.id",
" JOIN agreego.contact contact_8 ON contact_8.id = relationship_7.id",
" JOIN agreego.relationship relationship_7",
" JOIN agreego.contact contact_8",
" WHERE",
" NOT entity_6.archived",
" AND relationship_7.target_type = 'phone_number'",
@ -2079,15 +2079,15 @@
" 'address', email_address_15.address",
" )",
" FROM agreego.entity entity_14",
" JOIN agreego.email_address email_address_15 ON email_address_15.id = entity_14.id",
" JOIN agreego.email_address email_address_15",
" WHERE",
" NOT entity_14.archived",
" AND relationship_12.target_id = entity_14.id",
" )",
" )), '[]'::jsonb)",
" FROM agreego.entity entity_11",
" JOIN agreego.relationship relationship_12 ON relationship_12.id = entity_11.id",
" JOIN agreego.contact contact_13 ON contact_13.id = relationship_12.id",
" JOIN agreego.relationship relationship_12",
" JOIN agreego.contact contact_13",
" WHERE",
" NOT entity_11.archived",
" AND relationship_12.target_type = 'email_address'",
@ -2109,15 +2109,15 @@
" 'city', address_20.city",
" )",
" FROM agreego.entity entity_19",
" JOIN agreego.address address_20 ON address_20.id = entity_19.id",
" JOIN agreego.address address_20",
" WHERE",
" NOT entity_19.archived",
" AND relationship_17.target_id = entity_19.id",
" )",
" )), '[]'::jsonb)",
" FROM agreego.entity entity_16",
" JOIN agreego.relationship relationship_17 ON relationship_17.id = entity_16.id",
" JOIN agreego.contact contact_18 ON contact_18.id = relationship_17.id",
" JOIN agreego.relationship relationship_17",
" JOIN agreego.contact contact_18",
" WHERE",
" NOT entity_16.archived",
" AND relationship_17.target_type = 'address'",
@ -2140,7 +2140,7 @@
" 'number', phone_number_25.number",
" )",
" FROM agreego.entity entity_24",
" JOIN agreego.phone_number phone_number_25 ON phone_number_25.id = entity_24.id",
" JOIN agreego.phone_number phone_number_25",
" WHERE",
" NOT entity_24.archived",
" AND relationship_22.target_id = entity_24.id",
@ -2154,7 +2154,7 @@
" 'address', email_address_27.address",
" )",
" FROM agreego.entity entity_26",
" JOIN agreego.email_address email_address_27 ON email_address_27.id = entity_26.id",
" JOIN agreego.email_address email_address_27",
" WHERE",
" NOT entity_26.archived",
" AND relationship_22.target_id = entity_26.id",
@ -2168,7 +2168,7 @@
" 'city', address_29.city",
" )",
" FROM agreego.entity entity_28",
" JOIN agreego.address address_29 ON address_29.id = entity_28.id",
" JOIN agreego.address address_29",
" WHERE",
" NOT entity_28.archived",
" AND relationship_22.target_id = entity_28.id",
@ -2177,16 +2177,16 @@
" END",
" )), '[]'::jsonb)",
" FROM agreego.entity entity_21",
" JOIN agreego.relationship relationship_22 ON relationship_22.id = entity_21.id",
" JOIN agreego.contact contact_23 ON contact_23.id = relationship_22.id",
" JOIN agreego.relationship relationship_22",
" JOIN agreego.contact contact_23",
" WHERE",
" NOT entity_21.archived",
" AND relationship_22.source_id = entity_3.id",
" )",
" )",
" FROM agreego.entity entity_3",
" JOIN agreego.organization organization_4 ON organization_4.id = entity_3.id",
" JOIN agreego.person person_5 ON person_5.id = organization_4.id",
" JOIN agreego.organization organization_4",
" JOIN agreego.person person_5",
" WHERE",
" NOT entity_3.archived",
" AND entity_3.id = entity_1.id",
@ -2194,7 +2194,7 @@
" ELSE NULL",
" END",
" FROM agreego.entity entity_1",
" JOIN agreego.organization organization_2 ON organization_2.id = entity_1.id",
" JOIN agreego.organization organization_2",
" WHERE",
" NOT entity_1.archived",
"))))"
@ -2230,15 +2230,15 @@
" 'age', person_5.age",
" )",
" FROM agreego.entity entity_3",
" JOIN agreego.organization organization_4 ON organization_4.id = entity_3.id",
" JOIN agreego.person person_5 ON person_5.id = organization_4.id",
" JOIN agreego.organization organization_4",
" JOIN agreego.person person_5",
" WHERE",
" NOT entity_3.archived",
" AND order_2.customer_id = person_5.id",
" )",
" )), '[]'::jsonb)",
" FROM agreego.entity entity_1",
" JOIN agreego.order order_2 ON order_2.id = entity_1.id",
" JOIN agreego.order order_2",
" WHERE",
" NOT entity_1.archived",
"))))"
@ -2273,7 +2273,7 @@
" ELSE NULL",
" END), '[]'::jsonb)",
" FROM agreego.entity entity_1",
" JOIN agreego.widget widget_2 ON widget_2.id = entity_1.id",
" JOIN agreego.widget widget_2",
" WHERE",
" NOT entity_1.archived",
"))))"
@ -2306,7 +2306,7 @@
" 'price', invoice_line_4.price",
" )), '[]'::jsonb)",
" FROM agreego.entity entity_3",
" JOIN agreego.invoice_line invoice_line_4 ON invoice_line_4.id = entity_3.id",
" JOIN agreego.invoice_line invoice_line_4",
" WHERE",
" NOT entity_3.archived",
" AND invoice_line_4.invoice_id = invoice_2.id",
@ -2317,7 +2317,7 @@
" 'metadata_nested_lines', invoice_2.metadata_nested_lines",
" )",
" FROM agreego.entity entity_1",
" JOIN agreego.invoice invoice_2 ON invoice_2.id = entity_1.id",
" JOIN agreego.invoice invoice_2",
" WHERE",
" NOT entity_1.archived",
"))))"
@ -2344,7 +2344,7 @@
" 'card_number', account_2.card_number",
" )",
" FROM agreego.entity entity_1",
" JOIN agreego.account account_2 ON account_2.id = entity_1.id",
" JOIN agreego.account account_2",
" WHERE",
" NOT entity_1.archived",
"))))"
@ -2381,8 +2381,8 @@
" 'role', bot_7.role",
" )",
" FROM agreego.entity entity_5",
" JOIN agreego.organization organization_6 ON organization_6.id = entity_5.id",
" JOIN agreego.bot bot_7 ON bot_7.id = organization_6.id",
" JOIN agreego.organization organization_6",
" JOIN agreego.bot bot_7",
" WHERE",
" NOT entity_5.archived",
" AND entity_5.id = entity_3.id",
@ -2396,7 +2396,7 @@
" 'name', organization_9.name",
" )",
" FROM agreego.entity entity_8",
" JOIN agreego.organization organization_9 ON organization_9.id = entity_8.id",
" JOIN agreego.organization organization_9",
" WHERE",
" NOT entity_8.archived",
" AND entity_8.id = entity_3.id",
@ -2413,8 +2413,8 @@
" 'age', person_12.age",
" )",
" FROM agreego.entity entity_10",
" JOIN agreego.organization organization_11 ON organization_11.id = entity_10.id",
" JOIN agreego.person person_12 ON person_12.id = organization_11.id",
" JOIN agreego.organization organization_11",
" JOIN agreego.person person_12",
" WHERE",
" NOT entity_10.archived",
" AND entity_10.id = entity_3.id",
@ -2422,14 +2422,14 @@
" ELSE NULL",
" END",
" FROM agreego.entity entity_3",
" JOIN agreego.organization organization_4 ON organization_4.id = entity_3.id",
" JOIN agreego.organization organization_4",
" WHERE",
" NOT entity_3.archived",
" AND order_2.counterparty_id = entity_3.id",
" )",
" )), '[]'::jsonb)",
" FROM agreego.entity entity_1",
" JOIN agreego.order order_2 ON order_2.id = entity_1.id",
" JOIN agreego.order order_2",
" WHERE",
" NOT entity_1.archived",
" AND order_2.counterparty_type IN ('bot', 'organization', 'person')",

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": {

2
flows

Submodule flows updated: 0d9bd8644e...89748a246e

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

@ -151,6 +151,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);

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

@ -1,6 +1,7 @@
use crate::database::action::Action;
use indexmap::IndexMap;
use serde::{Deserialize, Serialize};
use serde_json::Value;
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[serde(default)]
@ -13,6 +14,11 @@ pub struct Page {
pub sidebar: Option<Sidebar>,
#[serde(skip_serializing_if = "Option::is_none")]
pub actions: Option<IndexMap<String, Action>>,
/// The personal-scope filter template ("one surface, two scopes"): the
/// "mine" predicate the page merges into its filter when the app is in
/// personal scope, e.g. {"reserved_by": "$me"}. Opaque to the engine.
#[serde(skip_serializing_if = "Option::is_none")]
pub personal: Option<Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]

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()

View File

@ -744,9 +744,15 @@ impl Merger {
return Ok(None);
}
let where_clause = format!("WHERE {}", where_parts.join(" OR "));
let final_sql = format!("{} {}", fetch_sql_template, where_clause);
let final_sql = if where_parts.len() == 1 {
format!("{} WHERE {}", fetch_sql_template, where_parts[0])
} else {
where_parts
.iter()
.map(|p| format!("{} WHERE {}", fetch_sql_template, p))
.collect::<Vec<_>>()
.join(" UNION ")
};
let fetched = match self.db.query(&final_sql, None) {
Ok(Value::Array(table)) => {

File diff suppressed because it is too large Load Diff

View File

@ -179,14 +179,35 @@ impl SqlFormatter {
}
fn format_query(&mut self, query: &Query) {
match &*query.body {
self.format_set_expr(&query.body);
}
fn format_set_expr(&mut self, set_expr: &SetExpr) {
match set_expr {
SetExpr::Select(select) => self.format_select(select),
SetExpr::Query(inner_query) => {
self.push_str("(");
self.format_query(inner_query);
self.push_str(")");
}
_ => self.push_str(&query.to_string()),
SetExpr::SetOperation { op, left, right, set_quantifier } => {
self.format_set_expr(left);
let op_str = match op {
sqlparser::ast::SetOperator::Union => "UNION",
sqlparser::ast::SetOperator::Intersect => "INTERSECT",
sqlparser::ast::SetOperator::Except => "EXCEPT",
_ => "UNION",
};
let quant_str = match set_quantifier {
sqlparser::ast::SetQuantifier::All => " ALL",
sqlparser::ast::SetQuantifier::Distinct => " DISTINCT",
sqlparser::ast::SetQuantifier::None => "",
_ => "",
};
self.push_line(&format!("{}{} ", op_str, quant_str));
self.format_set_expr(right);
}
_ => self.push_str(&set_expr.to_string()),
}
}
@ -236,20 +257,22 @@ impl SqlFormatter {
}
fn format_join(&mut self, join: &Join) {
let op = match &join.join_operator {
let op_str = match &join.join_operator {
JoinOperator::Inner(_) => "JOIN",
JoinOperator::LeftOuter(_) => "LEFT JOIN",
JoinOperator::Left(_) => "LEFT JOIN",
JoinOperator::Right(_) => "RIGHT JOIN",
_ => "JOIN",
};
self.push_str(&format!("{} {} ON ", op, join.relation));
self.push_str(&format!("{} {}", op_str, join.relation));
match &join.join_operator {
JoinOperator::Inner(JoinConstraint::On(expr)) => self.format_expr(expr),
JoinOperator::LeftOuter(JoinConstraint::On(expr)) => self.format_expr(expr),
JoinOperator::Join(JoinConstraint::On(expr)) => self.format_expr(expr),
_ => {
println!("FALLBACK JOIN OP: {:?}", join.join_operator);
JoinOperator::Inner(JoinConstraint::On(expr))
| JoinOperator::Left(JoinConstraint::On(expr))
| JoinOperator::Right(JoinConstraint::On(expr)) => {
self.push_str(" ON ");
self.format_expr(expr);
}
_ => {}
}
}

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.172