diff --git a/.gitignore b/.gitignore index 21a6cf6..e431531 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,4 @@ /package .env /src/tests.rs -/pgrx-develop \ No newline at end of file +/pgrx-develop/.test diff --git a/GEMINI.md b/GEMINI.md index edaabe6..76254e1 100644 --- a/GEMINI.md +++ b/GEMINI.md @@ -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) @@ -97,7 +95,7 @@ Polymorphism is how an object boundary can dynamically take on entirely differen * *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 `$id` terminating in the base `.widget` (e.g., `stock.widget`). The `$family` automatically uses `kind` as the discriminator. + * *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. @@ -112,7 +110,6 @@ It evaluates as an **Independent Declarative Rules Engine**. Every `Case` block ```json { - "$id": "save_external_account", "cases": [ { "when": { @@ -171,10 +168,10 @@ 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. --- @@ -192,6 +189,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. --- diff --git a/fixtures/additionalProperties.json b/fixtures/additionalProperties.json index f7ed365..9f643ef 100644 --- a/fixtures/additionalProperties.json +++ b/fixtures/additionalProperties.json @@ -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": [ { diff --git a/fixtures/booleanSchema.json b/fixtures/booleanSchema.json index 5eb5511..cc44d38 100644 --- a/fixtures/booleanSchema.json +++ b/fixtures/booleanSchema.json @@ -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": [ { diff --git a/fixtures/cases.json b/fixtures/cases.json index f490c99..0000d89 100644 --- a/fixtures/cases.json +++ b/fixtures/cases.json @@ -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" + } + } ] } } ] } -] +] \ No newline at end of file diff --git a/fixtures/const.json b/fixtures/const.json index 866c644..bd4b796 100644 --- a/fixtures/const.json +++ b/fixtures/const.json @@ -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": [ { diff --git a/fixtures/contains.json b/fixtures/contains.json index b8a36b8..3578ff9 100644 --- a/fixtures/contains.json +++ b/fixtures/contains.json @@ -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": [ { diff --git a/fixtures/content.json b/fixtures/content.json index 6638382..1e33e68 100644 --- a/fixtures/content.json +++ b/fixtures/content.json @@ -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": [ { diff --git a/fixtures/database.json b/fixtures/database.json index 7163de0..51dd349 100644 --- a/fixtures/database.json +++ b/fixtures/database.json @@ -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": [ diff --git a/fixtures/dependencies.json b/fixtures/dependencies.json index 5810b62..75472de 100644 --- a/fixtures/dependencies.json +++ b/fixtures/dependencies.json @@ -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": [ { diff --git a/fixtures/emptyString.json b/fixtures/emptyString.json index 0132c48..0e81d3f 100644 --- a/fixtures/emptyString.json +++ b/fixtures/emptyString.json @@ -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" + } } ] } diff --git a/fixtures/enum.json b/fixtures/enum.json index cfc0774..15d7f43 100644 --- a/fixtures/enum.json +++ b/fixtures/enum.json @@ -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": [ { diff --git a/fixtures/exclusiveMaximum.json b/fixtures/exclusiveMaximum.json index e44aaf7..b67410c 100644 --- a/fixtures/exclusiveMaximum.json +++ b/fixtures/exclusiveMaximum.json @@ -2,12 +2,11 @@ { "description": "exclusiveMaximum validation", "database": { - "schemas": [ - { - "exclusiveMaximum": 3, - "$id": "exclusiveMaximum_0_0" + "schemas": { + "exclusiveMaximum_0_0": { + "exclusiveMaximum": 3 } - ] + } }, "tests": [ { diff --git a/fixtures/exclusiveMinimum.json b/fixtures/exclusiveMinimum.json index 27bdd3f..d07b09d 100644 --- a/fixtures/exclusiveMinimum.json +++ b/fixtures/exclusiveMinimum.json @@ -2,12 +2,11 @@ { "description": "exclusiveMinimum validation", "database": { - "schemas": [ - { - "exclusiveMinimum": 1.1, - "$id": "exclusiveMinimum_0_0" + "schemas": { + "exclusiveMinimum_0_0": { + "exclusiveMinimum": 1.1 } - ] + } }, "tests": [ { diff --git a/fixtures/format.json b/fixtures/format.json index b76f1c3..a15da7b 100644 --- a/fixtures/format.json +++ b/fixtures/format.json @@ -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": [ { diff --git a/fixtures/invoice.json b/fixtures/invoice.json index 4dac76a..865458d 100644 --- a/fixtures/invoice.json +++ b/fixtures/invoice.json @@ -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", diff --git a/fixtures/items.json b/fixtures/items.json index 350b558..209e5d7 100644 --- a/fixtures/items.json +++ b/fixtures/items.json @@ -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": [ { diff --git a/fixtures/maxContains.json b/fixtures/maxContains.json index 764825e..18461fd 100644 --- a/fixtures/maxContains.json +++ b/fixtures/maxContains.json @@ -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": [ { diff --git a/fixtures/maxItems.json b/fixtures/maxItems.json index a9771c9..e840538 100644 --- a/fixtures/maxItems.json +++ b/fixtures/maxItems.json @@ -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": [ { diff --git a/fixtures/maxLength.json b/fixtures/maxLength.json index 56ffb24..278ffba 100644 --- a/fixtures/maxLength.json +++ b/fixtures/maxLength.json @@ -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": [ { diff --git a/fixtures/maxProperties.json b/fixtures/maxProperties.json index 8f97dc2..1ff64f2 100644 --- a/fixtures/maxProperties.json +++ b/fixtures/maxProperties.json @@ -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": [ { diff --git a/fixtures/maximum.json b/fixtures/maximum.json index 38ff88c..7a36bdf 100644 --- a/fixtures/maximum.json +++ b/fixtures/maximum.json @@ -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": [ { diff --git a/fixtures/merge.json b/fixtures/merge.json index 6c4c950..3ac011e 100644 --- a/fixtures/merge.json +++ b/fixtures/merge.json @@ -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": [ { diff --git a/fixtures/merger.json b/fixtures/merger.json index 190a704..c9b6517 100644 --- a/fixtures/merger.json +++ b/fixtures/merger.json @@ -1,2821 +1,2808 @@ [ - { - "description": "Merger Execution", - "database": { - "puncs": [], - "enums": [ - { - "id": "11111111-1111-1111-1111-111111111111", - "type": "relation_type", - "enum": "relation_type", - "values": [ - "foreign_key", - "polymorphic", - "graph" - ] - } - ], - "relations": [ - { - "id": "22222222-2222-2222-2222-222222222222", - "type": "relation", - "constraint": "fk_order_customer_person", - "source_type": "order", - "source_columns": [ - "customer_id" - ], - "destination_type": "person", - "destination_columns": [ - "id" - ], - "prefix": "customer" - }, - { - "id": "33333333-3333-3333-3333-333333333333", - "type": "relation", - "constraint": "fk_order_line_order", - "source_type": "order_line", - "source_columns": [ - "order_id" - ], - "destination_type": "order", - "destination_columns": [ - "id" - ] - }, - { - "id": "44444444-4444-4444-4444-444444444444", - "type": "relation", - "constraint": "fk_relationship_source_entity", - "source_type": "relationship", - "source_columns": [ - "source_id", - "source_type" - ], - "destination_type": "entity", - "destination_columns": [ - "id", - "type" - ], - "prefix": "source" - }, - { - "id": "55555555-5555-5555-5555-555555555555", - "type": "relation", - "constraint": "fk_relationship_target_entity", - "source_type": "relationship", - "source_columns": [ - "target_id", - "target_type" - ], - "destination_type": "entity", - "destination_columns": [ - "id", - "type" - ], - "prefix": "target" - }, - { - "id": "66666666-6666-6666-6666-666666666666", - "type": "relation", - "constraint": "fk_entity_organization", - "source_type": "entity", - "source_columns": [ - "organization_id" - ], - "destination_type": "organization", - "destination_columns": [ - "id" - ], - "prefix": null - } - ], - "types": [ - { - "name": "entity", - "schemas": [ - { - "$id": "entity", - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "type": { - "type": "string" - }, - "archived": { - "type": "boolean" - }, - "created_by": { - "type": "string" - }, - "modified_by": { - "type": "string" - }, - "created_at": { - "type": "string", - "format": "date-time" - }, - "modified_at": { - "type": "string", - "format": "date-time" - } - }, - "required": [ - "id", - "type", - "created_by", - "created_at", - "modified_by", - "modified_at" - ] - } - ], - "hierarchy": [ - "entity" - ], - "fields": [ - "id", - "type", - "created_at", - "created_by", - "modified_at", - "modified_by", - "archived" - ], - "grouped_fields": { - "entity": [ - "id", - "type", - "created_at", - "created_by", - "modified_at", - "modified_by", - "archived" - ] - }, - "lookup_fields": [], - "historical": true, - "notify": true, - "relationship": false - }, - { - "name": "organization", - "schemas": [ - { - "$id": "organization", - "type": "entity", - "properties": { - "name": { - "type": "string" - } - } - } - ], - "hierarchy": [ - "organization", - "entity" - ], - "fields": [ - "id", - "type", - "name", - "created_at", - "created_by", - "modified_at", - "modified_by", - "archived" - ], - "grouped_fields": { - "organization": [ - "id", - "type", - "name" - ], - "entity": [ - "id", - "type", - "created_at", - "created_by", - "modified_at", - "modified_by", - "archived" - ] - }, - "lookup_fields": [], - "historical": true, - "notify": true, - "relationship": false - }, - { - "name": "user", - "schemas": [ - { - "$id": "user", - "type": "organization", - "properties": {} - } - ], - "hierarchy": [ - "user", - "organization", - "entity" - ], - "fields": [ - "id", - "type", - "name", - "created_at", - "created_by", - "modified_at", - "modified_by", - "archived" - ], - "grouped_fields": { - "user": [ - "id", - "type" - ], - "organization": [ - "id", - "type", - "name" - ], - "entity": [ - "id", - "type", - "created_at", - "created_by", - "modified_at", - "modified_by", - "archived" - ] - }, - "lookup_fields": [], - "historical": true, - "notify": true, - "relationship": false - }, - { - "name": "person", - "schemas": [ - { - "$id": "person", - "type": "user", - "properties": { - "first_name": { - "type": "string" - }, - "last_name": { - "type": "string" - }, - "date_of_birth": { - "type": "string" - }, - "pronouns": { - "type": "string" - }, - "contact_id": { - "type": "string" - }, - "contacts": { - "type": "array", - "items": { - "type": "contact", - "properties": { - "target": { - "oneOf": [ - { - "type": "phone_number" - }, - { - "type": "email_address" - } - ] - } - } - } - }, - "email_addresses": { - "type": "array", - "items": { - "type": "contact", - "properties": { - "target": { - "type": "email_address" - } - } - } - } - } - } - ], - "hierarchy": [ - "person", - "user", - "organization", - "entity" - ], - "fields": [ - "id", - "type", - "first_name", - "last_name", - "date_of_birth", - "pronouns", - "contact_id", - "name", - "created_at", - "created_by", - "modified_at", - "modified_by", - "archived" - ], - "grouped_fields": { - "person": [ - "id", - "type", - "first_name", - "last_name", - "date_of_birth", - "pronouns", - "contact_id" - ], - "user": [ - "id", - "type" - ], - "organization": [ - "id", - "type", - "name" - ], - "entity": [ - "id", - "type", - "created_at", - "created_by", - "modified_at", - "modified_by", - "archived" - ] - }, - "lookup_fields": [ - "first_name", - "last_name", - "date_of_birth", - "pronouns" - ], - "historical": true, - "notify": true, - "relationship": false - }, - { - "name": "order", - "schemas": [ - { - "$id": "order", - "type": "entity", - "properties": { - "total": { - "type": "number" - }, - "customer_id": { - "type": "string" - }, - "customer": { - "type": "person" - }, - "lines": { - "type": "array", - "items": { - "type": "order_line" - } - } - } - } - ], - "hierarchy": [ - "order", - "entity" - ], - "fields": [ - "id", - "type", - "total", - "customer_id", - "created_at", - "created_by", - "modified_at", - "modified_by", - "archived" - ], - "grouped_fields": { - "order": [ - "id", - "type", - "total", - "customer_id" - ], - "entity": [ - "id", - "type", - "created_at", - "created_by", - "modified_at", - "modified_by", - "archived" - ] - }, - "lookup_fields": [ - "id" - ], - "historical": true, - "notify": true, - "relationship": false - }, - { - "name": "order_line", - "schemas": [ - { - "$id": "order_line", - "type": "entity", - "properties": { - "order_id": { - "type": "string" - }, - "product": { - "type": "string" - }, - "price": { - "type": "number" - } - } - } - ], - "hierarchy": [ - "order_line", - "entity" - ], - "fields": [ - "id", - "type", - "order_id", - "product", - "price", - "created_at", - "created_by", - "modified_at", - "modified_by", - "archived" - ], - "grouped_fields": { - "order_line": [ - "id", - "type", - "order_id", - "product", - "price" - ], - "entity": [ - "id", - "type", - "created_at", - "created_by", - "modified_at", - "modified_by", - "archived" - ] - }, - "lookup_fields": [], - "historical": true, - "notify": true, - "relationship": false - }, - { - "name": "relationship", - "relationship": true, - "hierarchy": [ - "relationship", - "entity" - ], - "fields": [ - "source_id", - "source_type", - "target_id", - "target_type", - "id", - "type", - "name", - "archived", - "created_at", - "created_by", - "modified_at", - "modified_by" - ], - "grouped_fields": { - "entity": [ - "id", - "type", - "name", - "archived", - "created_at", - "created_by", - "modified_at", - "modified_by" - ], - "relationship": [ - "source_id", - "source_type", - "target_id", - "target_type" - ] - }, - "field_types": { - "id": "uuid", - "type": "text", - "archived": "boolean", - "source_id": "uuid", - "source_type": "text", - "target_id": "uuid", - "target_type": "text", - "name": "text", - "created_at": "timestamptz", - "created_by": "uuid", - "modified_at": "timestamptz", - "modified_by": "uuid" - }, - "schemas": [ - { - "$id": "relationship", - "type": "entity", - "properties": {} - } - ], - "lookup_fields": [], - "historical": true, - "notify": true - }, - { - "name": "contact", - "relationship": true, - "hierarchy": [ - "contact", - "relationship", - "entity" - ], - "fields": [ - "is_primary", - "source_id", - "source_type", - "target_id", - "target_type", - "id", - "type", - "name", - "archived", - "created_at", - "created_by", - "modified_at", - "modified_by" - ], - "grouped_fields": { - "entity": [ - "id", - "type", - "name", - "archived", - "created_at", - "created_by", - "modified_at", - "modified_by" - ], - "relationship": [ - "source_id", - "source_type", - "target_id", - "target_type" - ], - "contact": [ - "is_primary" - ] - }, - "field_types": { - "id": "uuid", - "type": "text", - "archived": "boolean", - "source_id": "uuid", - "source_type": "text", - "target_id": "uuid", - "target_type": "text", - "is_primary": "boolean", - "name": "text", - "created_at": "timestamptz", - "created_by": "uuid", - "modified_at": "timestamptz", - "modified_by": "uuid" - }, - "schemas": [ - { - "$id": "contact", - "type": "relationship", - "properties": { - "is_primary": { - "type": "boolean" - } - } - } - ], - "lookup_fields": [], - "historical": true, - "notify": true - }, - { - "name": "phone_number", - "hierarchy": [ - "phone_number", - "entity" - ], - "fields": [ - "number", - "id", - "type", - "name", - "archived", - "created_at", - "created_by", - "modified_at", - "modified_by" - ], - "grouped_fields": { - "entity": [ - "id", - "type", - "name", - "archived", - "created_at", - "created_by", - "modified_at", - "modified_by" - ], - "phone_number": [ - "number" - ] - }, - "field_types": { - "id": "uuid", - "type": "text", - "archived": "boolean", - "number": "text", - "name": "text", - "created_at": "timestamptz", - "created_by": "uuid", - "modified_at": "timestamptz", - "modified_by": "uuid" - }, - "schemas": [ - { - "$id": "phone_number", - "type": "entity", - "properties": { - "number": { - "type": "string" - } - } - } - ], - "lookup_fields": [], - "historical": true, - "notify": true, - "relationship": false - }, - { - "name": "email_address", - "hierarchy": [ - "email_address", - "entity" - ], - "fields": [ - "address", - "id", - "type", - "name", - "archived", - "created_at", - "created_by", - "modified_at", - "modified_by" - ], - "grouped_fields": { - "entity": [ - "id", - "type", - "name", - "archived", - "created_at", - "created_by", - "modified_at", - "modified_by" - ], - "email_address": [ - "address" - ] - }, - "field_types": { - "id": "uuid", - "type": "text", - "archived": "boolean", - "address": "text", - "name": "text", - "created_at": "timestamptz", - "created_by": "uuid", - "modified_at": "timestamptz", - "modified_by": "uuid" - }, - "schemas": [ - { - "$id": "email_address", - "type": "entity", - "properties": { - "address": { - "type": "string" - } - } - } - ], - "lookup_fields": [], - "historical": true, - "notify": true, - "relationship": false - }, - { - "name": "attachment", - "schemas": [ - { - "$id": "type_metadata", - "type": "object", - "properties": { - "type": { - "type": "string" - } - } - }, - { - "$id": "other_metadata", - "type": "object", - "properties": { - "other": { - "type": "string" - } - } - }, - { - "$id": "attachment", - "type": "entity", - "properties": { - "flags": { - "type": "array", - "items": { - "type": "string" - } - }, - "type_metadata": { - "type": "type_metadata" - }, - "other_metadata": { - "type": "other_metadata" - } - } - } - ], - "hierarchy": [ - "attachment", - "entity" - ], - "fields": [ - "id", - "type", - "flags", - "type_metadata", - "other_metadata", - "created_at", - "created_by", - "modified_at", - "modified_by", - "archived" - ], - "grouped_fields": { - "attachment": [ - "id", - "type", - "flags", - "type_metadata", - "other_metadata" - ], - "entity": [ - "id", - "type", - "created_at", - "created_by", - "modified_at", - "modified_by", - "archived" - ] - }, - "field_types": { - "id": "uuid", - "type": "text", - "flags": "_text", - "type_metadata": "jsonb", - "other_metadata": "jsonb", - "created_at": "timestamptz", - "created_by": "uuid", - "modified_at": "timestamptz", - "modified_by": "uuid", - "archived": "boolean" - }, - "lookup_fields": [], - "historical": true, - "notify": true, - "relationship": false - } - ] + { + "description": "Merger Execution", + "database": { + "puncs": [], + "enums": [ + { + "id": "11111111-1111-1111-1111-111111111111", + "type": "relation_type", + "enum": "relation_type", + "values": [ + "foreign_key", + "polymorphic", + "graph" + ] + } + ], + "relations": [ + { + "id": "22222222-2222-2222-2222-222222222222", + "type": "relation", + "constraint": "fk_order_customer_person", + "source_type": "order", + "source_columns": [ + "customer_id" + ], + "destination_type": "person", + "destination_columns": [ + "id" + ], + "prefix": "customer" }, - "tests": [ - { - "description": "Insert person without id (no lookup)", - "action": "merge", - "data": { - "type": "person", - "first_name": "IncompleteFirst", - "last_name": "IncompleteLast" + { + "id": "33333333-3333-3333-3333-333333333333", + "type": "relation", + "constraint": "fk_order_line_order", + "source_type": "order_line", + "source_columns": [ + "order_id" + ], + "destination_type": "order", + "destination_columns": [ + "id" + ] + }, + { + "id": "44444444-4444-4444-4444-444444444444", + "type": "relation", + "constraint": "fk_relationship_source_entity", + "source_type": "relationship", + "source_columns": [ + "source_id", + "source_type" + ], + "destination_type": "entity", + "destination_columns": [ + "id", + "type" + ], + "prefix": "source" + }, + { + "id": "55555555-5555-5555-5555-555555555555", + "type": "relation", + "constraint": "fk_relationship_target_entity", + "source_type": "relationship", + "source_columns": [ + "target_id", + "target_type" + ], + "destination_type": "entity", + "destination_columns": [ + "id", + "type" + ], + "prefix": "target" + }, + { + "id": "66666666-6666-6666-6666-666666666666", + "type": "relation", + "constraint": "fk_entity_organization", + "source_type": "entity", + "source_columns": [ + "organization_id" + ], + "destination_type": "organization", + "destination_columns": [ + "id" + ], + "prefix": null + } + ], + "types": [ + { + "name": "entity", + "schemas": { + "entity": { + "type": "object", + "properties": { + "id": { + "type": "string" }, - "mocks": [ - { - "id": "33333333-3333-3333-3333-333333333333", - "type": "person", - "first_name": "IncompleteFirst", - "last_name": "IncompleteLast", - "date_of_birth": "1990-01-01T00:00:00Z", - "pronouns": "they/them", - "contact_id": "old-contact" - } - ], - "schema_id": "person", - "expect": { - "success": true, - "sql": [ - [ - "INSERT INTO agreego.\"entity\" (", - " \"created_at\",", - " \"created_by\",", - " \"id\",", - " \"modified_at\",", - " \"modified_by\",", - " \"type\"", - ")", - "VALUES (", - " '{{timestamp}}',", - " '00000000-0000-0000-0000-000000000000',", - " '{{uuid:person_id}}',", - " '{{timestamp}}',", - " '00000000-0000-0000-0000-000000000000',", - " 'person'", - ")" - ], - [ - "INSERT INTO agreego.\"organization\" (", - " \"id\",", - " \"type\"", - ")", - "VALUES (", - " '{{uuid:person_id}}',", - " 'person'", - ")" - ], - [ - "INSERT INTO agreego.\"user\" (", - " \"id\",", - " \"type\"", - ")", - "VALUES (", - " '{{uuid:person_id}}',", - " 'person'", - ")" - ], - [ - "INSERT INTO agreego.\"person\" (", - " \"first_name\",", - " \"id\",", - " \"last_name\",", - " \"type\"", - ")", - "VALUES (", - " 'IncompleteFirst',", - " '{{uuid:person_id}}',", - " 'IncompleteLast',", - " 'person'", - ")" - ], - [ - "INSERT INTO agreego.change (", - " \"old\",", - " \"new\",", - " entity_id,", - " id,", - " kind,", - " modified_at,", - " modified_by", - ")", - "VALUES (", - " NULL,", - " '{", - " \"first_name\":\"IncompleteFirst\",", - " \"last_name\":\"IncompleteLast\",", - " \"type\":\"person\"", - " }',", - " '{{uuid:person_id}}',", - " '{{uuid}}',", - " 'create',", - " '{{timestamp}}',", - " '00000000-0000-0000-0000-000000000000'", - ")" - ], - [ - "SELECT pg_notify('entity', '{", - " \"complete\":{", - " \"created_at\":\"{{timestamp}}\",", - " \"created_by\":\"00000000-0000-0000-0000-000000000000\",", - " \"first_name\":\"IncompleteFirst\",", - " \"id\":\"{{uuid:person_id}}\",", - " \"last_name\":\"IncompleteLast\",", - " \"modified_at\":\"{{timestamp}}\",", - " \"modified_by\":\"00000000-0000-0000-0000-000000000000\",", - " \"type\":\"person\"", - " },", - " \"new\":{", - " \"first_name\":\"IncompleteFirst\",", - " \"last_name\":\"IncompleteLast\",", - " \"type\":\"person\"", - " }", - " }')" - ] - ] - } - }, - { - "description": "Update existing person without id (lookup)", - "action": "merge", - "data": { - "type": "person", - "first_name": "LookupFirst", - "last_name": "LookupLast", - "date_of_birth": "1990-01-01T00:00:00Z", - "pronouns": "they/them", - "contact_id": "abc-contact" + "type": { + "type": "string" }, - "mocks": [ - { - "id": "22222222-2222-2222-2222-222222222222", - "type": "person", - "first_name": "LookupFirst", - "last_name": "LookupLast", - "date_of_birth": "1990-01-01T00:00:00Z", - "pronouns": "they/them", - "contact_id": "old-contact" - } - ], - "schema_id": "person", - "expect": { - "success": true, - "sql": [ - [ - "SELECT to_jsonb(t1.*) || to_jsonb(t2.*) || to_jsonb(t3.*) || to_jsonb(t4.*)", - "FROM agreego.\"person\" t1", - "LEFT JOIN agreego.\"user\" t2 ON t2.id = t1.id", - "LEFT JOIN agreego.\"organization\" t3 ON t3.id = t1.id", - "LEFT JOIN agreego.\"entity\" t4 ON t4.id = t1.id", - "WHERE (", - " \"first_name\" = 'LookupFirst'", - " AND \"last_name\" = 'LookupLast'", - " AND \"date_of_birth\" = '1990-01-01T00:00:00Z'", - " AND \"pronouns\" = 'they/them'", - ")" - ], - [ - "UPDATE agreego.\"person\"", - "SET", - " \"contact_id\" = 'abc-contact'", - "WHERE", - " id = '22222222-2222-2222-2222-222222222222'" - ], - [ - "UPDATE agreego.\"entity\"", - "SET", - " \"modified_at\" = '2026-03-10T00:00:00Z',", - " \"modified_by\" = '00000000-0000-0000-0000-000000000000'", - "WHERE", - " id = '22222222-2222-2222-2222-222222222222'" - ], - [ - "INSERT INTO agreego.change (", - " \"old\",", - " \"new\",", - " entity_id,", - " id,", - " kind,", - " modified_at,", - " modified_by", - ")", - "VALUES (", - " '{", - " \"contact_id\":\"old-contact\"", - " }',", - " '{", - " \"contact_id\":\"abc-contact\",", - " \"type\":\"person\"", - " }',", - " '22222222-2222-2222-2222-222222222222',", - " '{{uuid}}',", - " 'update',", - " '{{timestamp}}',", - " '00000000-0000-0000-0000-000000000000'", - ")" - ], - [ - "SELECT pg_notify('entity', '{", - " \"complete\":{", - " \"contact_id\":\"abc-contact\",", - " \"date_of_birth\":\"1990-01-01T00:00:00Z\",", - " \"first_name\":\"LookupFirst\",", - " \"id\":\"22222222-2222-2222-2222-222222222222\",", - " \"last_name\":\"LookupLast\",", - " \"modified_at\":\"2026-03-10T00:00:00Z\",", - " \"modified_by\":\"00000000-0000-0000-0000-000000000000\",", - " \"pronouns\":\"they/them\",", - " \"type\":\"person\"", - " },", - " \"new\":{", - " \"contact_id\":\"abc-contact\",", - " \"type\":\"person\"", - " },", - " \"old\":{", - " \"contact_id\":\"old-contact\"", - " }", - " }')" - ] - ] - } - }, - { - "description": "Update existing person with id (lookup)", - "action": "merge", - "data": { - "id": "33333333-3333-3333-3333-333333333333", - "type": "person", - "first_name": "LookupFirst", - "last_name": "LookupLast", - "date_of_birth": "1990-01-01T00:00:00Z", - "pronouns": "they/them", - "contact_id": "abc-contact" + "archived": { + "type": "boolean" }, - "mocks": [ - { - "id": "22222222-2222-2222-2222-222222222222", - "type": "person", - "first_name": "LookupFirst", - "last_name": "LookupLast", - "date_of_birth": "1990-01-01T00:00:00Z", - "pronouns": "they/them", - "contact_id": "old-contact" - } - ], - "schema_id": "person", - "expect": { - "success": true, - "sql": [ - [ - "SELECT to_jsonb(t1.*) || to_jsonb(t2.*) || to_jsonb(t3.*) || to_jsonb(t4.*)", - "FROM agreego.\"person\" t1", - "LEFT JOIN agreego.\"user\" t2 ON t2.id = t1.id", - "LEFT JOIN agreego.\"organization\" t3 ON t3.id = t1.id", - "LEFT JOIN agreego.\"entity\" t4 ON t4.id = t1.id", - "WHERE", - " t1.id = '33333333-3333-3333-3333-333333333333'", - " OR (", - " \"first_name\" = 'LookupFirst'", - " AND \"last_name\" = 'LookupLast'", - " AND \"date_of_birth\" = '1990-01-01T00:00:00Z'", - " AND \"pronouns\" = 'they/them'", - " )" - ], - [ - "UPDATE agreego.\"person\"", - "SET", - " \"contact_id\" = 'abc-contact'", - "WHERE", - " id = '22222222-2222-2222-2222-222222222222'" - ], - [ - "UPDATE agreego.\"entity\"", - "SET", - " \"modified_at\" = '2026-03-10T00:00:00Z',", - " \"modified_by\" = '00000000-0000-0000-0000-000000000000'", - "WHERE", - " id = '22222222-2222-2222-2222-222222222222'" - ], - [ - "INSERT INTO agreego.change (", - " \"old\",", - " \"new\",", - " entity_id,", - " id,", - " kind,", - " modified_at,", - " modified_by", - ")", - "VALUES (", - " '{", - " \"contact_id\":\"old-contact\"", - " }',", - " '{", - " \"contact_id\":\"abc-contact\",", - " \"type\":\"person\"", - " }',", - " '22222222-2222-2222-2222-222222222222',", - " '{{uuid}}',", - " 'update',", - " '{{timestamp}}',", - " '00000000-0000-0000-0000-000000000000'", - ")" - ], - [ - "SELECT pg_notify('entity', '{", - " \"complete\":{", - " \"contact_id\":\"abc-contact\",", - " \"date_of_birth\":\"1990-01-01T00:00:00Z\",", - " \"first_name\":\"LookupFirst\",", - " \"id\":\"22222222-2222-2222-2222-222222222222\",", - " \"last_name\":\"LookupLast\",", - " \"modified_at\":\"2026-03-10T00:00:00Z\",", - " \"modified_by\":\"00000000-0000-0000-0000-000000000000\",", - " \"pronouns\":\"they/them\",", - " \"type\":\"person\"", - " },", - " \"new\":{", - " \"contact_id\":\"abc-contact\",", - " \"type\":\"person\"", - " },", - " \"old\":{", - " \"contact_id\":\"old-contact\"", - " },", - " \"replaces\":\"33333333-3333-3333-3333-333333333333\"", - " }')" - ] - ] - } - }, - { - "description": "Replace existing person with id and no changes (lookup)", - "action": "merge", - "data": { - "id": "33333333-3333-3333-3333-333333333333", - "type": "person", - "first_name": "LookupFirst", - "last_name": "LookupLast", - "date_of_birth": "1990-01-01T00:00:00Z", - "pronouns": "they/them" + "created_by": { + "type": "string" }, - "mocks": [ - { - "id": "22222222-2222-2222-2222-222222222222", - "type": "person", - "first_name": "LookupFirst", - "last_name": "LookupLast", - "date_of_birth": "1990-01-01T00:00:00Z", - "pronouns": "they/them", - "contact_id": "old-contact" - } - ], - "schema_id": "person", - "expect": { - "success": true, - "sql": [ - [ - "SELECT to_jsonb(t1.*) || to_jsonb(t2.*) || to_jsonb(t3.*) || to_jsonb(t4.*)", - "FROM agreego.\"person\" t1", - "LEFT JOIN agreego.\"user\" t2 ON t2.id = t1.id", - "LEFT JOIN agreego.\"organization\" t3 ON t3.id = t1.id", - "LEFT JOIN agreego.\"entity\" t4 ON t4.id = t1.id", - "WHERE", - " t1.id = '33333333-3333-3333-3333-333333333333'", - " OR (", - " \"first_name\" = 'LookupFirst'", - " AND \"last_name\" = 'LookupLast'", - " AND \"date_of_birth\" = '1990-01-01T00:00:00Z'", - " AND \"pronouns\" = 'they/them'", - " )" - ], - [ - "SELECT pg_notify('entity', '{", - " \"complete\":{", - " \"contact_id\":\"old-contact\",", - " \"date_of_birth\":\"1990-01-01T00:00:00Z\",", - " \"first_name\":\"LookupFirst\",", - " \"id\":\"22222222-2222-2222-2222-222222222222\",", - " \"last_name\":\"LookupLast\",", - " \"modified_at\":\"2026-03-10T00:00:00Z\",", - " \"modified_by\":\"00000000-0000-0000-0000-000000000000\",", - " \"pronouns\":\"they/them\",", - " \"type\":\"person\"", - " },", - " \"new\":{", - " \"type\":\"person\"", - " },", - " \"replaces\":\"33333333-3333-3333-3333-333333333333\"", - " }')" - ] - ] - } - }, - { - "description": "Update existing person with id (no lookup)", - "action": "merge", - "data": { - "id": "11111111-1111-1111-1111-111111111111", - "type": "person", - "first_name": "NewFirst", - "last_name": "NewLast" + "modified_by": { + "type": "string" }, - "mocks": [ - { - "id": "11111111-1111-1111-1111-111111111111", - "type": "person", - "first_name": "OldFirst", - "last_name": "OldLast" - } - ], - "schema_id": "person", - "expect": { - "success": true, - "sql": [ - [ - "SELECT to_jsonb(t1.*) || to_jsonb(t2.*) || to_jsonb(t3.*) || to_jsonb(t4.*)", - "FROM agreego.\"person\" t1", - "LEFT JOIN agreego.\"user\" t2 ON t2.id = t1.id", - "LEFT JOIN agreego.\"organization\" t3 ON t3.id = t1.id", - "LEFT JOIN agreego.\"entity\" t4 ON t4.id = t1.id", - "WHERE t1.id = '11111111-1111-1111-1111-111111111111'" - ], - [ - "UPDATE agreego.\"person\"", - "SET", - " \"first_name\" = 'NewFirst',", - " \"last_name\" = 'NewLast'", - "WHERE", - " id = '11111111-1111-1111-1111-111111111111'" - ], - [ - "UPDATE agreego.\"entity\"", - "SET", - " \"modified_at\" = '2026-03-10T00:00:00Z',", - " \"modified_by\" = '00000000-0000-0000-0000-000000000000'", - "WHERE", - " id = '11111111-1111-1111-1111-111111111111'" - ], - [ - "INSERT INTO agreego.change (", - " \"old\",", - " \"new\",", - " entity_id,", - " id,", - " kind,", - " modified_at,", - " modified_by", - ")", - "VALUES (", - " '{", - " \"first_name\":\"OldFirst\",", - " \"last_name\":\"OldLast\"", - " }',", - " '{", - " \"first_name\":\"NewFirst\",", - " \"last_name\":\"NewLast\",", - " \"type\":\"person\"", - " }',", - " '11111111-1111-1111-1111-111111111111',", - " '{{uuid}}',", - " 'update',", - " '{{timestamp}}',", - " '00000000-0000-0000-0000-000000000000'", - ")" - ], - [ - "SELECT pg_notify('entity', '{", - " \"complete\":{", - " \"first_name\":\"NewFirst\",", - " \"id\":\"11111111-1111-1111-1111-111111111111\",", - " \"last_name\":\"NewLast\",", - " \"modified_at\":\"2026-03-10T00:00:00Z\",", - " \"modified_by\":\"00000000-0000-0000-0000-000000000000\",", - " \"type\":\"person\"", - " },", - " \"new\":{", - " \"first_name\":\"NewFirst\",", - " \"last_name\":\"NewLast\",", - " \"type\":\"person\"", - " },", - " \"old\":{", - " \"first_name\":\"OldFirst\",", - " \"last_name\":\"OldLast\"", - " }", - " }')" - ] - ] - } - }, - { - "description": "Empty strings null out fields", - "action": "merge", - "data": { - "id": "123", - "type": "person", - "first_name": "John", - "last_name": "Doe", - "date_of_birth": "1990-01-01T00:00:00Z", - "pronouns": "" + "created_at": { + "type": "string", + "format": "date-time" }, - "schema_id": "person", - "expect": { - "success": true, - "sql": [ - [ - "SELECT to_jsonb(t1.*) || to_jsonb(t2.*) || to_jsonb(t3.*) || to_jsonb(t4.*)", - "FROM agreego.\"person\" t1", - "LEFT JOIN agreego.\"user\" t2 ON t2.id = t1.id", - "LEFT JOIN agreego.\"organization\" t3 ON t3.id = t1.id", - "LEFT JOIN agreego.\"entity\" t4 ON t4.id = t1.id", - "WHERE t1.id = '123'" - ], - [ - "INSERT INTO agreego.\"entity\" (", - " \"created_at\",", - " \"created_by\",", - " \"id\",", - " \"modified_at\",", - " \"modified_by\",", - " \"type\"", - ")", - "VALUES (", - " '{{timestamp}}',", - " '00000000-0000-0000-0000-000000000000',", - " '123',", - " '{{timestamp}}',", - " '00000000-0000-0000-0000-000000000000',", - " 'person'", - ")" - ], - [ - "INSERT INTO agreego.\"organization\" (", - " \"id\",", - " \"type\"", - ")", - "VALUES (", - " '123',", - " 'person'", - ")" - ], - [ - "INSERT INTO agreego.\"user\" (", - " \"id\",", - " \"type\"", - ")", - "VALUES (", - " '123',", - " 'person'", - ")" - ], - [ - "INSERT INTO agreego.\"person\" (", - " \"date_of_birth\",", - " \"first_name\",", - " \"id\",", - " \"last_name\",", - " \"pronouns\",", - " \"type\"", - ")", - "VALUES (", - " '1990-01-01T00:00:00Z',", - " 'John',", - " '123',", - " 'Doe',", - " NULL,", - " 'person'", - ")" - ], - [ - "INSERT INTO agreego.change (", - " \"old\",", - " \"new\",", - " entity_id,", - " id,", - " kind,", - " modified_at,", - " modified_by", - ")", - "VALUES (", - " NULL,", - " '{", - " \"date_of_birth\":\"1990-01-01T00:00:00Z\",", - " \"first_name\":\"John\",", - " \"last_name\":\"Doe\",", - " \"pronouns\":\"\",", - " \"type\":\"person\"", - " }',", - " '123',", - " '{{uuid}}',", - " 'create',", - " '{{timestamp}}',", - " '00000000-0000-0000-0000-000000000000'", - ")" - ], - [ - "SELECT pg_notify('entity', '{", - " \"complete\":{", - " \"created_at\":\"{{timestamp}}\",", - " \"created_by\":\"00000000-0000-0000-0000-000000000000\",", - " \"date_of_birth\":\"1990-01-01T00:00:00Z\",", - " \"first_name\":\"John\",", - " \"id\":\"123\",", - " \"last_name\":\"Doe\",", - " \"modified_at\":\"{{timestamp}}\",", - " \"modified_by\":\"00000000-0000-0000-0000-000000000000\",", - " \"pronouns\":\"\",", - " \"type\":\"person\"", - " },", - " \"new\":{", - " \"date_of_birth\":\"1990-01-01T00:00:00Z\",", - " \"first_name\":\"John\",", - " \"last_name\":\"Doe\",", - " \"pronouns\":\"\",", - " \"type\":\"person\"", - " }", - " }')" - ] - ] - } - }, - { - "description": "Insert order with nested customer person", - "action": "merge", - "data": { - "type": "order", - "total": 100.0, - "customer": { - "type": "person", - "first_name": "Bob", - "last_name": "Smith", - "date_of_birth": "2000-01-01" - } - }, - "schema_id": "order", - "expect": { - "success": true, - "sql": [ - [ - "INSERT INTO agreego.\"entity\" (", - " \"created_at\",", - " \"created_by\",", - " \"id\",", - " \"modified_at\",", - " \"modified_by\",", - " \"type\"", - ")", - "VALUES (", - " '{{timestamp}}',", - " '00000000-0000-0000-0000-000000000000',", - " '{{uuid:customer_id}}',", - " '{{timestamp}}',", - " '00000000-0000-0000-0000-000000000000',", - " 'person'", - ")" - ], - [ - "INSERT INTO agreego.\"organization\" (", - " \"id\",", - " \"type\"", - ")", - "VALUES (", - " '{{uuid:customer_id}}',", - " 'person'", - ")" - ], - [ - "INSERT INTO agreego.\"user\" (", - " \"id\",", - " \"type\"", - ")", - "VALUES (", - " '{{uuid:customer_id}}',", - " 'person'", - ")" - ], - [ - "INSERT INTO agreego.\"person\" (", - " \"date_of_birth\",", - " \"first_name\",", - " \"id\",", - " \"last_name\",", - " \"type\"", - ")", - "VALUES (", - " '2000-01-01',", - " 'Bob',", - " '{{uuid:customer_id}}',", - " 'Smith',", - " 'person'", - ")" - ], - [ - "INSERT INTO agreego.change (", - " \"old\",", - " \"new\",", - " entity_id,", - " id,", - " kind,", - " modified_at,", - " modified_by", - ")", - "VALUES (", - " NULL,", - " '{", - " \"date_of_birth\":\"2000-01-01\",", - " \"first_name\":\"Bob\",", - " \"last_name\":\"Smith\",", - " \"type\":\"person\"", - " }',", - " '{{uuid:customer_id}}',", - " '{{uuid}}',", - " 'create',", - " '{{timestamp}}',", - " '00000000-0000-0000-0000-000000000000'", - ")" - ], - [ - "INSERT INTO agreego.\"entity\" (", - " \"created_at\",", - " \"created_by\",", - " \"id\",", - " \"modified_at\",", - " \"modified_by\",", - " \"type\"", - ")", - "VALUES (", - " '{{timestamp}}',", - " '00000000-0000-0000-0000-000000000000',", - " '{{uuid:order_id}}',", - " '{{timestamp}}',", - " '00000000-0000-0000-0000-000000000000',", - " 'order'", - ")" - ], - [ - "INSERT INTO agreego.\"order\" (", - " \"customer_id\",", - " \"id\",", - " \"total\",", - " \"type\"", - ")", - "VALUES (", - " '{{uuid:customer_id}}',", - " '{{uuid:order_id}}',", - " 100,", - " 'order'", - ")" - ], - [ - "INSERT INTO agreego.change (", - " \"old\",", - " \"new\",", - " entity_id,", - " id,", - " kind,", - " modified_at,", - " modified_by", - ")", - "VALUES (", - " NULL,", - " '{", - " \"customer_id\":\"{{uuid:customer_id}}\",", - " \"total\":100.0,", - " \"type\":\"order\"", - " }',", - " '{{uuid:order_id}}',", - " '{{uuid}}',", - " 'create',", - " '{{timestamp}}',", - " '00000000-0000-0000-0000-000000000000'", - ")" - ], - [ - "SELECT pg_notify('entity', '{", - " \"complete\":{", - " \"created_at\":\"{{timestamp}}\",", - " \"created_by\":\"00000000-0000-0000-0000-000000000000\",", - " \"customer_id\":\"{{uuid:customer_id}}\",", - " \"id\":\"{{uuid:order_id}}\",", - " \"modified_at\":\"{{timestamp}}\",", - " \"modified_by\":\"00000000-0000-0000-0000-000000000000\",", - " \"total\":100.0,", - " \"type\":\"order\"", - " },", - " \"new\":{", - " \"customer_id\":\"{{uuid:customer_id}}\",", - " \"total\":100.0,", - " \"type\":\"order\"", - " }", - " }')" - ], - [ - "SELECT pg_notify('entity', '{", - " \"complete\":{", - " \"created_at\":\"{{timestamp}}\",", - " \"created_by\":\"00000000-0000-0000-0000-000000000000\",", - " \"date_of_birth\":\"2000-01-01\",", - " \"first_name\":\"Bob\",", - " \"id\":\"{{uuid:customer_id}}\",", - " \"last_name\":\"Smith\",", - " \"modified_at\":\"{{timestamp}}\",", - " \"modified_by\":\"00000000-0000-0000-0000-000000000000\",", - " \"type\":\"person\"", - " },", - " \"new\":{", - " \"date_of_birth\":\"2000-01-01\",", - " \"first_name\":\"Bob\",", - " \"last_name\":\"Smith\",", - " \"type\":\"person\"", - " }", - " }')" - ] - ] - } - }, - { - "description": "Insert order with nested order lines", - "action": "merge", - "data": { - "id": "abc", - "type": "order", - "total": 99.0, - "lines": [ - { - "type": "order_line", - "product": "Widget", - "price": 99.0 - } - ] - }, - "schema_id": "order", - "expect": { - "success": true, - "sql": [ - [ - "SELECT to_jsonb(t1.*) || to_jsonb(t2.*)", - "FROM agreego.\"order\" t1", - "LEFT JOIN agreego.\"entity\" t2 ON t2.id = t1.id", - "WHERE t1.id = 'abc' OR (\"id\" = 'abc')" - ], - [ - "INSERT INTO agreego.\"entity\" (", - " \"created_at\",", - " \"created_by\",", - " \"id\",", - " \"modified_at\",", - " \"modified_by\",", - " \"type\"", - ")", - "VALUES (", - " '{{timestamp}}',", - " '00000000-0000-0000-0000-000000000000',", - " 'abc',", - " '{{timestamp}}',", - " '00000000-0000-0000-0000-000000000000',", - " 'order'", - ")" - ], - [ - "INSERT INTO agreego.\"order\" (", - " \"id\",", - " \"total\",", - " \"type\"", - ")", - "VALUES (", - " 'abc',", - " 99,", - " 'order'", - ")" - ], - [ - "INSERT INTO agreego.\"entity\" (", - " \"created_at\",", - " \"created_by\",", - " \"id\",", - " \"modified_at\",", - " \"modified_by\",", - " \"type\"", - ")", - "VALUES (", - " '{{timestamp}}',", - " '00000000-0000-0000-0000-000000000000',", - " '{{uuid:line_id}}',", - " '{{timestamp}}',", - " '00000000-0000-0000-0000-000000000000',", - " 'order_line'", - ")" - ], - [ - "INSERT INTO agreego.\"order_line\" (", - " \"id\",", - " \"order_id\",", - " \"price\",", - " \"product\",", - " \"type\")", - "VALUES (", - " '{{uuid:line_id}}',", - " 'abc',", - " 99,", - " 'Widget',", - " 'order_line'", - ")" - ], - [ - "INSERT INTO agreego.change (", - " \"old\",", - " \"new\",", - " entity_id,", - " id,", - " kind,", - " modified_at,", - " modified_by", - ")", - "VALUES (", - " NULL,", - " '{", - " \"order_id\":\"abc\",", - " \"price\":99.0,", - " \"product\":\"Widget\",", - " \"type\":\"order_line\"", - " }',", - " '{{uuid:line_id}}',", - " '{{uuid}}',", - " 'create',", - " '{{timestamp}}',", - " '00000000-0000-0000-0000-000000000000'", - ")" - ], - [ - "INSERT INTO agreego.change (", - " \"old\",", - " \"new\",", - " entity_id,", - " id,", - " kind,", - " modified_at,", - " modified_by", - ")", - "VALUES (", - " NULL,", - " '{", - " \"total\":99.0,", - " \"type\":\"order\"", - " }',", - " 'abc',", - " '{{uuid}}',", - " 'create',", - " '{{timestamp}}',", - " '00000000-0000-0000-0000-000000000000'", - ")" - ], - [ - "SELECT pg_notify('entity', '{", - " \"complete\":{", - " \"created_at\":\"{{timestamp}}\",", - " \"created_by\":\"00000000-0000-0000-0000-000000000000\",", - " \"id\":\"abc\",", - " \"modified_at\":\"{{timestamp}}\",", - " \"modified_by\":\"00000000-0000-0000-0000-000000000000\",", - " \"total\":99.0,", - " \"type\":\"order\"", - " },", - " \"new\":{", - " \"total\":99.0,", - " \"type\":\"order\"", - " }", - " }')" - ], - [ - "SELECT pg_notify('entity', '{", - " \"complete\":{", - " \"created_at\":\"{{timestamp}}\",", - " \"created_by\":\"00000000-0000-0000-0000-000000000000\",", - " \"id\":\"{{uuid:line_id}}\",", - " \"modified_at\":\"{{timestamp}}\",", - " \"modified_by\":\"00000000-0000-0000-0000-000000000000\",", - " \"order_id\":\"abc\",", - " \"price\":99.0,", - " \"product\":\"Widget\",", - " \"type\":\"order_line\"", - " },", - " \"new\":{", - " \"order_id\":\"abc\",", - " \"price\":99.0,", - " \"product\":\"Widget\",", - " \"type\":\"order_line\"", - " }", - " }')" - ] - ] - } - }, - { - "description": "Insert person with contacts and phone numbers (M:M)", - "action": "merge", - "data": { - "type": "person", - "first_name": "Relation", - "last_name": "Test", - "contacts": [ - { - "type": "contact", - "is_primary": true, - "target": { - "type": "phone_number", - "number": "555-0001" - } - }, - { - "type": "contact", - "is_primary": false, - "target": { - "type": "email_address", - "address": "test@example.com" - } - } - ], - "email_addresses": [ - { - "type": "contact", - "is_primary": false, - "target": { - "type": "email_address", - "address": "test2@example.com" - } - } - ] - }, - "schema_id": "person", - "expect": { - "success": true, - "sql": [ - [ - "INSERT INTO agreego.\"entity\" (", - " \"created_at\",", - " \"created_by\",", - " \"id\",", - " \"modified_at\",", - " \"modified_by\",", - " \"type\"", - ") VALUES (", - " '{{timestamp}}',", - " '00000000-0000-0000-0000-000000000000',", - " '{{uuid:person_id}}',", - " '{{timestamp}}',", - " '00000000-0000-0000-0000-000000000000',", - " 'person'", - ")" - ], - [ - "INSERT INTO agreego.\"organization\" (", - " \"id\",", - " \"type\"", - ") VALUES (", - " '{{uuid:person_id}}',", - " 'person'", - ")" - ], - [ - "INSERT INTO agreego.\"user\" (", - " \"id\",", - " \"type\"", - ") VALUES (", - " '{{uuid:person_id}}',", - " 'person'", - ")" - ], - [ - "INSERT INTO agreego.\"person\" (", - " \"first_name\",", - " \"id\",", - " \"last_name\",", - " \"type\"", - ") VALUES (", - " 'Relation',", - " '{{uuid:person_id}}',", - " 'Test',", - " 'person'", - ")" - ], - [ - "INSERT INTO agreego.\"entity\" (", - " \"created_at\",", - " \"created_by\",", - " \"id\",", - " \"modified_at\",", - " \"modified_by\",", - " \"type\"", - ") VALUES (", - " '{{timestamp}}',", - " '00000000-0000-0000-0000-000000000000',", - " '{{uuid:phone1_id}}',", - " '{{timestamp}}',", - " '00000000-0000-0000-0000-000000000000',", - " 'phone_number'", - ")" - ], - [ - "INSERT INTO agreego.\"phone_number\" (", - " \"number\"", - ") VALUES (", - " '555-0001'", - ")" - ], - [ - "INSERT INTO agreego.change (", - " \"old\",", - " \"new\",", - " entity_id,", - " id,", - " kind,", - " modified_at,", - " modified_by", - ") VALUES (", - " NULL,", - " '{", - " \"number\":\"555-0001\",", - " \"type\":\"phone_number\"", - " }',", - " '{{uuid:phone1_id}}',", - " '{{uuid}}',", - " 'create',", - " '{{timestamp}}',", - " '00000000-0000-0000-0000-000000000000'", - ")" - ], - [ - "INSERT INTO agreego.\"entity\" (", - " \"created_at\",", - " \"created_by\",", - " \"id\",", - " \"modified_at\",", - " \"modified_by\",", - " \"type\"", - ") VALUES (", - " '{{timestamp}}',", - " '00000000-0000-0000-0000-000000000000',", - " '{{uuid:contact1_id}}',", - " '{{timestamp}}',", - " '00000000-0000-0000-0000-000000000000',", - " 'contact'", - ")" - ], - [ - "INSERT INTO agreego.\"relationship\" (", - " \"source_id\",", - " \"source_type\",", - " \"target_id\",", - " \"target_type\"", - ") VALUES (", - " '{{uuid:person_id}}',", - " 'person',", - " '{{uuid:phone1_id}}',", - " 'phone_number'", - ")" - ], - [ - "INSERT INTO agreego.\"contact\" (", - " \"is_primary\"", - ") VALUES (", - " true", - ")" - ], - [ - "INSERT INTO agreego.change (", - " \"old\",", - " \"new\",", - " entity_id,", - " id,", - " kind,", - " modified_at,", - " modified_by", - ") VALUES (", - " NULL,", - " '{", - " \"is_primary\":true,", - " \"source_id\":\"{{uuid:person_id}}\",", - " \"source_type\":\"person\",", - " \"target_id\":\"{{uuid:phone1_id}}\",", - " \"target_type\":\"phone_number\",", - " \"type\":\"contact\"", - " }',", - " '{{uuid:contact1_id}}',", - " '{{uuid}}',", - " 'create',", - " '{{timestamp}}',", - " '00000000-0000-0000-0000-000000000000'", - ")" - ], - [ - "INSERT INTO agreego.\"entity\" (", - " \"created_at\",", - " \"created_by\",", - " \"id\",", - " \"modified_at\",", - " \"modified_by\",", - " \"type\"", - ") VALUES (", - " '{{timestamp}}',", - " '00000000-0000-0000-0000-000000000000',", - " '{{uuid:email1_id}}',", - " '{{timestamp}}',", - " '00000000-0000-0000-0000-000000000000',", - " 'email_address'", - ")" - ], - [ - "INSERT INTO agreego.\"email_address\" (", - " \"address\"", - ") VALUES (", - " 'test@example.com'", - ")" - ], - [ - "INSERT INTO agreego.change (", - " \"old\",", - " \"new\",", - " entity_id,", - " id,", - " kind,", - " modified_at,", - " modified_by", - ") VALUES (", - " NULL,", - " '{", - " \"address\":\"test@example.com\",", - " \"type\":\"email_address\"", - " }',", - " '{{uuid:email1_id}}',", - " '{{uuid}}',", - " 'create',", - " '{{timestamp}}',", - " '00000000-0000-0000-0000-000000000000'", - ")" - ], - [ - "INSERT INTO agreego.\"entity\" (", - " \"created_at\",", - " \"created_by\",", - " \"id\",", - " \"modified_at\",", - " \"modified_by\",", - " \"type\"", - ") VALUES (", - " '{{timestamp}}',", - " '00000000-0000-0000-0000-000000000000',", - " '{{uuid:contact2_id}}',", - " '{{timestamp}}',", - " '00000000-0000-0000-0000-000000000000',", - " 'contact'", - ")" - ], - [ - "INSERT INTO agreego.\"relationship\" (", - " \"source_id\",", - " \"source_type\",", - " \"target_id\",", - " \"target_type\"", - ") VALUES (", - " '{{uuid:person_id}}',", - " 'person',", - " '{{uuid:email1_id}}',", - " 'email_address'", - ")" - ], - [ - "INSERT INTO agreego.\"contact\" (", - " \"is_primary\"", - ") VALUES (", - " false", - ")" - ], - [ - "INSERT INTO agreego.change (", - " \"old\",", - " \"new\",", - " entity_id,", - " id,", - " kind,", - " modified_at,", - " modified_by", - ") VALUES (", - " NULL,", - " '{", - " \"is_primary\":false,", - " \"source_id\":\"{{uuid:person_id}}\",", - " \"source_type\":\"person\",", - " \"target_id\":\"{{uuid:email1_id}}\",", - " \"target_type\":\"email_address\",", - " \"type\":\"contact\"", - " }',", - " '{{uuid:contact2_id}}',", - " '{{uuid}}',", - " 'create',", - " '{{timestamp}}',", - " '00000000-0000-0000-0000-000000000000'", - ")" - ], - [ - "INSERT INTO agreego.\"entity\" (", - " \"created_at\",", - " \"created_by\",", - " \"id\",", - " \"modified_at\",", - " \"modified_by\",", - " \"type\"", - ") VALUES (", - " '{{timestamp}}',", - " '00000000-0000-0000-0000-000000000000',", - " '{{uuid:email2_id}}',", - " '{{timestamp}}',", - " '00000000-0000-0000-0000-000000000000',", - " 'email_address'", - ")" - ], - [ - "INSERT INTO agreego.\"email_address\" (", - " \"address\"", - ") VALUES (", - " 'test2@example.com'", - ")" - ], - [ - "INSERT INTO agreego.change (", - " \"old\",", - " \"new\",", - " entity_id,", - " id,", - " kind,", - " modified_at,", - " modified_by", - ") VALUES (", - " NULL,", - " '{", - " \"address\":\"test2@example.com\",", - " \"type\":\"email_address\"", - " }',", - " '{{uuid:email2_id}}',", - " '{{uuid}}',", - " 'create',", - " '{{timestamp}}',", - " '00000000-0000-0000-0000-000000000000'", - ")" - ], - [ - "INSERT INTO agreego.\"entity\" (", - " \"created_at\",", - " \"created_by\",", - " \"id\",", - " \"modified_at\",", - " \"modified_by\",", - " \"type\"", - ") VALUES (", - " '{{timestamp}}',", - " '00000000-0000-0000-0000-000000000000',", - " '{{uuid:contact3_id}}',", - " '{{timestamp}}',", - " '00000000-0000-0000-0000-000000000000',", - " 'contact'", - ")" - ], - [ - "INSERT INTO agreego.\"relationship\" (", - " \"source_id\",", - " \"source_type\",", - " \"target_id\",", - " \"target_type\"", - ") VALUES (", - " '{{uuid:person_id}}',", - " 'person',", - " '{{uuid:email2_id}}',", - " 'email_address'", - ")" - ], - [ - "INSERT INTO agreego.\"contact\" (", - " \"is_primary\"", - ") VALUES (", - " false", - ")" - ], - [ - "INSERT INTO agreego.change (", - " \"old\",", - " \"new\",", - " entity_id,", - " id,", - " kind,", - " modified_at,", - " modified_by", - ") VALUES (", - " NULL,", - " '{", - " \"is_primary\":false,", - " \"source_id\":\"{{uuid:person_id}}\",", - " \"source_type\":\"person\",", - " \"target_id\":\"{{uuid:email2_id}}\",", - " \"target_type\":\"email_address\",", - " \"type\":\"contact\"", - " }',", - " '{{uuid:contact3_id}}',", - " '{{uuid}}',", - " 'create',", - " '{{timestamp}}',", - " '00000000-0000-0000-0000-000000000000'", - ")" - ], - [ - "INSERT INTO agreego.change (", - " \"old\",", - " \"new\",", - " entity_id,", - " id,", - " kind,", - " modified_at,", - " modified_by", - ") VALUES (", - " NULL,", - " '{", - " \"first_name\":\"Relation\",", - " \"last_name\":\"Test\",", - " \"type\":\"person\"", - " }',", - " '{{uuid:person_id}}',", - " '{{uuid}}',", - " 'create',", - " '{{timestamp}}',", - " '00000000-0000-0000-0000-000000000000'", - ")" - ], - [ - "SELECT pg_notify('entity', '{", - " \"complete\":{", - " \"created_at\":\"{{timestamp}}\",", - " \"created_by\":\"00000000-0000-0000-0000-000000000000\",", - " \"first_name\":\"Relation\",", - " \"id\":\"{{uuid:person_id}}\",", - " \"last_name\":\"Test\",", - " \"modified_at\":\"{{timestamp}}\",", - " \"modified_by\":\"00000000-0000-0000-0000-000000000000\",", - " \"type\":\"person\"", - " },", - " \"new\":{", - " \"first_name\":\"Relation\",", - " \"last_name\":\"Test\",", - " \"type\":\"person\"", - " }", - " }')" - ], - [ - "SELECT pg_notify('entity', '{", - " \"complete\":{", - " \"created_at\":\"{{timestamp}}\",", - " \"created_by\":\"00000000-0000-0000-0000-000000000000\",", - " \"id\":\"{{uuid:contact1_id}}\",", - " \"is_primary\":true,", - " \"modified_at\":\"{{timestamp}}\",", - " \"modified_by\":\"00000000-0000-0000-0000-000000000000\",", - " \"source_id\":\"{{uuid:person_id}}\",", - " \"source_type\":\"person\",", - " \"target_id\":\"{{uuid:phone1_id}}\",", - " \"target_type\":\"phone_number\",", - " \"type\":\"contact\"", - " },", - " \"new\":{", - " \"is_primary\":true,", - " \"source_id\":\"{{uuid:person_id}}\",", - " \"source_type\":\"person\",", - " \"target_id\":\"{{uuid:phone1_id}}\",", - " \"target_type\":\"phone_number\",", - " \"type\":\"contact\"", - " }", - " }')" - ], - [ - "SELECT pg_notify('entity', '{", - " \"complete\":{", - " \"created_at\":\"{{timestamp}}\",", - " \"created_by\":\"00000000-0000-0000-0000-000000000000\",", - " \"id\":\"{{uuid:phone1_id}}\",", - " \"modified_at\":\"{{timestamp}}\",", - " \"modified_by\":\"00000000-0000-0000-0000-000000000000\",", - " \"number\":\"555-0001\",", - " \"type\":\"phone_number\"", - " },", - " \"new\":{", - " \"number\":\"555-0001\",", - " \"type\":\"phone_number\"", - " }", - " }')" - ], - [ - "SELECT pg_notify('entity', '{", - " \"complete\":{", - " \"created_at\":\"{{timestamp}}\",", - " \"created_by\":\"00000000-0000-0000-0000-000000000000\",", - " \"id\":\"{{uuid:contact2_id}}\",", - " \"is_primary\":false,", - " \"modified_at\":\"{{timestamp}}\",", - " \"modified_by\":\"00000000-0000-0000-0000-000000000000\",", - " \"source_id\":\"{{uuid:person_id}}\",", - " \"source_type\":\"person\",", - " \"target_id\":\"{{uuid:email1_id}}\",", - " \"target_type\":\"email_address\",", - " \"type\":\"contact\"", - " },", - " \"new\":{", - " \"is_primary\":false,", - " \"source_id\":\"{{uuid:person_id}}\",", - " \"source_type\":\"person\",", - " \"target_id\":\"{{uuid:email1_id}}\",", - " \"target_type\":\"email_address\",", - " \"type\":\"contact\"", - " }", - "}')" - ], - [ - "SELECT pg_notify('entity', '{", - " \"complete\":{", - " \"address\":\"test@example.com\",", - " \"created_at\":\"{{timestamp}}\",", - " \"created_by\":\"00000000-0000-0000-0000-000000000000\",", - " \"id\":\"{{uuid:email1_id}}\",", - " \"modified_at\":\"{{timestamp}}\",", - " \"modified_by\":\"00000000-0000-0000-0000-000000000000\",", - " \"type\":\"email_address\"", - " },", - " \"new\":{", - " \"address\":\"test@example.com\",", - " \"type\":\"email_address\"", - " }", - "}')" - ], - [ - "SELECT pg_notify('entity', '{", - " \"complete\":{", - " \"created_at\":\"{{timestamp}}\",", - " \"created_by\":\"00000000-0000-0000-0000-000000000000\",", - " \"id\":\"{{uuid:contact3_id}}\",", - " \"is_primary\":false,", - " \"modified_at\":\"{{timestamp}}\",", - " \"modified_by\":\"00000000-0000-0000-0000-000000000000\",", - " \"source_id\":\"{{uuid:person_id}}\",", - " \"source_type\":\"person\",", - " \"target_id\":\"{{uuid:email2_id}}\",", - " \"target_type\":\"email_address\",", - " \"type\":\"contact\"", - " },", - " \"new\":{", - " \"is_primary\":false,", - " \"source_id\":\"{{uuid:person_id}}\",", - " \"source_type\":\"person\",", - " \"target_id\":\"{{uuid:email2_id}}\",", - " \"target_type\":\"email_address\",", - " \"type\":\"contact\"", - " }", - "}')" - ], - [ - "SELECT pg_notify('entity', '{", - " \"complete\":{", - " \"address\":\"test2@example.com\",", - " \"created_at\":\"{{timestamp}}\",", - " \"created_by\":\"00000000-0000-0000-0000-000000000000\",", - " \"id\":\"{{uuid:email2_id}}\",", - " \"modified_at\":\"{{timestamp}}\",", - " \"modified_by\":\"00000000-0000-0000-0000-000000000000\",", - " \"type\":\"email_address\"", - " },", - " \"new\":{", - " \"address\":\"test2@example.com\",", - " \"type\":\"email_address\"", - " }", - "}')" - ] - ] - } - }, - { - "description": "Archive a person", - "action": "merge", - "data": { - "id": "abc-archived", - "type": "person", - "archived": true - }, - "mocks": [ - { - "id": "abc-archived", - "type": "person", - "first_name": "ArchivedFirst", - "last_name": "ArchivedLast", - "archived": false - } - ], - "schema_id": "person", - "expect": { - "success": true, - "sql": [ - [ - "SELECT to_jsonb(t1.*) || to_jsonb(t2.*) || to_jsonb(t3.*) || to_jsonb(t4.*)", - "FROM agreego.\"person\" t1", - "LEFT JOIN agreego.\"user\" t2 ON t2.id = t1.id", - "LEFT JOIN agreego.\"organization\" t3 ON t3.id = t1.id", - "LEFT JOIN agreego.\"entity\" t4 ON t4.id = t1.id", - "WHERE t1.id = 'abc-archived'" - ], - [ - "UPDATE agreego.\"entity\" SET", - " \"archived\" = true,", - " \"modified_at\" = '{{timestamp}}',", - " \"modified_by\" = '00000000-0000-0000-0000-000000000000'", - "WHERE id = 'abc-archived'" - ], - [ - "INSERT INTO agreego.change (", - " \"old\",", - " \"new\",", - " entity_id,", - " id,", - " kind,", - " modified_at,", - " modified_by", - ") VALUES (", - " '{", - " \"archived\":false", - " }',", - " '{\"archived\":true,\"type\":\"person\"}',", - " 'abc-archived',", - " '{{uuid}}',", - " 'delete',", - " '{{timestamp}}',", - " '00000000-0000-0000-0000-000000000000'", - ")" - ], - [ - "SELECT pg_notify('entity', '{", - " \"complete\":{", - " \"archived\":true,", - " \"first_name\":\"ArchivedFirst\",", - " \"id\":\"abc-archived\",", - " \"last_name\":\"ArchivedLast\",", - " \"modified_at\":\"{{timestamp}}\",", - " \"modified_by\":\"00000000-0000-0000-0000-000000000000\",", - " \"type\":\"person\"", - " },", - " \"new\":{", - " \"archived\":true,", - " \"type\":\"person\"", - " },", - " \"old\":{", - " \"archived\":false", - " }", - " }')" - ] - ] - } - }, - { - "description": "Attachment with text[] and jsonb metadata structures", - "action": "merge", - "data": { - "type": "attachment", - "flags": [ - "urgent", - "reviewed" - ], - "other_metadata": { - "other": "hello" - }, - "type_metadata": { - "type": "type_metadata" - } - }, - "schema_id": "attachment", - "expect": { - "success": true, - "sql": [ - [ - "INSERT INTO agreego.\"entity\" (", - " \"created_at\",", - " \"created_by\",", - " \"id\",", - " \"modified_at\",", - " \"modified_by\",", - " \"type\"", - ")", - "VALUES (", - " '{{timestamp}}',", - " '00000000-0000-0000-0000-000000000000',", - " '{{uuid:attachment_id}}',", - " '{{timestamp}}',", - " '00000000-0000-0000-0000-000000000000',", - " 'attachment'", - ")" - ], - [ - "INSERT INTO agreego.\"attachment\" (", - " \"flags\",", - " \"id\",", - " \"other_metadata\",", - " \"type\",", - " \"type_metadata\"", - ")", - "VALUES (", - " '{\"urgent\",\"reviewed\"}',", - " '{{uuid:attachment_id}}',", - " '{\"other\":\"hello\"}',", - " 'attachment',", - " '{\"type\":\"type_metadata\"}'", - ")" - ], - [ - "INSERT INTO agreego.change (", - " \"old\",", - " \"new\",", - " entity_id,", - " id,", - " kind,", - " modified_at,", - " modified_by", - ")", - "VALUES (", - " NULL,", - " '{", - " \"flags\":[\"urgent\",\"reviewed\"],", - " \"other_metadata\":{\"other\":\"hello\"},", - " \"type\":\"attachment\",", - " \"type_metadata\":{\"type\":\"type_metadata\"}", - " }',", - " '{{uuid:attachment_id}}',", - " '{{uuid}}',", - " 'create',", - " '{{timestamp}}',", - " '00000000-0000-0000-0000-000000000000'", - ")" - ], - [ - "SELECT pg_notify('entity', '{", - " \"complete\":{", - " \"created_at\":\"{{timestamp}}\",", - " \"created_by\":\"00000000-0000-0000-0000-000000000000\",", - " \"flags\":[\"urgent\",\"reviewed\"],", - " \"id\":\"{{uuid:attachment_id}}\",", - " \"modified_at\":\"{{timestamp}}\",", - " \"modified_by\":\"00000000-0000-0000-0000-000000000000\",", - " \"other_metadata\":{\"other\":\"hello\"},", - " \"type\":\"attachment\",", - " \"type_metadata\":{\"type\":\"type_metadata\"}", - " },", - " \"new\":{", - " \"flags\":[\"urgent\",\"reviewed\"],", - " \"other_metadata\":{\"other\":\"hello\"},", - " \"type\":\"attachment\",", - " \"type_metadata\":{\"type\":\"type_metadata\"}", - " }", - " }')" - ] - ] - } - }, - { - "description": "Anchor order and insert new line (no line id)", - "action": "merge", - "data": { - "id": "abc", - "type": "order", - "lines": [ - { - "type": "order_line", - "product": "Widget", - "price": 99.0 - } - ] - }, - "schema_id": "order", - "expect": { - "success": true, - "sql": [ - [ - "INSERT INTO agreego.\"entity\" (", - " \"created_at\",", - " \"created_by\",", - " \"id\",", - " \"modified_at\",", - " \"modified_by\",", - " \"type\"", - ")", - "VALUES (", - " '{{timestamp}}',", - " '00000000-0000-0000-0000-000000000000',", - " '{{uuid:line_id}}',", - " '{{timestamp}}',", - " '00000000-0000-0000-0000-000000000000',", - " 'order_line'", - ")" - ], - [ - "INSERT INTO agreego.\"order_line\" (", - " \"id\",", - " \"order_id\",", - " \"price\",", - " \"product\",", - " \"type\"", - ")", - "VALUES (", - " '{{uuid:line_id}}',", - " 'abc',", - " 99,", - " 'Widget',", - " 'order_line'", - ")" - ], - [ - "INSERT INTO agreego.change (", - " \"old\",", - " \"new\",", - " entity_id,", - " id,", - " kind,", - " modified_at,", - " modified_by", - ")", - "VALUES (", - " NULL,", - " '{", - " \"order_id\":\"abc\",", - " \"price\":99.0,", - " \"product\":\"Widget\",", - " \"type\":\"order_line\"", - " }',", - " '{{uuid:line_id}}',", - " '{{uuid}}',", - " 'create',", - " '{{timestamp}}',", - " '00000000-0000-0000-0000-000000000000'", - ")" - ], - [ - "SELECT pg_notify('entity', '{", - " \"complete\":{", - " \"created_at\":\"{{timestamp}}\",", - " \"created_by\":\"00000000-0000-0000-0000-000000000000\",", - " \"id\":\"{{uuid:line_id}}\",", - " \"modified_at\":\"{{timestamp}}\",", - " \"modified_by\":\"00000000-0000-0000-0000-000000000000\",", - " \"order_id\":\"abc\",", - " \"price\":99.0,", - " \"product\":\"Widget\",", - " \"type\":\"order_line\"", - " },", - " \"new\":{", - " \"order_id\":\"abc\",", - " \"price\":99.0,", - " \"product\":\"Widget\",", - " \"type\":\"order_line\"", - " }", - " }')" - ] - ] - } - }, - { - "description": "Anchor order and insert new line (with line id)", - "action": "merge", - "data": { - "id": "abc", - "type": "order", - "lines": [ - { - "id": "11111111-2222-3333-4444-555555555555", - "type": "order_line", - "product": "Widget", - "price": 99.0 - } - ] - }, - "schema_id": "order", - "expect": { - "success": true, - "sql": [ - [ - "SELECT to_jsonb(t1.*) || to_jsonb(t2.*)", - "FROM agreego.\"order_line\" t1", - "LEFT JOIN agreego.\"entity\" t2 ON t2.id = t1.id", - "WHERE t1.id = '11111111-2222-3333-4444-555555555555'" - ], - [ - "INSERT INTO agreego.\"entity\" (", - " \"created_at\",", - " \"created_by\",", - " \"id\",", - " \"modified_at\",", - " \"modified_by\",", - " \"type\"", - ")", - "VALUES (", - " '{{timestamp}}',", - " '00000000-0000-0000-0000-000000000000',", - " '11111111-2222-3333-4444-555555555555',", - " '{{timestamp}}',", - " '00000000-0000-0000-0000-000000000000',", - " 'order_line'", - ")" - ], - [ - "INSERT INTO agreego.\"order_line\" (", - " \"id\",", - " \"order_id\",", - " \"price\",", - " \"product\",", - " \"type\"", - ")", - "VALUES (", - " '11111111-2222-3333-4444-555555555555',", - " 'abc',", - " 99,", - " 'Widget',", - " 'order_line'", - ")" - ], - [ - "INSERT INTO agreego.change (", - " \"old\",", - " \"new\",", - " entity_id,", - " id,", - " kind,", - " modified_at,", - " modified_by", - ")", - "VALUES (", - " NULL,", - " '{", - " \"order_id\":\"abc\",", - " \"price\":99.0,", - " \"product\":\"Widget\",", - " \"type\":\"order_line\"", - " }',", - " '11111111-2222-3333-4444-555555555555',", - " '{{uuid}}',", - " 'create',", - " '{{timestamp}}',", - " '00000000-0000-0000-0000-000000000000'", - ")" - ], - [ - "SELECT pg_notify('entity', '{", - " \"complete\":{", - " \"created_at\":\"{{timestamp}}\",", - " \"created_by\":\"00000000-0000-0000-0000-000000000000\",", - " \"id\":\"11111111-2222-3333-4444-555555555555\",", - " \"modified_at\":\"{{timestamp}}\",", - " \"modified_by\":\"00000000-0000-0000-0000-000000000000\",", - " \"order_id\":\"abc\",", - " \"price\":99.0,", - " \"product\":\"Widget\",", - " \"type\":\"order_line\"", - " },", - " \"new\":{", - " \"order_id\":\"abc\",", - " \"price\":99.0,", - " \"product\":\"Widget\",", - " \"type\":\"order_line\"", - " }", - " }')" - ] - ] + "modified_at": { + "type": "string", + "format": "date-time" } + }, + "required": [ + "id", + "type", + "created_by", + "created_at", + "modified_by", + "modified_at" + ] } - ] - } + }, + "hierarchy": [ + "entity" + ], + "fields": [ + "id", + "type", + "created_at", + "created_by", + "modified_at", + "modified_by", + "archived" + ], + "grouped_fields": { + "entity": [ + "id", + "type", + "created_at", + "created_by", + "modified_at", + "modified_by", + "archived" + ] + }, + "lookup_fields": [], + "historical": true, + "notify": true, + "relationship": false + }, + { + "name": "organization", + "schemas": { + "organization": { + "type": "entity", + "properties": { + "name": { + "type": "string" + } + } + } + }, + "hierarchy": [ + "organization", + "entity" + ], + "fields": [ + "id", + "type", + "name", + "created_at", + "created_by", + "modified_at", + "modified_by", + "archived" + ], + "grouped_fields": { + "organization": [ + "id", + "type", + "name" + ], + "entity": [ + "id", + "type", + "created_at", + "created_by", + "modified_at", + "modified_by", + "archived" + ] + }, + "lookup_fields": [], + "historical": true, + "notify": true, + "relationship": false + }, + { + "name": "user", + "schemas": { + "user": { + "type": "organization", + "properties": {} + } + }, + "hierarchy": [ + "user", + "organization", + "entity" + ], + "fields": [ + "id", + "type", + "name", + "created_at", + "created_by", + "modified_at", + "modified_by", + "archived" + ], + "grouped_fields": { + "user": [ + "id", + "type" + ], + "organization": [ + "id", + "type", + "name" + ], + "entity": [ + "id", + "type", + "created_at", + "created_by", + "modified_at", + "modified_by", + "archived" + ] + }, + "lookup_fields": [], + "historical": true, + "notify": true, + "relationship": false + }, + { + "name": "person", + "schemas": { + "person": { + "type": "user", + "properties": { + "first_name": { + "type": "string" + }, + "last_name": { + "type": "string" + }, + "date_of_birth": { + "type": "string" + }, + "pronouns": { + "type": "string" + }, + "contact_id": { + "type": "string" + }, + "contacts": { + "type": "array", + "items": { + "type": "contact", + "properties": { + "target": { + "oneOf": [ + { + "type": "phone_number" + }, + { + "type": "email_address" + } + ] + } + } + } + }, + "email_addresses": { + "type": "array", + "items": { + "type": "contact", + "properties": { + "target": { + "type": "email_address" + } + } + } + } + } + } + }, + "hierarchy": [ + "person", + "user", + "organization", + "entity" + ], + "fields": [ + "id", + "type", + "first_name", + "last_name", + "date_of_birth", + "pronouns", + "contact_id", + "name", + "created_at", + "created_by", + "modified_at", + "modified_by", + "archived" + ], + "grouped_fields": { + "person": [ + "id", + "type", + "first_name", + "last_name", + "date_of_birth", + "pronouns", + "contact_id" + ], + "user": [ + "id", + "type" + ], + "organization": [ + "id", + "type", + "name" + ], + "entity": [ + "id", + "type", + "created_at", + "created_by", + "modified_at", + "modified_by", + "archived" + ] + }, + "lookup_fields": [ + "first_name", + "last_name", + "date_of_birth", + "pronouns" + ], + "historical": true, + "notify": true, + "relationship": false + }, + { + "name": "order", + "schemas": { + "order": { + "type": "entity", + "properties": { + "total": { + "type": "number" + }, + "customer_id": { + "type": "string" + }, + "customer": { + "type": "person" + }, + "lines": { + "type": "array", + "items": { + "type": "order_line" + } + } + } + } + }, + "hierarchy": [ + "order", + "entity" + ], + "fields": [ + "id", + "type", + "total", + "customer_id", + "created_at", + "created_by", + "modified_at", + "modified_by", + "archived" + ], + "grouped_fields": { + "order": [ + "id", + "type", + "total", + "customer_id" + ], + "entity": [ + "id", + "type", + "created_at", + "created_by", + "modified_at", + "modified_by", + "archived" + ] + }, + "lookup_fields": [ + "id" + ], + "historical": true, + "notify": true, + "relationship": false + }, + { + "name": "order_line", + "schemas": { + "order_line": { + "type": "entity", + "properties": { + "order_id": { + "type": "string" + }, + "product": { + "type": "string" + }, + "price": { + "type": "number" + } + } + } + }, + "hierarchy": [ + "order_line", + "entity" + ], + "fields": [ + "id", + "type", + "order_id", + "product", + "price", + "created_at", + "created_by", + "modified_at", + "modified_by", + "archived" + ], + "grouped_fields": { + "order_line": [ + "id", + "type", + "order_id", + "product", + "price" + ], + "entity": [ + "id", + "type", + "created_at", + "created_by", + "modified_at", + "modified_by", + "archived" + ] + }, + "lookup_fields": [], + "historical": true, + "notify": true, + "relationship": false + }, + { + "name": "relationship", + "relationship": true, + "hierarchy": [ + "relationship", + "entity" + ], + "fields": [ + "source_id", + "source_type", + "target_id", + "target_type", + "id", + "type", + "name", + "archived", + "created_at", + "created_by", + "modified_at", + "modified_by" + ], + "grouped_fields": { + "entity": [ + "id", + "type", + "name", + "archived", + "created_at", + "created_by", + "modified_at", + "modified_by" + ], + "relationship": [ + "source_id", + "source_type", + "target_id", + "target_type" + ] + }, + "field_types": { + "id": "uuid", + "type": "text", + "archived": "boolean", + "source_id": "uuid", + "source_type": "text", + "target_id": "uuid", + "target_type": "text", + "name": "text", + "created_at": "timestamptz", + "created_by": "uuid", + "modified_at": "timestamptz", + "modified_by": "uuid" + }, + "schemas": { + "relationship": { + "type": "entity", + "properties": {} + } + }, + "lookup_fields": [], + "historical": true, + "notify": true + }, + { + "name": "contact", + "relationship": true, + "hierarchy": [ + "contact", + "relationship", + "entity" + ], + "fields": [ + "is_primary", + "source_id", + "source_type", + "target_id", + "target_type", + "id", + "type", + "name", + "archived", + "created_at", + "created_by", + "modified_at", + "modified_by" + ], + "grouped_fields": { + "entity": [ + "id", + "type", + "name", + "archived", + "created_at", + "created_by", + "modified_at", + "modified_by" + ], + "relationship": [ + "source_id", + "source_type", + "target_id", + "target_type" + ], + "contact": [ + "is_primary" + ] + }, + "field_types": { + "id": "uuid", + "type": "text", + "archived": "boolean", + "source_id": "uuid", + "source_type": "text", + "target_id": "uuid", + "target_type": "text", + "is_primary": "boolean", + "name": "text", + "created_at": "timestamptz", + "created_by": "uuid", + "modified_at": "timestamptz", + "modified_by": "uuid" + }, + "schemas": { + "contact": { + "type": "relationship", + "properties": { + "is_primary": { + "type": "boolean" + } + } + } + }, + "lookup_fields": [], + "historical": true, + "notify": true + }, + { + "name": "phone_number", + "hierarchy": [ + "phone_number", + "entity" + ], + "fields": [ + "number", + "id", + "type", + "name", + "archived", + "created_at", + "created_by", + "modified_at", + "modified_by" + ], + "grouped_fields": { + "entity": [ + "id", + "type", + "name", + "archived", + "created_at", + "created_by", + "modified_at", + "modified_by" + ], + "phone_number": [ + "number" + ] + }, + "field_types": { + "id": "uuid", + "type": "text", + "archived": "boolean", + "number": "text", + "name": "text", + "created_at": "timestamptz", + "created_by": "uuid", + "modified_at": "timestamptz", + "modified_by": "uuid" + }, + "schemas": { + "phone_number": { + "type": "entity", + "properties": { + "number": { + "type": "string" + } + } + } + }, + "lookup_fields": [], + "historical": true, + "notify": true, + "relationship": false + }, + { + "name": "email_address", + "hierarchy": [ + "email_address", + "entity" + ], + "fields": [ + "address", + "id", + "type", + "name", + "archived", + "created_at", + "created_by", + "modified_at", + "modified_by" + ], + "grouped_fields": { + "entity": [ + "id", + "type", + "name", + "archived", + "created_at", + "created_by", + "modified_at", + "modified_by" + ], + "email_address": [ + "address" + ] + }, + "field_types": { + "id": "uuid", + "type": "text", + "archived": "boolean", + "address": "text", + "name": "text", + "created_at": "timestamptz", + "created_by": "uuid", + "modified_at": "timestamptz", + "modified_by": "uuid" + }, + "schemas": { + "email_address": { + "type": "entity", + "properties": { + "address": { + "type": "string" + } + } + } + }, + "lookup_fields": [], + "historical": true, + "notify": true, + "relationship": false + }, + { + "name": "attachment", + "schemas": { + "type_metadata": { + "type": "object", + "properties": { + "type": { + "type": "string" + } + } + }, + "other_metadata": { + "type": "object", + "properties": { + "other": { + "type": "string" + } + } + }, + "attachment": { + "type": "entity", + "properties": { + "flags": { + "type": "array", + "items": { + "type": "string" + } + }, + "type_metadata": { + "type": "type_metadata" + }, + "other_metadata": { + "type": "other_metadata" + } + } + } + }, + "hierarchy": [ + "attachment", + "entity" + ], + "fields": [ + "id", + "type", + "flags", + "type_metadata", + "other_metadata", + "created_at", + "created_by", + "modified_at", + "modified_by", + "archived" + ], + "grouped_fields": { + "attachment": [ + "id", + "type", + "flags", + "type_metadata", + "other_metadata" + ], + "entity": [ + "id", + "type", + "created_at", + "created_by", + "modified_at", + "modified_by", + "archived" + ] + }, + "field_types": { + "id": "uuid", + "type": "text", + "flags": "_text", + "type_metadata": "jsonb", + "other_metadata": "jsonb", + "created_at": "timestamptz", + "created_by": "uuid", + "modified_at": "timestamptz", + "modified_by": "uuid", + "archived": "boolean" + }, + "lookup_fields": [], + "historical": true, + "notify": true, + "relationship": false + } + ] + }, + "tests": [ + { + "description": "Insert person without id (no lookup)", + "action": "merge", + "data": { + "type": "person", + "first_name": "IncompleteFirst", + "last_name": "IncompleteLast" + }, + "mocks": [ + { + "id": "33333333-3333-3333-3333-333333333333", + "type": "person", + "first_name": "IncompleteFirst", + "last_name": "IncompleteLast", + "date_of_birth": "1990-01-01T00:00:00Z", + "pronouns": "they/them", + "contact_id": "old-contact" + } + ], + "schema_id": "person", + "expect": { + "success": true, + "sql": [ + [ + "INSERT INTO agreego.\"entity\" (", + " \"created_at\",", + " \"created_by\",", + " \"id\",", + " \"modified_at\",", + " \"modified_by\",", + " \"type\"", + ")", + "VALUES (", + " '{{timestamp}}',", + " '00000000-0000-0000-0000-000000000000',", + " '{{uuid:person_id}}',", + " '{{timestamp}}',", + " '00000000-0000-0000-0000-000000000000',", + " 'person'", + ")" + ], + [ + "INSERT INTO agreego.\"organization\" (", + " \"id\",", + " \"type\"", + ")", + "VALUES (", + " '{{uuid:person_id}}',", + " 'person'", + ")" + ], + [ + "INSERT INTO agreego.\"user\" (", + " \"id\",", + " \"type\"", + ")", + "VALUES (", + " '{{uuid:person_id}}',", + " 'person'", + ")" + ], + [ + "INSERT INTO agreego.\"person\" (", + " \"first_name\",", + " \"id\",", + " \"last_name\",", + " \"type\"", + ")", + "VALUES (", + " 'IncompleteFirst',", + " '{{uuid:person_id}}',", + " 'IncompleteLast',", + " 'person'", + ")" + ], + [ + "INSERT INTO agreego.change (", + " \"old\",", + " \"new\",", + " entity_id,", + " id,", + " kind,", + " modified_at,", + " modified_by", + ")", + "VALUES (", + " NULL,", + " '{", + " \"first_name\":\"IncompleteFirst\",", + " \"last_name\":\"IncompleteLast\",", + " \"type\":\"person\"", + " }',", + " '{{uuid:person_id}}',", + " '{{uuid}}',", + " 'create',", + " '{{timestamp}}',", + " '00000000-0000-0000-0000-000000000000'", + ")" + ], + [ + "SELECT pg_notify('entity', '{", + " \"complete\":{", + " \"created_at\":\"{{timestamp}}\",", + " \"created_by\":\"00000000-0000-0000-0000-000000000000\",", + " \"first_name\":\"IncompleteFirst\",", + " \"id\":\"{{uuid:person_id}}\",", + " \"last_name\":\"IncompleteLast\",", + " \"modified_at\":\"{{timestamp}}\",", + " \"modified_by\":\"00000000-0000-0000-0000-000000000000\",", + " \"type\":\"person\"", + " },", + " \"new\":{", + " \"first_name\":\"IncompleteFirst\",", + " \"last_name\":\"IncompleteLast\",", + " \"type\":\"person\"", + " }", + " }')" + ] + ] + } + }, + { + "description": "Update existing person without id (lookup)", + "action": "merge", + "data": { + "type": "person", + "first_name": "LookupFirst", + "last_name": "LookupLast", + "date_of_birth": "1990-01-01T00:00:00Z", + "pronouns": "they/them", + "contact_id": "abc-contact" + }, + "mocks": [ + { + "id": "22222222-2222-2222-2222-222222222222", + "type": "person", + "first_name": "LookupFirst", + "last_name": "LookupLast", + "date_of_birth": "1990-01-01T00:00:00Z", + "pronouns": "they/them", + "contact_id": "old-contact" + } + ], + "schema_id": "person", + "expect": { + "success": true, + "sql": [ + [ + "SELECT to_jsonb(t1.*) || to_jsonb(t2.*) || to_jsonb(t3.*) || to_jsonb(t4.*)", + "FROM agreego.\"person\" t1", + "LEFT JOIN agreego.\"user\" t2 ON t2.id = t1.id", + "LEFT JOIN agreego.\"organization\" t3 ON t3.id = t1.id", + "LEFT JOIN agreego.\"entity\" t4 ON t4.id = t1.id", + "WHERE (", + " \"first_name\" = 'LookupFirst'", + " AND \"last_name\" = 'LookupLast'", + " AND \"date_of_birth\" = '1990-01-01T00:00:00Z'", + " AND \"pronouns\" = 'they/them'", + ")" + ], + [ + "UPDATE agreego.\"person\"", + "SET", + " \"contact_id\" = 'abc-contact'", + "WHERE", + " id = '22222222-2222-2222-2222-222222222222'" + ], + [ + "UPDATE agreego.\"entity\"", + "SET", + " \"modified_at\" = '2026-03-10T00:00:00Z',", + " \"modified_by\" = '00000000-0000-0000-0000-000000000000'", + "WHERE", + " id = '22222222-2222-2222-2222-222222222222'" + ], + [ + "INSERT INTO agreego.change (", + " \"old\",", + " \"new\",", + " entity_id,", + " id,", + " kind,", + " modified_at,", + " modified_by", + ")", + "VALUES (", + " '{", + " \"contact_id\":\"old-contact\"", + " }',", + " '{", + " \"contact_id\":\"abc-contact\",", + " \"type\":\"person\"", + " }',", + " '22222222-2222-2222-2222-222222222222',", + " '{{uuid}}',", + " 'update',", + " '{{timestamp}}',", + " '00000000-0000-0000-0000-000000000000'", + ")" + ], + [ + "SELECT pg_notify('entity', '{", + " \"complete\":{", + " \"contact_id\":\"abc-contact\",", + " \"date_of_birth\":\"1990-01-01T00:00:00Z\",", + " \"first_name\":\"LookupFirst\",", + " \"id\":\"22222222-2222-2222-2222-222222222222\",", + " \"last_name\":\"LookupLast\",", + " \"modified_at\":\"2026-03-10T00:00:00Z\",", + " \"modified_by\":\"00000000-0000-0000-0000-000000000000\",", + " \"pronouns\":\"they/them\",", + " \"type\":\"person\"", + " },", + " \"new\":{", + " \"contact_id\":\"abc-contact\",", + " \"type\":\"person\"", + " },", + " \"old\":{", + " \"contact_id\":\"old-contact\"", + " }", + " }')" + ] + ] + } + }, + { + "description": "Update existing person with id (lookup)", + "action": "merge", + "data": { + "id": "33333333-3333-3333-3333-333333333333", + "type": "person", + "first_name": "LookupFirst", + "last_name": "LookupLast", + "date_of_birth": "1990-01-01T00:00:00Z", + "pronouns": "they/them", + "contact_id": "abc-contact" + }, + "mocks": [ + { + "id": "22222222-2222-2222-2222-222222222222", + "type": "person", + "first_name": "LookupFirst", + "last_name": "LookupLast", + "date_of_birth": "1990-01-01T00:00:00Z", + "pronouns": "they/them", + "contact_id": "old-contact" + } + ], + "schema_id": "person", + "expect": { + "success": true, + "sql": [ + [ + "SELECT to_jsonb(t1.*) || to_jsonb(t2.*) || to_jsonb(t3.*) || to_jsonb(t4.*)", + "FROM agreego.\"person\" t1", + "LEFT JOIN agreego.\"user\" t2 ON t2.id = t1.id", + "LEFT JOIN agreego.\"organization\" t3 ON t3.id = t1.id", + "LEFT JOIN agreego.\"entity\" t4 ON t4.id = t1.id", + "WHERE", + " t1.id = '33333333-3333-3333-3333-333333333333'", + " OR (", + " \"first_name\" = 'LookupFirst'", + " AND \"last_name\" = 'LookupLast'", + " AND \"date_of_birth\" = '1990-01-01T00:00:00Z'", + " AND \"pronouns\" = 'they/them'", + " )" + ], + [ + "UPDATE agreego.\"person\"", + "SET", + " \"contact_id\" = 'abc-contact'", + "WHERE", + " id = '22222222-2222-2222-2222-222222222222'" + ], + [ + "UPDATE agreego.\"entity\"", + "SET", + " \"modified_at\" = '2026-03-10T00:00:00Z',", + " \"modified_by\" = '00000000-0000-0000-0000-000000000000'", + "WHERE", + " id = '22222222-2222-2222-2222-222222222222'" + ], + [ + "INSERT INTO agreego.change (", + " \"old\",", + " \"new\",", + " entity_id,", + " id,", + " kind,", + " modified_at,", + " modified_by", + ")", + "VALUES (", + " '{", + " \"contact_id\":\"old-contact\"", + " }',", + " '{", + " \"contact_id\":\"abc-contact\",", + " \"type\":\"person\"", + " }',", + " '22222222-2222-2222-2222-222222222222',", + " '{{uuid}}',", + " 'update',", + " '{{timestamp}}',", + " '00000000-0000-0000-0000-000000000000'", + ")" + ], + [ + "SELECT pg_notify('entity', '{", + " \"complete\":{", + " \"contact_id\":\"abc-contact\",", + " \"date_of_birth\":\"1990-01-01T00:00:00Z\",", + " \"first_name\":\"LookupFirst\",", + " \"id\":\"22222222-2222-2222-2222-222222222222\",", + " \"last_name\":\"LookupLast\",", + " \"modified_at\":\"2026-03-10T00:00:00Z\",", + " \"modified_by\":\"00000000-0000-0000-0000-000000000000\",", + " \"pronouns\":\"they/them\",", + " \"type\":\"person\"", + " },", + " \"new\":{", + " \"contact_id\":\"abc-contact\",", + " \"type\":\"person\"", + " },", + " \"old\":{", + " \"contact_id\":\"old-contact\"", + " },", + " \"replaces\":\"33333333-3333-3333-3333-333333333333\"", + " }')" + ] + ] + } + }, + { + "description": "Replace existing person with id and no changes (lookup)", + "action": "merge", + "data": { + "id": "33333333-3333-3333-3333-333333333333", + "type": "person", + "first_name": "LookupFirst", + "last_name": "LookupLast", + "date_of_birth": "1990-01-01T00:00:00Z", + "pronouns": "they/them" + }, + "mocks": [ + { + "id": "22222222-2222-2222-2222-222222222222", + "type": "person", + "first_name": "LookupFirst", + "last_name": "LookupLast", + "date_of_birth": "1990-01-01T00:00:00Z", + "pronouns": "they/them", + "contact_id": "old-contact" + } + ], + "schema_id": "person", + "expect": { + "success": true, + "sql": [ + [ + "SELECT to_jsonb(t1.*) || to_jsonb(t2.*) || to_jsonb(t3.*) || to_jsonb(t4.*)", + "FROM agreego.\"person\" t1", + "LEFT JOIN agreego.\"user\" t2 ON t2.id = t1.id", + "LEFT JOIN agreego.\"organization\" t3 ON t3.id = t1.id", + "LEFT JOIN agreego.\"entity\" t4 ON t4.id = t1.id", + "WHERE", + " t1.id = '33333333-3333-3333-3333-333333333333'", + " OR (", + " \"first_name\" = 'LookupFirst'", + " AND \"last_name\" = 'LookupLast'", + " AND \"date_of_birth\" = '1990-01-01T00:00:00Z'", + " AND \"pronouns\" = 'they/them'", + " )" + ], + [ + "SELECT pg_notify('entity', '{", + " \"complete\":{", + " \"contact_id\":\"old-contact\",", + " \"date_of_birth\":\"1990-01-01T00:00:00Z\",", + " \"first_name\":\"LookupFirst\",", + " \"id\":\"22222222-2222-2222-2222-222222222222\",", + " \"last_name\":\"LookupLast\",", + " \"modified_at\":\"2026-03-10T00:00:00Z\",", + " \"modified_by\":\"00000000-0000-0000-0000-000000000000\",", + " \"pronouns\":\"they/them\",", + " \"type\":\"person\"", + " },", + " \"new\":{", + " \"type\":\"person\"", + " },", + " \"replaces\":\"33333333-3333-3333-3333-333333333333\"", + " }')" + ] + ] + } + }, + { + "description": "Update existing person with id (no lookup)", + "action": "merge", + "data": { + "id": "11111111-1111-1111-1111-111111111111", + "type": "person", + "first_name": "NewFirst", + "last_name": "NewLast" + }, + "mocks": [ + { + "id": "11111111-1111-1111-1111-111111111111", + "type": "person", + "first_name": "OldFirst", + "last_name": "OldLast" + } + ], + "schema_id": "person", + "expect": { + "success": true, + "sql": [ + [ + "SELECT to_jsonb(t1.*) || to_jsonb(t2.*) || to_jsonb(t3.*) || to_jsonb(t4.*)", + "FROM agreego.\"person\" t1", + "LEFT JOIN agreego.\"user\" t2 ON t2.id = t1.id", + "LEFT JOIN agreego.\"organization\" t3 ON t3.id = t1.id", + "LEFT JOIN agreego.\"entity\" t4 ON t4.id = t1.id", + "WHERE t1.id = '11111111-1111-1111-1111-111111111111'" + ], + [ + "UPDATE agreego.\"person\"", + "SET", + " \"first_name\" = 'NewFirst',", + " \"last_name\" = 'NewLast'", + "WHERE", + " id = '11111111-1111-1111-1111-111111111111'" + ], + [ + "UPDATE agreego.\"entity\"", + "SET", + " \"modified_at\" = '2026-03-10T00:00:00Z',", + " \"modified_by\" = '00000000-0000-0000-0000-000000000000'", + "WHERE", + " id = '11111111-1111-1111-1111-111111111111'" + ], + [ + "INSERT INTO agreego.change (", + " \"old\",", + " \"new\",", + " entity_id,", + " id,", + " kind,", + " modified_at,", + " modified_by", + ")", + "VALUES (", + " '{", + " \"first_name\":\"OldFirst\",", + " \"last_name\":\"OldLast\"", + " }',", + " '{", + " \"first_name\":\"NewFirst\",", + " \"last_name\":\"NewLast\",", + " \"type\":\"person\"", + " }',", + " '11111111-1111-1111-1111-111111111111',", + " '{{uuid}}',", + " 'update',", + " '{{timestamp}}',", + " '00000000-0000-0000-0000-000000000000'", + ")" + ], + [ + "SELECT pg_notify('entity', '{", + " \"complete\":{", + " \"first_name\":\"NewFirst\",", + " \"id\":\"11111111-1111-1111-1111-111111111111\",", + " \"last_name\":\"NewLast\",", + " \"modified_at\":\"2026-03-10T00:00:00Z\",", + " \"modified_by\":\"00000000-0000-0000-0000-000000000000\",", + " \"type\":\"person\"", + " },", + " \"new\":{", + " \"first_name\":\"NewFirst\",", + " \"last_name\":\"NewLast\",", + " \"type\":\"person\"", + " },", + " \"old\":{", + " \"first_name\":\"OldFirst\",", + " \"last_name\":\"OldLast\"", + " }", + " }')" + ] + ] + } + }, + { + "description": "Empty strings null out fields", + "action": "merge", + "data": { + "id": "123", + "type": "person", + "first_name": "John", + "last_name": "Doe", + "date_of_birth": "1990-01-01T00:00:00Z", + "pronouns": "" + }, + "schema_id": "person", + "expect": { + "success": true, + "sql": [ + [ + "SELECT to_jsonb(t1.*) || to_jsonb(t2.*) || to_jsonb(t3.*) || to_jsonb(t4.*)", + "FROM agreego.\"person\" t1", + "LEFT JOIN agreego.\"user\" t2 ON t2.id = t1.id", + "LEFT JOIN agreego.\"organization\" t3 ON t3.id = t1.id", + "LEFT JOIN agreego.\"entity\" t4 ON t4.id = t1.id", + "WHERE t1.id = '123'" + ], + [ + "INSERT INTO agreego.\"entity\" (", + " \"created_at\",", + " \"created_by\",", + " \"id\",", + " \"modified_at\",", + " \"modified_by\",", + " \"type\"", + ")", + "VALUES (", + " '{{timestamp}}',", + " '00000000-0000-0000-0000-000000000000',", + " '123',", + " '{{timestamp}}',", + " '00000000-0000-0000-0000-000000000000',", + " 'person'", + ")" + ], + [ + "INSERT INTO agreego.\"organization\" (", + " \"id\",", + " \"type\"", + ")", + "VALUES (", + " '123',", + " 'person'", + ")" + ], + [ + "INSERT INTO agreego.\"user\" (", + " \"id\",", + " \"type\"", + ")", + "VALUES (", + " '123',", + " 'person'", + ")" + ], + [ + "INSERT INTO agreego.\"person\" (", + " \"date_of_birth\",", + " \"first_name\",", + " \"id\",", + " \"last_name\",", + " \"pronouns\",", + " \"type\"", + ")", + "VALUES (", + " '1990-01-01T00:00:00Z',", + " 'John',", + " '123',", + " 'Doe',", + " NULL,", + " 'person'", + ")" + ], + [ + "INSERT INTO agreego.change (", + " \"old\",", + " \"new\",", + " entity_id,", + " id,", + " kind,", + " modified_at,", + " modified_by", + ")", + "VALUES (", + " NULL,", + " '{", + " \"date_of_birth\":\"1990-01-01T00:00:00Z\",", + " \"first_name\":\"John\",", + " \"last_name\":\"Doe\",", + " \"pronouns\":\"\",", + " \"type\":\"person\"", + " }',", + " '123',", + " '{{uuid}}',", + " 'create',", + " '{{timestamp}}',", + " '00000000-0000-0000-0000-000000000000'", + ")" + ], + [ + "SELECT pg_notify('entity', '{", + " \"complete\":{", + " \"created_at\":\"{{timestamp}}\",", + " \"created_by\":\"00000000-0000-0000-0000-000000000000\",", + " \"date_of_birth\":\"1990-01-01T00:00:00Z\",", + " \"first_name\":\"John\",", + " \"id\":\"123\",", + " \"last_name\":\"Doe\",", + " \"modified_at\":\"{{timestamp}}\",", + " \"modified_by\":\"00000000-0000-0000-0000-000000000000\",", + " \"pronouns\":\"\",", + " \"type\":\"person\"", + " },", + " \"new\":{", + " \"date_of_birth\":\"1990-01-01T00:00:00Z\",", + " \"first_name\":\"John\",", + " \"last_name\":\"Doe\",", + " \"pronouns\":\"\",", + " \"type\":\"person\"", + " }", + " }')" + ] + ] + } + }, + { + "description": "Insert order with nested customer person", + "action": "merge", + "data": { + "type": "order", + "total": 100.0, + "customer": { + "type": "person", + "first_name": "Bob", + "last_name": "Smith", + "date_of_birth": "2000-01-01" + } + }, + "schema_id": "order", + "expect": { + "success": true, + "sql": [ + [ + "INSERT INTO agreego.\"entity\" (", + " \"created_at\",", + " \"created_by\",", + " \"id\",", + " \"modified_at\",", + " \"modified_by\",", + " \"type\"", + ")", + "VALUES (", + " '{{timestamp}}',", + " '00000000-0000-0000-0000-000000000000',", + " '{{uuid:customer_id}}',", + " '{{timestamp}}',", + " '00000000-0000-0000-0000-000000000000',", + " 'person'", + ")" + ], + [ + "INSERT INTO agreego.\"organization\" (", + " \"id\",", + " \"type\"", + ")", + "VALUES (", + " '{{uuid:customer_id}}',", + " 'person'", + ")" + ], + [ + "INSERT INTO agreego.\"user\" (", + " \"id\",", + " \"type\"", + ")", + "VALUES (", + " '{{uuid:customer_id}}',", + " 'person'", + ")" + ], + [ + "INSERT INTO agreego.\"person\" (", + " \"date_of_birth\",", + " \"first_name\",", + " \"id\",", + " \"last_name\",", + " \"type\"", + ")", + "VALUES (", + " '2000-01-01',", + " 'Bob',", + " '{{uuid:customer_id}}',", + " 'Smith',", + " 'person'", + ")" + ], + [ + "INSERT INTO agreego.change (", + " \"old\",", + " \"new\",", + " entity_id,", + " id,", + " kind,", + " modified_at,", + " modified_by", + ")", + "VALUES (", + " NULL,", + " '{", + " \"date_of_birth\":\"2000-01-01\",", + " \"first_name\":\"Bob\",", + " \"last_name\":\"Smith\",", + " \"type\":\"person\"", + " }',", + " '{{uuid:customer_id}}',", + " '{{uuid}}',", + " 'create',", + " '{{timestamp}}',", + " '00000000-0000-0000-0000-000000000000'", + ")" + ], + [ + "INSERT INTO agreego.\"entity\" (", + " \"created_at\",", + " \"created_by\",", + " \"id\",", + " \"modified_at\",", + " \"modified_by\",", + " \"type\"", + ")", + "VALUES (", + " '{{timestamp}}',", + " '00000000-0000-0000-0000-000000000000',", + " '{{uuid:order_id}}',", + " '{{timestamp}}',", + " '00000000-0000-0000-0000-000000000000',", + " 'order'", + ")" + ], + [ + "INSERT INTO agreego.\"order\" (", + " \"customer_id\",", + " \"id\",", + " \"total\",", + " \"type\"", + ")", + "VALUES (", + " '{{uuid:customer_id}}',", + " '{{uuid:order_id}}',", + " 100,", + " 'order'", + ")" + ], + [ + "INSERT INTO agreego.change (", + " \"old\",", + " \"new\",", + " entity_id,", + " id,", + " kind,", + " modified_at,", + " modified_by", + ")", + "VALUES (", + " NULL,", + " '{", + " \"customer_id\":\"{{uuid:customer_id}}\",", + " \"total\":100.0,", + " \"type\":\"order\"", + " }',", + " '{{uuid:order_id}}',", + " '{{uuid}}',", + " 'create',", + " '{{timestamp}}',", + " '00000000-0000-0000-0000-000000000000'", + ")" + ], + [ + "SELECT pg_notify('entity', '{", + " \"complete\":{", + " \"created_at\":\"{{timestamp}}\",", + " \"created_by\":\"00000000-0000-0000-0000-000000000000\",", + " \"customer_id\":\"{{uuid:customer_id}}\",", + " \"id\":\"{{uuid:order_id}}\",", + " \"modified_at\":\"{{timestamp}}\",", + " \"modified_by\":\"00000000-0000-0000-0000-000000000000\",", + " \"total\":100.0,", + " \"type\":\"order\"", + " },", + " \"new\":{", + " \"customer_id\":\"{{uuid:customer_id}}\",", + " \"total\":100.0,", + " \"type\":\"order\"", + " }", + " }')" + ], + [ + "SELECT pg_notify('entity', '{", + " \"complete\":{", + " \"created_at\":\"{{timestamp}}\",", + " \"created_by\":\"00000000-0000-0000-0000-000000000000\",", + " \"date_of_birth\":\"2000-01-01\",", + " \"first_name\":\"Bob\",", + " \"id\":\"{{uuid:customer_id}}\",", + " \"last_name\":\"Smith\",", + " \"modified_at\":\"{{timestamp}}\",", + " \"modified_by\":\"00000000-0000-0000-0000-000000000000\",", + " \"type\":\"person\"", + " },", + " \"new\":{", + " \"date_of_birth\":\"2000-01-01\",", + " \"first_name\":\"Bob\",", + " \"last_name\":\"Smith\",", + " \"type\":\"person\"", + " }", + " }')" + ] + ] + } + }, + { + "description": "Insert order with nested order lines", + "action": "merge", + "data": { + "id": "abc", + "type": "order", + "total": 99.0, + "lines": [ + { + "type": "order_line", + "product": "Widget", + "price": 99.0 + } + ] + }, + "schema_id": "order", + "expect": { + "success": true, + "sql": [ + [ + "SELECT to_jsonb(t1.*) || to_jsonb(t2.*)", + "FROM agreego.\"order\" t1", + "LEFT JOIN agreego.\"entity\" t2 ON t2.id = t1.id", + "WHERE t1.id = 'abc' OR (\"id\" = 'abc')" + ], + [ + "INSERT INTO agreego.\"entity\" (", + " \"created_at\",", + " \"created_by\",", + " \"id\",", + " \"modified_at\",", + " \"modified_by\",", + " \"type\"", + ")", + "VALUES (", + " '{{timestamp}}',", + " '00000000-0000-0000-0000-000000000000',", + " 'abc',", + " '{{timestamp}}',", + " '00000000-0000-0000-0000-000000000000',", + " 'order'", + ")" + ], + [ + "INSERT INTO agreego.\"order\" (", + " \"id\",", + " \"total\",", + " \"type\"", + ")", + "VALUES (", + " 'abc',", + " 99,", + " 'order'", + ")" + ], + [ + "INSERT INTO agreego.\"entity\" (", + " \"created_at\",", + " \"created_by\",", + " \"id\",", + " \"modified_at\",", + " \"modified_by\",", + " \"type\"", + ")", + "VALUES (", + " '{{timestamp}}',", + " '00000000-0000-0000-0000-000000000000',", + " '{{uuid:line_id}}',", + " '{{timestamp}}',", + " '00000000-0000-0000-0000-000000000000',", + " 'order_line'", + ")" + ], + [ + "INSERT INTO agreego.\"order_line\" (", + " \"id\",", + " \"order_id\",", + " \"price\",", + " \"product\",", + " \"type\")", + "VALUES (", + " '{{uuid:line_id}}',", + " 'abc',", + " 99,", + " 'Widget',", + " 'order_line'", + ")" + ], + [ + "INSERT INTO agreego.change (", + " \"old\",", + " \"new\",", + " entity_id,", + " id,", + " kind,", + " modified_at,", + " modified_by", + ")", + "VALUES (", + " NULL,", + " '{", + " \"order_id\":\"abc\",", + " \"price\":99.0,", + " \"product\":\"Widget\",", + " \"type\":\"order_line\"", + " }',", + " '{{uuid:line_id}}',", + " '{{uuid}}',", + " 'create',", + " '{{timestamp}}',", + " '00000000-0000-0000-0000-000000000000'", + ")" + ], + [ + "INSERT INTO agreego.change (", + " \"old\",", + " \"new\",", + " entity_id,", + " id,", + " kind,", + " modified_at,", + " modified_by", + ")", + "VALUES (", + " NULL,", + " '{", + " \"total\":99.0,", + " \"type\":\"order\"", + " }',", + " 'abc',", + " '{{uuid}}',", + " 'create',", + " '{{timestamp}}',", + " '00000000-0000-0000-0000-000000000000'", + ")" + ], + [ + "SELECT pg_notify('entity', '{", + " \"complete\":{", + " \"created_at\":\"{{timestamp}}\",", + " \"created_by\":\"00000000-0000-0000-0000-000000000000\",", + " \"id\":\"abc\",", + " \"modified_at\":\"{{timestamp}}\",", + " \"modified_by\":\"00000000-0000-0000-0000-000000000000\",", + " \"total\":99.0,", + " \"type\":\"order\"", + " },", + " \"new\":{", + " \"total\":99.0,", + " \"type\":\"order\"", + " }", + " }')" + ], + [ + "SELECT pg_notify('entity', '{", + " \"complete\":{", + " \"created_at\":\"{{timestamp}}\",", + " \"created_by\":\"00000000-0000-0000-0000-000000000000\",", + " \"id\":\"{{uuid:line_id}}\",", + " \"modified_at\":\"{{timestamp}}\",", + " \"modified_by\":\"00000000-0000-0000-0000-000000000000\",", + " \"order_id\":\"abc\",", + " \"price\":99.0,", + " \"product\":\"Widget\",", + " \"type\":\"order_line\"", + " },", + " \"new\":{", + " \"order_id\":\"abc\",", + " \"price\":99.0,", + " \"product\":\"Widget\",", + " \"type\":\"order_line\"", + " }", + " }')" + ] + ] + } + }, + { + "description": "Insert person with contacts and phone numbers (M:M)", + "action": "merge", + "data": { + "type": "person", + "first_name": "Relation", + "last_name": "Test", + "contacts": [ + { + "type": "contact", + "is_primary": true, + "target": { + "type": "phone_number", + "number": "555-0001" + } + }, + { + "type": "contact", + "is_primary": false, + "target": { + "type": "email_address", + "address": "test@example.com" + } + } + ], + "email_addresses": [ + { + "type": "contact", + "is_primary": false, + "target": { + "type": "email_address", + "address": "test2@example.com" + } + } + ] + }, + "schema_id": "person", + "expect": { + "success": true, + "sql": [ + [ + "INSERT INTO agreego.\"entity\" (", + " \"created_at\",", + " \"created_by\",", + " \"id\",", + " \"modified_at\",", + " \"modified_by\",", + " \"type\"", + ") VALUES (", + " '{{timestamp}}',", + " '00000000-0000-0000-0000-000000000000',", + " '{{uuid:person_id}}',", + " '{{timestamp}}',", + " '00000000-0000-0000-0000-000000000000',", + " 'person'", + ")" + ], + [ + "INSERT INTO agreego.\"organization\" (", + " \"id\",", + " \"type\"", + ") VALUES (", + " '{{uuid:person_id}}',", + " 'person'", + ")" + ], + [ + "INSERT INTO agreego.\"user\" (", + " \"id\",", + " \"type\"", + ") VALUES (", + " '{{uuid:person_id}}',", + " 'person'", + ")" + ], + [ + "INSERT INTO agreego.\"person\" (", + " \"first_name\",", + " \"id\",", + " \"last_name\",", + " \"type\"", + ") VALUES (", + " 'Relation',", + " '{{uuid:person_id}}',", + " 'Test',", + " 'person'", + ")" + ], + [ + "INSERT INTO agreego.\"entity\" (", + " \"created_at\",", + " \"created_by\",", + " \"id\",", + " \"modified_at\",", + " \"modified_by\",", + " \"type\"", + ") VALUES (", + " '{{timestamp}}',", + " '00000000-0000-0000-0000-000000000000',", + " '{{uuid:phone1_id}}',", + " '{{timestamp}}',", + " '00000000-0000-0000-0000-000000000000',", + " 'phone_number'", + ")" + ], + [ + "INSERT INTO agreego.\"phone_number\" (", + " \"number\"", + ") VALUES (", + " '555-0001'", + ")" + ], + [ + "INSERT INTO agreego.change (", + " \"old\",", + " \"new\",", + " entity_id,", + " id,", + " kind,", + " modified_at,", + " modified_by", + ") VALUES (", + " NULL,", + " '{", + " \"number\":\"555-0001\",", + " \"type\":\"phone_number\"", + " }',", + " '{{uuid:phone1_id}}',", + " '{{uuid}}',", + " 'create',", + " '{{timestamp}}',", + " '00000000-0000-0000-0000-000000000000'", + ")" + ], + [ + "INSERT INTO agreego.\"entity\" (", + " \"created_at\",", + " \"created_by\",", + " \"id\",", + " \"modified_at\",", + " \"modified_by\",", + " \"type\"", + ") VALUES (", + " '{{timestamp}}',", + " '00000000-0000-0000-0000-000000000000',", + " '{{uuid:contact1_id}}',", + " '{{timestamp}}',", + " '00000000-0000-0000-0000-000000000000',", + " 'contact'", + ")" + ], + [ + "INSERT INTO agreego.\"relationship\" (", + " \"source_id\",", + " \"source_type\",", + " \"target_id\",", + " \"target_type\"", + ") VALUES (", + " '{{uuid:person_id}}',", + " 'person',", + " '{{uuid:phone1_id}}',", + " 'phone_number'", + ")" + ], + [ + "INSERT INTO agreego.\"contact\" (", + " \"is_primary\"", + ") VALUES (", + " true", + ")" + ], + [ + "INSERT INTO agreego.change (", + " \"old\",", + " \"new\",", + " entity_id,", + " id,", + " kind,", + " modified_at,", + " modified_by", + ") VALUES (", + " NULL,", + " '{", + " \"is_primary\":true,", + " \"source_id\":\"{{uuid:person_id}}\",", + " \"source_type\":\"person\",", + " \"target_id\":\"{{uuid:phone1_id}}\",", + " \"target_type\":\"phone_number\",", + " \"type\":\"contact\"", + " }',", + " '{{uuid:contact1_id}}',", + " '{{uuid}}',", + " 'create',", + " '{{timestamp}}',", + " '00000000-0000-0000-0000-000000000000'", + ")" + ], + [ + "INSERT INTO agreego.\"entity\" (", + " \"created_at\",", + " \"created_by\",", + " \"id\",", + " \"modified_at\",", + " \"modified_by\",", + " \"type\"", + ") VALUES (", + " '{{timestamp}}',", + " '00000000-0000-0000-0000-000000000000',", + " '{{uuid:email1_id}}',", + " '{{timestamp}}',", + " '00000000-0000-0000-0000-000000000000',", + " 'email_address'", + ")" + ], + [ + "INSERT INTO agreego.\"email_address\" (", + " \"address\"", + ") VALUES (", + " 'test@example.com'", + ")" + ], + [ + "INSERT INTO agreego.change (", + " \"old\",", + " \"new\",", + " entity_id,", + " id,", + " kind,", + " modified_at,", + " modified_by", + ") VALUES (", + " NULL,", + " '{", + " \"address\":\"test@example.com\",", + " \"type\":\"email_address\"", + " }',", + " '{{uuid:email1_id}}',", + " '{{uuid}}',", + " 'create',", + " '{{timestamp}}',", + " '00000000-0000-0000-0000-000000000000'", + ")" + ], + [ + "INSERT INTO agreego.\"entity\" (", + " \"created_at\",", + " \"created_by\",", + " \"id\",", + " \"modified_at\",", + " \"modified_by\",", + " \"type\"", + ") VALUES (", + " '{{timestamp}}',", + " '00000000-0000-0000-0000-000000000000',", + " '{{uuid:contact2_id}}',", + " '{{timestamp}}',", + " '00000000-0000-0000-0000-000000000000',", + " 'contact'", + ")" + ], + [ + "INSERT INTO agreego.\"relationship\" (", + " \"source_id\",", + " \"source_type\",", + " \"target_id\",", + " \"target_type\"", + ") VALUES (", + " '{{uuid:person_id}}',", + " 'person',", + " '{{uuid:email1_id}}',", + " 'email_address'", + ")" + ], + [ + "INSERT INTO agreego.\"contact\" (", + " \"is_primary\"", + ") VALUES (", + " false", + ")" + ], + [ + "INSERT INTO agreego.change (", + " \"old\",", + " \"new\",", + " entity_id,", + " id,", + " kind,", + " modified_at,", + " modified_by", + ") VALUES (", + " NULL,", + " '{", + " \"is_primary\":false,", + " \"source_id\":\"{{uuid:person_id}}\",", + " \"source_type\":\"person\",", + " \"target_id\":\"{{uuid:email1_id}}\",", + " \"target_type\":\"email_address\",", + " \"type\":\"contact\"", + " }',", + " '{{uuid:contact2_id}}',", + " '{{uuid}}',", + " 'create',", + " '{{timestamp}}',", + " '00000000-0000-0000-0000-000000000000'", + ")" + ], + [ + "INSERT INTO agreego.\"entity\" (", + " \"created_at\",", + " \"created_by\",", + " \"id\",", + " \"modified_at\",", + " \"modified_by\",", + " \"type\"", + ") VALUES (", + " '{{timestamp}}',", + " '00000000-0000-0000-0000-000000000000',", + " '{{uuid:email2_id}}',", + " '{{timestamp}}',", + " '00000000-0000-0000-0000-000000000000',", + " 'email_address'", + ")" + ], + [ + "INSERT INTO agreego.\"email_address\" (", + " \"address\"", + ") VALUES (", + " 'test2@example.com'", + ")" + ], + [ + "INSERT INTO agreego.change (", + " \"old\",", + " \"new\",", + " entity_id,", + " id,", + " kind,", + " modified_at,", + " modified_by", + ") VALUES (", + " NULL,", + " '{", + " \"address\":\"test2@example.com\",", + " \"type\":\"email_address\"", + " }',", + " '{{uuid:email2_id}}',", + " '{{uuid}}',", + " 'create',", + " '{{timestamp}}',", + " '00000000-0000-0000-0000-000000000000'", + ")" + ], + [ + "INSERT INTO agreego.\"entity\" (", + " \"created_at\",", + " \"created_by\",", + " \"id\",", + " \"modified_at\",", + " \"modified_by\",", + " \"type\"", + ") VALUES (", + " '{{timestamp}}',", + " '00000000-0000-0000-0000-000000000000',", + " '{{uuid:contact3_id}}',", + " '{{timestamp}}',", + " '00000000-0000-0000-0000-000000000000',", + " 'contact'", + ")" + ], + [ + "INSERT INTO agreego.\"relationship\" (", + " \"source_id\",", + " \"source_type\",", + " \"target_id\",", + " \"target_type\"", + ") VALUES (", + " '{{uuid:person_id}}',", + " 'person',", + " '{{uuid:email2_id}}',", + " 'email_address'", + ")" + ], + [ + "INSERT INTO agreego.\"contact\" (", + " \"is_primary\"", + ") VALUES (", + " false", + ")" + ], + [ + "INSERT INTO agreego.change (", + " \"old\",", + " \"new\",", + " entity_id,", + " id,", + " kind,", + " modified_at,", + " modified_by", + ") VALUES (", + " NULL,", + " '{", + " \"is_primary\":false,", + " \"source_id\":\"{{uuid:person_id}}\",", + " \"source_type\":\"person\",", + " \"target_id\":\"{{uuid:email2_id}}\",", + " \"target_type\":\"email_address\",", + " \"type\":\"contact\"", + " }',", + " '{{uuid:contact3_id}}',", + " '{{uuid}}',", + " 'create',", + " '{{timestamp}}',", + " '00000000-0000-0000-0000-000000000000'", + ")" + ], + [ + "INSERT INTO agreego.change (", + " \"old\",", + " \"new\",", + " entity_id,", + " id,", + " kind,", + " modified_at,", + " modified_by", + ") VALUES (", + " NULL,", + " '{", + " \"first_name\":\"Relation\",", + " \"last_name\":\"Test\",", + " \"type\":\"person\"", + " }',", + " '{{uuid:person_id}}',", + " '{{uuid}}',", + " 'create',", + " '{{timestamp}}',", + " '00000000-0000-0000-0000-000000000000'", + ")" + ], + [ + "SELECT pg_notify('entity', '{", + " \"complete\":{", + " \"created_at\":\"{{timestamp}}\",", + " \"created_by\":\"00000000-0000-0000-0000-000000000000\",", + " \"first_name\":\"Relation\",", + " \"id\":\"{{uuid:person_id}}\",", + " \"last_name\":\"Test\",", + " \"modified_at\":\"{{timestamp}}\",", + " \"modified_by\":\"00000000-0000-0000-0000-000000000000\",", + " \"type\":\"person\"", + " },", + " \"new\":{", + " \"first_name\":\"Relation\",", + " \"last_name\":\"Test\",", + " \"type\":\"person\"", + " }", + " }')" + ], + [ + "SELECT pg_notify('entity', '{", + " \"complete\":{", + " \"created_at\":\"{{timestamp}}\",", + " \"created_by\":\"00000000-0000-0000-0000-000000000000\",", + " \"id\":\"{{uuid:contact1_id}}\",", + " \"is_primary\":true,", + " \"modified_at\":\"{{timestamp}}\",", + " \"modified_by\":\"00000000-0000-0000-0000-000000000000\",", + " \"source_id\":\"{{uuid:person_id}}\",", + " \"source_type\":\"person\",", + " \"target_id\":\"{{uuid:phone1_id}}\",", + " \"target_type\":\"phone_number\",", + " \"type\":\"contact\"", + " },", + " \"new\":{", + " \"is_primary\":true,", + " \"source_id\":\"{{uuid:person_id}}\",", + " \"source_type\":\"person\",", + " \"target_id\":\"{{uuid:phone1_id}}\",", + " \"target_type\":\"phone_number\",", + " \"type\":\"contact\"", + " }", + " }')" + ], + [ + "SELECT pg_notify('entity', '{", + " \"complete\":{", + " \"created_at\":\"{{timestamp}}\",", + " \"created_by\":\"00000000-0000-0000-0000-000000000000\",", + " \"id\":\"{{uuid:phone1_id}}\",", + " \"modified_at\":\"{{timestamp}}\",", + " \"modified_by\":\"00000000-0000-0000-0000-000000000000\",", + " \"number\":\"555-0001\",", + " \"type\":\"phone_number\"", + " },", + " \"new\":{", + " \"number\":\"555-0001\",", + " \"type\":\"phone_number\"", + " }", + " }')" + ], + [ + "SELECT pg_notify('entity', '{", + " \"complete\":{", + " \"created_at\":\"{{timestamp}}\",", + " \"created_by\":\"00000000-0000-0000-0000-000000000000\",", + " \"id\":\"{{uuid:contact2_id}}\",", + " \"is_primary\":false,", + " \"modified_at\":\"{{timestamp}}\",", + " \"modified_by\":\"00000000-0000-0000-0000-000000000000\",", + " \"source_id\":\"{{uuid:person_id}}\",", + " \"source_type\":\"person\",", + " \"target_id\":\"{{uuid:email1_id}}\",", + " \"target_type\":\"email_address\",", + " \"type\":\"contact\"", + " },", + " \"new\":{", + " \"is_primary\":false,", + " \"source_id\":\"{{uuid:person_id}}\",", + " \"source_type\":\"person\",", + " \"target_id\":\"{{uuid:email1_id}}\",", + " \"target_type\":\"email_address\",", + " \"type\":\"contact\"", + " }", + "}')" + ], + [ + "SELECT pg_notify('entity', '{", + " \"complete\":{", + " \"address\":\"test@example.com\",", + " \"created_at\":\"{{timestamp}}\",", + " \"created_by\":\"00000000-0000-0000-0000-000000000000\",", + " \"id\":\"{{uuid:email1_id}}\",", + " \"modified_at\":\"{{timestamp}}\",", + " \"modified_by\":\"00000000-0000-0000-0000-000000000000\",", + " \"type\":\"email_address\"", + " },", + " \"new\":{", + " \"address\":\"test@example.com\",", + " \"type\":\"email_address\"", + " }", + "}')" + ], + [ + "SELECT pg_notify('entity', '{", + " \"complete\":{", + " \"created_at\":\"{{timestamp}}\",", + " \"created_by\":\"00000000-0000-0000-0000-000000000000\",", + " \"id\":\"{{uuid:contact3_id}}\",", + " \"is_primary\":false,", + " \"modified_at\":\"{{timestamp}}\",", + " \"modified_by\":\"00000000-0000-0000-0000-000000000000\",", + " \"source_id\":\"{{uuid:person_id}}\",", + " \"source_type\":\"person\",", + " \"target_id\":\"{{uuid:email2_id}}\",", + " \"target_type\":\"email_address\",", + " \"type\":\"contact\"", + " },", + " \"new\":{", + " \"is_primary\":false,", + " \"source_id\":\"{{uuid:person_id}}\",", + " \"source_type\":\"person\",", + " \"target_id\":\"{{uuid:email2_id}}\",", + " \"target_type\":\"email_address\",", + " \"type\":\"contact\"", + " }", + "}')" + ], + [ + "SELECT pg_notify('entity', '{", + " \"complete\":{", + " \"address\":\"test2@example.com\",", + " \"created_at\":\"{{timestamp}}\",", + " \"created_by\":\"00000000-0000-0000-0000-000000000000\",", + " \"id\":\"{{uuid:email2_id}}\",", + " \"modified_at\":\"{{timestamp}}\",", + " \"modified_by\":\"00000000-0000-0000-0000-000000000000\",", + " \"type\":\"email_address\"", + " },", + " \"new\":{", + " \"address\":\"test2@example.com\",", + " \"type\":\"email_address\"", + " }", + "}')" + ] + ] + } + }, + { + "description": "Archive a person", + "action": "merge", + "data": { + "id": "abc-archived", + "type": "person", + "archived": true + }, + "mocks": [ + { + "id": "abc-archived", + "type": "person", + "first_name": "ArchivedFirst", + "last_name": "ArchivedLast", + "archived": false + } + ], + "schema_id": "person", + "expect": { + "success": true, + "sql": [ + [ + "SELECT to_jsonb(t1.*) || to_jsonb(t2.*) || to_jsonb(t3.*) || to_jsonb(t4.*)", + "FROM agreego.\"person\" t1", + "LEFT JOIN agreego.\"user\" t2 ON t2.id = t1.id", + "LEFT JOIN agreego.\"organization\" t3 ON t3.id = t1.id", + "LEFT JOIN agreego.\"entity\" t4 ON t4.id = t1.id", + "WHERE t1.id = 'abc-archived'" + ], + [ + "UPDATE agreego.\"entity\" SET", + " \"archived\" = true,", + " \"modified_at\" = '{{timestamp}}',", + " \"modified_by\" = '00000000-0000-0000-0000-000000000000'", + "WHERE id = 'abc-archived'" + ], + [ + "INSERT INTO agreego.change (", + " \"old\",", + " \"new\",", + " entity_id,", + " id,", + " kind,", + " modified_at,", + " modified_by", + ") VALUES (", + " '{", + " \"archived\":false", + " }',", + " '{\"archived\":true,\"type\":\"person\"}',", + " 'abc-archived',", + " '{{uuid}}',", + " 'delete',", + " '{{timestamp}}',", + " '00000000-0000-0000-0000-000000000000'", + ")" + ], + [ + "SELECT pg_notify('entity', '{", + " \"complete\":{", + " \"archived\":true,", + " \"first_name\":\"ArchivedFirst\",", + " \"id\":\"abc-archived\",", + " \"last_name\":\"ArchivedLast\",", + " \"modified_at\":\"{{timestamp}}\",", + " \"modified_by\":\"00000000-0000-0000-0000-000000000000\",", + " \"type\":\"person\"", + " },", + " \"new\":{", + " \"archived\":true,", + " \"type\":\"person\"", + " },", + " \"old\":{", + " \"archived\":false", + " }", + " }')" + ] + ] + } + }, + { + "description": "Attachment with text[] and jsonb metadata structures", + "action": "merge", + "data": { + "type": "attachment", + "flags": [ + "urgent", + "reviewed" + ], + "other_metadata": { + "other": "hello" + }, + "type_metadata": { + "type": "type_metadata" + } + }, + "schema_id": "attachment", + "expect": { + "success": true, + "sql": [ + [ + "INSERT INTO agreego.\"entity\" (", + " \"created_at\",", + " \"created_by\",", + " \"id\",", + " \"modified_at\",", + " \"modified_by\",", + " \"type\"", + ")", + "VALUES (", + " '{{timestamp}}',", + " '00000000-0000-0000-0000-000000000000',", + " '{{uuid:attachment_id}}',", + " '{{timestamp}}',", + " '00000000-0000-0000-0000-000000000000',", + " 'attachment'", + ")" + ], + [ + "INSERT INTO agreego.\"attachment\" (", + " \"flags\",", + " \"id\",", + " \"other_metadata\",", + " \"type\",", + " \"type_metadata\"", + ")", + "VALUES (", + " '{\"urgent\",\"reviewed\"}',", + " '{{uuid:attachment_id}}',", + " '{\"other\":\"hello\"}',", + " 'attachment',", + " '{\"type\":\"type_metadata\"}'", + ")" + ], + [ + "INSERT INTO agreego.change (", + " \"old\",", + " \"new\",", + " entity_id,", + " id,", + " kind,", + " modified_at,", + " modified_by", + ")", + "VALUES (", + " NULL,", + " '{", + " \"flags\":[\"urgent\",\"reviewed\"],", + " \"other_metadata\":{\"other\":\"hello\"},", + " \"type\":\"attachment\",", + " \"type_metadata\":{\"type\":\"type_metadata\"}", + " }',", + " '{{uuid:attachment_id}}',", + " '{{uuid}}',", + " 'create',", + " '{{timestamp}}',", + " '00000000-0000-0000-0000-000000000000'", + ")" + ], + [ + "SELECT pg_notify('entity', '{", + " \"complete\":{", + " \"created_at\":\"{{timestamp}}\",", + " \"created_by\":\"00000000-0000-0000-0000-000000000000\",", + " \"flags\":[\"urgent\",\"reviewed\"],", + " \"id\":\"{{uuid:attachment_id}}\",", + " \"modified_at\":\"{{timestamp}}\",", + " \"modified_by\":\"00000000-0000-0000-0000-000000000000\",", + " \"other_metadata\":{\"other\":\"hello\"},", + " \"type\":\"attachment\",", + " \"type_metadata\":{\"type\":\"type_metadata\"}", + " },", + " \"new\":{", + " \"flags\":[\"urgent\",\"reviewed\"],", + " \"other_metadata\":{\"other\":\"hello\"},", + " \"type\":\"attachment\",", + " \"type_metadata\":{\"type\":\"type_metadata\"}", + " }", + " }')" + ] + ] + } + }, + { + "description": "Anchor order and insert new line (no line id)", + "action": "merge", + "data": { + "id": "abc", + "type": "order", + "lines": [ + { + "type": "order_line", + "product": "Widget", + "price": 99.0 + } + ] + }, + "schema_id": "order", + "expect": { + "success": true, + "sql": [ + [ + "INSERT INTO agreego.\"entity\" (", + " \"created_at\",", + " \"created_by\",", + " \"id\",", + " \"modified_at\",", + " \"modified_by\",", + " \"type\"", + ")", + "VALUES (", + " '{{timestamp}}',", + " '00000000-0000-0000-0000-000000000000',", + " '{{uuid:line_id}}',", + " '{{timestamp}}',", + " '00000000-0000-0000-0000-000000000000',", + " 'order_line'", + ")" + ], + [ + "INSERT INTO agreego.\"order_line\" (", + " \"id\",", + " \"order_id\",", + " \"price\",", + " \"product\",", + " \"type\"", + ")", + "VALUES (", + " '{{uuid:line_id}}',", + " 'abc',", + " 99,", + " 'Widget',", + " 'order_line'", + ")" + ], + [ + "INSERT INTO agreego.change (", + " \"old\",", + " \"new\",", + " entity_id,", + " id,", + " kind,", + " modified_at,", + " modified_by", + ")", + "VALUES (", + " NULL,", + " '{", + " \"order_id\":\"abc\",", + " \"price\":99.0,", + " \"product\":\"Widget\",", + " \"type\":\"order_line\"", + " }',", + " '{{uuid:line_id}}',", + " '{{uuid}}',", + " 'create',", + " '{{timestamp}}',", + " '00000000-0000-0000-0000-000000000000'", + ")" + ], + [ + "SELECT pg_notify('entity', '{", + " \"complete\":{", + " \"created_at\":\"{{timestamp}}\",", + " \"created_by\":\"00000000-0000-0000-0000-000000000000\",", + " \"id\":\"{{uuid:line_id}}\",", + " \"modified_at\":\"{{timestamp}}\",", + " \"modified_by\":\"00000000-0000-0000-0000-000000000000\",", + " \"order_id\":\"abc\",", + " \"price\":99.0,", + " \"product\":\"Widget\",", + " \"type\":\"order_line\"", + " },", + " \"new\":{", + " \"order_id\":\"abc\",", + " \"price\":99.0,", + " \"product\":\"Widget\",", + " \"type\":\"order_line\"", + " }", + " }')" + ] + ] + } + }, + { + "description": "Anchor order and insert new line (with line id)", + "action": "merge", + "data": { + "id": "abc", + "type": "order", + "lines": [ + { + "id": "11111111-2222-3333-4444-555555555555", + "type": "order_line", + "product": "Widget", + "price": 99.0 + } + ] + }, + "schema_id": "order", + "expect": { + "success": true, + "sql": [ + [ + "SELECT to_jsonb(t1.*) || to_jsonb(t2.*)", + "FROM agreego.\"order_line\" t1", + "LEFT JOIN agreego.\"entity\" t2 ON t2.id = t1.id", + "WHERE t1.id = '11111111-2222-3333-4444-555555555555'" + ], + [ + "INSERT INTO agreego.\"entity\" (", + " \"created_at\",", + " \"created_by\",", + " \"id\",", + " \"modified_at\",", + " \"modified_by\",", + " \"type\"", + ")", + "VALUES (", + " '{{timestamp}}',", + " '00000000-0000-0000-0000-000000000000',", + " '11111111-2222-3333-4444-555555555555',", + " '{{timestamp}}',", + " '00000000-0000-0000-0000-000000000000',", + " 'order_line'", + ")" + ], + [ + "INSERT INTO agreego.\"order_line\" (", + " \"id\",", + " \"order_id\",", + " \"price\",", + " \"product\",", + " \"type\"", + ")", + "VALUES (", + " '11111111-2222-3333-4444-555555555555',", + " 'abc',", + " 99,", + " 'Widget',", + " 'order_line'", + ")" + ], + [ + "INSERT INTO agreego.change (", + " \"old\",", + " \"new\",", + " entity_id,", + " id,", + " kind,", + " modified_at,", + " modified_by", + ")", + "VALUES (", + " NULL,", + " '{", + " \"order_id\":\"abc\",", + " \"price\":99.0,", + " \"product\":\"Widget\",", + " \"type\":\"order_line\"", + " }',", + " '11111111-2222-3333-4444-555555555555',", + " '{{uuid}}',", + " 'create',", + " '{{timestamp}}',", + " '00000000-0000-0000-0000-000000000000'", + ")" + ], + [ + "SELECT pg_notify('entity', '{", + " \"complete\":{", + " \"created_at\":\"{{timestamp}}\",", + " \"created_by\":\"00000000-0000-0000-0000-000000000000\",", + " \"id\":\"11111111-2222-3333-4444-555555555555\",", + " \"modified_at\":\"{{timestamp}}\",", + " \"modified_by\":\"00000000-0000-0000-0000-000000000000\",", + " \"order_id\":\"abc\",", + " \"price\":99.0,", + " \"product\":\"Widget\",", + " \"type\":\"order_line\"", + " },", + " \"new\":{", + " \"order_id\":\"abc\",", + " \"price\":99.0,", + " \"product\":\"Widget\",", + " \"type\":\"order_line\"", + " }", + " }')" + ] + ] + } + } + ] + } ] \ No newline at end of file diff --git a/fixtures/minContains.json b/fixtures/minContains.json index 5f39485..b621a9d 100644 --- a/fixtures/minContains.json +++ b/fixtures/minContains.json @@ -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": [ { diff --git a/fixtures/minItems.json b/fixtures/minItems.json index e5f03f5..3566412 100644 --- a/fixtures/minItems.json +++ b/fixtures/minItems.json @@ -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": [ { diff --git a/fixtures/minLength.json b/fixtures/minLength.json index 3c6e10a..b3e73c5 100644 --- a/fixtures/minLength.json +++ b/fixtures/minLength.json @@ -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": [ { diff --git a/fixtures/minProperties.json b/fixtures/minProperties.json index 6e2da84..93f29fd 100644 --- a/fixtures/minProperties.json +++ b/fixtures/minProperties.json @@ -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": [ { diff --git a/fixtures/minimum.json b/fixtures/minimum.json index f3b39dd..8762643 100644 --- a/fixtures/minimum.json +++ b/fixtures/minimum.json @@ -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": [ { diff --git a/fixtures/multipleOf.json b/fixtures/multipleOf.json index 3a2163f..93a166c 100644 --- a/fixtures/multipleOf.json +++ b/fixtures/multipleOf.json @@ -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": [ { diff --git a/fixtures/not.json b/fixtures/not.json index 4ad9bf6..e9c422d 100644 --- a/fixtures/not.json +++ b/fixtures/not.json @@ -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": [ { diff --git a/fixtures/objectTypes.json b/fixtures/objectTypes.json index 704462c..510cb89 100644 --- a/fixtures/objectTypes.json +++ b/fixtures/objectTypes.json @@ -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 + } } ] } -] +] \ No newline at end of file diff --git a/fixtures/paths.json b/fixtures/paths.json index 4ff7707..d68d69e 100644 --- a/fixtures/paths.json +++ b/fixtures/paths.json @@ -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" + } } ] } diff --git a/fixtures/pattern.json b/fixtures/pattern.json index 264d7ab..171a983 100644 --- a/fixtures/pattern.json +++ b/fixtures/pattern.json @@ -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": [ { diff --git a/fixtures/patternProperties.json b/fixtures/patternProperties.json index ada1c83..63f0f00 100644 --- a/fixtures/patternProperties.json +++ b/fixtures/patternProperties.json @@ -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": [ { diff --git a/fixtures/polymorphism.json b/fixtures/polymorphism.json index a92db16..9017f04 100644 --- a/fixtures/polymorphism.json +++ b/fixtures/polymorphism.json @@ -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,95 +155,125 @@ "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": "" + } + } + ] + } } ] }, @@ -199,72 +283,112 @@ "types": [ { "name": "widget", - "variations": ["widget"], - "schemas": [ - { - "$id": "widget", + "variations": [ + "widget" + ], + "schemas": { + "widget": { "type": "object", "properties": { - "type": { "type": "string" } + "type": { + "type": "string" + } } }, - { - "$id": "stock.widget", + "stock.widget": { "type": "widget", "properties": { - "kind": { "type": "string" }, - "amount": { "type": "integer" } + "kind": { + "type": "string" + }, + "amount": { + "type": "integer" + } } }, - { - "$id": "super_stock.widget", + "super_stock.widget": { "type": "stock.widget", "properties": { - "super_amount": { "type": "integer" } + "super_amount": { + "type": "integer" + } } } - ] + } } ], - "schemas": [ - { - "$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" + } + } + ] + } } ] }, @@ -274,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 } } ] diff --git a/fixtures/prefixItems.json b/fixtures/prefixItems.json index a3ce3c2..b38a44f 100644 --- a/fixtures/prefixItems.json +++ b/fixtures/prefixItems.json @@ -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": [ { diff --git a/fixtures/primitiveTypes.json b/fixtures/primitiveTypes.json index 4a5eab3..714efa2 100644 --- a/fixtures/primitiveTypes.json +++ b/fixtures/primitiveTypes.json @@ -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": [ { diff --git a/fixtures/properties.json b/fixtures/properties.json index 6f8923e..3c6b3a3 100644 --- a/fixtures/properties.json +++ b/fixtures/properties.json @@ -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": [ { diff --git a/fixtures/propertyNames.json b/fixtures/propertyNames.json index edf6a60..85a8c37 100644 --- a/fixtures/propertyNames.json +++ b/fixtures/propertyNames.json @@ -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": [ { diff --git a/fixtures/queryer.json b/fixtures/queryer.json index 71c2a8f..0019ceb 100644 --- a/fixtures/queryer.json +++ b/fixtures/queryer.json @@ -5,66 +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_light_organization", - "schemas": [ - { - "$id": "get_light_organization.response", + "schemas": { + "get_light_organization.response": { "$family": "light.organization" } - ] + } }, { "name": "get_full_organization", - "schemas": [ - { - "$id": "get_full_organization.response", + "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": [ - { - "$id": "get_widgets.response", + "schemas": { + "get_widgets.response": { "type": "array", "items": { "$family": "widget" } } - ] + } } ], "enums": [], @@ -165,9 +159,8 @@ "created_at": "timestamptz", "type": "text" }, - "schemas": [ - { - "$id": "entity", + "schemas": { + "entity": { "type": "object", "properties": { "id": { @@ -189,7 +182,7 @@ } } } - ], + }, "fields": [ "id", "type", @@ -256,9 +249,8 @@ "organization", "person" ], - "schemas": [ - { - "$id": "organization", + "schemas": { + "organization": { "type": "entity", "properties": { "name": { @@ -266,7 +258,7 @@ } } } - ] + } }, { "name": "bot", @@ -309,9 +301,8 @@ "role": "text", "created_at": "timestamptz" }, - "schemas": [ - { - "$id": "bot", + "schemas": { + "bot": { "type": "organization", "properties": { "token": { @@ -322,8 +313,7 @@ } } }, - { - "$id": "light.bot", + "light.bot": { "type": "organization", "properties": { "token": { @@ -331,7 +321,7 @@ } } } - ], + }, "variations": [ "bot" ] @@ -379,9 +369,8 @@ "age": "numeric", "created_at": "timestamptz" }, - "schemas": [ - { - "$id": "person", + "schemas": { + "person": { "type": "organization", "properties": { "first_name": { @@ -395,8 +384,7 @@ } } }, - { - "$id": "light.person", + "light.person": { "type": "organization", "properties": { "first_name": { @@ -407,8 +395,7 @@ } } }, - { - "$id": "full.person", + "full.person": { "type": "person", "properties": { "phone_numbers": { @@ -467,7 +454,7 @@ } } } - ], + }, "variations": [ "person" ] @@ -513,13 +500,12 @@ "target_type": "text", "created_at": "timestamptz" }, - "schemas": [ - { - "$id": "relationship", + "schemas": { + "relationship": { "type": "entity", "properties": {} } - ], + }, "variations": [ "contact", "relationship" @@ -572,9 +558,8 @@ "is_primary": "boolean", "created_at": "timestamptz" }, - "schemas": [ - { - "$id": "contact", + "schemas": { + "contact": { "type": "relationship", "properties": { "is_primary": { @@ -582,7 +567,7 @@ } } } - ], + }, "variations": [ "contact" ] @@ -618,9 +603,8 @@ "number": "text", "created_at": "timestamptz" }, - "schemas": [ - { - "$id": "phone_number", + "schemas": { + "phone_number": { "type": "entity", "properties": { "number": { @@ -628,7 +612,7 @@ } } } - ], + }, "variations": [ "phone_number" ] @@ -664,9 +648,8 @@ "address": "text", "created_at": "timestamptz" }, - "schemas": [ - { - "$id": "email_address", + "schemas": { + "email_address": { "type": "entity", "properties": { "address": { @@ -674,7 +657,7 @@ } } } - ], + }, "variations": [ "email_address" ] @@ -710,9 +693,8 @@ "city": "text", "created_at": "timestamptz" }, - "schemas": [ - { - "$id": "address", + "schemas": { + "address": { "type": "entity", "properties": { "city": { @@ -720,16 +702,15 @@ } } } - ], + }, "variations": [ "address" ] }, { "name": "order", - "schemas": [ - { - "$id": "order", + "schemas": { + "order": { "type": "entity", "properties": { "total": { @@ -740,8 +721,7 @@ } } }, - { - "$id": "light.order", + "light.order": { "type": "order", "properties": { "customer": { @@ -749,8 +729,7 @@ } } }, - { - "$id": "full.order", + "full.order": { "type": "order", "properties": { "customer": { @@ -764,7 +743,7 @@ } } } - ], + }, "hierarchy": [ "order", "entity" @@ -825,9 +804,8 @@ }, { "name": "order_line", - "schemas": [ - { - "$id": "order_line", + "schemas": { + "order_line": { "type": "entity", "properties": { "order_id": { @@ -841,7 +819,7 @@ } } } - ], + }, "hierarchy": [ "order_line", "entity" @@ -929,9 +907,8 @@ "variations": [ "widget" ], - "schemas": [ - { - "$id": "widget", + "schemas": { + "widget": { "type": "entity", "properties": { "kind": { @@ -939,25 +916,15 @@ } } }, - { - "$id": "stock.widget", + "stock.widget": { "type": "widget", - "properties": { - "kind": { - "const": "stock" - } - } + "properties": {} }, - { - "$id": "tasks.widget", + "tasks.widget": { "type": "widget", - "properties": { - "kind": { - "const": "tasks" - } - } + "properties": {} } - ] + } } ] }, @@ -1506,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,", @@ -1525,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", diff --git a/fixtures/required.json b/fixtures/required.json index f8f918a..97fd836 100644 --- a/fixtures/required.json +++ b/fixtures/required.json @@ -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": [ { diff --git a/fixtures/uniqueItems.json b/fixtures/uniqueItems.json index 8b5af36..17adf27 100644 --- a/fixtures/uniqueItems.json +++ b/fixtures/uniqueItems.json @@ -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": [ { diff --git a/out.txt b/out.txt deleted file mode 100644 index eeda86e..0000000 --- a/out.txt +++ /dev/null @@ -1,45 +0,0 @@ -warning: unused variable: `is_family` - --> src/database/schema.rs:495:9 - | -495 | let is_family = self.obj.family.is_some(); - | ^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_is_family` - | - = note: `#[warn(unused_variables)]` (part of `#[warn(unused)]`) on by default - -warning: `jspg` (lib) generated 1 warning (run `cargo fix --lib -p jspg` to apply 1 suggestion) -warning: `jspg` (lib test) generated 1 warning (1 duplicate) - Finished `test` profile [unoptimized + debuginfo] target(s) in 0.32s - Running unittests src/lib.rs (target/debug/deps/jspg-d3f18ff3a7e2b386) - -running 1 test -test tests::test_queryer_0_8 ... FAILED - -failures: - ----- tests::test_queryer_0_8 stdout ---- -[DEBUG SPLIT] ID: light.person, dim: type, is_split: false, props: Some(["age", "archived", "created", "created_at", "first_name", "id", "last_name", "name", "type"]) -[DEBUG SPLIT] ID: light.person, dim: kind, is_split: false, props: Some(["age", "archived", "created", "created_at", "first_name", "id", "last_name", "name", "type"]) -[DEBUG SPLIT] ID: light.person, dim: type, is_split: false, props: Some(["age", "archived", "created", "created_at", "first_name", "id", "last_name", "name", "type"]) -[DEBUG SPLIT] ID: light.person, dim: type, is_split: false, props: Some(["age", "archived", "created", "created_at", "first_name", "id", "last_name", "name", "type"]) -[DEBUG SPLIT] ID: light.person, dim: kind, is_split: false, props: Some(["age", "archived", "created", "created_at", "first_name", "id", "last_name", "name", "type"]) -[DEBUG SPLIT] ID: light.person, dim: type, is_split: false, props: Some(["age", "archived", "created", "created_at", "first_name", "id", "last_name", "name", "type"]) -[DEBUG POLYMORPHISM] ID: get_person.response, Strategy: type, Pathways: {"full.person": "full.person", "light.person": "light.person", "person": "person"} -[DEBUG QUERYER] Evaluating node. Target family: Some("organization"), disc: Some("type"), pathways: Some({"bot": "bot", "full.person": "full.person", "get_organization.response": "get_organization.response", "light.person": "light.person", "organization": "organization", "person": "person"}) -JSPG_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 = 'full.person' THEN ((SELECT jsonb_build_object('addresses', (SELECT COALESCE(jsonb_agg(jsonb_build_object('archived', entity_11.archived, 'created_at', entity_11.created_at, 'id', entity_11.id, 'is_primary', contact_9.is_primary, 'target', (SELECT jsonb_build_object('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_12 JOIN agreego.entity entity_13 ON entity_13.id = address_12.id WHERE NOT entity_13.archived AND relationship_10.target_id = entity_13.id), 'type', entity_11.type)), '[]'::jsonb) FROM agreego.contact contact_9 JOIN agreego.relationship relationship_10 ON relationship_10.id = contact_9.id JOIN agreego.entity entity_11 ON entity_11.id = relationship_10.id WHERE NOT entity_11.archived AND relationship_10.target_type = 'address' AND relationship_10.source_id = entity_8.id), 'age', person_6.age, 'archived', entity_8.archived, 'contacts', (SELECT COALESCE(jsonb_agg(jsonb_build_object('archived', entity_16.archived, 'created_at', entity_16.created_at, 'id', entity_16.id, 'is_primary', contact_14.is_primary, 'target', CASE WHEN entity_16.target_type = 'address' THEN ((SELECT jsonb_build_object('archived', entity_18.archived, 'city', address_17.city, 'created_at', entity_18.created_at, 'id', entity_18.id, 'type', entity_18.type) FROM agreego.address address_17 JOIN agreego.entity entity_18 ON entity_18.id = address_17.id WHERE NOT entity_18.archived AND relationship_15.target_id = entity_18.id)) WHEN entity_16.target_type = 'email_address' THEN ((SELECT jsonb_build_object('address', email_address_19.address, 'archived', entity_20.archived, 'created_at', entity_20.created_at, 'id', entity_20.id, 'type', entity_20.type) FROM agreego.email_address email_address_19 JOIN agreego.entity entity_20 ON entity_20.id = email_address_19.id WHERE NOT entity_20.archived AND relationship_15.target_id = entity_20.id)) WHEN entity_16.target_type = 'phone_number' THEN ((SELECT jsonb_build_object('archived', entity_22.archived, 'created_at', entity_22.created_at, 'id', entity_22.id, 'number', phone_number_21.number, 'type', entity_22.type) FROM agreego.phone_number phone_number_21 JOIN agreego.entity entity_22 ON entity_22.id = phone_number_21.id WHERE NOT entity_22.archived AND relationship_15.target_id = entity_22.id)) ELSE NULL END, 'type', entity_16.type)), '[]'::jsonb) FROM agreego.contact contact_14 JOIN agreego.relationship relationship_15 ON relationship_15.id = contact_14.id JOIN agreego.entity entity_16 ON entity_16.id = relationship_15.id WHERE NOT entity_16.archived AND relationship_15.source_id = entity_8.id), 'created_at', entity_8.created_at, 'email_addresses', (SELECT COALESCE(jsonb_agg(jsonb_build_object('archived', entity_25.archived, 'created_at', entity_25.created_at, 'id', entity_25.id, 'is_primary', contact_23.is_primary, 'target', (SELECT jsonb_build_object('address', email_address_26.address, 'archived', entity_27.archived, 'created_at', entity_27.created_at, 'id', entity_27.id, 'type', entity_27.type) FROM agreego.email_address email_address_26 JOIN agreego.entity entity_27 ON entity_27.id = email_address_26.id WHERE NOT entity_27.archived AND relationship_24.target_id = entity_27.id), 'type', entity_25.type)), '[]'::jsonb) FROM agreego.contact contact_23 JOIN agreego.relationship relationship_24 ON relationship_24.id = contact_23.id JOIN agreego.entity entity_25 ON entity_25.id = relationship_24.id WHERE NOT entity_25.archived AND relationship_24.target_type = 'email_address' AND relationship_24.source_id = entity_8.id), 'first_name', person_6.first_name, 'id', entity_8.id, 'last_name', person_6.last_name, 'name', organization_7.name, 'phone_numbers', (SELECT COALESCE(jsonb_agg(jsonb_build_object('archived', entity_30.archived, 'created_at', entity_30.created_at, 'id', entity_30.id, 'is_primary', contact_28.is_primary, 'target', (SELECT jsonb_build_object('archived', entity_32.archived, 'created_at', entity_32.created_at, 'id', entity_32.id, 'number', phone_number_31.number, 'type', entity_32.type) FROM agreego.phone_number phone_number_31 JOIN agreego.entity entity_32 ON entity_32.id = phone_number_31.id WHERE NOT entity_32.archived AND relationship_29.target_id = entity_32.id), 'type', entity_30.type)), '[]'::jsonb) FROM agreego.contact contact_28 JOIN agreego.relationship relationship_29 ON relationship_29.id = contact_28.id JOIN agreego.entity entity_30 ON entity_30.id = relationship_29.id WHERE NOT entity_30.archived AND relationship_29.target_type = 'phone_number' AND relationship_29.source_id = entity_8.id), '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)) WHEN organization_1.type = 'get_organization.response' THEN ((SELECT jsonb_build_object('archived', entity_34.archived, 'created_at', entity_34.created_at, 'id', entity_34.id, 'name', organization_33.name, 'type', entity_34.type) FROM agreego.organization organization_33 JOIN agreego.entity entity_34 ON entity_34.id = organization_33.id WHERE NOT entity_34.archived)) WHEN organization_1.type = 'light.person' THEN ((SELECT jsonb_build_object('age', person_35.age, 'archived', entity_37.archived, 'created_at', entity_37.created_at, 'first_name', person_35.first_name, 'id', entity_37.id, 'last_name', person_35.last_name, 'name', organization_36.name, 'type', entity_37.type) FROM agreego.person person_35 JOIN agreego.organization organization_36 ON organization_36.id = person_35.id JOIN agreego.entity entity_37 ON entity_37.id = organization_36.id WHERE NOT entity_37.archived)) WHEN organization_1.type = 'organization' THEN ((SELECT jsonb_build_object('archived', entity_39.archived, 'created_at', entity_39.created_at, 'id', entity_39.id, 'name', organization_38.name, 'type', entity_39.type) FROM agreego.organization organization_38 JOIN agreego.entity entity_39 ON entity_39.id = organization_38.id WHERE NOT entity_39.archived)) WHEN organization_1.type = 'person' THEN ((SELECT jsonb_build_object('age', person_40.age, 'archived', entity_42.archived, 'created_at', entity_42.created_at, 'first_name', person_40.first_name, 'id', entity_42.id, 'last_name', person_40.last_name, 'name', organization_41.name, 'type', entity_42.type) FROM agreego.person person_40 JOIN agreego.organization organization_41 ON organization_41.id = person_40.id JOIN agreego.entity entity_42 ON entity_42.id = organization_41.id WHERE NOT entity_42.archived)) 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))) -TEST QUERY ERROR FOR 'Organizations select via a punc response with family': Line mismatched at execution sequence 1. -Expected Pattern: (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)FROM agreego.organization organization_1 JOIN agreego.entity entity_2 ON entity_2.id=organization_1.id WHERE NOT entity_2.archived))) -Actual 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='full.person'THEN((SELECT jsonb_build_object('addresses',(SELECT COALESCE(jsonb_agg(jsonb_build_object('archived',entity_11.archived,'created_at',entity_11.created_at,'id',entity_11.id,'is_primary',contact_9.is_primary,'target',(SELECT jsonb_build_object('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_12 JOIN agreego.entity entity_13 ON entity_13.id=address_12.id WHERE NOT entity_13.archived AND relationship_10.target_id=entity_13.id),'type',entity_11.type)),'[]'::jsonb)FROM agreego.contact contact_9 JOIN agreego.relationship relationship_10 ON relationship_10.id=contact_9.id JOIN agreego.entity entity_11 ON entity_11.id=relationship_10.id WHERE NOT entity_11.archived AND relationship_10.target_type='address'AND relationship_10.source_id=entity_8.id),'age',person_6.age,'archived',entity_8.archived,'contacts',(SELECT COALESCE(jsonb_agg(jsonb_build_object('archived',entity_16.archived,'created_at',entity_16.created_at,'id',entity_16.id,'is_primary',contact_14.is_primary,'target',CASE WHEN entity_16.target_type='address'THEN((SELECT jsonb_build_object('archived',entity_18.archived,'city',address_17.city,'created_at',entity_18.created_at,'id',entity_18.id,'type',entity_18.type)FROM agreego.address address_17 JOIN agreego.entity entity_18 ON entity_18.id=address_17.id WHERE NOT entity_18.archived AND relationship_15.target_id=entity_18.id))WHEN entity_16.target_type='email_address'THEN((SELECT jsonb_build_object('address',email_address_19.address,'archived',entity_20.archived,'created_at',entity_20.created_at,'id',entity_20.id,'type',entity_20.type)FROM agreego.email_address email_address_19 JOIN agreego.entity entity_20 ON entity_20.id=email_address_19.id WHERE NOT entity_20.archived AND relationship_15.target_id=entity_20.id))WHEN entity_16.target_type='phone_number'THEN((SELECT jsonb_build_object('archived',entity_22.archived,'created_at',entity_22.created_at,'id',entity_22.id,'number',phone_number_21.number,'type',entity_22.type)FROM agreego.phone_number phone_number_21 JOIN agreego.entity entity_22 ON entity_22.id=phone_number_21.id WHERE NOT entity_22.archived AND relationship_15.target_id=entity_22.id))ELSE NULL END,'type',entity_16.type)),'[]'::jsonb)FROM agreego.contact contact_14 JOIN agreego.relationship relationship_15 ON relationship_15.id=contact_14.id JOIN agreego.entity entity_16 ON entity_16.id=relationship_15.id WHERE NOT entity_16.archived AND relationship_15.source_id=entity_8.id),'created_at',entity_8.created_at,'email_addresses',(SELECT COALESCE(jsonb_agg(jsonb_build_object('archived',entity_25.archived,'created_at',entity_25.created_at,'id',entity_25.id,'is_primary',contact_23.is_primary,'target',(SELECT jsonb_build_object('address',email_address_26.address,'archived',entity_27.archived,'created_at',entity_27.created_at,'id',entity_27.id,'type',entity_27.type)FROM agreego.email_address email_address_26 JOIN agreego.entity entity_27 ON entity_27.id=email_address_26.id WHERE NOT entity_27.archived AND relationship_24.target_id=entity_27.id),'type',entity_25.type)),'[]'::jsonb)FROM agreego.contact contact_23 JOIN agreego.relationship relationship_24 ON relationship_24.id=contact_23.id JOIN agreego.entity entity_25 ON entity_25.id=relationship_24.id WHERE NOT entity_25.archived AND relationship_24.target_type='email_address'AND relationship_24.source_id=entity_8.id),'first_name',person_6.first_name,'id',entity_8.id,'last_name',person_6.last_name,'name',organization_7.name,'phone_numbers',(SELECT COALESCE(jsonb_agg(jsonb_build_object('archived',entity_30.archived,'created_at',entity_30.created_at,'id',entity_30.id,'is_primary',contact_28.is_primary,'target',(SELECT jsonb_build_object('archived',entity_32.archived,'created_at',entity_32.created_at,'id',entity_32.id,'number',phone_number_31.number,'type',entity_32.type)FROM agreego.phone_number phone_number_31 JOIN agreego.entity entity_32 ON entity_32.id=phone_number_31.id WHERE NOT entity_32.archived AND relationship_29.target_id=entity_32.id),'type',entity_30.type)),'[]'::jsonb)FROM agreego.contact contact_28 JOIN agreego.relationship relationship_29 ON relationship_29.id=contact_28.id JOIN agreego.entity entity_30 ON entity_30.id=relationship_29.id WHERE NOT entity_30.archived AND relationship_29.target_type='phone_number'AND relationship_29.source_id=entity_8.id),'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))WHEN organization_1.type='get_organization.response'THEN((SELECT jsonb_build_object('archived',entity_34.archived,'created_at',entity_34.created_at,'id',entity_34.id,'name',organization_33.name,'type',entity_34.type)FROM agreego.organization organization_33 JOIN agreego.entity entity_34 ON entity_34.id=organization_33.id WHERE NOT entity_34.archived))WHEN organization_1.type='light.person'THEN((SELECT jsonb_build_object('age',person_35.age,'archived',entity_37.archived,'created_at',entity_37.created_at,'first_name',person_35.first_name,'id',entity_37.id,'last_name',person_35.last_name,'name',organization_36.name,'type',entity_37.type)FROM agreego.person person_35 JOIN agreego.organization organization_36 ON organization_36.id=person_35.id JOIN agreego.entity entity_37 ON entity_37.id=organization_36.id WHERE NOT entity_37.archived))WHEN organization_1.type='organization'THEN((SELECT jsonb_build_object('archived',entity_39.archived,'created_at',entity_39.created_at,'id',entity_39.id,'name',organization_38.name,'type',entity_39.type)FROM agreego.organization organization_38 JOIN agreego.entity entity_39 ON entity_39.id=organization_38.id WHERE NOT entity_39.archived))WHEN organization_1.type='person'THEN((SELECT jsonb_build_object('age',person_40.age,'archived',entity_42.archived,'created_at',entity_42.created_at,'first_name',person_40.first_name,'id',entity_42.id,'last_name',person_40.last_name,'name',organization_41.name,'type',entity_42.type)FROM agreego.person person_40 JOIN agreego.organization organization_41 ON organization_41.id=person_40.id JOIN agreego.entity entity_42 ON entity_42.id=organization_41.id WHERE NOT entity_42.archived))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))) -Regex used: \(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\)FROM agreego\.organization organization_1 JOIN agreego\.entity entity_2 ON entity_2\.id=organization_1\.id WHERE NOT entity_2\.archived\)\)\) -Variables Mapped: {} - -thread 'tests::test_queryer_0_8' (97340744) panicked at src/tests/fixtures.rs:1427:54: -called `Result::unwrap()` on an `Err` value: "[Queryer Execution] Query Test 'Organizations select via a punc response with family' failed. Error: Line mismatched at execution sequence 1.\nExpected Pattern: (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)FROM agreego.organization organization_1 JOIN agreego.entity entity_2 ON entity_2.id=organization_1.id WHERE NOT entity_2.archived)))\nActual 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='full.person'THEN((SELECT jsonb_build_object('addresses',(SELECT COALESCE(jsonb_agg(jsonb_build_object('archived',entity_11.archived,'created_at',entity_11.created_at,'id',entity_11.id,'is_primary',contact_9.is_primary,'target',(SELECT jsonb_build_object('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_12 JOIN agreego.entity entity_13 ON entity_13.id=address_12.id WHERE NOT entity_13.archived AND relationship_10.target_id=entity_13.id),'type',entity_11.type)),'[]'::jsonb)FROM agreego.contact contact_9 JOIN agreego.relationship relationship_10 ON relationship_10.id=contact_9.id JOIN agreego.entity entity_11 ON entity_11.id=relationship_10.id WHERE NOT entity_11.archived AND relationship_10.target_type='address'AND relationship_10.source_id=entity_8.id),'age',person_6.age,'archived',entity_8.archived,'contacts',(SELECT COALESCE(jsonb_agg(jsonb_build_object('archived',entity_16.archived,'created_at',entity_16.created_at,'id',entity_16.id,'is_primary',contact_14.is_primary,'target',CASE WHEN entity_16.target_type='address'THEN((SELECT jsonb_build_object('archived',entity_18.archived,'city',address_17.city,'created_at',entity_18.created_at,'id',entity_18.id,'type',entity_18.type)FROM agreego.address address_17 JOIN agreego.entity entity_18 ON entity_18.id=address_17.id WHERE NOT entity_18.archived AND relationship_15.target_id=entity_18.id))WHEN entity_16.target_type='email_address'THEN((SELECT jsonb_build_object('address',email_address_19.address,'archived',entity_20.archived,'created_at',entity_20.created_at,'id',entity_20.id,'type',entity_20.type)FROM agreego.email_address email_address_19 JOIN agreego.entity entity_20 ON entity_20.id=email_address_19.id WHERE NOT entity_20.archived AND relationship_15.target_id=entity_20.id))WHEN entity_16.target_type='phone_number'THEN((SELECT jsonb_build_object('archived',entity_22.archived,'created_at',entity_22.created_at,'id',entity_22.id,'number',phone_number_21.number,'type',entity_22.type)FROM agreego.phone_number phone_number_21 JOIN agreego.entity entity_22 ON entity_22.id=phone_number_21.id WHERE NOT entity_22.archived AND relationship_15.target_id=entity_22.id))ELSE NULL END,'type',entity_16.type)),'[]'::jsonb)FROM agreego.contact contact_14 JOIN agreego.relationship relationship_15 ON relationship_15.id=contact_14.id JOIN agreego.entity entity_16 ON entity_16.id=relationship_15.id WHERE NOT entity_16.archived AND relationship_15.source_id=entity_8.id),'created_at',entity_8.created_at,'email_addresses',(SELECT COALESCE(jsonb_agg(jsonb_build_object('archived',entity_25.archived,'created_at',entity_25.created_at,'id',entity_25.id,'is_primary',contact_23.is_primary,'target',(SELECT jsonb_build_object('address',email_address_26.address,'archived',entity_27.archived,'created_at',entity_27.created_at,'id',entity_27.id,'type',entity_27.type)FROM agreego.email_address email_address_26 JOIN agreego.entity entity_27 ON entity_27.id=email_address_26.id WHERE NOT entity_27.archived AND relationship_24.target_id=entity_27.id),'type',entity_25.type)),'[]'::jsonb)FROM agreego.contact contact_23 JOIN agreego.relationship relationship_24 ON relationship_24.id=contact_23.id JOIN agreego.entity entity_25 ON entity_25.id=relationship_24.id WHERE NOT entity_25.archived AND relationship_24.target_type='email_address'AND relationship_24.source_id=entity_8.id),'first_name',person_6.first_name,'id',entity_8.id,'last_name',person_6.last_name,'name',organization_7.name,'phone_numbers',(SELECT COALESCE(jsonb_agg(jsonb_build_object('archived',entity_30.archived,'created_at',entity_30.created_at,'id',entity_30.id,'is_primary',contact_28.is_primary,'target',(SELECT jsonb_build_object('archived',entity_32.archived,'created_at',entity_32.created_at,'id',entity_32.id,'number',phone_number_31.number,'type',entity_32.type)FROM agreego.phone_number phone_number_31 JOIN agreego.entity entity_32 ON entity_32.id=phone_number_31.id WHERE NOT entity_32.archived AND relationship_29.target_id=entity_32.id),'type',entity_30.type)),'[]'::jsonb)FROM agreego.contact contact_28 JOIN agreego.relationship relationship_29 ON relationship_29.id=contact_28.id JOIN agreego.entity entity_30 ON entity_30.id=relationship_29.id WHERE NOT entity_30.archived AND relationship_29.target_type='phone_number'AND relationship_29.source_id=entity_8.id),'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))WHEN organization_1.type='get_organization.response'THEN((SELECT jsonb_build_object('archived',entity_34.archived,'created_at',entity_34.created_at,'id',entity_34.id,'name',organization_33.name,'type',entity_34.type)FROM agreego.organization organization_33 JOIN agreego.entity entity_34 ON entity_34.id=organization_33.id WHERE NOT entity_34.archived))WHEN organization_1.type='light.person'THEN((SELECT jsonb_build_object('age',person_35.age,'archived',entity_37.archived,'created_at',entity_37.created_at,'first_name',person_35.first_name,'id',entity_37.id,'last_name',person_35.last_name,'name',organization_36.name,'type',entity_37.type)FROM agreego.person person_35 JOIN agreego.organization organization_36 ON organization_36.id=person_35.id JOIN agreego.entity entity_37 ON entity_37.id=organization_36.id WHERE NOT entity_37.archived))WHEN organization_1.type='organization'THEN((SELECT jsonb_build_object('archived',entity_39.archived,'created_at',entity_39.created_at,'id',entity_39.id,'name',organization_38.name,'type',entity_39.type)FROM agreego.organization organization_38 JOIN agreego.entity entity_39 ON entity_39.id=organization_38.id WHERE NOT entity_39.archived))WHEN organization_1.type='person'THEN((SELECT jsonb_build_object('age',person_40.age,'archived',entity_42.archived,'created_at',entity_42.created_at,'first_name',person_40.first_name,'id',entity_42.id,'last_name',person_40.last_name,'name',organization_41.name,'type',entity_42.type)FROM agreego.person person_40 JOIN agreego.organization organization_41 ON organization_41.id=person_40.id JOIN agreego.entity entity_42 ON entity_42.id=organization_41.id WHERE NOT entity_42.archived))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)))\nRegex used: \\(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\\)FROM agreego\\.organization organization_1 JOIN agreego\\.entity entity_2 ON entity_2\\.id=organization_1\\.id WHERE NOT entity_2\\.archived\\)\\)\\)\nVariables Mapped: {}" -note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace - - -failures: - tests::test_queryer_0_8 - -test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 1350 filtered out; finished in 0.01s - -error: test failed, to rerun pass `--lib` diff --git a/src/database/enum.rs b/src/database/enum.rs index 72d9c4d..f6df2e5 100644 --- a/src/database/enum.rs +++ b/src/database/enum.rs @@ -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, - pub schemas: Vec, + #[serde(default)] + pub schemas: std::collections::BTreeMap>, } diff --git a/src/database/mod.rs b/src/database/mod.rs index fc881f3..1cc8ec6 100644 --- a/src/database/mod.rs +++ b/src/database/mod.rs @@ -125,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::(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, Arc::new(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(), }); } @@ -185,21 +179,21 @@ impl Database { pub fn compile(&mut self, errors: &mut Vec) { let mut harvested = Vec::new(); - for schema_arc in self.schemas.values_mut() { - if let Some(s) = std::sync::Arc::get_mut(schema_arc) { - s.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) in harvested { - self.schemas.insert(id, Arc::new(schema)); + for (id, schema_arc) in harvested { + self.schemas.insert(id, schema_arc); } self.collect_schemas(errors); // Mathematically evaluate all property inheritances, formats, schemas, and foreign key edges topographically over OnceLocks let mut visited = std::collections::HashSet::new(); - for schema_arc in self.schemas.values() { - schema_arc.as_ref().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,23 +203,26 @@ 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, Arc::new(schema)); + for (id, schema_arc) in to_insert { + self.schemas.insert(id, schema_arc); } } diff --git a/src/database/object.rs b/src/database/object.rs index 4d26214..479250a 100644 --- a/src/database/object.rs +++ b/src/database/object.rs @@ -1,9 +1,9 @@ +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; -use crate::database::schema::Schema; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Case { @@ -19,9 +19,6 @@ pub struct Case { #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct SchemaObject { // Core Schema Keywords - #[serde(rename = "$id")] - #[serde(skip_serializing_if = "Option::is_none")] - pub id: Option, #[serde(skip_serializing_if = "Option::is_none")] pub description: Option, #[serde(skip_serializing_if = "Option::is_none")] @@ -176,7 +173,7 @@ pub struct SchemaObject { #[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>, + pub compiled_options: OnceLock, Option)>>, #[serde(rename = "compiledEdges")] #[serde(skip_deserializing)] @@ -275,93 +272,49 @@ pub fn is_primitive_type(t: &str) -> bool { } impl SchemaObject { - pub fn identifier(&self) -> Option { - if let Some(id) = &self.id { - return Some(id.split('.').next_back().unwrap_or("").to_string()); - } - if let Some(SchemaTypeOrArray::Single(t)) = &self.type_ { - if !is_primitive_type(t) { - return Some(t.split('.').next_back().unwrap_or("").to_string()); - } - } - None - } - - pub fn get_discriminator_value(&self, dim: &str) -> Option { + pub fn get_discriminator_value(&self, dim: &str, schema_id: &str) -> Option { let is_split = self .compiled_properties .get() .map_or(false, |p| p.contains_key("kind")); - if let Some(id) = &self.id { - if id.contains("light.person") || id.contains("light.organization") { - println!( - "[DEBUG SPLIT] ID: {}, dim: {}, is_split: {:?}, props: {:?}", - id, - dim, - is_split, - self - .compiled_properties - .get() - .map(|p| p.keys().cloned().collect::>()) - ); - } - } - - if let Some(props) = self.compiled_properties.get() { - if let Some(prop_schema) = props.get(dim) { - if let Some(c) = &prop_schema.obj.const_ { - if let Some(s) = c.as_str() { - return Some(s.to_string()); - } - } - if let Some(e) = &prop_schema.obj.enum_ { - if e.len() == 1 { - if let Some(s) = e[0].as_str() { - return Some(s.to_string()); - } - } - } - } - } if dim == "kind" { - if let Some(id) = &self.id { - let base = id.split('/').last().unwrap_or(id); - if let Some(idx) = base.rfind('.') { - return Some(base[..idx].to_string()); - } - } - if let Some(SchemaTypeOrArray::Single(t)) = &self.type_ { - if !is_primitive_type(t) { - let base = t.split('/').last().unwrap_or(t); - if let Some(idx) = base.rfind('.') { - return Some(base[..idx].to_string()); - } - } + 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" { - if let Some(id) = &self.id { - let base = id.split('/').last().unwrap_or(id); - if is_split { - return Some(base.split('.').next_back().unwrap_or(base).to_string()); - } else { - return Some(base.to_string()); - } - } - if let Some(SchemaTypeOrArray::Single(t)) = &self.type_ { - if !is_primitive_type(t) { - let base = t.split('/').last().unwrap_or(t); - if is_split { - return Some(base.split('.').next_back().unwrap_or(base).to_string()); - } else { - return Some(base.to_string()); - } - } + 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 + } } diff --git a/src/database/punc.rs b/src/database/punc.rs index 6cea891..6bf41fe 100644 --- a/src/database/punc.rs +++ b/src/database/punc.rs @@ -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, pub page: Option, #[serde(default)] - pub schemas: Vec, + pub schemas: std::collections::BTreeMap>, } diff --git a/src/database/schema.rs b/src/database/schema.rs index f664c1f..1de96a6 100644 --- a/src/database/schema.rs +++ b/src/database/schema.rs @@ -1,7 +1,7 @@ +use crate::database::object::*; use serde::{Deserialize, Serialize}; use serde_json::Value; use std::sync::Arc; -use crate::database::object::*; #[derive(Debug, Clone, Serialize, Default)] pub struct Schema { #[serde(flatten)] @@ -26,6 +26,8 @@ impl Schema { pub fn compile( &self, db: &crate::database::Database, + root_id: &str, + path: String, visited: &mut std::collections::HashSet, errors: &mut Vec, ) { @@ -33,9 +35,11 @@ impl Schema { return; } - if let Some(id) = &self.obj.id { - if !visited.insert(id.clone()) { - return; // Break cyclical resolution + if let Some(crate::database::object::SchemaTypeOrArray::Single(t)) = &self.obj.type_ { + if !crate::database::object::is_primitive_type(t) { + if !visited.insert(t.clone()) { + return; // Break cyclical resolution + } } } @@ -75,7 +79,7 @@ impl Schema { if let Some(crate::database::object::SchemaTypeOrArray::Single(t)) = &self.obj.type_ { if !crate::database::object::is_primitive_type(t) { if let Some(parent) = db.schemas.get(t) { - parent.as_ref().compile(db, visited, errors); + parent.as_ref().compile(db, t, t.clone(), visited, errors); if let Some(p_props) = parent.obj.compiled_properties.get() { props.extend(p_props.clone()); } @@ -95,11 +99,12 @@ impl Schema { errors.push(crate::drop::Error { code: "MULTIPLE_INHERITANCE_PROHIBITED".to_string(), message: format!( - "Schema '{}' attempts to extend multiple custom object pointers in its type array. Use 'oneOf' for polymorphism and tagged unions.", - self.obj.identifier().unwrap_or("unknown".to_string()) + "Schema attempts to extend multiple custom object pointers in its type array {:?}. Use 'oneOf' for polymorphism and tagged unions.", + types ), details: crate::drop::ErrorDetails { - path: self.obj.identifier().unwrap_or("unknown".to_string()), + path: path.clone(), + schema: Some(root_id.to_string()), ..Default::default() } }); @@ -108,7 +113,7 @@ impl Schema { for t in types { if !crate::database::object::is_primitive_type(t) { if let Some(parent) = db.schemas.get(t) { - parent.as_ref().compile(db, visited, errors); + parent.as_ref().compile(db, t, t.clone(), visited, errors); } } } @@ -128,60 +133,68 @@ impl Schema { let _ = self.obj.compiled_property_names.set(names); // 4. Compute Edges natively - let schema_edges = self.compile_edges(db, visited, &props, errors); + let schema_edges = self.compile_edges(db, root_id, &path, visited, &props, errors); let _ = self.obj.compiled_edges.set(schema_edges); // 5. Build our inline children properties recursively NOW! (Depth-first search) if let Some(local_props) = &self.obj.properties { - for child in local_props.values() { - child.compile(db, visited, errors); + for (k, child) in local_props { + child.compile(db, root_id, format!("{}/{}", path, k), visited, errors); } } if let Some(items) = &self.obj.items { - items.compile(db, visited, errors); + items.compile(db, root_id, format!("{}/items", path), visited, errors); } if let Some(pattern_props) = &self.obj.pattern_properties { - for child in pattern_props.values() { - child.compile(db, visited, errors); + for (k, child) in pattern_props { + child.compile(db, root_id, format!("{}/{}", path, k), visited, errors); } } if let Some(additional_props) = &self.obj.additional_properties { - additional_props.compile(db, visited, errors); + additional_props.compile( + db, + root_id, + format!("{}/additionalProperties", path), + visited, + errors, + ); } if let Some(one_of) = &self.obj.one_of { - for child in one_of { - child.compile(db, visited, errors); + for (i, child) in one_of.iter().enumerate() { + child.compile(db, root_id, format!("{}/oneOf/{}", path, i), visited, errors); } } if let Some(arr) = &self.obj.prefix_items { - for child in arr { - child.compile(db, visited, errors); + for (i, child) in arr.iter().enumerate() { + child.compile(db, root_id, format!("{}/prefixItems/{}", path, i), visited, errors); } } if let Some(child) = &self.obj.not { - child.compile(db, visited, errors); + child.compile(db, root_id, format!("{}/not", path), visited, errors); } if let Some(child) = &self.obj.contains { - child.compile(db, visited, errors); + child.compile(db, root_id, format!("{}/contains", path), visited, errors); } if let Some(cases) = &self.obj.cases { - for c in cases { + for (i, c) in cases.iter().enumerate() { if let Some(child) = &c.when { - child.compile(db, visited, errors); + child.compile(db, root_id, format!("{}/cases/{}/when", path, i), visited, errors); } if let Some(child) = &c.then { - child.compile(db, visited, errors); + child.compile(db, root_id, format!("{}/cases/{}/then", path, i), visited, errors); } if let Some(child) = &c.else_ { - child.compile(db, visited, errors); + child.compile(db, root_id, format!("{}/cases/{}/else", path, i), visited, errors); } } } - self.compile_polymorphism(db, errors); + self.compile_polymorphism(db, root_id, &path, errors); - if let Some(id) = &self.obj.id { - visited.remove(id); + if let Some(crate::database::object::SchemaTypeOrArray::Single(t)) = &self.obj.type_ { + if !crate::database::object::is_primitive_type(t) { + visited.remove(t); + } } } @@ -192,6 +205,8 @@ impl Schema { pub fn compile_edges( &self, db: &crate::database::Database, + root_id: &str, + path: &str, visited: &mut std::collections::HashSet, props: &std::collections::BTreeMap>, errors: &mut Vec, @@ -201,16 +216,33 @@ impl Schema { // Determine the physical Database Table Name this schema structurally represents // Plucks the polymorphic discriminator via dot-notation (e.g. extracting "person" from "full.person") let mut parent_type_name = None; + if let Some(family) = &self.obj.family { + // 1. Explicit horizontal routing parent_type_name = Some(family.split('.').next_back().unwrap_or(family).to_string()); - } else if let Some(identifier) = self.obj.identifier() { - parent_type_name = Some( - identifier - .split('.') - .next_back() - .unwrap_or(&identifier) - .to_string(), - ); + } else if !path.contains('/') { + // 2. Root nodes trust their exact registry footprint + let base_type_name = path.split('.').next_back().unwrap_or(path).to_string(); + if db.types.contains_key(&base_type_name) { + parent_type_name = Some(base_type_name); + } + } else if let Some(crate::database::object::SchemaTypeOrArray::Single(t)) = &self.obj.type_ { + // 3. Nested graphs trust their explicit struct pointer reference + if !crate::database::object::is_primitive_type(t) { + parent_type_name = Some(t.split('.').next_back().unwrap_or(t).to_string()); + } + } + + if parent_type_name.is_none() { + // 4. Absolute fallback for completely anonymous inline structures + let base_type_name = root_id + .split('.') + .next_back() + .unwrap_or(root_id) + .to_string(); + if db.types.contains_key(&base_type_name) { + parent_type_name = Some(base_type_name); + } } if let Some(p_type) = parent_type_name { @@ -237,13 +269,19 @@ impl Schema { // Determine the physical Postgres table backing the nested child schema recursively if let Some(family) = &target_schema.obj.family { child_type_name = Some(family.split('.').next_back().unwrap_or(family).to_string()); - } else if let Some(ref_id) = target_schema.obj.identifier() { - child_type_name = Some(ref_id.split('.').next_back().unwrap_or(&ref_id).to_string()); + } else if let Some(crate::database::object::SchemaTypeOrArray::Single(t)) = + &target_schema.obj.type_ + { + if !crate::database::object::is_primitive_type(t) { + child_type_name = Some(t.split('.').next_back().unwrap_or(t).to_string()); + } } else if let Some(arr) = &target_schema.obj.one_of { if let Some(first) = arr.first() { - if let Some(ref_id) = first.obj.identifier() { - child_type_name = - Some(ref_id.split('.').next_back().unwrap_or(&ref_id).to_string()); + if let Some(crate::database::object::SchemaTypeOrArray::Single(t)) = &first.obj.type_ + { + if !crate::database::object::is_primitive_type(t) { + child_type_name = Some(t.split('.').next_back().unwrap_or(t).to_string()); + } } } } @@ -252,7 +290,7 @@ impl Schema { if db.types.contains_key(&c_type) { // Ensure the child Schema's AST has accurately compiled its own physical property keys so we can // inject them securely for Many-to-Many Twin Deduction disambiguation matching. - target_schema.compile(db, visited, errors); + target_schema.compile(db, root_id, format!("{}/{}", path, prop_name), visited, errors); if let Some(compiled_target_props) = target_schema.obj.compiled_properties.get() { let keys_for_ambiguity: Vec = compiled_target_props.keys().cloned().collect(); @@ -264,8 +302,8 @@ impl Schema { prop_name, Some(&keys_for_ambiguity), is_array, - self.id.as_deref(), - &format!("/{}", prop_name), + Some(root_id), + &format!("{}/{}", path, prop_name), errors, ) { schema_edges.insert( @@ -288,6 +326,8 @@ impl Schema { pub fn compile_polymorphism( &self, db: &crate::database::Database, + root_id: &str, + path: &str, errors: &mut Vec, ) { let mut options = std::collections::BTreeMap::new(); @@ -312,7 +352,7 @@ impl Schema { }; if db.schemas.contains_key(&target_id) { - options.insert(var.to_string(), target_id); + options.insert(var.to_string(), (None, Some(target_id))); } } } else { @@ -321,12 +361,10 @@ impl Schema { let suffix = format!(".{}", family_base); - for schema in &type_def.schemas { - if let Some(id) = &schema.obj.id { - if id.ends_with(&suffix) || id == &family_base { - if let Some(kind_val) = schema.obj.get_discriminator_value("kind") { - options.insert(kind_val, id.to_string()); - } + for (id, schema) in &type_def.schemas { + if id.ends_with(&suffix) || id == &family_base { + if let Some(kind_val) = schema.obj.get_discriminator_value("kind", id) { + options.insert(kind_val, (None, Some(id.to_string()))); } } } @@ -335,65 +373,114 @@ impl Schema { } else if let Some(one_of) = &self.obj.one_of { let mut type_vals = std::collections::HashSet::new(); let mut kind_vals = std::collections::HashSet::new(); + let mut disjoint_base = true; + let mut structural_types = std::collections::HashSet::new(); for c in one_of { - if let Some(t_val) = c.obj.get_discriminator_value("type") { - type_vals.insert(t_val); + let mut child_id = String::new(); + let mut child_is_primitive = false; + + if let Some(crate::database::object::SchemaTypeOrArray::Single(t)) = &c.obj.type_ { + if crate::database::object::is_primitive_type(t) { + child_is_primitive = true; + structural_types.insert(t.clone()); + } else { + child_id = t.clone(); + structural_types.insert("object".to_string()); + } + } else { + disjoint_base = false; } - if let Some(k_val) = c.obj.get_discriminator_value("kind") { - kind_vals.insert(k_val); + + if !child_is_primitive { + if let Some(t_val) = c.obj.get_discriminator_value("type", &child_id) { + type_vals.insert(t_val); + } + if let Some(k_val) = c.obj.get_discriminator_value("kind", &child_id) { + kind_vals.insert(k_val); + } } } - strategy = if type_vals.len() > 1 && type_vals.len() == one_of.len() { - "type".to_string() - } else if kind_vals.len() > 1 && kind_vals.len() == one_of.len() { - "kind".to_string() + if disjoint_base && structural_types.len() == one_of.len() { + strategy = "".to_string(); + for (i, c) in one_of.iter().enumerate() { + if let Some(crate::database::object::SchemaTypeOrArray::Single(t)) = &c.obj.type_ { + if crate::database::object::is_primitive_type(t) { + options.insert(t.clone(), (Some(i), None)); + } else { + options.insert("object".to_string(), (Some(i), None)); + } + } + } } else { - "".to_string() - }; + strategy = if type_vals.len() > 1 && type_vals.len() == one_of.len() { + "type".to_string() + } else if kind_vals.len() > 1 && kind_vals.len() == one_of.len() { + "kind".to_string() + } else { + "".to_string() + }; - if strategy.is_empty() { - return; - } - - for c in one_of { - if let Some(val) = c.obj.get_discriminator_value(&strategy) { - if options.contains_key(&val) { - errors.push(crate::drop::Error { - code: "POLYMORPHIC_COLLISION".to_string(), - message: format!("Polymorphic boundary defines multiple candidates mapped to the identical discriminator value '{}'.", val), - details: crate::drop::ErrorDetails::default() - }); - continue; + if strategy.is_empty() { + errors.push(crate::drop::Error { + code: "AMBIGUOUS_POLYMORPHISM".to_string(), + message: format!("oneOf boundaries must map mathematically unique 'type' or 'kind' discriminators, or strictly contain disjoint primitive types."), + details: crate::drop::ErrorDetails { + path: path.to_string(), + schema: Some(root_id.to_string()), + ..Default::default() + } + }); + return; } - let mut target_id = c.obj.id.clone(); - if target_id.is_none() { + for (i, c) in one_of.iter().enumerate() { + let mut child_id = String::new(); if let Some(crate::database::object::SchemaTypeOrArray::Single(t)) = &c.obj.type_ { if !crate::database::object::is_primitive_type(t) { - target_id = Some(t.clone()); + child_id = t.clone(); } } - } - if let Some(tid) = target_id { - options.insert(val, tid); + if let Some(val) = c.obj.get_discriminator_value(&strategy, &child_id) { + if options.contains_key(&val) { + errors.push(crate::drop::Error { + code: "POLYMORPHIC_COLLISION".to_string(), + message: format!("Polymorphic boundary defines multiple candidates mapped to the identical discriminator value '{}'.", val), + details: crate::drop::ErrorDetails { + path: path.to_string(), + schema: Some(root_id.to_string()), + ..Default::default() + } + }); + continue; + } + + options.insert(val, (Some(i), None)); + } } - } } } else { return; } if !options.is_empty() { - let _ = self.obj.compiled_discriminator.set(strategy); + if !strategy.is_empty() { + let _ = self.obj.compiled_discriminator.set(strategy); + } let _ = self.obj.compiled_options.set(options); } } #[allow(unused_variables)] - fn validate_identifier(id: &str, field_name: &str, errors: &mut Vec) { + fn validate_identifier( + id: &str, + field_name: &str, + root_id: &str, + path: &str, + errors: &mut Vec, + ) { #[cfg(not(test))] for c in id.chars() { if !c.is_ascii_lowercase() && !c.is_ascii_digit() && c != '_' && c != '.' { @@ -401,9 +488,13 @@ impl Schema { code: "INVALID_IDENTIFIER".to_string(), message: format!( "Invalid character '{}' in JSON Schema '{}' property: '{}'. Identifiers must exclusively contain [a-z0-9_.]", - c, field_name, id + c, field_name, id ), - details: crate::drop::ErrorDetails::default(), + details: crate::drop::ErrorDetails { + path: path.to_string(), + schema: Some(root_id.to_string()), + ..Default::default() + }, }); return; } @@ -411,116 +502,124 @@ impl Schema { } pub fn collect_schemas( - &mut self, - tracking_path: Option, - to_insert: &mut Vec<(String, Schema)>, + schema_arc: &Arc, + root_id: &str, + path: String, + to_insert: &mut Vec<(String, Arc)>, errors: &mut Vec, ) { - if let Some(id) = &self.obj.id { - Self::validate_identifier(id, "$id", errors); - to_insert.push((id.clone(), self.clone())); + let mut should_push = false; + + // Push ad-hoc inline composition into the addressable registry + if schema_arc.obj.properties.is_some() + || schema_arc.obj.items.is_some() + || schema_arc.obj.family.is_some() + || schema_arc.obj.one_of.is_some() + { + should_push = true; } - if let Some(crate::database::object::SchemaTypeOrArray::Single(t)) = &self.obj.type_ { + + if let Some(crate::database::object::SchemaTypeOrArray::Single(t)) = &schema_arc.obj.type_ { if !crate::database::object::is_primitive_type(t) { - Self::validate_identifier(t, "type", errors); - } - } - if let Some(family) = &self.obj.family { - Self::validate_identifier(family, "$family", errors); - } - - // Is this schema an inline ad-hoc composition? - // Meaning it has a tracking context, lacks an explicit $id, but extends an Entity ref with explicit properties! - if self.obj.id.is_none() && self.obj.properties.is_some() { - if let Some(crate::database::object::SchemaTypeOrArray::Single(t)) = &self.obj.type_ { - if !crate::database::object::is_primitive_type(t) { - if let Some(ref path) = tracking_path { - to_insert.push((path.clone(), self.clone())); - } - } + Self::validate_identifier(t, "type", root_id, &path, errors); + should_push = true; } } - // Provide the path origin to children natively, prioritizing the explicit `$id` boundary if one exists - let origin_path = self.obj.id.clone().or(tracking_path); + if let Some(family) = &schema_arc.obj.family { + Self::validate_identifier(family, "$family", root_id, &path, errors); + } - self.collect_child_schemas(origin_path, to_insert, errors); + if should_push { + to_insert.push((path.clone(), Arc::clone(schema_arc))); + } + + Self::collect_child_schemas(schema_arc, root_id, path, to_insert, errors); } pub fn collect_child_schemas( - &mut self, - origin_path: Option, - to_insert: &mut Vec<(String, Schema)>, + schema_arc: &Arc, + root_id: &str, + path: String, + to_insert: &mut Vec<(String, Arc)>, errors: &mut Vec, ) { - if let Some(props) = &mut self.obj.properties { - for (k, v) in props.iter_mut() { - let mut inner = (**v).clone(); - let next_path = origin_path.as_ref().map(|o| format!("{}/{}", o, k)); - inner.collect_schemas(next_path, to_insert, errors); - *v = Arc::new(inner); + if let Some(props) = &schema_arc.obj.properties { + for (k, v) in props.iter() { + let next_path = format!("{}/{}", path, k); + Self::collect_schemas(v, root_id, next_path, to_insert, errors); } } - if let Some(pattern_props) = &mut self.obj.pattern_properties { - for (k, v) in pattern_props.iter_mut() { - let mut inner = (**v).clone(); - let next_path = origin_path.as_ref().map(|o| format!("{}/{}", o, k)); - inner.collect_schemas(next_path, to_insert, errors); - *v = Arc::new(inner); + if let Some(pattern_props) = &schema_arc.obj.pattern_properties { + for (k, v) in pattern_props.iter() { + let next_path = format!("{}/{}", path, k); + Self::collect_schemas(v, root_id, next_path, to_insert, errors); } } - let mut map_arr = |arr: &mut Vec>| { - for v in arr.iter_mut() { - let mut inner = (**v).clone(); - inner.collect_schemas(origin_path.clone(), to_insert, errors); - *v = Arc::new(inner); + let mut map_arr = |arr: &Vec>, sub: &str| { + for (i, v) in arr.iter().enumerate() { + Self::collect_schemas(v, root_id, format!("{}/{}/{}", path, sub, i), to_insert, errors); } }; - if let Some(arr) = &mut self.obj.prefix_items { - map_arr(arr); + if let Some(arr) = &schema_arc.obj.prefix_items { + map_arr(arr, "prefixItems"); } - if let Some(arr) = &mut self.obj.one_of { - map_arr(arr); + if let Some(arr) = &schema_arc.obj.one_of { + map_arr(arr, "oneOf"); } - let mut map_opt = |opt: &mut Option>, pass_path: bool| { + let mut map_opt = |opt: &Option>, pass_path: bool, sub: &str| { if let Some(v) = opt { - let mut inner = (**v).clone(); - let next = if pass_path { origin_path.clone() } else { None }; - inner.collect_schemas(next, to_insert, errors); - *v = Arc::new(inner); + if pass_path { + Self::collect_schemas(v, root_id, format!("{}/{}", path, sub), to_insert, errors); + } else { + Self::collect_child_schemas(v, root_id, format!("{}/{}", path, sub), to_insert, errors); + } } }; - map_opt(&mut self.obj.additional_properties, false); + map_opt( + &schema_arc.obj.additional_properties, + false, + "additionalProperties", + ); + map_opt(&schema_arc.obj.items, true, "items"); + map_opt(&schema_arc.obj.not, false, "not"); + map_opt(&schema_arc.obj.contains, false, "contains"); + map_opt(&schema_arc.obj.property_names, false, "propertyNames"); - // `items` absolutely must inherit the EXACT property path assigned to the Array wrapper! - // This allows nested Arrays enclosing bare Entity structs to correctly register as the boundary mapping. - map_opt(&mut self.obj.items, true); - - map_opt(&mut self.obj.not, false); - map_opt(&mut self.obj.contains, false); - map_opt(&mut self.obj.property_names, false); - if let Some(cases) = &mut self.obj.cases { - for c in cases.iter_mut() { - if let Some(when) = &mut c.when { - let mut inner = (**when).clone(); - inner.collect_schemas(origin_path.clone(), to_insert, errors); - *when = Arc::new(inner); + if let Some(cases) = &schema_arc.obj.cases { + for (i, c) in cases.iter().enumerate() { + if let Some(when) = &c.when { + Self::collect_schemas( + when, + root_id, + format!("{}/cases/{}/when", path, i), + to_insert, + errors, + ); } - if let Some(then) = &mut c.then { - let mut inner = (**then).clone(); - inner.collect_schemas(origin_path.clone(), to_insert, errors); - *then = Arc::new(inner); + if let Some(then) = &c.then { + Self::collect_schemas( + then, + root_id, + format!("{}/cases/{}/then", path, i), + to_insert, + errors, + ); } - if let Some(else_) = &mut c.else_ { - let mut inner = (**else_).clone(); - inner.collect_schemas(origin_path.clone(), to_insert, errors); - *else_ = Arc::new(inner); + if let Some(else_) = &c.else_ { + Self::collect_schemas( + else_, + root_id, + format!("{}/cases/{}/else", path, i), + to_insert, + errors, + ); } } } diff --git a/src/database/type.rs b/src/database/type.rs index f3554f7..98a9fcd 100644 --- a/src/database/type.rs +++ b/src/database/type.rs @@ -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, pub field_types: Option, #[serde(default)] - pub schemas: Vec, + pub schemas: std::collections::BTreeMap>, } diff --git a/src/merger/mod.rs b/src/merger/mod.rs index 28b64bb..240cd5f 100644 --- a/src/merger/mod.rs +++ b/src/merger/mod.rs @@ -142,11 +142,21 @@ impl Merger { 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(target_id) = options.get(v) { - if let Some(target_schema) = self.db.schemas.get(target_id) { - schema = Arc::clone(target_schema); + 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 '{}' not found in database registry", target_id)); + return Err(format!("Polymorphic mapped target has no path")); } } else { return Err(format!("Polymorphic discriminator {}='{}' matched no compiled options", disc, v)); @@ -215,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; } @@ -234,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); } } @@ -524,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())); @@ -564,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(), diff --git a/src/queryer/compiler.rs b/src/queryer/compiler.rs index 6ea1056..75c1aac 100644 --- a/src/queryer/compiler.rs +++ b/src/queryer/compiler.rs @@ -18,6 +18,7 @@ pub struct Node<'a> { pub depth: usize, pub ast_path: String, pub is_polymorphic_branch: bool, + pub schema_id: Option, } impl<'a> Compiler<'a> { @@ -47,6 +48,7 @@ impl<'a> Compiler<'a> { depth: 0, ast_path: String::new(), is_polymorphic_branch: false, + schema_id: Some(schema_id.to_string()), }; let (sql, _) = compiler.compile_node(node)?; @@ -66,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 { @@ -105,16 +121,28 @@ 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() { - 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) = 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); } @@ -126,6 +154,7 @@ impl<'a> Compiler<'a> { if let Some(target_schema) = self.db.schemas.get(t) { let mut ref_node = node.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)); @@ -133,17 +162,21 @@ impl<'a> Compiler<'a> { } // 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_id = options.values().next().unwrap(); - 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 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); } - } - return self.compile_one_of(node); + } + } + return self.compile_one_of(node); } // Just an inline object definition? @@ -171,27 +204,27 @@ impl<'a> Compiler<'a> { let (table_aliases, from_clauses) = self.compile_from_clause(r#type); 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()); - - 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 base_alias = table_aliases + .get(&r#type.name) + .cloned() + .unwrap_or_else(|| node.parent_alias.to_string()); - let (case_sql, _) = self.compile_one_of(case_node)?; - case_sql + 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 (case_sql, _) = self.compile_one_of(case_node)?; + case_sql } else { - 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(", ")) - } + 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 @@ -249,14 +282,21 @@ impl<'a> Compiler<'a> { Ok((combined, "object".to_string())) } - fn compile_one_of( - &mut self, - 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 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!("{}_{}", prop, disc) @@ -264,36 +304,73 @@ impl<'a> Compiler<'a> { disc.to_string() }; - for (disc_val, target_id) in options { - 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.is_polymorphic_branch = true; + 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; - 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() { + 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 { + } else { format!("jsonb_build_object({})", select_args.join(", ")) - } - } else { - let (sql, _) = self.compile_node(child_node)?; - sql - }; + } + } else { + let (sql, _) = self.compile_node(child_node)?; + sql + }; - case_statements.push(format!( - "WHEN {}.{} = '{}' THEN ({})", - node.parent_alias, type_col, disc_val, val_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())); } @@ -339,14 +416,14 @@ impl<'a> Compiler<'a> { let mut select_args = Vec::new(); let grouped_fields = r#type.grouped_fields.as_ref().and_then(|v| v.as_object()); let default_props = std::collections::BTreeMap::new(); - let merged_props = node.schema.obj.compiled_properties.get().unwrap_or(&default_props); - - 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 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::object::SchemaTypeOrArray::Single(s)) => { s == "object" || s == "array" @@ -410,6 +487,7 @@ impl<'a> Compiler<'a> { format!("{}/{}", node.ast_path, prop_key) }, is_polymorphic_branch: false, + schema_id: None, }; let (val_sql, val_type) = self.compile_node(child_node)?; @@ -449,7 +527,7 @@ 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, @@ -491,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 { diff --git a/src/tests/fixtures.rs b/src/tests/fixtures.rs index 73aafed..51c80da 100644 --- a/src/tests/fixtures.rs +++ b/src/tests/fixtures.rs @@ -1553,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")); @@ -3558,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] @@ -7740,21 +7722,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] diff --git a/src/tests/mod.rs b/src/tests/mod.rs index 2a3bb0c..d9ee539 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -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", @@ -104,8 +107,11 @@ fn test_library_api() { } } }, + "source_schema/target": { + "type": "target_schema", + "compiledProperties": ["value"] + }, "target_schema": { - "$id": "target_schema", "type": "object", "properties": { "value": { "type": "number" } diff --git a/src/validator/rules/array.rs b/src/validator/rules/array.rs index b374c51..010068a 100644 --- a/src/validator/rules/array.rs +++ b/src/validator/rules/array.rs @@ -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( diff --git a/src/validator/rules/object.rs b/src/validator/rules/object.rs index c888673..cd0ec41 100644 --- a/src/validator/rules/object.rs +++ b/src/validator/rules/object.rs @@ -13,10 +13,17 @@ impl<'a> ValidationContext<'a> { ) -> Result { 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()); diff --git a/src/validator/rules/polymorphism.rs b/src/validator/rules/polymorphism.rs index 45bc723..097d7a4 100644 --- a/src/validator/rules/polymorphism.rs +++ b/src/validator/rules/polymorphism.rs @@ -30,77 +30,34 @@ impl<'a> ValidationContext<'a> { if self.schema.family.is_some() { if let Some(options) = self.schema.compiled_options.get() { - if let Some(disc) = self.schema.compiled_discriminator.get() { - return self.execute_polymorph(disc, options, result); - } + 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 { - if let Some(one_of) = &self.schema.one_of { + if self.schema.one_of.is_some() { if let Some(options) = self.schema.compiled_options.get() { - if let Some(disc) = self.schema.compiled_discriminator.get() { - return self.execute_polymorph(disc, options, result); - } - } - - // Native Draft2020-12 oneOf Evaluation Fallback - let mut valid_count = 0; - let mut final_successful_result = None; - let mut failed_candidates = Vec::new(); - - for child_schema in one_of { - let derived = self.derive_for_schema(child_schema, false); - if let Ok(sub_res) = derived.validate_scoped() { - if sub_res.is_valid() { - valid_count += 1; - final_successful_result = Some(sub_res.clone()); - } else { - failed_candidates.push(sub_res); - } - } - } - - if valid_count == 1 { - if let Some(successful_res) = final_successful_result { - result.merge(successful_res); - } - return Ok(true); - } else if valid_count == 0 { - result.errors.push(ValidationError { - code: "NO_ONEOF_MATCH".to_string(), - message: "Payload matches none of the required candidate sub-schemas natively".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); - } - } - } - - return Ok(false); + return self.execute_polymorph(options, result); } else { - result.errors.push(ValidationError { - code: "AMBIGUOUS_POLYMORPHIC_MATCH".to_string(), - message: "Matches multiple polymorphic candidates inextricably natively".to_string(), + 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); + return Ok(false); } } Ok(true) @@ -108,46 +65,115 @@ impl<'a> ValidationContext<'a> { pub(crate) fn execute_polymorph( &self, - disc: &str, - options: &std::collections::BTreeMap, + options: &std::collections::BTreeMap, Option)>, result: &mut ValidationResult, ) -> Result { // 1. O(1) Fast-Path Router & Extractor - let instance_val = self.instance.as_object().and_then(|o| o.get(disc)).and_then(|t| t.as_str()); + 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()), + } + }; if let Some(val) = instance_val { - result.evaluated_keys.insert(disc.to_string()); - - if let Some(target_id) = options.get(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); + 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); + 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); } - } else { - 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 provided discriminator {}='{}' which matches none of the required candidate sub-schemas", disc, val), - path: self.path.to_string(), - }); + } 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); + } + } else { return Ok(false); - } - } else { + } + } else { + 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: "MISSING_TYPE".to_string(), - message: format!("Missing '{}' discriminator. Unable to resolve polymorphic boundaries", disc), + 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(), }); return Ok(false); + } + } else { + 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); } } @@ -179,14 +205,16 @@ impl<'a> ValidationContext<'a> { } } 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 + if arr.contains(&payload_primitive.to_string()) + || (payload_primitive == "integer" && arr.contains(&"number".to_string())) + { + // It natively matched a primitive in the array options, skip forcing custom proxy fallback } else { - for t in arr { - if !crate::database::object::is_primitive_type(t) { - custom_types.push(t.clone()); - } - } + for t in arr { + if !crate::database::object::is_primitive_type(t) { + custom_types.push(t.clone()); + } + } } } None => {}