Compare commits

..

13 Commits

Author SHA1 Message Date
93d1315f0b version: 1.0.114 2026-04-14 06:09:30 -04:00
e407adf6a1 version: 1.0.113 2026-04-14 06:09:14 -04:00
0b4607b7d4 fixed and tested subschema promotions for beat processing 2026-04-14 06:09:00 -04:00
a53e89df52 version: 1.0.112 2026-04-13 22:48:27 -04:00
57bbe11013 version: 1.0.111 2026-04-13 22:48:12 -04:00
386b7ad012 docs(jspg): document scalar tuple routing boundary 2026-04-13 22:47:21 -04:00
a91460b390 cleanup 2026-04-13 22:44:18 -04:00
0017c598e1 chore: JSPG Engine tuple decoupling and core routing optimizations 2026-04-13 22:41:32 -04:00
665a821bf9 version: 1.0.110 2026-04-10 01:06:16 -04:00
be78af1507 more tests 2026-04-10 01:06:02 -04:00
3cca5ef2d5 checkpoint 2026-04-09 19:55:35 -04:00
5f45df6c11 checkpoint 2026-04-09 18:39:52 -04:00
9387152859 version: 1.0.109 2026-04-08 13:09:04 -04:00
67 changed files with 7258 additions and 5880 deletions

View File

@ -37,12 +37,12 @@ These functions operate on the global `GLOBAL_JSPG` engine instance and provide
JSPG augments standard JSON Schema 2020-12 to provide an opinionated, strict, and highly ergonomic Object-Oriented paradigm. Developers defining Punc Data Models should follow these conventions.
### Types of Types
* **Table-Backed (Entity Types)**: Primarily defined in root type schemas. These represent physical Postgres tables.
* They absolutely **require** an `$id`.
* **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.
* Can inherit other entity types to build lineage (e.g. `person` -> `organization` -> `entity`).
* Can inherit other entity types to build lineage (e.g. `person` -> `organization` -> `entity`) natively using the `type` property.
* **Field-Backed (JSONB Bubbles)**: These are shapes that live entirely inside a Postgres JSONB column without being tied to a top-level table constraint.
* **Global `$id` Promotion**: Utilizing explicit `$id` declarations promotes the schema to the Global Registry. This effectively creates strictly-typed code-generator universes (e.g., generating an `InvoiceNotificationMetadata` Dart class) operating cleanly inside unstructured Postgres JSONB columns.
* **Global Schema Registration**: Roots must be attached to the top-level keys mapped from the `types`, `enums`, or `puncs` database tables.
* They can re-use the standard `type` discriminator locally for `oneOf` polymorphism without conflicting with global Postgres Table constraints.
### Discriminators & The Dot Convention (A.B)
@ -50,30 +50,28 @@ In Punc, polymorphic targets like explicit tagged unions or STI (Single Table In
**The 2-Tier Paradigm**: The system inherently prevents "God Tables" by restricting routing to exactly two dimensions, guaranteeing absolute $O(1)$ lookups without ambiguity:
1. **Vertical Routing (`type`)**: Identifies the specific Postgres Table lineage (e.g. `person` vs `organization`).
2. **Horizontal Routing (`kind.type`)**: Natively evaluates Single Table Inheritance. The runtime dynamically concatenates `$kind.$type` to yield the namespace-protected schema `$id` (e.g. `light.person`), maintaining collision-free schema registration.
2. **Horizontal Routing (`kind.type`)**: Natively evaluates Single Table Inheritance. The runtime dynamically concatenates `$kind.$type` to yield the namespace-protected schema key (e.g. `light.person`), maintaining collision-free schema registration.
Therefore, any schema that participates in polymorphic discrimination MUST explicitly define its discriminator properties natively inside its `properties` block. However, to stay DRY and maintain flexible APIs, you **DO NOT** need to hardcode `const` values, nor should you add them to your `required` array. The Punc engine treats `type` and `kind` as **magic properties**.
**Magic Validation Constraints**:
* **Dynamically Required**: The system inherently drives the need for their requirement. The Validator dynamically expects the discriminators and structurally bubbles `MISSING_TYPE` ultimata ONLY when a polymorphic router (`$family` / `oneOf`) dynamically requires them to resolve a path. You never manually put them in the JSON schema `required` block.
* **Implicit Resolution**: When wrapped in `$family` or `oneOf`, the polymorphic router can mathematically parse the schema `$id` (e.g. `light.person`) and natively validate that `type` equals `"person"` and `kind` equals `"light"`, bubbling `CONST_VIOLATED` if they mismatch, all without you ever hardcoding `const` limitations.
* **Implicit Resolution**: When wrapped in `$family` or `oneOf`, the polymorphic router can mathematically parse the schema key (e.g. `light.person`) and natively validate that `type` equals `"person"` and `kind` equals `"light"`, bubbling `CONST_VIOLATED` if they mismatch, all without you ever hardcoding `const` limitations.
* **Generator Explicitness**: Because Postgres is the Single Source of Truth, forcing the explicit definition in `properties` initially guarantees the downstream Dart/Go code generators observe the fields and can cleanly serialize them dynamically back to the server.
For example, a schema representing `$id: "light.person"` must natively define its own structural boundaries:
For example, a schema registered under the exact key `"light.person"` inside the database registry must natively define its own structural boundaries:
```json
{
"$id": "light.person",
"type": "person",
"properties": {
"type": { "type": "string" },
"kind": { "type": "string" }
},
"required": ["type", "kind"]
}
}
```
* **The Object Contract (Presence)**: The Object enforces its own structural integrity mechanically. Standard JSON Validation natively ensures `type` and `kind` are present, bubbling `REQUIRED_FIELD_MISSING` organically if omitted.
* **The Dynamic Values (`db.types`)**: Because the `type` and `kind` properties technically exist, the Punc engine dynamically intercepts them during `validate_object`. It mathematically parses the schema `$id` (e.g. `light.person`) and natively validates that `type` equals `"person"` (or a valid descendant in `db.types`) and `kind` equals `"light"`, bubbling `CONST_VIOLATED` if they mismatch.
* **The Object Contract (Presence)**: The Object enforces its own structural integrity mechanically. Standard JSON Validation natively ensures `type` and `kind` are dynamically present as expected.
* **The Dynamic Values (`db.types`)**: Because the `type` and `kind` properties technically exist, the Punc engine dynamically intercepts them during `validate_object`. It mathematically parses the schema key (e.g. `light.person`) and natively validates that `type` equals `"person"` (or a valid descendant in `db.types`) and `kind` equals `"light"`, bubbling `CONST_VIOLATED` if they mismatch.
* **The Routing Contract**: When wrapped in `$family` or `oneOf`, the polymorphic router can execute Lightning Fast $O(1)$ fast-paths by reading the payload's `type`/`kind` identifiers, and gracefully fallback to standard structural failure if omitted.
### Composition & Inheritance (The `type` keyword)
@ -84,11 +82,26 @@ Punc completely abandons the standard JSON Schema `$ref` keyword. Instead, it ov
* **Strict Array Constraint**: To explicitly prevent mathematically ambiguous Multiple Inheritance, a `type` array is strictly constrained to at most **ONE** Custom Object Pointer. Defining `"type": ["person", "organization"]` will intentionally trigger a fatal database compilation error natively instructing developers to build a proper tagged union (`oneOf`) instead.
### Polymorphism (`$family` and `oneOf`)
Polymorphism is how an object boundary can dynamically take on entirely different shapes based on the payload provided at runtime.
* **`$family` (Target-Based Polymorphism)**: An explicit Punc compiler macro instructing the database compiler to dynamically search its internal `db.descendants` registry and find all physical schemas that mathematically resolve to the target.
* *Across Tables (Vertical)*: If `$family: entity` is requested, the payload's `type` field acts as the discriminator, dynamically routing to standard variations like `organization` or `person` spanning multiple Postgres tables.
* *Single Table (Horizontal)*: If `$family: widget` is requested, the router explicitly evaluates the Dot Convention dynamically. If the payload possesses `"type": "widget"` and `"kind": "stock"`, the router mathematically resolves to the string `"stock.widget"` and routes exclusively to that explicit `JSPG` schema.
* **`oneOf` (Strict Tagged Unions)**: A hardcoded array of JSON Schema candidate options. Punc strictly bans mathematical "Union of Sets" evaluation. Every `oneOf` candidate item MUST either be a pure primitive (`{ "type": "null" }`) or a user-defined Object Pointer providing a specific discriminator (e.g., `{ "type": "invoice_metadata" }`). This ensures validations remain pure $O(1)$ fast-paths and allows the Dart generator to emit pristine `sealed classes`.
Polymorphism is how an object boundary can dynamically take on entirely different shapes based on the payload provided at runtime. Punc utilizes the static database metadata generated from Postgres (`db.types`) to enforce these boundaries deterministically, rather than relying on ambiguous tree-traversals.
* **`$family` (Target-Based Polymorphism)**: An explicit Punc compiler macro instructing the engine to resolve dynamic options against the registered database `types` variations or its inner schema registry. It uses the exact physical constraints of the database to build SQL and validation routes.
* **Scenario A: Global Tables (Vertical Routing)**
* *Setup*: `{ "$family": "organization" }`
* *Execution*: The engine queries `db.types.get("organization").variations` and finds `["bot", "organization", "person"]`. Because organizations are structurally table-backed, the `$family` automatically uses `type` as the discriminator.
* *Options*: `bot` -> `bot`, `person` -> `person`, `organization` -> `organization`.
* **Scenario B: Prefixed Tables (Vertical Projection)**
* *Setup*: `{ "$family": "light.organization" }`
* *Execution*: The engine sees the prefix `light.` and base `organization`. It queries `db.types.get("organization").variations` and dynamically prepends the prefix to discover the relevant UI schemas.
* *Options*: `person` -> `light.person`, `organization` -> `light.organization`. (If a projection like `light.bot` does not exist in `db.schemas`, it is safely ignored).
* **Scenario C: Single Table Inheritance (Horizontal Routing)**
* *Setup*: `{ "$family": "widget" }` (Where `widget` is a table type but has no external variations).
* *Execution*: The engine queries `db.types.get("widget").variations` and finds only `["widget"]`. Since it lacks table inheritance, it is treated as STI. The engine scans the specific, confined `schemas` array directly under `db.types.get("widget")` for any registered key terminating in the base `.widget` (e.g., `stock.widget`). The `$family` automatically uses `kind` as the discriminator.
* *Options*: `stock` -> `stock.widget`, `tasks` -> `tasks.widget`.
* **`oneOf` (Strict Tagged Unions)**: A hardcoded list of candidate schemas. Unlike `$family` which relies on global DB metadata, `oneOf` forces pure mathematical structural evaluation of the provided candidates. It strictly bans typical JSON Schema "Union of Sets" fallback searches. Every candidate MUST possess a mathematically unique discriminator payload to allow $O(1)$ routing.
* **Disjoint Types**: `oneOf: [{ "type": "person" }, { "type": "widget" }]`. The engine succeeds because the native `type` acts as a unique discriminator (`"person"` vs `"widget"`).
* **STI Types**: `oneOf: [{ "type": "heavy.person" }, { "type": "light.person" }]`. The engine succeeds. Even though both share `"type": "person"`, their explicit discriminator is `kind` (`"heavy"` vs `"light"`), ensuring unique $O(1)$ fast-paths.
* **Conflicting Types**: `oneOf: [{ "type": "person" }, { "type": "light.person" }]`. The engine **fails compilation natively**. Both schemas evaluate to `"type": "person"` and neither provides a disjoint `kind` constraint, making them mathematically ambiguous and impossible to route in $O(1)$ time.
### Conditionals (`cases`)
Standard JSON Schema forces developers to write deeply nested `allOf` -> `if` -> `properties` blocks just to execute conditional branching. **JSPG completely abandons `allOf` and this practice.** For declarative business logic and structural mutations conditionally based upon property bounds, use the top-level `cases` array.
@ -97,7 +110,6 @@ It evaluates as an **Independent Declarative Rules Engine**. Every `Case` block
```json
{
"$id": "save_external_account",
"cases": [
{
"when": {
@ -156,10 +168,11 @@ When compiling nested object graphs or arrays, the JSPG engine must dynamically
5. **Implicit Base Fallback (1:M)**: If no explicit prefix matches, and M:M deduction fails, the compiler filters for exactly one remaining relation with a `null` prefix (e.g. `fk_invoice_line_invoice` -> `prefix: null`). A `null` prefix mathematically denotes the core structural parent-child ownership edge and is used safely as a fallback.
6. **Deterministic Abort**: If the engine exhausts all deduction pathways and the edge remains ambiguous, it explicitly aborts schema compilation (`returns None`) rather than silently generating unpredictable SQL.
### Ad-Hoc Schema Promotion
To seamlessly support deeply nested, inline Object definitions that don't declare an explicit `$id`, JSPG aggressively promotes them to standalone topological entities during the database compilation phase.
* **Hash Generation:** While evaluating the unified graph, if the compiler enters an `Object` or `Array` structure completely lacking an `$id`, it dynamically calculates a localized hash alias representing exactly its structural constraints.
* **Promotion:** This inline chunk is mathematically elevated to its own `$id` in the `db.schemas` cache registry. This guarantees that $O(1)$ WebSockets or isolated queries can natively target any arbitrary sub-object of a massive database topology directly without recursively re-parsing its parent's AST block every read.
### Subschema Promotion
To seamlessly support deeply nested Object and Array structures, JSPG aggressively promotes them to standalone topological entities during the database compilation phase.
* **Path Generation:** While evaluating a unified graph originating from a base `types`, `enums`, or `puncs` key, the compiler tracks its exact path descent into nested objects and arrays. It dynamically calculates a localized alias string by appending a `/` pathing syntax (e.g., `base_schema_key/nested/path`) representing exactly its structural constraints.
* **Promotion:** This nested subschema chunk is mathematically elevated to its own independent key in the `db.schemas` cache registry using its full path. This guarantees that $O(1)$ WebSockets or isolated queries can natively target any arbitrary nested sub-object of a massive database topology directly without recursively re-parsing its parent's AST block every read. Note that you cannot use the `type` attribute to statically inherit from these automatically promoted subschemas.
* **Primitive Confinement:** Purely scalar or primitive branches (like `oneOf: [{type: "string"}, {type: "null"}]`) bypass global topological promotion. They are evaluated directly within the execution engine via isolated Tuple Indexes to explicitly protect the global DB Registry and Go Mixer from memory bloat.
---
@ -177,6 +190,7 @@ JSPG implements specific extensions to the Draft 2020-12 standard to support the
* **Discriminator Fast Paths & Extraction**: When executing a polymorphic node (`oneOf` or `$family`), the engine statically analyzes the incoming JSON payload for the literal `type` and `kind` string coordinates. It routes the evaluation specifically to matching candidates in $O(1)$ while returning `MISSING_TYPE` ultimata directly.
* **Missing Type Ultimatum**: If an entity logically requires a discriminator and the JSON payload omits it, JSPG short-circuits branch execution entirely, bubbling a single, perfectly-pathed `MISSING_TYPE` error back to the UI natively to prevent confusing cascading failures.
* **Golden Match Context**: When exactly one structural candidate perfectly maps a discriminator, the Validator exclusively cascades that specific structural error context directly to the user, stripping away all noise generated by other parallel schemas.
* **Topological Array Pathing**: Instead of relying on explicit `$id` references or injected properties, array iteration paths are dynamically typed based on their compiler boundary constraints. If the array's `items` schema resolves to a topological table-backed entity (e.g., inheriting via a `$family` macro tracked in the global DB catalog), the array locks paths and derives element indexes from their actual UUID paths (`array/widget-1/name`), natively enforcing database continuity. If evaluating isolated ad-hoc JSONB elements, strict numeric indexing is enforced natively (`array/1/name`) preventing synthetic payload manipulation.
---
@ -225,7 +239,7 @@ The Queryer transforms Postgres into a pre-compiled Semantic Query Engine, desig
* **Type Casting**: Safely resolves dynamic combinations by casting values instantly into the physical database types mapped in the schema (e.g. parsing `uuid` bindings to `::uuid`, formatting DateTimes to `::timestamptz`, and numbers to `::numeric`).
* **Polymorphic SQL Generation (`$family`)**: Compiles `$family` properties by analyzing the **Physical Database Variations**, *not* the schema descendants.
* **The Dot Convention**: When a schema requests `$family: "target.schema"`, the compiler extracts the base type (e.g. `schema`) and looks up its Physical Table definition.
* **Multi-Table Branching**: If the Physical Table is a parent to other tables (e.g. `organization` has variations `["organization", "bot", "person"]`), the compiler generates a dynamic `CASE WHEN type = '...' THEN ...` query, expanding into `JOIN`s for each variation.
* **Multi-Table Branching**: If the Physical Table is a parent to other tables (e.g. `organization` has variations `["organization", "bot", "person"]`), the compiler generates a dynamic `CASE WHEN type = '...' THEN ...` query, expanding into sub-queries for each variation. To ensure safe resolution, the compiler dynamically evaluates correlation boundaries: it attempts standard Relational Edge discovery first. If no explicit relational edge exists (indicating pure Table Inheritance rather than a standard foreign-key graph relationship), it safely invokes a **Table Parity Fallback**. This generates an explicit ID correlation constraint (`AND inner.id = outer.id`), perfectly binding the structural variations back to the parent row to eliminate Cartesian products.
* **Single-Table Bypass**: If the Physical Table is a leaf node with only one variation (e.g. `person` has variations `["person"]`), the compiler cleanly bypasses `CASE` generation and compiles a simple `SELECT` across the base table, as all schema extensions (e.g. `light.person`, `full.person`) are guaranteed to reside in the exact same physical row.
---

152
append_test.py Normal file
View File

@ -0,0 +1,152 @@
import json
path = "fixtures/database.json"
with open(path, "r") as f:
data = json.load(f)
new_test = {
"description": "Schema Promotion Accuracy Test - -- One Database to Rule Them All --",
"database": {
"puncs": [],
"enums": [],
"relations": [],
"types": [
{
"id": "t1",
"type": "type",
"name": "person",
"module": "core",
"source": "person",
"hierarchy": ["person"],
"variations": ["person", "student"],
"schemas": {
"full.person": {
"type": "object",
"properties": {
"type": {"type": "string"},
"name": {"type": "string"},
"email": {
"$family": "email_address"
},
"generic_bubble": {
"type": "some_bubble"
},
"ad_hoc_bubble": {
"type": "some_bubble",
"properties": {
"extra_inline_feature": {"type": "string"}
}
},
"tags": {
"type": "array",
"items": {"type": "string"}
},
"standard_relations": {
"type": "array",
"items": {"type": "contact"}
},
"extended_relations": {
"type": "array",
"items": {
"type": "contact",
"properties": {
"target": {"type": "email_address"}
}
}
}
}
},
"student.person": {
"type": "object",
"properties": {
"type": {"type": "string"},
"kind": {"type": "string"},
"school": {"type": "string"}
}
}
}
},
{
"id": "t2",
"type": "type",
"name": "email_address",
"module": "core",
"source": "email_address",
"hierarchy": ["email_address"],
"variations": ["email_address"],
"schemas": {
"light.email_address": {
"type": "object",
"properties": {
"address": {"type": "string"}
}
}
}
},
{
"id": "t3",
"type": "type",
"name": "contact",
"module": "core",
"source": "contact",
"hierarchy": ["contact"],
"variations": ["contact"],
"schemas": {
"full.contact": {
"type": "object",
"properties": {
"id": {"type": "string"}
}
}
}
},
{
"id": "t4",
"type": "type",
"name": "some_bubble",
"module": "core",
"source": "some_bubble",
"hierarchy": ["some_bubble"],
"variations": ["some_bubble"],
"schemas": {
"some_bubble": {
"type": "object",
"properties": {
"bubble_prop": {"type": "string"}
}
}
}
}
]
},
"tests": [
{
"description": "Assert exact topological schema promotion paths",
"action": "compile",
"expect": {
"success": True,
"schemas": [
"ad_hoc_bubble",
"email_address",
"extended_relations",
"extended_relations/target",
"full.contact",
"full.person",
"full.person/ad_hoc_bubble",
"full.person/extended_relations",
"full.person/extended_relations/target",
"light.email_address",
"person",
"some_bubble",
"student.person"
]
}
}
]
}
data.append(new_test)
with open(path, "w") as f:
json.dump(data, f, indent=2)

43
debug.log Normal file

File diff suppressed because one or more lines are too long

34
fix_everything.py Normal file
View File

@ -0,0 +1,34 @@
import json
path = "fixtures/database.json"
with open(path, "r") as f:
data = json.load(f)
test_case = data[-1]
# Get full.person object properties
props = test_case["database"]["types"][0]["schemas"]["full.person"]["properties"]
# Find extended_relations target and add properties!
target_ref = props["extended_relations"]["items"]["properties"]["target"]
target_ref["properties"] = {
"extra_3rd_level": {"type": "string"}
}
# The target is now an ad-hoc composition itself!
# We expect `full.person/extended_relations/target` to be globally promoted.
test_case["tests"][0]["expect"]["schemas"] = [
"full.contact",
"full.person",
"full.person/ad_hoc_bubble",
"full.person/extended_relations",
"full.person/extended_relations/target", # BOOM! Right here, 3 levels deep!
"light.email_address",
"some_bubble",
"student.person"
]
with open(path, "w") as f:
json.dump(data, f, indent=2)

22
fix_expect.py Normal file
View File

@ -0,0 +1,22 @@
import json
path = "fixtures/database.json"
with open(path, "r") as f:
data = json.load(f)
test_case = data[-1]
test_case["tests"][0]["expect"]["schemas"] = [
"full.contact",
"full.person",
"full.person/ad_hoc_bubble",
"full.person/extended_relations",
"full.person/extended_relations/items",
"light.email_address",
"some_bubble",
"student.person"
]
with open(path, "w") as f:
json.dump(data, f, indent=2)

63
fix_test.py Normal file
View File

@ -0,0 +1,63 @@
import json
path = "fixtures/database.json"
with open(path, "r") as f:
data = json.load(f)
test_case = data[-1]
test_case["database"]["relations"] = [
{
"id": "r1",
"type": "relation",
"constraint": "fk_person_email",
"source_type": "person", "source_columns": ["email_id"],
"destination_type": "email_address", "destination_columns": ["id"],
"prefix": "email"
},
{
"id": "r2",
"type": "relation",
"constraint": "fk_person_ad_hoc_bubble",
"source_type": "person", "source_columns": ["ad_hoc_bubble_id"],
"destination_type": "some_bubble", "destination_columns": ["id"],
"prefix": "ad_hoc_bubble"
},
{
"id": "r3",
"type": "relation",
"constraint": "fk_person_generic_bubble",
"source_type": "person", "source_columns": ["generic_bubble_id"],
"destination_type": "some_bubble", "destination_columns": ["id"],
"prefix": "generic_bubble"
},
{
"id": "r4",
"type": "relation",
"constraint": "fk_person_extended_relations",
"source_type": "contact", "source_columns": ["source_id"],
"destination_type": "person", "destination_columns": ["id"],
"prefix": "extended_relations"
},
{
"id": "r5",
"type": "relation",
"constraint": "fk_person_standard_relations",
"source_type": "contact", "source_columns": ["source_id_2"],
"destination_type": "person", "destination_columns": ["id"],
"prefix": "standard_relations"
},
{
"id": "r6",
"type": "relation",
"constraint": "fk_contact_target",
"source_type": "contact", "source_columns": ["target_id"],
"destination_type": "email_address", "destination_columns": ["id"],
"prefix": "target"
}
]
with open(path, "w") as f:
json.dump(data, f, indent=2)

View File

@ -2,9 +2,8 @@
{
"description": "additionalProperties validates properties not matched by properties",
"database": {
"schemas": [
{
"$id": "schema1",
"schemas": {
"schema1": {
"properties": {
"foo": {
"type": "string"
@ -17,7 +16,7 @@
"type": "boolean"
}
}
]
}
},
"tests": [
{
@ -62,8 +61,8 @@
{
"description": "extensible: true with additionalProperties still validates structure",
"database": {
"schemas": [
{
"schemas": {
"additionalProperties_1_0": {
"properties": {
"foo": {
"type": "string"
@ -72,10 +71,9 @@
"extensible": true,
"additionalProperties": {
"type": "integer"
},
"$id": "additionalProperties_1_0"
}
}
]
}
},
"tests": [
{
@ -108,9 +106,8 @@
{
"description": "complex additionalProperties with object and array items",
"database": {
"schemas": [
{
"$id": "schema3",
"schemas": {
"schema3": {
"properties": {
"type": {
"type": "string"
@ -123,7 +120,7 @@
}
}
}
]
}
},
"tests": [
{

View File

@ -2,11 +2,9 @@
{
"description": "boolean schema 'true'",
"database": {
"schemas": [
{
"$id": "booleanSchema_0_0"
}
]
"schemas": {
"booleanSchema_0_0": {}
}
},
"tests": [
{
@ -99,12 +97,11 @@
{
"description": "boolean schema 'false'",
"database": {
"schemas": [
{
"not": {},
"$id": "booleanSchema_1_0"
"schemas": {
"booleanSchema_1_0": {
"not": {}
}
]
}
},
"tests": [
{

View File

@ -2,87 +2,193 @@
{
"description": "Multi-Paradigm Declarative Cases",
"database": {
"schemas": [
{
"$id": "parallel_rules",
"schemas": {
"parallel_rules": {
"type": "object",
"properties": {
"status": { "type": "string" },
"kind": { "type": "string" }
},
"cases": [
{
"when": { "properties": { "status": {"const": "unverified"} }, "required": ["status"] },
"then": {
"properties": {
"amount_1": {"type": "number"},
"amount_2": {"type": "number"}
},
"required": ["amount_1", "amount_2"]
}
"status": {
"type": "string"
},
{
"when": { "properties": { "kind": {"const": "credit"} }, "required": ["kind"] },
"then": {
"properties": {
"cvv": {"type": "number"}
},
"required": ["cvv"]
}
"kind": {
"type": "string"
}
]
},
{
"$id": "mutually_exclusive",
"type": "object",
"properties": {
"type": { "type": "string" }
},
"cases": [
{
"when": { "properties": { "type": {"const": "A"} }, "required": ["type"] },
"then": {
"properties": { "field_a": {"type": "number"} },
"required": ["field_a"]
}
},
{
"when": { "properties": { "type": {"const": "B"} }, "required": ["type"] },
"then": {
"properties": { "field_b": {"type": "number"} },
"required": ["field_b"]
"when": {
"properties": {
"status": {
"const": "unverified"
}
},
"required": [
"status"
]
},
"else": {
"properties": { "fallback_b": {"type": "number"} },
"required": ["fallback_b"]
"then": {
"properties": {
"amount_1": {
"type": "number"
},
"amount_2": {
"type": "number"
}
},
"required": [
"amount_1",
"amount_2"
]
}
},
{
"when": {
"properties": {
"kind": {
"const": "credit"
}
},
"required": [
"kind"
]
},
"then": {
"properties": {
"cvv": {
"type": "number"
}
},
"required": [
"cvv"
]
}
}
]
},
{
"$id": "nested_fallbacks",
"mutually_exclusive": {
"type": "object",
"properties": {
"tier": { "type": "string" }
"type": {
"type": "string"
}
},
"cases": [
{
"when": { "properties": { "tier": {"const": "1"} }, "required": ["tier"] },
"then": {
"properties": { "basic": {"type": "number"} },
"required": ["basic"]
"when": {
"properties": {
"type": {
"const": "A"
}
},
"required": [
"type"
]
},
"then": {
"properties": {
"field_a": {
"type": "number"
}
},
"required": [
"field_a"
]
}
},
{
"when": {
"properties": {
"type": {
"const": "B"
}
},
"required": [
"type"
]
},
"then": {
"properties": {
"field_b": {
"type": "number"
}
},
"required": [
"field_b"
]
},
"else": {
"properties": {
"fallback_b": {
"type": "number"
}
},
"required": [
"fallback_b"
]
}
}
]
},
"nested_fallbacks": {
"type": "object",
"properties": {
"tier": {
"type": "string"
}
},
"cases": [
{
"when": {
"properties": {
"tier": {
"const": "1"
}
},
"required": [
"tier"
]
},
"then": {
"properties": {
"basic": {
"type": "number"
}
},
"required": [
"basic"
]
},
"else": {
"cases": [
{
"when": { "properties": { "tier": {"const": "2"} }, "required": ["tier"] },
"then": {
"properties": { "standard": {"type": "number"} },
"required": ["standard"]
"when": {
"properties": {
"tier": {
"const": "2"
}
},
"required": [
"tier"
]
},
"else": {
"properties": { "premium": {"type": "number"} },
"required": ["premium"]
"then": {
"properties": {
"standard": {
"type": "number"
}
},
"required": [
"standard"
]
},
"else": {
"properties": {
"premium": {
"type": "number"
}
},
"required": [
"premium"
]
}
}
]
@ -90,94 +196,159 @@
}
]
},
{
"$id": "missing_when",
"missing_when": {
"type": "object",
"cases": [
{
"else": {
"properties": { "unconditional": {"type": "number"} },
"required": ["unconditional"]
"else": {
"properties": {
"unconditional": {
"type": "number"
}
},
"required": [
"unconditional"
]
}
}
]
}
]
}
},
"tests": [
{
"description": "Fires only the first rule successfully",
"data": { "status": "unverified", "amount_1": 1, "amount_2": 2 },
"data": {
"status": "unverified",
"amount_1": 1,
"amount_2": 2
},
"schema_id": "parallel_rules",
"action": "validate",
"expect": { "success": true }
"expect": {
"success": true
}
},
{
"description": "Fires both independent parallel rules flawlessly",
"data": { "status": "unverified", "kind": "credit", "amount_1": 1, "amount_2": 2, "cvv": 123 },
"data": {
"status": "unverified",
"kind": "credit",
"amount_1": 1,
"amount_2": 2,
"cvv": 123
},
"schema_id": "parallel_rules",
"action": "validate",
"expect": { "success": true }
"expect": {
"success": true
}
},
{
"description": "Catches errors triggered concurrently by multiple independent blocked rules",
"data": { "status": "unverified", "kind": "credit" },
"data": {
"status": "unverified",
"kind": "credit"
},
"schema_id": "parallel_rules",
"action": "validate",
"expect": {
"success": false,
"errors": [
{ "code": "REQUIRED_FIELD_MISSING", "details": { "path": "amount_1" } },
{ "code": "REQUIRED_FIELD_MISSING", "details": { "path": "amount_2" } },
{ "code": "REQUIRED_FIELD_MISSING", "details": { "path": "cvv" } }
{
"code": "REQUIRED_FIELD_MISSING",
"details": {
"path": "amount_1"
}
},
{
"code": "REQUIRED_FIELD_MISSING",
"details": {
"path": "amount_2"
}
},
{
"code": "REQUIRED_FIELD_MISSING",
"details": {
"path": "cvv"
}
}
]
}
},
{
"description": "STRICT_PROPERTY_VIOLATION throws if an un-triggered then property is submitted",
"data": { "status": "verified", "cvv": 123 },
"data": {
"status": "verified",
"cvv": 123
},
"schema_id": "parallel_rules",
"action": "validate",
"expect": {
"success": false,
"errors": [
{ "code": "STRICT_PROPERTY_VIOLATION", "details": { "path": "cvv" } }
{
"code": "STRICT_PROPERTY_VIOLATION",
"details": {
"path": "cvv"
}
}
]
}
},
{
"description": "Successfully routes mutually exclusive properties seamlessly",
"data": { "type": "A", "field_a": 1, "fallback_b": 2 },
"data": {
"type": "A",
"field_a": 1,
"fallback_b": 2
},
"schema_id": "mutually_exclusive",
"action": "validate",
"expect": { "success": true }
"expect": {
"success": true
}
},
{
"description": "Nested fallbacks execute seamlessly",
"data": { "tier": "3", "premium": 1 },
"data": {
"tier": "3",
"premium": 1
},
"schema_id": "nested_fallbacks",
"action": "validate",
"expect": { "success": true }
"expect": {
"success": true
}
},
{
"description": "A case without a when executes its else indiscriminately",
"data": { "unconditional": 1 },
"data": {
"unconditional": 1
},
"schema_id": "missing_when",
"action": "validate",
"expect": { "success": true }
"expect": {
"success": true
}
},
{
"description": "A case without a when throws if else unconditionally requires field",
"data": { },
"data": {},
"schema_id": "missing_when",
"action": "validate",
"expect": {
"success": false,
"errors": [
{ "code": "REQUIRED_FIELD_MISSING", "details": { "path": "unconditional" } }
{
"code": "REQUIRED_FIELD_MISSING",
"details": {
"path": "unconditional"
}
}
]
}
}
]
}
]
]

View File

@ -2,12 +2,11 @@
{
"description": "const validation",
"database": {
"schemas": [
{
"const": 2,
"$id": "const_0_0"
"schemas": {
"const_0_0": {
"const": 2
}
]
}
},
"tests": [
{
@ -42,8 +41,8 @@
{
"description": "const with object",
"database": {
"schemas": [
{
"schemas": {
"const_1_0": {
"const": {
"foo": "bar",
"baz": "bax"
@ -51,10 +50,9 @@
"properties": {
"foo": {},
"baz": {}
},
"$id": "const_1_0"
}
}
]
}
},
"tests": [
{
@ -109,16 +107,15 @@
{
"description": "const with array",
"database": {
"schemas": [
{
"schemas": {
"const_2_0": {
"const": [
{
"foo": "bar"
}
],
"$id": "const_2_0"
]
}
]
}
},
"tests": [
{
@ -163,12 +160,11 @@
{
"description": "const with null",
"database": {
"schemas": [
{
"const": null,
"$id": "const_3_0"
"schemas": {
"const_3_0": {
"const": null
}
]
}
},
"tests": [
{
@ -194,12 +190,11 @@
{
"description": "const with false does not match 0",
"database": {
"schemas": [
{
"const": false,
"$id": "const_4_0"
"schemas": {
"const_4_0": {
"const": false
}
]
}
},
"tests": [
{
@ -234,12 +229,11 @@
{
"description": "const with true does not match 1",
"database": {
"schemas": [
{
"const": true,
"$id": "const_5_0"
"schemas": {
"const_5_0": {
"const": true
}
]
}
},
"tests": [
{
@ -274,14 +268,13 @@
{
"description": "const with [false] does not match [0]",
"database": {
"schemas": [
{
"schemas": {
"const_6_0": {
"const": [
false
],
"$id": "const_6_0"
]
}
]
}
},
"tests": [
{
@ -322,14 +315,13 @@
{
"description": "const with [true] does not match [1]",
"database": {
"schemas": [
{
"schemas": {
"const_7_0": {
"const": [
true
],
"$id": "const_7_0"
]
}
]
}
},
"tests": [
{
@ -370,14 +362,13 @@
{
"description": "const with {\"a\": false} does not match {\"a\": 0}",
"database": {
"schemas": [
{
"schemas": {
"const_8_0": {
"const": {
"a": false
},
"$id": "const_8_0"
}
}
]
}
},
"tests": [
{
@ -418,14 +409,13 @@
{
"description": "const with {\"a\": true} does not match {\"a\": 1}",
"database": {
"schemas": [
{
"schemas": {
"const_9_0": {
"const": {
"a": true
},
"$id": "const_9_0"
}
}
]
}
},
"tests": [
{
@ -466,12 +456,11 @@
{
"description": "const with 0 does not match other zero-like types",
"database": {
"schemas": [
{
"const": 0,
"$id": "const_10_0"
"schemas": {
"const_10_0": {
"const": 0
}
]
}
},
"tests": [
{
@ -533,12 +522,11 @@
{
"description": "const with 1 does not match true",
"database": {
"schemas": [
{
"const": 1,
"$id": "const_11_0"
"schemas": {
"const_11_0": {
"const": 1
}
]
}
},
"tests": [
{
@ -573,12 +561,11 @@
{
"description": "const with -2.0 matches integer and float types",
"database": {
"schemas": [
{
"const": -2,
"$id": "const_12_0"
"schemas": {
"const_12_0": {
"const": -2
}
]
}
},
"tests": [
{
@ -631,12 +618,11 @@
{
"description": "float and integers are equal up to 64-bit representation limits",
"database": {
"schemas": [
{
"const": 9007199254740992,
"$id": "const_13_0"
"schemas": {
"const_13_0": {
"const": 9007199254740992
}
]
}
},
"tests": [
{
@ -680,12 +666,11 @@
{
"description": "nul characters in strings",
"database": {
"schemas": [
{
"const": "hello\u0000there",
"$id": "const_14_0"
"schemas": {
"const_14_0": {
"const": "hello\u0000there"
}
]
}
},
"tests": [
{
@ -711,13 +696,12 @@
{
"description": "characters with the same visual representation but different codepoint",
"database": {
"schemas": [
{
"schemas": {
"const_15_0": {
"const": "μ",
"$comment": "U+03BC",
"$id": "const_15_0"
"$comment": "U+03BC"
}
]
}
},
"tests": [
{
@ -745,13 +729,12 @@
{
"description": "characters with the same visual representation, but different number of codepoints",
"database": {
"schemas": [
{
"schemas": {
"const_16_0": {
"const": "ä",
"$comment": "U+00E4",
"$id": "const_16_0"
"$comment": "U+00E4"
}
]
}
},
"tests": [
{
@ -779,15 +762,14 @@
{
"description": "extensible: true allows extra properties in const object match",
"database": {
"schemas": [
{
"schemas": {
"const_17_0": {
"const": {
"a": 1
},
"extensible": true,
"$id": "const_17_0"
"extensible": true
}
]
}
},
"tests": [
{

View File

@ -2,15 +2,14 @@
{
"description": "contains keyword validation",
"database": {
"schemas": [
{
"schemas": {
"contains_0_0": {
"contains": {
"minimum": 5
},
"items": true,
"$id": "contains_0_0"
"items": true
}
]
}
},
"tests": [
{
@ -89,15 +88,14 @@
{
"description": "contains keyword with const keyword",
"database": {
"schemas": [
{
"schemas": {
"contains_1_0": {
"contains": {
"const": 5
},
"items": true,
"$id": "contains_1_0"
"items": true
}
]
}
},
"tests": [
{
@ -146,12 +144,11 @@
{
"description": "contains keyword with boolean schema true",
"database": {
"schemas": [
{
"contains": true,
"$id": "contains_2_0"
"schemas": {
"contains_2_0": {
"contains": true
}
]
}
},
"tests": [
{
@ -179,12 +176,11 @@
{
"description": "contains keyword with boolean schema false",
"database": {
"schemas": [
{
"contains": false,
"$id": "contains_3_0"
"schemas": {
"contains_3_0": {
"contains": false
}
]
}
},
"tests": [
{
@ -221,17 +217,16 @@
{
"description": "items + contains",
"database": {
"schemas": [
{
"schemas": {
"contains_4_0": {
"items": {
"multipleOf": 2
},
"contains": {
"multipleOf": 3
},
"$id": "contains_4_0"
}
}
]
}
},
"tests": [
{
@ -289,15 +284,14 @@
{
"description": "contains with false if subschema",
"database": {
"schemas": [
{
"schemas": {
"contains_5_0": {
"contains": {
"if": false,
"else": true
},
"$id": "contains_5_0"
}
}
]
}
},
"tests": [
{
@ -325,14 +319,13 @@
{
"description": "contains with null instance elements",
"database": {
"schemas": [
{
"schemas": {
"contains_6_0": {
"contains": {
"type": "null"
},
"$id": "contains_6_0"
}
}
]
}
},
"tests": [
{
@ -351,15 +344,14 @@
{
"description": "extensible: true allows non-matching items in contains",
"database": {
"schemas": [
{
"schemas": {
"contains_7_0": {
"contains": {
"const": 1
},
"extensible": true,
"$id": "contains_7_0"
"extensible": true
}
]
}
},
"tests": [
{
@ -379,14 +371,13 @@
{
"description": "strict by default: non-matching items in contains are invalid",
"database": {
"schemas": [
{
"schemas": {
"contains_8_0": {
"contains": {
"const": 1
},
"$id": "contains_8_0"
}
}
]
}
},
"tests": [
{

View File

@ -2,12 +2,11 @@
{
"description": "validation of string-encoded content based on media type",
"database": {
"schemas": [
{
"contentMediaType": "application/json",
"$id": "content_0_0"
"schemas": {
"content_0_0": {
"contentMediaType": "application/json"
}
]
}
},
"tests": [
{
@ -42,12 +41,11 @@
{
"description": "validation of binary string-encoding",
"database": {
"schemas": [
{
"contentEncoding": "base64",
"$id": "content_1_0"
"schemas": {
"content_1_0": {
"contentEncoding": "base64"
}
]
}
},
"tests": [
{
@ -82,13 +80,12 @@
{
"description": "validation of binary-encoded media type documents",
"database": {
"schemas": [
{
"schemas": {
"content_2_0": {
"contentMediaType": "application/json",
"contentEncoding": "base64",
"$id": "content_2_0"
"contentEncoding": "base64"
}
]
}
},
"tests": [
{
@ -132,8 +129,8 @@
{
"description": "validation of binary-encoded media type documents with schema",
"database": {
"schemas": [
{
"schemas": {
"content_3_0": {
"contentMediaType": "application/json",
"contentEncoding": "base64",
"contentSchema": {
@ -149,10 +146,9 @@
"type": "integer"
}
}
},
"$id": "content_3_0"
}
}
]
}
},
"tests": [
{

View File

@ -15,9 +15,8 @@
"variations": [
"org"
],
"schemas": [
{
"$id": "full.org",
"schemas": {
"full.org": {
"type": "object",
"properties": {
"missing_users": {
@ -28,7 +27,7 @@
}
}
}
]
}
},
{
"id": "22222222-2222-2222-2222-222222222222",
@ -42,13 +41,12 @@
"variations": [
"user"
],
"schemas": [
{
"$id": "full.user",
"schemas": {
"full.user": {
"type": "object",
"properties": {}
}
]
}
}
],
"relations": []
@ -84,9 +82,8 @@
"variations": [
"parent"
],
"schemas": [
{
"$id": "full.parent",
"schemas": {
"full.parent": {
"type": "object",
"properties": {
"children": {
@ -97,7 +94,7 @@
}
}
}
]
}
},
{
"id": "22222222-2222-2222-2222-222222222222",
@ -111,13 +108,12 @@
"variations": [
"child"
],
"schemas": [
{
"$id": "full.child",
"schemas": {
"full.child": {
"type": "object",
"properties": {}
}
]
}
}
],
"relations": [
@ -167,9 +163,8 @@
"variations": [
"invoice"
],
"schemas": [
{
"$id": "full.invoice",
"schemas": {
"full.invoice": {
"type": "object",
"properties": {
"activities": {
@ -180,7 +175,7 @@
}
}
}
]
}
},
{
"id": "22222222-2222-2222-2222-222222222222",
@ -194,13 +189,12 @@
"variations": [
"activity"
],
"schemas": [
{
"$id": "full.activity",
"schemas": {
"full.activity": {
"type": "object",
"properties": {}
}
]
}
}
],
"relations": [
@ -274,9 +268,8 @@
"variations": [
"actor"
],
"schemas": [
{
"$id": "full.actor",
"schemas": {
"full.actor": {
"type": "object",
"properties": {
"ambiguous_edge": {
@ -287,7 +280,7 @@
}
}
}
]
}
},
{
"id": "22222222-2222-2222-2222-222222222222",
@ -301,13 +294,12 @@
"variations": [
"junction"
],
"schemas": [
{
"$id": "empty.junction",
"schemas": {
"empty.junction": {
"type": "object",
"properties": {}
}
]
}
}
],
"relations": [
@ -406,5 +398,271 @@
}
}
]
},
{
"description": "Schema Promotion Accuracy Test",
"database": {
"puncs": [],
"enums": [],
"relations": [
{
"id": "r1",
"type": "relation",
"constraint": "fk_person_email",
"source_type": "person",
"source_columns": [
"email_id"
],
"destination_type": "email_address",
"destination_columns": [
"id"
],
"prefix": "email"
},
{
"id": "r2",
"type": "relation",
"constraint": "fk_person_ad_hoc_bubble",
"source_type": "person",
"source_columns": [
"ad_hoc_bubble_id"
],
"destination_type": "some_bubble",
"destination_columns": [
"id"
],
"prefix": "ad_hoc_bubble"
},
{
"id": "r3",
"type": "relation",
"constraint": "fk_person_generic_bubble",
"source_type": "person",
"source_columns": [
"generic_bubble_id"
],
"destination_type": "some_bubble",
"destination_columns": [
"id"
],
"prefix": "generic_bubble"
},
{
"id": "r4",
"type": "relation",
"constraint": "fk_person_extended_relations",
"source_type": "contact",
"source_columns": [
"source_id"
],
"destination_type": "person",
"destination_columns": [
"id"
],
"prefix": "extended_relations"
},
{
"id": "r5",
"type": "relation",
"constraint": "fk_person_standard_relations",
"source_type": "contact",
"source_columns": [
"source_id_2"
],
"destination_type": "person",
"destination_columns": [
"id"
],
"prefix": "standard_relations"
},
{
"id": "r6",
"type": "relation",
"constraint": "fk_contact_target",
"source_type": "contact",
"source_columns": [
"target_id"
],
"destination_type": "email_address",
"destination_columns": [
"id"
],
"prefix": "target"
}
],
"types": [
{
"id": "t1",
"type": "type",
"name": "person",
"module": "core",
"source": "person",
"hierarchy": [
"person"
],
"variations": [
"person",
"student"
],
"schemas": {
"full.person": {
"type": "object",
"properties": {
"type": {
"type": "string"
},
"name": {
"type": "string"
},
"email": {
"$family": "email_address"
},
"generic_bubble": {
"type": "some_bubble"
},
"ad_hoc_bubble": {
"type": "some_bubble",
"properties": {
"extra_inline_feature": {
"type": "string"
}
}
},
"tags": {
"type": "array",
"items": {
"type": "string"
}
},
"standard_relations": {
"type": "array",
"items": {
"type": "contact"
}
},
"extended_relations": {
"type": "array",
"items": {
"type": "contact",
"properties": {
"target": {
"type": "email_address",
"properties": {
"extra_3rd_level": {
"type": "string"
}
}
}
}
}
}
}
},
"student.person": {
"type": "object",
"properties": {
"type": {
"type": "string"
},
"kind": {
"type": "string"
},
"school": {
"type": "string"
}
}
}
}
},
{
"id": "t2",
"type": "type",
"name": "email_address",
"module": "core",
"source": "email_address",
"hierarchy": [
"email_address"
],
"variations": [
"email_address"
],
"schemas": {
"light.email_address": {
"type": "object",
"properties": {
"address": {
"type": "string"
}
}
}
}
},
{
"id": "t3",
"type": "type",
"name": "contact",
"module": "core",
"source": "contact",
"hierarchy": [
"contact"
],
"variations": [
"contact"
],
"schemas": {
"full.contact": {
"type": "object",
"properties": {
"id": {
"type": "string"
}
}
}
}
},
{
"id": "t4",
"type": "type",
"name": "some_bubble",
"module": "core",
"source": "some_bubble",
"hierarchy": [
"some_bubble"
],
"variations": [
"some_bubble"
],
"schemas": {
"some_bubble": {
"type": "object",
"properties": {
"bubble_prop": {
"type": "string"
}
}
}
}
}
]
},
"tests": [
{
"description": "Assert exact topological schema promotion paths",
"action": "compile",
"expect": {
"success": true,
"schemas": [
"full.contact",
"full.person",
"full.person/ad_hoc_bubble",
"full.person/extended_relations",
"full.person/extended_relations/target",
"light.email_address",
"some_bubble",
"student.person"
]
}
}
]
}
]

View File

@ -2,10 +2,9 @@
{
"description": "single dependency (required)",
"database": {
"schemas": [
{
"schemas": {
"schema1": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "schema1",
"dependencies": {
"bar": [
"foo"
@ -13,7 +12,7 @@
},
"extensible": true
}
]
}
},
"tests": [
{
@ -93,16 +92,15 @@
{
"description": "empty dependents",
"database": {
"schemas": [
{
"schemas": {
"schema2": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "schema2",
"dependencies": {
"bar": []
},
"extensible": true
}
]
}
},
"tests": [
{
@ -139,10 +137,9 @@
{
"description": "multiple dependents required",
"database": {
"schemas": [
{
"schemas": {
"schema3": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "schema3",
"dependencies": {
"quux": [
"foo",
@ -151,7 +148,7 @@
},
"extensible": true
}
]
}
},
"tests": [
{
@ -228,10 +225,9 @@
{
"description": "dependencies with escaped characters",
"database": {
"schemas": [
{
"schemas": {
"schema4": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "schema4",
"dependencies": {
"foo\nbar": [
"foo\rbar"
@ -242,7 +238,7 @@
},
"extensible": true
}
]
}
},
"tests": [
{
@ -297,10 +293,9 @@
{
"description": "extensible: true allows extra properties in dependentRequired",
"database": {
"schemas": [
{
"schemas": {
"schema5": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "schema5",
"dependencies": {
"bar": [
"foo"
@ -308,7 +303,7 @@
},
"extensible": true
}
]
}
},
"tests": [
{
@ -329,10 +324,9 @@
{
"description": "single dependency (schemas, STRICT)",
"database": {
"schemas": [
{
"schemas": {
"schema_schema1": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "schema_schema1",
"properties": {
"foo": true,
"bar": true
@ -350,7 +344,7 @@
}
}
}
]
}
},
"tests": [
{
@ -451,10 +445,9 @@
{
"description": "single dependency (schemas, EXTENSIBLE)",
"database": {
"schemas": [
{
"schemas": {
"schema_schema2": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "schema_schema2",
"properties": {
"foo": true,
"bar": true
@ -473,7 +466,7 @@
},
"extensible": true
}
]
}
},
"tests": [
{
@ -492,10 +485,9 @@
{
"description": "boolean subschemas",
"database": {
"schemas": [
{
"schemas": {
"schema_schema3": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "schema_schema3",
"properties": {
"foo": true,
"bar": true
@ -505,7 +497,7 @@
"bar": false
}
}
]
}
},
"tests": [
{
@ -556,10 +548,9 @@
{
"description": "dependencies with escaped characters",
"database": {
"schemas": [
{
"schemas": {
"schema_schema4": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "schema_schema4",
"properties": {
"foo\tbar": true,
"foo'bar": true,
@ -579,7 +570,7 @@
}
}
}
]
}
},
"tests": [
{
@ -637,10 +628,9 @@
{
"description": "dependent subschema incompatible with root (STRICT)",
"database": {
"schemas": [
{
"schemas": {
"schema_schema5": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "schema_schema5",
"properties": {
"foo": {},
"baz": true
@ -653,7 +643,7 @@
}
}
}
]
}
},
"tests": [
{
@ -711,10 +701,9 @@
{
"description": "dependent subschema incompatible with root (EXTENSIBLE)",
"database": {
"schemas": [
{
"schemas": {
"schema_schema6": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "schema_schema6",
"properties": {
"foo": {},
"baz": true
@ -729,7 +718,7 @@
},
"extensible": true
}
]
}
},
"tests": [
{

View File

@ -2,8 +2,8 @@
{
"description": "empty string is valid for all types (except const)",
"database": {
"schemas": [
{
"schemas": {
"emptyString_0_0": {
"properties": {
"obj": {
"type": "object"
@ -36,10 +36,9 @@
"con_empty": {
"const": ""
}
},
"$id": "emptyString_0_0"
}
}
]
}
},
"tests": [
{
@ -142,7 +141,9 @@
"errors": [
{
"code": "CONST_VIOLATED",
"details": { "path": "con" }
"details": {
"path": "con"
}
}
]
}

View File

@ -2,16 +2,15 @@
{
"description": "simple enum validation",
"database": {
"schemas": [
{
"schemas": {
"enum_0_0": {
"enum": [
1,
2,
3
],
"$id": "enum_0_0"
]
}
]
}
},
"tests": [
{
@ -37,8 +36,8 @@
{
"description": "heterogeneous enum validation",
"database": {
"schemas": [
{
"schemas": {
"enum_1_0": {
"enum": [
6,
"foo",
@ -50,10 +49,9 @@
],
"properties": {
"foo": {}
},
"$id": "enum_1_0"
}
}
]
}
},
"tests": [
{
@ -113,15 +111,14 @@
{
"description": "heterogeneous enum-with-null validation",
"database": {
"schemas": [
{
"schemas": {
"enum_2_0": {
"enum": [
6,
null
],
"$id": "enum_2_0"
]
}
]
}
},
"tests": [
{
@ -156,8 +153,8 @@
{
"description": "enums in properties",
"database": {
"schemas": [
{
"schemas": {
"enum_3_0": {
"type": "object",
"properties": {
"foo": {
@ -173,10 +170,9 @@
},
"required": [
"bar"
],
"$id": "enum_3_0"
]
}
]
}
},
"tests": [
{
@ -251,15 +247,14 @@
{
"description": "enum with escaped characters",
"database": {
"schemas": [
{
"schemas": {
"enum_4_0": {
"enum": [
"foo\nbar",
"foo\rbar"
],
"$id": "enum_4_0"
]
}
]
}
},
"tests": [
{
@ -294,14 +289,13 @@
{
"description": "enum with false does not match 0",
"database": {
"schemas": [
{
"schemas": {
"enum_5_0": {
"enum": [
false
],
"$id": "enum_5_0"
]
}
]
}
},
"tests": [
{
@ -336,16 +330,15 @@
{
"description": "enum with [false] does not match [0]",
"database": {
"schemas": [
{
"schemas": {
"enum_6_0": {
"enum": [
[
false
]
],
"$id": "enum_6_0"
]
}
]
}
},
"tests": [
{
@ -386,14 +379,13 @@
{
"description": "enum with true does not match 1",
"database": {
"schemas": [
{
"schemas": {
"enum_7_0": {
"enum": [
true
],
"$id": "enum_7_0"
]
}
]
}
},
"tests": [
{
@ -428,16 +420,15 @@
{
"description": "enum with [true] does not match [1]",
"database": {
"schemas": [
{
"schemas": {
"enum_8_0": {
"enum": [
[
true
]
],
"$id": "enum_8_0"
]
}
]
}
},
"tests": [
{
@ -478,14 +469,13 @@
{
"description": "enum with 0 does not match false",
"database": {
"schemas": [
{
"schemas": {
"enum_9_0": {
"enum": [
0
],
"$id": "enum_9_0"
]
}
]
}
},
"tests": [
{
@ -520,16 +510,15 @@
{
"description": "enum with [0] does not match [false]",
"database": {
"schemas": [
{
"schemas": {
"enum_10_0": {
"enum": [
[
0
]
],
"$id": "enum_10_0"
]
}
]
}
},
"tests": [
{
@ -570,14 +559,13 @@
{
"description": "enum with 1 does not match true",
"database": {
"schemas": [
{
"schemas": {
"enum_11_0": {
"enum": [
1
],
"$id": "enum_11_0"
]
}
]
}
},
"tests": [
{
@ -612,16 +600,15 @@
{
"description": "enum with [1] does not match [true]",
"database": {
"schemas": [
{
"schemas": {
"enum_12_0": {
"enum": [
[
1
]
],
"$id": "enum_12_0"
]
}
]
}
},
"tests": [
{
@ -662,14 +649,13 @@
{
"description": "nul characters in strings",
"database": {
"schemas": [
{
"schemas": {
"enum_13_0": {
"enum": [
"hello\u0000there"
],
"$id": "enum_13_0"
]
}
]
}
},
"tests": [
{
@ -695,17 +681,16 @@
{
"description": "extensible: true allows extra properties in enum object match",
"database": {
"schemas": [
{
"schemas": {
"enum_14_0": {
"enum": [
{
"foo": 1
}
],
"extensible": true,
"$id": "enum_14_0"
"extensible": true
}
]
}
},
"tests": [
{

View File

@ -2,12 +2,11 @@
{
"description": "exclusiveMaximum validation",
"database": {
"schemas": [
{
"exclusiveMaximum": 3,
"$id": "exclusiveMaximum_0_0"
"schemas": {
"exclusiveMaximum_0_0": {
"exclusiveMaximum": 3
}
]
}
},
"tests": [
{

View File

@ -2,12 +2,11 @@
{
"description": "exclusiveMinimum validation",
"database": {
"schemas": [
{
"exclusiveMinimum": 1.1,
"$id": "exclusiveMinimum_0_0"
"schemas": {
"exclusiveMinimum_0_0": {
"exclusiveMinimum": 1.1
}
]
}
},
"tests": [
{

View File

@ -2,12 +2,11 @@
{
"description": "validation of date-time strings",
"database": {
"schemas": [
{
"format": "date-time",
"$id": "format_0_0"
"schemas": {
"format_0_0": {
"format": "date-time"
}
]
}
},
"tests": [
{
@ -249,12 +248,11 @@
{
"description": "validation of date strings",
"database": {
"schemas": [
{
"format": "date",
"$id": "format_1_0"
"schemas": {
"format_1_0": {
"format": "date"
}
]
}
},
"tests": [
{
@ -694,12 +692,11 @@
{
"description": "validation of duration strings",
"database": {
"schemas": [
{
"format": "duration",
"$id": "format_2_0"
"schemas": {
"format_2_0": {
"format": "duration"
}
]
}
},
"tests": [
{
@ -941,12 +938,11 @@
{
"description": "\\a is not an ECMA 262 control escape",
"database": {
"schemas": [
{
"format": "regex",
"$id": "format_3_0"
"schemas": {
"format_3_0": {
"format": "regex"
}
]
}
},
"tests": [
{
@ -963,12 +959,11 @@
{
"description": "validation of e-mail addresses",
"database": {
"schemas": [
{
"format": "email",
"$id": "format_4_0"
"schemas": {
"format_4_0": {
"format": "email"
}
]
}
},
"tests": [
{
@ -1192,12 +1187,11 @@
{
"description": "validation of host names",
"database": {
"schemas": [
{
"format": "hostname",
"$id": "format_5_0"
"schemas": {
"format_5_0": {
"format": "hostname"
}
]
}
},
"tests": [
{
@ -1422,12 +1416,11 @@
{
"description": "validation of A-label (punycode) host names",
"database": {
"schemas": [
{
"format": "hostname",
"$id": "format_6_0"
"schemas": {
"format_6_0": {
"format": "hostname"
}
]
}
},
"tests": [
{
@ -1803,12 +1796,11 @@
{
"description": "validation of an internationalized e-mail addresses",
"database": {
"schemas": [
{
"format": "idn-email",
"$id": "format_7_0"
"schemas": {
"format_7_0": {
"format": "idn-email"
}
]
}
},
"tests": [
{
@ -1906,12 +1898,11 @@
{
"description": "validation of internationalized host names",
"database": {
"schemas": [
{
"format": "idn-hostname",
"$id": "format_8_0"
"schemas": {
"format_8_0": {
"format": "idn-hostname"
}
]
}
},
"tests": [
{
@ -2479,12 +2470,11 @@
}
],
"database": {
"schemas": [
{
"format": "idn-hostname",
"$id": "format_9_0"
"schemas": {
"format_9_0": {
"format": "idn-hostname"
}
]
}
},
"tests": [
{
@ -2672,12 +2662,11 @@
{
"description": "validation of IP addresses",
"database": {
"schemas": [
{
"format": "ipv4",
"$id": "format_10_0"
"schemas": {
"format_10_0": {
"format": "ipv4"
}
]
}
},
"tests": [
{
@ -2830,12 +2819,11 @@
{
"description": "validation of IPv6 addresses",
"database": {
"schemas": [
{
"format": "ipv6",
"$id": "format_11_0"
"schemas": {
"format_11_0": {
"format": "ipv6"
}
]
}
},
"tests": [
{
@ -3203,12 +3191,11 @@
{
"description": "validation of IRI References",
"database": {
"schemas": [
{
"format": "iri-reference",
"$id": "format_12_0"
"schemas": {
"format_12_0": {
"format": "iri-reference"
}
]
}
},
"tests": [
{
@ -3333,12 +3320,11 @@
{
"description": "validation of IRIs",
"database": {
"schemas": [
{
"format": "iri",
"$id": "format_13_0"
"schemas": {
"format_13_0": {
"format": "iri"
}
]
}
},
"tests": [
{
@ -3481,12 +3467,11 @@
{
"description": "validation of JSON-pointers (JSON String Representation)",
"database": {
"schemas": [
{
"format": "json-pointer",
"$id": "format_14_0"
"schemas": {
"format_14_0": {
"format": "json-pointer"
}
]
}
},
"tests": [
{
@ -3836,12 +3821,11 @@
{
"description": "validation of regular expressions",
"database": {
"schemas": [
{
"format": "regex",
"$id": "format_15_0"
"schemas": {
"format_15_0": {
"format": "regex"
}
]
}
},
"tests": [
{
@ -3921,12 +3905,11 @@
{
"description": "validation of Relative JSON Pointers (RJP)",
"database": {
"schemas": [
{
"format": "relative-json-pointer",
"$id": "format_16_0"
"schemas": {
"format_16_0": {
"format": "relative-json-pointer"
}
]
}
},
"tests": [
{
@ -4096,12 +4079,11 @@
{
"description": "validation of time strings",
"database": {
"schemas": [
{
"format": "time",
"$id": "format_17_0"
"schemas": {
"format_17_0": {
"format": "time"
}
]
}
},
"tests": [
{
@ -4523,12 +4505,11 @@
{
"description": "unknown format",
"database": {
"schemas": [
{
"format": "unknown",
"$id": "format_18_0"
"schemas": {
"format_18_0": {
"format": "unknown"
}
]
}
},
"tests": [
{
@ -4599,12 +4580,11 @@
{
"description": "validation of URI References",
"database": {
"schemas": [
{
"format": "uri-reference",
"$id": "format_19_0"
"schemas": {
"format_19_0": {
"format": "uri-reference"
}
]
}
},
"tests": [
{
@ -4747,12 +4727,11 @@
{
"description": "format: uri-template",
"database": {
"schemas": [
{
"format": "uri-template",
"$id": "format_20_0"
"schemas": {
"format_20_0": {
"format": "uri-template"
}
]
}
},
"tests": [
{
@ -4850,12 +4829,11 @@
{
"description": "validation of URIs",
"database": {
"schemas": [
{
"format": "uri",
"$id": "format_21_0"
"schemas": {
"format_21_0": {
"format": "uri"
}
]
}
},
"tests": [
{
@ -5187,12 +5165,11 @@
{
"description": "uuid format",
"database": {
"schemas": [
{
"format": "uuid",
"$id": "format_22_0"
"schemas": {
"format_22_0": {
"format": "uuid"
}
]
}
},
"tests": [
{
@ -5398,12 +5375,11 @@
{
"description": "period format",
"database": {
"schemas": [
{
"format": "period",
"$id": "format_23_0"
"schemas": {
"format_23_0": {
"format": "period"
}
]
}
},
"tests": [
{

View File

@ -5,15 +5,18 @@
"puncs": [
{
"name": "get_invoice",
"schemas": [
{
"$id": "get_invoice.response",
"schemas": {
"get_invoice.response": {
"oneOf": [
{ "type": "invoice" },
{ "type": "null" }
{
"type": "invoice"
},
{
"type": "null"
}
]
}
]
}
}
],
"enums": [],
@ -23,32 +26,64 @@
"type": "relation",
"constraint": "fk_attachment_attachable_entity",
"source_type": "attachment",
"source_columns": ["attachable_id", "attachable_type"],
"source_columns": [
"attachable_id",
"attachable_type"
],
"destination_type": "entity",
"destination_columns": ["id", "type"],
"destination_columns": [
"id",
"type"
],
"prefix": null
}
],
"types": [
{
"name": "entity",
"schemas": [
{
"$id": "entity",
"schemas": {
"entity": {
"type": "object",
"properties": {
"id": { "type": "string", "format": "uuid" },
"type": { "type": "string" },
"archived": { "type": "boolean" },
"created_at": { "type": "string", "format": "date-time" }
"id": {
"type": "string",
"format": "uuid"
},
"type": {
"type": "string"
},
"archived": {
"type": "boolean"
},
"created_at": {
"type": "string",
"format": "date-time"
}
}
}
},
"hierarchy": [
"entity"
],
"variations": [
"entity",
"activity",
"invoice",
"attachment"
],
"fields": [
"id",
"type",
"archived",
"created_at"
],
"hierarchy": ["entity"],
"variations": ["entity", "activity", "invoice", "attachment"],
"fields": ["id", "type", "archived", "created_at"],
"grouped_fields": {
"entity": ["id", "type", "archived", "created_at"]
"entity": [
"id",
"type",
"archived",
"created_at"
]
},
"field_types": {
"id": "uuid",
@ -63,21 +98,42 @@
},
{
"name": "activity",
"schemas": [
{
"$id": "activity",
"schemas": {
"activity": {
"type": "entity",
"properties": {
"start_date": { "type": "string", "format": "date-time" }
"start_date": {
"type": "string",
"format": "date-time"
}
}
}
},
"hierarchy": [
"activity",
"entity"
],
"variations": [
"activity",
"invoice"
],
"fields": [
"id",
"type",
"archived",
"created_at",
"start_date"
],
"hierarchy": ["activity", "entity"],
"variations": ["activity", "invoice"],
"fields": ["id", "type", "archived", "created_at", "start_date"],
"grouped_fields": {
"entity": ["id", "type", "archived", "created_at"],
"activity": ["start_date"]
"entity": [
"id",
"type",
"archived",
"created_at"
],
"activity": [
"start_date"
]
},
"field_types": {
"id": "uuid",
@ -93,26 +149,51 @@
},
{
"name": "invoice",
"schemas": [
{
"$id": "invoice",
"schemas": {
"invoice": {
"type": "activity",
"properties": {
"status": { "type": "string" },
"status": {
"type": "string"
},
"attachments": {
"type": "array",
"items": { "type": "attachment" }
"items": {
"type": "attachment"
}
}
}
}
},
"hierarchy": [
"invoice",
"activity",
"entity"
],
"variations": [
"invoice"
],
"fields": [
"id",
"type",
"archived",
"created_at",
"start_date",
"status"
],
"hierarchy": ["invoice", "activity", "entity"],
"variations": ["invoice"],
"fields": ["id", "type", "archived", "created_at", "start_date", "status"],
"grouped_fields": {
"entity": ["id", "type", "archived", "created_at"],
"activity": ["start_date"],
"invoice": ["status"]
"entity": [
"id",
"type",
"archived",
"created_at"
],
"activity": [
"start_date"
],
"invoice": [
"status"
]
},
"field_types": {
"id": "uuid",
@ -129,26 +210,66 @@
},
{
"name": "attachment",
"schemas": [
{
"$id": "attachment",
"schemas": {
"attachment": {
"type": "entity",
"properties": {
"name": { "type": "string" },
"attachable_id": { "type": "string", "format": "uuid" },
"attachable_type": { "type": "string" },
"kind": { "type": "string" },
"file": { "type": "string" },
"data": { "type": "object" }
"name": {
"type": "string"
},
"attachable_id": {
"type": "string",
"format": "uuid"
},
"attachable_type": {
"type": "string"
},
"kind": {
"type": "string"
},
"file": {
"type": "string"
},
"data": {
"type": "object"
}
}
}
},
"hierarchy": [
"attachment",
"entity"
],
"variations": [
"attachment"
],
"fields": [
"id",
"type",
"archived",
"created_at",
"attachable_id",
"attachable_type",
"kind",
"file",
"data",
"name"
],
"hierarchy": ["attachment", "entity"],
"variations": ["attachment"],
"fields": ["id", "type", "archived", "created_at", "attachable_id", "attachable_type", "kind", "file", "data", "name"],
"grouped_fields": {
"entity": ["id", "type", "archived", "created_at"],
"attachment": ["attachable_id", "attachable_type", "kind", "file", "data", "name"]
"entity": [
"id",
"type",
"archived",
"created_at"
],
"attachment": [
"attachable_id",
"attachable_type",
"kind",
"file",
"data",
"name"
]
},
"field_types": {
"id": "uuid",

View File

@ -2,14 +2,13 @@
{
"description": "a schema given for items",
"database": {
"schemas": [
{
"schemas": {
"items_0_0": {
"items": {
"type": "integer"
},
"$id": "items_0_0"
}
}
]
}
},
"tests": [
{
@ -65,12 +64,11 @@
{
"description": "items with boolean schema (true)",
"database": {
"schemas": [
{
"items": true,
"$id": "items_1_0"
"schemas": {
"items_1_0": {
"items": true
}
]
}
},
"tests": [
{
@ -100,12 +98,11 @@
{
"description": "items with boolean schema (false)",
"database": {
"schemas": [
{
"items": false,
"$id": "items_2_0"
"schemas": {
"items_2_0": {
"items": false
}
]
}
},
"tests": [
{
@ -135,8 +132,8 @@
{
"description": "items and subitems",
"database": {
"schemas": [
{
"schemas": {
"items_3_0": {
"type": "array",
"items": false,
"prefixItems": [
@ -149,11 +146,9 @@
{
"type": "item"
}
],
"$id": "items_3_0"
]
},
{
"$id": "item",
"item": {
"type": "array",
"items": false,
"prefixItems": [
@ -165,14 +160,13 @@
}
]
},
{
"$id": "sub-item",
"sub-item": {
"type": "object",
"required": [
"foo"
]
}
]
}
},
"tests": [
{
@ -374,8 +368,8 @@
{
"description": "nested items",
"database": {
"schemas": [
{
"schemas": {
"items_4_0": {
"type": "array",
"items": {
"type": "array",
@ -388,10 +382,9 @@
}
}
}
},
"$id": "items_4_0"
}
}
]
}
},
"tests": [
{
@ -507,17 +500,16 @@
{
"description": "prefixItems with no additional items allowed",
"database": {
"schemas": [
{
"schemas": {
"items_5_0": {
"prefixItems": [
{},
{},
{}
],
"items": false,
"$id": "items_5_0"
"items": false
}
]
}
},
"tests": [
{
@ -584,8 +576,8 @@
{
"description": "items does not look in applicators, valid case",
"database": {
"schemas": [
{
"schemas": {
"items_6_0": {
"allOf": [
{
"prefixItems": [
@ -597,10 +589,9 @@
],
"items": {
"minimum": 5
},
"$id": "items_6_0"
}
}
]
}
},
"tests": [
{
@ -632,8 +623,8 @@
{
"description": "prefixItems validation adjusts the starting index for items",
"database": {
"schemas": [
{
"schemas": {
"items_7_0": {
"prefixItems": [
{
"type": "string"
@ -641,10 +632,9 @@
],
"items": {
"type": "integer"
},
"$id": "items_7_0"
}
}
]
}
},
"tests": [
{
@ -677,15 +667,14 @@
{
"description": "items with heterogeneous array",
"database": {
"schemas": [
{
"schemas": {
"items_8_0": {
"prefixItems": [
{}
],
"items": false,
"$id": "items_8_0"
"items": false
}
]
}
},
"tests": [
{
@ -717,14 +706,13 @@
{
"description": "items with null instance elements",
"database": {
"schemas": [
{
"schemas": {
"items_9_0": {
"items": {
"type": "null"
},
"$id": "items_9_0"
}
}
]
}
},
"tests": [
{
@ -743,13 +731,12 @@
{
"description": "extensible: true allows extra items (when items is false)",
"database": {
"schemas": [
{
"schemas": {
"items_10_0": {
"items": false,
"extensible": true,
"$id": "items_10_0"
"extensible": true
}
]
}
},
"tests": [
{
@ -768,15 +755,14 @@
{
"description": "extensible: true allows extra properties for items",
"database": {
"schemas": [
{
"schemas": {
"items_11_0": {
"items": {
"minimum": 5
},
"extensible": true,
"$id": "items_11_0"
"extensible": true
}
]
}
},
"tests": [
{
@ -807,13 +793,12 @@
{
"description": "array: simple extensible array",
"database": {
"schemas": [
{
"schemas": {
"items_12_0": {
"type": "array",
"extensible": true,
"$id": "items_12_0"
"extensible": true
}
]
}
},
"tests": [
{
@ -842,13 +827,12 @@
{
"description": "array: strict array",
"database": {
"schemas": [
{
"schemas": {
"items_13_0": {
"type": "array",
"extensible": false,
"$id": "items_13_0"
"extensible": false
}
]
}
},
"tests": [
{
@ -876,15 +860,14 @@
{
"description": "array: items extensible",
"database": {
"schemas": [
{
"schemas": {
"items_14_0": {
"type": "array",
"items": {
"extensible": true
},
"$id": "items_14_0"
}
}
]
}
},
"tests": [
{
@ -914,16 +897,15 @@
{
"description": "array: items strict",
"database": {
"schemas": [
{
"schemas": {
"items_15_0": {
"type": "array",
"items": {
"type": "object",
"extensible": false
},
"$id": "items_15_0"
}
}
]
}
},
"tests": [
{

View File

@ -2,13 +2,12 @@
{
"description": "maxContains without contains is ignored",
"database": {
"schemas": [
{
"schemas": {
"maxContains_0_0": {
"maxContains": 1,
"extensible": true,
"$id": "maxContains_0_0"
"extensible": true
}
]
}
},
"tests": [
{
@ -39,16 +38,15 @@
{
"description": "maxContains with contains",
"database": {
"schemas": [
{
"schemas": {
"maxContains_1_0": {
"contains": {
"const": 1
},
"maxContains": 1,
"extensible": true,
"$id": "maxContains_1_0"
"extensible": true
}
]
}
},
"tests": [
{
@ -113,16 +111,15 @@
{
"description": "maxContains with contains, value with a decimal",
"database": {
"schemas": [
{
"schemas": {
"maxContains_2_0": {
"contains": {
"const": 1
},
"maxContains": 1,
"extensible": true,
"$id": "maxContains_2_0"
"extensible": true
}
]
}
},
"tests": [
{
@ -153,17 +150,16 @@
{
"description": "minContains < maxContains",
"database": {
"schemas": [
{
"schemas": {
"maxContains_3_0": {
"contains": {
"const": 1
},
"minContains": 1,
"maxContains": 3,
"extensible": true,
"$id": "maxContains_3_0"
"extensible": true
}
]
}
},
"tests": [
{
@ -206,16 +202,15 @@
{
"description": "extensible: true allows non-matching items in maxContains",
"database": {
"schemas": [
{
"schemas": {
"maxContains_4_0": {
"contains": {
"const": 1
},
"maxContains": 1,
"extensible": true,
"$id": "maxContains_4_0"
"extensible": true
}
]
}
},
"tests": [
{

View File

@ -2,13 +2,12 @@
{
"description": "maxItems validation",
"database": {
"schemas": [
{
"schemas": {
"maxItems_0_0": {
"maxItems": 2,
"extensible": true,
"$id": "maxItems_0_0"
"extensible": true
}
]
}
},
"tests": [
{
@ -61,13 +60,12 @@
{
"description": "maxItems validation with a decimal",
"database": {
"schemas": [
{
"schemas": {
"maxItems_1_0": {
"maxItems": 2,
"extensible": true,
"$id": "maxItems_1_0"
"extensible": true
}
]
}
},
"tests": [
{
@ -99,13 +97,12 @@
{
"description": "extensible: true allows extra items in maxItems (but counted)",
"database": {
"schemas": [
{
"schemas": {
"maxItems_2_0": {
"maxItems": 2,
"extensible": true,
"$id": "maxItems_2_0"
"extensible": true
}
]
}
},
"tests": [
{

View File

@ -2,12 +2,11 @@
{
"description": "maxLength validation",
"database": {
"schemas": [
{
"maxLength": 2,
"$id": "maxLength_0_0"
"schemas": {
"maxLength_0_0": {
"maxLength": 2
}
]
}
},
"tests": [
{
@ -60,12 +59,11 @@
{
"description": "maxLength validation with a decimal",
"database": {
"schemas": [
{
"maxLength": 2,
"$id": "maxLength_1_0"
"schemas": {
"maxLength_1_0": {
"maxLength": 2
}
]
}
},
"tests": [
{

View File

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

View File

@ -2,12 +2,11 @@
{
"description": "maximum validation",
"database": {
"schemas": [
{
"maximum": 3,
"$id": "maximum_0_0"
"schemas": {
"maximum_0_0": {
"maximum": 3
}
]
}
},
"tests": [
{
@ -51,12 +50,11 @@
{
"description": "maximum validation with unsigned integer",
"database": {
"schemas": [
{
"maximum": 300,
"$id": "maximum_1_0"
"schemas": {
"maximum_1_0": {
"maximum": 300
}
]
}
},
"tests": [
{

View File

@ -2,25 +2,23 @@
{
"description": "merging: properties accumulate",
"database": {
"schemas": [
{
"$id": "base_0",
"schemas": {
"base_0": {
"properties": {
"base_prop": {
"type": "string"
}
}
},
{
"merge_0_0": {
"type": "base_0",
"properties": {
"child_prop": {
"type": "string"
}
},
"$id": "merge_0_0"
}
}
]
}
},
"tests": [
{
@ -48,7 +46,9 @@
"errors": [
{
"code": "INVALID_TYPE",
"details": { "path": "base_prop" }
"details": {
"path": "base_prop"
}
}
]
}
@ -58,9 +58,8 @@
{
"description": "merging: required fields accumulate",
"database": {
"schemas": [
{
"$id": "base_1",
"schemas": {
"base_1": {
"properties": {
"a": {
"type": "string"
@ -70,7 +69,7 @@
"a"
]
},
{
"merge_1_0": {
"type": "base_1",
"properties": {
"b": {
@ -79,10 +78,9 @@
},
"required": [
"b"
],
"$id": "merge_1_0"
]
}
]
}
},
"tests": [
{
@ -109,7 +107,9 @@
"errors": [
{
"code": "REQUIRED_FIELD_MISSING",
"details": { "path": "a" }
"details": {
"path": "a"
}
}
]
}
@ -126,7 +126,9 @@
"errors": [
{
"code": "REQUIRED_FIELD_MISSING",
"details": { "path": "b" }
"details": {
"path": "b"
}
}
]
}
@ -136,9 +138,8 @@
{
"description": "merging: dependencies accumulate",
"database": {
"schemas": [
{
"$id": "base_2",
"schemas": {
"base_2": {
"properties": {
"trigger": {
"type": "string"
@ -153,7 +154,7 @@
]
}
},
{
"merge_2_0": {
"type": "base_2",
"properties": {
"child_dep": {
@ -164,10 +165,9 @@
"trigger": [
"child_dep"
]
},
"$id": "merge_2_0"
}
}
]
}
},
"tests": [
{
@ -196,7 +196,9 @@
"errors": [
{
"code": "DEPENDENCY_MISSING",
"details": { "path": "" }
"details": {
"path": ""
}
}
]
}
@ -214,7 +216,9 @@
"errors": [
{
"code": "DEPENDENCY_MISSING",
"details": { "path": "" }
"details": {
"path": ""
}
}
]
}
@ -224,9 +228,8 @@
{
"description": "merging: form and display do NOT merge",
"database": {
"schemas": [
{
"$id": "base_3",
"schemas": {
"base_3": {
"properties": {
"a": {
"type": "string"
@ -240,7 +243,7 @@
"b"
]
},
{
"merge_3_0": {
"type": "base_3",
"properties": {
"c": {
@ -249,10 +252,9 @@
},
"form": [
"c"
],
"$id": "merge_3_0"
]
}
]
}
},
"tests": [
{

File diff suppressed because it is too large Load Diff

View File

@ -2,13 +2,12 @@
{
"description": "minContains without contains is ignored",
"database": {
"schemas": [
{
"schemas": {
"minContains_0_0": {
"minContains": 1,
"extensible": true,
"$id": "minContains_0_0"
"extensible": true
}
]
}
},
"tests": [
{
@ -36,16 +35,15 @@
{
"description": "minContains=1 with contains",
"database": {
"schemas": [
{
"schemas": {
"minContains_1_0": {
"contains": {
"const": 1
},
"minContains": 1,
"extensible": true,
"$id": "minContains_1_0"
"extensible": true
}
]
}
},
"tests": [
{
@ -108,16 +106,15 @@
{
"description": "minContains=2 with contains",
"database": {
"schemas": [
{
"schemas": {
"minContains_2_0": {
"contains": {
"const": 1
},
"minContains": 2,
"extensible": true,
"$id": "minContains_2_0"
"extensible": true
}
]
}
},
"tests": [
{
@ -195,16 +192,15 @@
{
"description": "minContains=2 with contains with a decimal value",
"database": {
"schemas": [
{
"schemas": {
"minContains_3_0": {
"contains": {
"const": 1
},
"minContains": 2,
"extensible": true,
"$id": "minContains_3_0"
"extensible": true
}
]
}
},
"tests": [
{
@ -235,17 +231,16 @@
{
"description": "maxContains = minContains",
"database": {
"schemas": [
{
"schemas": {
"minContains_4_0": {
"contains": {
"const": 1
},
"maxContains": 2,
"minContains": 2,
"extensible": true,
"$id": "minContains_4_0"
"extensible": true
}
]
}
},
"tests": [
{
@ -298,17 +293,16 @@
{
"description": "maxContains < minContains",
"database": {
"schemas": [
{
"schemas": {
"minContains_5_0": {
"contains": {
"const": 1
},
"maxContains": 1,
"minContains": 3,
"extensible": true,
"$id": "minContains_5_0"
"extensible": true
}
]
}
},
"tests": [
{
@ -361,16 +355,15 @@
{
"description": "minContains = 0",
"database": {
"schemas": [
{
"schemas": {
"minContains_6_0": {
"contains": {
"const": 1
},
"minContains": 0,
"extensible": true,
"$id": "minContains_6_0"
"extensible": true
}
]
}
},
"tests": [
{
@ -398,17 +391,16 @@
{
"description": "minContains = 0 with maxContains",
"database": {
"schemas": [
{
"schemas": {
"minContains_7_0": {
"contains": {
"const": 1
},
"minContains": 0,
"maxContains": 1,
"extensible": true,
"$id": "minContains_7_0"
"extensible": true
}
]
}
},
"tests": [
{
@ -448,16 +440,15 @@
{
"description": "extensible: true allows non-matching items in minContains",
"database": {
"schemas": [
{
"schemas": {
"minContains_8_0": {
"contains": {
"const": 1
},
"minContains": 1,
"extensible": true,
"$id": "minContains_8_0"
"extensible": true
}
]
}
},
"tests": [
{

View File

@ -2,13 +2,12 @@
{
"description": "minItems validation",
"database": {
"schemas": [
{
"schemas": {
"minItems_0_0": {
"minItems": 1,
"extensible": true,
"$id": "minItems_0_0"
"extensible": true
}
]
}
},
"tests": [
{
@ -57,13 +56,12 @@
{
"description": "minItems validation with a decimal",
"database": {
"schemas": [
{
"schemas": {
"minItems_1_0": {
"minItems": 1,
"extensible": true,
"$id": "minItems_1_0"
"extensible": true
}
]
}
},
"tests": [
{
@ -92,13 +90,12 @@
{
"description": "extensible: true allows extra items in minItems",
"database": {
"schemas": [
{
"schemas": {
"minItems_2_0": {
"minItems": 1,
"extensible": true,
"$id": "minItems_2_0"
"extensible": true
}
]
}
},
"tests": [
{

View File

@ -2,12 +2,11 @@
{
"description": "minLength validation",
"database": {
"schemas": [
{
"minLength": 2,
"$id": "minLength_0_0"
"schemas": {
"minLength_0_0": {
"minLength": 2
}
]
}
},
"tests": [
{
@ -60,12 +59,11 @@
{
"description": "minLength validation with a decimal",
"database": {
"schemas": [
{
"minLength": 2,
"$id": "minLength_1_0"
"schemas": {
"minLength_1_0": {
"minLength": 2
}
]
}
},
"tests": [
{

View File

@ -2,13 +2,12 @@
{
"description": "minProperties validation",
"database": {
"schemas": [
{
"schemas": {
"minProperties_0_0": {
"minProperties": 1,
"extensible": true,
"$id": "minProperties_0_0"
"extensible": true
}
]
}
},
"tests": [
{
@ -75,13 +74,12 @@
{
"description": "minProperties validation with a decimal",
"database": {
"schemas": [
{
"schemas": {
"minProperties_1_0": {
"minProperties": 1,
"extensible": true,
"$id": "minProperties_1_0"
"extensible": true
}
]
}
},
"tests": [
{
@ -110,13 +108,12 @@
{
"description": "extensible: true allows extra properties in minProperties",
"database": {
"schemas": [
{
"schemas": {
"minProperties_2_0": {
"minProperties": 1,
"extensible": true,
"$id": "minProperties_2_0"
"extensible": true
}
]
}
},
"tests": [
{

View File

@ -2,12 +2,11 @@
{
"description": "minimum validation",
"database": {
"schemas": [
{
"minimum": 1.1,
"$id": "minimum_0_0"
"schemas": {
"minimum_0_0": {
"minimum": 1.1
}
]
}
},
"tests": [
{
@ -51,12 +50,11 @@
{
"description": "minimum validation with signed integer",
"database": {
"schemas": [
{
"minimum": -2,
"$id": "minimum_1_0"
"schemas": {
"minimum_1_0": {
"minimum": -2
}
]
}
},
"tests": [
{

View File

@ -2,12 +2,11 @@
{
"description": "by int",
"database": {
"schemas": [
{
"multipleOf": 2,
"$id": "multipleOf_0_0"
"schemas": {
"multipleOf_0_0": {
"multipleOf": 2
}
]
}
},
"tests": [
{
@ -42,12 +41,11 @@
{
"description": "by number",
"database": {
"schemas": [
{
"multipleOf": 1.5,
"$id": "multipleOf_1_0"
"schemas": {
"multipleOf_1_0": {
"multipleOf": 1.5
}
]
}
},
"tests": [
{
@ -82,12 +80,11 @@
{
"description": "by small number",
"database": {
"schemas": [
{
"multipleOf": 0.0001,
"$id": "multipleOf_2_0"
"schemas": {
"multipleOf_2_0": {
"multipleOf": 0.0001
}
]
}
},
"tests": [
{
@ -113,13 +110,12 @@
{
"description": "small multiple of large integer",
"database": {
"schemas": [
{
"schemas": {
"multipleOf_3_0": {
"type": "integer",
"multipleOf": 1e-8,
"$id": "multipleOf_3_0"
"multipleOf": 1e-08
}
]
}
},
"tests": [
{

View File

@ -2,14 +2,13 @@
{
"description": "not",
"database": {
"schemas": [
{
"schemas": {
"not_0_0": {
"not": {
"type": "integer"
},
"$id": "not_0_0"
}
}
]
}
},
"tests": [
{
@ -35,17 +34,16 @@
{
"description": "not multiple types",
"database": {
"schemas": [
{
"schemas": {
"not_1_0": {
"not": {
"type": [
"integer",
"boolean"
]
},
"$id": "not_1_0"
}
}
]
}
},
"tests": [
{
@ -80,8 +78,8 @@
{
"description": "not more complex schema",
"database": {
"schemas": [
{
"schemas": {
"not_2_0": {
"not": {
"type": "object",
"properties": {
@ -90,10 +88,9 @@
}
}
},
"extensible": true,
"$id": "not_2_0"
"extensible": true
}
]
}
},
"tests": [
{
@ -132,16 +129,15 @@
{
"description": "forbidden property",
"database": {
"schemas": [
{
"schemas": {
"not_3_0": {
"properties": {
"foo": {
"not": {}
}
},
"$id": "not_3_0"
}
}
]
}
},
"tests": [
{
@ -170,12 +166,11 @@
{
"description": "forbid everything with empty schema",
"database": {
"schemas": [
{
"not": {},
"$id": "not_4_0"
"schemas": {
"not_4_0": {
"not": {}
}
]
}
},
"tests": [
{
@ -268,12 +263,11 @@
{
"description": "forbid everything with boolean schema true",
"database": {
"schemas": [
{
"not": true,
"$id": "not_5_0"
"schemas": {
"not_5_0": {
"not": true
}
]
}
},
"tests": [
{
@ -366,13 +360,12 @@
{
"description": "allow everything with boolean schema false",
"database": {
"schemas": [
{
"schemas": {
"not_6_0": {
"not": false,
"extensible": true,
"$id": "not_6_0"
"extensible": true
}
]
}
},
"tests": [
{
@ -465,14 +458,13 @@
{
"description": "double negation",
"database": {
"schemas": [
{
"schemas": {
"not_7_0": {
"not": {
"not": {}
},
"$id": "not_7_0"
}
}
]
}
},
"tests": [
{
@ -489,15 +481,14 @@
{
"description": "extensible: true allows extra properties in not",
"database": {
"schemas": [
{
"schemas": {
"not_8_0": {
"not": {
"type": "integer"
},
"extensible": true,
"$id": "not_8_0"
"extensible": true
}
]
}
},
"tests": [
{
@ -516,14 +507,13 @@
{
"description": "extensible: false (default) forbids extra properties in not",
"database": {
"schemas": [
{
"schemas": {
"not_9_0": {
"not": {
"type": "integer"
},
"$id": "not_9_0"
}
}
]
}
},
"tests": [
{
@ -542,8 +532,8 @@
{
"description": "property next to not (extensible: true)",
"database": {
"schemas": [
{
"schemas": {
"not_10_0": {
"properties": {
"bar": {
"type": "string"
@ -552,10 +542,9 @@
"not": {
"type": "integer"
},
"extensible": true,
"$id": "not_10_0"
"extensible": true
}
]
}
},
"tests": [
{
@ -575,8 +564,8 @@
{
"description": "property next to not (extensible: false)",
"database": {
"schemas": [
{
"schemas": {
"not_11_0": {
"properties": {
"bar": {
"type": "string"
@ -584,10 +573,9 @@
},
"not": {
"type": "integer"
},
"$id": "not_11_0"
}
}
]
}
},
"tests": [
{

View File

@ -2,166 +2,182 @@
{
"description": "Strict Inheritance",
"database": {
"schemas": [
{
"$id": "parent_type",
"schemas": {
"parent_type": {
"type": "object",
"properties": {"a": {"type": "integer"}},
"required": ["a"]
"properties": {
"a": {
"type": "integer"
}
},
"required": [
"a"
]
},
{
"$id": "child_type",
"child_type": {
"type": "parent_type",
"properties": {"b": {"type": "integer"}}
"properties": {
"b": {
"type": "integer"
}
}
}
]
}
},
"tests": [
{
"description": "valid child inherits properties and strictness",
"schema_id": "child_type",
"data": {"a": 1, "b": 2},
"data": {
"a": 1,
"b": 2
},
"action": "validate",
"expect": {"success": true}
"expect": {
"success": true
}
},
{
"description": "missing inherited required property fails",
"schema_id": "child_type",
"data": {"b": 2},
"data": {
"b": 2
},
"action": "validate",
"expect": {"success": false}
"expect": {
"success": false
}
},
{
"description": "additional properties fail due to inherited strictness",
"schema_id": "child_type",
"data": {"a": 1, "b": 2, "c": 3},
"data": {
"a": 1,
"b": 2,
"c": 3
},
"action": "validate",
"expect": {"success": false}
"expect": {
"success": false
}
}
]
},
{
"description": "Local Shadowing (Composition & Proxies)",
"database": {
"schemas": [
{
"$id": "budget",
"schemas": {
"budget": {
"type": "object",
"properties": {
"max": {"type": "integer", "maximum": 100}
"max": {
"type": "integer",
"maximum": 100
}
}
},
{
"$id": "custom_budget",
"custom_budget": {
"type": "budget",
"properties": {
"max": {"type": "integer", "maximum": 50}
"max": {
"type": "integer",
"maximum": 50
}
}
}
]
}
},
"tests": [
{
"description": "shadowing override applies (50 is locally allowed)",
"schema_id": "custom_budget",
"data": {"max": 40},
"data": {
"max": 40
},
"action": "validate",
"expect": {"success": true}
"expect": {
"success": true
}
},
{
"description": "shadowing override applies natively, rejecting 60 even though parent allowed 100",
"schema_id": "custom_budget",
"data": {"max": 60},
"data": {
"max": 60
},
"action": "validate",
"expect": {"success": false}
}
]
},
{
"description": "Inline id Resolution",
"database": {
"schemas": [
{
"$id": "api.request",
"type": "object",
"properties": {
"inline_obj": {
"$id": "inline_nested",
"type": "object",
"properties": {"foo": {"type": "string"}},
"required": ["foo"]
},
"proxy_obj": {
"type": "inline_nested"
}
}
"expect": {
"success": false
}
]
},
"tests": [
{
"description": "proxy resolves and validates to the inline component",
"schema_id": "api.request",
"data": {
"inline_obj": {"foo": "bar"},
"proxy_obj": {"foo": "baz"}
},
"action": "validate",
"expect": {"success": true}
},
{
"description": "proxy resolves and catches violation targeting inline component constraints",
"schema_id": "api.request",
"data": {
"inline_obj": {"foo": "bar"},
"proxy_obj": {}
},
"action": "validate",
"expect": {"success": false}
}
]
},
{
"description": "Primitive Array Shorthand (Optionality)",
"database": {
"schemas": [
{
"$id": "invoice",
"type": "object",
"properties": {"amount": {"type": "integer"}},
"required": ["amount"]
},
{
"$id": "request",
"schemas": {
"invoice": {
"type": "object",
"properties": {
"inv": {"type": ["invoice", "null"]}
"amount": {
"type": "integer"
}
},
"required": [
"amount"
]
},
"request": {
"type": "object",
"properties": {
"inv": {
"type": [
"invoice",
"null"
]
}
}
}
]
}
},
"tests": [
{
"description": "valid object passes shorthand inheritance check",
"schema_id": "request",
"data": {"inv": {"amount": 5}},
"data": {
"inv": {
"amount": 5
}
},
"action": "validate",
"expect": {"success": true}
"expect": {
"success": true
}
},
{
"description": "null passes shorthand inheritance check",
"schema_id": "request",
"data": {"inv": null},
"data": {
"inv": null
},
"action": "validate",
"expect": {"success": true}
"expect": {
"success": true
}
},
{
"description": "invalid object fails inner constraints safely",
"schema_id": "request",
"data": {"inv": {"amount": "string"}},
"data": {
"inv": {
"amount": "string"
}
},
"action": "validate",
"expect": {"success": false}
"expect": {
"success": false
}
}
]
}
]
]

View File

@ -2,9 +2,8 @@
{
"description": "Hybrid Array Pathing",
"database": {
"schemas": [
{
"$id": "hybrid_pathing",
"schemas": {
"hybrid_pathing": {
"type": "object",
"properties": {
"primitives": {
@ -69,7 +68,7 @@
}
}
}
]
}
},
"tests": [
{
@ -123,7 +122,9 @@
"errors": [
{
"code": "INVALID_TYPE",
"details": { "path": "primitives/1" }
"details": {
"path": "primitives/1"
}
}
]
}
@ -147,11 +148,15 @@
"errors": [
{
"code": "REQUIRED_FIELD_MISSING",
"details": { "path": "ad_hoc_objects/1/name" }
"details": {
"path": "ad_hoc_objects/1/name"
}
},
{
"code": "STRICT_PROPERTY_VIOLATION",
"details": { "path": "ad_hoc_objects/1/age" }
"details": {
"path": "ad_hoc_objects/1/age"
}
}
]
}
@ -177,7 +182,9 @@
"errors": [
{
"code": "MINIMUM_VIOLATED",
"details": { "path": "entities/entity-beta/value" }
"details": {
"path": "entities/entity-beta/value"
}
}
]
}
@ -208,7 +215,9 @@
"errors": [
{
"code": "INVALID_TYPE",
"details": { "path": "deep_entities/parent-omega/nested/child-beta/flag" }
"details": {
"path": "deep_entities/parent-omega/nested/child-beta/flag"
}
}
]
}
@ -216,43 +225,10 @@
]
},
{
"description": "Polymorphic Structure Pathing",
"description": "Ad-Hoc Union Pathing",
"database": {
"types": [
{
"name": "widget",
"variations": ["widget"],
"schemas": [
{
"$id": "widget",
"type": "object",
"properties": {
"id": { "type": "string" },
"type": { "type": "string" }
}
},
{
"$id": "stock.widget",
"type": "widget",
"properties": {
"kind": { "type": "string" },
"amount": { "type": "integer" },
"details": {
"type": "object",
"properties": {
"nested_metric": { "type": "number" }
},
"required": ["nested_metric"]
}
},
"required": ["amount", "details"]
}
]
}
],
"schemas": [
{
"$id": "polymorphic_pathing",
"schemas": {
"ad_hoc_pathing": {
"type": "object",
"properties": {
"metadata_bubbles": {
@ -260,26 +236,98 @@
"items": {
"oneOf": [
{
"$id": "contact_metadata",
"type": "object",
"properties": {
"type": { "const": "contact" },
"phone": { "type": "string" }
},
"required": ["phone"]
"type": "string"
},
{
"$id": "invoice_metadata",
"type": "object",
"properties": {
"type": { "const": "invoice" },
"invoice_id": { "type": "integer" }
},
"required": ["invoice_id"]
"type": "integer"
}
]
}
}
}
}
}
},
"tests": [
{
"description": "oneOf arrays natively support disjoint primitive types dynamically mapped into topological paths natively",
"data": {
"metadata_bubbles": [
100,
"metadata string",
true
]
},
"schema_id": "ad_hoc_pathing",
"action": "validate",
"expect": {
"success": false,
"errors": [
{
"code": "NO_ONEOF_MATCH",
"details": {
"path": "metadata_bubbles/2"
}
}
]
}
}
]
},
{
"description": "Polymorphic Family Pathing",
"database": {
"types": [
{
"name": "widget",
"variations": [
"widget"
],
"schemas": {
"widget": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"type": {
"type": "string"
}
}
},
"stock.widget": {
"type": "widget",
"properties": {
"kind": {
"type": "string"
},
"amount": {
"type": "integer"
},
"details": {
"type": "object",
"properties": {
"nested_metric": {
"type": "number"
}
},
"required": [
"nested_metric"
]
}
},
"required": [
"amount",
"details"
]
}
}
}
],
"schemas": {
"family_pathing": {
"type": "object",
"properties": {
"table_families": {
"type": "array",
"items": {
@ -288,33 +336,10 @@
}
}
}
]
}
},
"tests": [
{
"description": "oneOf tags natively bubble specific schema paths into deep array roots",
"data": {
"metadata_bubbles": [
{ "type": "invoice", "invoice_id": 100 },
{ "type": "contact", "phone": 12345, "rogue_field": "x" }
]
},
"schema_id": "polymorphic_pathing",
"action": "validate",
"expect": {
"success": false,
"errors": [
{
"code": "INVALID_TYPE",
"details": { "path": "metadata_bubbles/1/phone" }
},
{
"code": "STRICT_PROPERTY_VIOLATION",
"details": { "path": "metadata_bubbles/1/rogue_field" }
}
]
}
},
{
"description": "families mechanically map physical variants directly onto topological uuid array paths",
"data": {
@ -324,7 +349,9 @@
"type": "widget",
"kind": "stock",
"amount": 500,
"details": { "nested_metric": 42.0 }
"details": {
"nested_metric": 42.0
}
},
{
"id": "widget-2",
@ -332,32 +359,40 @@
"kind": "stock",
"amount": "not_an_int",
"details": {
"stray_child": true
"stray_child": true
},
"unexpected_root_prop": "hi"
}
]
},
"schema_id": "polymorphic_pathing",
"schema_id": "family_pathing",
"action": "validate",
"expect": {
"success": false,
"errors": [
{
"code": "INVALID_TYPE",
"details": { "path": "table_families/widget-2/amount" }
"details": {
"path": "table_families/widget-2/amount"
}
},
{
"code": "REQUIRED_FIELD_MISSING",
"details": { "path": "table_families/widget-2/details/nested_metric" }
"details": {
"path": "table_families/widget-2/details/nested_metric"
}
},
{
"code": "STRICT_PROPERTY_VIOLATION",
"details": { "path": "table_families/widget-2/details/stray_child" }
"details": {
"path": "table_families/widget-2/details/stray_child"
}
},
{
"code": "STRICT_PROPERTY_VIOLATION",
"details": { "path": "table_families/widget-2/unexpected_root_prop" }
"details": {
"path": "table_families/widget-2/unexpected_root_prop"
}
}
]
}

View File

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

View File

@ -2,17 +2,16 @@
{
"description": "patternProperties validates properties matching a regex",
"database": {
"schemas": [
{
"schemas": {
"patternProperties_0_0": {
"patternProperties": {
"f.*o": {
"type": "integer"
}
},
"items": {},
"$id": "patternProperties_0_0"
"items": {}
}
]
}
},
"tests": [
{
@ -108,8 +107,8 @@
{
"description": "multiple simultaneous patternProperties are validated",
"database": {
"schemas": [
{
"schemas": {
"patternProperties_1_0": {
"patternProperties": {
"a*": {
"type": "integer"
@ -117,10 +116,9 @@
"aaa*": {
"maximum": 20
}
},
"$id": "patternProperties_1_0"
}
}
]
}
},
"tests": [
{
@ -196,8 +194,8 @@
{
"description": "regexes are not anchored by default and are case sensitive",
"database": {
"schemas": [
{
"schemas": {
"patternProperties_2_0": {
"patternProperties": {
"[0-9]{2,}": {
"type": "boolean"
@ -206,10 +204,9 @@
"type": "string"
}
},
"extensible": true,
"$id": "patternProperties_2_0"
"extensible": true
}
]
}
},
"tests": [
{
@ -261,15 +258,14 @@
{
"description": "patternProperties with boolean schemas",
"database": {
"schemas": [
{
"schemas": {
"patternProperties_3_0": {
"patternProperties": {
"f.*": true,
"b.*": false
},
"$id": "patternProperties_3_0"
}
}
]
}
},
"tests": [
{
@ -331,16 +327,15 @@
{
"description": "patternProperties with null valued instance properties",
"database": {
"schemas": [
{
"schemas": {
"patternProperties_4_0": {
"patternProperties": {
"^.*bar$": {
"type": "null"
}
},
"$id": "patternProperties_4_0"
}
}
]
}
},
"tests": [
{
@ -359,17 +354,16 @@
{
"description": "extensible: true allows extra properties NOT matching pattern",
"database": {
"schemas": [
{
"schemas": {
"patternProperties_5_0": {
"patternProperties": {
"f.*o": {
"type": "integer"
}
},
"extensible": true,
"$id": "patternProperties_5_0"
"extensible": true
}
]
}
},
"tests": [
{

View File

@ -5,93 +5,147 @@
"types": [
{
"name": "entity",
"variations": ["entity", "organization", "person", "bot"],
"schemas": [
{
"$id": "entity",
"variations": [
"entity",
"organization",
"person",
"bot"
],
"schemas": {
"entity": {
"type": "object",
"properties": {
"type": { "type": "string" },
"id": { "type": "string" }
"type": {
"type": "string"
},
"id": {
"type": "string"
}
}
}
]
}
},
{
"name": "organization",
"variations": ["organization", "person"],
"schemas": [
{
"$id": "organization",
"variations": [
"organization",
"person"
],
"schemas": {
"organization": {
"type": "entity",
"properties": {
"name": { "type": "string" }
"name": {
"type": "string"
}
}
}
]
}
},
{
"name": "person",
"variations": ["person"],
"schemas": [
{
"$id": "person",
"variations": [
"person"
],
"schemas": {
"person": {
"type": "organization",
"properties": {
"first_name": { "type": "string" }
"first_name": {
"type": "string"
}
}
}
]
}
},
{
"name": "bot",
"variations": ["bot"],
"schemas": [
{
"$id": "bot",
"variations": [
"bot"
],
"schemas": {
"bot": {
"type": "entity",
"properties": {
"model": { "type": "string" }
"model": {
"type": "string"
}
}
}
]
}
}
],
"schemas": [
{
"$id": "family_entity",
"schemas": {
"family_entity": {
"$family": "entity"
}
]
}
},
"tests": [
{
"description": "Matches base entity",
"schema_id": "family_entity",
"data": { "type": "entity", "id": "1" },
"data": {
"type": "entity",
"id": "1"
},
"action": "validate",
"expect": { "success": true }
"expect": {
"success": true
}
},
{
"description": "Matches descendant person natively",
"schema_id": "family_entity",
"data": { "type": "person", "id": "2", "first_name": "Bob" },
"data": {
"type": "person",
"id": "2",
"first_name": "Bob"
},
"action": "validate",
"expect": { "success": true }
"expect": {
"success": true
}
},
{
"description": "Missing type fails out immediately",
"schema_id": "family_entity",
"data": { "id": "3", "first_name": "Bob" },
"data": {
"id": "3",
"first_name": "Bob"
},
"action": "validate",
"expect": { "success": false, "errors": [ { "code": "MISSING_TYPE", "details": { "path": "" } } ] }
"expect": {
"success": false,
"errors": [
{
"code": "MISSING_TYPE",
"details": {
"path": ""
}
}
]
}
},
{
"description": "Alias matching failure",
"schema_id": "family_entity",
"data": { "type": "alien", "id": "4" },
"data": {
"type": "alien",
"id": "4"
},
"action": "validate",
"expect": { "success": false, "errors": [ { "code": "NO_FAMILY_MATCH", "details": { "path": "" } } ] }
"expect": {
"success": false,
"errors": [
{
"code": "NO_FAMILY_MATCH",
"details": {
"path": ""
}
}
]
}
}
]
},
@ -101,162 +155,240 @@
"types": [
{
"name": "entity",
"variations": ["entity", "organization", "person", "bot"],
"schemas": [
{
"$id": "entity",
"variations": [
"entity",
"organization",
"person",
"bot"
],
"schemas": {
"entity": {
"type": "object",
"properties": {
"type": { "type": "string" }
"type": {
"type": "string"
}
}
},
{
"$id": "light.entity",
"light.entity": {
"type": "entity",
"properties": {
"kind": { "type": "string" }
"kind": {
"type": "string"
}
}
}
]
}
},
{
"name": "organization",
"variations": ["organization", "person"],
"schemas": [
{
"$id": "organization",
"variations": [
"organization",
"person"
],
"schemas": {
"organization": {
"type": "entity"
},
{
"$id": "light.organization",
"light.organization": {
"type": "light.entity"
}
]
}
},
{
"name": "person",
"variations": ["person"],
"schemas": [
{
"$id": "person",
"variations": [
"person"
],
"schemas": {
"person": {
"type": "organization"
},
{
"$id": "light.person",
"light.person": {
"type": "light.organization"
}
]
}
},
{
"name": "bot",
"variations": ["bot"],
"schemas": [
{
"$id": "bot",
"variations": [
"bot"
],
"schemas": {
"bot": {
"type": "entity"
},
{
"$id": "light.bot",
"light.bot": {
"type": "light.entity"
}
]
}
}
],
"schemas": [
{
"$id": "family_light_org",
"schemas": {
"family_light_org": {
"$family": "light.organization"
}
]
}
},
"tests": [
{
"description": "Matches light.organization exact matrix target",
"schema_id": "family_light_org",
"data": { "type": "organization", "kind": "light" },
"data": {
"type": "organization",
"kind": "light"
},
"action": "validate",
"expect": { "success": true }
"expect": {
"success": true
}
},
{
"description": "Matches descendant light.person through matrix evaluation",
"schema_id": "family_light_org",
"data": { "type": "person", "kind": "light" },
"data": {
"type": "person",
"kind": "light"
},
"action": "validate",
"expect": { "success": true }
"expect": {
"success": true
}
},
{
"description": "Structurally fails to route bot (bot is not descendant of organization)",
"schema_id": "family_light_org",
"data": { "type": "bot", "kind": "light" },
"data": {
"type": "bot",
"kind": "light"
},
"action": "validate",
"expect": { "success": false, "errors": [ { "code": "NO_FAMILY_MATCH", "details": { "path": "" } } ] }
"expect": {
"success": false,
"errors": [
{
"code": "NO_FAMILY_MATCH",
"details": {
"path": ""
}
}
]
}
}
]
},
{
"description": "Horizontal $family Routing (Virtual Variations)",
"database": {
"schemas": [
"types": [
{
"$id": "widget",
"type": "object",
"properties": {
"type": { "type": "string" }
"name": "widget",
"variations": [
"widget"
],
"schemas": {
"widget": {
"type": "object",
"properties": {
"type": {
"type": "string"
}
}
},
"stock.widget": {
"type": "widget",
"properties": {
"kind": {
"type": "string"
},
"amount": {
"type": "integer"
}
}
},
"super_stock.widget": {
"type": "stock.widget",
"properties": {
"super_amount": {
"type": "integer"
}
}
}
}
},
{
"$id": "stock.widget",
"type": "widget",
"properties": {
"kind": { "type": "string" },
"amount": { "type": "integer" }
}
},
{
"$id": "super_stock.widget",
"type": "stock.widget",
"properties": {
"super_amount": { "type": "integer" }
}
},
{
"$id": "family_widget",
}
],
"schemas": {
"family_widget": {
"$family": "widget"
},
{
"$id": "family_stock_widget",
"family_stock_widget": {
"$family": "stock.widget"
}
]
}
},
"tests": [
{
"description": "Base widget resolves stock widget horizontally",
"schema_id": "family_widget",
"data": { "type": "widget", "kind": "stock", "amount": 5 },
"data": {
"type": "widget",
"kind": "stock",
"amount": 5
},
"action": "validate",
"expect": { "success": true }
"expect": {
"success": true
}
},
{
"description": "Base widget resolves nested super stock widget natively via descendants crawl",
"schema_id": "family_widget",
"data": { "type": "widget", "kind": "super_stock", "amount": 5, "super_amount": 10 },
"data": {
"type": "widget",
"kind": "super_stock",
"amount": 5,
"super_amount": 10
},
"action": "validate",
"expect": { "success": true }
"expect": {
"success": true
}
},
{
"description": "stock.widget explicit family resolves nested super stock via fast path",
"schema_id": "family_stock_widget",
"data": { "type": "widget", "kind": "super_stock", "amount": 5, "super_amount": 10 },
"data": {
"type": "widget",
"kind": "super_stock",
"amount": 5,
"super_amount": 10
},
"action": "validate",
"expect": { "success": true }
"expect": {
"success": true
}
},
{
"description": "stock.widget fails when presented an invalid payload constraint",
"schema_id": "family_stock_widget",
"data": { "type": "widget", "kind": "super_stock", "amount": 5, "super_amount": "not_an_int" },
"data": {
"type": "widget",
"kind": "super_stock",
"amount": 5,
"super_amount": "not_an_int"
},
"action": "validate",
"expect": { "success": false, "errors": [ { "code": "INVALID_TYPE", "details": { "path": "super_amount" } } ] }
"expect": {
"success": false,
"errors": [
{
"code": "INVALID_TYPE",
"details": {
"path": "super_amount"
}
}
]
}
}
]
},
@ -266,212 +398,241 @@
"types": [
{
"name": "entity",
"variations": ["entity", "person", "bot"],
"schemas": [
{
"$id": "entity",
"variations": [
"entity",
"person",
"bot"
],
"schemas": {
"entity": {
"type": "object",
"properties": {
"type": { "type": "string" }
"type": {
"type": "string"
}
}
}
]
}
},
{
"name": "person",
"variations": ["person"],
"schemas": [
{
"$id": "person",
"variations": [
"person"
],
"schemas": {
"person": {
"type": "entity"
},
{
"$id": "full.person",
"full.person": {
"type": "person",
"properties": {
"kind": { "type": "string" },
"age": { "type": "integer" }
"kind": {
"type": "string"
},
"age": {
"type": "integer"
}
},
"required": ["age"]
"required": [
"age"
]
}
]
}
},
{
"name": "bot",
"variations": ["bot"],
"schemas": [
{
"$id": "bot",
"variations": [
"bot"
],
"schemas": {
"bot": {
"type": "entity"
},
{
"$id": "full.bot",
"full.bot": {
"type": "bot",
"properties": {
"kind": { "type": "string" },
"version": { "type": "string" }
"kind": {
"type": "string"
},
"version": {
"type": "string"
}
},
"required": ["version"]
"required": [
"version"
]
}
}
}
],
"schemas": {
"oneOf_union": {
"oneOf": [
{
"type": "full.person"
},
{
"type": "full.bot"
}
]
}
],
"schemas": [
{
"$id": "oneOf_union",
"oneOf": [
{ "type": "full.person" },
{ "type": "full.bot" }
]
}
]
}
},
"tests": [
{
"description": "Throws MISSING_TYPE if discriminator matches neither",
"schema_id": "oneOf_union",
"data": { "kind": "full", "age": 5 },
"data": {
"kind": "full",
"age": 5
},
"action": "validate",
"expect": { "success": false, "errors": [ { "code": "MISSING_TYPE", "details": { "path": "" } } ] }
"expect": {
"success": false,
"errors": [
{
"code": "MISSING_TYPE",
"details": {
"path": ""
}
}
]
}
},
{
"description": "Golden match throws pure structural error exclusively on person",
"schema_id": "oneOf_union",
"data": { "type": "person", "kind": "full", "age": "five" },
"data": {
"type": "person",
"kind": "full",
"age": "five"
},
"action": "validate",
"expect": { "success": false, "errors": [ { "code": "INVALID_TYPE", "details": { "path": "age" } } ] }
"expect": {
"success": false,
"errors": [
{
"code": "INVALID_TYPE",
"details": {
"path": "age"
}
}
]
}
},
{
"description": "Golden matches perfectly",
"schema_id": "oneOf_union",
"data": { "type": "person", "kind": "full", "age": 5 },
"data": {
"type": "person",
"kind": "full",
"age": 5
},
"action": "validate",
"expect": { "success": true }
"expect": {
"success": true
}
},
{
"description": "Fails nicely with NO_ONEOF_MATCH",
"schema_id": "oneOf_union",
"data": { "type": "alien", "kind": "full", "age": 5 },
"data": {
"type": "alien",
"kind": "full",
"age": 5
},
"action": "validate",
"expect": { "success": false, "errors": [ { "code": "NO_ONEOF_MATCH", "details": { "path": "" } } ] }
"expect": {
"success": false,
"errors": [
{
"code": "NO_ONEOF_MATCH",
"details": {
"path": ""
}
}
]
}
}
]
},
{
"description": "JSONB Field Bubble oneOf Discrimination (Promoted IDs)",
"database": {
"schemas": [
{
"$id": "metadata",
"schemas": {
"metadata": {
"type": "object",
"properties": {
"type": { "type": "string" }
"type": {
"type": "string"
}
}
},
{
"$id": "invoice.metadata",
"invoice.metadata": {
"type": "metadata",
"properties": {
"invoice_id": { "type": "integer" }
"invoice_id": {
"type": "integer"
}
},
"required": ["invoice_id"]
"required": [
"invoice_id"
]
},
{
"$id": "payment.metadata",
"payment.metadata": {
"type": "metadata",
"properties": {
"payment_id": { "type": "integer" }
"payment_id": {
"type": "integer"
}
},
"required": ["payment_id"]
"required": [
"payment_id"
]
},
{
"$id": "oneOf_bubble",
"oneOf_bubble": {
"oneOf": [
{ "type": "invoice.metadata" },
{ "type": "payment.metadata" }
{
"type": "invoice.metadata"
},
{
"type": "payment.metadata"
}
]
}
]
}
},
"tests": [
{
"description": "Extracts golden match natively from explicit JSONB type discriminator",
"schema_id": "oneOf_bubble",
"data": { "type": "invoice.metadata", "invoice_id": "nan" },
"data": {
"type": "invoice.metadata",
"invoice_id": "nan"
},
"action": "validate",
"expect": {
"success": false,
"errors": [ { "code": "INVALID_TYPE", "details": { "path": "invoice_id" } } ]
"errors": [
{
"code": "INVALID_TYPE",
"details": {
"path": "invoice_id"
}
}
]
}
},
{
"description": "Valid payload succeeds perfectly in JSONB universe",
"schema_id": "oneOf_bubble",
"data": { "type": "payment.metadata", "payment_id": 123 },
"action": "validate",
"expect": { "success": true }
}
]
},
{
"description": "Standard JSON Schema oneOf",
"database": {
"schemas": [
{
"$id": "oneOf_scalars",
"oneOf": [
{ "type": "integer" },
{ "minimum": 2 }
]
"data": {
"type": "payment.metadata",
"payment_id": 123
},
{
"$id": "oneOf_dedupe",
"oneOf": [
{
"type": "object",
"properties": {
"shared": { "type": "integer" }
},
"required": ["shared"]
},
{
"type": "object",
"properties": {
"shared": { "type": "integer" },
"extra": { "type": "string" }
},
"required": ["shared", "extra"]
}
]
}
]
},
"tests": [
{
"description": "Valid exclusively against first scalar choice",
"schema_id": "oneOf_scalars",
"data": 1,
"action": "validate",
"expect": { "success": true }
},
{
"description": "Fails mathematically if matches both schemas natively",
"schema_id": "oneOf_scalars",
"data": 3,
"action": "validate",
"expect": { "success": false }
},
{
"description": "Deduper merges shared errors dynamically exactly like JSON Schema",
"schema_id": "oneOf_dedupe",
"data": { "shared": "nan" },
"action": "validate",
"expect": {
"success": false,
"errors": [
{ "code": "NO_ONEOF_MATCH", "details": { "path": "" } },
{ "code": "INVALID_TYPE", "details": { "path": "shared" } }
]
"success": true
}
}
]

View File

@ -2,8 +2,8 @@
{
"description": "a schema given for prefixItems",
"database": {
"schemas": [
{
"schemas": {
"prefixItems_0_0": {
"prefixItems": [
{
"type": "integer"
@ -11,10 +11,9 @@
{
"type": "string"
}
],
"$id": "prefixItems_0_0"
]
}
]
}
},
"tests": [
{
@ -92,15 +91,14 @@
{
"description": "prefixItems with boolean schemas",
"database": {
"schemas": [
{
"schemas": {
"prefixItems_1_0": {
"prefixItems": [
true,
false
],
"$id": "prefixItems_1_0"
]
}
]
}
},
"tests": [
{
@ -140,17 +138,16 @@
{
"description": "additional items are allowed by default",
"database": {
"schemas": [
{
"schemas": {
"prefixItems_2_0": {
"prefixItems": [
{
"type": "integer"
}
],
"extensible": true,
"$id": "prefixItems_2_0"
"extensible": true
}
]
}
},
"tests": [
{
@ -171,16 +168,15 @@
{
"description": "prefixItems with null instance elements",
"database": {
"schemas": [
{
"schemas": {
"prefixItems_3_0": {
"prefixItems": [
{
"type": "null"
}
],
"$id": "prefixItems_3_0"
]
}
]
}
},
"tests": [
{
@ -199,17 +195,16 @@
{
"description": "extensible: true allows extra items with prefixItems",
"database": {
"schemas": [
{
"schemas": {
"prefixItems_4_0": {
"prefixItems": [
{
"type": "integer"
}
],
"extensible": true,
"$id": "prefixItems_4_0"
"extensible": true
}
]
}
},
"tests": [
{

View File

@ -2,12 +2,11 @@
{
"description": "integer type matches integers",
"database": {
"schemas": [
{
"type": "integer",
"$id": "type_0_0"
"schemas": {
"type_0_0": {
"type": "integer"
}
]
}
},
"tests": [
{
@ -96,12 +95,11 @@
{
"description": "number type matches numbers",
"database": {
"schemas": [
{
"type": "number",
"$id": "type_1_0"
"schemas": {
"type_1_0": {
"type": "number"
}
]
}
},
"tests": [
{
@ -190,12 +188,11 @@
{
"description": "string type matches strings",
"database": {
"schemas": [
{
"type": "string",
"$id": "type_2_0"
"schemas": {
"type_2_0": {
"type": "string"
}
]
}
},
"tests": [
{
@ -284,12 +281,11 @@
{
"description": "object type matches objects",
"database": {
"schemas": [
{
"type": "object",
"$id": "type_3_0"
"schemas": {
"type_3_0": {
"type": "object"
}
]
}
},
"tests": [
{
@ -360,12 +356,11 @@
{
"description": "array type matches arrays",
"database": {
"schemas": [
{
"type": "array",
"$id": "type_4_0"
"schemas": {
"type_4_0": {
"type": "array"
}
]
}
},
"tests": [
{
@ -436,12 +431,11 @@
{
"description": "boolean type matches booleans",
"database": {
"schemas": [
{
"type": "boolean",
"$id": "type_5_0"
"schemas": {
"type_5_0": {
"type": "boolean"
}
]
}
},
"tests": [
{
@ -539,12 +533,11 @@
{
"description": "null type matches only the null object",
"database": {
"schemas": [
{
"type": "null",
"$id": "type_6_0"
"schemas": {
"type_6_0": {
"type": "null"
}
]
}
},
"tests": [
{
@ -642,15 +635,14 @@
{
"description": "multiple types can be specified in an array",
"database": {
"schemas": [
{
"schemas": {
"type_7_0": {
"type": [
"integer",
"string"
],
"$id": "type_7_0"
]
}
]
}
},
"tests": [
{
@ -721,14 +713,13 @@
{
"description": "type as array with one item",
"database": {
"schemas": [
{
"schemas": {
"type_8_0": {
"type": [
"string"
],
"$id": "type_8_0"
]
}
]
}
},
"tests": [
{
@ -754,16 +745,15 @@
{
"description": "type: array or object",
"database": {
"schemas": [
{
"schemas": {
"type_9_0": {
"type": [
"array",
"object"
],
"items": {},
"$id": "type_9_0"
"items": {}
}
]
}
},
"tests": [
{
@ -820,17 +810,16 @@
{
"description": "type: array, object or null",
"database": {
"schemas": [
{
"schemas": {
"type_10_0": {
"type": [
"array",
"object",
"null"
],
"items": {},
"$id": "type_10_0"
"items": {}
}
]
}
},
"tests": [
{
@ -887,13 +876,12 @@
{
"description": "extensible: true allows extra properties",
"database": {
"schemas": [
{
"schemas": {
"type_11_0": {
"type": "object",
"extensible": true,
"$id": "type_11_0"
"extensible": true
}
]
}
},
"tests": [
{

View File

@ -2,8 +2,8 @@
{
"description": "object properties validation",
"database": {
"schemas": [
{
"schemas": {
"properties_0_0": {
"properties": {
"foo": {
"type": "integer"
@ -11,10 +11,9 @@
"bar": {
"type": "string"
}
},
"$id": "properties_0_0"
}
}
]
}
},
"tests": [
{
@ -85,15 +84,14 @@
{
"description": "properties with boolean schema",
"database": {
"schemas": [
{
"schemas": {
"properties_1_0": {
"properties": {
"foo": true,
"bar": false
},
"$id": "properties_1_0"
}
}
]
}
},
"tests": [
{
@ -144,8 +142,8 @@
{
"description": "properties with escaped characters",
"database": {
"schemas": [
{
"schemas": {
"properties_2_0": {
"properties": {
"foo\nbar": {
"type": "number"
@ -165,10 +163,9 @@
"foo\fbar": {
"type": "number"
}
},
"$id": "properties_2_0"
}
}
]
}
},
"tests": [
{
@ -208,16 +205,15 @@
{
"description": "properties with null valued instance properties",
"database": {
"schemas": [
{
"schemas": {
"properties_3_0": {
"properties": {
"foo": {
"type": "null"
}
},
"$id": "properties_3_0"
}
}
]
}
},
"tests": [
{
@ -237,8 +233,8 @@
"description": "properties whose names are Javascript object property names",
"comment": "Ensure JS implementations don't universally consider e.g. __proto__ to always be present in an object.",
"database": {
"schemas": [
{
"schemas": {
"properties_4_0": {
"properties": {
"__proto__": {
"type": "number"
@ -253,10 +249,9 @@
"constructor": {
"type": "number"
}
},
"$id": "properties_4_0"
}
}
]
}
},
"tests": [
{
@ -343,17 +338,16 @@
{
"description": "extensible: true allows extra properties",
"database": {
"schemas": [
{
"schemas": {
"properties_5_0": {
"properties": {
"foo": {
"type": "integer"
}
},
"extensible": true,
"$id": "properties_5_0"
"extensible": true
}
]
}
},
"tests": [
{
@ -373,16 +367,15 @@
{
"description": "strict by default: extra properties invalid",
"database": {
"schemas": [
{
"schemas": {
"properties_6_0": {
"properties": {
"foo": {
"type": "string"
}
},
"$id": "properties_6_0"
}
}
]
}
},
"tests": [
{
@ -402,8 +395,8 @@
{
"description": "inheritance: nested object inherits strictness from strict parent",
"database": {
"schemas": [
{
"schemas": {
"properties_7_0": {
"properties": {
"nested": {
"properties": {
@ -412,10 +405,9 @@
}
}
}
},
"$id": "properties_7_0"
}
}
]
}
},
"tests": [
{
@ -437,8 +429,8 @@
{
"description": "override: nested object allows extra properties if extensible: true",
"database": {
"schemas": [
{
"schemas": {
"properties_8_0": {
"properties": {
"nested": {
"extensible": true,
@ -448,10 +440,9 @@
}
}
}
},
"$id": "properties_8_0"
}
}
]
}
},
"tests": [
{
@ -473,8 +464,8 @@
{
"description": "inheritance: nested object inherits looseness from loose parent",
"database": {
"schemas": [
{
"schemas": {
"properties_9_0": {
"extensible": true,
"properties": {
"nested": {
@ -484,10 +475,9 @@
}
}
}
},
"$id": "properties_9_0"
}
}
]
}
},
"tests": [
{
@ -509,8 +499,8 @@
{
"description": "override: nested object enforces strictness if extensible: false",
"database": {
"schemas": [
{
"schemas": {
"properties_10_0": {
"extensible": true,
"properties": {
"nested": {
@ -521,10 +511,9 @@
}
}
}
},
"$id": "properties_10_0"
}
}
]
}
},
"tests": [
{
@ -546,8 +535,8 @@
{
"description": "arrays: inline items inherit strictness from strict parent",
"database": {
"schemas": [
{
"schemas": {
"properties_11_0": {
"properties": {
"list": {
"type": "array",
@ -559,10 +548,9 @@
}
}
}
},
"$id": "properties_11_0"
}
}
]
}
},
"tests": [
{
@ -586,8 +574,8 @@
{
"description": "arrays: inline items inherit looseness from loose parent",
"database": {
"schemas": [
{
"schemas": {
"properties_12_0": {
"extensible": true,
"properties": {
"list": {
@ -600,10 +588,9 @@
}
}
}
},
"$id": "properties_12_0"
}
}
]
}
},
"tests": [
{

View File

@ -2,15 +2,14 @@
{
"description": "propertyNames validation",
"database": {
"schemas": [
{
"schemas": {
"propertyNames_0_0": {
"propertyNames": {
"maxLength": 3
},
"extensible": true,
"$id": "propertyNames_0_0"
"extensible": true
}
]
}
},
"tests": [
{
@ -83,15 +82,14 @@
{
"description": "propertyNames validation with pattern",
"database": {
"schemas": [
{
"schemas": {
"propertyNames_1_0": {
"propertyNames": {
"pattern": "^a+$"
},
"extensible": true,
"$id": "propertyNames_1_0"
"extensible": true
}
]
}
},
"tests": [
{
@ -132,13 +130,12 @@
{
"description": "propertyNames with boolean schema true",
"database": {
"schemas": [
{
"schemas": {
"propertyNames_2_0": {
"propertyNames": true,
"extensible": true,
"$id": "propertyNames_2_0"
"extensible": true
}
]
}
},
"tests": [
{
@ -166,13 +163,12 @@
{
"description": "propertyNames with boolean schema false",
"database": {
"schemas": [
{
"schemas": {
"propertyNames_3_0": {
"propertyNames": false,
"extensible": true,
"$id": "propertyNames_3_0"
"extensible": true
}
]
}
},
"tests": [
{
@ -200,15 +196,14 @@
{
"description": "propertyNames with const",
"database": {
"schemas": [
{
"schemas": {
"propertyNames_4_0": {
"propertyNames": {
"const": "foo"
},
"extensible": true,
"$id": "propertyNames_4_0"
"extensible": true
}
]
}
},
"tests": [
{
@ -247,18 +242,17 @@
{
"description": "propertyNames with enum",
"database": {
"schemas": [
{
"schemas": {
"propertyNames_5_0": {
"propertyNames": {
"enum": [
"foo",
"bar"
]
},
"extensible": true,
"$id": "propertyNames_5_0"
"extensible": true
}
]
}
},
"tests": [
{
@ -309,15 +303,14 @@
{
"description": "extensible: true allows extra properties (checked by propertyNames)",
"database": {
"schemas": [
{
"schemas": {
"propertyNames_6_0": {
"propertyNames": {
"maxLength": 3
},
"extensible": true,
"$id": "propertyNames_6_0"
"extensible": true
}
]
}
},
"tests": [
{

View File

@ -5,45 +5,60 @@
"puncs": [
{
"name": "get_organization",
"schemas": [
{
"$id": "get_organization.response",
"schemas": {
"get_organization.response": {
"type": "organization"
}
]
}
},
{
"name": "get_organizations",
"schemas": [
{
"$id": "get_organizations.response",
"schemas": {
"get_organizations.response": {
"type": "array",
"items": {
"$family": "organization"
}
}
]
}
},
{
"name": "get_person",
"schemas": [
{
"$id": "get_person.response",
"$family": "person"
"name": "get_light_organization",
"schemas": {
"get_light_organization.response": {
"$family": "light.organization"
}
]
}
},
{
"name": "get_full_organization",
"schemas": {
"get_full_organization.response": {
"$family": "full.organization"
}
}
},
{
"name": "get_orders",
"schemas": [
{
"$id": "get_orders.response",
"schemas": {
"get_orders.response": {
"type": "array",
"items": {
"type": "light.order"
}
}
]
}
},
{
"name": "get_widgets",
"schemas": {
"get_widgets.response": {
"type": "array",
"items": {
"$family": "widget"
}
}
}
}
],
"enums": [],
@ -144,9 +159,8 @@
"created_at": "timestamptz",
"type": "text"
},
"schemas": [
{
"$id": "entity",
"schemas": {
"entity": {
"type": "object",
"properties": {
"id": {
@ -168,7 +182,7 @@
}
}
}
],
},
"fields": [
"id",
"type",
@ -235,9 +249,8 @@
"organization",
"person"
],
"schemas": [
{
"$id": "organization",
"schemas": {
"organization": {
"type": "entity",
"properties": {
"name": {
@ -245,7 +258,7 @@
}
}
}
]
}
},
{
"name": "bot",
@ -260,7 +273,9 @@
"type",
"name",
"archived",
"created_at"
"created_at",
"token",
"role"
],
"grouped_fields": {
"entity": [
@ -273,7 +288,8 @@
"name"
],
"bot": [
"token"
"token",
"role"
]
},
"field_types": {
@ -282,11 +298,22 @@
"archived": "boolean",
"name": "text",
"token": "text",
"role": "text",
"created_at": "timestamptz"
},
"schemas": [
{
"$id": "bot",
"schemas": {
"bot": {
"type": "organization",
"properties": {
"token": {
"type": "string"
},
"role": {
"type": "string"
}
}
},
"light.bot": {
"type": "organization",
"properties": {
"token": {
@ -294,7 +321,7 @@
}
}
}
],
},
"variations": [
"bot"
]
@ -342,9 +369,8 @@
"age": "numeric",
"created_at": "timestamptz"
},
"schemas": [
{
"$id": "person",
"schemas": {
"person": {
"type": "organization",
"properties": {
"first_name": {
@ -358,13 +384,18 @@
}
}
},
{
"$id": "light.person",
"type": "person",
"properties": {}
"light.person": {
"type": "organization",
"properties": {
"first_name": {
"type": "string"
},
"last_name": {
"type": "string"
}
}
},
{
"$id": "full.person",
"full.person": {
"type": "person",
"properties": {
"phone_numbers": {
@ -423,7 +454,7 @@
}
}
}
],
},
"variations": [
"person"
]
@ -469,13 +500,12 @@
"target_type": "text",
"created_at": "timestamptz"
},
"schemas": [
{
"$id": "relationship",
"schemas": {
"relationship": {
"type": "entity",
"properties": {}
}
],
},
"variations": [
"contact",
"relationship"
@ -528,9 +558,8 @@
"is_primary": "boolean",
"created_at": "timestamptz"
},
"schemas": [
{
"$id": "contact",
"schemas": {
"contact": {
"type": "relationship",
"properties": {
"is_primary": {
@ -538,7 +567,7 @@
}
}
}
],
},
"variations": [
"contact"
]
@ -574,9 +603,8 @@
"number": "text",
"created_at": "timestamptz"
},
"schemas": [
{
"$id": "phone_number",
"schemas": {
"phone_number": {
"type": "entity",
"properties": {
"number": {
@ -584,7 +612,7 @@
}
}
}
],
},
"variations": [
"phone_number"
]
@ -620,9 +648,8 @@
"address": "text",
"created_at": "timestamptz"
},
"schemas": [
{
"$id": "email_address",
"schemas": {
"email_address": {
"type": "entity",
"properties": {
"address": {
@ -630,7 +657,7 @@
}
}
}
],
},
"variations": [
"email_address"
]
@ -666,9 +693,8 @@
"city": "text",
"created_at": "timestamptz"
},
"schemas": [
{
"$id": "address",
"schemas": {
"address": {
"type": "entity",
"properties": {
"city": {
@ -676,16 +702,15 @@
}
}
}
],
},
"variations": [
"address"
]
},
{
"name": "order",
"schemas": [
{
"$id": "order",
"schemas": {
"order": {
"type": "entity",
"properties": {
"total": {
@ -696,8 +721,7 @@
}
}
},
{
"$id": "light.order",
"light.order": {
"type": "order",
"properties": {
"customer": {
@ -705,8 +729,7 @@
}
}
},
{
"$id": "full.order",
"full.order": {
"type": "order",
"properties": {
"customer": {
@ -720,7 +743,7 @@
}
}
}
],
},
"hierarchy": [
"order",
"entity"
@ -781,9 +804,8 @@
},
{
"name": "order_line",
"schemas": [
{
"$id": "order_line",
"schemas": {
"order_line": {
"type": "entity",
"properties": {
"order_id": {
@ -797,7 +819,7 @@
}
}
}
],
},
"hierarchy": [
"order_line",
"entity"
@ -850,6 +872,59 @@
"variations": [
"order_line"
]
},
{
"name": "widget",
"hierarchy": [
"widget",
"entity"
],
"fields": [
"id",
"type",
"kind",
"archived",
"created_at"
],
"grouped_fields": {
"entity": [
"id",
"type",
"archived",
"created_at"
],
"widget": [
"kind"
]
},
"field_types": {
"id": "uuid",
"type": "text",
"kind": "text",
"archived": "boolean",
"created_at": "timestamptz"
},
"variations": [
"widget"
],
"schemas": {
"widget": {
"type": "entity",
"properties": {
"kind": {
"type": "string"
}
}
},
"stock.widget": {
"type": "widget",
"properties": {}
},
"tasks.widget": {
"type": "widget",
"properties": {}
}
}
}
]
},
@ -1004,17 +1079,17 @@
" 'target', CASE",
" WHEN entity_11.target_type = 'address' THEN",
" ((SELECT jsonb_build_object(",
" 'archived', entity_17.archived,",
" 'city', address_16.city,",
" 'created_at', entity_17.created_at,",
" 'id', entity_17.id,",
" 'type', entity_17.type",
" 'archived', entity_13.archived,",
" 'city', address_12.city,",
" 'created_at', entity_13.created_at,",
" 'id', entity_13.id,",
" 'type', entity_13.type",
" )",
" FROM agreego.address address_16",
" JOIN agreego.entity entity_17 ON entity_17.id = address_16.id",
" FROM agreego.address address_12",
" JOIN agreego.entity entity_13 ON entity_13.id = address_12.id",
" WHERE",
" NOT entity_17.archived",
" AND relationship_10.target_id = entity_17.id))",
" NOT entity_13.archived",
" AND relationship_10.target_id = entity_13.id))",
" WHEN entity_11.target_type = 'email_address' THEN",
" ((SELECT jsonb_build_object(",
" 'address', email_address_14.address,",
@ -1030,17 +1105,17 @@
" AND relationship_10.target_id = entity_15.id))",
" WHEN entity_11.target_type = 'phone_number' THEN",
" ((SELECT jsonb_build_object(",
" 'archived', entity_13.archived,",
" 'created_at', entity_13.created_at,",
" 'id', entity_13.id,",
" 'number', phone_number_12.number,",
" 'type', entity_13.type",
" 'archived', entity_17.archived,",
" 'created_at', entity_17.created_at,",
" 'id', entity_17.id,",
" 'number', phone_number_16.number,",
" 'type', entity_17.type",
" )",
" FROM agreego.phone_number phone_number_12",
" JOIN agreego.entity entity_13 ON entity_13.id = phone_number_12.id",
" FROM agreego.phone_number phone_number_16",
" JOIN agreego.entity entity_17 ON entity_17.id = phone_number_16.id",
" WHERE",
" NOT entity_13.archived",
" AND relationship_10.target_id = entity_13.id))",
" NOT entity_17.archived",
" AND relationship_10.target_id = entity_17.id))",
" ELSE NULL END,",
" 'type', entity_11.type",
" )), '[]'::jsonb)",
@ -1240,17 +1315,17 @@
" 'target', CASE",
" WHEN entity_11.target_type = 'address' THEN",
" ((SELECT jsonb_build_object(",
" 'archived', entity_17.archived,",
" 'city', address_16.city,",
" 'created_at', entity_17.created_at,",
" 'id', entity_17.id,",
" 'type', entity_17.type",
" 'archived', entity_13.archived,",
" 'city', address_12.city,",
" 'created_at', entity_13.created_at,",
" 'id', entity_13.id,",
" 'type', entity_13.type",
" )",
" FROM agreego.address address_16",
" JOIN agreego.entity entity_17 ON entity_17.id = address_16.id",
" FROM agreego.address address_12",
" JOIN agreego.entity entity_13 ON entity_13.id = address_12.id",
" WHERE",
" NOT entity_17.archived",
" AND relationship_10.target_id = entity_17.id))",
" NOT entity_13.archived",
" AND relationship_10.target_id = entity_13.id))",
" WHEN entity_11.target_type = 'email_address' THEN",
" ((SELECT jsonb_build_object(",
" 'address', email_address_14.address,",
@ -1266,17 +1341,17 @@
" AND relationship_10.target_id = entity_15.id))",
" WHEN entity_11.target_type = 'phone_number' THEN",
" ((SELECT jsonb_build_object(",
" 'archived', entity_13.archived,",
" 'created_at', entity_13.created_at,",
" 'id', entity_13.id,",
" 'number', phone_number_12.number,",
" 'type', entity_13.type",
" 'archived', entity_17.archived,",
" 'created_at', entity_17.created_at,",
" 'id', entity_17.id,",
" 'number', phone_number_16.number,",
" 'type', entity_17.type",
" )",
" FROM agreego.phone_number phone_number_12",
" JOIN agreego.entity entity_13 ON entity_13.id = phone_number_12.id",
" FROM agreego.phone_number phone_number_16",
" JOIN agreego.entity entity_17 ON entity_17.id = phone_number_16.id",
" WHERE",
" NOT entity_13.archived",
" AND relationship_10.target_id = entity_13.id))",
" NOT entity_17.archived",
" AND relationship_10.target_id = entity_17.id))",
" ELSE NULL END,",
" 'type', entity_11.type",
" )), '[]'::jsonb)",
@ -1398,7 +1473,7 @@
"success": true,
"sql": [
[
"(SELECT jsonb_strip_nulls((SELECT jsonb_build_object(",
"(SELECT jsonb_strip_nulls((SELECT COALESCE(jsonb_agg(jsonb_build_object(",
" 'archived', entity_3.archived,",
" 'created_at', entity_3.created_at,",
" 'id', entity_3.id,",
@ -1417,7 +1492,7 @@
" NOT entity_5.archived",
" AND relationship_2.target_id = entity_5.id),",
" 'type', entity_3.type",
")",
")), '[]'::jsonb)",
"FROM agreego.contact contact_1",
"JOIN agreego.relationship relationship_2 ON relationship_2.id = contact_1.id",
"JOIN agreego.entity entity_3 ON entity_3.id = relationship_2.id",
@ -1513,50 +1588,60 @@
"success": true,
"sql": [
[
"(SELECT jsonb_strip_nulls((SELECT COALESCE(jsonb_agg(jsonb_build_object(",
" 'id', organization_1.id,",
" 'type', CASE",
" WHEN organization_1.type = 'bot' THEN",
" ((SELECT jsonb_build_object(",
" 'archived', entity_5.archived,",
" 'created_at', entity_5.created_at,",
" 'id', entity_5.id,",
" 'name', organization_4.name,",
" 'token', bot_3.token,",
" 'type', entity_5.type",
" )",
" FROM agreego.bot bot_3",
" JOIN agreego.organization organization_4 ON organization_4.id = bot_3.id",
" JOIN agreego.entity entity_5 ON entity_5.id = organization_4.id",
" WHERE NOT entity_5.archived))",
" WHEN organization_1.type = 'organization' THEN",
" ((SELECT jsonb_build_object(",
" 'archived', entity_7.archived,",
" 'created_at', entity_7.created_at,",
" 'id', entity_7.id,",
" 'name', organization_6.name,",
" 'type', entity_7.type",
" )",
" FROM agreego.organization organization_6",
" JOIN agreego.entity entity_7 ON entity_7.id = organization_6.id",
" WHERE NOT entity_7.archived))",
" WHEN organization_1.type = 'person' THEN",
" ((SELECT jsonb_build_object(",
" 'age', person_8.age,",
" 'archived', entity_10.archived,",
" 'created_at', entity_10.created_at,",
" 'first_name', person_8.first_name,",
" 'id', entity_10.id,",
" 'last_name', person_8.last_name,",
" 'name', organization_9.name,",
" 'type', entity_10.type",
" )",
" FROM agreego.person person_8",
" JOIN agreego.organization organization_9 ON organization_9.id = person_8.id",
" JOIN agreego.entity entity_10 ON entity_10.id = organization_9.id",
" WHERE NOT entity_10.archived))",
" ELSE NULL END",
")), '[]'::jsonb)",
"(SELECT jsonb_strip_nulls((SELECT COALESCE(jsonb_agg(",
" CASE",
" WHEN organization_1.type = 'bot' THEN (",
" (SELECT jsonb_build_object(",
" 'archived', entity_5.archived,",
" 'created_at', entity_5.created_at,",
" 'id', entity_5.id,",
" 'name', organization_4.name,",
" 'role', bot_3.role,",
" 'token', bot_3.token,",
" 'type', entity_5.type",
" )",
" FROM agreego.bot bot_3",
" JOIN agreego.organization organization_4 ON organization_4.id = bot_3.id",
" JOIN agreego.entity entity_5 ON entity_5.id = organization_4.id",
" WHERE",
" NOT entity_5.archived",
" AND entity_5.id = entity_2.id)",
" )",
" WHEN organization_1.type = 'organization' THEN (",
" (SELECT jsonb_build_object(",
" 'archived', entity_7.archived,",
" 'created_at', entity_7.created_at,",
" 'id', entity_7.id,",
" 'name', organization_6.name,",
" 'type', entity_7.type",
" )",
" FROM agreego.organization organization_6",
" JOIN agreego.entity entity_7 ON entity_7.id = organization_6.id",
" WHERE",
" NOT entity_7.archived",
" AND entity_7.id = entity_2.id)",
" )",
" WHEN organization_1.type = 'person' THEN (",
" (SELECT jsonb_build_object(",
" 'age', person_8.age,",
" 'archived', entity_10.archived,",
" 'created_at', entity_10.created_at,",
" 'first_name', person_8.first_name,",
" 'id', entity_10.id,",
" 'last_name', person_8.last_name,",
" 'name', organization_9.name,",
" 'type', entity_10.type",
" )",
" FROM agreego.person person_8",
" JOIN agreego.organization organization_9 ON organization_9.id = person_8.id",
" JOIN agreego.entity entity_10 ON entity_10.id = organization_9.id",
" WHERE",
" NOT entity_10.archived",
" AND entity_10.id = entity_2.id)",
" )",
" ELSE NULL",
" END",
"), '[]'::jsonb)",
"FROM agreego.organization organization_1",
"JOIN agreego.entity entity_2 ON entity_2.id = organization_1.id",
"WHERE NOT entity_2.archived)))"
@ -1565,27 +1650,226 @@
}
},
{
"description": "Person select via a punc response with family",
"description": "Light organizations select via a punc response with family",
"action": "query",
"schema_id": "get_person.response",
"schema_id": "get_light_organization.response",
"expect": {
"success": true,
"sql": [
[
"(SELECT jsonb_strip_nulls((SELECT jsonb_build_object(",
" 'age', person_1.age,",
" 'archived', entity_3.archived,",
" 'created_at', entity_3.created_at,",
" 'first_name', person_1.first_name,",
" 'id', entity_3.id,",
" 'last_name', person_1.last_name,",
" 'name', organization_2.name,",
" 'type', entity_3.type",
")",
"FROM agreego.person person_1",
"JOIN agreego.organization organization_2 ON organization_2.id = person_1.id",
"JOIN agreego.entity entity_3 ON entity_3.id = organization_2.id",
"WHERE NOT entity_3.archived)))"
"(SELECT jsonb_strip_nulls((SELECT ",
" CASE",
" WHEN organization_1.type = 'bot' THEN (",
" (SELECT jsonb_build_object(",
" 'archived', entity_5.archived,",
" 'created_at', entity_5.created_at,",
" 'id', entity_5.id,",
" 'name', organization_4.name,",
" 'token', bot_3.token,",
" 'type', entity_5.type",
" )",
" FROM agreego.bot bot_3",
" JOIN agreego.organization organization_4 ON organization_4.id = bot_3.id",
" JOIN agreego.entity entity_5 ON entity_5.id = organization_4.id",
" WHERE NOT entity_5.archived AND entity_5.id = entity_2.id)",
" )",
" WHEN organization_1.type = 'person' THEN (",
" (SELECT jsonb_build_object(",
" 'archived', entity_8.archived,",
" 'created_at', entity_8.created_at,",
" 'first_name', person_6.first_name,",
" 'id', entity_8.id,",
" 'last_name', person_6.last_name,",
" 'name', organization_7.name,",
" 'type', entity_8.type",
" )",
" FROM agreego.person person_6",
" JOIN agreego.organization organization_7 ON organization_7.id = person_6.id",
" JOIN agreego.entity entity_8 ON entity_8.id = organization_7.id",
" WHERE NOT entity_8.archived AND entity_8.id = entity_2.id)",
" )",
" ELSE NULL",
" END",
"FROM agreego.organization organization_1",
"JOIN agreego.entity entity_2 ON entity_2.id = organization_1.id",
"WHERE NOT entity_2.archived)))"
]
]
}
},
{
"description": "Full organizations select via a punc response with family",
"action": "query",
"schema_id": "get_full_organization.response",
"expect": {
"success": true,
"sql": [
[
"(SELECT jsonb_strip_nulls((SELECT CASE",
" WHEN organization_1.type = 'person' THEN (",
" (SELECT jsonb_build_object(",
" 'addresses',",
" (SELECT COALESCE(jsonb_agg(jsonb_build_object(",
" 'archived', entity_8.archived,",
" 'created_at', entity_8.created_at,",
" 'id', entity_8.id,",
" 'is_primary', contact_6.is_primary,",
" 'target',",
" (SELECT jsonb_build_object(",
" 'archived', entity_10.archived,",
" 'city', address_9.city,",
" 'created_at', entity_10.created_at,",
" 'id', entity_10.id,",
" 'type', entity_10.type",
" )",
" FROM agreego.address address_9",
" JOIN agreego.entity entity_10 ON entity_10.id = address_9.id",
" WHERE",
" NOT entity_10.archived",
" AND relationship_7.target_id = entity_10.id),",
" 'type', entity_8.type",
" )), '[]'::jsonb)",
" FROM agreego.contact contact_6",
" JOIN agreego.relationship relationship_7 ON relationship_7.id = contact_6.id",
" JOIN agreego.entity entity_8 ON entity_8.id = relationship_7.id",
" WHERE",
" NOT entity_8.archived",
" AND relationship_7.target_type = 'address'",
" AND relationship_7.source_id = entity_5.id),",
" 'age', person_3.age,",
" 'archived', entity_5.archived,",
" 'contacts',",
" (SELECT COALESCE(jsonb_agg(jsonb_build_object(",
" 'archived', entity_13.archived,",
" 'created_at', entity_13.created_at,",
" 'id', entity_13.id,",
" 'is_primary', contact_11.is_primary,",
" 'target',",
" CASE",
" WHEN entity_13.target_type = 'address' THEN (",
" (SELECT jsonb_build_object(",
" 'archived', entity_15.archived,",
" 'city', address_14.city,",
" 'created_at', entity_15.created_at,",
" 'id', entity_15.id,",
" 'type', entity_15.type",
" )",
" FROM agreego.address address_14",
" JOIN agreego.entity entity_15 ON entity_15.id = address_14.id",
" WHERE",
" NOT entity_15.archived",
" AND relationship_12.target_id = entity_15.id)",
" )",
" WHEN entity_13.target_type = 'email_address' THEN (",
" (SELECT jsonb_build_object(",
" 'address', email_address_16.address,",
" 'archived', entity_17.archived,",
" 'created_at', entity_17.created_at,",
" 'id', entity_17.id,",
" 'type', entity_17.type",
" )",
" FROM agreego.email_address email_address_16",
" JOIN agreego.entity entity_17 ON entity_17.id = email_address_16.id",
" WHERE",
" NOT entity_17.archived",
" AND relationship_12.target_id = entity_17.id)",
" )",
" WHEN entity_13.target_type = 'phone_number' THEN (",
" (SELECT jsonb_build_object(",
" 'archived', entity_19.archived,",
" 'created_at', entity_19.created_at,",
" 'id', entity_19.id,",
" 'number', phone_number_18.number,",
" 'type', entity_19.type",
" )",
" FROM agreego.phone_number phone_number_18",
" JOIN agreego.entity entity_19 ON entity_19.id = phone_number_18.id",
" WHERE",
" NOT entity_19.archived",
" AND relationship_12.target_id = entity_19.id)",
" )",
" ELSE NULL",
" END,",
" 'type', entity_13.type",
" )), '[]'::jsonb)",
" FROM agreego.contact contact_11",
" JOIN agreego.relationship relationship_12 ON relationship_12.id = contact_11.id",
" JOIN agreego.entity entity_13 ON entity_13.id = relationship_12.id",
" WHERE",
" NOT entity_13.archived",
" AND relationship_12.source_id = entity_5.id),",
" 'created_at', entity_5.created_at,",
" 'email_addresses',",
" (SELECT COALESCE(jsonb_agg(jsonb_build_object(",
" 'archived', entity_22.archived,",
" 'created_at', entity_22.created_at,",
" 'id', entity_22.id,",
" 'is_primary', contact_20.is_primary,",
" 'target',",
" (SELECT jsonb_build_object(",
" 'address', email_address_23.address,",
" 'archived', entity_24.archived,",
" 'created_at', entity_24.created_at,",
" 'id', entity_24.id,",
" 'type', entity_24.type",
" )",
" FROM agreego.email_address email_address_23",
" JOIN agreego.entity entity_24 ON entity_24.id = email_address_23.id",
" WHERE",
" NOT entity_24.archived",
" AND relationship_21.target_id = entity_24.id),",
" 'type', entity_22.type",
" )), '[]'::jsonb)",
" FROM agreego.contact contact_20",
" JOIN agreego.relationship relationship_21 ON relationship_21.id = contact_20.id",
" JOIN agreego.entity entity_22 ON entity_22.id = relationship_21.id",
" WHERE",
" NOT entity_22.archived",
" AND relationship_21.target_type = 'email_address'",
" AND relationship_21.source_id = entity_5.id),",
" 'first_name', person_3.first_name,",
" 'id', entity_5.id,",
" 'last_name', person_3.last_name,",
" 'name', organization_4.name,",
" 'phone_numbers',",
" (SELECT COALESCE(jsonb_agg(jsonb_build_object(",
" 'archived', entity_27.archived,",
" 'created_at', entity_27.created_at,",
" 'id', entity_27.id,",
" 'is_primary', contact_25.is_primary,",
" 'target',",
" (SELECT jsonb_build_object(",
" 'archived', entity_29.archived,",
" 'created_at', entity_29.created_at,",
" 'id', entity_29.id,",
" 'number', phone_number_28.number,",
" 'type', entity_29.type",
" )",
" FROM agreego.phone_number phone_number_28",
" JOIN agreego.entity entity_29 ON entity_29.id = phone_number_28.id",
" WHERE",
" NOT entity_29.archived",
" AND relationship_26.target_id = entity_29.id),",
" 'type', entity_27.type",
" )), '[]'::jsonb)",
" FROM agreego.contact contact_25",
" JOIN agreego.relationship relationship_26 ON relationship_26.id = contact_25.id",
" JOIN agreego.entity entity_27 ON entity_27.id = relationship_26.id",
" WHERE",
" NOT entity_27.archived",
" AND relationship_26.target_type = 'phone_number'",
" AND relationship_26.source_id = entity_5.id),",
" 'type', entity_5.type",
" )",
" FROM agreego.person person_3",
" JOIN agreego.organization organization_4 ON organization_4.id = person_3.id",
" JOIN agreego.entity entity_5 ON entity_5.id = organization_4.id",
" WHERE NOT entity_5.archived AND entity_5.id = entity_2.id))",
" ELSE NULL",
"END",
"FROM agreego.organization organization_1",
"JOIN agreego.entity entity_2 ON entity_2.id = organization_1.id",
"WHERE NOT entity_2.archived)))"
]
]
}
@ -1629,6 +1913,44 @@
]
]
}
},
{
"description": "Widgets select via a punc response with family (STI)",
"action": "query",
"schema_id": "get_widgets.response",
"expect": {
"success": true,
"sql": [
[
"(SELECT jsonb_strip_nulls((SELECT COALESCE(jsonb_agg(",
" CASE",
" WHEN widget_1.kind = 'stock' THEN (",
" jsonb_build_object(",
" 'archived', entity_2.archived,",
" 'created_at', entity_2.created_at,",
" 'id', entity_2.id,",
" 'kind', widget_1.kind,",
" 'type', entity_2.type",
" )",
" )",
" WHEN widget_1.kind = 'tasks' THEN (",
" jsonb_build_object(",
" 'archived', entity_2.archived,",
" 'created_at', entity_2.created_at,",
" 'id', entity_2.id,",
" 'kind', widget_1.kind,",
" 'type', entity_2.type",
" )",
" )",
" ELSE NULL",
" END",
"), '[]'::jsonb)",
"FROM agreego.widget widget_1",
"JOIN agreego.entity entity_2 ON entity_2.id = widget_1.id",
"WHERE NOT entity_2.archived)))"
]
]
}
}
]
}

View File

@ -2,18 +2,17 @@
{
"description": "required validation",
"database": {
"schemas": [
{
"schemas": {
"required_0_0": {
"properties": {
"foo": {},
"bar": {}
},
"required": [
"foo"
],
"$id": "required_0_0"
]
}
]
}
},
"tests": [
{
@ -88,14 +87,13 @@
{
"description": "required default validation",
"database": {
"schemas": [
{
"schemas": {
"required_1_0": {
"properties": {
"foo": {}
},
"$id": "required_1_0"
}
}
]
}
},
"tests": [
{
@ -112,15 +110,14 @@
{
"description": "required with empty array",
"database": {
"schemas": [
{
"schemas": {
"required_2_0": {
"properties": {
"foo": {}
},
"required": [],
"$id": "required_2_0"
"required": []
}
]
}
},
"tests": [
{
@ -137,8 +134,8 @@
{
"description": "required with escaped characters",
"database": {
"schemas": [
{
"schemas": {
"required_3_0": {
"required": [
"foo\nbar",
"foo\"bar",
@ -147,10 +144,9 @@
"foo\tbar",
"foo\fbar"
],
"extensible": true,
"$id": "required_3_0"
"extensible": true
}
]
}
},
"tests": [
{
@ -187,17 +183,16 @@
"description": "required properties whose names are Javascript object property names",
"comment": "Ensure JS implementations don't universally consider e.g. __proto__ to always be present in an object.",
"database": {
"schemas": [
{
"schemas": {
"required_4_0": {
"required": [
"__proto__",
"toString",
"constructor"
],
"extensible": true,
"$id": "required_4_0"
"extensible": true
}
]
}
},
"tests": [
{
@ -284,15 +279,14 @@
{
"description": "extensible: true allows extra properties in required",
"database": {
"schemas": [
{
"schemas": {
"required_5_0": {
"required": [
"foo"
],
"extensible": true,
"$id": "required_5_0"
"extensible": true
}
]
}
},
"tests": [
{

View File

@ -2,13 +2,12 @@
{
"description": "uniqueItems validation",
"database": {
"schemas": [
{
"schemas": {
"uniqueItems_0_0": {
"uniqueItems": true,
"extensible": true,
"$id": "uniqueItems_0_0"
"extensible": true
}
]
}
},
"tests": [
{
@ -469,8 +468,8 @@
{
"description": "uniqueItems with an array of items",
"database": {
"schemas": [
{
"schemas": {
"uniqueItems_1_0": {
"prefixItems": [
{
"type": "boolean"
@ -480,10 +479,9 @@
}
],
"uniqueItems": true,
"extensible": true,
"$id": "uniqueItems_1_0"
"extensible": true
}
]
}
},
"tests": [
{
@ -595,8 +593,8 @@
{
"description": "uniqueItems with an array of items and additionalItems=false",
"database": {
"schemas": [
{
"schemas": {
"uniqueItems_2_0": {
"prefixItems": [
{
"type": "boolean"
@ -606,10 +604,9 @@
}
],
"uniqueItems": true,
"items": false,
"$id": "uniqueItems_2_0"
"items": false
}
]
}
},
"tests": [
{
@ -678,13 +675,12 @@
{
"description": "uniqueItems=false validation",
"database": {
"schemas": [
{
"schemas": {
"uniqueItems_3_0": {
"uniqueItems": false,
"extensible": true,
"$id": "uniqueItems_3_0"
"extensible": true
}
]
}
},
"tests": [
{
@ -924,8 +920,8 @@
{
"description": "uniqueItems=false with an array of items",
"database": {
"schemas": [
{
"schemas": {
"uniqueItems_4_0": {
"prefixItems": [
{
"type": "boolean"
@ -935,10 +931,9 @@
}
],
"uniqueItems": false,
"extensible": true,
"$id": "uniqueItems_4_0"
"extensible": true
}
]
}
},
"tests": [
{
@ -1050,8 +1045,8 @@
{
"description": "uniqueItems=false with an array of items and additionalItems=false",
"database": {
"schemas": [
{
"schemas": {
"uniqueItems_5_0": {
"prefixItems": [
{
"type": "boolean"
@ -1061,10 +1056,9 @@
}
],
"uniqueItems": false,
"items": false,
"$id": "uniqueItems_5_0"
"items": false
}
]
}
},
"tests": [
{
@ -1133,13 +1127,12 @@
{
"description": "extensible: true allows extra items in uniqueItems",
"database": {
"schemas": [
{
"schemas": {
"uniqueItems_6_0": {
"uniqueItems": true,
"extensible": true,
"$id": "uniqueItems_6_0"
"extensible": true
}
]
}
},
"tests": [
{

19
scratch.rs Normal file
View File

@ -0,0 +1,19 @@
use cellular_jspg::database::{Database, object::SchemaTypeOrArray};
use cellular_jspg::tests::fixtures::get_queryer_db;
fn main() {
let db_json = get_queryer_db();
let db = Database::from_json(&db_json).unwrap();
let keys: Vec<_> = db.schemas.keys().collect();
println!("Found schemas: {}", keys.len());
let mut found = false;
for k in keys {
if k.contains("email_addresses") {
println!("Contains email_addresses: {}", k);
found = true;
}
}
if !found {
println!("No email_addresses found at all!");
}
}

View File

@ -1,5 +1,6 @@
use crate::database::schema::Schema;
use serde::{Deserialize, Serialize};
use std::sync::Arc;
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[serde(default)]
@ -8,5 +9,6 @@ pub struct Enum {
pub module: String,
pub source: String,
pub values: Vec<String>,
pub schemas: Vec<Schema>,
#[serde(default)]
pub schemas: std::collections::BTreeMap<String, Arc<Schema>>,
}

View File

@ -4,6 +4,7 @@ pub mod executors;
pub mod formats;
pub mod page;
pub mod punc;
pub mod object;
pub mod relation;
pub mod schema;
pub mod r#type;
@ -23,7 +24,8 @@ use punc::Punc;
use relation::Relation;
use schema::Schema;
use serde_json::Value;
use std::collections::{HashMap, HashSet};
use std::collections::HashMap;
use std::sync::Arc;
use r#type::Type;
pub struct Database {
@ -31,9 +33,7 @@ pub struct Database {
pub types: HashMap<String, Type>,
pub puncs: HashMap<String, Punc>,
pub relations: HashMap<String, Relation>,
pub schemas: HashMap<String, Schema>,
pub descendants: HashMap<String, Vec<String>>,
pub depths: HashMap<String, usize>,
pub schemas: HashMap<String, Arc<Schema>>,
pub executor: Box<dyn DatabaseExecutor + Send + Sync>,
}
@ -45,8 +45,6 @@ impl Database {
relations: HashMap::new(),
puncs: HashMap::new(),
schemas: HashMap::new(),
descendants: HashMap::new(),
depths: HashMap::new(),
#[cfg(not(test))]
executor: Box::new(SpiExecutor::new()),
#[cfg(test)]
@ -127,22 +125,16 @@ impl Database {
}
}
if let Some(arr) = val.get("schemas").and_then(|v| v.as_array()) {
for (i, item) in arr.iter().enumerate() {
if let Some(map) = val.get("schemas").and_then(|v| v.as_object()) {
for (key, item) in map.iter() {
match serde_json::from_value::<Schema>(item.clone()) {
Ok(mut schema) => {
let id = schema
.obj
.id
.clone()
.unwrap_or_else(|| format!("schema_{}", i));
schema.obj.id = Some(id.clone());
db.schemas.insert(id, schema);
Ok(schema) => {
db.schemas.insert(key.clone(), Arc::new(schema));
}
Err(e) => {
errors.push(crate::drop::Error {
code: "DATABASE_SCHEMA_PARSE_FAILED".to_string(),
message: format!("Failed to parse database schema: {}", e),
message: format!("Failed to parse database schema key '{}': {}", key, e),
details: crate::drop::ErrorDetails::default(),
});
}
@ -187,19 +179,21 @@ impl Database {
pub fn compile(&mut self, errors: &mut Vec<crate::drop::Error>) {
let mut harvested = Vec::new();
for schema in self.schemas.values_mut() {
schema.collect_schemas(None, &mut harvested, errors);
for (id, schema_arc) in &self.schemas {
crate::database::schema::Schema::collect_schemas(schema_arc, id, id.clone(), &mut harvested, errors);
}
for (id, schema_arc) in harvested {
self.schemas.insert(id, schema_arc);
}
self.schemas.extend(harvested);
self.collect_schemas(errors);
self.collect_depths();
self.collect_descendants();
// Mathematically evaluate all property inheritances, formats, schemas, and foreign key edges topographically over OnceLocks
let mut visited = std::collections::HashSet::new();
for schema in self.schemas.values() {
schema.compile(self, &mut visited, errors);
for (id, schema_arc) in &self.schemas {
// First compile pass initializes exact structural root_id mapping to resolve DB constraints
let root_id = id.split('/').next().unwrap_or(id);
schema_arc.as_ref().compile(self, root_id, id.clone(), &mut visited, errors);
}
}
@ -209,90 +203,204 @@ impl Database {
// Pass 1: Extract all Schemas structurally off top level definitions into the master registry.
// Validate every node recursively via string filters natively!
for type_def in self.types.values() {
for mut schema in type_def.schemas.clone() {
schema.collect_schemas(None, &mut to_insert, errors);
for (id, schema_arc) in &type_def.schemas {
to_insert.push((id.clone(), Arc::clone(schema_arc)));
crate::database::schema::Schema::collect_schemas(schema_arc, id, id.clone(), &mut to_insert, errors);
}
}
for punc_def in self.puncs.values() {
for mut schema in punc_def.schemas.clone() {
schema.collect_schemas(None, &mut to_insert, errors);
for (id, schema_arc) in &punc_def.schemas {
to_insert.push((id.clone(), Arc::clone(schema_arc)));
crate::database::schema::Schema::collect_schemas(schema_arc, id, id.clone(), &mut to_insert, errors);
}
}
for enum_def in self.enums.values() {
for mut schema in enum_def.schemas.clone() {
schema.collect_schemas(None, &mut to_insert, errors);
for (id, schema_arc) in &enum_def.schemas {
to_insert.push((id.clone(), Arc::clone(schema_arc)));
crate::database::schema::Schema::collect_schemas(schema_arc, id, id.clone(), &mut to_insert, errors);
}
}
for (id, schema) in to_insert {
self.schemas.insert(id, schema);
for (id, schema_arc) in to_insert {
self.schemas.insert(id, schema_arc);
}
}
fn collect_depths(&mut self) {
let mut depths: HashMap<String, usize> = HashMap::new();
let schema_ids: Vec<String> = self.schemas.keys().cloned().collect();
/// Inspects the Postgres pg_constraint relations catalog to securely identify
/// the precise Foreign Key connecting a parent and child hierarchy path.
pub fn resolve_relation<'a>(
&'a self,
parent_type: &str,
child_type: &str,
prop_name: &str,
relative_keys: Option<&Vec<String>>,
is_array: bool,
schema_id: Option<&str>,
path: &str,
errors: &mut Vec<crate::drop::Error>,
) -> Option<(&'a crate::database::relation::Relation, bool)> {
// Enforce graph locality by ensuring we don't accidentally crawl to pure structural entity boundaries
if parent_type == "entity" && child_type == "entity" {
return None;
}
for id in schema_ids {
let mut current_id = id.clone();
let mut depth = 0;
let mut visited = HashSet::new();
let p_def = self.types.get(parent_type)?;
let c_def = self.types.get(child_type)?;
while let Some(schema) = self.schemas.get(&current_id) {
if !visited.insert(current_id.clone()) {
break; // Cycle detected
let mut matching_rels = Vec::new();
let mut directions = Vec::new();
// Scour the complete catalog for any Edge matching the inheritance scope of the two objects
// This automatically binds polymorphic structures (e.g. recognizing a relationship targeting User
// also natively binds instances specifically typed as Person).
let mut all_rels: Vec<&crate::database::relation::Relation> = self.relations.values().collect();
all_rels.sort_by(|a, b| a.constraint.cmp(&b.constraint));
for rel in all_rels {
let mut is_forward =
p_def.hierarchy.contains(&rel.source_type) && c_def.hierarchy.contains(&rel.destination_type);
let is_reverse =
p_def.hierarchy.contains(&rel.destination_type) && c_def.hierarchy.contains(&rel.source_type);
// Structural Cardinality Filtration:
// If the schema requires a collection (Array), it is mathematically impossible for a pure
// Forward scalar edge (where the parent holds exactly one UUID pointer) to fulfill a One-to-Many request.
// Thus, if it's an array, we fully reject pure Forward edges and only accept Reverse edges (or Junction edges).
if is_array && is_forward && !is_reverse {
is_forward = false;
}
if is_forward {
matching_rels.push(rel);
directions.push(true);
} else if is_reverse {
matching_rels.push(rel);
directions.push(false);
}
}
// Abort relation discovery early if no hierarchical inheritance match was found
if matching_rels.is_empty() {
let mut details = crate::drop::ErrorDetails {
path: path.to_string(),
..Default::default()
};
if let Some(sid) = schema_id {
details.schema = Some(sid.to_string());
}
errors.push(crate::drop::Error {
code: "EDGE_MISSING".to_string(),
message: format!(
"No database relation exists between '{}' and '{}' for property '{}'",
parent_type, child_type, prop_name
),
details,
});
return None;
}
// Ideal State: The objects only share a solitary structural relation, resolving ambiguity instantly.
if matching_rels.len() == 1 {
return Some((matching_rels[0], directions[0]));
}
let mut chosen_idx = 0;
let mut resolved = false;
// Exact Prefix Disambiguation: Determine if the database specifically names this constraint
// directly mapping to the JSON Schema property name (e.g., `fk_{child}_{property_name}`)
for (i, rel) in matching_rels.iter().enumerate() {
if let Some(prefix) = &rel.prefix {
if prop_name.starts_with(prefix)
|| prefix.starts_with(prop_name)
|| prefix.replace("_", "") == prop_name.replace("_", "")
{
chosen_idx = i;
resolved = true;
break;
}
if let Some(crate::database::schema::SchemaTypeOrArray::Single(t)) = &schema.obj.type_ {
if !crate::database::schema::is_primitive_type(t) {
current_id = t.clone();
depth += 1;
continue;
}
}
// Complex Subgraph Resolution: The database contains multiple equally explicit foreign key constraints
// linking these objects (such as pointing to `source` and `target` in Many-to-Many junction models).
if !resolved && relative_keys.is_some() {
// Twin Deduction Pass 1: We inspect the exact properties structurally defined inside the compiled payload
// to observe which explicit relation arrow the child payload natively consumes.
let keys = relative_keys.unwrap();
let mut consumed_rel_idx = None;
for (i, rel) in matching_rels.iter().enumerate() {
if let Some(prefix) = &rel.prefix {
if keys.contains(prefix) {
consumed_rel_idx = Some(i);
break; // Found the routing edge explicitly consumed by the schema payload
}
}
break;
}
depths.insert(id, depth);
}
self.depths = depths;
}
fn collect_descendants(&mut self) {
let mut direct_refs: HashMap<String, Vec<String>> = HashMap::new();
for (id, schema) in &self.schemas {
if let Some(crate::database::schema::SchemaTypeOrArray::Single(t)) = &schema.obj.type_ {
if !crate::database::schema::is_primitive_type(t) {
direct_refs
.entry(t.clone())
.or_default()
.push(id.clone());
// Twin Deduction Pass 2: Knowing which arrow points outbound, we can mathematically deduce its twin
// providing the reverse ownership on the same junction boundary must be the incoming Edge to the parent.
if let Some(used_idx) = consumed_rel_idx {
let used_rel = matching_rels[used_idx];
let mut twin_ids = Vec::new();
for (i, rel) in matching_rels.iter().enumerate() {
if i != used_idx
&& rel.source_type == used_rel.source_type
&& rel.destination_type == used_rel.destination_type
&& rel.prefix.is_some()
{
twin_ids.push(i);
}
}
if twin_ids.len() == 1 {
chosen_idx = twin_ids[0];
resolved = true;
}
}
}
// Cache exhaustive descendants matrix for generic $family string lookups natively
let mut descendants = HashMap::new();
for id in self.schemas.keys() {
let mut desc_set = HashSet::new();
Self::collect_descendants_recursively(id, &direct_refs, &mut desc_set);
let mut desc_vec: Vec<String> = desc_set.into_iter().collect();
desc_vec.sort();
descendants.insert(id.clone(), desc_vec);
}
self.descendants = descendants;
}
fn collect_descendants_recursively(
target: &str,
direct_refs: &std::collections::HashMap<String, Vec<String>>,
descendants: &mut std::collections::HashSet<String>,
) {
if let Some(children) = direct_refs.get(target) {
for child in children {
if descendants.insert(child.clone()) {
Self::collect_descendants_recursively(child, direct_refs, descendants);
// Implicit Base Fallback: If no complex explicit paths resolve, but exactly one relation
// sits entirely naked (without a constraint prefix), it must be the core structural parent ownership.
if !resolved {
let mut null_prefix_ids = Vec::new();
for (i, rel) in matching_rels.iter().enumerate() {
if rel.prefix.is_none() {
null_prefix_ids.push(i);
}
}
if null_prefix_ids.len() == 1 {
chosen_idx = null_prefix_ids[0];
resolved = true;
}
}
// If we exhausted all mathematical deduction pathways and STILL cannot isolate a single edge,
// we must abort rather than silently guessing. Returning None prevents arbitrary SQL generation
// and forces a clean structural error for the architect.
if !resolved {
let mut details = crate::drop::ErrorDetails {
path: path.to_string(),
context: serde_json::to_value(&matching_rels).ok(),
cause: Some("Multiple conflicting constraints found matching prefixes".to_string()),
..Default::default()
};
if let Some(sid) = schema_id {
details.schema = Some(sid.to_string());
}
errors.push(crate::drop::Error {
code: "AMBIGUOUS_TYPE_RELATIONS".to_string(),
message: format!(
"Ambiguous database relation between '{}' and '{}' for property '{}'",
parent_type, child_type, prop_name
),
details,
});
return None;
}
Some((matching_rels[chosen_idx], directions[chosen_idx]))
}
}

320
src/database/object.rs Normal file
View File

@ -0,0 +1,320 @@
use crate::database::schema::Schema;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::BTreeMap;
use std::sync::Arc;
use std::sync::OnceLock;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Case {
#[serde(skip_serializing_if = "Option::is_none")]
pub when: Option<Arc<Schema>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub then: Option<Arc<Schema>>,
#[serde(rename = "else")]
#[serde(skip_serializing_if = "Option::is_none")]
pub else_: Option<Arc<Schema>>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct SchemaObject {
// Core Schema Keywords
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(default)] // Allow missing type
#[serde(rename = "type")]
#[serde(skip_serializing_if = "Option::is_none")]
pub type_: Option<SchemaTypeOrArray>, // Handles string or array of strings
// Object Keywords
#[serde(skip_serializing_if = "Option::is_none")]
pub properties: Option<BTreeMap<String, Arc<Schema>>>,
#[serde(rename = "patternProperties")]
#[serde(skip_serializing_if = "Option::is_none")]
pub pattern_properties: Option<BTreeMap<String, Arc<Schema>>>,
#[serde(rename = "additionalProperties")]
#[serde(skip_serializing_if = "Option::is_none")]
pub additional_properties: Option<Arc<Schema>>,
#[serde(rename = "$family")]
#[serde(skip_serializing_if = "Option::is_none")]
pub family: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub required: Option<Vec<String>>,
// dependencies can be schema dependencies or property dependencies
#[serde(skip_serializing_if = "Option::is_none")]
pub dependencies: Option<BTreeMap<String, Dependency>>,
// Array Keywords
#[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")]
#[serde(skip_serializing_if = "Option::is_none")]
pub min_length: Option<f64>,
#[serde(rename = "maxLength")]
#[serde(skip_serializing_if = "Option::is_none")]
pub max_length: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub pattern: Option<String>,
// Array Validation
#[serde(rename = "minItems")]
#[serde(skip_serializing_if = "Option::is_none")]
pub min_items: Option<f64>,
#[serde(rename = "maxItems")]
#[serde(skip_serializing_if = "Option::is_none")]
pub max_items: Option<f64>,
#[serde(rename = "uniqueItems")]
#[serde(skip_serializing_if = "Option::is_none")]
pub unique_items: Option<bool>,
#[serde(rename = "contains")]
#[serde(skip_serializing_if = "Option::is_none")]
pub contains: Option<Arc<Schema>>,
#[serde(rename = "minContains")]
#[serde(skip_serializing_if = "Option::is_none")]
pub min_contains: Option<f64>,
#[serde(rename = "maxContains")]
#[serde(skip_serializing_if = "Option::is_none")]
pub max_contains: Option<f64>,
// Object Validation
#[serde(rename = "minProperties")]
#[serde(skip_serializing_if = "Option::is_none")]
pub min_properties: Option<f64>,
#[serde(rename = "maxProperties")]
#[serde(skip_serializing_if = "Option::is_none")]
pub max_properties: Option<f64>,
#[serde(rename = "propertyNames")]
#[serde(skip_serializing_if = "Option::is_none")]
pub property_names: Option<Arc<Schema>>,
// Numeric Validation
#[serde(skip_serializing_if = "Option::is_none")]
pub format: Option<String>,
#[serde(rename = "enum")]
#[serde(skip_serializing_if = "Option::is_none")]
pub enum_: Option<Vec<Value>>, // `enum` is a reserved keyword in Rust
#[serde(
default,
rename = "const",
deserialize_with = "crate::database::object::deserialize_some"
)]
#[serde(skip_serializing_if = "Option::is_none")]
pub const_: Option<Value>,
// Numeric Validation
#[serde(rename = "multipleOf")]
#[serde(skip_serializing_if = "Option::is_none")]
pub multiple_of: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub minimum: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub maximum: Option<f64>,
#[serde(rename = "exclusiveMinimum")]
#[serde(skip_serializing_if = "Option::is_none")]
pub exclusive_minimum: Option<f64>,
#[serde(rename = "exclusiveMaximum")]
#[serde(skip_serializing_if = "Option::is_none")]
pub exclusive_maximum: Option<f64>,
// Combining Keywords
#[serde(skip_serializing_if = "Option::is_none")]
pub cases: Option<Vec<Case>>,
#[serde(rename = "oneOf")]
#[serde(skip_serializing_if = "Option::is_none")]
pub one_of: Option<Vec<Arc<Schema>>>,
#[serde(rename = "not")]
#[serde(skip_serializing_if = "Option::is_none")]
pub not: Option<Arc<Schema>>,
// Custom Vocabularies
#[serde(skip_serializing_if = "Option::is_none")]
pub form: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub display: Option<Vec<String>>,
#[serde(rename = "enumNames")]
#[serde(skip_serializing_if = "Option::is_none")]
pub enum_names: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub control: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub actions: Option<BTreeMap<String, Action>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub computer: Option<String>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub extensible: Option<bool>,
#[serde(rename = "compiledProperties")]
#[serde(skip_deserializing)]
#[serde(skip_serializing_if = "crate::database::object::is_once_lock_vec_empty")]
#[serde(serialize_with = "crate::database::object::serialize_once_lock")]
pub compiled_property_names: OnceLock<Vec<String>>,
#[serde(skip)]
pub compiled_properties: OnceLock<BTreeMap<String, Arc<Schema>>>,
#[serde(rename = "compiledDiscriminator")]
#[serde(skip_deserializing)]
#[serde(skip_serializing_if = "crate::database::object::is_once_lock_string_empty")]
#[serde(serialize_with = "crate::database::object::serialize_once_lock")]
pub compiled_discriminator: OnceLock<String>,
#[serde(rename = "compiledOptions")]
#[serde(skip_deserializing)]
#[serde(skip_serializing_if = "crate::database::object::is_once_lock_map_empty")]
#[serde(serialize_with = "crate::database::object::serialize_once_lock")]
pub compiled_options: OnceLock<BTreeMap<String, (Option<usize>, Option<String>)>>,
#[serde(rename = "compiledEdges")]
#[serde(skip_deserializing)]
#[serde(skip_serializing_if = "crate::database::object::is_once_lock_map_empty")]
#[serde(serialize_with = "crate::database::object::serialize_once_lock")]
pub compiled_edges: OnceLock<BTreeMap<String, crate::database::edge::Edge>>,
#[serde(skip)]
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
#[derive(Clone)]
pub enum CompiledFormat {
Func(fn(&serde_json::Value) -> Result<(), Box<dyn std::error::Error + Send + Sync>>),
Regex(regex::Regex),
}
impl std::fmt::Debug for CompiledFormat {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
CompiledFormat::Func(_) => write!(f, "CompiledFormat::Func(...)"),
CompiledFormat::Regex(r) => write!(f, "CompiledFormat::Regex({:?})", r),
}
}
}
/// A wrapper for compiled regex patterns
#[derive(Debug, Clone)]
pub struct CompiledRegex(pub regex::Regex);
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum SchemaTypeOrArray {
Single(String),
Multiple(Vec<String>),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Action {
#[serde(skip_serializing_if = "Option::is_none")]
pub navigate: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub punc: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum Dependency {
Props(Vec<String>),
Schema(Arc<Schema>),
}
pub fn serialize_once_lock<T: serde::Serialize, S: serde::Serializer>(
lock: &OnceLock<T>,
serializer: S,
) -> Result<S::Ok, S::Error> {
if let Some(val) = lock.get() {
val.serialize(serializer)
} else {
serializer.serialize_none()
}
}
pub fn is_once_lock_map_empty<K, V>(lock: &OnceLock<std::collections::BTreeMap<K, V>>) -> bool {
lock.get().map_or(true, |m| m.is_empty())
}
pub fn is_once_lock_vec_empty<T>(lock: &OnceLock<Vec<T>>) -> bool {
lock.get().map_or(true, |v| v.is_empty())
}
pub fn is_once_lock_string_empty(lock: &OnceLock<String>) -> bool {
lock.get().map_or(true, |s| s.is_empty())
}
// Schema mirrors the Go Punc Generator's schema struct for consistency.
// It is an order-preserving representation of a JSON Schema.
pub fn deserialize_some<'de, D>(deserializer: D) -> Result<Option<Value>, D::Error>
where
D: serde::Deserializer<'de>,
{
let v = Value::deserialize(deserializer)?;
Ok(Some(v))
}
pub fn is_primitive_type(t: &str) -> bool {
matches!(
t,
"string" | "number" | "integer" | "boolean" | "object" | "array" | "null"
)
}
impl SchemaObject {
pub fn get_discriminator_value(&self, dim: &str, schema_id: &str) -> Option<String> {
let is_split = self
.compiled_properties
.get()
.map_or(false, |p| p.contains_key("kind"));
if dim == "kind" {
let base = schema_id.split('/').last().unwrap_or(schema_id);
if let Some(idx) = base.rfind('.') {
return Some(base[..idx].to_string());
}
}
if dim == "type" {
let base = schema_id.split('/').last().unwrap_or(schema_id);
if is_split {
return Some(base.split('.').next_back().unwrap_or(base).to_string());
} else {
return Some(base.to_string());
}
}
None
}
pub fn requires_uuid_path(&self, db: &crate::database::Database) -> bool {
// 1. Explicitly defines "id" either directly or via inheritance/extension?
if self
.compiled_properties
.get()
.map_or(false, |p| p.contains_key("id"))
{
return true;
}
// 2. Implicit table-backed rule: Does its $family boundary map directly to the global database catalog?
if let Some(family) = &self.family {
let base = family.split('.').next_back().unwrap_or(family);
if db.types.contains_key(base) {
return true;
}
}
false
}
}

View File

@ -1,6 +1,7 @@
use crate::database::page::Page;
use crate::database::schema::Schema;
use serde::{Deserialize, Serialize};
use std::sync::Arc;
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[serde(default)]
@ -16,5 +17,5 @@ pub struct Punc {
pub get: Option<String>,
pub page: Option<Page>,
#[serde(default)]
pub schemas: Vec<Schema>,
pub schemas: std::collections::BTreeMap<String, Arc<Schema>>,
}

File diff suppressed because it is too large Load Diff

View File

@ -2,6 +2,7 @@ use std::collections::HashSet;
use crate::database::schema::Schema;
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use serde_json::Value;
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
@ -38,5 +39,5 @@ pub struct Type {
pub default_fields: Vec<String>,
pub field_types: Option<Value>,
#[serde(default)]
pub schemas: Vec<Schema>,
pub schemas: std::collections::BTreeMap<String, Arc<Schema>>,
}

View File

@ -25,7 +25,7 @@ impl Merger {
let mut notifications_queue = Vec::new();
let target_schema = match self.db.schemas.get(schema_id) {
Some(s) => Arc::new(s.clone()),
Some(s) => Arc::clone(s),
None => {
return crate::drop::Drop::with_errors(vec![crate::drop::Error {
code: "MERGE_FAILED".to_string(),
@ -131,13 +131,43 @@ impl Merger {
pub(crate) fn merge_internal(
&self,
schema: Arc<crate::database::schema::Schema>,
mut schema: Arc<crate::database::schema::Schema>,
data: Value,
notifications: &mut Vec<String>,
) -> Result<Value, String> {
match data {
Value::Array(items) => self.merge_array(schema, items, notifications),
Value::Object(map) => self.merge_object(schema, map, notifications),
Value::Object(map) => {
if let Some(options) = schema.obj.compiled_options.get() {
if let Some(disc) = schema.obj.compiled_discriminator.get() {
let val = map.get(disc).and_then(|v| v.as_str());
if let Some(v) = val {
if let Some((idx_opt, target_id_opt)) = options.get(v) {
if let Some(target_id) = target_id_opt {
if let Some(target_schema) = self.db.schemas.get(target_id) {
schema = Arc::clone(target_schema);
} else {
return Err(format!("Polymorphic mapped target '{}' not found in database registry", target_id));
}
} else if let Some(idx) = idx_opt {
if let Some(target_schema) = schema.obj.one_of.as_ref().and_then(|options| options.get(*idx)) {
schema = Arc::clone(target_schema);
} else {
return Err(format!("Polymorphic index target '{}' not found in local oneOf array", idx));
}
} else {
return Err(format!("Polymorphic mapped target has no path"));
}
} else {
return Err(format!("Polymorphic discriminator {}='{}' matched no compiled options", disc, v));
}
} else {
return Err(format!("Polymorphic merging failed: missing required discriminator '{}'", disc));
}
}
}
self.merge_object(schema, map, notifications)
},
_ => Err("Invalid merge payload: root must be an Object or Array".to_string()),
}
}
@ -149,7 +179,7 @@ impl Merger {
notifications: &mut Vec<String>,
) -> Result<Value, String> {
let mut item_schema = schema.clone();
if let Some(crate::database::schema::SchemaTypeOrArray::Single(t)) = &schema.obj.type_ {
if let Some(crate::database::object::SchemaTypeOrArray::Single(t)) = &schema.obj.type_ {
if t == "array" {
if let Some(items_def) = &schema.obj.items {
item_schema = items_def.clone();
@ -195,7 +225,7 @@ impl Merger {
for (k, v) in obj {
// Always retain system and unmapped core fields natively implicitly mapped to the Postgres tables
if k == "id" || k == "type" || k == "created" {
entity_fields.insert(k.clone(), v.clone());
entity_fields.insert(k, v);
continue;
}
@ -214,18 +244,18 @@ impl Merger {
_ => "field", // Malformed edge data?
};
if typeof_v == "object" {
entity_objects.insert(k.clone(), (v.clone(), prop_schema.clone()));
entity_objects.insert(k, (v, prop_schema.clone()));
} else if typeof_v == "array" {
entity_arrays.insert(k.clone(), (v.clone(), prop_schema.clone()));
entity_arrays.insert(k, (v, prop_schema.clone()));
} else {
entity_fields.insert(k.clone(), v.clone());
entity_fields.insert(k, v);
}
} else {
// Not an edge! It's a raw Postgres column (e.g., JSONB, text[])
entity_fields.insert(k.clone(), v.clone());
entity_fields.insert(k, v);
}
} else if type_def.fields.contains(&k) {
entity_fields.insert(k.clone(), v.clone());
entity_fields.insert(k, v);
}
}
@ -369,7 +399,7 @@ impl Merger {
);
let mut item_schema = rel_schema.clone();
if let Some(crate::database::schema::SchemaTypeOrArray::Single(t)) =
if let Some(crate::database::object::SchemaTypeOrArray::Single(t)) =
&rel_schema.obj.type_
{
if t == "array" {
@ -504,7 +534,7 @@ impl Merger {
entity_change_kind = Some("create".to_string());
let mut new_fields = changes.clone();
let mut new_fields = changes;
new_fields.insert("id".to_string(), id_val);
new_fields.insert("type".to_string(), Value::String(type_name.to_string()));
new_fields.insert("created_by".to_string(), Value::String(user_id.to_string()));
@ -544,7 +574,7 @@ impl Merger {
Some("update".to_string())
};
let mut new_fields = changes.clone();
let mut new_fields = changes;
new_fields.insert(
"id".to_string(),
entity_fetched.as_ref().unwrap().get("id").unwrap().clone(),

View File

@ -1,5 +1,6 @@
use crate::database::Database;
use std::sync::Arc;
pub struct Compiler<'a> {
pub db: &'a Database,
pub filter_keys: &'a [String],
@ -16,6 +17,8 @@ pub struct Node<'a> {
pub property_name: Option<String>,
pub depth: usize,
pub ast_path: String,
pub is_polymorphic_branch: bool,
pub schema_id: Option<String>,
}
impl<'a> Compiler<'a> {
@ -27,7 +30,7 @@ impl<'a> Compiler<'a> {
.get(schema_id)
.ok_or_else(|| format!("Schema not found: {}", schema_id))?;
let target_schema = std::sync::Arc::new(schema.clone());
let target_schema = std::sync::Arc::clone(schema);
let mut compiler = Compiler {
db: &self.db,
@ -44,6 +47,8 @@ impl<'a> Compiler<'a> {
property_name: None,
depth: 0,
ast_path: String::new(),
is_polymorphic_branch: false,
schema_id: Some(schema_id.to_string()),
};
let (sql, _) = compiler.compile_node(node)?;
@ -55,7 +60,7 @@ impl<'a> Compiler<'a> {
fn compile_node(&mut self, node: Node<'a>) -> Result<(String, String), String> {
// Determine the base schema type (could be an array, object, or literal)
match &node.schema.obj.type_ {
Some(crate::database::schema::SchemaTypeOrArray::Single(t)) if t == "array" => {
Some(crate::database::object::SchemaTypeOrArray::Single(t)) if t == "array" => {
self.compile_array(node)
}
_ => self.compile_reference(node),
@ -63,17 +68,31 @@ impl<'a> Compiler<'a> {
}
fn compile_array(&mut self, node: Node<'a>) -> Result<(String, String), String> {
// 1. Array of DB Entities (`type` or `$family` pointing to a table limit)
if let Some(items) = &node.schema.obj.items {
let mut resolved_type = None;
if let Some(family_target) = items.obj.family.as_ref() {
let base_type_name = family_target
.split('.')
.next_back()
.unwrap_or(family_target);
resolved_type = self.db.types.get(base_type_name);
} else if let Some(base_type_name) = items.obj.identifier() {
resolved_type = self.db.types.get(&base_type_name);
if let Some(sid) = &node.schema_id {
resolved_type = self
.db
.types
.get(&sid.split('.').next_back().unwrap_or(sid).to_string());
}
if resolved_type.is_none() {
if let Some(family_target) = items.obj.family.as_ref() {
let base_type_name = family_target
.split('.')
.next_back()
.unwrap_or(family_target);
resolved_type = self.db.types.get(base_type_name);
} else if let Some(crate::database::object::SchemaTypeOrArray::Single(t)) = &items.obj.type_
{
if !crate::database::object::is_primitive_type(t) {
resolved_type = self
.db
.types
.get(&t.split('.').next_back().unwrap_or(t).to_string());
}
}
}
if let Some(type_def) = resolved_type {
@ -102,57 +121,62 @@ impl<'a> Compiler<'a> {
// Determine if this schema represents a Database Entity
let mut resolved_type = None;
if let Some(family_target) = node.schema.obj.family.as_ref() {
resolved_type = self.db.types.get(family_target);
} else if let Some(base_type_name) = node.schema.obj.identifier() {
if let Some(sid) = &node.schema_id {
let base_type_name = sid.split('.').next_back().unwrap_or(sid).to_string();
resolved_type = self.db.types.get(&base_type_name);
}
if resolved_type.is_none() {
if let Some(family_target) = node.schema.obj.family.as_ref() {
let base_type_name = family_target
.split('.')
.next_back()
.unwrap_or(family_target);
resolved_type = self.db.types.get(base_type_name);
} else if let Some(crate::database::object::SchemaTypeOrArray::Single(t)) =
&node.schema.obj.type_
{
if !crate::database::object::is_primitive_type(t) {
let base_type_name = t.split('.').next_back().unwrap_or(t).to_string();
resolved_type = self.db.types.get(&base_type_name);
}
}
}
if let Some(type_def) = resolved_type {
return self.compile_entity(type_def, node.clone(), false);
}
// Handle Direct Refs via type pointer
if let Some(crate::database::schema::SchemaTypeOrArray::Single(t)) = &node.schema.obj.type_ {
if !crate::database::schema::is_primitive_type(t) {
if let Some(crate::database::object::SchemaTypeOrArray::Single(t)) = &node.schema.obj.type_ {
if !crate::database::object::is_primitive_type(t) {
// If it's just an ad-hoc struct ref, we should resolve it
if let Some(target_schema) = self.db.schemas.get(t) {
let mut ref_node = node.clone();
ref_node.schema = std::sync::Arc::new(target_schema.clone());
ref_node.schema = Arc::clone(target_schema);
ref_node.schema_id = Some(t.clone());
return self.compile_node(ref_node);
}
return Err(format!("Unresolved schema type pointer: {}", t));
}
}
// Handle $family Polymorphism fallbacks for relations
if let Some(family_target) = &node.schema.obj.family {
let mut all_targets = vec![family_target.clone()];
if let Some(descendants) = self.db.descendants.get(family_target) {
all_targets.extend(descendants.clone());
// Handle Polymorphism fallbacks for relations
if node.schema.obj.family.is_some() || node.schema.obj.one_of.is_some() {
if let Some(options) = node.schema.obj.compiled_options.get() {
if options.len() == 1 {
let (_, target_opt) = options.values().next().unwrap();
if let Some(target_id) = target_opt {
let mut bypass_schema = crate::database::schema::Schema::default();
bypass_schema.obj.type_ = Some(crate::database::object::SchemaTypeOrArray::Single(
target_id.clone(),
));
let mut bypass_node = node.clone();
bypass_node.schema = std::sync::Arc::new(bypass_schema);
return self.compile_node(bypass_node);
}
}
}
if all_targets.len() == 1 {
let mut bypass_schema = crate::database::schema::Schema::default();
bypass_schema.obj.type_ = Some(crate::database::schema::SchemaTypeOrArray::Single(all_targets[0].clone()));
let mut bypass_node = node.clone();
bypass_node.schema = std::sync::Arc::new(bypass_schema);
return self.compile_node(bypass_node);
}
all_targets.sort();
let mut family_schemas = Vec::new();
for variation in &all_targets {
let mut ref_schema = crate::database::schema::Schema::default();
ref_schema.obj.type_ = Some(crate::database::schema::SchemaTypeOrArray::Single(variation.clone()));
family_schemas.push(std::sync::Arc::new(ref_schema));
}
return self.compile_one_of(&family_schemas, node);
}
// Handle oneOf Polymorphism fallbacks for relations
if let Some(one_of) = &node.schema.obj.one_of {
return self.compile_one_of(one_of, node.clone());
return self.compile_one_of(node);
}
// Just an inline object definition?
@ -179,17 +203,28 @@ impl<'a> Compiler<'a> {
) -> Result<(String, String), String> {
let (table_aliases, from_clauses) = self.compile_from_clause(r#type);
// 2. Map properties and build jsonb_build_object args
let mut select_args = self.compile_select_clause(r#type, &table_aliases, node.clone())?;
let jsonb_obj_sql = if node.schema.obj.family.is_some() || node.schema.obj.one_of.is_some() {
let base_alias = table_aliases
.get(&r#type.name)
.cloned()
.unwrap_or_else(|| node.parent_alias.to_string());
// 2.5 Inject polymorphism directly into the query object
let mut poly_args = self.compile_polymorphism_select(r#type, &table_aliases, node.clone())?;
select_args.append(&mut poly_args);
let mut case_node = node.clone();
case_node.parent_alias = base_alias.clone();
let arc_aliases = std::sync::Arc::new(table_aliases.clone());
case_node.parent_type_aliases = Some(arc_aliases);
case_node.parent_type = Some(r#type);
let jsonb_obj_sql = if select_args.is_empty() {
"jsonb_build_object()".to_string()
let (case_sql, _) = self.compile_one_of(case_node)?;
case_sql
} else {
format!("jsonb_build_object({})", select_args.join(", "))
let select_args = self.compile_select_clause(r#type, &table_aliases, node.clone())?;
if select_args.is_empty() {
"jsonb_build_object()".to_string()
} else {
format!("jsonb_build_object({})", select_args.join(", "))
}
};
// 3. Build WHERE clauses
@ -218,90 +253,6 @@ impl<'a> Compiler<'a> {
))
}
fn compile_polymorphism_select(
&mut self,
r#type: &'a crate::database::r#type::Type,
table_aliases: &std::collections::HashMap<String, String>,
node: Node<'a>,
) -> Result<Vec<String>, String> {
let mut select_args = Vec::new();
if let Some(family_target) = node.schema.obj.family.as_ref() {
let family_prefix = family_target.rfind('.').map(|idx| &family_target[..idx]);
let mut all_targets = vec![family_target.clone()];
if let Some(descendants) = self.db.descendants.get(family_target) {
all_targets.extend(descendants.clone());
}
// Filter targets to EXACTLY match the family_target prefix
let mut final_targets = Vec::new();
for target in all_targets {
let target_prefix = target.rfind('.').map(|idx| &target[..idx]);
if target_prefix == family_prefix {
final_targets.push(target);
}
}
final_targets.sort();
final_targets.dedup();
if final_targets.len() == 1 {
let variation = &final_targets[0];
if let Some(target_schema) = self.db.schemas.get(variation) {
let mut bypass_node = node.clone();
bypass_node.schema = std::sync::Arc::new(target_schema.clone());
let mut bypassed_args = self.compile_select_clause(r#type, table_aliases, bypass_node)?;
select_args.append(&mut bypassed_args);
} else {
return Err(format!("Could not find schema for variation {}", variation));
}
} else {
let mut family_schemas = Vec::new();
for variation in &final_targets {
if let Some(target_schema) = self.db.schemas.get(variation) {
family_schemas.push(std::sync::Arc::new(target_schema.clone()));
} else {
return Err(format!(
"Could not find schema metadata for variation {}",
variation
));
}
}
let base_alias = table_aliases
.get(&r#type.name)
.cloned()
.unwrap_or_else(|| node.parent_alias.to_string());
select_args.push(format!("'id', {}.id", base_alias));
let mut case_node = node.clone();
case_node.parent_alias = base_alias.clone();
let arc_aliases = std::sync::Arc::new(table_aliases.clone());
case_node.parent_type_aliases = Some(arc_aliases);
let (case_sql, _) = self.compile_one_of(&family_schemas, case_node)?;
select_args.push(format!("'type', {}", case_sql));
}
} else if let Some(one_of) = &node.schema.obj.one_of {
let base_alias = table_aliases
.get(&r#type.name)
.cloned()
.unwrap_or_else(|| node.parent_alias.to_string());
select_args.push(format!("'id', {}.id", base_alias));
let mut case_node = node.clone();
case_node.parent_alias = base_alias.clone();
let arc_aliases = std::sync::Arc::new(table_aliases.clone());
case_node.parent_type_aliases = Some(arc_aliases);
let (case_sql, _) = self.compile_one_of(one_of, case_node)?;
select_args.push(format!("'type', {}", case_sql));
}
Ok(select_args)
}
fn compile_object(
&mut self,
props: &std::collections::BTreeMap<String, std::sync::Arc<crate::database::schema::Schema>>,
@ -331,32 +282,95 @@ impl<'a> Compiler<'a> {
Ok((combined, "object".to_string()))
}
fn compile_one_of(
&mut self,
schemas: &[Arc<crate::database::schema::Schema>],
node: Node<'a>,
) -> Result<(String, String), String> {
fn compile_one_of(&mut self, node: Node<'a>) -> Result<(String, String), String> {
let mut case_statements = Vec::new();
let options = node
.schema
.obj
.compiled_options
.get()
.ok_or("Missing compiled options for polymorphism")?;
let disc = node
.schema
.obj
.compiled_discriminator
.get()
.ok_or("Missing compiled discriminator for polymorphism")?;
let type_col = if let Some(prop) = &node.property_name {
format!("{}_type", prop)
format!("{}_{}", prop, disc)
} else {
"type".to_string()
disc.to_string()
};
for option_schema in schemas {
if let Some(base_type_name) = option_schema.obj.identifier() {
// Generate the nested SQL for this specific target type
let mut child_node = node.clone();
child_node.schema = std::sync::Arc::clone(option_schema);
let (val_sql, _) = self.compile_node(child_node)?;
for (disc_val, (idx_opt, target_id_opt)) in options {
if let Some(target_id) = target_id_opt {
if let Some(target_schema) = self.db.schemas.get(target_id) {
let mut child_node = node.clone();
child_node.schema = Arc::clone(target_schema);
child_node.schema_id = Some(target_id.clone());
child_node.is_polymorphic_branch = true;
case_statements.push(format!(
"WHEN {}.{} = '{}' THEN ({})",
node.parent_alias, type_col, base_type_name, val_sql
));
let val_sql =
if disc == "kind" && node.parent_type.is_some() && node.parent_type_aliases.is_some() {
let aliases_arc = node.parent_type_aliases.as_ref().unwrap();
let aliases = aliases_arc.as_ref();
let p_type = node.parent_type.unwrap();
let select_args = self.compile_select_clause(p_type, aliases, child_node.clone())?;
if select_args.is_empty() {
"jsonb_build_object()".to_string()
} else {
format!("jsonb_build_object({})", select_args.join(", "))
}
} else {
let (sql, _) = self.compile_node(child_node)?;
sql
};
case_statements.push(format!(
"WHEN {}.{} = '{}' THEN ({})",
node.parent_alias, type_col, disc_val, val_sql
));
}
} else if let Some(idx) = idx_opt {
if let Some(target_schema) = node
.schema
.obj
.one_of
.as_ref()
.and_then(|options| options.get(*idx))
{
let mut child_node = node.clone();
child_node.schema = Arc::clone(target_schema);
child_node.is_polymorphic_branch = true;
let val_sql = if disc == "kind" && node.parent_type.is_some() && node.parent_type_aliases.is_some() {
let aliases_arc = node.parent_type_aliases.as_ref().unwrap();
let aliases = aliases_arc.as_ref();
let p_type = node.parent_type.unwrap();
let select_args = self.compile_select_clause(p_type, aliases, child_node.clone())?;
if select_args.is_empty() {
"jsonb_build_object()".to_string()
} else {
format!("jsonb_build_object({})", select_args.join(", "))
}
} else {
let (sql, _) = self.compile_node(child_node)?;
sql
};
case_statements.push(format!(
"WHEN {}.{} = '{}' THEN ({})",
node.parent_alias, type_col, disc_val, val_sql
));
}
}
}
if case_statements.is_empty() {
return Ok(("NULL".to_string(), "string".to_string()));
}
@ -401,26 +415,28 @@ impl<'a> Compiler<'a> {
) -> Result<Vec<String>, String> {
let mut select_args = Vec::new();
let grouped_fields = r#type.grouped_fields.as_ref().and_then(|v| v.as_object());
let merged_props = node.schema.obj.compiled_properties.get().unwrap();
let mut sorted_keys: Vec<&String> = merged_props.keys().collect();
sorted_keys.sort();
for prop_key in sorted_keys {
let prop_schema = &merged_props[prop_key];
let default_props = std::collections::BTreeMap::new();
let merged_props = node
.schema
.obj
.compiled_properties
.get()
.unwrap_or(&default_props);
for (prop_key, prop_schema) in merged_props {
let is_object_or_array = match &prop_schema.obj.type_ {
Some(crate::database::schema::SchemaTypeOrArray::Single(s)) => {
Some(crate::database::object::SchemaTypeOrArray::Single(s)) => {
s == "object" || s == "array"
}
Some(crate::database::schema::SchemaTypeOrArray::Multiple(v)) => {
Some(crate::database::object::SchemaTypeOrArray::Multiple(v)) => {
v.contains(&"object".to_string()) || v.contains(&"array".to_string())
}
_ => false,
};
let is_custom_object_pointer = match &prop_schema.obj.type_ {
Some(crate::database::schema::SchemaTypeOrArray::Single(s)) => {
!crate::database::schema::is_primitive_type(s)
Some(crate::database::object::SchemaTypeOrArray::Single(s)) => {
!crate::database::object::is_primitive_type(s)
}
_ => false,
};
@ -470,6 +486,8 @@ impl<'a> Compiler<'a> {
} else {
format!("{}/{}", node.ast_path, prop_key)
},
is_polymorphic_branch: false,
schema_id: None,
};
let (val_sql, val_type) = self.compile_node(child_node)?;
@ -509,6 +527,8 @@ impl<'a> Compiler<'a> {
self.compile_filter_conditions(r#type, type_aliases, &node, &base_alias, &mut where_clauses);
self.compile_polymorphic_bounds(r#type, type_aliases, &node, &mut where_clauses);
let start_len = where_clauses.len();
self.compile_relation_conditions(
r#type,
type_aliases,
@ -517,6 +537,14 @@ impl<'a> Compiler<'a> {
&mut where_clauses,
)?;
if node.is_polymorphic_branch && where_clauses.len() == start_len {
if let Some(parent_aliases) = &node.parent_type_aliases {
if let Some(outer_entity_alias) = parent_aliases.get("entity") {
where_clauses.push(format!("{}.id = {}.id", entity_alias, outer_entity_alias));
}
}
}
Ok(where_clauses)
}
@ -541,8 +569,12 @@ impl<'a> Compiler<'a> {
.unwrap_or(family_target)
.to_string(),
);
} else if let Some(lookup_key) = prop_schema.obj.identifier() {
bound_type_name = Some(lookup_key);
} else if let Some(crate::database::object::SchemaTypeOrArray::Single(t)) =
&prop_schema.obj.type_
{
if !crate::database::object::is_primitive_type(t) {
bound_type_name = Some(t.split('.').next_back().unwrap_or(t).to_string());
}
}
if let Some(type_name) = bound_type_name {

View File

@ -1439,6 +1439,18 @@ fn test_queryer_0_10() {
crate::tests::runner::run_test_case(&path, 0, 10).unwrap();
}
#[test]
fn test_queryer_0_11() {
let path = format!("{}/fixtures/queryer.json", env!("CARGO_MANIFEST_DIR"));
crate::tests::runner::run_test_case(&path, 0, 11).unwrap();
}
#[test]
fn test_queryer_0_12() {
let path = format!("{}/fixtures/queryer.json", env!("CARGO_MANIFEST_DIR"));
crate::tests::runner::run_test_case(&path, 0, 12).unwrap();
}
#[test]
fn test_polymorphism_0_0() {
let path = format!("{}/fixtures/polymorphism.json", env!("CARGO_MANIFEST_DIR"));
@ -1541,24 +1553,6 @@ fn test_polymorphism_4_1() {
crate::tests::runner::run_test_case(&path, 4, 1).unwrap();
}
#[test]
fn test_polymorphism_5_0() {
let path = format!("{}/fixtures/polymorphism.json", env!("CARGO_MANIFEST_DIR"));
crate::tests::runner::run_test_case(&path, 5, 0).unwrap();
}
#[test]
fn test_polymorphism_5_1() {
let path = format!("{}/fixtures/polymorphism.json", env!("CARGO_MANIFEST_DIR"));
crate::tests::runner::run_test_case(&path, 5, 1).unwrap();
}
#[test]
fn test_polymorphism_5_2() {
let path = format!("{}/fixtures/polymorphism.json", env!("CARGO_MANIFEST_DIR"));
crate::tests::runner::run_test_case(&path, 5, 2).unwrap();
}
#[test]
fn test_not_0_0() {
let path = format!("{}/fixtures/not.json", env!("CARGO_MANIFEST_DIR"));
@ -3546,9 +3540,9 @@ fn test_paths_1_0() {
}
#[test]
fn test_paths_1_1() {
fn test_paths_2_0() {
let path = format!("{}/fixtures/paths.json", env!("CARGO_MANIFEST_DIR"));
crate::tests::runner::run_test_case(&path, 1, 1).unwrap();
crate::tests::runner::run_test_case(&path, 2, 0).unwrap();
}
#[test]
@ -3689,6 +3683,12 @@ fn test_database_4_0() {
crate::tests::runner::run_test_case(&path, 4, 0).unwrap();
}
#[test]
fn test_database_5_0() {
let path = format!("{}/fixtures/database.json", env!("CARGO_MANIFEST_DIR"));
crate::tests::runner::run_test_case(&path, 5, 0).unwrap();
}
#[test]
fn test_cases_0_0() {
let path = format!("{}/fixtures/cases.json", env!("CARGO_MANIFEST_DIR"));
@ -7728,21 +7728,9 @@ fn test_object_types_2_1() {
}
#[test]
fn test_object_types_3_0() {
fn test_object_types_2_2() {
let path = format!("{}/fixtures/objectTypes.json", env!("CARGO_MANIFEST_DIR"));
crate::tests::runner::run_test_case(&path, 3, 0).unwrap();
}
#[test]
fn test_object_types_3_1() {
let path = format!("{}/fixtures/objectTypes.json", env!("CARGO_MANIFEST_DIR"));
crate::tests::runner::run_test_case(&path, 3, 1).unwrap();
}
#[test]
fn test_object_types_3_2() {
let path = format!("{}/fixtures/objectTypes.json", env!("CARGO_MANIFEST_DIR"));
crate::tests::runner::run_test_case(&path, 3, 2).unwrap();
crate::tests::runner::run_test_case(&path, 2, 2).unwrap();
}
#[test]

View File

@ -44,27 +44,30 @@ fn test_library_api() {
"name": "source_schema",
"variations": ["source_schema"],
"hierarchy": ["source_schema", "entity"],
"schemas": [{
"$id": "source_schema",
"type": "object",
"properties": {
"name": { "type": "string" },
"target": { "type": "target_schema" }
},
"required": ["name"]
}]
"schemas": {
"source_schema": {
"type": "object",
"properties": {
"type": { "type": "string" },
"name": { "type": "string" },
"target": { "type": "target_schema" }
},
"required": ["name"]
}
}
},
{
"name": "target_schema",
"variations": ["target_schema"],
"hierarchy": ["target_schema", "entity"],
"schemas": [{
"$id": "target_schema",
"type": "object",
"properties": {
"value": { "type": "number" }
"schemas": {
"target_schema": {
"type": "object",
"properties": {
"value": { "type": "number" }
}
}
}]
}
}
]
});
@ -86,9 +89,9 @@ fn test_library_api() {
"type": "drop",
"response": {
"source_schema": {
"$id": "source_schema",
"type": "object",
"properties": {
"type": { "type": "string" },
"name": { "type": "string" },
"target": {
"type": "target_schema",
@ -96,7 +99,7 @@ fn test_library_api() {
}
},
"required": ["name"],
"compiledProperties": ["name", "target"],
"compiledProperties": ["name", "target", "type"],
"compiledEdges": {
"target": {
"constraint": "fk_test_target",
@ -105,7 +108,6 @@ fn test_library_api() {
}
},
"target_schema": {
"$id": "target_schema",
"type": "object",
"properties": {
"value": { "type": "number" }

View File

@ -49,7 +49,13 @@ impl Case {
Err(d) => d.clone(),
};
expect.assert_drop(&result)
expect.assert_drop(&result)?;
if let Ok(db) = db_res {
expect.assert_schemas(db)?;
}
Ok(())
}
pub fn run_validate(&self, db: Arc<Database>) -> Result<(), String> {

View File

@ -1,6 +1,7 @@
pub mod pattern;
pub mod sql;
pub mod drop;
pub mod schema;
use serde::Deserialize;
@ -18,4 +19,6 @@ pub struct Expect {
pub errors: Option<Vec<serde_json::Value>>,
#[serde(default)]
pub sql: Option<Vec<SqlExpectation>>,
#[serde(default)]
pub schemas: Option<Vec<String>>,
}

View File

@ -0,0 +1,27 @@
use super::Expect;
use std::sync::Arc;
impl Expect {
pub fn assert_schemas(&self, db: &Arc<crate::database::Database>) -> Result<(), String> {
if let Some(expected_schemas) = &self.schemas {
// Collect actual schemas and sort
let mut actual: Vec<String> = db.schemas.keys().cloned().collect();
actual.sort();
// Collect expected schemas and sort
let mut expected: Vec<String> = expected_schemas.clone();
expected.sort();
if actual != expected {
return Err(format!(
"Schema Promotion Mismatch!\nExpected Schemas ({}):\n{:#?}\n\nActual Promoted Schemas ({}):\n{:#?}",
expected.len(),
expected,
actual.len(),
actual
));
}
}
Ok(())
}
}

View File

@ -93,9 +93,12 @@ impl<'a> ValidationContext<'a> {
if i < len {
if let Some(child_instance) = arr.get(i) {
let mut item_path = self.join_path(&i.to_string());
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 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(
@ -116,12 +119,15 @@ impl<'a> ValidationContext<'a> {
}
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 {
if let Some(child_instance) = arr.get(i) {
let mut item_path = self.join_path(&i.to_string());
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 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(

View File

@ -13,7 +13,7 @@ impl<'a> ValidationContext<'a> {
if let Some(ref type_) = self.schema.type_ {
match type_ {
crate::database::schema::SchemaTypeOrArray::Single(t) => {
crate::database::object::SchemaTypeOrArray::Single(t) => {
if !Validator::check_type(t, current) {
result.errors.push(ValidationError {
code: "INVALID_TYPE".to_string(),
@ -22,7 +22,7 @@ impl<'a> ValidationContext<'a> {
});
}
}
crate::database::schema::SchemaTypeOrArray::Multiple(types) => {
crate::database::object::SchemaTypeOrArray::Multiple(types) => {
let mut valid = false;
for t in types {
if Validator::check_type(t, current) {

View File

@ -10,7 +10,7 @@ impl<'a> ValidationContext<'a> {
let current = self.instance;
if let Some(compiled_fmt) = self.schema.compiled_format.get() {
match compiled_fmt {
crate::database::schema::CompiledFormat::Func(f) => {
crate::database::object::CompiledFormat::Func(f) => {
let should = if let Some(s) = current.as_str() {
!s.is_empty()
} else {
@ -24,7 +24,7 @@ impl<'a> ValidationContext<'a> {
});
}
}
crate::database::schema::CompiledFormat::Regex(re) => {
crate::database::object::CompiledFormat::Regex(re) => {
if let Some(s) = current.as_str()
&& !re.is_match(s)
{

View File

@ -13,10 +13,17 @@ impl<'a> ValidationContext<'a> {
) -> Result<bool, ValidationError> {
let current = self.instance;
if let Some(obj) = current.as_object() {
let mut schema_identifier = None;
if let Some(crate::database::object::SchemaTypeOrArray::Single(t)) = &self.schema.type_ {
if !crate::database::object::is_primitive_type(t) {
schema_identifier = Some(t.clone());
}
}
// Entity implicit type validation
if let Some(schema_identifier) = self.schema.identifier() {
if let Some(ref schema_identifier_str) = schema_identifier {
// We decompose identity string routing inherently
let expected_type = schema_identifier.split('.').last().unwrap_or(&schema_identifier);
let expected_type = schema_identifier_str.split('.').last().unwrap_or(schema_identifier_str);
// Check if the identifier represents a registered global database entity boundary mathematically
if let Some(type_def) = self.db.types.get(expected_type) {
@ -46,7 +53,7 @@ impl<'a> ValidationContext<'a> {
}
// If the target mathematically declares a horizontal structural STI variation natively
if schema_identifier.contains('.') {
if schema_identifier_str.contains('.') {
if obj.get("kind").is_none() {
result.errors.push(ValidationError {
code: "MISSING_KIND".to_string(),
@ -69,7 +76,7 @@ impl<'a> ValidationContext<'a> {
}
}
if let Some(kind_val) = obj.get("kind") {
if let Some((kind_str, _)) = schema_identifier.rsplit_once('.') {
if let Some((kind_str, _)) = schema_identifier_str.rsplit_once('.') {
if let Some(actual_kind) = kind_val.as_str() {
if actual_kind == kind_str {
result.evaluated_keys.insert("kind".to_string());
@ -124,7 +131,7 @@ impl<'a> ValidationContext<'a> {
for (prop, dep) in deps {
if obj.contains_key(prop) {
match dep {
crate::database::schema::Dependency::Props(required_props) => {
crate::database::object::Dependency::Props(required_props) => {
for req_prop in required_props {
if !obj.contains_key(req_prop) {
result.errors.push(ValidationError {
@ -135,7 +142,7 @@ impl<'a> ValidationContext<'a> {
}
}
}
crate::database::schema::Dependency::Schema(dep_schema) => {
crate::database::object::Dependency::Schema(dep_schema) => {
let derived = self.derive_for_schema(dep_schema, false);
let dep_res = derived.validate()?;
result.evaluated_keys.extend(dep_res.evaluated_keys.clone());
@ -155,7 +162,7 @@ impl<'a> ValidationContext<'a> {
if let Some(child_instance) = obj.get(key) {
let new_path = self.join_path(key);
let is_ref = match &sub_schema.type_ {
Some(crate::database::schema::SchemaTypeOrArray::Single(t)) => !crate::database::schema::is_primitive_type(t),
Some(crate::database::object::SchemaTypeOrArray::Single(t)) => !crate::database::object::is_primitive_type(t),
_ => false,
};
let next_extensible = if is_ref { false } else { self.extensible };
@ -184,7 +191,7 @@ impl<'a> ValidationContext<'a> {
if compiled_re.0.is_match(key) {
let new_path = self.join_path(key);
let is_ref = match &sub_schema.type_ {
Some(crate::database::schema::SchemaTypeOrArray::Single(t)) => !crate::database::schema::is_primitive_type(t),
Some(crate::database::object::SchemaTypeOrArray::Single(t)) => !crate::database::object::is_primitive_type(t),
_ => false,
};
let next_extensible = if is_ref { false } else { self.extensible };
@ -226,7 +233,7 @@ impl<'a> ValidationContext<'a> {
if !locally_matched {
let new_path = self.join_path(key);
let is_ref = match &additional_schema.type_ {
Some(crate::database::schema::SchemaTypeOrArray::Single(t)) => !crate::database::schema::is_primitive_type(t),
Some(crate::database::object::SchemaTypeOrArray::Single(t)) => !crate::database::object::is_primitive_type(t),
_ => false,
};
let next_extensible = if is_ref { false } else { self.extensible };

View File

@ -1,4 +1,3 @@
use crate::database::schema::Schema;
use crate::validator::context::ValidationContext;
use crate::validator::error::ValidationError;
use crate::validator::result::ValidationResult;
@ -29,276 +28,153 @@ impl<'a> ValidationContext<'a> {
}
}
if let Some(family_target) = &self.schema.family {
if let Some(descendants) = self.db.descendants.get(family_target) {
let mut candidates = Vec::new();
// Add the target base schema itself
if let Some(base_schema) = self.db.schemas.get(family_target) {
candidates.push(base_schema);
}
// Add all descendants
for child_id in descendants {
if let Some(child_schema) = self.db.schemas.get(child_id) {
candidates.push(child_schema);
}
}
// Use prefix from family string (e.g. `light.`)
let prefix = family_target
.rsplit_once('.')
.map(|(p, _)| format!("{}.", p))
.unwrap_or_default();
if !self.validate_polymorph(&candidates, Some(&prefix), result)? {
return Ok(false);
}
if self.schema.family.is_some() {
if let Some(options) = self.schema.compiled_options.get() {
return self.execute_polymorph(options, result);
} else {
result.errors.push(ValidationError {
code: "UNCOMPILED_FAMILY".to_string(),
message: "Encountered family block that could not be mapped to deterministic options during db schema compilation.".to_string(),
path: self.path.to_string(),
});
return Ok(false);
}
}
Ok(true)
}
pub(crate) fn validate_one_of(
&self,
result: &mut ValidationResult,
) -> Result<bool, ValidationError> {
if let Some(ref one_of) = self.schema.one_of {
let mut candidates = Vec::new();
for schema in one_of {
candidates.push(schema.as_ref());
}
if !self.validate_polymorph(&candidates, None, result)? {
if self.schema.one_of.is_some() {
if let Some(options) = self.schema.compiled_options.get() {
return self.execute_polymorph(options, result);
} else {
result.errors.push(ValidationError {
code: "UNCOMPILED_ONEOF".to_string(),
message: "Encountered oneOf block that could not be mapped to deterministic compiled options natively.".to_string(),
path: self.path.to_string(),
});
return Ok(false);
}
}
Ok(true)
}
pub(crate) fn validate_polymorph(
pub(crate) fn execute_polymorph(
&self,
candidates: &[&Schema],
family_prefix: Option<&str>,
options: &std::collections::BTreeMap<String, (Option<usize>, Option<String>)>,
result: &mut ValidationResult,
) -> Result<bool, ValidationError> {
let mut passed_candidates: Vec<(Option<String>, ValidationResult)> = Vec::new();
let mut failed_candidates: Vec<ValidationResult> = Vec::new();
// 1. O(1) Fast-Path Router & Extractor
let instance_type = self.instance.as_object().and_then(|o| o.get("type")).and_then(|t| t.as_str());
let instance_kind = self.instance.as_object().and_then(|o| o.get("kind")).and_then(|k| k.as_str());
let mut viable_candidates = Vec::new();
for sub in candidates {
let _child_id = sub.identifier().unwrap_or_default();
let mut can_match = true;
if let Some(t) = instance_type {
// Fast Path 1: Pure Ad-Hoc Match (schema identifier == type)
// If it matches exactly, it's our golden candidate. Make all others non-viable manually?
// Wait, we loop through all and filter down. If exact match is found, we should ideally break and use ONLY that.
// Let's implement the logic safely.
let mut exact_match_found = false;
if let Some(schema_id) = &sub.id {
// Compute Vertical Exact Target (e.g. "person" or "light.person")
let exact_target = if let Some(prefix) = family_prefix {
format!("{}{}", prefix, t)
} else {
t.to_string()
};
// Fast Path 1 & 2: Vertical Exact Match
if schema_id == &exact_target {
if instance_kind.is_none() {
exact_match_found = true;
}
}
// Fast Path 3: Horizontal Sibling Match (kind + . + type)
if let Some(k) = instance_kind {
let sibling_target = format!("{}.{}", k, t);
if schema_id == &sibling_target {
exact_match_found = true;
}
}
}
if exact_match_found {
// We found an exact literal structural identity match!
// Wipe the existing viable_candidates and only yield this guy!
viable_candidates.clear();
viable_candidates.push(*sub);
break;
}
// Fast Path 4: Vertical Inheritance Fallback (Physical DB constraint)
if let Some(crate::database::schema::SchemaTypeOrArray::Single(t_ptr)) = &sub.type_ {
if !crate::database::schema::is_primitive_type(t_ptr) {
if let Some(base_type) = t_ptr.split('.').last() {
if let Some(type_def) = self.db.types.get(base_type) {
if !type_def.variations.contains(&t.to_string()) {
can_match = false;
}
} else {
if t_ptr != t {
can_match = false;
}
}
}
let instance_val = if let Some(disc) = self.schema.compiled_discriminator.get() {
let val = self
.instance
.as_object()
.and_then(|o| o.get(disc))
.and_then(|t| t.as_str());
if val.is_some() {
result.evaluated_keys.insert(disc.to_string());
}
val.map(|s| s.to_string())
} else {
match self.instance {
serde_json::Value::Null => Some("null".to_string()),
serde_json::Value::Bool(_) => Some("boolean".to_string()),
serde_json::Value::Number(n) => {
if n.is_i64() || n.is_u64() {
Some("integer".to_string())
} else {
Some("number".to_string())
}
}
serde_json::Value::String(_) => Some("string".to_string()),
serde_json::Value::Array(_) => Some("array".to_string()),
serde_json::Value::Object(_) => Some("object".to_string()),
}
};
// Fast Path 5: Explicit Schema JSON `const` values check
if can_match {
if let Some(props) = &sub.properties {
if let Some(type_prop) = props.get("type") {
if let Some(const_val) = &type_prop.const_ {
if let Some(const_str) = const_val.as_str() {
if const_str != t {
can_match = false;
}
}
}
}
if let Some(val) = instance_val {
if let Some((idx_opt, target_id_opt)) = options.get(&val) {
if let Some(target_id) = target_id_opt {
if let Some(target_schema) = self.db.schemas.get(target_id) {
let derived = self.derive_for_schema(target_schema.as_ref(), false);
let sub_res = derived.validate()?;
let is_valid = sub_res.is_valid();
result.merge(sub_res);
return Ok(is_valid);
} else {
result.errors.push(ValidationError {
code: "MISSING_COMPILED_SCHEMA".to_string(),
message: format!(
"Polymorphic router target '{}' does not exist in the database schemas map",
target_id
),
path: self.path.to_string(),
});
return Ok(false);
}
}
}
if can_match {
viable_candidates.push(*sub);
}
}
println!("DEBUG VIABLE: {:?}", viable_candidates.iter().map(|s| s.id.clone()).collect::<Vec<_>>());
// 2. Evaluate Viable Candidates
// 2. Evaluate Viable Candidates
// Composition validation is natively handled directly via type compilation.
// The deprecated allOf JSON structure is no longer supported nor traversed.
for sub in viable_candidates.clone() {
let derived = self.derive_for_schema(sub, false);
let sub_res = derived.validate()?;
if sub_res.is_valid() {
passed_candidates.push((sub.id.clone(), sub_res));
} else {
failed_candidates.push(sub_res);
}
}
for f in &failed_candidates {
println!(" - Failed candidate errors: {:?}", f.errors.iter().map(|e| e.code.clone()).collect::<Vec<_>>());
}
if passed_candidates.len() == 1 {
result.merge(passed_candidates.pop().unwrap().1);
} else if passed_candidates.is_empty() {
// 3. Discriminator Pathing (Failure Analytics)
let type_path = self.join_path("type");
if instance_type.is_some() {
// Filter to candidates that didn't explicitly throw a CONST violation on `type`
let mut genuinely_failed = Vec::new();
for res in &failed_candidates {
let rejected_type = res.errors.iter().any(|e| {
(e.code == "CONST_VIOLATED" || e.code == "ENUM_VIOLATED") && e.path == type_path
});
if !rejected_type {
genuinely_failed.push(res.clone());
} else if let Some(idx) = idx_opt {
if let Some(target_schema) = self
.schema
.one_of
.as_ref()
.and_then(|options| options.get(*idx))
{
let derived = self.derive_for_schema(target_schema.as_ref(), false);
let sub_res = derived.validate()?;
let is_valid = sub_res.is_valid();
result.merge(sub_res);
return Ok(is_valid);
} else {
result.errors.push(ValidationError {
code: "MISSING_COMPILED_SCHEMA".to_string(),
message: format!(
"Polymorphic index target '{}' does not exist in the local oneOf array",
idx
),
path: self.path.to_string(),
});
return Ok(false);
}
}
println!("DEBUG genuinely_failed len: {}", genuinely_failed.len());
if genuinely_failed.len() == 1 {
// Golden Type Match (1 candidate was structurally possible but failed property validation)
let sub_res = genuinely_failed.pop().unwrap();
result.errors.extend(sub_res.errors);
result.evaluated_keys.extend(sub_res.evaluated_keys);
return Ok(false);
} else {
// Pure Ad-Hoc Union
result.errors.push(ValidationError {
code: if self.schema.family.is_some() { "NO_FAMILY_MATCH".to_string() } else { "NO_ONEOF_MATCH".to_string() },
message: "Payload matches none of the required candidate sub-schemas".to_string(),
path: self.path.to_string(),
});
for sub_res in &failed_candidates {
result.evaluated_keys.extend(sub_res.evaluated_keys.clone());
}
println!("DEBUG ELSE NO_FAMILY_MATCH RUNNING. Genuinely Failed len: {}", genuinely_failed.len());
if viable_candidates.is_empty() {
if let Some(obj) = self.instance.as_object() {
result.evaluated_keys.extend(obj.keys().cloned());
}
}
for sub_res in genuinely_failed {
for e in sub_res.errors {
if !result.errors.iter().any(|existing| existing.code == e.code && existing.path == e.path) {
result.errors.push(e);
}
}
}
return Ok(false);
}
} else {
// Instance missing type
// Instance missing type
let expects_type = viable_candidates.iter().any(|c| {
c.compiled_property_names.get().map_or(false, |props| props.contains(&"type".to_string()))
let disc_msg = if let Some(d) = self.schema.compiled_discriminator.get() {
format!("discriminator {}='{}'", d, val)
} else {
format!("structural JSON base primitive '{}'", val)
};
result.errors.push(ValidationError {
code: if self.schema.family.is_some() {
"NO_FAMILY_MATCH".to_string()
} else {
"NO_ONEOF_MATCH".to_string()
},
message: format!(
"Payload matched no candidate boundaries based on its {}",
disc_msg
),
path: self.path.to_string(),
});
if expects_type {
result.errors.push(ValidationError {
code: "MISSING_TYPE".to_string(),
message: "Missing type discriminator. Unable to resolve polymorphic boundaries".to_string(),
path: self.path.to_string(),
});
for sub_res in failed_candidates {
result.evaluated_keys.extend(sub_res.evaluated_keys);
}
return Ok(false);
} else {
// Pure Ad-Hoc Union
result.errors.push(ValidationError {
code: if self.schema.family.is_some() { "NO_FAMILY_MATCH".to_string() } else { "NO_ONEOF_MATCH".to_string() },
message: "Payload matches none of the required candidate sub-schemas".to_string(),
path: self.path.to_string(),
});
if let Some(first) = failed_candidates.first() {
let mut shared_errors = first.errors.clone();
for sub_res in failed_candidates.iter().skip(1) {
shared_errors.retain(|e1| {
sub_res.errors.iter().any(|e2| e1.code == e2.code && e1.path == e2.path)
});
}
for e in shared_errors {
if !result.errors.iter().any(|existing| existing.code == e.code && existing.path == e.path) {
result.errors.push(e);
}
}
}
for sub_res in failed_candidates {
result.evaluated_keys.extend(sub_res.evaluated_keys);
}
return Ok(false);
}
return Ok(false);
}
} else {
result.errors.push(ValidationError {
code: "AMBIGUOUS_POLYMORPHIC_MATCH".to_string(),
message: "Matches multiple polymorphic candidates inextricably".to_string(),
path: self.path.to_string(),
});
if let Some(d) = self.schema.compiled_discriminator.get() {
result.errors.push(ValidationError {
code: "MISSING_TYPE".to_string(),
message: format!(
"Missing explicit '{}' discriminator. Unable to resolve polymorphic boundaries",
d
),
path: self.path.to_string(),
});
}
return Ok(false);
}
Ok(true)
}
pub(crate) fn validate_type_inheritance(
@ -323,20 +199,22 @@ impl<'a> ValidationContext<'a> {
let mut custom_types = Vec::new();
match &self.schema.type_ {
Some(crate::database::schema::SchemaTypeOrArray::Single(t)) => {
if !crate::database::schema::is_primitive_type(t) {
Some(crate::database::object::SchemaTypeOrArray::Single(t)) => {
if !crate::database::object::is_primitive_type(t) {
custom_types.push(t.clone());
}
}
Some(crate::database::schema::SchemaTypeOrArray::Multiple(arr)) => {
if arr.contains(&payload_primitive.to_string()) || (payload_primitive == "integer" && arr.contains(&"number".to_string())) {
// It natively matched a primitive in the array options, skip forcing custom proxy fallback
Some(crate::database::object::SchemaTypeOrArray::Multiple(arr)) => {
if arr.contains(&payload_primitive.to_string())
|| (payload_primitive == "integer" && arr.contains(&"number".to_string()))
{
// It natively matched a primitive in the array options, skip forcing custom proxy fallback
} else {
for t in arr {
if !crate::database::schema::is_primitive_type(t) {
custom_types.push(t.clone());
}
}
for t in arr {
if !crate::database::object::is_primitive_type(t) {
custom_types.push(t.clone());
}
}
}
}
None => {}

View File

@ -1 +1 @@
1.0.108
1.0.114