Compare commits
23 Commits
1.0.110
...
0b072d66e7
| Author | SHA1 | Date | |
|---|---|---|---|
| 0b072d66e7 | |||
| 41649766db | |||
| 61a8c5eed7 | |||
| 77af67aef5 | |||
| cd85a8a2c3 | |||
| d3cb72a5e2 | |||
| 57baa389b6 | |||
| 8ceb4f05a2 | |||
| a3bd79deef | |||
| c38d81efa4 | |||
| 3e7fafd736 | |||
| 8984acaa5f | |||
| 24adf3ffc6 | |||
| bfe3dd2e8c | |||
| 609371c03c | |||
| 93d1315f0b | |||
| e407adf6a1 | |||
| 0b4607b7d4 | |||
| a53e89df52 | |||
| 57bbe11013 | |||
| 386b7ad012 | |||
| a91460b390 | |||
| 0017c598e1 |
61
GEMINI.md
61
GEMINI.md
@ -13,7 +13,7 @@ JSPG operates by deeply integrating the JSON Schema Draft 2020-12 specification
|
|||||||
1. **Draft 2020-12 Based**: Attempt to adhere to the official JSON Schema Draft 2020-12 specification, while heavily augmenting it for strict structural typing.
|
1. **Draft 2020-12 Based**: Attempt to adhere to the official JSON Schema Draft 2020-12 specification, while heavily augmenting it for strict structural typing.
|
||||||
2. **Ultra-Fast Execution**: Compile schemas into optimized in-memory validation trees and cached SQL SPIs to bypass Postgres Query Builder overheads.
|
2. **Ultra-Fast Execution**: Compile schemas into optimized in-memory validation trees and cached SQL SPIs to bypass Postgres Query Builder overheads.
|
||||||
3. **Connection-Bound Caching**: Leverage the PostgreSQL session lifecycle using an **Atomic Swap** pattern. Schemas are 100% frozen, completely eliminating locks during read access.
|
3. **Connection-Bound Caching**: Leverage the PostgreSQL session lifecycle using an **Atomic Swap** pattern. Schemas are 100% frozen, completely eliminating locks during read access.
|
||||||
4. **Structural Inheritance**: Support object-oriented schema design via Implicit Keyword Shadowing and virtual `$family` references natively mapped to Postgres table constraints.
|
4. **Structural Inheritance**: Support object-oriented schema design via Implicit Keyword Shadowing and virtual `family` references natively mapped to Postgres table constraints.
|
||||||
5. **Reactive Beats**: Provide ultra-fast natively generated flat payloads mapping directly to the Dart topological state for dynamic websocket reactivity.
|
5. **Reactive Beats**: Provide ultra-fast natively generated flat payloads mapping directly to the Dart topological state for dynamic websocket reactivity.
|
||||||
|
|
||||||
### Concurrency & Threading ("Immutable Graphs")
|
### Concurrency & Threading ("Immutable Graphs")
|
||||||
@ -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.
|
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
|
### Types of Types
|
||||||
* **Table-Backed (Entity Types)**: Primarily defined in root type schemas. These represent physical Postgres tables.
|
* **Table-Backed (Entity Types)**: Primarily defined in root `types` schemas. These represent physical Postgres tables.
|
||||||
* They absolutely **require** an `$id`.
|
* 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.
|
* 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.
|
* **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.
|
* 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)
|
### Discriminators & The Dot Convention (A.B)
|
||||||
@ -50,31 +50,29 @@ 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:
|
**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`).
|
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**.
|
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**:
|
**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.
|
* **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.
|
* **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
|
```json
|
||||||
{
|
{
|
||||||
"$id": "light.person",
|
|
||||||
"type": "person",
|
"type": "person",
|
||||||
"properties": {
|
"properties": {
|
||||||
"type": { "type": "string" },
|
"type": { "type": "string" },
|
||||||
"kind": { "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 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 `$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 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.
|
* **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)
|
### Composition & Inheritance (The `type` keyword)
|
||||||
Punc completely abandons the standard JSON Schema `$ref` keyword. Instead, it overloads the exact same `type` keyword used for primitives. A `"type"` in Punc is mathematically evaluated as either a Native Primitive (`"string"`, `"null"`) or a Custom Object Pointer (`"budget"`, `"user"`).
|
Punc completely abandons the standard JSON Schema `$ref` keyword. Instead, it overloads the exact same `type` keyword used for primitives. A `"type"` in Punc is mathematically evaluated as either a Native Primitive (`"string"`, `"null"`) or a Custom Object Pointer (`"budget"`, `"user"`).
|
||||||
@ -83,24 +81,24 @@ Punc completely abandons the standard JSON Schema `$ref` keyword. Instead, it ov
|
|||||||
* **Primitive Array Shorthand (Optionality)**: The `type` array syntax is heavily optimized for nullable fields. Defining `"type": ["budget", "null"]` natively builds a nullable strict, generating `Budget? budget;` in Dart. You can freely mix primitives like `["string", "number", "null"]`.
|
* **Primitive Array Shorthand (Optionality)**: The `type` array syntax is heavily optimized for nullable fields. Defining `"type": ["budget", "null"]` natively builds a nullable strict, generating `Budget? budget;` in Dart. You can freely mix primitives like `["string", "number", "null"]`.
|
||||||
* **Strict Array Constraint**: To explicitly prevent mathematically ambiguous Multiple Inheritance, a `type` array is strictly constrained to at most **ONE** Custom Object Pointer. Defining `"type": ["person", "organization"]` will intentionally trigger a fatal database compilation error natively instructing developers to build a proper tagged union (`oneOf`) instead.
|
* **Strict Array Constraint**: To explicitly prevent mathematically ambiguous Multiple Inheritance, a `type` array is strictly constrained to at most **ONE** Custom Object Pointer. Defining `"type": ["person", "organization"]` will intentionally trigger a fatal database compilation error natively instructing developers to build a proper tagged union (`oneOf`) instead.
|
||||||
|
|
||||||
### Polymorphism (`$family` and `oneOf`)
|
### Polymorphism (`family` and `oneOf`)
|
||||||
Polymorphism is how an object boundary can dynamically take on entirely different shapes based on the payload provided at runtime. Punc utilizes the static database metadata generated from Postgres (`db.types`) to enforce these boundaries deterministically, rather than relying on ambiguous tree-traversals.
|
Polymorphism is how an object boundary can dynamically take on entirely different shapes based on the payload provided at runtime. Punc utilizes the static database metadata generated from Postgres (`db.types`) to enforce these boundaries deterministically, rather than relying on ambiguous tree-traversals.
|
||||||
|
|
||||||
* **`$family` (Target-Based Polymorphism)**: An explicit Punc compiler macro instructing the engine to resolve dynamic options against the registered database `types` variations or its inner schema registry. It uses the exact physical constraints of the database to build SQL and validation routes.
|
* **`family` (Target-Based Polymorphism)**: An explicit Punc compiler macro instructing the engine to resolve dynamic options against the registered database `types` variations or its inner schema registry. It uses the exact physical constraints of the database to build SQL and validation routes.
|
||||||
* **Scenario A: Global Tables (Vertical Routing)**
|
* **Scenario A: Global Tables (Vertical Routing)**
|
||||||
* *Setup*: `{ "$family": "organization" }`
|
* *Setup*: `{ "family": "organization" }`
|
||||||
* *Execution*: The engine queries `db.types.get("organization").variations` and finds `["bot", "organization", "person"]`. Because organizations are structurally table-backed, the `$family` automatically uses `type` as the discriminator.
|
* *Execution*: The engine queries `db.types.get("organization").variations` and finds `["bot", "organization", "person"]`. Because organizations are structurally table-backed, the `family` automatically uses `type` as the discriminator.
|
||||||
* *Options*: `bot` -> `bot`, `person` -> `person`, `organization` -> `organization`.
|
* *Options*: `bot` -> `bot`, `person` -> `person`, `organization` -> `organization`.
|
||||||
* **Scenario B: Prefixed Tables (Vertical Projection)**
|
* **Scenario B: Prefixed Tables (Vertical Projection)**
|
||||||
* *Setup*: `{ "$family": "light.organization" }`
|
* *Setup*: `{ "family": "light.organization" }`
|
||||||
* *Execution*: The engine sees the prefix `light.` and base `organization`. It queries `db.types.get("organization").variations` and dynamically prepends the prefix to discover the relevant UI schemas.
|
* *Execution*: The engine sees the prefix `light.` and base `organization`. It queries `db.types.get("organization").variations` and dynamically prepends the prefix to discover the relevant UI schemas.
|
||||||
* *Options*: `person` -> `light.person`, `organization` -> `light.organization`. (If a projection like `light.bot` does not exist in `db.schemas`, it is safely ignored).
|
* *Options*: `person` -> `light.person`, `organization` -> `light.organization`. (If a projection like `light.bot` does not exist in `db.schemas`, it is safely ignored).
|
||||||
* **Scenario C: Single Table Inheritance (Horizontal Routing)**
|
* **Scenario C: Single Table Inheritance (Horizontal Routing)**
|
||||||
* *Setup*: `{ "$family": "widget" }` (Where `widget` is a table type but has no external variations).
|
* *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`.
|
* *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.
|
* **`oneOf` (Strict Tagged Unions)**: A hardcoded list of candidate schemas. Unlike `family` which relies on global DB metadata, `oneOf` forces pure mathematical structural evaluation of the provided candidates. It strictly bans typical JSON Schema "Union of Sets" fallback searches. Every candidate MUST possess a mathematically unique discriminator payload to allow $O(1)$ routing.
|
||||||
* **Disjoint Types**: `oneOf: [{ "type": "person" }, { "type": "widget" }]`. The engine succeeds because the native `type` acts as a unique discriminator (`"person"` vs `"widget"`).
|
* **Disjoint Types**: `oneOf: [{ "type": "person" }, { "type": "widget" }]`. The engine succeeds because the native `type` acts as a unique discriminator (`"person"` vs `"widget"`).
|
||||||
* **STI Types**: `oneOf: [{ "type": "heavy.person" }, { "type": "light.person" }]`. The engine succeeds. Even though both share `"type": "person"`, their explicit discriminator is `kind` (`"heavy"` vs `"light"`), ensuring unique $O(1)$ fast-paths.
|
* **STI Types**: `oneOf: [{ "type": "heavy.person" }, { "type": "light.person" }]`. The engine succeeds. Even though both share `"type": "person"`, their explicit discriminator is `kind` (`"heavy"` vs `"light"`), ensuring unique $O(1)$ fast-paths.
|
||||||
* **Conflicting Types**: `oneOf: [{ "type": "person" }, { "type": "light.person" }]`. The engine **fails compilation natively**. Both schemas evaluate to `"type": "person"` and neither provides a disjoint `kind` constraint, making them mathematically ambiguous and impossible to route in $O(1)$ time.
|
* **Conflicting Types**: `oneOf: [{ "type": "person" }, { "type": "light.person" }]`. The engine **fails compilation natively**. Both schemas evaluate to `"type": "person"` and neither provides a disjoint `kind` constraint, making them mathematically ambiguous and impossible to route in $O(1)$ time.
|
||||||
@ -112,7 +110,6 @@ It evaluates as an **Independent Declarative Rules Engine**. Every `Case` block
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"$id": "save_external_account",
|
|
||||||
"cases": [
|
"cases": [
|
||||||
{
|
{
|
||||||
"when": {
|
"when": {
|
||||||
@ -171,10 +168,11 @@ When compiling nested object graphs or arrays, the JSPG engine must dynamically
|
|||||||
5. **Implicit Base Fallback (1:M)**: If no explicit prefix matches, and M:M deduction fails, the compiler filters for exactly one remaining relation with a `null` prefix (e.g. `fk_invoice_line_invoice` -> `prefix: null`). A `null` prefix mathematically denotes the core structural parent-child ownership edge and is used safely as a fallback.
|
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.
|
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
|
### Subschema 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.
|
To seamlessly support deeply nested Object and Array structures, 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.
|
* **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 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.
|
* **Promotion:** This nested subschema chunk is mathematically elevated to its own independent key in the `db.schemas` cache registry using its full path. This guarantees that $O(1)$ WebSockets or isolated queries can natively target any arbitrary nested sub-object of a massive database topology directly without recursively re-parsing its parent's AST block every read. Note that you cannot use the `type` attribute to statically inherit from these automatically promoted subschemas.
|
||||||
|
* **Primitive Confinement:** Purely scalar or primitive branches (like `oneOf: [{type: "string"}, {type: "null"}]`) bypass global topological promotion. They are evaluated directly within the execution engine via isolated Tuple Indexes to explicitly protect the global DB Registry and Go Mixer from memory bloat.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -189,9 +187,10 @@ The Validator provides strict, schema-driven evaluation for the "Punc" architect
|
|||||||
JSPG implements specific extensions to the Draft 2020-12 standard to support the Punc architecture's object-oriented needs while heavily optimizing for zero-runtime lookups.
|
JSPG implements specific extensions to the Draft 2020-12 standard to support the Punc architecture's object-oriented needs while heavily optimizing for zero-runtime lookups.
|
||||||
|
|
||||||
* **Caching Strategy**: The Validator caches the pre-compiled `Database` registry in memory upon initialization (`jspg_setup`). This registry holds the comprehensive graph of schema boundaries, Types, ENUMs, and Foreign Key relationships, acting as the Single Source of Truth for all validation operations without polling Postgres.
|
* **Caching Strategy**: The Validator caches the pre-compiled `Database` registry in memory upon initialization (`jspg_setup`). This registry holds the comprehensive graph of schema boundaries, Types, ENUMs, and Foreign Key relationships, acting as the Single Source of Truth for all validation operations without polling Postgres.
|
||||||
* **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.
|
* **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.
|
* **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.
|
* **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.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -238,8 +237,8 @@ The Queryer transforms Postgres into a pre-compiled Semantic Query Engine, desig
|
|||||||
* **Array Inclusion**: `{"$in": [values]}`, `{"$nin": [values]}` use native `jsonb_array_elements_text()` bindings to enforce `IN` and `NOT IN` logic without runtime SQL injection risks.
|
* **Array Inclusion**: `{"$in": [values]}`, `{"$nin": [values]}` use native `jsonb_array_elements_text()` bindings to enforce `IN` and `NOT IN` logic without runtime SQL injection risks.
|
||||||
* **Text Matching (ILIKE)**: Evaluates `$eq` or `$ne` against string fields containing the `%` character natively into Postgres `ILIKE` and `NOT ILIKE` partial substring matches.
|
* **Text Matching (ILIKE)**: Evaluates `$eq` or `$ne` against string fields containing the `%` character natively into Postgres `ILIKE` and `NOT ILIKE` partial substring matches.
|
||||||
* **Type Casting**: Safely resolves dynamic combinations by casting values instantly into the physical database types mapped in the schema (e.g. parsing `uuid` bindings to `::uuid`, formatting DateTimes to `::timestamptz`, and numbers to `::numeric`).
|
* **Type Casting**: Safely resolves dynamic combinations by casting values instantly into the physical database types mapped in the schema (e.g. parsing `uuid` bindings to `::uuid`, formatting DateTimes to `::timestamptz`, and numbers to `::numeric`).
|
||||||
* **Polymorphic SQL Generation (`$family`)**: Compiles `$family` properties by analyzing the **Physical Database Variations**, *not* the schema descendants.
|
* **Polymorphic SQL Generation (`family`)**: Compiles `family` properties by analyzing the **Physical Database Variations**, *not* the schema descendants.
|
||||||
* **The Dot Convention**: When a schema requests `$family: "target.schema"`, the compiler extracts the base type (e.g. `schema`) and looks up its Physical Table definition.
|
* **The Dot Convention**: When a schema requests `family: "target.schema"`, the compiler extracts the base type (e.g. `schema`) and looks up its Physical Table definition.
|
||||||
* **Multi-Table Branching**: If the Physical Table is a parent to other tables (e.g. `organization` has variations `["organization", "bot", "person"]`), the compiler generates a dynamic `CASE WHEN type = '...' THEN ...` query, expanding into sub-queries for each variation. To ensure safe resolution, the compiler dynamically evaluates correlation boundaries: it attempts standard Relational Edge discovery first. If no explicit relational edge exists (indicating pure Table Inheritance rather than a standard foreign-key graph relationship), it safely invokes a **Table Parity Fallback**. This generates an explicit ID correlation constraint (`AND inner.id = outer.id`), perfectly binding the structural variations back to the parent row to eliminate Cartesian products.
|
* **Multi-Table Branching**: If the Physical Table is a parent to other tables (e.g. `organization` has variations `["organization", "bot", "person"]`), the compiler generates a dynamic `CASE WHEN type = '...' THEN ...` query, expanding into sub-queries for each variation. To ensure safe resolution, the compiler dynamically evaluates correlation boundaries: it attempts standard Relational Edge discovery first. If no explicit relational edge exists (indicating pure Table Inheritance rather than a standard foreign-key graph relationship), it safely invokes a **Table Parity Fallback**. This generates an explicit ID correlation constraint (`AND inner.id = outer.id`), perfectly binding the structural variations back to the parent row to eliminate Cartesian products.
|
||||||
* **Single-Table Bypass**: If the Physical Table is a leaf node with only one variation (e.g. `person` has variations `["person"]`), the compiler cleanly bypasses `CASE` generation and compiles a simple `SELECT` across the base table, as all schema extensions (e.g. `light.person`, `full.person`) are guaranteed to reside in the exact same physical row.
|
* **Single-Table Bypass**: If the Physical Table is a leaf node with only one variation (e.g. `person` has variations `["person"]`), the compiler cleanly bypasses `CASE` generation and compiles a simple `SELECT` across the base table, as all schema extensions (e.g. `light.person`, `full.person`) are guaranteed to reside in the exact same physical row.
|
||||||
|
|
||||||
|
|||||||
@ -2,9 +2,8 @@
|
|||||||
{
|
{
|
||||||
"description": "additionalProperties validates properties not matched by properties",
|
"description": "additionalProperties validates properties not matched by properties",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"schema1": {
|
||||||
"$id": "schema1",
|
|
||||||
"properties": {
|
"properties": {
|
||||||
"foo": {
|
"foo": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
@ -17,7 +16,7 @@
|
|||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -62,8 +61,8 @@
|
|||||||
{
|
{
|
||||||
"description": "extensible: true with additionalProperties still validates structure",
|
"description": "extensible: true with additionalProperties still validates structure",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"additionalProperties_1_0": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"foo": {
|
"foo": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
@ -72,10 +71,9 @@
|
|||||||
"extensible": true,
|
"extensible": true,
|
||||||
"additionalProperties": {
|
"additionalProperties": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
},
|
}
|
||||||
"$id": "additionalProperties_1_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -108,9 +106,8 @@
|
|||||||
{
|
{
|
||||||
"description": "complex additionalProperties with object and array items",
|
"description": "complex additionalProperties with object and array items",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"schema3": {
|
||||||
"$id": "schema3",
|
|
||||||
"properties": {
|
"properties": {
|
||||||
"type": {
|
"type": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
@ -123,7 +120,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
|
|||||||
@ -2,11 +2,9 @@
|
|||||||
{
|
{
|
||||||
"description": "boolean schema 'true'",
|
"description": "boolean schema 'true'",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"booleanSchema_0_0": {}
|
||||||
"$id": "booleanSchema_0_0"
|
}
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -99,12 +97,11 @@
|
|||||||
{
|
{
|
||||||
"description": "boolean schema 'false'",
|
"description": "boolean schema 'false'",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"booleanSchema_1_0": {
|
||||||
"not": {},
|
"not": {}
|
||||||
"$id": "booleanSchema_1_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
|
|||||||
@ -2,87 +2,193 @@
|
|||||||
{
|
{
|
||||||
"description": "Multi-Paradigm Declarative Cases",
|
"description": "Multi-Paradigm Declarative Cases",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"parallel_rules": {
|
||||||
"$id": "parallel_rules",
|
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"status": { "type": "string" },
|
"status": {
|
||||||
"kind": { "type": "string" }
|
"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"]
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
"kind": {
|
||||||
"when": { "properties": { "kind": {"const": "credit"} }, "required": ["kind"] },
|
"type": "string"
|
||||||
"then": {
|
|
||||||
"properties": {
|
|
||||||
"cvv": {"type": "number"}
|
|
||||||
},
|
|
||||||
"required": ["cvv"]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"$id": "mutually_exclusive",
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"type": { "type": "string" }
|
|
||||||
},
|
},
|
||||||
"cases": [
|
"cases": [
|
||||||
{
|
{
|
||||||
"when": { "properties": { "type": {"const": "A"} }, "required": ["type"] },
|
"when": {
|
||||||
"then": {
|
"properties": {
|
||||||
"properties": { "field_a": {"type": "number"} },
|
"status": {
|
||||||
"required": ["field_a"]
|
"const": "unverified"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
"required": [
|
||||||
"when": { "properties": { "type": {"const": "B"} }, "required": ["type"] },
|
"status"
|
||||||
"then": {
|
]
|
||||||
"properties": { "field_b": {"type": "number"} },
|
|
||||||
"required": ["field_b"]
|
|
||||||
},
|
},
|
||||||
"else": {
|
"then": {
|
||||||
"properties": { "fallback_b": {"type": "number"} },
|
"properties": {
|
||||||
"required": ["fallback_b"]
|
"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"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
"mutually_exclusive": {
|
||||||
"$id": "nested_fallbacks",
|
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"tier": { "type": "string" }
|
"type": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"cases": [
|
"cases": [
|
||||||
{
|
{
|
||||||
"when": { "properties": { "tier": {"const": "1"} }, "required": ["tier"] },
|
"when": {
|
||||||
"then": {
|
"properties": {
|
||||||
"properties": { "basic": {"type": "number"} },
|
"type": {
|
||||||
"required": ["basic"]
|
"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": {
|
"else": {
|
||||||
"cases": [
|
"cases": [
|
||||||
{
|
{
|
||||||
"when": { "properties": { "tier": {"const": "2"} }, "required": ["tier"] },
|
"when": {
|
||||||
"then": {
|
"properties": {
|
||||||
"properties": { "standard": {"type": "number"} },
|
"tier": {
|
||||||
"required": ["standard"]
|
"const": "2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"tier"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"else": {
|
"then": {
|
||||||
"properties": { "premium": {"type": "number"} },
|
"properties": {
|
||||||
"required": ["premium"]
|
"standard": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"standard"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"else": {
|
||||||
|
"properties": {
|
||||||
|
"premium": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"premium"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -90,94 +196,159 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
"missing_when": {
|
||||||
"$id": "missing_when",
|
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"cases": [
|
"cases": [
|
||||||
{
|
{
|
||||||
"else": {
|
"else": {
|
||||||
"properties": { "unconditional": {"type": "number"} },
|
"properties": {
|
||||||
"required": ["unconditional"]
|
"unconditional": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"unconditional"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
"description": "Fires only the first rule successfully",
|
"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",
|
"schema_id": "parallel_rules",
|
||||||
"action": "validate",
|
"action": "validate",
|
||||||
"expect": { "success": true }
|
"expect": {
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "Fires both independent parallel rules flawlessly",
|
"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",
|
"schema_id": "parallel_rules",
|
||||||
"action": "validate",
|
"action": "validate",
|
||||||
"expect": { "success": true }
|
"expect": {
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "Catches errors triggered concurrently by multiple independent blocked rules",
|
"description": "Catches errors triggered concurrently by multiple independent blocked rules",
|
||||||
"data": { "status": "unverified", "kind": "credit" },
|
"data": {
|
||||||
|
"status": "unverified",
|
||||||
|
"kind": "credit"
|
||||||
|
},
|
||||||
"schema_id": "parallel_rules",
|
"schema_id": "parallel_rules",
|
||||||
"action": "validate",
|
"action": "validate",
|
||||||
"expect": {
|
"expect": {
|
||||||
"success": false,
|
"success": false,
|
||||||
"errors": [
|
"errors": [
|
||||||
{ "code": "REQUIRED_FIELD_MISSING", "details": { "path": "amount_1" } },
|
{
|
||||||
{ "code": "REQUIRED_FIELD_MISSING", "details": { "path": "amount_2" } },
|
"code": "REQUIRED_FIELD_MISSING",
|
||||||
{ "code": "REQUIRED_FIELD_MISSING", "details": { "path": "cvv" } }
|
"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",
|
"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",
|
"schema_id": "parallel_rules",
|
||||||
"action": "validate",
|
"action": "validate",
|
||||||
"expect": {
|
"expect": {
|
||||||
"success": false,
|
"success": false,
|
||||||
"errors": [
|
"errors": [
|
||||||
{ "code": "STRICT_PROPERTY_VIOLATION", "details": { "path": "cvv" } }
|
{
|
||||||
|
"code": "STRICT_PROPERTY_VIOLATION",
|
||||||
|
"details": {
|
||||||
|
"path": "cvv"
|
||||||
|
}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "Successfully routes mutually exclusive properties seamlessly",
|
"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",
|
"schema_id": "mutually_exclusive",
|
||||||
"action": "validate",
|
"action": "validate",
|
||||||
"expect": { "success": true }
|
"expect": {
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "Nested fallbacks execute seamlessly",
|
"description": "Nested fallbacks execute seamlessly",
|
||||||
"data": { "tier": "3", "premium": 1 },
|
"data": {
|
||||||
|
"tier": "3",
|
||||||
|
"premium": 1
|
||||||
|
},
|
||||||
"schema_id": "nested_fallbacks",
|
"schema_id": "nested_fallbacks",
|
||||||
"action": "validate",
|
"action": "validate",
|
||||||
"expect": { "success": true }
|
"expect": {
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "A case without a when executes its else indiscriminately",
|
"description": "A case without a when executes its else indiscriminately",
|
||||||
"data": { "unconditional": 1 },
|
"data": {
|
||||||
|
"unconditional": 1
|
||||||
|
},
|
||||||
"schema_id": "missing_when",
|
"schema_id": "missing_when",
|
||||||
"action": "validate",
|
"action": "validate",
|
||||||
"expect": { "success": true }
|
"expect": {
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "A case without a when throws if else unconditionally requires field",
|
"description": "A case without a when throws if else unconditionally requires field",
|
||||||
"data": { },
|
"data": {},
|
||||||
"schema_id": "missing_when",
|
"schema_id": "missing_when",
|
||||||
"action": "validate",
|
"action": "validate",
|
||||||
"expect": {
|
"expect": {
|
||||||
"success": false,
|
"success": false,
|
||||||
"errors": [
|
"errors": [
|
||||||
{ "code": "REQUIRED_FIELD_MISSING", "details": { "path": "unconditional" } }
|
{
|
||||||
|
"code": "REQUIRED_FIELD_MISSING",
|
||||||
|
"details": {
|
||||||
|
"path": "unconditional"
|
||||||
|
}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -2,12 +2,11 @@
|
|||||||
{
|
{
|
||||||
"description": "const validation",
|
"description": "const validation",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"const_0_0": {
|
||||||
"const": 2,
|
"const": 2
|
||||||
"$id": "const_0_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -42,8 +41,8 @@
|
|||||||
{
|
{
|
||||||
"description": "const with object",
|
"description": "const with object",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"const_1_0": {
|
||||||
"const": {
|
"const": {
|
||||||
"foo": "bar",
|
"foo": "bar",
|
||||||
"baz": "bax"
|
"baz": "bax"
|
||||||
@ -51,10 +50,9 @@
|
|||||||
"properties": {
|
"properties": {
|
||||||
"foo": {},
|
"foo": {},
|
||||||
"baz": {}
|
"baz": {}
|
||||||
},
|
}
|
||||||
"$id": "const_1_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -109,16 +107,15 @@
|
|||||||
{
|
{
|
||||||
"description": "const with array",
|
"description": "const with array",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"const_2_0": {
|
||||||
"const": [
|
"const": [
|
||||||
{
|
{
|
||||||
"foo": "bar"
|
"foo": "bar"
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
"$id": "const_2_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -163,12 +160,11 @@
|
|||||||
{
|
{
|
||||||
"description": "const with null",
|
"description": "const with null",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"const_3_0": {
|
||||||
"const": null,
|
"const": null
|
||||||
"$id": "const_3_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -194,12 +190,11 @@
|
|||||||
{
|
{
|
||||||
"description": "const with false does not match 0",
|
"description": "const with false does not match 0",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"const_4_0": {
|
||||||
"const": false,
|
"const": false
|
||||||
"$id": "const_4_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -234,12 +229,11 @@
|
|||||||
{
|
{
|
||||||
"description": "const with true does not match 1",
|
"description": "const with true does not match 1",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"const_5_0": {
|
||||||
"const": true,
|
"const": true
|
||||||
"$id": "const_5_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -274,14 +268,13 @@
|
|||||||
{
|
{
|
||||||
"description": "const with [false] does not match [0]",
|
"description": "const with [false] does not match [0]",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"const_6_0": {
|
||||||
"const": [
|
"const": [
|
||||||
false
|
false
|
||||||
],
|
]
|
||||||
"$id": "const_6_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -322,14 +315,13 @@
|
|||||||
{
|
{
|
||||||
"description": "const with [true] does not match [1]",
|
"description": "const with [true] does not match [1]",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"const_7_0": {
|
||||||
"const": [
|
"const": [
|
||||||
true
|
true
|
||||||
],
|
]
|
||||||
"$id": "const_7_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -370,14 +362,13 @@
|
|||||||
{
|
{
|
||||||
"description": "const with {\"a\": false} does not match {\"a\": 0}",
|
"description": "const with {\"a\": false} does not match {\"a\": 0}",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"const_8_0": {
|
||||||
"const": {
|
"const": {
|
||||||
"a": false
|
"a": false
|
||||||
},
|
}
|
||||||
"$id": "const_8_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -418,14 +409,13 @@
|
|||||||
{
|
{
|
||||||
"description": "const with {\"a\": true} does not match {\"a\": 1}",
|
"description": "const with {\"a\": true} does not match {\"a\": 1}",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"const_9_0": {
|
||||||
"const": {
|
"const": {
|
||||||
"a": true
|
"a": true
|
||||||
},
|
}
|
||||||
"$id": "const_9_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -466,12 +456,11 @@
|
|||||||
{
|
{
|
||||||
"description": "const with 0 does not match other zero-like types",
|
"description": "const with 0 does not match other zero-like types",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"const_10_0": {
|
||||||
"const": 0,
|
"const": 0
|
||||||
"$id": "const_10_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -533,12 +522,11 @@
|
|||||||
{
|
{
|
||||||
"description": "const with 1 does not match true",
|
"description": "const with 1 does not match true",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"const_11_0": {
|
||||||
"const": 1,
|
"const": 1
|
||||||
"$id": "const_11_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -573,12 +561,11 @@
|
|||||||
{
|
{
|
||||||
"description": "const with -2.0 matches integer and float types",
|
"description": "const with -2.0 matches integer and float types",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"const_12_0": {
|
||||||
"const": -2,
|
"const": -2
|
||||||
"$id": "const_12_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -631,12 +618,11 @@
|
|||||||
{
|
{
|
||||||
"description": "float and integers are equal up to 64-bit representation limits",
|
"description": "float and integers are equal up to 64-bit representation limits",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"const_13_0": {
|
||||||
"const": 9007199254740992,
|
"const": 9007199254740992
|
||||||
"$id": "const_13_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -680,12 +666,11 @@
|
|||||||
{
|
{
|
||||||
"description": "nul characters in strings",
|
"description": "nul characters in strings",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"const_14_0": {
|
||||||
"const": "hello\u0000there",
|
"const": "hello\u0000there"
|
||||||
"$id": "const_14_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -711,13 +696,12 @@
|
|||||||
{
|
{
|
||||||
"description": "characters with the same visual representation but different codepoint",
|
"description": "characters with the same visual representation but different codepoint",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"const_15_0": {
|
||||||
"const": "μ",
|
"const": "μ",
|
||||||
"$comment": "U+03BC",
|
"$comment": "U+03BC"
|
||||||
"$id": "const_15_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -745,13 +729,12 @@
|
|||||||
{
|
{
|
||||||
"description": "characters with the same visual representation, but different number of codepoints",
|
"description": "characters with the same visual representation, but different number of codepoints",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"const_16_0": {
|
||||||
"const": "ä",
|
"const": "ä",
|
||||||
"$comment": "U+00E4",
|
"$comment": "U+00E4"
|
||||||
"$id": "const_16_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -779,15 +762,14 @@
|
|||||||
{
|
{
|
||||||
"description": "extensible: true allows extra properties in const object match",
|
"description": "extensible: true allows extra properties in const object match",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"const_17_0": {
|
||||||
"const": {
|
"const": {
|
||||||
"a": 1
|
"a": 1
|
||||||
},
|
},
|
||||||
"extensible": true,
|
"extensible": true
|
||||||
"$id": "const_17_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
|
|||||||
@ -2,15 +2,14 @@
|
|||||||
{
|
{
|
||||||
"description": "contains keyword validation",
|
"description": "contains keyword validation",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"contains_0_0": {
|
||||||
"contains": {
|
"contains": {
|
||||||
"minimum": 5
|
"minimum": 5
|
||||||
},
|
},
|
||||||
"items": true,
|
"items": true
|
||||||
"$id": "contains_0_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -89,15 +88,14 @@
|
|||||||
{
|
{
|
||||||
"description": "contains keyword with const keyword",
|
"description": "contains keyword with const keyword",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"contains_1_0": {
|
||||||
"contains": {
|
"contains": {
|
||||||
"const": 5
|
"const": 5
|
||||||
},
|
},
|
||||||
"items": true,
|
"items": true
|
||||||
"$id": "contains_1_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -146,12 +144,11 @@
|
|||||||
{
|
{
|
||||||
"description": "contains keyword with boolean schema true",
|
"description": "contains keyword with boolean schema true",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"contains_2_0": {
|
||||||
"contains": true,
|
"contains": true
|
||||||
"$id": "contains_2_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -179,12 +176,11 @@
|
|||||||
{
|
{
|
||||||
"description": "contains keyword with boolean schema false",
|
"description": "contains keyword with boolean schema false",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"contains_3_0": {
|
||||||
"contains": false,
|
"contains": false
|
||||||
"$id": "contains_3_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -221,17 +217,16 @@
|
|||||||
{
|
{
|
||||||
"description": "items + contains",
|
"description": "items + contains",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"contains_4_0": {
|
||||||
"items": {
|
"items": {
|
||||||
"multipleOf": 2
|
"multipleOf": 2
|
||||||
},
|
},
|
||||||
"contains": {
|
"contains": {
|
||||||
"multipleOf": 3
|
"multipleOf": 3
|
||||||
},
|
}
|
||||||
"$id": "contains_4_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -289,15 +284,14 @@
|
|||||||
{
|
{
|
||||||
"description": "contains with false if subschema",
|
"description": "contains with false if subschema",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"contains_5_0": {
|
||||||
"contains": {
|
"contains": {
|
||||||
"if": false,
|
"if": false,
|
||||||
"else": true
|
"else": true
|
||||||
},
|
}
|
||||||
"$id": "contains_5_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -325,14 +319,13 @@
|
|||||||
{
|
{
|
||||||
"description": "contains with null instance elements",
|
"description": "contains with null instance elements",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"contains_6_0": {
|
||||||
"contains": {
|
"contains": {
|
||||||
"type": "null"
|
"type": "null"
|
||||||
},
|
}
|
||||||
"$id": "contains_6_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -351,15 +344,14 @@
|
|||||||
{
|
{
|
||||||
"description": "extensible: true allows non-matching items in contains",
|
"description": "extensible: true allows non-matching items in contains",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"contains_7_0": {
|
||||||
"contains": {
|
"contains": {
|
||||||
"const": 1
|
"const": 1
|
||||||
},
|
},
|
||||||
"extensible": true,
|
"extensible": true
|
||||||
"$id": "contains_7_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -379,14 +371,13 @@
|
|||||||
{
|
{
|
||||||
"description": "strict by default: non-matching items in contains are invalid",
|
"description": "strict by default: non-matching items in contains are invalid",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"contains_8_0": {
|
||||||
"contains": {
|
"contains": {
|
||||||
"const": 1
|
"const": 1
|
||||||
},
|
}
|
||||||
"$id": "contains_8_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
|
|||||||
@ -2,12 +2,11 @@
|
|||||||
{
|
{
|
||||||
"description": "validation of string-encoded content based on media type",
|
"description": "validation of string-encoded content based on media type",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"content_0_0": {
|
||||||
"contentMediaType": "application/json",
|
"contentMediaType": "application/json"
|
||||||
"$id": "content_0_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -42,12 +41,11 @@
|
|||||||
{
|
{
|
||||||
"description": "validation of binary string-encoding",
|
"description": "validation of binary string-encoding",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"content_1_0": {
|
||||||
"contentEncoding": "base64",
|
"contentEncoding": "base64"
|
||||||
"$id": "content_1_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -82,13 +80,12 @@
|
|||||||
{
|
{
|
||||||
"description": "validation of binary-encoded media type documents",
|
"description": "validation of binary-encoded media type documents",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"content_2_0": {
|
||||||
"contentMediaType": "application/json",
|
"contentMediaType": "application/json",
|
||||||
"contentEncoding": "base64",
|
"contentEncoding": "base64"
|
||||||
"$id": "content_2_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -132,8 +129,8 @@
|
|||||||
{
|
{
|
||||||
"description": "validation of binary-encoded media type documents with schema",
|
"description": "validation of binary-encoded media type documents with schema",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"content_3_0": {
|
||||||
"contentMediaType": "application/json",
|
"contentMediaType": "application/json",
|
||||||
"contentEncoding": "base64",
|
"contentEncoding": "base64",
|
||||||
"contentSchema": {
|
"contentSchema": {
|
||||||
@ -149,10 +146,9 @@
|
|||||||
"type": "integer"
|
"type": "integer"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
"$id": "content_3_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
|
|||||||
@ -15,9 +15,8 @@
|
|||||||
"variations": [
|
"variations": [
|
||||||
"org"
|
"org"
|
||||||
],
|
],
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"full.org": {
|
||||||
"$id": "full.org",
|
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"missing_users": {
|
"missing_users": {
|
||||||
@ -28,7 +27,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "22222222-2222-2222-2222-222222222222",
|
"id": "22222222-2222-2222-2222-222222222222",
|
||||||
@ -42,13 +41,12 @@
|
|||||||
"variations": [
|
"variations": [
|
||||||
"user"
|
"user"
|
||||||
],
|
],
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"full.user": {
|
||||||
"$id": "full.user",
|
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {}
|
"properties": {}
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"relations": []
|
"relations": []
|
||||||
@ -84,9 +82,8 @@
|
|||||||
"variations": [
|
"variations": [
|
||||||
"parent"
|
"parent"
|
||||||
],
|
],
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"full.parent": {
|
||||||
"$id": "full.parent",
|
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"children": {
|
"children": {
|
||||||
@ -97,7 +94,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "22222222-2222-2222-2222-222222222222",
|
"id": "22222222-2222-2222-2222-222222222222",
|
||||||
@ -111,13 +108,12 @@
|
|||||||
"variations": [
|
"variations": [
|
||||||
"child"
|
"child"
|
||||||
],
|
],
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"full.child": {
|
||||||
"$id": "full.child",
|
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {}
|
"properties": {}
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"relations": [
|
"relations": [
|
||||||
@ -167,9 +163,8 @@
|
|||||||
"variations": [
|
"variations": [
|
||||||
"invoice"
|
"invoice"
|
||||||
],
|
],
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"full.invoice": {
|
||||||
"$id": "full.invoice",
|
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"activities": {
|
"activities": {
|
||||||
@ -180,7 +175,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "22222222-2222-2222-2222-222222222222",
|
"id": "22222222-2222-2222-2222-222222222222",
|
||||||
@ -194,13 +189,12 @@
|
|||||||
"variations": [
|
"variations": [
|
||||||
"activity"
|
"activity"
|
||||||
],
|
],
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"full.activity": {
|
||||||
"$id": "full.activity",
|
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {}
|
"properties": {}
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"relations": [
|
"relations": [
|
||||||
@ -274,9 +268,8 @@
|
|||||||
"variations": [
|
"variations": [
|
||||||
"actor"
|
"actor"
|
||||||
],
|
],
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"full.actor": {
|
||||||
"$id": "full.actor",
|
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"ambiguous_edge": {
|
"ambiguous_edge": {
|
||||||
@ -287,7 +280,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "22222222-2222-2222-2222-222222222222",
|
"id": "22222222-2222-2222-2222-222222222222",
|
||||||
@ -301,13 +294,12 @@
|
|||||||
"variations": [
|
"variations": [
|
||||||
"junction"
|
"junction"
|
||||||
],
|
],
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"empty.junction": {
|
||||||
"$id": "empty.junction",
|
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {}
|
"properties": {}
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"relations": [
|
"relations": [
|
||||||
@ -406,5 +398,534 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Schema Promotion Accuracy Test",
|
||||||
|
"database": {
|
||||||
|
"puncs": [],
|
||||||
|
"enums": [],
|
||||||
|
"relations": [
|
||||||
|
{
|
||||||
|
"id": "r1",
|
||||||
|
"type": "relation",
|
||||||
|
"constraint": "fk_person_email",
|
||||||
|
"source_type": "person",
|
||||||
|
"source_columns": [
|
||||||
|
"email_id"
|
||||||
|
],
|
||||||
|
"destination_type": "email_address",
|
||||||
|
"destination_columns": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"prefix": "email"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "r2",
|
||||||
|
"type": "relation",
|
||||||
|
"constraint": "fk_person_ad_hoc_bubble",
|
||||||
|
"source_type": "person",
|
||||||
|
"source_columns": [
|
||||||
|
"ad_hoc_bubble_id"
|
||||||
|
],
|
||||||
|
"destination_type": "some_bubble",
|
||||||
|
"destination_columns": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"prefix": "ad_hoc_bubble"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "r3",
|
||||||
|
"type": "relation",
|
||||||
|
"constraint": "fk_person_generic_bubble",
|
||||||
|
"source_type": "person",
|
||||||
|
"source_columns": [
|
||||||
|
"generic_bubble_id"
|
||||||
|
],
|
||||||
|
"destination_type": "some_bubble",
|
||||||
|
"destination_columns": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"prefix": "generic_bubble"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "r4",
|
||||||
|
"type": "relation",
|
||||||
|
"constraint": "fk_person_extended_relations",
|
||||||
|
"source_type": "contact",
|
||||||
|
"source_columns": [
|
||||||
|
"source_id"
|
||||||
|
],
|
||||||
|
"destination_type": "person",
|
||||||
|
"destination_columns": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"prefix": "extended_relations"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "r5",
|
||||||
|
"type": "relation",
|
||||||
|
"constraint": "fk_person_standard_relations",
|
||||||
|
"source_type": "contact",
|
||||||
|
"source_columns": [
|
||||||
|
"source_id_2"
|
||||||
|
],
|
||||||
|
"destination_type": "person",
|
||||||
|
"destination_columns": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"prefix": "standard_relations"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "r6",
|
||||||
|
"type": "relation",
|
||||||
|
"constraint": "fk_contact_target",
|
||||||
|
"source_type": "contact",
|
||||||
|
"source_columns": [
|
||||||
|
"target_id"
|
||||||
|
],
|
||||||
|
"destination_type": "email_address",
|
||||||
|
"destination_columns": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"prefix": "target"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"types": [
|
||||||
|
{
|
||||||
|
"id": "t1",
|
||||||
|
"type": "type",
|
||||||
|
"name": "person",
|
||||||
|
"module": "core",
|
||||||
|
"source": "person",
|
||||||
|
"hierarchy": [
|
||||||
|
"person"
|
||||||
|
],
|
||||||
|
"variations": [
|
||||||
|
"person",
|
||||||
|
"student"
|
||||||
|
],
|
||||||
|
"schemas": {
|
||||||
|
"full.person": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"family": "email_address"
|
||||||
|
},
|
||||||
|
"generic_bubble": {
|
||||||
|
"type": "some_bubble"
|
||||||
|
},
|
||||||
|
"ad_hoc_bubble": {
|
||||||
|
"type": "some_bubble",
|
||||||
|
"properties": {
|
||||||
|
"extra_inline_feature": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"standard_relations": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "contact"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"extended_relations": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "contact",
|
||||||
|
"properties": {
|
||||||
|
"target": {
|
||||||
|
"type": "email_address",
|
||||||
|
"properties": {
|
||||||
|
"extra_3rd_level": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"student.person": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"kind": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"school": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "t2",
|
||||||
|
"type": "type",
|
||||||
|
"name": "email_address",
|
||||||
|
"module": "core",
|
||||||
|
"source": "email_address",
|
||||||
|
"hierarchy": [
|
||||||
|
"email_address"
|
||||||
|
],
|
||||||
|
"variations": [
|
||||||
|
"email_address"
|
||||||
|
],
|
||||||
|
"schemas": {
|
||||||
|
"light.email_address": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"address": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "t3",
|
||||||
|
"type": "type",
|
||||||
|
"name": "contact",
|
||||||
|
"module": "core",
|
||||||
|
"source": "contact",
|
||||||
|
"hierarchy": [
|
||||||
|
"contact"
|
||||||
|
],
|
||||||
|
"variations": [
|
||||||
|
"contact"
|
||||||
|
],
|
||||||
|
"schemas": {
|
||||||
|
"full.contact": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "t4",
|
||||||
|
"type": "type",
|
||||||
|
"name": "some_bubble",
|
||||||
|
"module": "core",
|
||||||
|
"source": "some_bubble",
|
||||||
|
"hierarchy": [
|
||||||
|
"some_bubble"
|
||||||
|
],
|
||||||
|
"variations": [
|
||||||
|
"some_bubble"
|
||||||
|
],
|
||||||
|
"schemas": {
|
||||||
|
"some_bubble": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"bubble_prop": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"tests": [
|
||||||
|
{
|
||||||
|
"description": "Assert exact topological schema promotion paths",
|
||||||
|
"action": "compile",
|
||||||
|
"expect": {
|
||||||
|
"success": true,
|
||||||
|
"schemas": [
|
||||||
|
"full.contact",
|
||||||
|
"full.person",
|
||||||
|
"full.person/ad_hoc_bubble",
|
||||||
|
"full.person/extended_relations",
|
||||||
|
"full.person/extended_relations/target",
|
||||||
|
"light.email_address",
|
||||||
|
"some_bubble",
|
||||||
|
"student.person"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "JSONB boundaries",
|
||||||
|
"database": {
|
||||||
|
"relations": [
|
||||||
|
{
|
||||||
|
"id": "33333333-3333-3333-3333-333333333333",
|
||||||
|
"type": "relation",
|
||||||
|
"constraint": "fk_invoice_line_invoice",
|
||||||
|
"source_type": "invoice_line",
|
||||||
|
"source_columns": [
|
||||||
|
"invoice_id"
|
||||||
|
],
|
||||||
|
"destination_type": "invoice",
|
||||||
|
"destination_columns": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"types": [
|
||||||
|
{
|
||||||
|
"name": "entity",
|
||||||
|
"hierarchy": [
|
||||||
|
"entity"
|
||||||
|
],
|
||||||
|
"grouped_fields": {
|
||||||
|
"entity": [
|
||||||
|
"id",
|
||||||
|
"type",
|
||||||
|
"archived",
|
||||||
|
"created_at"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"field_types": {
|
||||||
|
"id": "uuid",
|
||||||
|
"archived": "boolean",
|
||||||
|
"created_at": "timestamptz",
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
"schemas": {
|
||||||
|
"entity": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "uuid"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"archived": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time"
|
||||||
|
},
|
||||||
|
"created": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"fields": [
|
||||||
|
"id",
|
||||||
|
"type",
|
||||||
|
"archived",
|
||||||
|
"created_at"
|
||||||
|
],
|
||||||
|
"variations": [
|
||||||
|
"entity",
|
||||||
|
"invoice",
|
||||||
|
"invoice_line"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "invoice",
|
||||||
|
"schemas": {
|
||||||
|
"invoice": {
|
||||||
|
"type": "entity",
|
||||||
|
"properties": {
|
||||||
|
"total": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"lines": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "invoice_line"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"metadata_line": {
|
||||||
|
"type": "invoice_line"
|
||||||
|
},
|
||||||
|
"metadata_lines": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "invoice_line"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"metadata_nested_line": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"line": {
|
||||||
|
"type": "invoice_line"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"metadata_nested_lines": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"lines": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "invoice_line"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"hierarchy": [
|
||||||
|
"invoice",
|
||||||
|
"entity"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
"id",
|
||||||
|
"type",
|
||||||
|
"total",
|
||||||
|
"metadata_line",
|
||||||
|
"metadata_lines",
|
||||||
|
"metadata_nested_line",
|
||||||
|
"metadata_nested_lines",
|
||||||
|
"created_at",
|
||||||
|
"created_by",
|
||||||
|
"modified_at",
|
||||||
|
"modified_by",
|
||||||
|
"archived"
|
||||||
|
],
|
||||||
|
"grouped_fields": {
|
||||||
|
"invoice": [
|
||||||
|
"id",
|
||||||
|
"type",
|
||||||
|
"total",
|
||||||
|
"metadata_line",
|
||||||
|
"metadata_lines",
|
||||||
|
"metadata_nested_line",
|
||||||
|
"metadata_nested_lines"
|
||||||
|
],
|
||||||
|
"entity": [
|
||||||
|
"id",
|
||||||
|
"type",
|
||||||
|
"created_at",
|
||||||
|
"created_by",
|
||||||
|
"modified_at",
|
||||||
|
"modified_by",
|
||||||
|
"archived"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"lookup_fields": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"historical": true,
|
||||||
|
"relationship": false,
|
||||||
|
"field_types": {
|
||||||
|
"id": "uuid",
|
||||||
|
"type": "text",
|
||||||
|
"archived": "boolean",
|
||||||
|
"total": "numeric",
|
||||||
|
"metadata_line": "jsonb",
|
||||||
|
"metadata_lines": "jsonb",
|
||||||
|
"metadata_nested_line": "jsonb",
|
||||||
|
"metadata_nested_lines": "jsonb",
|
||||||
|
"created_at": "timestamptz",
|
||||||
|
"created_by": "uuid",
|
||||||
|
"modified_at": "timestamptz",
|
||||||
|
"modified_by": "uuid"
|
||||||
|
},
|
||||||
|
"variations": [
|
||||||
|
"invoice"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "invoice_line",
|
||||||
|
"schemas": {
|
||||||
|
"invoice_line": {
|
||||||
|
"type": "entity",
|
||||||
|
"properties": {
|
||||||
|
"invoice_id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"price": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"hierarchy": [
|
||||||
|
"invoice_line",
|
||||||
|
"entity"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
"id",
|
||||||
|
"type",
|
||||||
|
"invoice_id",
|
||||||
|
"price",
|
||||||
|
"created_at",
|
||||||
|
"created_by",
|
||||||
|
"modified_at",
|
||||||
|
"modified_by",
|
||||||
|
"archived"
|
||||||
|
],
|
||||||
|
"grouped_fields": {
|
||||||
|
"invoice_line": [
|
||||||
|
"id",
|
||||||
|
"type",
|
||||||
|
"invoice_id",
|
||||||
|
"price"
|
||||||
|
],
|
||||||
|
"entity": [
|
||||||
|
"id",
|
||||||
|
"type",
|
||||||
|
"created_at",
|
||||||
|
"created_by",
|
||||||
|
"modified_at",
|
||||||
|
"modified_by",
|
||||||
|
"archived"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"lookup_fields": [],
|
||||||
|
"historical": true,
|
||||||
|
"relationship": false,
|
||||||
|
"field_types": {
|
||||||
|
"id": "uuid",
|
||||||
|
"type": "text",
|
||||||
|
"archived": "boolean",
|
||||||
|
"invoice_id": "uuid",
|
||||||
|
"price": "numeric",
|
||||||
|
"created_at": "timestamptz",
|
||||||
|
"created_by": "uuid",
|
||||||
|
"modified_at": "timestamptz",
|
||||||
|
"modified_by": "uuid"
|
||||||
|
},
|
||||||
|
"variations": [
|
||||||
|
"invoice_line"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"tests": [
|
||||||
|
{
|
||||||
|
"description": "Assert no JSONB paths promoted",
|
||||||
|
"action": "compile",
|
||||||
|
"expect": {
|
||||||
|
"success": true,
|
||||||
|
"schemas": [
|
||||||
|
"entity",
|
||||||
|
"invoice",
|
||||||
|
"invoice_line"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -2,10 +2,9 @@
|
|||||||
{
|
{
|
||||||
"description": "single dependency (required)",
|
"description": "single dependency (required)",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"schema1": {
|
||||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
"$id": "schema1",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bar": [
|
"bar": [
|
||||||
"foo"
|
"foo"
|
||||||
@ -13,7 +12,7 @@
|
|||||||
},
|
},
|
||||||
"extensible": true
|
"extensible": true
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -93,16 +92,15 @@
|
|||||||
{
|
{
|
||||||
"description": "empty dependents",
|
"description": "empty dependents",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"schema2": {
|
||||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
"$id": "schema2",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bar": []
|
"bar": []
|
||||||
},
|
},
|
||||||
"extensible": true
|
"extensible": true
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -139,10 +137,9 @@
|
|||||||
{
|
{
|
||||||
"description": "multiple dependents required",
|
"description": "multiple dependents required",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"schema3": {
|
||||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
"$id": "schema3",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"quux": [
|
"quux": [
|
||||||
"foo",
|
"foo",
|
||||||
@ -151,7 +148,7 @@
|
|||||||
},
|
},
|
||||||
"extensible": true
|
"extensible": true
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -228,10 +225,9 @@
|
|||||||
{
|
{
|
||||||
"description": "dependencies with escaped characters",
|
"description": "dependencies with escaped characters",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"schema4": {
|
||||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
"$id": "schema4",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"foo\nbar": [
|
"foo\nbar": [
|
||||||
"foo\rbar"
|
"foo\rbar"
|
||||||
@ -242,7 +238,7 @@
|
|||||||
},
|
},
|
||||||
"extensible": true
|
"extensible": true
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -297,10 +293,9 @@
|
|||||||
{
|
{
|
||||||
"description": "extensible: true allows extra properties in dependentRequired",
|
"description": "extensible: true allows extra properties in dependentRequired",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"schema5": {
|
||||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
"$id": "schema5",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bar": [
|
"bar": [
|
||||||
"foo"
|
"foo"
|
||||||
@ -308,7 +303,7 @@
|
|||||||
},
|
},
|
||||||
"extensible": true
|
"extensible": true
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -329,10 +324,9 @@
|
|||||||
{
|
{
|
||||||
"description": "single dependency (schemas, STRICT)",
|
"description": "single dependency (schemas, STRICT)",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"schema_schema1": {
|
||||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
"$id": "schema_schema1",
|
|
||||||
"properties": {
|
"properties": {
|
||||||
"foo": true,
|
"foo": true,
|
||||||
"bar": true
|
"bar": true
|
||||||
@ -350,7 +344,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -451,10 +445,9 @@
|
|||||||
{
|
{
|
||||||
"description": "single dependency (schemas, EXTENSIBLE)",
|
"description": "single dependency (schemas, EXTENSIBLE)",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"schema_schema2": {
|
||||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
"$id": "schema_schema2",
|
|
||||||
"properties": {
|
"properties": {
|
||||||
"foo": true,
|
"foo": true,
|
||||||
"bar": true
|
"bar": true
|
||||||
@ -473,7 +466,7 @@
|
|||||||
},
|
},
|
||||||
"extensible": true
|
"extensible": true
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -492,10 +485,9 @@
|
|||||||
{
|
{
|
||||||
"description": "boolean subschemas",
|
"description": "boolean subschemas",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"schema_schema3": {
|
||||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
"$id": "schema_schema3",
|
|
||||||
"properties": {
|
"properties": {
|
||||||
"foo": true,
|
"foo": true,
|
||||||
"bar": true
|
"bar": true
|
||||||
@ -505,7 +497,7 @@
|
|||||||
"bar": false
|
"bar": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -556,10 +548,9 @@
|
|||||||
{
|
{
|
||||||
"description": "dependencies with escaped characters",
|
"description": "dependencies with escaped characters",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"schema_schema4": {
|
||||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
"$id": "schema_schema4",
|
|
||||||
"properties": {
|
"properties": {
|
||||||
"foo\tbar": true,
|
"foo\tbar": true,
|
||||||
"foo'bar": true,
|
"foo'bar": true,
|
||||||
@ -579,7 +570,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -637,10 +628,9 @@
|
|||||||
{
|
{
|
||||||
"description": "dependent subschema incompatible with root (STRICT)",
|
"description": "dependent subschema incompatible with root (STRICT)",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"schema_schema5": {
|
||||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
"$id": "schema_schema5",
|
|
||||||
"properties": {
|
"properties": {
|
||||||
"foo": {},
|
"foo": {},
|
||||||
"baz": true
|
"baz": true
|
||||||
@ -653,7 +643,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -711,10 +701,9 @@
|
|||||||
{
|
{
|
||||||
"description": "dependent subschema incompatible with root (EXTENSIBLE)",
|
"description": "dependent subschema incompatible with root (EXTENSIBLE)",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"schema_schema6": {
|
||||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
"$id": "schema_schema6",
|
|
||||||
"properties": {
|
"properties": {
|
||||||
"foo": {},
|
"foo": {},
|
||||||
"baz": true
|
"baz": true
|
||||||
@ -729,7 +718,7 @@
|
|||||||
},
|
},
|
||||||
"extensible": true
|
"extensible": true
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
|
|||||||
@ -2,8 +2,8 @@
|
|||||||
{
|
{
|
||||||
"description": "empty string is valid for all types (except const)",
|
"description": "empty string is valid for all types (except const)",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"emptyString_0_0": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"obj": {
|
"obj": {
|
||||||
"type": "object"
|
"type": "object"
|
||||||
@ -36,10 +36,9 @@
|
|||||||
"con_empty": {
|
"con_empty": {
|
||||||
"const": ""
|
"const": ""
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
"$id": "emptyString_0_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -142,7 +141,9 @@
|
|||||||
"errors": [
|
"errors": [
|
||||||
{
|
{
|
||||||
"code": "CONST_VIOLATED",
|
"code": "CONST_VIOLATED",
|
||||||
"details": { "path": "con" }
|
"details": {
|
||||||
|
"path": "con"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,16 +2,15 @@
|
|||||||
{
|
{
|
||||||
"description": "simple enum validation",
|
"description": "simple enum validation",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"enum_0_0": {
|
||||||
"enum": [
|
"enum": [
|
||||||
1,
|
1,
|
||||||
2,
|
2,
|
||||||
3
|
3
|
||||||
],
|
]
|
||||||
"$id": "enum_0_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -37,8 +36,8 @@
|
|||||||
{
|
{
|
||||||
"description": "heterogeneous enum validation",
|
"description": "heterogeneous enum validation",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"enum_1_0": {
|
||||||
"enum": [
|
"enum": [
|
||||||
6,
|
6,
|
||||||
"foo",
|
"foo",
|
||||||
@ -50,10 +49,9 @@
|
|||||||
],
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"foo": {}
|
"foo": {}
|
||||||
},
|
}
|
||||||
"$id": "enum_1_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -113,15 +111,14 @@
|
|||||||
{
|
{
|
||||||
"description": "heterogeneous enum-with-null validation",
|
"description": "heterogeneous enum-with-null validation",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"enum_2_0": {
|
||||||
"enum": [
|
"enum": [
|
||||||
6,
|
6,
|
||||||
null
|
null
|
||||||
],
|
]
|
||||||
"$id": "enum_2_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -156,8 +153,8 @@
|
|||||||
{
|
{
|
||||||
"description": "enums in properties",
|
"description": "enums in properties",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"enum_3_0": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"foo": {
|
"foo": {
|
||||||
@ -173,10 +170,9 @@
|
|||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"bar"
|
"bar"
|
||||||
],
|
]
|
||||||
"$id": "enum_3_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -251,15 +247,14 @@
|
|||||||
{
|
{
|
||||||
"description": "enum with escaped characters",
|
"description": "enum with escaped characters",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"enum_4_0": {
|
||||||
"enum": [
|
"enum": [
|
||||||
"foo\nbar",
|
"foo\nbar",
|
||||||
"foo\rbar"
|
"foo\rbar"
|
||||||
],
|
]
|
||||||
"$id": "enum_4_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -294,14 +289,13 @@
|
|||||||
{
|
{
|
||||||
"description": "enum with false does not match 0",
|
"description": "enum with false does not match 0",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"enum_5_0": {
|
||||||
"enum": [
|
"enum": [
|
||||||
false
|
false
|
||||||
],
|
]
|
||||||
"$id": "enum_5_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -336,16 +330,15 @@
|
|||||||
{
|
{
|
||||||
"description": "enum with [false] does not match [0]",
|
"description": "enum with [false] does not match [0]",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"enum_6_0": {
|
||||||
"enum": [
|
"enum": [
|
||||||
[
|
[
|
||||||
false
|
false
|
||||||
]
|
]
|
||||||
],
|
]
|
||||||
"$id": "enum_6_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -386,14 +379,13 @@
|
|||||||
{
|
{
|
||||||
"description": "enum with true does not match 1",
|
"description": "enum with true does not match 1",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"enum_7_0": {
|
||||||
"enum": [
|
"enum": [
|
||||||
true
|
true
|
||||||
],
|
]
|
||||||
"$id": "enum_7_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -428,16 +420,15 @@
|
|||||||
{
|
{
|
||||||
"description": "enum with [true] does not match [1]",
|
"description": "enum with [true] does not match [1]",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"enum_8_0": {
|
||||||
"enum": [
|
"enum": [
|
||||||
[
|
[
|
||||||
true
|
true
|
||||||
]
|
]
|
||||||
],
|
]
|
||||||
"$id": "enum_8_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -478,14 +469,13 @@
|
|||||||
{
|
{
|
||||||
"description": "enum with 0 does not match false",
|
"description": "enum with 0 does not match false",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"enum_9_0": {
|
||||||
"enum": [
|
"enum": [
|
||||||
0
|
0
|
||||||
],
|
]
|
||||||
"$id": "enum_9_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -520,16 +510,15 @@
|
|||||||
{
|
{
|
||||||
"description": "enum with [0] does not match [false]",
|
"description": "enum with [0] does not match [false]",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"enum_10_0": {
|
||||||
"enum": [
|
"enum": [
|
||||||
[
|
[
|
||||||
0
|
0
|
||||||
]
|
]
|
||||||
],
|
]
|
||||||
"$id": "enum_10_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -570,14 +559,13 @@
|
|||||||
{
|
{
|
||||||
"description": "enum with 1 does not match true",
|
"description": "enum with 1 does not match true",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"enum_11_0": {
|
||||||
"enum": [
|
"enum": [
|
||||||
1
|
1
|
||||||
],
|
]
|
||||||
"$id": "enum_11_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -612,16 +600,15 @@
|
|||||||
{
|
{
|
||||||
"description": "enum with [1] does not match [true]",
|
"description": "enum with [1] does not match [true]",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"enum_12_0": {
|
||||||
"enum": [
|
"enum": [
|
||||||
[
|
[
|
||||||
1
|
1
|
||||||
]
|
]
|
||||||
],
|
]
|
||||||
"$id": "enum_12_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -662,14 +649,13 @@
|
|||||||
{
|
{
|
||||||
"description": "nul characters in strings",
|
"description": "nul characters in strings",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"enum_13_0": {
|
||||||
"enum": [
|
"enum": [
|
||||||
"hello\u0000there"
|
"hello\u0000there"
|
||||||
],
|
]
|
||||||
"$id": "enum_13_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -695,17 +681,16 @@
|
|||||||
{
|
{
|
||||||
"description": "extensible: true allows extra properties in enum object match",
|
"description": "extensible: true allows extra properties in enum object match",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"enum_14_0": {
|
||||||
"enum": [
|
"enum": [
|
||||||
{
|
{
|
||||||
"foo": 1
|
"foo": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"extensible": true,
|
"extensible": true
|
||||||
"$id": "enum_14_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
|
|||||||
@ -2,12 +2,11 @@
|
|||||||
{
|
{
|
||||||
"description": "exclusiveMaximum validation",
|
"description": "exclusiveMaximum validation",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"exclusiveMaximum_0_0": {
|
||||||
"exclusiveMaximum": 3,
|
"exclusiveMaximum": 3
|
||||||
"$id": "exclusiveMaximum_0_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
|
|||||||
@ -2,12 +2,11 @@
|
|||||||
{
|
{
|
||||||
"description": "exclusiveMinimum validation",
|
"description": "exclusiveMinimum validation",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"exclusiveMinimum_0_0": {
|
||||||
"exclusiveMinimum": 1.1,
|
"exclusiveMinimum": 1.1
|
||||||
"$id": "exclusiveMinimum_0_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
|
|||||||
@ -2,12 +2,11 @@
|
|||||||
{
|
{
|
||||||
"description": "validation of date-time strings",
|
"description": "validation of date-time strings",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"format_0_0": {
|
||||||
"format": "date-time",
|
"format": "date-time"
|
||||||
"$id": "format_0_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -249,12 +248,11 @@
|
|||||||
{
|
{
|
||||||
"description": "validation of date strings",
|
"description": "validation of date strings",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"format_1_0": {
|
||||||
"format": "date",
|
"format": "date"
|
||||||
"$id": "format_1_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -694,12 +692,11 @@
|
|||||||
{
|
{
|
||||||
"description": "validation of duration strings",
|
"description": "validation of duration strings",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"format_2_0": {
|
||||||
"format": "duration",
|
"format": "duration"
|
||||||
"$id": "format_2_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -941,12 +938,11 @@
|
|||||||
{
|
{
|
||||||
"description": "\\a is not an ECMA 262 control escape",
|
"description": "\\a is not an ECMA 262 control escape",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"format_3_0": {
|
||||||
"format": "regex",
|
"format": "regex"
|
||||||
"$id": "format_3_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -963,12 +959,11 @@
|
|||||||
{
|
{
|
||||||
"description": "validation of e-mail addresses",
|
"description": "validation of e-mail addresses",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"format_4_0": {
|
||||||
"format": "email",
|
"format": "email"
|
||||||
"$id": "format_4_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -1192,12 +1187,11 @@
|
|||||||
{
|
{
|
||||||
"description": "validation of host names",
|
"description": "validation of host names",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"format_5_0": {
|
||||||
"format": "hostname",
|
"format": "hostname"
|
||||||
"$id": "format_5_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -1422,12 +1416,11 @@
|
|||||||
{
|
{
|
||||||
"description": "validation of A-label (punycode) host names",
|
"description": "validation of A-label (punycode) host names",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"format_6_0": {
|
||||||
"format": "hostname",
|
"format": "hostname"
|
||||||
"$id": "format_6_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -1803,12 +1796,11 @@
|
|||||||
{
|
{
|
||||||
"description": "validation of an internationalized e-mail addresses",
|
"description": "validation of an internationalized e-mail addresses",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"format_7_0": {
|
||||||
"format": "idn-email",
|
"format": "idn-email"
|
||||||
"$id": "format_7_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -1906,12 +1898,11 @@
|
|||||||
{
|
{
|
||||||
"description": "validation of internationalized host names",
|
"description": "validation of internationalized host names",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"format_8_0": {
|
||||||
"format": "idn-hostname",
|
"format": "idn-hostname"
|
||||||
"$id": "format_8_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -2479,12 +2470,11 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"format_9_0": {
|
||||||
"format": "idn-hostname",
|
"format": "idn-hostname"
|
||||||
"$id": "format_9_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -2672,12 +2662,11 @@
|
|||||||
{
|
{
|
||||||
"description": "validation of IP addresses",
|
"description": "validation of IP addresses",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"format_10_0": {
|
||||||
"format": "ipv4",
|
"format": "ipv4"
|
||||||
"$id": "format_10_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -2830,12 +2819,11 @@
|
|||||||
{
|
{
|
||||||
"description": "validation of IPv6 addresses",
|
"description": "validation of IPv6 addresses",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"format_11_0": {
|
||||||
"format": "ipv6",
|
"format": "ipv6"
|
||||||
"$id": "format_11_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -3203,12 +3191,11 @@
|
|||||||
{
|
{
|
||||||
"description": "validation of IRI References",
|
"description": "validation of IRI References",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"format_12_0": {
|
||||||
"format": "iri-reference",
|
"format": "iri-reference"
|
||||||
"$id": "format_12_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -3333,12 +3320,11 @@
|
|||||||
{
|
{
|
||||||
"description": "validation of IRIs",
|
"description": "validation of IRIs",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"format_13_0": {
|
||||||
"format": "iri",
|
"format": "iri"
|
||||||
"$id": "format_13_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -3481,12 +3467,11 @@
|
|||||||
{
|
{
|
||||||
"description": "validation of JSON-pointers (JSON String Representation)",
|
"description": "validation of JSON-pointers (JSON String Representation)",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"format_14_0": {
|
||||||
"format": "json-pointer",
|
"format": "json-pointer"
|
||||||
"$id": "format_14_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -3836,12 +3821,11 @@
|
|||||||
{
|
{
|
||||||
"description": "validation of regular expressions",
|
"description": "validation of regular expressions",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"format_15_0": {
|
||||||
"format": "regex",
|
"format": "regex"
|
||||||
"$id": "format_15_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -3921,12 +3905,11 @@
|
|||||||
{
|
{
|
||||||
"description": "validation of Relative JSON Pointers (RJP)",
|
"description": "validation of Relative JSON Pointers (RJP)",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"format_16_0": {
|
||||||
"format": "relative-json-pointer",
|
"format": "relative-json-pointer"
|
||||||
"$id": "format_16_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -4096,12 +4079,11 @@
|
|||||||
{
|
{
|
||||||
"description": "validation of time strings",
|
"description": "validation of time strings",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"format_17_0": {
|
||||||
"format": "time",
|
"format": "time"
|
||||||
"$id": "format_17_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -4523,12 +4505,11 @@
|
|||||||
{
|
{
|
||||||
"description": "unknown format",
|
"description": "unknown format",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"format_18_0": {
|
||||||
"format": "unknown",
|
"format": "unknown"
|
||||||
"$id": "format_18_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -4599,12 +4580,11 @@
|
|||||||
{
|
{
|
||||||
"description": "validation of URI References",
|
"description": "validation of URI References",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"format_19_0": {
|
||||||
"format": "uri-reference",
|
"format": "uri-reference"
|
||||||
"$id": "format_19_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -4747,12 +4727,11 @@
|
|||||||
{
|
{
|
||||||
"description": "format: uri-template",
|
"description": "format: uri-template",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"format_20_0": {
|
||||||
"format": "uri-template",
|
"format": "uri-template"
|
||||||
"$id": "format_20_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -4850,12 +4829,11 @@
|
|||||||
{
|
{
|
||||||
"description": "validation of URIs",
|
"description": "validation of URIs",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"format_21_0": {
|
||||||
"format": "uri",
|
"format": "uri"
|
||||||
"$id": "format_21_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -5187,12 +5165,11 @@
|
|||||||
{
|
{
|
||||||
"description": "uuid format",
|
"description": "uuid format",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"format_22_0": {
|
||||||
"format": "uuid",
|
"format": "uuid"
|
||||||
"$id": "format_22_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -5398,12 +5375,11 @@
|
|||||||
{
|
{
|
||||||
"description": "period format",
|
"description": "period format",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"format_23_0": {
|
||||||
"format": "period",
|
"format": "period"
|
||||||
"$id": "format_23_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
|
|||||||
@ -5,15 +5,18 @@
|
|||||||
"puncs": [
|
"puncs": [
|
||||||
{
|
{
|
||||||
"name": "get_invoice",
|
"name": "get_invoice",
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"get_invoice.response": {
|
||||||
"$id": "get_invoice.response",
|
|
||||||
"oneOf": [
|
"oneOf": [
|
||||||
{ "type": "invoice" },
|
{
|
||||||
{ "type": "null" }
|
"type": "invoice"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "null"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"enums": [],
|
"enums": [],
|
||||||
@ -23,32 +26,64 @@
|
|||||||
"type": "relation",
|
"type": "relation",
|
||||||
"constraint": "fk_attachment_attachable_entity",
|
"constraint": "fk_attachment_attachable_entity",
|
||||||
"source_type": "attachment",
|
"source_type": "attachment",
|
||||||
"source_columns": ["attachable_id", "attachable_type"],
|
"source_columns": [
|
||||||
|
"attachable_id",
|
||||||
|
"attachable_type"
|
||||||
|
],
|
||||||
"destination_type": "entity",
|
"destination_type": "entity",
|
||||||
"destination_columns": ["id", "type"],
|
"destination_columns": [
|
||||||
|
"id",
|
||||||
|
"type"
|
||||||
|
],
|
||||||
"prefix": null
|
"prefix": null
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"types": [
|
"types": [
|
||||||
{
|
{
|
||||||
"name": "entity",
|
"name": "entity",
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"entity": {
|
||||||
"$id": "entity",
|
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"id": { "type": "string", "format": "uuid" },
|
"id": {
|
||||||
"type": { "type": "string" },
|
"type": "string",
|
||||||
"archived": { "type": "boolean" },
|
"format": "uuid"
|
||||||
"created_at": { "type": "string", "format": "date-time" }
|
},
|
||||||
|
"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": {
|
"grouped_fields": {
|
||||||
"entity": ["id", "type", "archived", "created_at"]
|
"entity": [
|
||||||
|
"id",
|
||||||
|
"type",
|
||||||
|
"archived",
|
||||||
|
"created_at"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"field_types": {
|
"field_types": {
|
||||||
"id": "uuid",
|
"id": "uuid",
|
||||||
@ -63,21 +98,42 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "activity",
|
"name": "activity",
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"activity": {
|
||||||
"$id": "activity",
|
|
||||||
"type": "entity",
|
"type": "entity",
|
||||||
"properties": {
|
"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": {
|
"grouped_fields": {
|
||||||
"entity": ["id", "type", "archived", "created_at"],
|
"entity": [
|
||||||
"activity": ["start_date"]
|
"id",
|
||||||
|
"type",
|
||||||
|
"archived",
|
||||||
|
"created_at"
|
||||||
|
],
|
||||||
|
"activity": [
|
||||||
|
"start_date"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"field_types": {
|
"field_types": {
|
||||||
"id": "uuid",
|
"id": "uuid",
|
||||||
@ -93,26 +149,51 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "invoice",
|
"name": "invoice",
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"invoice": {
|
||||||
"$id": "invoice",
|
|
||||||
"type": "activity",
|
"type": "activity",
|
||||||
"properties": {
|
"properties": {
|
||||||
"status": { "type": "string" },
|
"status": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"attachments": {
|
"attachments": {
|
||||||
"type": "array",
|
"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": {
|
"grouped_fields": {
|
||||||
"entity": ["id", "type", "archived", "created_at"],
|
"entity": [
|
||||||
"activity": ["start_date"],
|
"id",
|
||||||
"invoice": ["status"]
|
"type",
|
||||||
|
"archived",
|
||||||
|
"created_at"
|
||||||
|
],
|
||||||
|
"activity": [
|
||||||
|
"start_date"
|
||||||
|
],
|
||||||
|
"invoice": [
|
||||||
|
"status"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"field_types": {
|
"field_types": {
|
||||||
"id": "uuid",
|
"id": "uuid",
|
||||||
@ -129,26 +210,66 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "attachment",
|
"name": "attachment",
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"attachment": {
|
||||||
"$id": "attachment",
|
|
||||||
"type": "entity",
|
"type": "entity",
|
||||||
"properties": {
|
"properties": {
|
||||||
"name": { "type": "string" },
|
"name": {
|
||||||
"attachable_id": { "type": "string", "format": "uuid" },
|
"type": "string"
|
||||||
"attachable_type": { "type": "string" },
|
},
|
||||||
"kind": { "type": "string" },
|
"attachable_id": {
|
||||||
"file": { "type": "string" },
|
"type": "string",
|
||||||
"data": { "type": "object" }
|
"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": {
|
"grouped_fields": {
|
||||||
"entity": ["id", "type", "archived", "created_at"],
|
"entity": [
|
||||||
"attachment": ["attachable_id", "attachable_type", "kind", "file", "data", "name"]
|
"id",
|
||||||
|
"type",
|
||||||
|
"archived",
|
||||||
|
"created_at"
|
||||||
|
],
|
||||||
|
"attachment": [
|
||||||
|
"attachable_id",
|
||||||
|
"attachable_type",
|
||||||
|
"kind",
|
||||||
|
"file",
|
||||||
|
"data",
|
||||||
|
"name"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"field_types": {
|
"field_types": {
|
||||||
"id": "uuid",
|
"id": "uuid",
|
||||||
|
|||||||
@ -2,14 +2,13 @@
|
|||||||
{
|
{
|
||||||
"description": "a schema given for items",
|
"description": "a schema given for items",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"items_0_0": {
|
||||||
"items": {
|
"items": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
},
|
}
|
||||||
"$id": "items_0_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -65,12 +64,11 @@
|
|||||||
{
|
{
|
||||||
"description": "items with boolean schema (true)",
|
"description": "items with boolean schema (true)",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"items_1_0": {
|
||||||
"items": true,
|
"items": true
|
||||||
"$id": "items_1_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -100,12 +98,11 @@
|
|||||||
{
|
{
|
||||||
"description": "items with boolean schema (false)",
|
"description": "items with boolean schema (false)",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"items_2_0": {
|
||||||
"items": false,
|
"items": false
|
||||||
"$id": "items_2_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -135,8 +132,8 @@
|
|||||||
{
|
{
|
||||||
"description": "items and subitems",
|
"description": "items and subitems",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"items_3_0": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": false,
|
"items": false,
|
||||||
"prefixItems": [
|
"prefixItems": [
|
||||||
@ -149,11 +146,9 @@
|
|||||||
{
|
{
|
||||||
"type": "item"
|
"type": "item"
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
"$id": "items_3_0"
|
|
||||||
},
|
},
|
||||||
{
|
"item": {
|
||||||
"$id": "item",
|
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": false,
|
"items": false,
|
||||||
"prefixItems": [
|
"prefixItems": [
|
||||||
@ -165,14 +160,13 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
"sub-item": {
|
||||||
"$id": "sub-item",
|
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
"foo"
|
"foo"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -374,8 +368,8 @@
|
|||||||
{
|
{
|
||||||
"description": "nested items",
|
"description": "nested items",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"items_4_0": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
@ -388,10 +382,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
"$id": "items_4_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -507,17 +500,16 @@
|
|||||||
{
|
{
|
||||||
"description": "prefixItems with no additional items allowed",
|
"description": "prefixItems with no additional items allowed",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"items_5_0": {
|
||||||
"prefixItems": [
|
"prefixItems": [
|
||||||
{},
|
{},
|
||||||
{},
|
{},
|
||||||
{}
|
{}
|
||||||
],
|
],
|
||||||
"items": false,
|
"items": false
|
||||||
"$id": "items_5_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -584,8 +576,8 @@
|
|||||||
{
|
{
|
||||||
"description": "items does not look in applicators, valid case",
|
"description": "items does not look in applicators, valid case",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"items_6_0": {
|
||||||
"allOf": [
|
"allOf": [
|
||||||
{
|
{
|
||||||
"prefixItems": [
|
"prefixItems": [
|
||||||
@ -597,10 +589,9 @@
|
|||||||
],
|
],
|
||||||
"items": {
|
"items": {
|
||||||
"minimum": 5
|
"minimum": 5
|
||||||
},
|
}
|
||||||
"$id": "items_6_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -632,8 +623,8 @@
|
|||||||
{
|
{
|
||||||
"description": "prefixItems validation adjusts the starting index for items",
|
"description": "prefixItems validation adjusts the starting index for items",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"items_7_0": {
|
||||||
"prefixItems": [
|
"prefixItems": [
|
||||||
{
|
{
|
||||||
"type": "string"
|
"type": "string"
|
||||||
@ -641,10 +632,9 @@
|
|||||||
],
|
],
|
||||||
"items": {
|
"items": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
},
|
}
|
||||||
"$id": "items_7_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -677,15 +667,14 @@
|
|||||||
{
|
{
|
||||||
"description": "items with heterogeneous array",
|
"description": "items with heterogeneous array",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"items_8_0": {
|
||||||
"prefixItems": [
|
"prefixItems": [
|
||||||
{}
|
{}
|
||||||
],
|
],
|
||||||
"items": false,
|
"items": false
|
||||||
"$id": "items_8_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -717,14 +706,13 @@
|
|||||||
{
|
{
|
||||||
"description": "items with null instance elements",
|
"description": "items with null instance elements",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"items_9_0": {
|
||||||
"items": {
|
"items": {
|
||||||
"type": "null"
|
"type": "null"
|
||||||
},
|
}
|
||||||
"$id": "items_9_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -743,13 +731,12 @@
|
|||||||
{
|
{
|
||||||
"description": "extensible: true allows extra items (when items is false)",
|
"description": "extensible: true allows extra items (when items is false)",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"items_10_0": {
|
||||||
"items": false,
|
"items": false,
|
||||||
"extensible": true,
|
"extensible": true
|
||||||
"$id": "items_10_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -768,15 +755,14 @@
|
|||||||
{
|
{
|
||||||
"description": "extensible: true allows extra properties for items",
|
"description": "extensible: true allows extra properties for items",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"items_11_0": {
|
||||||
"items": {
|
"items": {
|
||||||
"minimum": 5
|
"minimum": 5
|
||||||
},
|
},
|
||||||
"extensible": true,
|
"extensible": true
|
||||||
"$id": "items_11_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -807,13 +793,12 @@
|
|||||||
{
|
{
|
||||||
"description": "array: simple extensible array",
|
"description": "array: simple extensible array",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"items_12_0": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"extensible": true,
|
"extensible": true
|
||||||
"$id": "items_12_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -842,13 +827,12 @@
|
|||||||
{
|
{
|
||||||
"description": "array: strict array",
|
"description": "array: strict array",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"items_13_0": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"extensible": false,
|
"extensible": false
|
||||||
"$id": "items_13_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -876,15 +860,14 @@
|
|||||||
{
|
{
|
||||||
"description": "array: items extensible",
|
"description": "array: items extensible",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"items_14_0": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"extensible": true
|
"extensible": true
|
||||||
},
|
}
|
||||||
"$id": "items_14_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -914,16 +897,15 @@
|
|||||||
{
|
{
|
||||||
"description": "array: items strict",
|
"description": "array: items strict",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"items_15_0": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"extensible": false
|
"extensible": false
|
||||||
},
|
}
|
||||||
"$id": "items_15_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
|
|||||||
@ -2,13 +2,12 @@
|
|||||||
{
|
{
|
||||||
"description": "maxContains without contains is ignored",
|
"description": "maxContains without contains is ignored",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"maxContains_0_0": {
|
||||||
"maxContains": 1,
|
"maxContains": 1,
|
||||||
"extensible": true,
|
"extensible": true
|
||||||
"$id": "maxContains_0_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -39,16 +38,15 @@
|
|||||||
{
|
{
|
||||||
"description": "maxContains with contains",
|
"description": "maxContains with contains",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"maxContains_1_0": {
|
||||||
"contains": {
|
"contains": {
|
||||||
"const": 1
|
"const": 1
|
||||||
},
|
},
|
||||||
"maxContains": 1,
|
"maxContains": 1,
|
||||||
"extensible": true,
|
"extensible": true
|
||||||
"$id": "maxContains_1_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -113,16 +111,15 @@
|
|||||||
{
|
{
|
||||||
"description": "maxContains with contains, value with a decimal",
|
"description": "maxContains with contains, value with a decimal",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"maxContains_2_0": {
|
||||||
"contains": {
|
"contains": {
|
||||||
"const": 1
|
"const": 1
|
||||||
},
|
},
|
||||||
"maxContains": 1,
|
"maxContains": 1,
|
||||||
"extensible": true,
|
"extensible": true
|
||||||
"$id": "maxContains_2_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -153,17 +150,16 @@
|
|||||||
{
|
{
|
||||||
"description": "minContains < maxContains",
|
"description": "minContains < maxContains",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"maxContains_3_0": {
|
||||||
"contains": {
|
"contains": {
|
||||||
"const": 1
|
"const": 1
|
||||||
},
|
},
|
||||||
"minContains": 1,
|
"minContains": 1,
|
||||||
"maxContains": 3,
|
"maxContains": 3,
|
||||||
"extensible": true,
|
"extensible": true
|
||||||
"$id": "maxContains_3_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -206,16 +202,15 @@
|
|||||||
{
|
{
|
||||||
"description": "extensible: true allows non-matching items in maxContains",
|
"description": "extensible: true allows non-matching items in maxContains",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"maxContains_4_0": {
|
||||||
"contains": {
|
"contains": {
|
||||||
"const": 1
|
"const": 1
|
||||||
},
|
},
|
||||||
"maxContains": 1,
|
"maxContains": 1,
|
||||||
"extensible": true,
|
"extensible": true
|
||||||
"$id": "maxContains_4_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
|
|||||||
@ -2,13 +2,12 @@
|
|||||||
{
|
{
|
||||||
"description": "maxItems validation",
|
"description": "maxItems validation",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"maxItems_0_0": {
|
||||||
"maxItems": 2,
|
"maxItems": 2,
|
||||||
"extensible": true,
|
"extensible": true
|
||||||
"$id": "maxItems_0_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -61,13 +60,12 @@
|
|||||||
{
|
{
|
||||||
"description": "maxItems validation with a decimal",
|
"description": "maxItems validation with a decimal",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"maxItems_1_0": {
|
||||||
"maxItems": 2,
|
"maxItems": 2,
|
||||||
"extensible": true,
|
"extensible": true
|
||||||
"$id": "maxItems_1_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -99,13 +97,12 @@
|
|||||||
{
|
{
|
||||||
"description": "extensible: true allows extra items in maxItems (but counted)",
|
"description": "extensible: true allows extra items in maxItems (but counted)",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"maxItems_2_0": {
|
||||||
"maxItems": 2,
|
"maxItems": 2,
|
||||||
"extensible": true,
|
"extensible": true
|
||||||
"$id": "maxItems_2_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
|
|||||||
@ -2,12 +2,11 @@
|
|||||||
{
|
{
|
||||||
"description": "maxLength validation",
|
"description": "maxLength validation",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"maxLength_0_0": {
|
||||||
"maxLength": 2,
|
"maxLength": 2
|
||||||
"$id": "maxLength_0_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -60,12 +59,11 @@
|
|||||||
{
|
{
|
||||||
"description": "maxLength validation with a decimal",
|
"description": "maxLength validation with a decimal",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"maxLength_1_0": {
|
||||||
"maxLength": 2,
|
"maxLength": 2
|
||||||
"$id": "maxLength_1_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
|
|||||||
@ -2,13 +2,12 @@
|
|||||||
{
|
{
|
||||||
"description": "maxProperties validation",
|
"description": "maxProperties validation",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"maxProperties_0_0": {
|
||||||
"maxProperties": 2,
|
"maxProperties": 2,
|
||||||
"extensible": true,
|
"extensible": true
|
||||||
"$id": "maxProperties_0_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -83,13 +82,12 @@
|
|||||||
{
|
{
|
||||||
"description": "maxProperties validation with a decimal",
|
"description": "maxProperties validation with a decimal",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"maxProperties_1_0": {
|
||||||
"maxProperties": 2,
|
"maxProperties": 2,
|
||||||
"extensible": true,
|
"extensible": true
|
||||||
"$id": "maxProperties_1_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -121,13 +119,12 @@
|
|||||||
{
|
{
|
||||||
"description": "maxProperties = 0 means the object is empty",
|
"description": "maxProperties = 0 means the object is empty",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"maxProperties_2_0": {
|
||||||
"maxProperties": 0,
|
"maxProperties": 0,
|
||||||
"extensible": true,
|
"extensible": true
|
||||||
"$id": "maxProperties_2_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -155,13 +152,12 @@
|
|||||||
{
|
{
|
||||||
"description": "extensible: true allows extra properties in maxProperties (though maxProperties still counts them!)",
|
"description": "extensible: true allows extra properties in maxProperties (though maxProperties still counts them!)",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"maxProperties_3_0": {
|
||||||
"maxProperties": 2,
|
"maxProperties": 2,
|
||||||
"extensible": true,
|
"extensible": true
|
||||||
"$id": "maxProperties_3_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
|
|||||||
@ -2,12 +2,11 @@
|
|||||||
{
|
{
|
||||||
"description": "maximum validation",
|
"description": "maximum validation",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"maximum_0_0": {
|
||||||
"maximum": 3,
|
"maximum": 3
|
||||||
"$id": "maximum_0_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -51,12 +50,11 @@
|
|||||||
{
|
{
|
||||||
"description": "maximum validation with unsigned integer",
|
"description": "maximum validation with unsigned integer",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"maximum_1_0": {
|
||||||
"maximum": 300,
|
"maximum": 300
|
||||||
"$id": "maximum_1_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
|
|||||||
@ -2,25 +2,23 @@
|
|||||||
{
|
{
|
||||||
"description": "merging: properties accumulate",
|
"description": "merging: properties accumulate",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"base_0": {
|
||||||
"$id": "base_0",
|
|
||||||
"properties": {
|
"properties": {
|
||||||
"base_prop": {
|
"base_prop": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
"merge_0_0": {
|
||||||
"type": "base_0",
|
"type": "base_0",
|
||||||
"properties": {
|
"properties": {
|
||||||
"child_prop": {
|
"child_prop": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
"$id": "merge_0_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -48,7 +46,9 @@
|
|||||||
"errors": [
|
"errors": [
|
||||||
{
|
{
|
||||||
"code": "INVALID_TYPE",
|
"code": "INVALID_TYPE",
|
||||||
"details": { "path": "base_prop" }
|
"details": {
|
||||||
|
"path": "base_prop"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -58,9 +58,8 @@
|
|||||||
{
|
{
|
||||||
"description": "merging: required fields accumulate",
|
"description": "merging: required fields accumulate",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"base_1": {
|
||||||
"$id": "base_1",
|
|
||||||
"properties": {
|
"properties": {
|
||||||
"a": {
|
"a": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
@ -70,7 +69,7 @@
|
|||||||
"a"
|
"a"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
"merge_1_0": {
|
||||||
"type": "base_1",
|
"type": "base_1",
|
||||||
"properties": {
|
"properties": {
|
||||||
"b": {
|
"b": {
|
||||||
@ -79,10 +78,9 @@
|
|||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"b"
|
"b"
|
||||||
],
|
]
|
||||||
"$id": "merge_1_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -109,7 +107,9 @@
|
|||||||
"errors": [
|
"errors": [
|
||||||
{
|
{
|
||||||
"code": "REQUIRED_FIELD_MISSING",
|
"code": "REQUIRED_FIELD_MISSING",
|
||||||
"details": { "path": "a" }
|
"details": {
|
||||||
|
"path": "a"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -126,7 +126,9 @@
|
|||||||
"errors": [
|
"errors": [
|
||||||
{
|
{
|
||||||
"code": "REQUIRED_FIELD_MISSING",
|
"code": "REQUIRED_FIELD_MISSING",
|
||||||
"details": { "path": "b" }
|
"details": {
|
||||||
|
"path": "b"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -136,9 +138,8 @@
|
|||||||
{
|
{
|
||||||
"description": "merging: dependencies accumulate",
|
"description": "merging: dependencies accumulate",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"base_2": {
|
||||||
"$id": "base_2",
|
|
||||||
"properties": {
|
"properties": {
|
||||||
"trigger": {
|
"trigger": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
@ -153,7 +154,7 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
"merge_2_0": {
|
||||||
"type": "base_2",
|
"type": "base_2",
|
||||||
"properties": {
|
"properties": {
|
||||||
"child_dep": {
|
"child_dep": {
|
||||||
@ -164,10 +165,9 @@
|
|||||||
"trigger": [
|
"trigger": [
|
||||||
"child_dep"
|
"child_dep"
|
||||||
]
|
]
|
||||||
},
|
}
|
||||||
"$id": "merge_2_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -196,7 +196,9 @@
|
|||||||
"errors": [
|
"errors": [
|
||||||
{
|
{
|
||||||
"code": "DEPENDENCY_MISSING",
|
"code": "DEPENDENCY_MISSING",
|
||||||
"details": { "path": "" }
|
"details": {
|
||||||
|
"path": ""
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -214,7 +216,9 @@
|
|||||||
"errors": [
|
"errors": [
|
||||||
{
|
{
|
||||||
"code": "DEPENDENCY_MISSING",
|
"code": "DEPENDENCY_MISSING",
|
||||||
"details": { "path": "" }
|
"details": {
|
||||||
|
"path": ""
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -224,9 +228,8 @@
|
|||||||
{
|
{
|
||||||
"description": "merging: form and display do NOT merge",
|
"description": "merging: form and display do NOT merge",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"base_3": {
|
||||||
"$id": "base_3",
|
|
||||||
"properties": {
|
"properties": {
|
||||||
"a": {
|
"a": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
@ -240,7 +243,7 @@
|
|||||||
"b"
|
"b"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
"merge_3_0": {
|
||||||
"type": "base_3",
|
"type": "base_3",
|
||||||
"properties": {
|
"properties": {
|
||||||
"c": {
|
"c": {
|
||||||
@ -249,10 +252,9 @@
|
|||||||
},
|
},
|
||||||
"form": [
|
"form": [
|
||||||
"c"
|
"c"
|
||||||
],
|
]
|
||||||
"$id": "merge_3_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
|
|||||||
6109
fixtures/merger.json
6109
fixtures/merger.json
File diff suppressed because it is too large
Load Diff
@ -2,13 +2,12 @@
|
|||||||
{
|
{
|
||||||
"description": "minContains without contains is ignored",
|
"description": "minContains without contains is ignored",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"minContains_0_0": {
|
||||||
"minContains": 1,
|
"minContains": 1,
|
||||||
"extensible": true,
|
"extensible": true
|
||||||
"$id": "minContains_0_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -36,16 +35,15 @@
|
|||||||
{
|
{
|
||||||
"description": "minContains=1 with contains",
|
"description": "minContains=1 with contains",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"minContains_1_0": {
|
||||||
"contains": {
|
"contains": {
|
||||||
"const": 1
|
"const": 1
|
||||||
},
|
},
|
||||||
"minContains": 1,
|
"minContains": 1,
|
||||||
"extensible": true,
|
"extensible": true
|
||||||
"$id": "minContains_1_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -108,16 +106,15 @@
|
|||||||
{
|
{
|
||||||
"description": "minContains=2 with contains",
|
"description": "minContains=2 with contains",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"minContains_2_0": {
|
||||||
"contains": {
|
"contains": {
|
||||||
"const": 1
|
"const": 1
|
||||||
},
|
},
|
||||||
"minContains": 2,
|
"minContains": 2,
|
||||||
"extensible": true,
|
"extensible": true
|
||||||
"$id": "minContains_2_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -195,16 +192,15 @@
|
|||||||
{
|
{
|
||||||
"description": "minContains=2 with contains with a decimal value",
|
"description": "minContains=2 with contains with a decimal value",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"minContains_3_0": {
|
||||||
"contains": {
|
"contains": {
|
||||||
"const": 1
|
"const": 1
|
||||||
},
|
},
|
||||||
"minContains": 2,
|
"minContains": 2,
|
||||||
"extensible": true,
|
"extensible": true
|
||||||
"$id": "minContains_3_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -235,17 +231,16 @@
|
|||||||
{
|
{
|
||||||
"description": "maxContains = minContains",
|
"description": "maxContains = minContains",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"minContains_4_0": {
|
||||||
"contains": {
|
"contains": {
|
||||||
"const": 1
|
"const": 1
|
||||||
},
|
},
|
||||||
"maxContains": 2,
|
"maxContains": 2,
|
||||||
"minContains": 2,
|
"minContains": 2,
|
||||||
"extensible": true,
|
"extensible": true
|
||||||
"$id": "minContains_4_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -298,17 +293,16 @@
|
|||||||
{
|
{
|
||||||
"description": "maxContains < minContains",
|
"description": "maxContains < minContains",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"minContains_5_0": {
|
||||||
"contains": {
|
"contains": {
|
||||||
"const": 1
|
"const": 1
|
||||||
},
|
},
|
||||||
"maxContains": 1,
|
"maxContains": 1,
|
||||||
"minContains": 3,
|
"minContains": 3,
|
||||||
"extensible": true,
|
"extensible": true
|
||||||
"$id": "minContains_5_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -361,16 +355,15 @@
|
|||||||
{
|
{
|
||||||
"description": "minContains = 0",
|
"description": "minContains = 0",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"minContains_6_0": {
|
||||||
"contains": {
|
"contains": {
|
||||||
"const": 1
|
"const": 1
|
||||||
},
|
},
|
||||||
"minContains": 0,
|
"minContains": 0,
|
||||||
"extensible": true,
|
"extensible": true
|
||||||
"$id": "minContains_6_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -398,17 +391,16 @@
|
|||||||
{
|
{
|
||||||
"description": "minContains = 0 with maxContains",
|
"description": "minContains = 0 with maxContains",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"minContains_7_0": {
|
||||||
"contains": {
|
"contains": {
|
||||||
"const": 1
|
"const": 1
|
||||||
},
|
},
|
||||||
"minContains": 0,
|
"minContains": 0,
|
||||||
"maxContains": 1,
|
"maxContains": 1,
|
||||||
"extensible": true,
|
"extensible": true
|
||||||
"$id": "minContains_7_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -448,16 +440,15 @@
|
|||||||
{
|
{
|
||||||
"description": "extensible: true allows non-matching items in minContains",
|
"description": "extensible: true allows non-matching items in minContains",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"minContains_8_0": {
|
||||||
"contains": {
|
"contains": {
|
||||||
"const": 1
|
"const": 1
|
||||||
},
|
},
|
||||||
"minContains": 1,
|
"minContains": 1,
|
||||||
"extensible": true,
|
"extensible": true
|
||||||
"$id": "minContains_8_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
|
|||||||
@ -2,13 +2,12 @@
|
|||||||
{
|
{
|
||||||
"description": "minItems validation",
|
"description": "minItems validation",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"minItems_0_0": {
|
||||||
"minItems": 1,
|
"minItems": 1,
|
||||||
"extensible": true,
|
"extensible": true
|
||||||
"$id": "minItems_0_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -57,13 +56,12 @@
|
|||||||
{
|
{
|
||||||
"description": "minItems validation with a decimal",
|
"description": "minItems validation with a decimal",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"minItems_1_0": {
|
||||||
"minItems": 1,
|
"minItems": 1,
|
||||||
"extensible": true,
|
"extensible": true
|
||||||
"$id": "minItems_1_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -92,13 +90,12 @@
|
|||||||
{
|
{
|
||||||
"description": "extensible: true allows extra items in minItems",
|
"description": "extensible: true allows extra items in minItems",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"minItems_2_0": {
|
||||||
"minItems": 1,
|
"minItems": 1,
|
||||||
"extensible": true,
|
"extensible": true
|
||||||
"$id": "minItems_2_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
|
|||||||
@ -2,12 +2,11 @@
|
|||||||
{
|
{
|
||||||
"description": "minLength validation",
|
"description": "minLength validation",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"minLength_0_0": {
|
||||||
"minLength": 2,
|
"minLength": 2
|
||||||
"$id": "minLength_0_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -60,12 +59,11 @@
|
|||||||
{
|
{
|
||||||
"description": "minLength validation with a decimal",
|
"description": "minLength validation with a decimal",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"minLength_1_0": {
|
||||||
"minLength": 2,
|
"minLength": 2
|
||||||
"$id": "minLength_1_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
|
|||||||
@ -2,13 +2,12 @@
|
|||||||
{
|
{
|
||||||
"description": "minProperties validation",
|
"description": "minProperties validation",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"minProperties_0_0": {
|
||||||
"minProperties": 1,
|
"minProperties": 1,
|
||||||
"extensible": true,
|
"extensible": true
|
||||||
"$id": "minProperties_0_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -75,13 +74,12 @@
|
|||||||
{
|
{
|
||||||
"description": "minProperties validation with a decimal",
|
"description": "minProperties validation with a decimal",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"minProperties_1_0": {
|
||||||
"minProperties": 1,
|
"minProperties": 1,
|
||||||
"extensible": true,
|
"extensible": true
|
||||||
"$id": "minProperties_1_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -110,13 +108,12 @@
|
|||||||
{
|
{
|
||||||
"description": "extensible: true allows extra properties in minProperties",
|
"description": "extensible: true allows extra properties in minProperties",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"minProperties_2_0": {
|
||||||
"minProperties": 1,
|
"minProperties": 1,
|
||||||
"extensible": true,
|
"extensible": true
|
||||||
"$id": "minProperties_2_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
|
|||||||
@ -2,12 +2,11 @@
|
|||||||
{
|
{
|
||||||
"description": "minimum validation",
|
"description": "minimum validation",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"minimum_0_0": {
|
||||||
"minimum": 1.1,
|
"minimum": 1.1
|
||||||
"$id": "minimum_0_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -51,12 +50,11 @@
|
|||||||
{
|
{
|
||||||
"description": "minimum validation with signed integer",
|
"description": "minimum validation with signed integer",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"minimum_1_0": {
|
||||||
"minimum": -2,
|
"minimum": -2
|
||||||
"$id": "minimum_1_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
|
|||||||
@ -2,12 +2,11 @@
|
|||||||
{
|
{
|
||||||
"description": "by int",
|
"description": "by int",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"multipleOf_0_0": {
|
||||||
"multipleOf": 2,
|
"multipleOf": 2
|
||||||
"$id": "multipleOf_0_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -42,12 +41,11 @@
|
|||||||
{
|
{
|
||||||
"description": "by number",
|
"description": "by number",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"multipleOf_1_0": {
|
||||||
"multipleOf": 1.5,
|
"multipleOf": 1.5
|
||||||
"$id": "multipleOf_1_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -82,12 +80,11 @@
|
|||||||
{
|
{
|
||||||
"description": "by small number",
|
"description": "by small number",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"multipleOf_2_0": {
|
||||||
"multipleOf": 0.0001,
|
"multipleOf": 0.0001
|
||||||
"$id": "multipleOf_2_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -113,13 +110,12 @@
|
|||||||
{
|
{
|
||||||
"description": "small multiple of large integer",
|
"description": "small multiple of large integer",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"multipleOf_3_0": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"multipleOf": 1e-8,
|
"multipleOf": 1e-08
|
||||||
"$id": "multipleOf_3_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
|
|||||||
@ -2,14 +2,13 @@
|
|||||||
{
|
{
|
||||||
"description": "not",
|
"description": "not",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"not_0_0": {
|
||||||
"not": {
|
"not": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
},
|
}
|
||||||
"$id": "not_0_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -35,17 +34,16 @@
|
|||||||
{
|
{
|
||||||
"description": "not multiple types",
|
"description": "not multiple types",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"not_1_0": {
|
||||||
"not": {
|
"not": {
|
||||||
"type": [
|
"type": [
|
||||||
"integer",
|
"integer",
|
||||||
"boolean"
|
"boolean"
|
||||||
]
|
]
|
||||||
},
|
}
|
||||||
"$id": "not_1_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -80,8 +78,8 @@
|
|||||||
{
|
{
|
||||||
"description": "not more complex schema",
|
"description": "not more complex schema",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"not_2_0": {
|
||||||
"not": {
|
"not": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@ -90,10 +88,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"extensible": true,
|
"extensible": true
|
||||||
"$id": "not_2_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -132,16 +129,15 @@
|
|||||||
{
|
{
|
||||||
"description": "forbidden property",
|
"description": "forbidden property",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"not_3_0": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"foo": {
|
"foo": {
|
||||||
"not": {}
|
"not": {}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
"$id": "not_3_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -170,12 +166,11 @@
|
|||||||
{
|
{
|
||||||
"description": "forbid everything with empty schema",
|
"description": "forbid everything with empty schema",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"not_4_0": {
|
||||||
"not": {},
|
"not": {}
|
||||||
"$id": "not_4_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -268,12 +263,11 @@
|
|||||||
{
|
{
|
||||||
"description": "forbid everything with boolean schema true",
|
"description": "forbid everything with boolean schema true",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"not_5_0": {
|
||||||
"not": true,
|
"not": true
|
||||||
"$id": "not_5_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -366,13 +360,12 @@
|
|||||||
{
|
{
|
||||||
"description": "allow everything with boolean schema false",
|
"description": "allow everything with boolean schema false",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"not_6_0": {
|
||||||
"not": false,
|
"not": false,
|
||||||
"extensible": true,
|
"extensible": true
|
||||||
"$id": "not_6_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -465,14 +458,13 @@
|
|||||||
{
|
{
|
||||||
"description": "double negation",
|
"description": "double negation",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"not_7_0": {
|
||||||
"not": {
|
"not": {
|
||||||
"not": {}
|
"not": {}
|
||||||
},
|
}
|
||||||
"$id": "not_7_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -489,15 +481,14 @@
|
|||||||
{
|
{
|
||||||
"description": "extensible: true allows extra properties in not",
|
"description": "extensible: true allows extra properties in not",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"not_8_0": {
|
||||||
"not": {
|
"not": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
"extensible": true,
|
"extensible": true
|
||||||
"$id": "not_8_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -516,14 +507,13 @@
|
|||||||
{
|
{
|
||||||
"description": "extensible: false (default) forbids extra properties in not",
|
"description": "extensible: false (default) forbids extra properties in not",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"not_9_0": {
|
||||||
"not": {
|
"not": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
},
|
}
|
||||||
"$id": "not_9_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -542,8 +532,8 @@
|
|||||||
{
|
{
|
||||||
"description": "property next to not (extensible: true)",
|
"description": "property next to not (extensible: true)",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"not_10_0": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"bar": {
|
"bar": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
@ -552,10 +542,9 @@
|
|||||||
"not": {
|
"not": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
"extensible": true,
|
"extensible": true
|
||||||
"$id": "not_10_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -575,8 +564,8 @@
|
|||||||
{
|
{
|
||||||
"description": "property next to not (extensible: false)",
|
"description": "property next to not (extensible: false)",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"not_11_0": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"bar": {
|
"bar": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
@ -584,10 +573,9 @@
|
|||||||
},
|
},
|
||||||
"not": {
|
"not": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
},
|
}
|
||||||
"$id": "not_11_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
|
|||||||
@ -2,166 +2,182 @@
|
|||||||
{
|
{
|
||||||
"description": "Strict Inheritance",
|
"description": "Strict Inheritance",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"parent_type": {
|
||||||
"$id": "parent_type",
|
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {"a": {"type": "integer"}},
|
"properties": {
|
||||||
"required": ["a"]
|
"a": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"a"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
"child_type": {
|
||||||
"$id": "child_type",
|
|
||||||
"type": "parent_type",
|
"type": "parent_type",
|
||||||
"properties": {"b": {"type": "integer"}}
|
"properties": {
|
||||||
|
"b": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
"description": "valid child inherits properties and strictness",
|
"description": "valid child inherits properties and strictness",
|
||||||
"schema_id": "child_type",
|
"schema_id": "child_type",
|
||||||
"data": {"a": 1, "b": 2},
|
"data": {
|
||||||
|
"a": 1,
|
||||||
|
"b": 2
|
||||||
|
},
|
||||||
"action": "validate",
|
"action": "validate",
|
||||||
"expect": {"success": true}
|
"expect": {
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "missing inherited required property fails",
|
"description": "missing inherited required property fails",
|
||||||
"schema_id": "child_type",
|
"schema_id": "child_type",
|
||||||
"data": {"b": 2},
|
"data": {
|
||||||
|
"b": 2
|
||||||
|
},
|
||||||
"action": "validate",
|
"action": "validate",
|
||||||
"expect": {"success": false}
|
"expect": {
|
||||||
|
"success": false
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "additional properties fail due to inherited strictness",
|
"description": "additional properties fail due to inherited strictness",
|
||||||
"schema_id": "child_type",
|
"schema_id": "child_type",
|
||||||
"data": {"a": 1, "b": 2, "c": 3},
|
"data": {
|
||||||
|
"a": 1,
|
||||||
|
"b": 2,
|
||||||
|
"c": 3
|
||||||
|
},
|
||||||
"action": "validate",
|
"action": "validate",
|
||||||
"expect": {"success": false}
|
"expect": {
|
||||||
|
"success": false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "Local Shadowing (Composition & Proxies)",
|
"description": "Local Shadowing (Composition & Proxies)",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"budget": {
|
||||||
"$id": "budget",
|
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"max": {"type": "integer", "maximum": 100}
|
"max": {
|
||||||
|
"type": "integer",
|
||||||
|
"maximum": 100
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
"custom_budget": {
|
||||||
"$id": "custom_budget",
|
|
||||||
"type": "budget",
|
"type": "budget",
|
||||||
"properties": {
|
"properties": {
|
||||||
"max": {"type": "integer", "maximum": 50}
|
"max": {
|
||||||
|
"type": "integer",
|
||||||
|
"maximum": 50
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
"description": "shadowing override applies (50 is locally allowed)",
|
"description": "shadowing override applies (50 is locally allowed)",
|
||||||
"schema_id": "custom_budget",
|
"schema_id": "custom_budget",
|
||||||
"data": {"max": 40},
|
"data": {
|
||||||
|
"max": 40
|
||||||
|
},
|
||||||
"action": "validate",
|
"action": "validate",
|
||||||
"expect": {"success": true}
|
"expect": {
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "shadowing override applies natively, rejecting 60 even though parent allowed 100",
|
"description": "shadowing override applies natively, rejecting 60 even though parent allowed 100",
|
||||||
"schema_id": "custom_budget",
|
"schema_id": "custom_budget",
|
||||||
"data": {"max": 60},
|
"data": {
|
||||||
|
"max": 60
|
||||||
|
},
|
||||||
"action": "validate",
|
"action": "validate",
|
||||||
"expect": {"success": false}
|
"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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
|
||||||
},
|
|
||||||
"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)",
|
"description": "Primitive Array Shorthand (Optionality)",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"invoice": {
|
||||||
"$id": "invoice",
|
|
||||||
"type": "object",
|
|
||||||
"properties": {"amount": {"type": "integer"}},
|
|
||||||
"required": ["amount"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"$id": "request",
|
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"inv": {"type": ["invoice", "null"]}
|
"amount": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"amount"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"request": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"inv": {
|
||||||
|
"type": [
|
||||||
|
"invoice",
|
||||||
|
"null"
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
"description": "valid object passes shorthand inheritance check",
|
"description": "valid object passes shorthand inheritance check",
|
||||||
"schema_id": "request",
|
"schema_id": "request",
|
||||||
"data": {"inv": {"amount": 5}},
|
"data": {
|
||||||
|
"inv": {
|
||||||
|
"amount": 5
|
||||||
|
}
|
||||||
|
},
|
||||||
"action": "validate",
|
"action": "validate",
|
||||||
"expect": {"success": true}
|
"expect": {
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "null passes shorthand inheritance check",
|
"description": "null passes shorthand inheritance check",
|
||||||
"schema_id": "request",
|
"schema_id": "request",
|
||||||
"data": {"inv": null},
|
"data": {
|
||||||
|
"inv": null
|
||||||
|
},
|
||||||
"action": "validate",
|
"action": "validate",
|
||||||
"expect": {"success": true}
|
"expect": {
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "invalid object fails inner constraints safely",
|
"description": "invalid object fails inner constraints safely",
|
||||||
"schema_id": "request",
|
"schema_id": "request",
|
||||||
"data": {"inv": {"amount": "string"}},
|
"data": {
|
||||||
|
"inv": {
|
||||||
|
"amount": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
"action": "validate",
|
"action": "validate",
|
||||||
"expect": {"success": false}
|
"expect": {
|
||||||
|
"success": false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -2,9 +2,8 @@
|
|||||||
{
|
{
|
||||||
"description": "Hybrid Array Pathing",
|
"description": "Hybrid Array Pathing",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"hybrid_pathing": {
|
||||||
"$id": "hybrid_pathing",
|
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"primitives": {
|
"primitives": {
|
||||||
@ -69,7 +68,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -123,7 +122,9 @@
|
|||||||
"errors": [
|
"errors": [
|
||||||
{
|
{
|
||||||
"code": "INVALID_TYPE",
|
"code": "INVALID_TYPE",
|
||||||
"details": { "path": "primitives/1" }
|
"details": {
|
||||||
|
"path": "primitives/1"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -147,11 +148,15 @@
|
|||||||
"errors": [
|
"errors": [
|
||||||
{
|
{
|
||||||
"code": "REQUIRED_FIELD_MISSING",
|
"code": "REQUIRED_FIELD_MISSING",
|
||||||
"details": { "path": "ad_hoc_objects/1/name" }
|
"details": {
|
||||||
|
"path": "ad_hoc_objects/1/name"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"code": "STRICT_PROPERTY_VIOLATION",
|
"code": "STRICT_PROPERTY_VIOLATION",
|
||||||
"details": { "path": "ad_hoc_objects/1/age" }
|
"details": {
|
||||||
|
"path": "ad_hoc_objects/1/age"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -177,7 +182,9 @@
|
|||||||
"errors": [
|
"errors": [
|
||||||
{
|
{
|
||||||
"code": "MINIMUM_VIOLATED",
|
"code": "MINIMUM_VIOLATED",
|
||||||
"details": { "path": "entities/entity-beta/value" }
|
"details": {
|
||||||
|
"path": "entities/entity-beta/value"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -208,7 +215,9 @@
|
|||||||
"errors": [
|
"errors": [
|
||||||
{
|
{
|
||||||
"code": "INVALID_TYPE",
|
"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": {
|
"database": {
|
||||||
"types": [
|
"schemas": {
|
||||||
{
|
"ad_hoc_pathing": {
|
||||||
"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",
|
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"metadata_bubbles": {
|
"metadata_bubbles": {
|
||||||
@ -260,61 +236,109 @@
|
|||||||
"items": {
|
"items": {
|
||||||
"oneOf": [
|
"oneOf": [
|
||||||
{
|
{
|
||||||
"$id": "contact_metadata",
|
"type": "string"
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"type": { "const": "contact" },
|
|
||||||
"phone": { "type": "string" }
|
|
||||||
},
|
|
||||||
"required": ["phone"]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"$id": "invoice_metadata",
|
"type": "integer"
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"type": { "const": "invoice" },
|
|
||||||
"invoice_id": { "type": "integer" }
|
|
||||||
},
|
|
||||||
"required": ["invoice_id"]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"table_families": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$family": "widget"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
"description": "oneOf tags natively bubble specific schema paths into deep array roots",
|
"description": "oneOf arrays natively support disjoint primitive types dynamically mapped into topological paths natively",
|
||||||
"data": {
|
"data": {
|
||||||
"metadata_bubbles": [
|
"metadata_bubbles": [
|
||||||
{ "type": "invoice", "invoice_id": 100 },
|
100,
|
||||||
{ "type": "contact", "phone": 12345, "rogue_field": "x" }
|
"metadata string",
|
||||||
|
true
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"schema_id": "polymorphic_pathing",
|
"schema_id": "ad_hoc_pathing",
|
||||||
"action": "validate",
|
"action": "validate",
|
||||||
"expect": {
|
"expect": {
|
||||||
"success": false,
|
"success": false,
|
||||||
"errors": [
|
"errors": [
|
||||||
{
|
{
|
||||||
"code": "INVALID_TYPE",
|
"code": "NO_ONEOF_MATCH",
|
||||||
"details": { "path": "metadata_bubbles/1/phone" }
|
"details": {
|
||||||
},
|
"path": "metadata_bubbles/2"
|
||||||
{
|
}
|
||||||
"code": "STRICT_PROPERTY_VIOLATION",
|
|
||||||
"details": { "path": "metadata_bubbles/1/rogue_field" }
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"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": {
|
||||||
|
"family": "widget"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tests": [
|
||||||
{
|
{
|
||||||
"description": "families mechanically map physical variants directly onto topological uuid array paths",
|
"description": "families mechanically map physical variants directly onto topological uuid array paths",
|
||||||
"data": {
|
"data": {
|
||||||
@ -324,7 +348,9 @@
|
|||||||
"type": "widget",
|
"type": "widget",
|
||||||
"kind": "stock",
|
"kind": "stock",
|
||||||
"amount": 500,
|
"amount": 500,
|
||||||
"details": { "nested_metric": 42.0 }
|
"details": {
|
||||||
|
"nested_metric": 42.0
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "widget-2",
|
"id": "widget-2",
|
||||||
@ -332,32 +358,40 @@
|
|||||||
"kind": "stock",
|
"kind": "stock",
|
||||||
"amount": "not_an_int",
|
"amount": "not_an_int",
|
||||||
"details": {
|
"details": {
|
||||||
"stray_child": true
|
"stray_child": true
|
||||||
},
|
},
|
||||||
"unexpected_root_prop": "hi"
|
"unexpected_root_prop": "hi"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"schema_id": "polymorphic_pathing",
|
"schema_id": "family_pathing",
|
||||||
"action": "validate",
|
"action": "validate",
|
||||||
"expect": {
|
"expect": {
|
||||||
"success": false,
|
"success": false,
|
||||||
"errors": [
|
"errors": [
|
||||||
{
|
{
|
||||||
"code": "INVALID_TYPE",
|
"code": "INVALID_TYPE",
|
||||||
"details": { "path": "table_families/widget-2/amount" }
|
"details": {
|
||||||
|
"path": "table_families/widget-2/amount"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"code": "REQUIRED_FIELD_MISSING",
|
"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",
|
"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",
|
"code": "STRICT_PROPERTY_VIOLATION",
|
||||||
"details": { "path": "table_families/widget-2/unexpected_root_prop" }
|
"details": {
|
||||||
|
"path": "table_families/widget-2/unexpected_root_prop"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,12 +2,11 @@
|
|||||||
{
|
{
|
||||||
"description": "pattern validation",
|
"description": "pattern validation",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"pattern_0_0": {
|
||||||
"pattern": "^a*$",
|
"pattern": "^a*$"
|
||||||
"$id": "pattern_0_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -87,12 +86,11 @@
|
|||||||
{
|
{
|
||||||
"description": "pattern is not anchored",
|
"description": "pattern is not anchored",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"pattern_1_0": {
|
||||||
"pattern": "a+",
|
"pattern": "a+"
|
||||||
"$id": "pattern_1_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
|
|||||||
@ -2,17 +2,16 @@
|
|||||||
{
|
{
|
||||||
"description": "patternProperties validates properties matching a regex",
|
"description": "patternProperties validates properties matching a regex",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"patternProperties_0_0": {
|
||||||
"patternProperties": {
|
"patternProperties": {
|
||||||
"f.*o": {
|
"f.*o": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"items": {},
|
"items": {}
|
||||||
"$id": "patternProperties_0_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -108,8 +107,8 @@
|
|||||||
{
|
{
|
||||||
"description": "multiple simultaneous patternProperties are validated",
|
"description": "multiple simultaneous patternProperties are validated",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"patternProperties_1_0": {
|
||||||
"patternProperties": {
|
"patternProperties": {
|
||||||
"a*": {
|
"a*": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
@ -117,10 +116,9 @@
|
|||||||
"aaa*": {
|
"aaa*": {
|
||||||
"maximum": 20
|
"maximum": 20
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
"$id": "patternProperties_1_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -196,8 +194,8 @@
|
|||||||
{
|
{
|
||||||
"description": "regexes are not anchored by default and are case sensitive",
|
"description": "regexes are not anchored by default and are case sensitive",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"patternProperties_2_0": {
|
||||||
"patternProperties": {
|
"patternProperties": {
|
||||||
"[0-9]{2,}": {
|
"[0-9]{2,}": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
@ -206,10 +204,9 @@
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"extensible": true,
|
"extensible": true
|
||||||
"$id": "patternProperties_2_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -261,15 +258,14 @@
|
|||||||
{
|
{
|
||||||
"description": "patternProperties with boolean schemas",
|
"description": "patternProperties with boolean schemas",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"patternProperties_3_0": {
|
||||||
"patternProperties": {
|
"patternProperties": {
|
||||||
"f.*": true,
|
"f.*": true,
|
||||||
"b.*": false
|
"b.*": false
|
||||||
},
|
}
|
||||||
"$id": "patternProperties_3_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -331,16 +327,15 @@
|
|||||||
{
|
{
|
||||||
"description": "patternProperties with null valued instance properties",
|
"description": "patternProperties with null valued instance properties",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"patternProperties_4_0": {
|
||||||
"patternProperties": {
|
"patternProperties": {
|
||||||
"^.*bar$": {
|
"^.*bar$": {
|
||||||
"type": "null"
|
"type": "null"
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
"$id": "patternProperties_4_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -359,17 +354,16 @@
|
|||||||
{
|
{
|
||||||
"description": "extensible: true allows extra properties NOT matching pattern",
|
"description": "extensible: true allows extra properties NOT matching pattern",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"patternProperties_5_0": {
|
||||||
"patternProperties": {
|
"patternProperties": {
|
||||||
"f.*o": {
|
"f.*o": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"extensible": true,
|
"extensible": true
|
||||||
"$id": "patternProperties_5_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
|
|||||||
@ -1,270 +1,394 @@
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
"description": "Vertical $family Routing (Across Tables)",
|
"description": "Vertical family Routing (Across Tables)",
|
||||||
"database": {
|
"database": {
|
||||||
"types": [
|
"types": [
|
||||||
{
|
{
|
||||||
"name": "entity",
|
"name": "entity",
|
||||||
"variations": ["entity", "organization", "person", "bot"],
|
"variations": [
|
||||||
"schemas": [
|
"entity",
|
||||||
{
|
"organization",
|
||||||
"$id": "entity",
|
"person",
|
||||||
|
"bot"
|
||||||
|
],
|
||||||
|
"schemas": {
|
||||||
|
"entity": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"type": { "type": "string" },
|
"type": {
|
||||||
"id": { "type": "string" }
|
"type": "string"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "organization",
|
"name": "organization",
|
||||||
"variations": ["organization", "person"],
|
"variations": [
|
||||||
"schemas": [
|
"organization",
|
||||||
{
|
"person"
|
||||||
"$id": "organization",
|
],
|
||||||
|
"schemas": {
|
||||||
|
"organization": {
|
||||||
"type": "entity",
|
"type": "entity",
|
||||||
"properties": {
|
"properties": {
|
||||||
"name": { "type": "string" }
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "person",
|
"name": "person",
|
||||||
"variations": ["person"],
|
"variations": [
|
||||||
"schemas": [
|
"person"
|
||||||
{
|
],
|
||||||
"$id": "person",
|
"schemas": {
|
||||||
|
"person": {
|
||||||
"type": "organization",
|
"type": "organization",
|
||||||
"properties": {
|
"properties": {
|
||||||
"first_name": { "type": "string" }
|
"first_name": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "bot",
|
"name": "bot",
|
||||||
"variations": ["bot"],
|
"variations": [
|
||||||
"schemas": [
|
"bot"
|
||||||
{
|
],
|
||||||
"$id": "bot",
|
"schemas": {
|
||||||
|
"bot": {
|
||||||
"type": "entity",
|
"type": "entity",
|
||||||
"properties": {
|
"properties": {
|
||||||
"model": { "type": "string" }
|
"model": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"family_entity": {
|
||||||
"$id": "family_entity",
|
"family": "entity"
|
||||||
"$family": "entity"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
"description": "Matches base entity",
|
"description": "Matches base entity",
|
||||||
"schema_id": "family_entity",
|
"schema_id": "family_entity",
|
||||||
"data": { "type": "entity", "id": "1" },
|
"data": {
|
||||||
|
"type": "entity",
|
||||||
|
"id": "1"
|
||||||
|
},
|
||||||
"action": "validate",
|
"action": "validate",
|
||||||
"expect": { "success": true }
|
"expect": {
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "Matches descendant person natively",
|
"description": "Matches descendant person natively",
|
||||||
"schema_id": "family_entity",
|
"schema_id": "family_entity",
|
||||||
"data": { "type": "person", "id": "2", "first_name": "Bob" },
|
"data": {
|
||||||
|
"type": "person",
|
||||||
|
"id": "2",
|
||||||
|
"first_name": "Bob"
|
||||||
|
},
|
||||||
"action": "validate",
|
"action": "validate",
|
||||||
"expect": { "success": true }
|
"expect": {
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "Missing type fails out immediately",
|
"description": "Missing type fails out immediately",
|
||||||
"schema_id": "family_entity",
|
"schema_id": "family_entity",
|
||||||
"data": { "id": "3", "first_name": "Bob" },
|
"data": {
|
||||||
|
"id": "3",
|
||||||
|
"first_name": "Bob"
|
||||||
|
},
|
||||||
"action": "validate",
|
"action": "validate",
|
||||||
"expect": { "success": false, "errors": [ { "code": "MISSING_TYPE", "details": { "path": "" } } ] }
|
"expect": {
|
||||||
|
"success": false,
|
||||||
|
"errors": [
|
||||||
|
{
|
||||||
|
"code": "MISSING_TYPE",
|
||||||
|
"details": {
|
||||||
|
"path": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "Alias matching failure",
|
"description": "Alias matching failure",
|
||||||
"schema_id": "family_entity",
|
"schema_id": "family_entity",
|
||||||
"data": { "type": "alien", "id": "4" },
|
"data": {
|
||||||
|
"type": "alien",
|
||||||
|
"id": "4"
|
||||||
|
},
|
||||||
"action": "validate",
|
"action": "validate",
|
||||||
"expect": { "success": false, "errors": [ { "code": "NO_FAMILY_MATCH", "details": { "path": "" } } ] }
|
"expect": {
|
||||||
|
"success": false,
|
||||||
|
"errors": [
|
||||||
|
{
|
||||||
|
"code": "NO_FAMILY_MATCH",
|
||||||
|
"details": {
|
||||||
|
"path": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "Matrix $family Routing (Vertical + Horizontal Intersections)",
|
"description": "Matrix family Routing (Vertical + Horizontal Intersections)",
|
||||||
"database": {
|
"database": {
|
||||||
"types": [
|
"types": [
|
||||||
{
|
{
|
||||||
"name": "entity",
|
"name": "entity",
|
||||||
"variations": ["entity", "organization", "person", "bot"],
|
"variations": [
|
||||||
"schemas": [
|
"entity",
|
||||||
{
|
"organization",
|
||||||
"$id": "entity",
|
"person",
|
||||||
|
"bot"
|
||||||
|
],
|
||||||
|
"schemas": {
|
||||||
|
"entity": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"type": { "type": "string" }
|
"type": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
"light.entity": {
|
||||||
"$id": "light.entity",
|
|
||||||
"type": "entity",
|
"type": "entity",
|
||||||
"properties": {
|
"properties": {
|
||||||
"kind": { "type": "string" }
|
"kind": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "organization",
|
"name": "organization",
|
||||||
"variations": ["organization", "person"],
|
"variations": [
|
||||||
"schemas": [
|
"organization",
|
||||||
{
|
"person"
|
||||||
"$id": "organization",
|
],
|
||||||
|
"schemas": {
|
||||||
|
"organization": {
|
||||||
"type": "entity"
|
"type": "entity"
|
||||||
},
|
},
|
||||||
{
|
"light.organization": {
|
||||||
"$id": "light.organization",
|
|
||||||
"type": "light.entity"
|
"type": "light.entity"
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "person",
|
"name": "person",
|
||||||
"variations": ["person"],
|
"variations": [
|
||||||
"schemas": [
|
"person"
|
||||||
{
|
],
|
||||||
"$id": "person",
|
"schemas": {
|
||||||
|
"person": {
|
||||||
"type": "organization"
|
"type": "organization"
|
||||||
},
|
},
|
||||||
{
|
"light.person": {
|
||||||
"$id": "light.person",
|
|
||||||
"type": "light.organization"
|
"type": "light.organization"
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "bot",
|
"name": "bot",
|
||||||
"variations": ["bot"],
|
"variations": [
|
||||||
"schemas": [
|
"bot"
|
||||||
{
|
],
|
||||||
"$id": "bot",
|
"schemas": {
|
||||||
|
"bot": {
|
||||||
"type": "entity"
|
"type": "entity"
|
||||||
},
|
},
|
||||||
{
|
"light.bot": {
|
||||||
"$id": "light.bot",
|
|
||||||
"type": "light.entity"
|
"type": "light.entity"
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"family_light_org": {
|
||||||
"$id": "family_light_org",
|
"family": "light.organization"
|
||||||
"$family": "light.organization"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
"description": "Matches light.organization exact matrix target",
|
"description": "Matches light.organization exact matrix target",
|
||||||
"schema_id": "family_light_org",
|
"schema_id": "family_light_org",
|
||||||
"data": { "type": "organization", "kind": "light" },
|
"data": {
|
||||||
|
"type": "organization",
|
||||||
|
"kind": "light"
|
||||||
|
},
|
||||||
"action": "validate",
|
"action": "validate",
|
||||||
"expect": { "success": true }
|
"expect": {
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "Matches descendant light.person through matrix evaluation",
|
"description": "Matches descendant light.person through matrix evaluation",
|
||||||
"schema_id": "family_light_org",
|
"schema_id": "family_light_org",
|
||||||
"data": { "type": "person", "kind": "light" },
|
"data": {
|
||||||
|
"type": "person",
|
||||||
|
"kind": "light"
|
||||||
|
},
|
||||||
"action": "validate",
|
"action": "validate",
|
||||||
"expect": { "success": true }
|
"expect": {
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "Structurally fails to route bot (bot is not descendant of organization)",
|
"description": "Structurally fails to route bot (bot is not descendant of organization)",
|
||||||
"schema_id": "family_light_org",
|
"schema_id": "family_light_org",
|
||||||
"data": { "type": "bot", "kind": "light" },
|
"data": {
|
||||||
|
"type": "bot",
|
||||||
|
"kind": "light"
|
||||||
|
},
|
||||||
"action": "validate",
|
"action": "validate",
|
||||||
"expect": { "success": false, "errors": [ { "code": "NO_FAMILY_MATCH", "details": { "path": "" } } ] }
|
"expect": {
|
||||||
}
|
"success": false,
|
||||||
]
|
"errors": [
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Horizontal $family Routing (Virtual Variations)",
|
|
||||||
"database": {
|
|
||||||
"types": [
|
|
||||||
{
|
|
||||||
"name": "widget",
|
|
||||||
"variations": ["widget"],
|
|
||||||
"schemas": [
|
|
||||||
{
|
{
|
||||||
"$id": "widget",
|
"code": "NO_FAMILY_MATCH",
|
||||||
"type": "object",
|
"details": {
|
||||||
"properties": {
|
"path": ""
|
||||||
"type": { "type": "string" }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"$id": "stock.widget",
|
|
||||||
"type": "widget",
|
|
||||||
"properties": {
|
|
||||||
"kind": { "type": "string" },
|
|
||||||
"amount": { "type": "integer" }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"$id": "super_stock.widget",
|
|
||||||
"type": "stock.widget",
|
|
||||||
"properties": {
|
|
||||||
"super_amount": { "type": "integer" }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
}
|
||||||
"schemas": [
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Horizontal family Routing (Virtual Variations)",
|
||||||
|
"database": {
|
||||||
|
"types": [
|
||||||
{
|
{
|
||||||
"$id": "family_widget",
|
"name": "widget",
|
||||||
"$family": "widget"
|
"variations": [
|
||||||
},
|
"widget"
|
||||||
{
|
],
|
||||||
"$id": "family_stock_widget",
|
"schemas": {
|
||||||
"$family": "stock.widget"
|
"widget": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"stock.widget": {
|
||||||
|
"type": "widget",
|
||||||
|
"properties": {
|
||||||
|
"kind": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"amount": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"super_stock.widget": {
|
||||||
|
"type": "stock.widget",
|
||||||
|
"properties": {
|
||||||
|
"super_amount": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"schemas": {
|
||||||
|
"family_widget": {
|
||||||
|
"family": "widget"
|
||||||
|
},
|
||||||
|
"family_stock_widget": {
|
||||||
|
"family": "stock.widget"
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
"description": "Base widget resolves stock widget horizontally",
|
"description": "Base widget resolves stock widget horizontally",
|
||||||
"schema_id": "family_widget",
|
"schema_id": "family_widget",
|
||||||
"data": { "type": "widget", "kind": "stock", "amount": 5 },
|
"data": {
|
||||||
|
"type": "widget",
|
||||||
|
"kind": "stock",
|
||||||
|
"amount": 5
|
||||||
|
},
|
||||||
"action": "validate",
|
"action": "validate",
|
||||||
"expect": { "success": true }
|
"expect": {
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "Base widget resolves nested super stock widget natively via descendants crawl",
|
"description": "Base widget resolves nested super stock widget natively via descendants crawl",
|
||||||
"schema_id": "family_widget",
|
"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",
|
"action": "validate",
|
||||||
"expect": { "success": true }
|
"expect": {
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "stock.widget explicit family resolves nested super stock via fast path",
|
"description": "stock.widget explicit family resolves nested super stock via fast path",
|
||||||
"schema_id": "family_stock_widget",
|
"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",
|
"action": "validate",
|
||||||
"expect": { "success": true }
|
"expect": {
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "stock.widget fails when presented an invalid payload constraint",
|
"description": "stock.widget fails when presented an invalid payload constraint",
|
||||||
"schema_id": "family_stock_widget",
|
"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",
|
"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,211 +398,345 @@
|
|||||||
"types": [
|
"types": [
|
||||||
{
|
{
|
||||||
"name": "entity",
|
"name": "entity",
|
||||||
"variations": ["entity", "person", "bot"],
|
"variations": [
|
||||||
"schemas": [
|
"entity",
|
||||||
{
|
"person",
|
||||||
"$id": "entity",
|
"bot"
|
||||||
|
],
|
||||||
|
"schemas": {
|
||||||
|
"entity": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"type": { "type": "string" }
|
"type": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "person",
|
"name": "person",
|
||||||
"variations": ["person"],
|
"variations": [
|
||||||
"schemas": [
|
"person"
|
||||||
{
|
],
|
||||||
"$id": "person",
|
"schemas": {
|
||||||
|
"person": {
|
||||||
"type": "entity"
|
"type": "entity"
|
||||||
},
|
},
|
||||||
{
|
"full.person": {
|
||||||
"$id": "full.person",
|
|
||||||
"type": "person",
|
"type": "person",
|
||||||
"properties": {
|
"properties": {
|
||||||
"kind": { "type": "string" },
|
"kind": {
|
||||||
"age": { "type": "integer" }
|
"type": "string"
|
||||||
|
},
|
||||||
|
"age": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"required": ["age"]
|
"required": [
|
||||||
|
"age"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "bot",
|
"name": "bot",
|
||||||
"variations": ["bot"],
|
"variations": [
|
||||||
"schemas": [
|
"bot"
|
||||||
{
|
],
|
||||||
"$id": "bot",
|
"schemas": {
|
||||||
|
"bot": {
|
||||||
"type": "entity"
|
"type": "entity"
|
||||||
},
|
},
|
||||||
{
|
"full.bot": {
|
||||||
"$id": "full.bot",
|
|
||||||
"type": "bot",
|
"type": "bot",
|
||||||
"properties": {
|
"properties": {
|
||||||
"kind": { "type": "string" },
|
"kind": {
|
||||||
"version": { "type": "string" }
|
"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": [
|
"tests": [
|
||||||
{
|
{
|
||||||
"description": "Throws MISSING_TYPE if discriminator matches neither",
|
"description": "Throws MISSING_TYPE if discriminator matches neither",
|
||||||
"schema_id": "oneOf_union",
|
"schema_id": "oneOf_union",
|
||||||
"data": { "kind": "full", "age": 5 },
|
"data": {
|
||||||
|
"kind": "full",
|
||||||
|
"age": 5
|
||||||
|
},
|
||||||
"action": "validate",
|
"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",
|
"description": "Golden match throws pure structural error exclusively on person",
|
||||||
"schema_id": "oneOf_union",
|
"schema_id": "oneOf_union",
|
||||||
"data": { "type": "person", "kind": "full", "age": "five" },
|
"data": {
|
||||||
|
"type": "person",
|
||||||
|
"kind": "full",
|
||||||
|
"age": "five"
|
||||||
|
},
|
||||||
"action": "validate",
|
"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",
|
"description": "Golden matches perfectly",
|
||||||
"schema_id": "oneOf_union",
|
"schema_id": "oneOf_union",
|
||||||
"data": { "type": "person", "kind": "full", "age": 5 },
|
"data": {
|
||||||
|
"type": "person",
|
||||||
|
"kind": "full",
|
||||||
|
"age": 5
|
||||||
|
},
|
||||||
"action": "validate",
|
"action": "validate",
|
||||||
"expect": { "success": true }
|
"expect": {
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "Fails nicely with NO_ONEOF_MATCH",
|
"description": "Fails nicely with NO_ONEOF_MATCH",
|
||||||
"schema_id": "oneOf_union",
|
"schema_id": "oneOf_union",
|
||||||
"data": { "type": "alien", "kind": "full", "age": 5 },
|
"data": {
|
||||||
|
"type": "alien",
|
||||||
|
"kind": "full",
|
||||||
|
"age": 5
|
||||||
|
},
|
||||||
"action": "validate",
|
"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)",
|
"description": "JSONB Field Bubble oneOf Discrimination (Promoted IDs)",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"metadata": {
|
||||||
"$id": "metadata",
|
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"type": { "type": "string" }
|
"type": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
"invoice.metadata": {
|
||||||
"$id": "invoice.metadata",
|
|
||||||
"type": "metadata",
|
"type": "metadata",
|
||||||
"properties": {
|
"properties": {
|
||||||
"invoice_id": { "type": "integer" }
|
"invoice_id": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"required": ["invoice_id"]
|
"required": [
|
||||||
|
"invoice_id"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
"payment.metadata": {
|
||||||
"$id": "payment.metadata",
|
|
||||||
"type": "metadata",
|
"type": "metadata",
|
||||||
"properties": {
|
"properties": {
|
||||||
"payment_id": { "type": "integer" }
|
"payment_id": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"required": ["payment_id"]
|
"required": [
|
||||||
|
"payment_id"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
"oneOf_bubble": {
|
||||||
"$id": "oneOf_bubble",
|
|
||||||
"oneOf": [
|
"oneOf": [
|
||||||
{ "type": "invoice.metadata" },
|
{
|
||||||
{ "type": "payment.metadata" }
|
"type": "invoice.metadata"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "payment.metadata"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
"description": "Extracts golden match natively from explicit JSONB type discriminator",
|
"description": "Extracts golden match natively from explicit JSONB type discriminator",
|
||||||
"schema_id": "oneOf_bubble",
|
"schema_id": "oneOf_bubble",
|
||||||
"data": { "type": "invoice.metadata", "invoice_id": "nan" },
|
"data": {
|
||||||
|
"type": "invoice.metadata",
|
||||||
|
"invoice_id": "nan"
|
||||||
|
},
|
||||||
"action": "validate",
|
"action": "validate",
|
||||||
"expect": {
|
"expect": {
|
||||||
"success": false,
|
"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",
|
"description": "Valid payload succeeds perfectly in JSONB universe",
|
||||||
"schema_id": "oneOf_bubble",
|
"schema_id": "oneOf_bubble",
|
||||||
"data": { "type": "payment.metadata", "payment_id": 123 },
|
"data": {
|
||||||
|
"type": "payment.metadata",
|
||||||
|
"payment_id": 123
|
||||||
|
},
|
||||||
"action": "validate",
|
"action": "validate",
|
||||||
"expect": { "success": true }
|
"expect": {
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "Standard JSON Schema oneOf",
|
"description": "STI Projections (Lacking Kind Discriminator Definitions)",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"types": [
|
||||||
{
|
{
|
||||||
"$id": "oneOf_scalars",
|
"name": "widget",
|
||||||
"oneOf": [
|
"variations": [
|
||||||
{ "type": "integer" },
|
"widget"
|
||||||
{ "minimum": 2 }
|
],
|
||||||
]
|
"schemas": {
|
||||||
},
|
"widget": {
|
||||||
{
|
|
||||||
"$id": "oneOf_dedupe",
|
|
||||||
"oneOf": [
|
|
||||||
{
|
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"shared": { "type": "integer" }
|
"type": {
|
||||||
},
|
"type": "string"
|
||||||
"required": ["shared"]
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
"stock.widget": {
|
||||||
"type": "object",
|
"type": "widget",
|
||||||
"properties": {
|
"properties": {
|
||||||
"shared": { "type": "integer" },
|
"kind": {
|
||||||
"extra": { "type": "string" }
|
"type": "string"
|
||||||
},
|
},
|
||||||
"required": ["shared", "extra"]
|
"amount": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"projected.widget": {
|
||||||
|
"type": "widget",
|
||||||
|
"properties": {
|
||||||
|
"alias": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"schemas": {
|
||||||
|
"stock_widget_validation": {
|
||||||
|
"type": "stock.widget"
|
||||||
|
},
|
||||||
|
"projected_widget_validation": {
|
||||||
|
"type": "projected.widget"
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
"description": "Valid exclusively against first scalar choice",
|
"description": "stock.widget securely expects kind when configured",
|
||||||
"schema_id": "oneOf_scalars",
|
"schema_id": "stock_widget_validation",
|
||||||
"data": 1,
|
"data": {
|
||||||
"action": "validate",
|
"type": "widget",
|
||||||
"expect": { "success": true }
|
"amount": 5
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"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",
|
"action": "validate",
|
||||||
"expect": {
|
"expect": {
|
||||||
"success": false,
|
"success": false,
|
||||||
"errors": [
|
"errors": [
|
||||||
{ "code": "NO_ONEOF_MATCH", "details": { "path": "" } },
|
{
|
||||||
{ "code": "INVALID_TYPE", "details": { "path": "shared" } }
|
"code": "MISSING_KIND",
|
||||||
|
"details": {
|
||||||
|
"path": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "projected.widget seamlessly bypasses kind expectation when excluded from schema",
|
||||||
|
"schema_id": "projected_widget_validation",
|
||||||
|
"data": {
|
||||||
|
"type": "widget",
|
||||||
|
"alias": "Test Projection"
|
||||||
|
},
|
||||||
|
"action": "validate",
|
||||||
|
"expect": {
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "projected.widget securely fails if user erroneously provides extra kind property",
|
||||||
|
"schema_id": "projected_widget_validation",
|
||||||
|
"data": {
|
||||||
|
"type": "widget",
|
||||||
|
"alias": "Test Projection",
|
||||||
|
"kind": "projected"
|
||||||
|
},
|
||||||
|
"action": "validate",
|
||||||
|
"expect": {
|
||||||
|
"success": false,
|
||||||
|
"errors": [
|
||||||
|
{
|
||||||
|
"code": "STRICT_PROPERTY_VIOLATION",
|
||||||
|
"details": {
|
||||||
|
"path": "kind"
|
||||||
|
}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,8 +2,8 @@
|
|||||||
{
|
{
|
||||||
"description": "a schema given for prefixItems",
|
"description": "a schema given for prefixItems",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"prefixItems_0_0": {
|
||||||
"prefixItems": [
|
"prefixItems": [
|
||||||
{
|
{
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
@ -11,10 +11,9 @@
|
|||||||
{
|
{
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
"$id": "prefixItems_0_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -92,15 +91,14 @@
|
|||||||
{
|
{
|
||||||
"description": "prefixItems with boolean schemas",
|
"description": "prefixItems with boolean schemas",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"prefixItems_1_0": {
|
||||||
"prefixItems": [
|
"prefixItems": [
|
||||||
true,
|
true,
|
||||||
false
|
false
|
||||||
],
|
]
|
||||||
"$id": "prefixItems_1_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -140,17 +138,16 @@
|
|||||||
{
|
{
|
||||||
"description": "additional items are allowed by default",
|
"description": "additional items are allowed by default",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"prefixItems_2_0": {
|
||||||
"prefixItems": [
|
"prefixItems": [
|
||||||
{
|
{
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"extensible": true,
|
"extensible": true
|
||||||
"$id": "prefixItems_2_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -171,16 +168,15 @@
|
|||||||
{
|
{
|
||||||
"description": "prefixItems with null instance elements",
|
"description": "prefixItems with null instance elements",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"prefixItems_3_0": {
|
||||||
"prefixItems": [
|
"prefixItems": [
|
||||||
{
|
{
|
||||||
"type": "null"
|
"type": "null"
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
"$id": "prefixItems_3_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -199,17 +195,16 @@
|
|||||||
{
|
{
|
||||||
"description": "extensible: true allows extra items with prefixItems",
|
"description": "extensible: true allows extra items with prefixItems",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"prefixItems_4_0": {
|
||||||
"prefixItems": [
|
"prefixItems": [
|
||||||
{
|
{
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"extensible": true,
|
"extensible": true
|
||||||
"$id": "prefixItems_4_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
|
|||||||
@ -2,12 +2,11 @@
|
|||||||
{
|
{
|
||||||
"description": "integer type matches integers",
|
"description": "integer type matches integers",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"type_0_0": {
|
||||||
"type": "integer",
|
"type": "integer"
|
||||||
"$id": "type_0_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -96,12 +95,11 @@
|
|||||||
{
|
{
|
||||||
"description": "number type matches numbers",
|
"description": "number type matches numbers",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"type_1_0": {
|
||||||
"type": "number",
|
"type": "number"
|
||||||
"$id": "type_1_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -190,12 +188,11 @@
|
|||||||
{
|
{
|
||||||
"description": "string type matches strings",
|
"description": "string type matches strings",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"type_2_0": {
|
||||||
"type": "string",
|
"type": "string"
|
||||||
"$id": "type_2_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -284,12 +281,11 @@
|
|||||||
{
|
{
|
||||||
"description": "object type matches objects",
|
"description": "object type matches objects",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"type_3_0": {
|
||||||
"type": "object",
|
"type": "object"
|
||||||
"$id": "type_3_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -360,12 +356,11 @@
|
|||||||
{
|
{
|
||||||
"description": "array type matches arrays",
|
"description": "array type matches arrays",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"type_4_0": {
|
||||||
"type": "array",
|
"type": "array"
|
||||||
"$id": "type_4_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -436,12 +431,11 @@
|
|||||||
{
|
{
|
||||||
"description": "boolean type matches booleans",
|
"description": "boolean type matches booleans",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"type_5_0": {
|
||||||
"type": "boolean",
|
"type": "boolean"
|
||||||
"$id": "type_5_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -539,12 +533,11 @@
|
|||||||
{
|
{
|
||||||
"description": "null type matches only the null object",
|
"description": "null type matches only the null object",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"type_6_0": {
|
||||||
"type": "null",
|
"type": "null"
|
||||||
"$id": "type_6_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -642,15 +635,14 @@
|
|||||||
{
|
{
|
||||||
"description": "multiple types can be specified in an array",
|
"description": "multiple types can be specified in an array",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"type_7_0": {
|
||||||
"type": [
|
"type": [
|
||||||
"integer",
|
"integer",
|
||||||
"string"
|
"string"
|
||||||
],
|
]
|
||||||
"$id": "type_7_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -721,14 +713,13 @@
|
|||||||
{
|
{
|
||||||
"description": "type as array with one item",
|
"description": "type as array with one item",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"type_8_0": {
|
||||||
"type": [
|
"type": [
|
||||||
"string"
|
"string"
|
||||||
],
|
]
|
||||||
"$id": "type_8_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -754,16 +745,15 @@
|
|||||||
{
|
{
|
||||||
"description": "type: array or object",
|
"description": "type: array or object",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"type_9_0": {
|
||||||
"type": [
|
"type": [
|
||||||
"array",
|
"array",
|
||||||
"object"
|
"object"
|
||||||
],
|
],
|
||||||
"items": {},
|
"items": {}
|
||||||
"$id": "type_9_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -820,17 +810,16 @@
|
|||||||
{
|
{
|
||||||
"description": "type: array, object or null",
|
"description": "type: array, object or null",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"type_10_0": {
|
||||||
"type": [
|
"type": [
|
||||||
"array",
|
"array",
|
||||||
"object",
|
"object",
|
||||||
"null"
|
"null"
|
||||||
],
|
],
|
||||||
"items": {},
|
"items": {}
|
||||||
"$id": "type_10_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -887,13 +876,12 @@
|
|||||||
{
|
{
|
||||||
"description": "extensible: true allows extra properties",
|
"description": "extensible: true allows extra properties",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"type_11_0": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"extensible": true,
|
"extensible": true
|
||||||
"$id": "type_11_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
|
|||||||
@ -2,8 +2,8 @@
|
|||||||
{
|
{
|
||||||
"description": "object properties validation",
|
"description": "object properties validation",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"properties_0_0": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"foo": {
|
"foo": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
@ -11,10 +11,9 @@
|
|||||||
"bar": {
|
"bar": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
"$id": "properties_0_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -85,15 +84,14 @@
|
|||||||
{
|
{
|
||||||
"description": "properties with boolean schema",
|
"description": "properties with boolean schema",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"properties_1_0": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"foo": true,
|
"foo": true,
|
||||||
"bar": false
|
"bar": false
|
||||||
},
|
}
|
||||||
"$id": "properties_1_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -144,8 +142,8 @@
|
|||||||
{
|
{
|
||||||
"description": "properties with escaped characters",
|
"description": "properties with escaped characters",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"properties_2_0": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"foo\nbar": {
|
"foo\nbar": {
|
||||||
"type": "number"
|
"type": "number"
|
||||||
@ -165,10 +163,9 @@
|
|||||||
"foo\fbar": {
|
"foo\fbar": {
|
||||||
"type": "number"
|
"type": "number"
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
"$id": "properties_2_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -208,16 +205,15 @@
|
|||||||
{
|
{
|
||||||
"description": "properties with null valued instance properties",
|
"description": "properties with null valued instance properties",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"properties_3_0": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"foo": {
|
"foo": {
|
||||||
"type": "null"
|
"type": "null"
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
"$id": "properties_3_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -237,8 +233,8 @@
|
|||||||
"description": "properties whose names are Javascript object property names",
|
"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.",
|
"comment": "Ensure JS implementations don't universally consider e.g. __proto__ to always be present in an object.",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"properties_4_0": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"__proto__": {
|
"__proto__": {
|
||||||
"type": "number"
|
"type": "number"
|
||||||
@ -253,10 +249,9 @@
|
|||||||
"constructor": {
|
"constructor": {
|
||||||
"type": "number"
|
"type": "number"
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
"$id": "properties_4_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -343,17 +338,16 @@
|
|||||||
{
|
{
|
||||||
"description": "extensible: true allows extra properties",
|
"description": "extensible: true allows extra properties",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"properties_5_0": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"foo": {
|
"foo": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"extensible": true,
|
"extensible": true
|
||||||
"$id": "properties_5_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -373,16 +367,15 @@
|
|||||||
{
|
{
|
||||||
"description": "strict by default: extra properties invalid",
|
"description": "strict by default: extra properties invalid",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"properties_6_0": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"foo": {
|
"foo": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
"$id": "properties_6_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -402,8 +395,8 @@
|
|||||||
{
|
{
|
||||||
"description": "inheritance: nested object inherits strictness from strict parent",
|
"description": "inheritance: nested object inherits strictness from strict parent",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"properties_7_0": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"nested": {
|
"nested": {
|
||||||
"properties": {
|
"properties": {
|
||||||
@ -412,10 +405,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
"$id": "properties_7_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -437,8 +429,8 @@
|
|||||||
{
|
{
|
||||||
"description": "override: nested object allows extra properties if extensible: true",
|
"description": "override: nested object allows extra properties if extensible: true",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"properties_8_0": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"nested": {
|
"nested": {
|
||||||
"extensible": true,
|
"extensible": true,
|
||||||
@ -448,10 +440,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
"$id": "properties_8_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -473,8 +464,8 @@
|
|||||||
{
|
{
|
||||||
"description": "inheritance: nested object inherits looseness from loose parent",
|
"description": "inheritance: nested object inherits looseness from loose parent",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"properties_9_0": {
|
||||||
"extensible": true,
|
"extensible": true,
|
||||||
"properties": {
|
"properties": {
|
||||||
"nested": {
|
"nested": {
|
||||||
@ -484,10 +475,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
"$id": "properties_9_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -509,8 +499,8 @@
|
|||||||
{
|
{
|
||||||
"description": "override: nested object enforces strictness if extensible: false",
|
"description": "override: nested object enforces strictness if extensible: false",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"properties_10_0": {
|
||||||
"extensible": true,
|
"extensible": true,
|
||||||
"properties": {
|
"properties": {
|
||||||
"nested": {
|
"nested": {
|
||||||
@ -521,10 +511,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
"$id": "properties_10_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -546,8 +535,8 @@
|
|||||||
{
|
{
|
||||||
"description": "arrays: inline items inherit strictness from strict parent",
|
"description": "arrays: inline items inherit strictness from strict parent",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"properties_11_0": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"list": {
|
"list": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
@ -559,10 +548,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
"$id": "properties_11_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -586,8 +574,8 @@
|
|||||||
{
|
{
|
||||||
"description": "arrays: inline items inherit looseness from loose parent",
|
"description": "arrays: inline items inherit looseness from loose parent",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"properties_12_0": {
|
||||||
"extensible": true,
|
"extensible": true,
|
||||||
"properties": {
|
"properties": {
|
||||||
"list": {
|
"list": {
|
||||||
@ -600,10 +588,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
"$id": "properties_12_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
|
|||||||
@ -2,15 +2,14 @@
|
|||||||
{
|
{
|
||||||
"description": "propertyNames validation",
|
"description": "propertyNames validation",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"propertyNames_0_0": {
|
||||||
"propertyNames": {
|
"propertyNames": {
|
||||||
"maxLength": 3
|
"maxLength": 3
|
||||||
},
|
},
|
||||||
"extensible": true,
|
"extensible": true
|
||||||
"$id": "propertyNames_0_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -83,15 +82,14 @@
|
|||||||
{
|
{
|
||||||
"description": "propertyNames validation with pattern",
|
"description": "propertyNames validation with pattern",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"propertyNames_1_0": {
|
||||||
"propertyNames": {
|
"propertyNames": {
|
||||||
"pattern": "^a+$"
|
"pattern": "^a+$"
|
||||||
},
|
},
|
||||||
"extensible": true,
|
"extensible": true
|
||||||
"$id": "propertyNames_1_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -132,13 +130,12 @@
|
|||||||
{
|
{
|
||||||
"description": "propertyNames with boolean schema true",
|
"description": "propertyNames with boolean schema true",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"propertyNames_2_0": {
|
||||||
"propertyNames": true,
|
"propertyNames": true,
|
||||||
"extensible": true,
|
"extensible": true
|
||||||
"$id": "propertyNames_2_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -166,13 +163,12 @@
|
|||||||
{
|
{
|
||||||
"description": "propertyNames with boolean schema false",
|
"description": "propertyNames with boolean schema false",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"propertyNames_3_0": {
|
||||||
"propertyNames": false,
|
"propertyNames": false,
|
||||||
"extensible": true,
|
"extensible": true
|
||||||
"$id": "propertyNames_3_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -200,15 +196,14 @@
|
|||||||
{
|
{
|
||||||
"description": "propertyNames with const",
|
"description": "propertyNames with const",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"propertyNames_4_0": {
|
||||||
"propertyNames": {
|
"propertyNames": {
|
||||||
"const": "foo"
|
"const": "foo"
|
||||||
},
|
},
|
||||||
"extensible": true,
|
"extensible": true
|
||||||
"$id": "propertyNames_4_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -247,18 +242,17 @@
|
|||||||
{
|
{
|
||||||
"description": "propertyNames with enum",
|
"description": "propertyNames with enum",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"propertyNames_5_0": {
|
||||||
"propertyNames": {
|
"propertyNames": {
|
||||||
"enum": [
|
"enum": [
|
||||||
"foo",
|
"foo",
|
||||||
"bar"
|
"bar"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"extensible": true,
|
"extensible": true
|
||||||
"$id": "propertyNames_5_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -309,15 +303,14 @@
|
|||||||
{
|
{
|
||||||
"description": "extensible: true allows extra properties (checked by propertyNames)",
|
"description": "extensible: true allows extra properties (checked by propertyNames)",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"propertyNames_6_0": {
|
||||||
"propertyNames": {
|
"propertyNames": {
|
||||||
"maxLength": 3
|
"maxLength": 3
|
||||||
},
|
},
|
||||||
"extensible": true,
|
"extensible": true
|
||||||
"$id": "propertyNames_6_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -2,18 +2,17 @@
|
|||||||
{
|
{
|
||||||
"description": "required validation",
|
"description": "required validation",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"required_0_0": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"foo": {},
|
"foo": {},
|
||||||
"bar": {}
|
"bar": {}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"foo"
|
"foo"
|
||||||
],
|
]
|
||||||
"$id": "required_0_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -88,14 +87,13 @@
|
|||||||
{
|
{
|
||||||
"description": "required default validation",
|
"description": "required default validation",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"required_1_0": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"foo": {}
|
"foo": {}
|
||||||
},
|
}
|
||||||
"$id": "required_1_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -112,15 +110,14 @@
|
|||||||
{
|
{
|
||||||
"description": "required with empty array",
|
"description": "required with empty array",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"required_2_0": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"foo": {}
|
"foo": {}
|
||||||
},
|
},
|
||||||
"required": [],
|
"required": []
|
||||||
"$id": "required_2_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -137,8 +134,8 @@
|
|||||||
{
|
{
|
||||||
"description": "required with escaped characters",
|
"description": "required with escaped characters",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"required_3_0": {
|
||||||
"required": [
|
"required": [
|
||||||
"foo\nbar",
|
"foo\nbar",
|
||||||
"foo\"bar",
|
"foo\"bar",
|
||||||
@ -147,10 +144,9 @@
|
|||||||
"foo\tbar",
|
"foo\tbar",
|
||||||
"foo\fbar"
|
"foo\fbar"
|
||||||
],
|
],
|
||||||
"extensible": true,
|
"extensible": true
|
||||||
"$id": "required_3_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -187,17 +183,16 @@
|
|||||||
"description": "required properties whose names are Javascript object property names",
|
"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.",
|
"comment": "Ensure JS implementations don't universally consider e.g. __proto__ to always be present in an object.",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"required_4_0": {
|
||||||
"required": [
|
"required": [
|
||||||
"__proto__",
|
"__proto__",
|
||||||
"toString",
|
"toString",
|
||||||
"constructor"
|
"constructor"
|
||||||
],
|
],
|
||||||
"extensible": true,
|
"extensible": true
|
||||||
"$id": "required_4_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -284,15 +279,14 @@
|
|||||||
{
|
{
|
||||||
"description": "extensible: true allows extra properties in required",
|
"description": "extensible: true allows extra properties in required",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"required_5_0": {
|
||||||
"required": [
|
"required": [
|
||||||
"foo"
|
"foo"
|
||||||
],
|
],
|
||||||
"extensible": true,
|
"extensible": true
|
||||||
"$id": "required_5_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
|
|||||||
@ -2,13 +2,12 @@
|
|||||||
{
|
{
|
||||||
"description": "uniqueItems validation",
|
"description": "uniqueItems validation",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"uniqueItems_0_0": {
|
||||||
"uniqueItems": true,
|
"uniqueItems": true,
|
||||||
"extensible": true,
|
"extensible": true
|
||||||
"$id": "uniqueItems_0_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -469,8 +468,8 @@
|
|||||||
{
|
{
|
||||||
"description": "uniqueItems with an array of items",
|
"description": "uniqueItems with an array of items",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"uniqueItems_1_0": {
|
||||||
"prefixItems": [
|
"prefixItems": [
|
||||||
{
|
{
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
@ -480,10 +479,9 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"uniqueItems": true,
|
"uniqueItems": true,
|
||||||
"extensible": true,
|
"extensible": true
|
||||||
"$id": "uniqueItems_1_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -595,8 +593,8 @@
|
|||||||
{
|
{
|
||||||
"description": "uniqueItems with an array of items and additionalItems=false",
|
"description": "uniqueItems with an array of items and additionalItems=false",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"uniqueItems_2_0": {
|
||||||
"prefixItems": [
|
"prefixItems": [
|
||||||
{
|
{
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
@ -606,10 +604,9 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"uniqueItems": true,
|
"uniqueItems": true,
|
||||||
"items": false,
|
"items": false
|
||||||
"$id": "uniqueItems_2_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -678,13 +675,12 @@
|
|||||||
{
|
{
|
||||||
"description": "uniqueItems=false validation",
|
"description": "uniqueItems=false validation",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"uniqueItems_3_0": {
|
||||||
"uniqueItems": false,
|
"uniqueItems": false,
|
||||||
"extensible": true,
|
"extensible": true
|
||||||
"$id": "uniqueItems_3_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -924,8 +920,8 @@
|
|||||||
{
|
{
|
||||||
"description": "uniqueItems=false with an array of items",
|
"description": "uniqueItems=false with an array of items",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"uniqueItems_4_0": {
|
||||||
"prefixItems": [
|
"prefixItems": [
|
||||||
{
|
{
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
@ -935,10 +931,9 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"uniqueItems": false,
|
"uniqueItems": false,
|
||||||
"extensible": true,
|
"extensible": true
|
||||||
"$id": "uniqueItems_4_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -1050,8 +1045,8 @@
|
|||||||
{
|
{
|
||||||
"description": "uniqueItems=false with an array of items and additionalItems=false",
|
"description": "uniqueItems=false with an array of items and additionalItems=false",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"uniqueItems_5_0": {
|
||||||
"prefixItems": [
|
"prefixItems": [
|
||||||
{
|
{
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
@ -1061,10 +1056,9 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"uniqueItems": false,
|
"uniqueItems": false,
|
||||||
"items": false,
|
"items": false
|
||||||
"$id": "uniqueItems_5_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
@ -1133,13 +1127,12 @@
|
|||||||
{
|
{
|
||||||
"description": "extensible: true allows extra items in uniqueItems",
|
"description": "extensible: true allows extra items in uniqueItems",
|
||||||
"database": {
|
"database": {
|
||||||
"schemas": [
|
"schemas": {
|
||||||
{
|
"uniqueItems_6_0": {
|
||||||
"uniqueItems": true,
|
"uniqueItems": true,
|
||||||
"extensible": true,
|
"extensible": true
|
||||||
"$id": "uniqueItems_6_0"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
{
|
{
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
use crate::database::schema::Schema;
|
use crate::database::schema::Schema;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
@ -8,5 +9,6 @@ pub struct Enum {
|
|||||||
pub module: String,
|
pub module: String,
|
||||||
pub source: String,
|
pub source: String,
|
||||||
pub values: Vec<String>,
|
pub values: Vec<String>,
|
||||||
pub schemas: Vec<Schema>,
|
#[serde(default)]
|
||||||
|
pub schemas: std::collections::BTreeMap<String, Arc<Schema>>,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,9 +2,9 @@ pub mod edge;
|
|||||||
pub mod r#enum;
|
pub mod r#enum;
|
||||||
pub mod executors;
|
pub mod executors;
|
||||||
pub mod formats;
|
pub mod formats;
|
||||||
|
pub mod object;
|
||||||
pub mod page;
|
pub mod page;
|
||||||
pub mod punc;
|
pub mod punc;
|
||||||
pub mod object;
|
|
||||||
pub mod relation;
|
pub mod relation;
|
||||||
pub mod schema;
|
pub mod schema;
|
||||||
pub mod r#type;
|
pub mod r#type;
|
||||||
@ -60,10 +60,17 @@ impl Database {
|
|||||||
db.enums.insert(def.name.clone(), def);
|
db.enums.insert(def.name.clone(), def);
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
let name = item
|
||||||
|
.get("name")
|
||||||
|
.and_then(|v| v.as_str())
|
||||||
|
.unwrap_or("unknown");
|
||||||
errors.push(crate::drop::Error {
|
errors.push(crate::drop::Error {
|
||||||
code: "DATABASE_ENUM_PARSE_FAILED".to_string(),
|
code: "DATABASE_ENUM_PARSE_FAILED".to_string(),
|
||||||
message: format!("Failed to parse database enum: {}", e),
|
message: format!("Failed to parse database enum '{}': {}", name, e),
|
||||||
details: crate::drop::ErrorDetails::default(),
|
details: crate::drop::ErrorDetails {
|
||||||
|
context: Some(serde_json::json!(name)),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -77,10 +84,17 @@ impl Database {
|
|||||||
db.types.insert(def.name.clone(), def);
|
db.types.insert(def.name.clone(), def);
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
let name = item
|
||||||
|
.get("name")
|
||||||
|
.and_then(|v| v.as_str())
|
||||||
|
.unwrap_or("unknown");
|
||||||
errors.push(crate::drop::Error {
|
errors.push(crate::drop::Error {
|
||||||
code: "DATABASE_TYPE_PARSE_FAILED".to_string(),
|
code: "DATABASE_TYPE_PARSE_FAILED".to_string(),
|
||||||
message: format!("Failed to parse database type: {}", e),
|
message: format!("Failed to parse database type '{}': {}", name, e),
|
||||||
details: crate::drop::ErrorDetails::default(),
|
details: crate::drop::ErrorDetails {
|
||||||
|
context: Some(serde_json::json!(name)),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -98,10 +112,17 @@ impl Database {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
let constraint = item
|
||||||
|
.get("constraint")
|
||||||
|
.and_then(|v| v.as_str())
|
||||||
|
.unwrap_or("unknown");
|
||||||
errors.push(crate::drop::Error {
|
errors.push(crate::drop::Error {
|
||||||
code: "DATABASE_RELATION_PARSE_FAILED".to_string(),
|
code: "DATABASE_RELATION_PARSE_FAILED".to_string(),
|
||||||
message: format!("Failed to parse database relation: {}", e),
|
message: format!("Failed to parse database relation '{}': {}", constraint, e),
|
||||||
details: crate::drop::ErrorDetails::default(),
|
details: crate::drop::ErrorDetails {
|
||||||
|
context: Some(serde_json::json!(constraint)),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -115,33 +136,37 @@ impl Database {
|
|||||||
db.puncs.insert(def.name.clone(), def);
|
db.puncs.insert(def.name.clone(), def);
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
let name = item
|
||||||
|
.get("name")
|
||||||
|
.and_then(|v| v.as_str())
|
||||||
|
.unwrap_or("unknown");
|
||||||
errors.push(crate::drop::Error {
|
errors.push(crate::drop::Error {
|
||||||
code: "DATABASE_PUNC_PARSE_FAILED".to_string(),
|
code: "DATABASE_PUNC_PARSE_FAILED".to_string(),
|
||||||
message: format!("Failed to parse database punc: {}", e),
|
message: format!("Failed to parse database punc '{}': {}", name, e),
|
||||||
details: crate::drop::ErrorDetails::default(),
|
details: crate::drop::ErrorDetails {
|
||||||
|
context: Some(serde_json::json!(name)),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(arr) = val.get("schemas").and_then(|v| v.as_array()) {
|
if let Some(map) = val.get("schemas").and_then(|v| v.as_object()) {
|
||||||
for (i, item) in arr.iter().enumerate() {
|
for (key, item) in map.iter() {
|
||||||
match serde_json::from_value::<Schema>(item.clone()) {
|
match serde_json::from_value::<Schema>(item.clone()) {
|
||||||
Ok(mut schema) => {
|
Ok(schema) => {
|
||||||
let id = schema
|
db.schemas.insert(key.clone(), Arc::new(schema));
|
||||||
.obj
|
|
||||||
.id
|
|
||||||
.clone()
|
|
||||||
.unwrap_or_else(|| format!("schema_{}", i));
|
|
||||||
schema.obj.id = Some(id.clone());
|
|
||||||
db.schemas.insert(id, Arc::new(schema));
|
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
errors.push(crate::drop::Error {
|
errors.push(crate::drop::Error {
|
||||||
code: "DATABASE_SCHEMA_PARSE_FAILED".to_string(),
|
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(),
|
details: crate::drop::ErrorDetails {
|
||||||
|
context: Some(serde_json::json!(key)),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -185,21 +210,28 @@ impl Database {
|
|||||||
|
|
||||||
pub fn compile(&mut self, errors: &mut Vec<crate::drop::Error>) {
|
pub fn compile(&mut self, errors: &mut Vec<crate::drop::Error>) {
|
||||||
let mut harvested = Vec::new();
|
let mut harvested = Vec::new();
|
||||||
for schema_arc in self.schemas.values_mut() {
|
for (id, schema_arc) in &self.schemas {
|
||||||
if let Some(s) = std::sync::Arc::get_mut(schema_arc) {
|
crate::database::schema::Schema::collect_schemas(
|
||||||
s.collect_schemas(None, &mut harvested, errors);
|
schema_arc,
|
||||||
}
|
id,
|
||||||
|
id.clone(),
|
||||||
|
&mut harvested,
|
||||||
|
errors,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
for (id, schema) in harvested {
|
for (id, schema_arc) in harvested {
|
||||||
self.schemas.insert(id, Arc::new(schema));
|
self.schemas.insert(id, schema_arc);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.collect_schemas(errors);
|
self.collect_schemas(errors);
|
||||||
|
|
||||||
// Mathematically evaluate all property inheritances, formats, schemas, and foreign key edges topographically over OnceLocks
|
// Mathematically evaluate all property inheritances, formats, schemas, and foreign key edges topographically over OnceLocks
|
||||||
let mut visited = std::collections::HashSet::new();
|
for (id, schema_arc) in &self.schemas {
|
||||||
for schema_arc in self.schemas.values() {
|
// First compile pass initializes exact structural root_id mapping to resolve DB constraints
|
||||||
schema_arc.as_ref().compile(self, &mut visited, errors);
|
let root_id = id.split('/').next().unwrap_or(id);
|
||||||
|
schema_arc
|
||||||
|
.as_ref()
|
||||||
|
.compile(self, root_id, id.clone(), errors);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -209,23 +241,44 @@ impl Database {
|
|||||||
// Pass 1: Extract all Schemas structurally off top level definitions into the master registry.
|
// Pass 1: Extract all Schemas structurally off top level definitions into the master registry.
|
||||||
// Validate every node recursively via string filters natively!
|
// Validate every node recursively via string filters natively!
|
||||||
for type_def in self.types.values() {
|
for type_def in self.types.values() {
|
||||||
for mut schema in type_def.schemas.clone() {
|
for (id, schema_arc) in &type_def.schemas {
|
||||||
schema.collect_schemas(None, &mut to_insert, errors);
|
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 punc_def in self.puncs.values() {
|
||||||
for mut schema in punc_def.schemas.clone() {
|
for (id, schema_arc) in &punc_def.schemas {
|
||||||
schema.collect_schemas(None, &mut to_insert, errors);
|
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 enum_def in self.enums.values() {
|
||||||
for mut schema in enum_def.schemas.clone() {
|
for (id, schema_arc) in &enum_def.schemas {
|
||||||
schema.collect_schemas(None, &mut to_insert, errors);
|
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 {
|
for (id, schema_arc) in to_insert {
|
||||||
self.schemas.insert(id, Arc::new(schema));
|
self.schemas.insert(id, schema_arc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -260,10 +313,10 @@ impl Database {
|
|||||||
all_rels.sort_by(|a, b| a.constraint.cmp(&b.constraint));
|
all_rels.sort_by(|a, b| a.constraint.cmp(&b.constraint));
|
||||||
|
|
||||||
for rel in all_rels {
|
for rel in all_rels {
|
||||||
let mut is_forward =
|
let mut is_forward = p_def.hierarchy.contains(&rel.source_type)
|
||||||
p_def.hierarchy.contains(&rel.source_type) && c_def.hierarchy.contains(&rel.destination_type);
|
&& c_def.hierarchy.contains(&rel.destination_type);
|
||||||
let is_reverse =
|
let is_reverse = p_def.hierarchy.contains(&rel.destination_type)
|
||||||
p_def.hierarchy.contains(&rel.destination_type) && c_def.hierarchy.contains(&rel.source_type);
|
&& c_def.hierarchy.contains(&rel.source_type);
|
||||||
|
|
||||||
// Structural Cardinality Filtration:
|
// Structural Cardinality Filtration:
|
||||||
// If the schema requires a collection (Array), it is mathematically impossible for a pure
|
// If the schema requires a collection (Array), it is mathematically impossible for a pure
|
||||||
@ -285,7 +338,7 @@ impl Database {
|
|||||||
// Abort relation discovery early if no hierarchical inheritance match was found
|
// Abort relation discovery early if no hierarchical inheritance match was found
|
||||||
if matching_rels.is_empty() {
|
if matching_rels.is_empty() {
|
||||||
let mut details = crate::drop::ErrorDetails {
|
let mut details = crate::drop::ErrorDetails {
|
||||||
path: path.to_string(),
|
path: Some(path.to_string()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
if let Some(sid) = schema_id {
|
if let Some(sid) = schema_id {
|
||||||
@ -384,7 +437,7 @@ impl Database {
|
|||||||
// and forces a clean structural error for the architect.
|
// and forces a clean structural error for the architect.
|
||||||
if !resolved {
|
if !resolved {
|
||||||
let mut details = crate::drop::ErrorDetails {
|
let mut details = crate::drop::ErrorDetails {
|
||||||
path: path.to_string(),
|
path: Some(path.to_string()),
|
||||||
context: serde_json::to_value(&matching_rels).ok(),
|
context: serde_json::to_value(&matching_rels).ok(),
|
||||||
cause: Some("Multiple conflicting constraints found matching prefixes".to_string()),
|
cause: Some("Multiple conflicting constraints found matching prefixes".to_string()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
|
use crate::database::schema::Schema;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::sync::OnceLock;
|
use std::sync::OnceLock;
|
||||||
use crate::database::schema::Schema;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct Case {
|
pub struct Case {
|
||||||
@ -19,9 +19,6 @@ pub struct Case {
|
|||||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||||
pub struct SchemaObject {
|
pub struct SchemaObject {
|
||||||
// Core Schema Keywords
|
// Core Schema Keywords
|
||||||
#[serde(rename = "$id")]
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub id: Option<String>,
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub description: Option<String>,
|
pub description: Option<String>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
@ -40,7 +37,7 @@ pub struct SchemaObject {
|
|||||||
#[serde(rename = "additionalProperties")]
|
#[serde(rename = "additionalProperties")]
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub additional_properties: Option<Arc<Schema>>,
|
pub additional_properties: Option<Arc<Schema>>,
|
||||||
#[serde(rename = "$family")]
|
#[serde(rename = "family")]
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub family: Option<String>,
|
pub family: Option<String>,
|
||||||
|
|
||||||
@ -157,12 +154,15 @@ pub struct SchemaObject {
|
|||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub extensible: Option<bool>,
|
pub extensible: Option<bool>,
|
||||||
|
|
||||||
#[serde(rename = "compiledProperties")]
|
// Contains ALL structural fields perfectly flattened from the ENTIRE Database inheritance tree (e.g. `entity` fields like `id`) as well as local fields hidden inside conditional `cases` blocks.
|
||||||
|
// This JSON exported array gives clients absolute deterministic visibility to O(1) validation and masking bounds without duplicating structural memory.
|
||||||
|
#[serde(rename = "compiledPropertyNames")]
|
||||||
#[serde(skip_deserializing)]
|
#[serde(skip_deserializing)]
|
||||||
#[serde(skip_serializing_if = "crate::database::object::is_once_lock_vec_empty")]
|
#[serde(skip_serializing_if = "crate::database::object::is_once_lock_vec_empty")]
|
||||||
#[serde(serialize_with = "crate::database::object::serialize_once_lock")]
|
#[serde(serialize_with = "crate::database::object::serialize_once_lock")]
|
||||||
pub compiled_property_names: OnceLock<Vec<String>>,
|
pub compiled_property_names: OnceLock<Vec<String>>,
|
||||||
|
|
||||||
|
// Internal structural representation caching active AST Node maps. Unlike the Go framework counterpart, the JSPG implementation DOES natively include ALL ancestral inheritance boundary schemas because it compiles locally against the raw database graph.
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
pub compiled_properties: OnceLock<BTreeMap<String, Arc<Schema>>>,
|
pub compiled_properties: OnceLock<BTreeMap<String, Arc<Schema>>>,
|
||||||
|
|
||||||
@ -176,7 +176,7 @@ pub struct SchemaObject {
|
|||||||
#[serde(skip_deserializing)]
|
#[serde(skip_deserializing)]
|
||||||
#[serde(skip_serializing_if = "crate::database::object::is_once_lock_map_empty")]
|
#[serde(skip_serializing_if = "crate::database::object::is_once_lock_map_empty")]
|
||||||
#[serde(serialize_with = "crate::database::object::serialize_once_lock")]
|
#[serde(serialize_with = "crate::database::object::serialize_once_lock")]
|
||||||
pub compiled_options: OnceLock<BTreeMap<String, String>>,
|
pub compiled_options: OnceLock<BTreeMap<String, (Option<usize>, Option<String>)>>,
|
||||||
|
|
||||||
#[serde(rename = "compiledEdges")]
|
#[serde(rename = "compiledEdges")]
|
||||||
#[serde(skip_deserializing)]
|
#[serde(skip_deserializing)]
|
||||||
@ -275,93 +275,49 @@ pub fn is_primitive_type(t: &str) -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl SchemaObject {
|
impl SchemaObject {
|
||||||
pub fn identifier(&self) -> Option<String> {
|
pub fn get_discriminator_value(&self, dim: &str, schema_id: &str) -> Option<String> {
|
||||||
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<String> {
|
|
||||||
let is_split = self
|
let is_split = self
|
||||||
.compiled_properties
|
.compiled_properties
|
||||||
.get()
|
.get()
|
||||||
.map_or(false, |p| p.contains_key("kind"));
|
.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::<Vec<_>>())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 dim == "kind" {
|
||||||
if let Some(id) = &self.id {
|
let base = schema_id.split('/').last().unwrap_or(schema_id);
|
||||||
let base = id.split('/').last().unwrap_or(id);
|
if let Some(idx) = base.rfind('.') {
|
||||||
if let Some(idx) = base.rfind('.') {
|
return Some(base[..idx].to_string());
|
||||||
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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if dim == "type" {
|
if dim == "type" {
|
||||||
if let Some(id) = &self.id {
|
let base = schema_id.split('/').last().unwrap_or(schema_id);
|
||||||
let base = id.split('/').last().unwrap_or(id);
|
if is_split {
|
||||||
if is_split {
|
return Some(base.split('.').next_back().unwrap_or(base).to_string());
|
||||||
return Some(base.split('.').next_back().unwrap_or(base).to_string());
|
} else {
|
||||||
} else {
|
return Some(base.to_string());
|
||||||
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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
None
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
use crate::database::page::Page;
|
use crate::database::page::Page;
|
||||||
use crate::database::schema::Schema;
|
use crate::database::schema::Schema;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
@ -16,5 +17,5 @@ pub struct Punc {
|
|||||||
pub get: Option<String>,
|
pub get: Option<String>,
|
||||||
pub page: Option<Page>,
|
pub page: Option<Page>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub schemas: Vec<Schema>,
|
pub schemas: std::collections::BTreeMap<String, Arc<Schema>>,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
|
use crate::database::object::*;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use crate::database::object::*;
|
|
||||||
#[derive(Debug, Clone, Serialize, Default)]
|
#[derive(Debug, Clone, Serialize, Default)]
|
||||||
pub struct Schema {
|
pub struct Schema {
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
@ -26,19 +26,14 @@ impl Schema {
|
|||||||
pub fn compile(
|
pub fn compile(
|
||||||
&self,
|
&self,
|
||||||
db: &crate::database::Database,
|
db: &crate::database::Database,
|
||||||
visited: &mut std::collections::HashSet<String>,
|
root_id: &str,
|
||||||
|
path: String,
|
||||||
errors: &mut Vec<crate::drop::Error>,
|
errors: &mut Vec<crate::drop::Error>,
|
||||||
) {
|
) {
|
||||||
if self.obj.compiled_properties.get().is_some() {
|
if self.obj.compiled_properties.get().is_some() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(id) = &self.obj.id {
|
|
||||||
if !visited.insert(id.clone()) {
|
|
||||||
return; // Break cyclical resolution
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(format_str) = &self.obj.format {
|
if let Some(format_str) = &self.obj.format {
|
||||||
if let Some(fmt) = crate::database::formats::FORMATS.get(format_str.as_str()) {
|
if let Some(fmt) = crate::database::formats::FORMATS.get(format_str.as_str()) {
|
||||||
let _ = self
|
let _ = self
|
||||||
@ -75,7 +70,7 @@ impl Schema {
|
|||||||
if let Some(crate::database::object::SchemaTypeOrArray::Single(t)) = &self.obj.type_ {
|
if let Some(crate::database::object::SchemaTypeOrArray::Single(t)) = &self.obj.type_ {
|
||||||
if !crate::database::object::is_primitive_type(t) {
|
if !crate::database::object::is_primitive_type(t) {
|
||||||
if let Some(parent) = db.schemas.get(t) {
|
if let Some(parent) = db.schemas.get(t) {
|
||||||
parent.as_ref().compile(db, visited, errors);
|
parent.as_ref().compile(db, t, t.clone(), errors);
|
||||||
if let Some(p_props) = parent.obj.compiled_properties.get() {
|
if let Some(p_props) = parent.obj.compiled_properties.get() {
|
||||||
props.extend(p_props.clone());
|
props.extend(p_props.clone());
|
||||||
}
|
}
|
||||||
@ -95,11 +90,12 @@ impl Schema {
|
|||||||
errors.push(crate::drop::Error {
|
errors.push(crate::drop::Error {
|
||||||
code: "MULTIPLE_INHERITANCE_PROHIBITED".to_string(),
|
code: "MULTIPLE_INHERITANCE_PROHIBITED".to_string(),
|
||||||
message: format!(
|
message: format!(
|
||||||
"Schema '{}' attempts to extend multiple custom object pointers in its type array. Use 'oneOf' for polymorphism and tagged unions.",
|
"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())
|
types
|
||||||
),
|
),
|
||||||
details: crate::drop::ErrorDetails {
|
details: crate::drop::ErrorDetails {
|
||||||
path: self.obj.identifier().unwrap_or("unknown".to_string()),
|
path: Some(path.clone()),
|
||||||
|
schema: Some(root_id.to_string()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -108,7 +104,7 @@ impl Schema {
|
|||||||
for t in types {
|
for t in types {
|
||||||
if !crate::database::object::is_primitive_type(t) {
|
if !crate::database::object::is_primitive_type(t) {
|
||||||
if let Some(parent) = db.schemas.get(t) {
|
if let Some(parent) = db.schemas.get(t) {
|
||||||
parent.as_ref().compile(db, visited, errors);
|
parent.as_ref().compile(db, t, t.clone(), errors);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -121,68 +117,77 @@ impl Schema {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Set the OnceLock!
|
// 3. Add cases conditionally-defined properties recursively
|
||||||
|
if let Some(cases) = &self.obj.cases {
|
||||||
|
for (i, c) in cases.iter().enumerate() {
|
||||||
|
if let Some(child) = &c.when {
|
||||||
|
child.compile(db, root_id, format!("{}/cases/{}/when", path, i), errors);
|
||||||
|
}
|
||||||
|
if let Some(child) = &c.then {
|
||||||
|
child.compile(db, root_id, format!("{}/cases/{}/then", path, i), errors);
|
||||||
|
if let Some(t_props) = child.obj.compiled_properties.get() {
|
||||||
|
props.extend(t_props.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(child) = &c.else_ {
|
||||||
|
child.compile(db, root_id, format!("{}/cases/{}/else", path, i), errors);
|
||||||
|
if let Some(e_props) = child.obj.compiled_properties.get() {
|
||||||
|
props.extend(e_props.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Set the OnceLock!
|
||||||
let _ = self.obj.compiled_properties.set(props.clone());
|
let _ = self.obj.compiled_properties.set(props.clone());
|
||||||
let mut names: Vec<String> = props.keys().cloned().collect();
|
let mut names: Vec<String> = props.keys().cloned().collect();
|
||||||
names.sort();
|
names.sort();
|
||||||
let _ = self.obj.compiled_property_names.set(names);
|
let _ = self.obj.compiled_property_names.set(names);
|
||||||
|
|
||||||
// 4. Compute Edges natively
|
// 5. Compute Edges natively
|
||||||
let schema_edges = self.compile_edges(db, visited, &props, errors);
|
let schema_edges = self.compile_edges(db, root_id, &path, &props, errors);
|
||||||
let _ = self.obj.compiled_edges.set(schema_edges);
|
let _ = self.obj.compiled_edges.set(schema_edges);
|
||||||
|
|
||||||
// 5. Build our inline children properties recursively NOW! (Depth-first search)
|
// 5. Build our inline children properties recursively NOW! (Depth-first search)
|
||||||
if let Some(local_props) = &self.obj.properties {
|
if let Some(local_props) = &self.obj.properties {
|
||||||
for child in local_props.values() {
|
for (k, child) in local_props {
|
||||||
child.compile(db, visited, errors);
|
child.compile(db, root_id, format!("{}/{}", path, k), errors);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(items) = &self.obj.items {
|
if let Some(items) = &self.obj.items {
|
||||||
items.compile(db, visited, errors);
|
items.compile(db, root_id, format!("{}/items", path), errors);
|
||||||
}
|
}
|
||||||
if let Some(pattern_props) = &self.obj.pattern_properties {
|
if let Some(pattern_props) = &self.obj.pattern_properties {
|
||||||
for child in pattern_props.values() {
|
for (k, child) in pattern_props {
|
||||||
child.compile(db, visited, errors);
|
child.compile(db, root_id, format!("{}/{}", path, k), errors);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(additional_props) = &self.obj.additional_properties {
|
if let Some(additional_props) = &self.obj.additional_properties {
|
||||||
additional_props.compile(db, visited, errors);
|
additional_props.compile(
|
||||||
|
db,
|
||||||
|
root_id,
|
||||||
|
format!("{}/additionalProperties", path),
|
||||||
|
errors,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if let Some(one_of) = &self.obj.one_of {
|
if let Some(one_of) = &self.obj.one_of {
|
||||||
for child in one_of {
|
for (i, child) in one_of.iter().enumerate() {
|
||||||
child.compile(db, visited, errors);
|
child.compile(db, root_id, format!("{}/oneOf/{}", path, i), errors);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(arr) = &self.obj.prefix_items {
|
if let Some(arr) = &self.obj.prefix_items {
|
||||||
for child in arr {
|
for (i, child) in arr.iter().enumerate() {
|
||||||
child.compile(db, visited, errors);
|
child.compile(db, root_id, format!("{}/prefixItems/{}", path, i), errors);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(child) = &self.obj.not {
|
if let Some(child) = &self.obj.not {
|
||||||
child.compile(db, visited, errors);
|
child.compile(db, root_id, format!("{}/not", path), errors);
|
||||||
}
|
}
|
||||||
if let Some(child) = &self.obj.contains {
|
if let Some(child) = &self.obj.contains {
|
||||||
child.compile(db, visited, errors);
|
child.compile(db, root_id, format!("{}/contains", path), errors);
|
||||||
}
|
|
||||||
if let Some(cases) = &self.obj.cases {
|
|
||||||
for c in cases {
|
|
||||||
if let Some(child) = &c.when {
|
|
||||||
child.compile(db, visited, errors);
|
|
||||||
}
|
|
||||||
if let Some(child) = &c.then {
|
|
||||||
child.compile(db, visited, errors);
|
|
||||||
}
|
|
||||||
if let Some(child) = &c.else_ {
|
|
||||||
child.compile(db, 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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Dynamically infers and compiles all structural database relationships between this Schema
|
/// Dynamically infers and compiles all structural database relationships between this Schema
|
||||||
@ -192,7 +197,8 @@ impl Schema {
|
|||||||
pub fn compile_edges(
|
pub fn compile_edges(
|
||||||
&self,
|
&self,
|
||||||
db: &crate::database::Database,
|
db: &crate::database::Database,
|
||||||
visited: &mut std::collections::HashSet<String>,
|
root_id: &str,
|
||||||
|
path: &str,
|
||||||
props: &std::collections::BTreeMap<String, std::sync::Arc<Schema>>,
|
props: &std::collections::BTreeMap<String, std::sync::Arc<Schema>>,
|
||||||
errors: &mut Vec<crate::drop::Error>,
|
errors: &mut Vec<crate::drop::Error>,
|
||||||
) -> std::collections::BTreeMap<String, crate::database::edge::Edge> {
|
) -> std::collections::BTreeMap<String, crate::database::edge::Edge> {
|
||||||
@ -201,21 +207,26 @@ impl Schema {
|
|||||||
// Determine the physical Database Table Name this schema structurally represents
|
// Determine the physical Database Table Name this schema structurally represents
|
||||||
// Plucks the polymorphic discriminator via dot-notation (e.g. extracting "person" from "full.person")
|
// Plucks the polymorphic discriminator via dot-notation (e.g. extracting "person" from "full.person")
|
||||||
let mut parent_type_name = None;
|
let mut parent_type_name = None;
|
||||||
|
|
||||||
if let Some(family) = &self.obj.family {
|
if let Some(family) = &self.obj.family {
|
||||||
|
// 1. Explicit horizontal routing
|
||||||
parent_type_name = Some(family.split('.').next_back().unwrap_or(family).to_string());
|
parent_type_name = Some(family.split('.').next_back().unwrap_or(family).to_string());
|
||||||
} else if let Some(identifier) = self.obj.identifier() {
|
} else if path == root_id {
|
||||||
parent_type_name = Some(
|
// 2. Root nodes trust their exact registry footprint
|
||||||
identifier
|
let base_type_name = path.split('.').next_back().unwrap_or(path).to_string();
|
||||||
.split('.')
|
if db.types.contains_key(&base_type_name) {
|
||||||
.next_back()
|
parent_type_name = Some(base_type_name);
|
||||||
.unwrap_or(&identifier)
|
}
|
||||||
.to_string(),
|
} 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 let Some(p_type) = parent_type_name {
|
if let Some(p_type) = parent_type_name {
|
||||||
// Proceed only if the resolved table physically exists within the Postgres Type hierarchy
|
// Proceed only if the resolved table physically exists within the Postgres Type hierarchy
|
||||||
if db.types.contains_key(&p_type) {
|
if let Some(type_def) = db.types.get(&p_type) {
|
||||||
// Iterate over all discovered schema boundaries mapped inside the object
|
// Iterate over all discovered schema boundaries mapped inside the object
|
||||||
for (prop_name, prop_schema) in props {
|
for (prop_name, prop_schema) in props {
|
||||||
let mut child_type_name = None;
|
let mut child_type_name = None;
|
||||||
@ -237,22 +248,41 @@ impl Schema {
|
|||||||
// Determine the physical Postgres table backing the nested child schema recursively
|
// Determine the physical Postgres table backing the nested child schema recursively
|
||||||
if let Some(family) = &target_schema.obj.family {
|
if let Some(family) = &target_schema.obj.family {
|
||||||
child_type_name = Some(family.split('.').next_back().unwrap_or(family).to_string());
|
child_type_name = Some(family.split('.').next_back().unwrap_or(family).to_string());
|
||||||
} else if let Some(ref_id) = target_schema.obj.identifier() {
|
} else if let Some(crate::database::object::SchemaTypeOrArray::Single(t)) =
|
||||||
child_type_name = Some(ref_id.split('.').next_back().unwrap_or(&ref_id).to_string());
|
&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 {
|
} else if let Some(arr) = &target_schema.obj.one_of {
|
||||||
if let Some(first) = arr.first() {
|
if let Some(first) = arr.first() {
|
||||||
if let Some(ref_id) = first.obj.identifier() {
|
if let Some(crate::database::object::SchemaTypeOrArray::Single(t)) = &first.obj.type_
|
||||||
child_type_name =
|
{
|
||||||
Some(ref_id.split('.').next_back().unwrap_or(&ref_id).to_string());
|
if !crate::database::object::is_primitive_type(t) {
|
||||||
|
child_type_name = Some(t.split('.').next_back().unwrap_or(t).to_string());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(c_type) = child_type_name {
|
if let Some(c_type) = child_type_name {
|
||||||
|
// Skip edge compilation for JSONB columns — they store data inline, not relationally.
|
||||||
|
// The physical column type from field_types is the single source of truth.
|
||||||
|
if let Some(ft) = type_def
|
||||||
|
.field_types
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|v| v.get(prop_name.as_str()))
|
||||||
|
.and_then(|v| v.as_str())
|
||||||
|
{
|
||||||
|
if ft == "jsonb" {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
if db.types.contains_key(&c_type) {
|
if db.types.contains_key(&c_type) {
|
||||||
// Ensure the child Schema's AST has accurately compiled its own physical property keys so we can
|
// 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.
|
// 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), errors);
|
||||||
|
|
||||||
if let Some(compiled_target_props) = target_schema.obj.compiled_properties.get() {
|
if let Some(compiled_target_props) = target_schema.obj.compiled_properties.get() {
|
||||||
let keys_for_ambiguity: Vec<String> =
|
let keys_for_ambiguity: Vec<String> =
|
||||||
compiled_target_props.keys().cloned().collect();
|
compiled_target_props.keys().cloned().collect();
|
||||||
@ -264,8 +294,8 @@ impl Schema {
|
|||||||
prop_name,
|
prop_name,
|
||||||
Some(&keys_for_ambiguity),
|
Some(&keys_for_ambiguity),
|
||||||
is_array,
|
is_array,
|
||||||
self.id.as_deref(),
|
Some(root_id),
|
||||||
&format!("/{}", prop_name),
|
&format!("{}/{}", path, prop_name),
|
||||||
errors,
|
errors,
|
||||||
) {
|
) {
|
||||||
schema_edges.insert(
|
schema_edges.insert(
|
||||||
@ -288,6 +318,8 @@ impl Schema {
|
|||||||
pub fn compile_polymorphism(
|
pub fn compile_polymorphism(
|
||||||
&self,
|
&self,
|
||||||
db: &crate::database::Database,
|
db: &crate::database::Database,
|
||||||
|
root_id: &str,
|
||||||
|
path: &str,
|
||||||
errors: &mut Vec<crate::drop::Error>,
|
errors: &mut Vec<crate::drop::Error>,
|
||||||
) {
|
) {
|
||||||
let mut options = std::collections::BTreeMap::new();
|
let mut options = std::collections::BTreeMap::new();
|
||||||
@ -312,7 +344,7 @@ impl Schema {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if db.schemas.contains_key(&target_id) {
|
if db.schemas.contains_key(&target_id) {
|
||||||
options.insert(var.to_string(), target_id);
|
options.insert(var.to_string(), (None, Some(target_id)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -321,12 +353,10 @@ impl Schema {
|
|||||||
|
|
||||||
let suffix = format!(".{}", family_base);
|
let suffix = format!(".{}", family_base);
|
||||||
|
|
||||||
for schema in &type_def.schemas {
|
for (id, schema) in &type_def.schemas {
|
||||||
if let Some(id) = &schema.obj.id {
|
if id.ends_with(&suffix) || id == &family_base {
|
||||||
if id.ends_with(&suffix) || id == &family_base {
|
if let Some(kind_val) = schema.obj.get_discriminator_value("kind", id) {
|
||||||
if let Some(kind_val) = schema.obj.get_discriminator_value("kind") {
|
options.insert(kind_val, (None, Some(id.to_string())));
|
||||||
options.insert(kind_val, id.to_string());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -335,50 +365,91 @@ impl Schema {
|
|||||||
} else if let Some(one_of) = &self.obj.one_of {
|
} else if let Some(one_of) = &self.obj.one_of {
|
||||||
let mut type_vals = std::collections::HashSet::new();
|
let mut type_vals = std::collections::HashSet::new();
|
||||||
let mut kind_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 {
|
for c in one_of {
|
||||||
if let Some(t_val) = c.obj.get_discriminator_value("type") {
|
let mut child_id = String::new();
|
||||||
type_vals.insert(t_val);
|
let mut child_is_primitive = false;
|
||||||
}
|
|
||||||
if let Some(k_val) = c.obj.get_discriminator_value("kind") {
|
|
||||||
kind_vals.insert(k_val);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
strategy = if type_vals.len() > 1 && type_vals.len() == one_of.len() {
|
if let Some(crate::database::object::SchemaTypeOrArray::Single(t)) = &c.obj.type_ {
|
||||||
"type".to_string()
|
if crate::database::object::is_primitive_type(t) {
|
||||||
} else if kind_vals.len() > 1 && kind_vals.len() == one_of.len() {
|
child_is_primitive = true;
|
||||||
"kind".to_string()
|
structural_types.insert(t.clone());
|
||||||
} else {
|
} else {
|
||||||
"".to_string()
|
child_id = t.clone();
|
||||||
};
|
structural_types.insert("object".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;
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
disjoint_base = false;
|
||||||
|
}
|
||||||
|
|
||||||
let mut target_id = c.obj.id.clone();
|
if !child_is_primitive {
|
||||||
if target_id.is_none() {
|
if let Some(t_val) = c.obj.get_discriminator_value("type", &child_id) {
|
||||||
if let Some(crate::database::object::SchemaTypeOrArray::Single(t)) = &c.obj.type_ {
|
type_vals.insert(t_val);
|
||||||
if !crate::database::object::is_primitive_type(t) {
|
}
|
||||||
target_id = Some(t.clone());
|
if let Some(k_val) = c.obj.get_discriminator_value("kind", &child_id) {
|
||||||
}
|
kind_vals.insert(k_val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
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() {
|
||||||
|
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: Some(path.to_string()),
|
||||||
|
schema: Some(root_id.to_string()),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
child_id = t.clone();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(tid) = target_id {
|
if let Some(val) = c.obj.get_discriminator_value(&strategy, &child_id) {
|
||||||
options.insert(val, tid);
|
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: Some(path.to_string()),
|
||||||
|
schema: Some(root_id.to_string()),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
options.insert(val, (Some(i), None));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -387,13 +458,21 @@ impl Schema {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !options.is_empty() {
|
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);
|
let _ = self.obj.compiled_options.set(options);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused_variables)]
|
#[allow(unused_variables)]
|
||||||
fn validate_identifier(id: &str, field_name: &str, errors: &mut Vec<crate::drop::Error>) {
|
fn validate_identifier(
|
||||||
|
id: &str,
|
||||||
|
field_name: &str,
|
||||||
|
root_id: &str,
|
||||||
|
path: &str,
|
||||||
|
errors: &mut Vec<crate::drop::Error>,
|
||||||
|
) {
|
||||||
#[cfg(not(test))]
|
#[cfg(not(test))]
|
||||||
for c in id.chars() {
|
for c in id.chars() {
|
||||||
if !c.is_ascii_lowercase() && !c.is_ascii_digit() && c != '_' && c != '.' {
|
if !c.is_ascii_lowercase() && !c.is_ascii_digit() && c != '_' && c != '.' {
|
||||||
@ -401,9 +480,13 @@ impl Schema {
|
|||||||
code: "INVALID_IDENTIFIER".to_string(),
|
code: "INVALID_IDENTIFIER".to_string(),
|
||||||
message: format!(
|
message: format!(
|
||||||
"Invalid character '{}' in JSON Schema '{}' property: '{}'. Identifiers must exclusively contain [a-z0-9_.]",
|
"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: Some(path.to_string()),
|
||||||
|
schema: Some(root_id.to_string()),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -411,116 +494,131 @@ impl Schema {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn collect_schemas(
|
pub fn collect_schemas(
|
||||||
&mut self,
|
schema_arc: &Arc<Schema>,
|
||||||
tracking_path: Option<String>,
|
root_id: &str,
|
||||||
to_insert: &mut Vec<(String, Schema)>,
|
path: String,
|
||||||
|
to_insert: &mut Vec<(String, Arc<Schema>)>,
|
||||||
errors: &mut Vec<crate::drop::Error>,
|
errors: &mut Vec<crate::drop::Error>,
|
||||||
) {
|
) {
|
||||||
if let Some(id) = &self.obj.id {
|
if let Some(crate::database::object::SchemaTypeOrArray::Single(t)) = &schema_arc.obj.type_ {
|
||||||
Self::validate_identifier(id, "$id", errors);
|
if t == "array" {
|
||||||
to_insert.push((id.clone(), self.clone()));
|
if let Some(items) = &schema_arc.obj.items {
|
||||||
}
|
if let Some(crate::database::object::SchemaTypeOrArray::Single(it)) = &items.obj.type_ {
|
||||||
if let Some(crate::database::object::SchemaTypeOrArray::Single(t)) = &self.obj.type_ {
|
if !crate::database::object::is_primitive_type(it) {
|
||||||
if !crate::database::object::is_primitive_type(t) {
|
if items.obj.properties.is_some() || items.obj.cases.is_some() {
|
||||||
Self::validate_identifier(t, "type", errors);
|
to_insert.push((path.clone(), Arc::clone(schema_arc)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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()));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if !crate::database::object::is_primitive_type(t) {
|
||||||
|
Self::validate_identifier(t, "type", root_id, &path, errors);
|
||||||
|
|
||||||
|
// Is this an explicit inline ad-hoc composition?
|
||||||
|
if schema_arc.obj.properties.is_some() || schema_arc.obj.cases.is_some() {
|
||||||
|
to_insert.push((path.clone(), Arc::clone(schema_arc)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Provide the path origin to children natively, prioritizing the explicit `$id` boundary if one exists
|
if let Some(family) = &schema_arc.obj.family {
|
||||||
let origin_path = self.obj.id.clone().or(tracking_path);
|
Self::validate_identifier(family, "family", root_id, &path, errors);
|
||||||
|
}
|
||||||
|
|
||||||
self.collect_child_schemas(origin_path, to_insert, errors);
|
Self::collect_child_schemas(schema_arc, root_id, path, to_insert, errors);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn collect_child_schemas(
|
pub fn collect_child_schemas(
|
||||||
&mut self,
|
schema_arc: &Arc<Schema>,
|
||||||
origin_path: Option<String>,
|
root_id: &str,
|
||||||
to_insert: &mut Vec<(String, Schema)>,
|
path: String,
|
||||||
|
to_insert: &mut Vec<(String, Arc<Schema>)>,
|
||||||
errors: &mut Vec<crate::drop::Error>,
|
errors: &mut Vec<crate::drop::Error>,
|
||||||
) {
|
) {
|
||||||
if let Some(props) = &mut self.obj.properties {
|
if let Some(props) = &schema_arc.obj.properties {
|
||||||
for (k, v) in props.iter_mut() {
|
for (k, v) in props.iter() {
|
||||||
let mut inner = (**v).clone();
|
let next_path = format!("{}/{}", path, k);
|
||||||
let next_path = origin_path.as_ref().map(|o| format!("{}/{}", o, k));
|
Self::collect_schemas(v, root_id, next_path, to_insert, errors);
|
||||||
inner.collect_schemas(next_path, to_insert, errors);
|
|
||||||
*v = Arc::new(inner);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(pattern_props) = &mut self.obj.pattern_properties {
|
if let Some(pattern_props) = &schema_arc.obj.pattern_properties {
|
||||||
for (k, v) in pattern_props.iter_mut() {
|
for (k, v) in pattern_props.iter() {
|
||||||
let mut inner = (**v).clone();
|
let next_path = format!("{}/{}", path, k);
|
||||||
let next_path = origin_path.as_ref().map(|o| format!("{}/{}", o, k));
|
Self::collect_schemas(v, root_id, next_path, to_insert, errors);
|
||||||
inner.collect_schemas(next_path, to_insert, errors);
|
|
||||||
*v = Arc::new(inner);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut map_arr = |arr: &mut Vec<Arc<Schema>>| {
|
let mut map_arr = |arr: &Vec<Arc<Schema>>, sub: &str| {
|
||||||
for v in arr.iter_mut() {
|
for (i, v) in arr.iter().enumerate() {
|
||||||
let mut inner = (**v).clone();
|
Self::collect_schemas(
|
||||||
inner.collect_schemas(origin_path.clone(), to_insert, errors);
|
v,
|
||||||
*v = Arc::new(inner);
|
root_id,
|
||||||
|
format!("{}/{}/{}", path, sub, i),
|
||||||
|
to_insert,
|
||||||
|
errors,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(arr) = &mut self.obj.prefix_items {
|
if let Some(arr) = &schema_arc.obj.prefix_items {
|
||||||
map_arr(arr);
|
map_arr(arr, "prefixItems");
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(arr) = &mut self.obj.one_of {
|
if let Some(arr) = &schema_arc.obj.one_of {
|
||||||
map_arr(arr);
|
map_arr(arr, "oneOf");
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut map_opt = |opt: &mut Option<Arc<Schema>>, pass_path: bool| {
|
let mut map_opt = |opt: &Option<Arc<Schema>>, pass_path: bool, sub: &str| {
|
||||||
if let Some(v) = opt {
|
if let Some(v) = opt {
|
||||||
let mut inner = (**v).clone();
|
if pass_path {
|
||||||
let next = if pass_path { origin_path.clone() } else { None };
|
// Arrays explicitly push their wrapper natively.
|
||||||
inner.collect_schemas(next, to_insert, errors);
|
// 'items' becomes a transparent conduit, bypassing self-promotion and skipping the '/items' suffix.
|
||||||
*v = Arc::new(inner);
|
Self::collect_child_schemas(v, root_id, path.clone(), 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!
|
if let Some(cases) = &schema_arc.obj.cases {
|
||||||
// This allows nested Arrays enclosing bare Entity structs to correctly register as the boundary mapping.
|
for (i, c) in cases.iter().enumerate() {
|
||||||
map_opt(&mut self.obj.items, true);
|
if let Some(when) = &c.when {
|
||||||
|
Self::collect_schemas(
|
||||||
map_opt(&mut self.obj.not, false);
|
when,
|
||||||
map_opt(&mut self.obj.contains, false);
|
root_id,
|
||||||
map_opt(&mut self.obj.property_names, false);
|
format!("{}/cases/{}/when", path, i),
|
||||||
if let Some(cases) = &mut self.obj.cases {
|
to_insert,
|
||||||
for c in cases.iter_mut() {
|
errors,
|
||||||
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(then) = &mut c.then {
|
if let Some(then) = &c.then {
|
||||||
let mut inner = (**then).clone();
|
Self::collect_schemas(
|
||||||
inner.collect_schemas(origin_path.clone(), to_insert, errors);
|
then,
|
||||||
*then = Arc::new(inner);
|
root_id,
|
||||||
|
format!("{}/cases/{}/then", path, i),
|
||||||
|
to_insert,
|
||||||
|
errors,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if let Some(else_) = &mut c.else_ {
|
if let Some(else_) = &c.else_ {
|
||||||
let mut inner = (**else_).clone();
|
Self::collect_schemas(
|
||||||
inner.collect_schemas(origin_path.clone(), to_insert, errors);
|
else_,
|
||||||
*else_ = Arc::new(inner);
|
root_id,
|
||||||
|
format!("{}/cases/{}/else", path, i),
|
||||||
|
to_insert,
|
||||||
|
errors,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,6 +2,7 @@ use std::collections::HashSet;
|
|||||||
|
|
||||||
use crate::database::schema::Schema;
|
use crate::database::schema::Schema;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::sync::Arc;
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||||
@ -38,5 +39,5 @@ pub struct Type {
|
|||||||
pub default_fields: Vec<String>,
|
pub default_fields: Vec<String>,
|
||||||
pub field_types: Option<Value>,
|
pub field_types: Option<Value>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub schemas: Vec<Schema>,
|
pub schemas: std::collections::BTreeMap<String, Arc<Schema>>,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -66,7 +66,8 @@ pub struct Error {
|
|||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
||||||
pub struct ErrorDetails {
|
pub struct ErrorDetails {
|
||||||
pub path: String,
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub path: Option<String>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub cause: Option<String>,
|
pub cause: Option<String>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
|||||||
@ -30,7 +30,7 @@ fn jspg_failure() -> JsonB {
|
|||||||
code: "ENGINE_NOT_INITIALIZED".to_string(),
|
code: "ENGINE_NOT_INITIALIZED".to_string(),
|
||||||
message: "JSPG extension has not been initialized via jspg_setup".to_string(),
|
message: "JSPG extension has not been initialized via jspg_setup".to_string(),
|
||||||
details: crate::drop::ErrorDetails {
|
details: crate::drop::ErrorDetails {
|
||||||
path: "".to_string(),
|
path: None,
|
||||||
cause: None,
|
cause: None,
|
||||||
context: None,
|
context: None,
|
||||||
schema: None,
|
schema: None,
|
||||||
|
|||||||
@ -31,7 +31,7 @@ impl Merger {
|
|||||||
code: "MERGE_FAILED".to_string(),
|
code: "MERGE_FAILED".to_string(),
|
||||||
message: format!("Unknown schema_id: {}", schema_id),
|
message: format!("Unknown schema_id: {}", schema_id),
|
||||||
details: crate::drop::ErrorDetails {
|
details: crate::drop::ErrorDetails {
|
||||||
path: "".to_string(),
|
path: None,
|
||||||
cause: None,
|
cause: None,
|
||||||
context: Some(data),
|
context: Some(data),
|
||||||
schema: None,
|
schema: None,
|
||||||
@ -76,7 +76,7 @@ impl Merger {
|
|||||||
code: final_code,
|
code: final_code,
|
||||||
message: final_message,
|
message: final_message,
|
||||||
details: crate::drop::ErrorDetails {
|
details: crate::drop::ErrorDetails {
|
||||||
path: "".to_string(),
|
path: None,
|
||||||
cause: final_cause,
|
cause: final_cause,
|
||||||
context: None,
|
context: None,
|
||||||
schema: None,
|
schema: None,
|
||||||
@ -92,7 +92,7 @@ impl Merger {
|
|||||||
code: "MERGE_FAILED".to_string(),
|
code: "MERGE_FAILED".to_string(),
|
||||||
message: format!("Executor Error in pre-ordered notify: {:?}", e),
|
message: format!("Executor Error in pre-ordered notify: {:?}", e),
|
||||||
details: crate::drop::ErrorDetails {
|
details: crate::drop::ErrorDetails {
|
||||||
path: "".to_string(),
|
path: None,
|
||||||
cause: None,
|
cause: None,
|
||||||
context: None,
|
context: None,
|
||||||
schema: None,
|
schema: None,
|
||||||
@ -142,11 +142,21 @@ impl Merger {
|
|||||||
if let Some(disc) = schema.obj.compiled_discriminator.get() {
|
if let Some(disc) = schema.obj.compiled_discriminator.get() {
|
||||||
let val = map.get(disc).and_then(|v| v.as_str());
|
let val = map.get(disc).and_then(|v| v.as_str());
|
||||||
if let Some(v) = val {
|
if let Some(v) = val {
|
||||||
if let Some(target_id) = options.get(v) {
|
if let Some((idx_opt, target_id_opt)) = options.get(v) {
|
||||||
if let Some(target_schema) = self.db.schemas.get(target_id) {
|
if let Some(target_id) = target_id_opt {
|
||||||
schema = Arc::clone(target_schema);
|
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 {
|
} else {
|
||||||
return Err(format!("Polymorphic mapped target '{}' not found in database registry", target_id));
|
return Err(format!("Polymorphic mapped target has no path"));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return Err(format!("Polymorphic discriminator {}='{}' matched no compiled options", disc, v));
|
return Err(format!("Polymorphic discriminator {}='{}' matched no compiled options", disc, v));
|
||||||
@ -215,7 +225,7 @@ impl Merger {
|
|||||||
for (k, v) in obj {
|
for (k, v) in obj {
|
||||||
// Always retain system and unmapped core fields natively implicitly mapped to the Postgres tables
|
// Always retain system and unmapped core fields natively implicitly mapped to the Postgres tables
|
||||||
if k == "id" || k == "type" || k == "created" {
|
if k == "id" || k == "type" || k == "created" {
|
||||||
entity_fields.insert(k.clone(), v.clone());
|
entity_fields.insert(k, v);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -234,18 +244,18 @@ impl Merger {
|
|||||||
_ => "field", // Malformed edge data?
|
_ => "field", // Malformed edge data?
|
||||||
};
|
};
|
||||||
if typeof_v == "object" {
|
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" {
|
} else if typeof_v == "array" {
|
||||||
entity_arrays.insert(k.clone(), (v.clone(), prop_schema.clone()));
|
entity_arrays.insert(k, (v, prop_schema.clone()));
|
||||||
} else {
|
} else {
|
||||||
entity_fields.insert(k.clone(), v.clone());
|
entity_fields.insert(k, v);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Not an edge! It's a raw Postgres column (e.g., JSONB, text[])
|
// 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) {
|
} 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());
|
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("id".to_string(), id_val);
|
||||||
new_fields.insert("type".to_string(), Value::String(type_name.to_string()));
|
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()));
|
new_fields.insert("created_by".to_string(), Value::String(user_id.to_string()));
|
||||||
@ -564,7 +574,7 @@ impl Merger {
|
|||||||
Some("update".to_string())
|
Some("update".to_string())
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut new_fields = changes.clone();
|
let mut new_fields = changes;
|
||||||
new_fields.insert(
|
new_fields.insert(
|
||||||
"id".to_string(),
|
"id".to_string(),
|
||||||
entity_fetched.as_ref().unwrap().get("id").unwrap().clone(),
|
entity_fetched.as_ref().unwrap().get("id").unwrap().clone(),
|
||||||
|
|||||||
@ -18,6 +18,7 @@ pub struct Node<'a> {
|
|||||||
pub depth: usize,
|
pub depth: usize,
|
||||||
pub ast_path: String,
|
pub ast_path: String,
|
||||||
pub is_polymorphic_branch: bool,
|
pub is_polymorphic_branch: bool,
|
||||||
|
pub schema_id: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Compiler<'a> {
|
impl<'a> Compiler<'a> {
|
||||||
@ -47,6 +48,7 @@ impl<'a> Compiler<'a> {
|
|||||||
depth: 0,
|
depth: 0,
|
||||||
ast_path: String::new(),
|
ast_path: String::new(),
|
||||||
is_polymorphic_branch: false,
|
is_polymorphic_branch: false,
|
||||||
|
schema_id: Some(schema_id.to_string()),
|
||||||
};
|
};
|
||||||
|
|
||||||
let (sql, _) = compiler.compile_node(node)?;
|
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> {
|
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 {
|
if let Some(items) = &node.schema.obj.items {
|
||||||
let mut resolved_type = None;
|
let mut resolved_type = None;
|
||||||
if let Some(family_target) = items.obj.family.as_ref() {
|
if let Some(sid) = &node.schema_id {
|
||||||
let base_type_name = family_target
|
resolved_type = self
|
||||||
.split('.')
|
.db
|
||||||
.next_back()
|
.types
|
||||||
.unwrap_or(family_target);
|
.get(&sid.split('.').next_back().unwrap_or(sid).to_string());
|
||||||
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 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 {
|
if let Some(type_def) = resolved_type {
|
||||||
@ -105,16 +121,28 @@ impl<'a> Compiler<'a> {
|
|||||||
// Determine if this schema represents a Database Entity
|
// Determine if this schema represents a Database Entity
|
||||||
let mut resolved_type = None;
|
let mut resolved_type = None;
|
||||||
|
|
||||||
if let Some(family_target) = node.schema.obj.family.as_ref() {
|
if let Some(sid) = &node.schema_id {
|
||||||
let base_type_name = family_target
|
let base_type_name = sid.split('.').next_back().unwrap_or(sid).to_string();
|
||||||
.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() {
|
|
||||||
resolved_type = self.db.types.get(&base_type_name);
|
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 {
|
if let Some(type_def) = resolved_type {
|
||||||
return self.compile_entity(type_def, node.clone(), false);
|
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) {
|
if let Some(target_schema) = self.db.schemas.get(t) {
|
||||||
let mut ref_node = node.clone();
|
let mut ref_node = node.clone();
|
||||||
ref_node.schema = Arc::clone(target_schema);
|
ref_node.schema = Arc::clone(target_schema);
|
||||||
|
ref_node.schema_id = Some(t.clone());
|
||||||
return self.compile_node(ref_node);
|
return self.compile_node(ref_node);
|
||||||
}
|
}
|
||||||
return Err(format!("Unresolved schema type pointer: {}", t));
|
return Err(format!("Unresolved schema type pointer: {}", t));
|
||||||
@ -133,17 +162,21 @@ impl<'a> Compiler<'a> {
|
|||||||
}
|
}
|
||||||
// Handle Polymorphism fallbacks for relations
|
// Handle Polymorphism fallbacks for relations
|
||||||
if node.schema.obj.family.is_some() || node.schema.obj.one_of.is_some() {
|
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 let Some(options) = node.schema.obj.compiled_options.get() {
|
||||||
if options.len() == 1 {
|
if options.len() == 1 {
|
||||||
let target_id = options.values().next().unwrap();
|
let (_, target_opt) = options.values().next().unwrap();
|
||||||
let mut bypass_schema = crate::database::schema::Schema::default();
|
if let Some(target_id) = target_opt {
|
||||||
bypass_schema.obj.type_ = Some(crate::database::object::SchemaTypeOrArray::Single(target_id.clone()));
|
let mut bypass_schema = crate::database::schema::Schema::default();
|
||||||
let mut bypass_node = node.clone();
|
bypass_schema.obj.type_ = Some(crate::database::object::SchemaTypeOrArray::Single(
|
||||||
bypass_node.schema = std::sync::Arc::new(bypass_schema);
|
target_id.clone(),
|
||||||
return self.compile_node(bypass_node);
|
));
|
||||||
|
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?
|
// 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 (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 jsonb_obj_sql = if node.schema.obj.family.is_some() || node.schema.obj.one_of.is_some() {
|
||||||
let base_alias = table_aliases
|
let base_alias = table_aliases
|
||||||
.get(&r#type.name)
|
.get(&r#type.name)
|
||||||
.cloned()
|
.cloned()
|
||||||
.unwrap_or_else(|| node.parent_alias.to_string());
|
.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 (case_sql, _) = self.compile_one_of(case_node)?;
|
let mut case_node = node.clone();
|
||||||
case_sql
|
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 {
|
} else {
|
||||||
let select_args = self.compile_select_clause(r#type, &table_aliases, node.clone())?;
|
let select_args = self.compile_select_clause(r#type, &table_aliases, node.clone())?;
|
||||||
|
|
||||||
if select_args.is_empty() {
|
if select_args.is_empty() {
|
||||||
"jsonb_build_object()".to_string()
|
"jsonb_build_object()".to_string()
|
||||||
} else {
|
} else {
|
||||||
format!("jsonb_build_object({})", select_args.join(", "))
|
format!("jsonb_build_object({})", select_args.join(", "))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 3. Build WHERE clauses
|
// 3. Build WHERE clauses
|
||||||
@ -249,14 +282,21 @@ impl<'a> Compiler<'a> {
|
|||||||
Ok((combined, "object".to_string()))
|
Ok((combined, "object".to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compile_one_of(
|
fn compile_one_of(&mut self, node: Node<'a>) -> Result<(String, String), String> {
|
||||||
&mut self,
|
|
||||||
node: Node<'a>,
|
|
||||||
) -> Result<(String, String), String> {
|
|
||||||
let mut case_statements = Vec::new();
|
let mut case_statements = Vec::new();
|
||||||
|
|
||||||
let options = node.schema.obj.compiled_options.get().ok_or("Missing compiled options for polymorphism")?;
|
let options = node
|
||||||
let disc = node.schema.obj.compiled_discriminator.get().ok_or("Missing compiled discriminator for polymorphism")?;
|
.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 {
|
let type_col = if let Some(prop) = &node.property_name {
|
||||||
format!("{}_{}", prop, disc)
|
format!("{}_{}", prop, disc)
|
||||||
@ -264,36 +304,74 @@ impl<'a> Compiler<'a> {
|
|||||||
disc.to_string()
|
disc.to_string()
|
||||||
};
|
};
|
||||||
|
|
||||||
for (disc_val, target_id) in options {
|
for (disc_val, (idx_opt, target_id_opt)) in options {
|
||||||
if let Some(target_schema) = self.db.schemas.get(target_id) {
|
if let Some(target_id) = target_id_opt {
|
||||||
let mut child_node = node.clone();
|
if let Some(target_schema) = self.db.schemas.get(target_id) {
|
||||||
child_node.schema = Arc::clone(target_schema);
|
let mut child_node = node.clone();
|
||||||
child_node.is_polymorphic_branch = true;
|
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 val_sql =
|
||||||
let aliases_arc = node.parent_type_aliases.as_ref().unwrap();
|
if disc == "kind" && node.parent_type.is_some() && node.parent_type_aliases.is_some() {
|
||||||
let aliases = aliases_arc.as_ref();
|
let aliases_arc = node.parent_type_aliases.as_ref().unwrap();
|
||||||
let p_type = node.parent_type.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())?;
|
|
||||||
|
let select_args = self.compile_select_clause(p_type, aliases, child_node.clone())?;
|
||||||
if select_args.is_empty() {
|
|
||||||
|
if select_args.is_empty() {
|
||||||
"jsonb_build_object()".to_string()
|
"jsonb_build_object()".to_string()
|
||||||
} else {
|
} else {
|
||||||
format!("jsonb_build_object({})", select_args.join(", "))
|
format!("jsonb_build_object({})", select_args.join(", "))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let (sql, _) = self.compile_node(child_node)?;
|
let (sql, _) = self.compile_node(child_node)?;
|
||||||
sql
|
sql
|
||||||
};
|
};
|
||||||
|
|
||||||
case_statements.push(format!(
|
case_statements.push(format!(
|
||||||
"WHEN {}.{} = '{}' THEN ({})",
|
"WHEN {}.{} = '{}' THEN ({})",
|
||||||
node.parent_alias, type_col, disc_val, val_sql
|
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() {
|
if case_statements.is_empty() {
|
||||||
return Ok(("NULL".to_string(), "string".to_string()));
|
return Ok(("NULL".to_string(), "string".to_string()));
|
||||||
}
|
}
|
||||||
@ -339,14 +417,14 @@ impl<'a> Compiler<'a> {
|
|||||||
let mut select_args = Vec::new();
|
let mut select_args = Vec::new();
|
||||||
let grouped_fields = r#type.grouped_fields.as_ref().and_then(|v| v.as_object());
|
let grouped_fields = r#type.grouped_fields.as_ref().and_then(|v| v.as_object());
|
||||||
let default_props = std::collections::BTreeMap::new();
|
let default_props = std::collections::BTreeMap::new();
|
||||||
let merged_props = node.schema.obj.compiled_properties.get().unwrap_or(&default_props);
|
let merged_props = node
|
||||||
|
.schema
|
||||||
let mut sorted_keys: Vec<&String> = merged_props.keys().collect();
|
.obj
|
||||||
sorted_keys.sort();
|
.compiled_properties
|
||||||
|
.get()
|
||||||
for prop_key in sorted_keys {
|
.unwrap_or(&default_props);
|
||||||
let prop_schema = &merged_props[prop_key];
|
|
||||||
|
|
||||||
|
for (prop_key, prop_schema) in merged_props {
|
||||||
let is_object_or_array = match &prop_schema.obj.type_ {
|
let is_object_or_array = match &prop_schema.obj.type_ {
|
||||||
Some(crate::database::object::SchemaTypeOrArray::Single(s)) => {
|
Some(crate::database::object::SchemaTypeOrArray::Single(s)) => {
|
||||||
s == "object" || s == "array"
|
s == "object" || s == "array"
|
||||||
@ -396,6 +474,15 @@ impl<'a> Compiler<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(ft) = r#type.field_types.as_ref().and_then(|v| v.as_object()) {
|
||||||
|
if let Some(pg_type) = ft.get(prop_key).and_then(|v| v.as_str()) {
|
||||||
|
if pg_type == "json" || pg_type == "jsonb" {
|
||||||
|
select_args.push(format!("'{}', {}.{}", prop_key, owner_alias, prop_key));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let child_node = Node {
|
let child_node = Node {
|
||||||
schema: std::sync::Arc::clone(prop_schema),
|
schema: std::sync::Arc::clone(prop_schema),
|
||||||
parent_alias: owner_alias.clone(),
|
parent_alias: owner_alias.clone(),
|
||||||
@ -410,6 +497,7 @@ impl<'a> Compiler<'a> {
|
|||||||
format!("{}/{}", node.ast_path, prop_key)
|
format!("{}/{}", node.ast_path, prop_key)
|
||||||
},
|
},
|
||||||
is_polymorphic_branch: false,
|
is_polymorphic_branch: false,
|
||||||
|
schema_id: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let (val_sql, val_type) = self.compile_node(child_node)?;
|
let (val_sql, val_type) = self.compile_node(child_node)?;
|
||||||
@ -449,7 +537,7 @@ impl<'a> Compiler<'a> {
|
|||||||
|
|
||||||
self.compile_filter_conditions(r#type, type_aliases, &node, &base_alias, &mut where_clauses);
|
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);
|
self.compile_polymorphic_bounds(r#type, type_aliases, &node, &mut where_clauses);
|
||||||
|
|
||||||
let start_len = where_clauses.len();
|
let start_len = where_clauses.len();
|
||||||
self.compile_relation_conditions(
|
self.compile_relation_conditions(
|
||||||
r#type,
|
r#type,
|
||||||
@ -491,8 +579,12 @@ impl<'a> Compiler<'a> {
|
|||||||
.unwrap_or(family_target)
|
.unwrap_or(family_target)
|
||||||
.to_string(),
|
.to_string(),
|
||||||
);
|
);
|
||||||
} else if let Some(lookup_key) = prop_schema.obj.identifier() {
|
} else if let Some(crate::database::object::SchemaTypeOrArray::Single(t)) =
|
||||||
bound_type_name = Some(lookup_key);
|
&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 {
|
if let Some(type_name) = bound_type_name {
|
||||||
|
|||||||
@ -33,7 +33,7 @@ impl Queryer {
|
|||||||
code: "FILTER_PARSE_FAILED".to_string(),
|
code: "FILTER_PARSE_FAILED".to_string(),
|
||||||
message: msg.clone(),
|
message: msg.clone(),
|
||||||
details: crate::drop::ErrorDetails {
|
details: crate::drop::ErrorDetails {
|
||||||
path: "".to_string(), // filters apply to the root query
|
path: None, // filters apply to the root query
|
||||||
cause: Some(msg),
|
cause: Some(msg),
|
||||||
context: filters.cloned(),
|
context: filters.cloned(),
|
||||||
schema: Some(schema_id.to_string()),
|
schema: Some(schema_id.to_string()),
|
||||||
@ -138,7 +138,7 @@ impl Queryer {
|
|||||||
code: "QUERY_COMPILATION_FAILED".to_string(),
|
code: "QUERY_COMPILATION_FAILED".to_string(),
|
||||||
message: e.clone(),
|
message: e.clone(),
|
||||||
details: crate::drop::ErrorDetails {
|
details: crate::drop::ErrorDetails {
|
||||||
path: "".to_string(),
|
path: None,
|
||||||
cause: Some(e),
|
cause: Some(e),
|
||||||
context: None,
|
context: None,
|
||||||
schema: Some(schema_id.to_string()),
|
schema: Some(schema_id.to_string()),
|
||||||
@ -165,7 +165,7 @@ impl Queryer {
|
|||||||
code: "QUERY_FAILED".to_string(),
|
code: "QUERY_FAILED".to_string(),
|
||||||
message: format!("Expected array from generic query, got: {:?}", other),
|
message: format!("Expected array from generic query, got: {:?}", other),
|
||||||
details: crate::drop::ErrorDetails {
|
details: crate::drop::ErrorDetails {
|
||||||
path: "".to_string(),
|
path: None,
|
||||||
cause: Some(format!("Expected array, got {}", other)),
|
cause: Some(format!("Expected array, got {}", other)),
|
||||||
context: Some(serde_json::json!([sql])),
|
context: Some(serde_json::json!([sql])),
|
||||||
schema: Some(schema_id.to_string()),
|
schema: Some(schema_id.to_string()),
|
||||||
@ -175,7 +175,7 @@ impl Queryer {
|
|||||||
code: "QUERY_FAILED".to_string(),
|
code: "QUERY_FAILED".to_string(),
|
||||||
message: format!("SPI error in queryer: {}", e),
|
message: format!("SPI error in queryer: {}", e),
|
||||||
details: crate::drop::ErrorDetails {
|
details: crate::drop::ErrorDetails {
|
||||||
path: "".to_string(),
|
path: None,
|
||||||
cause: Some(format!("SPI error in queryer: {}", e)),
|
cause: Some(format!("SPI error in queryer: {}", e)),
|
||||||
context: Some(serde_json::json!([sql])),
|
context: Some(serde_json::json!([sql])),
|
||||||
schema: Some(schema_id.to_string()),
|
schema: Some(schema_id.to_string()),
|
||||||
|
|||||||
@ -1451,6 +1451,18 @@ fn test_queryer_0_12() {
|
|||||||
crate::tests::runner::run_test_case(&path, 0, 12).unwrap();
|
crate::tests::runner::run_test_case(&path, 0, 12).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_queryer_0_13() {
|
||||||
|
let path = format!("{}/fixtures/queryer.json", env!("CARGO_MANIFEST_DIR"));
|
||||||
|
crate::tests::runner::run_test_case(&path, 0, 13).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_queryer_0_14() {
|
||||||
|
let path = format!("{}/fixtures/queryer.json", env!("CARGO_MANIFEST_DIR"));
|
||||||
|
crate::tests::runner::run_test_case(&path, 0, 14).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_polymorphism_0_0() {
|
fn test_polymorphism_0_0() {
|
||||||
let path = format!("{}/fixtures/polymorphism.json", env!("CARGO_MANIFEST_DIR"));
|
let path = format!("{}/fixtures/polymorphism.json", env!("CARGO_MANIFEST_DIR"));
|
||||||
@ -3558,9 +3570,9 @@ fn test_paths_1_0() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_paths_1_1() {
|
fn test_paths_2_0() {
|
||||||
let path = format!("{}/fixtures/paths.json", env!("CARGO_MANIFEST_DIR"));
|
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]
|
#[test]
|
||||||
@ -3701,6 +3713,18 @@ fn test_database_4_0() {
|
|||||||
crate::tests::runner::run_test_case(&path, 4, 0).unwrap();
|
crate::tests::runner::run_test_case(&path, 4, 0).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_database_5_0() {
|
||||||
|
let path = format!("{}/fixtures/database.json", env!("CARGO_MANIFEST_DIR"));
|
||||||
|
crate::tests::runner::run_test_case(&path, 5, 0).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_database_6_0() {
|
||||||
|
let path = format!("{}/fixtures/database.json", env!("CARGO_MANIFEST_DIR"));
|
||||||
|
crate::tests::runner::run_test_case(&path, 6, 0).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_cases_0_0() {
|
fn test_cases_0_0() {
|
||||||
let path = format!("{}/fixtures/cases.json", env!("CARGO_MANIFEST_DIR"));
|
let path = format!("{}/fixtures/cases.json", env!("CARGO_MANIFEST_DIR"));
|
||||||
@ -7740,21 +7764,9 @@ fn test_object_types_2_1() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_object_types_3_0() {
|
fn test_object_types_2_2() {
|
||||||
let path = format!("{}/fixtures/objectTypes.json", env!("CARGO_MANIFEST_DIR"));
|
let path = format!("{}/fixtures/objectTypes.json", env!("CARGO_MANIFEST_DIR"));
|
||||||
crate::tests::runner::run_test_case(&path, 3, 0).unwrap();
|
crate::tests::runner::run_test_case(&path, 2, 2).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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -8110,3 +8122,15 @@ fn test_merger_0_12() {
|
|||||||
let path = format!("{}/fixtures/merger.json", env!("CARGO_MANIFEST_DIR"));
|
let path = format!("{}/fixtures/merger.json", env!("CARGO_MANIFEST_DIR"));
|
||||||
crate::tests::runner::run_test_case(&path, 0, 12).unwrap();
|
crate::tests::runner::run_test_case(&path, 0, 12).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_merger_0_13() {
|
||||||
|
let path = format!("{}/fixtures/merger.json", env!("CARGO_MANIFEST_DIR"));
|
||||||
|
crate::tests::runner::run_test_case(&path, 0, 13).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_merger_0_14() {
|
||||||
|
let path = format!("{}/fixtures/merger.json", env!("CARGO_MANIFEST_DIR"));
|
||||||
|
crate::tests::runner::run_test_case(&path, 0, 14).unwrap();
|
||||||
|
}
|
||||||
|
|||||||
@ -18,7 +18,7 @@ fn test_library_api() {
|
|||||||
"errors": [{
|
"errors": [{
|
||||||
"code": "ENGINE_NOT_INITIALIZED",
|
"code": "ENGINE_NOT_INITIALIZED",
|
||||||
"message": "JSPG extension has not been initialized via jspg_setup",
|
"message": "JSPG extension has not been initialized via jspg_setup",
|
||||||
"details": { "path": "" }
|
"details": {}
|
||||||
}]
|
}]
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@ -44,27 +44,30 @@ fn test_library_api() {
|
|||||||
"name": "source_schema",
|
"name": "source_schema",
|
||||||
"variations": ["source_schema"],
|
"variations": ["source_schema"],
|
||||||
"hierarchy": ["source_schema", "entity"],
|
"hierarchy": ["source_schema", "entity"],
|
||||||
"schemas": [{
|
"schemas": {
|
||||||
"$id": "source_schema",
|
"source_schema": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"name": { "type": "string" },
|
"type": { "type": "string" },
|
||||||
"target": { "type": "target_schema" }
|
"name": { "type": "string" },
|
||||||
},
|
"target": { "type": "target_schema" }
|
||||||
"required": ["name"]
|
},
|
||||||
}]
|
"required": ["name"]
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "target_schema",
|
"name": "target_schema",
|
||||||
"variations": ["target_schema"],
|
"variations": ["target_schema"],
|
||||||
"hierarchy": ["target_schema", "entity"],
|
"hierarchy": ["target_schema", "entity"],
|
||||||
"schemas": [{
|
"schemas": {
|
||||||
"$id": "target_schema",
|
"target_schema": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"value": { "type": "number" }
|
"value": { "type": "number" }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}]
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
@ -86,17 +89,17 @@ fn test_library_api() {
|
|||||||
"type": "drop",
|
"type": "drop",
|
||||||
"response": {
|
"response": {
|
||||||
"source_schema": {
|
"source_schema": {
|
||||||
"$id": "source_schema",
|
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"type": { "type": "string" },
|
||||||
"name": { "type": "string" },
|
"name": { "type": "string" },
|
||||||
"target": {
|
"target": {
|
||||||
"type": "target_schema",
|
"type": "target_schema",
|
||||||
"compiledProperties": ["value"]
|
"compiledPropertyNames": ["value"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": ["name"],
|
"required": ["name"],
|
||||||
"compiledProperties": ["name", "target"],
|
"compiledPropertyNames": ["name", "target", "type"],
|
||||||
"compiledEdges": {
|
"compiledEdges": {
|
||||||
"target": {
|
"target": {
|
||||||
"constraint": "fk_test_target",
|
"constraint": "fk_test_target",
|
||||||
@ -105,12 +108,11 @@ fn test_library_api() {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"target_schema": {
|
"target_schema": {
|
||||||
"$id": "target_schema",
|
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"value": { "type": "number" }
|
"value": { "type": "number" }
|
||||||
},
|
},
|
||||||
"compiledProperties": ["value"]
|
"compiledPropertyNames": ["value"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@ -86,7 +86,7 @@ pub fn run_test_case(path: &str, suite_idx: usize, case_idx: usize) -> Result<()
|
|||||||
let error_messages: Vec<String> = drop
|
let error_messages: Vec<String> = drop
|
||||||
.errors
|
.errors
|
||||||
.iter()
|
.iter()
|
||||||
.map(|e| format!("Error {} at path {}: {}", e.code, e.details.path, e.message))
|
.map(|e| format!("Error {} at path {}: {}", e.code, e.details.path.as_deref().unwrap_or("/"), e.message))
|
||||||
.collect();
|
.collect();
|
||||||
failures.push(format!(
|
failures.push(format!(
|
||||||
"[{}] Cannot run '{}' test '{}': System Setup Compilation structurally failed:\n{}",
|
"[{}] Cannot run '{}' test '{}': System Setup Compilation structurally failed:\n{}",
|
||||||
|
|||||||
@ -49,7 +49,13 @@ impl Case {
|
|||||||
Err(d) => d.clone(),
|
Err(d) => d.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
expect.assert_drop(&result)
|
expect.assert_drop(&result)?;
|
||||||
|
|
||||||
|
if let Ok(db) = db_res {
|
||||||
|
expect.assert_schemas(db)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_validate(&self, db: Arc<Database>) -> Result<(), String> {
|
pub fn run_validate(&self, db: Arc<Database>) -> Result<(), String> {
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
pub mod pattern;
|
pub mod pattern;
|
||||||
pub mod sql;
|
pub mod sql;
|
||||||
pub mod drop;
|
pub mod drop;
|
||||||
|
pub mod schema;
|
||||||
|
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
@ -18,4 +19,6 @@ pub struct Expect {
|
|||||||
pub errors: Option<Vec<serde_json::Value>>,
|
pub errors: Option<Vec<serde_json::Value>>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub sql: Option<Vec<SqlExpectation>>,
|
pub sql: Option<Vec<SqlExpectation>>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub schemas: Option<Vec<String>>,
|
||||||
}
|
}
|
||||||
|
|||||||
27
src/tests/types/expect/schema.rs
Normal file
27
src/tests/types/expect/schema.rs
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
use super::Expect;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
impl Expect {
|
||||||
|
pub fn assert_schemas(&self, db: &Arc<crate::database::Database>) -> Result<(), String> {
|
||||||
|
if let Some(expected_schemas) = &self.schemas {
|
||||||
|
// Collect actual schemas and sort
|
||||||
|
let mut actual: Vec<String> = db.schemas.keys().cloned().collect();
|
||||||
|
actual.sort();
|
||||||
|
|
||||||
|
// Collect expected schemas and sort
|
||||||
|
let mut expected: Vec<String> = expected_schemas.clone();
|
||||||
|
expected.sort();
|
||||||
|
|
||||||
|
if actual != expected {
|
||||||
|
return Err(format!(
|
||||||
|
"Schema Promotion Mismatch!\nExpected Schemas ({}):\n{:#?}\n\nActual Promoted Schemas ({}):\n{:#?}",
|
||||||
|
expected.len(),
|
||||||
|
expected,
|
||||||
|
actual.len(),
|
||||||
|
actual
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -68,7 +68,7 @@ impl Validator {
|
|||||||
code: e.code,
|
code: e.code,
|
||||||
message: e.message,
|
message: e.message,
|
||||||
details: crate::drop::ErrorDetails {
|
details: crate::drop::ErrorDetails {
|
||||||
path: e.path,
|
path: Some(e.path),
|
||||||
cause: None,
|
cause: None,
|
||||||
context: None,
|
context: None,
|
||||||
schema: None,
|
schema: None,
|
||||||
@ -82,7 +82,7 @@ impl Validator {
|
|||||||
code: e.code,
|
code: e.code,
|
||||||
message: e.message,
|
message: e.message,
|
||||||
details: crate::drop::ErrorDetails {
|
details: crate::drop::ErrorDetails {
|
||||||
path: e.path,
|
path: Some(e.path),
|
||||||
cause: None,
|
cause: None,
|
||||||
context: None,
|
context: None,
|
||||||
schema: None,
|
schema: None,
|
||||||
@ -94,7 +94,7 @@ impl Validator {
|
|||||||
code: "SCHEMA_NOT_FOUND".to_string(),
|
code: "SCHEMA_NOT_FOUND".to_string(),
|
||||||
message: format!("Schema {} not found", schema_id),
|
message: format!("Schema {} not found", schema_id),
|
||||||
details: crate::drop::ErrorDetails {
|
details: crate::drop::ErrorDetails {
|
||||||
path: "/".to_string(),
|
path: Some("/".to_string()),
|
||||||
cause: None,
|
cause: None,
|
||||||
context: None,
|
context: None,
|
||||||
schema: None,
|
schema: None,
|
||||||
|
|||||||
@ -93,9 +93,12 @@ impl<'a> ValidationContext<'a> {
|
|||||||
if i < len {
|
if i < len {
|
||||||
if let Some(child_instance) = arr.get(i) {
|
if let Some(child_instance) = arr.get(i) {
|
||||||
let mut item_path = self.join_path(&i.to_string());
|
let mut item_path = self.join_path(&i.to_string());
|
||||||
if let Some(obj) = child_instance.as_object() {
|
let is_topological = sub_schema.obj.requires_uuid_path(self.db);
|
||||||
if let Some(id_str) = obj.get("id").and_then(|v| v.as_str()) {
|
if is_topological {
|
||||||
item_path = self.join_path(id_str);
|
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(
|
let derived = self.derive(
|
||||||
@ -116,12 +119,15 @@ impl<'a> ValidationContext<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(ref items_schema) = self.schema.items {
|
if let Some(ref items_schema) = self.schema.items {
|
||||||
|
let is_topological = items_schema.obj.requires_uuid_path(self.db);
|
||||||
for i in validation_index..len {
|
for i in validation_index..len {
|
||||||
if let Some(child_instance) = arr.get(i) {
|
if let Some(child_instance) = arr.get(i) {
|
||||||
let mut item_path = self.join_path(&i.to_string());
|
let mut item_path = self.join_path(&i.to_string());
|
||||||
if let Some(obj) = child_instance.as_object() {
|
if is_topological {
|
||||||
if let Some(id_str) = obj.get("id").and_then(|v| v.as_str()) {
|
if let Some(obj) = child_instance.as_object() {
|
||||||
item_path = self.join_path(id_str);
|
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(
|
let derived = self.derive(
|
||||||
|
|||||||
@ -24,9 +24,6 @@ impl<'a> ValidationContext<'a> {
|
|||||||
|
|
||||||
if let Some(obj) = self.instance.as_object() {
|
if let Some(obj) = self.instance.as_object() {
|
||||||
for key in obj.keys() {
|
for key in obj.keys() {
|
||||||
if key == "type" || key == "kind" {
|
|
||||||
continue; // Reserved keywords implicitly allowed
|
|
||||||
}
|
|
||||||
if !result.evaluated_keys.contains(key) && !self.overrides.contains(key) {
|
if !result.evaluated_keys.contains(key) && !self.overrides.contains(key) {
|
||||||
result.errors.push(ValidationError {
|
result.errors.push(ValidationError {
|
||||||
code: "STRICT_PROPERTY_VIOLATION".to_string(),
|
code: "STRICT_PROPERTY_VIOLATION".to_string(),
|
||||||
|
|||||||
@ -13,10 +13,17 @@ impl<'a> ValidationContext<'a> {
|
|||||||
) -> Result<bool, ValidationError> {
|
) -> Result<bool, ValidationError> {
|
||||||
let current = self.instance;
|
let current = self.instance;
|
||||||
if let Some(obj) = current.as_object() {
|
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
|
// 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
|
// 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
|
// Check if the identifier represents a registered global database entity boundary mathematically
|
||||||
if let Some(type_def) = self.db.types.get(expected_type) {
|
if let Some(type_def) = self.db.types.get(expected_type) {
|
||||||
@ -46,15 +53,20 @@ impl<'a> ValidationContext<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If the target mathematically declares a horizontal structural STI variation natively
|
// 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() {
|
let requires_kind = self.schema.compiled_properties.get()
|
||||||
result.errors.push(ValidationError {
|
.map_or(false, |p| p.contains_key("kind"));
|
||||||
code: "MISSING_KIND".to_string(),
|
|
||||||
message: "Schema mechanically requires horizontal kind discrimination".to_string(),
|
if requires_kind {
|
||||||
path: self.path.clone(),
|
if obj.get("kind").is_none() {
|
||||||
});
|
result.errors.push(ValidationError {
|
||||||
} else {
|
code: "MISSING_KIND".to_string(),
|
||||||
result.evaluated_keys.insert("kind".to_string());
|
message: "Schema mechanically requires horizontal kind discrimination".to_string(),
|
||||||
|
path: self.path.clone(),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
result.evaluated_keys.insert("kind".to_string());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -69,7 +81,7 @@ impl<'a> ValidationContext<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(kind_val) = obj.get("kind") {
|
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 let Some(actual_kind) = kind_val.as_str() {
|
||||||
if actual_kind == kind_str {
|
if actual_kind == kind_str {
|
||||||
result.evaluated_keys.insert("kind".to_string());
|
result.evaluated_keys.insert("kind".to_string());
|
||||||
|
|||||||
@ -21,7 +21,7 @@ impl<'a> ValidationContext<'a> {
|
|||||||
if conflicts {
|
if conflicts {
|
||||||
result.errors.push(ValidationError {
|
result.errors.push(ValidationError {
|
||||||
code: "INVALID_SCHEMA".to_string(),
|
code: "INVALID_SCHEMA".to_string(),
|
||||||
message: "$family must be used exclusively without other constraints".to_string(),
|
message: "family must be used exclusively without other constraints".to_string(),
|
||||||
path: self.path.to_string(),
|
path: self.path.to_string(),
|
||||||
});
|
});
|
||||||
return Ok(false);
|
return Ok(false);
|
||||||
@ -30,77 +30,34 @@ impl<'a> ValidationContext<'a> {
|
|||||||
|
|
||||||
if self.schema.family.is_some() {
|
if self.schema.family.is_some() {
|
||||||
if let Some(options) = self.schema.compiled_options.get() {
|
if let Some(options) = self.schema.compiled_options.get() {
|
||||||
if let Some(disc) = self.schema.compiled_discriminator.get() {
|
return self.execute_polymorph(options, result);
|
||||||
return self.execute_polymorph(disc, 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)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn validate_one_of(
|
pub(crate) fn validate_one_of(
|
||||||
&self,
|
&self,
|
||||||
result: &mut ValidationResult,
|
result: &mut ValidationResult,
|
||||||
) -> Result<bool, ValidationError> {
|
) -> Result<bool, ValidationError> {
|
||||||
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(options) = self.schema.compiled_options.get() {
|
||||||
if let Some(disc) = self.schema.compiled_discriminator.get() {
|
return self.execute_polymorph(options, result);
|
||||||
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);
|
|
||||||
} else {
|
} else {
|
||||||
result.errors.push(ValidationError {
|
result.errors.push(ValidationError {
|
||||||
code: "AMBIGUOUS_POLYMORPHIC_MATCH".to_string(),
|
code: "UNCOMPILED_ONEOF".to_string(),
|
||||||
message: "Matches multiple polymorphic candidates inextricably natively".to_string(),
|
message: "Encountered oneOf block that could not be mapped to deterministic compiled options natively.".to_string(),
|
||||||
path: self.path.to_string(),
|
path: self.path.to_string(),
|
||||||
});
|
});
|
||||||
return Ok(false);
|
return Ok(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(true)
|
Ok(true)
|
||||||
@ -108,46 +65,115 @@ impl<'a> ValidationContext<'a> {
|
|||||||
|
|
||||||
pub(crate) fn execute_polymorph(
|
pub(crate) fn execute_polymorph(
|
||||||
&self,
|
&self,
|
||||||
disc: &str,
|
options: &std::collections::BTreeMap<String, (Option<usize>, Option<String>)>,
|
||||||
options: &std::collections::BTreeMap<String, String>,
|
|
||||||
result: &mut ValidationResult,
|
result: &mut ValidationResult,
|
||||||
) -> Result<bool, ValidationError> {
|
) -> Result<bool, ValidationError> {
|
||||||
// 1. O(1) Fast-Path Router & Extractor
|
// 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 {
|
if let Some(val) = instance_val {
|
||||||
result.evaluated_keys.insert(disc.to_string());
|
if let Some((idx_opt, target_id_opt)) = options.get(&val) {
|
||||||
|
if let Some(target_id) = target_id_opt {
|
||||||
if let Some(target_id) = options.get(val) {
|
|
||||||
if let Some(target_schema) = self.db.schemas.get(target_id) {
|
if let Some(target_schema) = self.db.schemas.get(target_id) {
|
||||||
let derived = self.derive_for_schema(target_schema.as_ref(), false);
|
let derived = self.derive_for_schema(target_schema.as_ref(), false);
|
||||||
let sub_res = derived.validate()?;
|
let sub_res = derived.validate()?;
|
||||||
let is_valid = sub_res.is_valid();
|
let is_valid = sub_res.is_valid();
|
||||||
result.merge(sub_res);
|
result.merge(sub_res);
|
||||||
return Ok(is_valid);
|
return Ok(is_valid);
|
||||||
} else {
|
} else {
|
||||||
result.errors.push(ValidationError {
|
result.errors.push(ValidationError {
|
||||||
code: "MISSING_COMPILED_SCHEMA".to_string(),
|
code: "MISSING_COMPILED_SCHEMA".to_string(),
|
||||||
message: format!("Polymorphic router target '{}' does not exist in the database schemas map", target_id),
|
message: format!(
|
||||||
path: self.path.to_string(),
|
"Polymorphic router target '{}' does not exist in the database schemas map",
|
||||||
});
|
target_id
|
||||||
return Ok(false);
|
),
|
||||||
|
path: self.path.to_string(),
|
||||||
|
});
|
||||||
|
return Ok(false);
|
||||||
}
|
}
|
||||||
} else {
|
} else if let Some(idx) = idx_opt {
|
||||||
result.errors.push(ValidationError {
|
if let Some(target_schema) = self
|
||||||
code: if self.schema.family.is_some() { "NO_FAMILY_MATCH".to_string() } else { "NO_ONEOF_MATCH".to_string() },
|
.schema
|
||||||
message: format!("Payload provided discriminator {}='{}' which matches none of the required candidate sub-schemas", disc, val),
|
.one_of
|
||||||
path: self.path.to_string(),
|
.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);
|
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 {
|
result.errors.push(ValidationError {
|
||||||
code: "MISSING_TYPE".to_string(),
|
code: if self.schema.family.is_some() {
|
||||||
message: format!("Missing '{}' discriminator. Unable to resolve polymorphic boundaries", disc),
|
"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(),
|
path: self.path.to_string(),
|
||||||
});
|
});
|
||||||
return Ok(false);
|
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)) => {
|
Some(crate::database::object::SchemaTypeOrArray::Multiple(arr)) => {
|
||||||
if arr.contains(&payload_primitive.to_string()) || (payload_primitive == "integer" && arr.contains(&"number".to_string())) {
|
if arr.contains(&payload_primitive.to_string())
|
||||||
// It natively matched a primitive in the array options, skip forcing custom proxy fallback
|
|| (payload_primitive == "integer" && arr.contains(&"number".to_string()))
|
||||||
|
{
|
||||||
|
// It natively matched a primitive in the array options, skip forcing custom proxy fallback
|
||||||
} else {
|
} else {
|
||||||
for t in arr {
|
for t in arr {
|
||||||
if !crate::database::object::is_primitive_type(t) {
|
if !crate::database::object::is_primitive_type(t) {
|
||||||
custom_types.push(t.clone());
|
custom_types.push(t.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => {}
|
None => {}
|
||||||
|
|||||||
Reference in New Issue
Block a user