Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a53e89df52 | |||
| 57bbe11013 | |||
| 386b7ad012 | |||
| a91460b390 | |||
| 0017c598e1 | |||
| 665a821bf9 | |||
| be78af1507 | |||
| 3cca5ef2d5 | |||
| 5f45df6c11 | |||
| 9387152859 | |||
| 4ab1b210ae | |||
| 7c8df22709 | |||
| e4286ac6a9 | |||
| 4411ac82f7 |
@ -4,9 +4,11 @@ description: jspg work preparation
|
||||
|
||||
This workflow will get you up-to-speed on the JSPG custom json-schema-based cargo pgrx postgres validation extension. Everything you read will be in the jspg directory/project.
|
||||
|
||||
Read over this entire workflow and commit to every section of work in a task list, so that you don't stop half way through before reviewing all of the directories and files mentioned. Do not ask for confirmation after generating this task list and proceed through all sections in your list.
|
||||
Read over this entire workflow and commit to every section of work in a fresh task list (DO THIS FIRST), so that you don't stop half way through before reviewing all of the directories and files mentioned. Do not ask for confirmation after generating this task list and proceed through all sections in your list.
|
||||
|
||||
Please analyze the files and directories and do not use cat, find, or the terminal to discover or read in any of these files. Analyze every file mentioned. If a directory is mentioned or a /*, please analyze the directory, every single file at its root, and recursively analyze every subdirectory and every single file in every subdirectory to capture not just critical files, but the entirety of what is requested. I state again, DO NOT just review a cherry picking of files in any folder or wildcard specified. Review 100% of all files discovered recursively!
|
||||
Please analyze the files and directories and do not use cat, find, or the terminal AT ALL to discover or read in any of these files! USE YOUR TOOLS ONLY. Analyze every file mentioned. If a directory is mentioned or a /*, please analyze the directory, every single file at its root, and recursively analyze every subdirectory and every single file in every subdirectory to capture not just critical files, but the entirety of what is requested. I state again, DO NOT just review a cherry picking of files in any folder or wildcard specified. Review 100% of all files discovered recursively!
|
||||
|
||||
Do not make any code changes. Just focus on your task list and reading files!
|
||||
|
||||
Section 1: Various Documentation
|
||||
|
||||
@ -48,7 +50,6 @@ Now, review some punc type and enum source in the api project with api/ these fi
|
||||
- api/punc/sql/tables.sql
|
||||
- api/punc/sql/domains.sql
|
||||
- api/punc/sql/indexes.sql
|
||||
- api/punc/sql/functions/entity.sql
|
||||
- api/punc/sql/functions/puncs.sql
|
||||
- api/punc/sql/puncs/entity.sql
|
||||
- api/punc/sql/puncs/persons.sql
|
||||
|
||||
198
GEMINI.md
198
GEMINI.md
@ -10,7 +10,7 @@ JSPG operates by deeply integrating the JSON Schema Draft 2020-12 specification
|
||||
* **Queryer**: Compile JSON Schemas into static, cached SQL SPI `SELECT` plans for fetching full entities or isolated ad-hoc object boundaries.
|
||||
|
||||
### 🎯 Goals
|
||||
1. **Draft 2020-12 Compliance**: Attempt to adhere to the official 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.
|
||||
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.
|
||||
4. **Structural Inheritance**: Support object-oriented schema design via Implicit Keyword Shadowing and virtual `$family` references natively mapped to Postgres table constraints.
|
||||
@ -20,9 +20,144 @@ JSPG operates by deeply integrating the JSON Schema Draft 2020-12 specification
|
||||
To support high-throughput operations while allowing for runtime updates (e.g., during hot-reloading), JSPG uses an **Atomic Swap** pattern:
|
||||
1. **Parser Phase**: Schema JSONs are parsed into ordered `Schema` structs.
|
||||
2. **Compiler Phase**: The database iterates all parsed schemas and pre-computes native optimization maps (Descendants Map, Depths Map, Variations Map).
|
||||
3. **Immutable AST Caching**: The `Validator` struct immutably owns the `Database` registry. Schemas themselves are frozen structurally, but utilize `OnceLock` interior mutability during the Compilation Phase to permanently cache resolved `$ref` inheritances, properties, and `compiled_edges` directly onto their AST nodes. This guarantees strict `O(1)` relationship and property validation execution at runtime without locking or recursive DB polling.
|
||||
3. **Immutable AST Caching**: The `Validator` struct immutably owns the `Database` registry. Schemas themselves are frozen structurally, but utilize `OnceLock` interior mutability during the Compilation Phase to permanently cache resolved `type` inheritances, properties, and `compiled_edges` directly onto their AST nodes. This guarantees strict `O(1)` relationship and property validation execution at runtime without locking or recursive DB polling.
|
||||
4. **Lock-Free Reads**: Incoming operations acquire a read lock just long enough to clone the `Arc` inside an `RwLock<Option<Arc<Validator>>>`, ensuring zero blocking during schema updates.
|
||||
|
||||
### Global API Reference
|
||||
These functions operate on the global `GLOBAL_JSPG` engine instance and provide administrative boundaries:
|
||||
|
||||
* `jspg_setup(database jsonb) -> jsonb`: Initializes the engine. Deserializes the full database schema registry (types, enums, puncs, relations) from Postgres and compiles them into memory atomically.
|
||||
* `jspg_teardown() -> jsonb`: Clears the current session's engine instance from `GLOBAL_JSPG`, resetting the cache.
|
||||
* `jspg_schemas() -> jsonb`: Exports the fully compiled AST snapshot (including all inherited dependencies) out of `GLOBAL_JSPG` into standard JSON Schema representations.
|
||||
|
||||
---
|
||||
|
||||
## 2. Schema Modeling (Punc Developer Guide)
|
||||
|
||||
JSPG augments standard JSON Schema 2020-12 to provide an opinionated, strict, and highly ergonomic Object-Oriented paradigm. Developers defining Punc Data Models should follow these conventions.
|
||||
|
||||
### Types of Types
|
||||
* **Table-Backed (Entity Types)**: Primarily defined in root `types` schemas. These represent physical Postgres tables.
|
||||
* They are implicitly registered in the Global Registry using their precise key name mapped from the database compilation phase.
|
||||
* The schema conceptually requires a `type` discriminator at runtime so the engine knows what physical variation to interact with.
|
||||
* Can inherit other entity types to build lineage (e.g. `person` -> `organization` -> `entity`) natively using the `type` property.
|
||||
* **Field-Backed (JSONB Bubbles)**: These are shapes that live entirely inside a Postgres JSONB column without being tied to a top-level table constraint.
|
||||
* **Global Schema Registration**: Roots must be attached to the top-level keys mapped from the `types`, `enums`, or `puncs` database tables.
|
||||
* They can re-use the standard `type` discriminator locally for `oneOf` polymorphism without conflicting with global Postgres Table constraints.
|
||||
|
||||
### Discriminators & The Dot Convention (A.B)
|
||||
In Punc, polymorphic targets like explicit tagged unions or STI (Single Table Inheritance) rely on discriminators. Because Punc favors universal consistency, a schema's data contract must be explicit and mathematically identical regardless of the routing context an endpoint consumes it through.
|
||||
|
||||
**The 2-Tier Paradigm**: The system inherently prevents "God Tables" by restricting routing to exactly two dimensions, guaranteeing absolute $O(1)$ lookups without ambiguity:
|
||||
1. **Vertical Routing (`type`)**: Identifies the specific Postgres Table lineage (e.g. `person` vs `organization`).
|
||||
2. **Horizontal Routing (`kind.type`)**: Natively evaluates Single Table Inheritance. The runtime dynamically concatenates `$kind.$type` to yield the namespace-protected schema key (e.g. `light.person`), maintaining collision-free schema registration.
|
||||
|
||||
Therefore, any schema that participates in polymorphic discrimination MUST explicitly define its discriminator properties natively inside its `properties` block. However, to stay DRY and maintain flexible APIs, you **DO NOT** need to hardcode `const` values, nor should you add them to your `required` array. The Punc engine treats `type` and `kind` as **magic properties**.
|
||||
|
||||
**Magic Validation Constraints**:
|
||||
* **Dynamically Required**: The system inherently drives the need for their requirement. The Validator dynamically expects the discriminators and structurally bubbles `MISSING_TYPE` ultimata ONLY when a polymorphic router (`$family` / `oneOf`) dynamically requires them to resolve a path. You never manually put them in the JSON schema `required` block.
|
||||
* **Implicit Resolution**: When wrapped in `$family` or `oneOf`, the polymorphic router can mathematically parse the schema key (e.g. `light.person`) and natively validate that `type` equals `"person"` and `kind` equals `"light"`, bubbling `CONST_VIOLATED` if they mismatch, all without you ever hardcoding `const` limitations.
|
||||
* **Generator Explicitness**: Because Postgres is the Single Source of Truth, forcing the explicit definition in `properties` initially guarantees the downstream Dart/Go code generators observe the fields and can cleanly serialize them dynamically back to the server.
|
||||
|
||||
For example, a schema registered under the exact key `"light.person"` inside the database registry must natively define its own structural boundaries:
|
||||
```json
|
||||
{
|
||||
"type": "person",
|
||||
"properties": {
|
||||
"type": { "type": "string" },
|
||||
"kind": { "type": "string" }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
* **The Object Contract (Presence)**: The Object enforces its own structural integrity mechanically. Standard JSON Validation natively ensures `type` and `kind` are dynamically present as expected.
|
||||
* **The Dynamic Values (`db.types`)**: Because the `type` and `kind` properties technically exist, the Punc engine dynamically intercepts them during `validate_object`. It mathematically parses the schema key (e.g. `light.person`) and natively validates that `type` equals `"person"` (or a valid descendant in `db.types`) and `kind` equals `"light"`, bubbling `CONST_VIOLATED` if they mismatch.
|
||||
* **The Routing Contract**: When wrapped in `$family` or `oneOf`, the polymorphic router can execute Lightning Fast $O(1)$ fast-paths by reading the payload's `type`/`kind` identifiers, and gracefully fallback to standard structural failure if omitted.
|
||||
|
||||
### Composition & Inheritance (The `type` keyword)
|
||||
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"`).
|
||||
* **Single Inheritance**: Setting `"type": "user"` acts exactly like an `extends` keyword. The schema borrows all fields and constraints from the `user` identity. During `jspg_setup`, the compiler recursively crawls the dependencies to map the physical Postgres table, permanently mapping its type restriction to `"object"` under the hood so JSON standards remain unbroken.
|
||||
* **Implicit Keyword Shadowing**: Unlike standard JSON Schema inheritance, local property definitions natively override and shadow inherited properties.
|
||||
* **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.
|
||||
|
||||
### 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.
|
||||
|
||||
* **`$family` (Target-Based Polymorphism)**: An explicit Punc compiler macro instructing the engine to resolve dynamic options against the registered database `types` variations or its inner schema registry. It uses the exact physical constraints of the database to build SQL and validation routes.
|
||||
* **Scenario A: Global Tables (Vertical Routing)**
|
||||
* *Setup*: `{ "$family": "organization" }`
|
||||
* *Execution*: The engine queries `db.types.get("organization").variations` and finds `["bot", "organization", "person"]`. Because organizations are structurally table-backed, the `$family` automatically uses `type` as the discriminator.
|
||||
* *Options*: `bot` -> `bot`, `person` -> `person`, `organization` -> `organization`.
|
||||
* **Scenario B: Prefixed Tables (Vertical Projection)**
|
||||
* *Setup*: `{ "$family": "light.organization" }`
|
||||
* *Execution*: The engine sees the prefix `light.` and base `organization`. It queries `db.types.get("organization").variations` and dynamically prepends the prefix to discover the relevant UI schemas.
|
||||
* *Options*: `person` -> `light.person`, `organization` -> `light.organization`. (If a projection like `light.bot` does not exist in `db.schemas`, it is safely ignored).
|
||||
* **Scenario C: Single Table Inheritance (Horizontal Routing)**
|
||||
* *Setup*: `{ "$family": "widget" }` (Where `widget` is a table type but has no external variations).
|
||||
* *Execution*: The engine queries `db.types.get("widget").variations` and finds only `["widget"]`. Since it lacks table inheritance, it is treated as STI. The engine scans the specific, confined `schemas` array directly under `db.types.get("widget")` for any registered key terminating in the base `.widget` (e.g., `stock.widget`). The `$family` automatically uses `kind` as the discriminator.
|
||||
* *Options*: `stock` -> `stock.widget`, `tasks` -> `tasks.widget`.
|
||||
|
||||
* **`oneOf` (Strict Tagged Unions)**: A hardcoded list of candidate schemas. Unlike `$family` which relies on global DB metadata, `oneOf` forces pure mathematical structural evaluation of the provided candidates. It strictly bans typical JSON Schema "Union of Sets" fallback searches. Every candidate MUST possess a mathematically unique discriminator payload to allow $O(1)$ routing.
|
||||
* **Disjoint Types**: `oneOf: [{ "type": "person" }, { "type": "widget" }]`. The engine succeeds because the native `type` acts as a unique discriminator (`"person"` vs `"widget"`).
|
||||
* **STI Types**: `oneOf: [{ "type": "heavy.person" }, { "type": "light.person" }]`. The engine succeeds. Even though both share `"type": "person"`, their explicit discriminator is `kind` (`"heavy"` vs `"light"`), ensuring unique $O(1)$ fast-paths.
|
||||
* **Conflicting Types**: `oneOf: [{ "type": "person" }, { "type": "light.person" }]`. The engine **fails compilation natively**. Both schemas evaluate to `"type": "person"` and neither provides a disjoint `kind` constraint, making them mathematically ambiguous and impossible to route in $O(1)$ time.
|
||||
|
||||
### Conditionals (`cases`)
|
||||
Standard JSON Schema forces developers to write deeply nested `allOf` -> `if` -> `properties` blocks just to execute conditional branching. **JSPG completely abandons `allOf` and this practice.** For declarative business logic and structural mutations conditionally based upon property bounds, use the top-level `cases` array.
|
||||
|
||||
It evaluates as an **Independent Declarative Rules Engine**. Every `Case` block within the array is evaluated independently in parallel. For a given rule, if the `when` condition evaluates to true, its `then` schema is executed. If it evaluates to false, its `else` schema is executed (if present). To maintain strict standard JSON Schema compatibility internally, the `when` block utilizes pure JSON Schema `properties` definitions (e.g. `enum`, `const`) rather than injecting unstandardized MongoDB operators. Because `when`, `then`, and `else` are themselves standard schemas, they natively support nested `cases` to handle mutually exclusive `else if` architectures.
|
||||
|
||||
```json
|
||||
{
|
||||
"cases": [
|
||||
{
|
||||
"when": {
|
||||
"properties": {
|
||||
"status": { "const": "unverified" }
|
||||
},
|
||||
"required": ["status"]
|
||||
},
|
||||
"then": {
|
||||
"required": ["amount_1", "amount_2"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"when": {
|
||||
"properties": { "kind": { "const": "credit" } },
|
||||
"required": ["kind"]
|
||||
},
|
||||
"then": {
|
||||
"required": ["details"]
|
||||
},
|
||||
"else": {
|
||||
"cases": [
|
||||
{
|
||||
"when": { "properties": { "kind": { "const": "checking" } }, "required": ["kind"] },
|
||||
"then": { "required": ["routing_number"] }
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Strict by Default & Extensibility
|
||||
* **Strictness**: By default, any property not explicitly defined in the schema causes a validation error (effectively enforcing `additionalProperties: false` globally).
|
||||
* **Extensibility (`extensible: true`)**: To allow a free-for-all of undefined properties, schemas must explicitly declare `"extensible": true`.
|
||||
* **Structured Additional Properties**: If `additionalProperties: {...}` is defined as a schema, arbitrary keys are allowed so long as their values match the defined type constraint.
|
||||
* **Inheritance Boundaries**: Strictness resets when crossing non-primitive `type` boundaries. A schema extending a strict parent remains strict unless it explicitly overrides with `"extensible": true`.
|
||||
|
||||
### Format Leniency for Empty Strings
|
||||
To simplify frontend form validation, format validators specifically for `uuid`, `date-time`, and `email` explicitly allow empty strings (`""`), treating them as "present but unset".
|
||||
|
||||
---
|
||||
|
||||
## 3. Database
|
||||
|
||||
The Database module manages the core execution graphs and structural compilation of the Postgres environment.
|
||||
|
||||
### Relational Edge Resolution
|
||||
When compiling nested object graphs or arrays, the JSPG engine must dynamically infer which Postgres Foreign Key constraint correctly bridges the parent to the nested schema. To guarantee deterministic SQL generation, it utilizes a strict, multi-step algebraic resolution process applied during the `OnceLock` Compilation phase:
|
||||
|
||||
@ -33,16 +168,15 @@ When compiling nested object graphs or arrays, the JSPG engine must dynamically
|
||||
5. **Implicit Base Fallback (1:M)**: If no explicit prefix matches, and M:M deduction fails, the compiler filters for exactly one remaining relation with a `null` prefix (e.g. `fk_invoice_line_invoice` -> `prefix: null`). A `null` prefix mathematically denotes the core structural parent-child ownership edge and is used safely as a fallback.
|
||||
6. **Deterministic Abort**: If the engine exhausts all deduction pathways and the edge remains ambiguous, it explicitly aborts schema compilation (`returns None`) rather than silently generating unpredictable SQL.
|
||||
|
||||
### Global API Reference
|
||||
These functions operate on the global `GLOBAL_JSPG` engine instance and provide administrative boundaries:
|
||||
|
||||
* `jspg_setup(database jsonb) -> jsonb`: Initializes the engine. Deserializes the full database schema registry (types, enums, puncs, relations) from Postgres and compiles them into memory atomically.
|
||||
* `jspg_teardown() -> jsonb`: Clears the current session's engine instance from `GLOBAL_JSPG`, resetting the cache.
|
||||
* `jspg_schemas() -> jsonb`: Exports the fully compiled AST snapshot (including all inherited dependencies) out of `GLOBAL_JSPG` into standard JSON Schema representations.
|
||||
### Subschema Promotion
|
||||
To seamlessly support deeply nested Object and Array structures, JSPG aggressively promotes them to standalone topological entities during the database compilation phase.
|
||||
* **Path Generation:** While evaluating a unified graph originating from a base `types`, `enums`, or `puncs` key, the compiler tracks its exact path descent into nested objects and arrays. It dynamically calculates a localized alias string by appending a `/` pathing syntax (e.g., `base_schema_key/nested/path`) representing exactly its structural constraints.
|
||||
* **Promotion:** This nested subschema chunk is mathematically elevated to its own independent key in the `db.schemas` cache registry using its full path. This guarantees that $O(1)$ WebSockets or isolated queries can natively target any arbitrary nested sub-object of a massive database topology directly without recursively re-parsing its parent's AST block every read. Note that you cannot use the `type` attribute to statically inherit from these automatically promoted subschemas.
|
||||
* **Primitive Confinement:** Purely scalar or primitive branches (like `oneOf: [{type: "string"}, {type: "null"}]`) bypass global topological promotion. They are evaluated directly within the execution engine via isolated Tuple Indexes to explicitly protect the global DB Registry and Go Mixer from memory bloat.
|
||||
|
||||
---
|
||||
|
||||
## 2. Validator
|
||||
## 4. Validator
|
||||
|
||||
The Validator provides strict, schema-driven evaluation for the "Punc" architecture.
|
||||
|
||||
@ -53,35 +187,14 @@ 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.
|
||||
|
||||
* **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.
|
||||
|
||||
#### A. Polymorphism & Referencing (`$ref`, `$family`, and Native Types)
|
||||
* **Native Type Discrimination (`variations`)**: Schemas defined inside a Postgres `type` are Entities. The validator securely and implicitly manages their `"type"` property. If an entity inherits from `user`, incoming JSON can safely define `{"type": "person"}` without errors, thanks to `compiled_variations` inheritance.
|
||||
* **Structural Inheritance & Viral Infection (`$ref`)**: `$ref` is used exclusively for structural inheritance and explicit composition, *never* for union creation. A `$ref` ALWAYS targets a specific, *single* schema struct (e.g., `full.person`). It represents an explicit, known structural shape. A Punc request schema that `$ref`s an Entity virally inherits all physical database polymorphism rules for that target.
|
||||
* **Shape Polymorphism (`$family`)**: Unlike `$ref`, `$family` ALWAYS targets an abstract *table lineage* (e.g., `organization` or `widget`). It instructs the engine to dynamically expand the response payload into multiple possible schema shapes based on the row's physical database `type`. If `{"$family": "widget"}` is used, the Validator dynamically identifies *every* schema in the registry that `$ref`s `widget` (e.g., `stock.widget`, `task.widget`) and recursively evaluates the JSON against all of them.
|
||||
* **Strict Matches & Depth Heuristic**: Polymorphic structures MUST match exactly **one** schema permutation. If multiple inherited struct permutations pass, JSPG applies the **Depth Heuristic Tie-Breaker**, selecting the candidate deepest in the inheritance tree.
|
||||
|
||||
#### B. Dot-Notation Schema Resolution & Database Mapping
|
||||
* **The Dot Convention**: When a schema represents a specific variation or shape of an underlying physical database `Type` (e.g., a "summary" of a "person"), its `$id` must adhere to a dot-notation suffix convention (e.g., `summary.person` or `full.person`).
|
||||
* **Entity Resolution**: The framework (Validator, Queryer, Merger) dynamically determines the backing PostgreSQL table structure by splitting the schema's `$id` (or `$ref`) by `.` and extracting the **last segment** (`next_back()`). If the last segment matches a known Database Type (like `person`), the framework natively applies that table's inheritance rules, variations, and physical foreign keys to the schema graph, regardless of the prefix.
|
||||
|
||||
#### C. Strict by Default & Extensibility
|
||||
* **Strictness**: By default, any property not explicitly defined in the schema causes a validation error (effectively enforcing `additionalProperties: false` globally).
|
||||
* **Extensibility (`extensible: true`)**: To allow a free-for-all of undefined properties, schemas must explicitly declare `"extensible": true`.
|
||||
* **Structured Additional Properties**: If `additionalProperties: {...}` is defined as a schema, arbitrary keys are allowed so long as their values match the defined type constraint.
|
||||
* **Inheritance Boundaries**: Strictness resets when crossing `$ref` boundaries. A schema extending a strict parent remains strict unless it explicitly overrides with `"extensible": true`.
|
||||
|
||||
#### D. Implicit Keyword Shadowing
|
||||
* **Inheritance (`$ref` + properties)**: Unlike standard JSON Schema, when a schema uses `$ref` alongside local properties, JSPG implements **Smart Merge**. Local constraints natively take precedence over (shadow) inherited constraints for the same keyword.
|
||||
* *Example*: If `entity` has `type: {const: "entity"}`, but `person` defines `type: {const: "person"}`, the local `person` const cleanly overrides the inherited one.
|
||||
* **Composition (`allOf`)**: When evaluating `allOf`, standard intersection rules apply seamlessly. No shadowing occurs, meaning all constraints from all branches must pass.
|
||||
|
||||
#### E. Format Leniency for Empty Strings
|
||||
To simplify frontend form validation, format validators specifically for `uuid`, `date-time`, and `email` explicitly allow empty strings (`""`), treating them as "present but unset".
|
||||
* **Discriminator Fast Paths & Extraction**: When executing a polymorphic node (`oneOf` or `$family`), the engine statically analyzes the incoming JSON payload for the literal `type` and `kind` string coordinates. It routes the evaluation specifically to matching candidates in $O(1)$ while returning `MISSING_TYPE` ultimata directly.
|
||||
* **Missing Type Ultimatum**: If an entity logically requires a discriminator and the JSON payload omits it, JSPG short-circuits branch execution entirely, bubbling a single, perfectly-pathed `MISSING_TYPE` error back to the UI natively to prevent confusing cascading failures.
|
||||
* **Golden Match Context**: When exactly one structural candidate perfectly maps a discriminator, the Validator exclusively cascades that specific structural error context directly to the user, stripping away all noise generated by other parallel schemas.
|
||||
* **Topological Array Pathing**: Instead of relying on explicit `$id` references or injected properties, array iteration paths are dynamically typed based on their compiler boundary constraints. If the array's `items` schema resolves to a topological table-backed entity (e.g., inheriting via a `$family` macro tracked in the global DB catalog), the array locks paths and derives element indexes from their actual UUID paths (`array/widget-1/name`), natively enforcing database continuity. If evaluating isolated ad-hoc JSONB elements, strict numeric indexing is enforced natively (`array/1/name`) preventing synthetic payload manipulation.
|
||||
|
||||
---
|
||||
|
||||
|
||||
## 3. Merger
|
||||
## 5. Merger
|
||||
|
||||
The Merger provides an automated, high-performance graph synchronization engine. It orchestrates the complex mapping of nested JSON objects into normalized Postgres relational tables, honoring all inheritance and graph constraints.
|
||||
|
||||
@ -106,7 +219,7 @@ The Merger provides an automated, high-performance graph synchronization engine.
|
||||
|
||||
---
|
||||
|
||||
## 4. Queryer
|
||||
## 6. Queryer
|
||||
|
||||
The Queryer transforms Postgres into a pre-compiled Semantic Query Engine, designed to serve the exact shape of Punc responses directly via SQL.
|
||||
|
||||
@ -116,7 +229,8 @@ The Queryer transforms Postgres into a pre-compiled Semantic Query Engine, desig
|
||||
### Core Features
|
||||
|
||||
* **Caching Strategy (DashMap SQL Caching)**: The Queryer securely caches its compiled, static SQL string templates per schema permutation inside the `GLOBAL_JSPG` concurrent `DashMap`. This eliminates recursive AST schema crawling on consecutive requests. Furthermore, it evaluates the strings via Postgres SPI (Server Programming Interface) Prepared Statements, leveraging native database caching of execution plans for extreme performance.
|
||||
* **Schema-to-SQL Compilation**: Compiles JSON Schema ASTs spanning deep arrays directly into static, pre-planned SQL multi-JOIN queries. This explicitly features the `Smart Merge` evaluation engine which natively translates properties through `allOf` and `$ref` inheritances, mapping JSON fields specifically to their physical database table aliases during translation.
|
||||
* **Schema-to-SQL Compilation**: Compiles JSON Schema ASTs spanning deep arrays directly into static, pre-planned SQL multi-JOIN queries. This explicitly features the `Smart Merge` evaluation engine which natively translates properties through `type` inheritances, mapping JSON fields specifically to their physical database table aliases during translation.
|
||||
* **Root Null-Stripping Optimization**: Unlike traditional nested document builders, the Queryer intelligently defers Postgres' natively recursive `jsonb_strip_nulls` execution to the absolute apex of the compiled query pipeline. The compiler organically layers millions of rapid `jsonb_build_object()` sub-query allocations instantly, wrapping them in a singular overarching pass. This strips all empty optionals uniformly before exiting the database, maximizing CPU throughput.
|
||||
* **Dynamic Filtering**: Binds parameters natively through `cue.filters` objects. The queryer enforces a strict, structured, MongoDB-style operator syntax to map incoming JSON request constraints directly to their originating structural table columns. Filters support both flat path notation (e.g., `"contacts/is_primary": {...}`) and deeply nested recursive JSON structures (e.g., `{"contacts": {"is_primary": {...}}}`). The queryer recursively traverses and flattens these structures at AST compilation time.
|
||||
* **Equality / Inequality**: `{"$eq": value}`, `{"$ne": value}` automatically map to `=` and `!=`.
|
||||
* **Comparison**: `{"$gt": ...}`, `{"$gte": ...}`, `{"$lt": ...}`, `{"$lte": ...}` directly compile to Postgres comparison operators (`> `, `>=`, `<`, `<=`).
|
||||
@ -125,16 +239,12 @@ The Queryer transforms Postgres into a pre-compiled Semantic Query Engine, desig
|
||||
* **Type Casting**: Safely resolves dynamic combinations by casting values instantly into the physical database types mapped in the schema (e.g. parsing `uuid` bindings to `::uuid`, formatting DateTimes to `::timestamptz`, and numbers to `::numeric`).
|
||||
* **Polymorphic SQL Generation (`$family`)**: Compiles `$family` properties by analyzing the **Physical Database Variations**, *not* the schema descendants.
|
||||
* **The Dot Convention**: When a schema requests `$family: "target.schema"`, the compiler extracts the base type (e.g. `schema`) and looks up its Physical Table definition.
|
||||
* **Multi-Table Branching**: If the Physical Table is a parent to other tables (e.g. `organization` has variations `["organization", "bot", "person"]`), the compiler generates a dynamic `CASE WHEN type = '...' THEN ...` query, expanding into `JOIN`s for each variation.
|
||||
* **Multi-Table Branching**: If the Physical Table is a parent to other tables (e.g. `organization` has variations `["organization", "bot", "person"]`), the compiler generates a dynamic `CASE WHEN type = '...' THEN ...` query, expanding into sub-queries for each variation. To ensure safe resolution, the compiler dynamically evaluates correlation boundaries: it attempts standard Relational Edge discovery first. If no explicit relational edge exists (indicating pure Table Inheritance rather than a standard foreign-key graph relationship), it safely invokes a **Table Parity Fallback**. This generates an explicit ID correlation constraint (`AND inner.id = outer.id`), perfectly binding the structural variations back to the parent row to eliminate Cartesian products.
|
||||
* **Single-Table Bypass**: If the Physical Table is a leaf node with only one variation (e.g. `person` has variations `["person"]`), the compiler cleanly bypasses `CASE` generation and compiles a simple `SELECT` across the base table, as all schema extensions (e.g. `light.person`, `full.person`) are guaranteed to reside in the exact same physical row.
|
||||
|
||||
### Ad-Hoc Schema Promotion
|
||||
---
|
||||
|
||||
To seamlessly support deeply nested, inline Object definitions that don't declare an explicit `$id`, JSPG aggressively promotes them to standalone topological entities during the database compilation phase.
|
||||
* **Hash Generation:** While evaluating the unified graph, if the compiler enters an `Object` or `Array` structure completely lacking an `$id`, it dynamically calculates a localized hash alias representing exactly its structural constraints.
|
||||
* **Promotion:** This inline chunk is mathematically elevated to its own `$id` in the `db.schemas` cache registry. This guarantees that $O(1)$ WebSockets or isolated queries can natively target any arbitrary sub-object of a massive database topology directly without recursively re-parsing its parent's AST block every read.
|
||||
|
||||
## 5. Testing & Execution Architecture
|
||||
## 7. Testing & Execution Architecture
|
||||
|
||||
JSPG implements a strict separation of concerns to bypass the need to boot a full PostgreSQL cluster for unit and integration testing. Because `pgrx::spi::Spi` directly links to PostgreSQL C-headers, building the library with `cargo test` on macOS natively normally results in fatal `dyld` crashes.
|
||||
|
||||
|
||||
@ -2,9 +2,8 @@
|
||||
{
|
||||
"description": "additionalProperties validates properties not matched by properties",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"$id": "schema1",
|
||||
"schemas": {
|
||||
"schema1": {
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "string"
|
||||
@ -17,7 +16,7 @@
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -62,8 +61,8 @@
|
||||
{
|
||||
"description": "extensible: true with additionalProperties still validates structure",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"additionalProperties_1_0": {
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "string"
|
||||
@ -72,10 +71,9 @@
|
||||
"extensible": true,
|
||||
"additionalProperties": {
|
||||
"type": "integer"
|
||||
},
|
||||
"$id": "additionalProperties_1_0"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -108,9 +106,8 @@
|
||||
{
|
||||
"description": "complex additionalProperties with object and array items",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"$id": "schema3",
|
||||
"schemas": {
|
||||
"schema3": {
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string"
|
||||
@ -123,7 +120,7 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
|
||||
@ -1,677 +0,0 @@
|
||||
[
|
||||
{
|
||||
"description": "allOf",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
"properties": {
|
||||
"bar": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"bar"
|
||||
]
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"foo"
|
||||
]
|
||||
}
|
||||
],
|
||||
"$id": "allOf_0_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "allOf",
|
||||
"data": {
|
||||
"foo": "baz",
|
||||
"bar": 2
|
||||
},
|
||||
"schema_id": "allOf_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "mismatch second",
|
||||
"data": {
|
||||
"foo": "baz"
|
||||
},
|
||||
"schema_id": "allOf_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "mismatch first",
|
||||
"data": {
|
||||
"bar": 2
|
||||
},
|
||||
"schema_id": "allOf_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "wrong type",
|
||||
"data": {
|
||||
"foo": "baz",
|
||||
"bar": "quux"
|
||||
},
|
||||
"schema_id": "allOf_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "allOf with base schema",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"properties": {
|
||||
"bar": {
|
||||
"type": "integer"
|
||||
},
|
||||
"baz": {},
|
||||
"foo": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"bar"
|
||||
],
|
||||
"allOf": [
|
||||
{
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"foo"
|
||||
]
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"baz": {
|
||||
"type": "null"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"baz"
|
||||
]
|
||||
}
|
||||
],
|
||||
"$id": "allOf_1_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "valid",
|
||||
"data": {
|
||||
"foo": "quux",
|
||||
"bar": 2,
|
||||
"baz": null
|
||||
},
|
||||
"schema_id": "allOf_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "mismatch base schema",
|
||||
"data": {
|
||||
"foo": "quux",
|
||||
"baz": null
|
||||
},
|
||||
"schema_id": "allOf_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "mismatch first allOf",
|
||||
"data": {
|
||||
"bar": 2,
|
||||
"baz": null
|
||||
},
|
||||
"schema_id": "allOf_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "mismatch second allOf",
|
||||
"data": {
|
||||
"foo": "quux",
|
||||
"bar": 2
|
||||
},
|
||||
"schema_id": "allOf_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "mismatch both",
|
||||
"data": {
|
||||
"bar": 2
|
||||
},
|
||||
"schema_id": "allOf_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "allOf simple types",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
"maximum": 30
|
||||
},
|
||||
{
|
||||
"minimum": 20
|
||||
}
|
||||
],
|
||||
"$id": "allOf_2_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "valid",
|
||||
"data": 25,
|
||||
"schema_id": "allOf_2_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "mismatch one",
|
||||
"data": 35,
|
||||
"schema_id": "allOf_2_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "allOf with boolean schemas, all true",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"allOf": [
|
||||
true,
|
||||
true
|
||||
],
|
||||
"$id": "allOf_3_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "any value is valid",
|
||||
"data": "foo",
|
||||
"schema_id": "allOf_3_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "allOf with boolean schemas, some false",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"allOf": [
|
||||
true,
|
||||
false
|
||||
],
|
||||
"$id": "allOf_4_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "any value is invalid",
|
||||
"data": "foo",
|
||||
"schema_id": "allOf_4_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "allOf with boolean schemas, all false",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"allOf": [
|
||||
false,
|
||||
false
|
||||
],
|
||||
"$id": "allOf_5_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "any value is invalid",
|
||||
"data": "foo",
|
||||
"schema_id": "allOf_5_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "allOf with one empty schema",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"allOf": [
|
||||
{}
|
||||
],
|
||||
"$id": "allOf_6_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "any data is valid",
|
||||
"data": 1,
|
||||
"schema_id": "allOf_6_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "allOf with two empty schemas",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"allOf": [
|
||||
{},
|
||||
{}
|
||||
],
|
||||
"$id": "allOf_7_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "any data is valid",
|
||||
"data": 1,
|
||||
"schema_id": "allOf_7_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "allOf with the first empty schema",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"allOf": [
|
||||
{},
|
||||
{
|
||||
"type": "number"
|
||||
}
|
||||
],
|
||||
"$id": "allOf_8_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "number is valid",
|
||||
"data": 1,
|
||||
"schema_id": "allOf_8_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "string is invalid",
|
||||
"data": "foo",
|
||||
"schema_id": "allOf_8_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "allOf with the last empty schema",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
"type": "number"
|
||||
},
|
||||
{}
|
||||
],
|
||||
"$id": "allOf_9_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "number is valid",
|
||||
"data": 1,
|
||||
"schema_id": "allOf_9_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "string is invalid",
|
||||
"data": "foo",
|
||||
"schema_id": "allOf_9_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "nested allOf, to check validation semantics",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"$id": "allOf_10_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "null is valid",
|
||||
"data": null,
|
||||
"schema_id": "allOf_10_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "anything non-null is invalid",
|
||||
"data": 123,
|
||||
"schema_id": "allOf_10_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "extensible: true allows extra properties in allOf",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
"properties": {
|
||||
"bar": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"bar"
|
||||
]
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"foo"
|
||||
]
|
||||
}
|
||||
],
|
||||
"extensible": true,
|
||||
"$id": "allOf_12_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "extra property is valid",
|
||||
"data": {
|
||||
"foo": "baz",
|
||||
"bar": 2,
|
||||
"qux": 3
|
||||
},
|
||||
"schema_id": "allOf_12_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "strict by default with allOf properties",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
"properties": {
|
||||
"foo": {
|
||||
"const": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"bar": {
|
||||
"const": 2
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"$id": "allOf_13_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "validates merged properties",
|
||||
"data": {
|
||||
"foo": 1,
|
||||
"bar": 2
|
||||
},
|
||||
"schema_id": "allOf_13_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "fails on extra property z explicitly",
|
||||
"data": {
|
||||
"foo": 1,
|
||||
"bar": 2,
|
||||
"z": 3
|
||||
},
|
||||
"schema_id": "allOf_13_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "allOf with nested extensible: true (partial looseness)",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
"properties": {
|
||||
"foo": {
|
||||
"const": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"extensible": true,
|
||||
"properties": {
|
||||
"bar": {
|
||||
"const": 2
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"$id": "allOf_14_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "extensible subschema doesn't make root extensible if root is strict",
|
||||
"data": {
|
||||
"foo": 1,
|
||||
"bar": 2,
|
||||
"z": 3
|
||||
},
|
||||
"schema_id": "allOf_14_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "strictness: allOf composition with strict refs",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "partA"
|
||||
},
|
||||
{
|
||||
"$ref": "partB"
|
||||
}
|
||||
],
|
||||
"$id": "allOf_15_0"
|
||||
},
|
||||
{
|
||||
"$id": "partA",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"$id": "partB",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "merged instance is valid",
|
||||
"data": {
|
||||
"id": "1",
|
||||
"name": "Me"
|
||||
},
|
||||
"schema_id": "allOf_15_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "extra property is invalid (root is strict)",
|
||||
"data": {
|
||||
"id": "1",
|
||||
"name": "Me",
|
||||
"extra": 1
|
||||
},
|
||||
"schema_id": "allOf_15_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "partA mismatch is invalid",
|
||||
"data": {
|
||||
"id": 1,
|
||||
"name": "Me"
|
||||
},
|
||||
"schema_id": "allOf_15_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@ -2,11 +2,9 @@
|
||||
{
|
||||
"description": "boolean schema 'true'",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"$id": "booleanSchema_0_0"
|
||||
}
|
||||
]
|
||||
"schemas": {
|
||||
"booleanSchema_0_0": {}
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -99,12 +97,11 @@
|
||||
{
|
||||
"description": "boolean schema 'false'",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"not": {},
|
||||
"$id": "booleanSchema_1_0"
|
||||
"schemas": {
|
||||
"booleanSchema_1_0": {
|
||||
"not": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
|
||||
354
fixtures/cases.json
Normal file
354
fixtures/cases.json
Normal file
@ -0,0 +1,354 @@
|
||||
[
|
||||
{
|
||||
"description": "Multi-Paradigm Declarative Cases",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"parallel_rules": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
"kind": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"cases": [
|
||||
{
|
||||
"when": {
|
||||
"properties": {
|
||||
"status": {
|
||||
"const": "unverified"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"status"
|
||||
]
|
||||
},
|
||||
"then": {
|
||||
"properties": {
|
||||
"amount_1": {
|
||||
"type": "number"
|
||||
},
|
||||
"amount_2": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"amount_1",
|
||||
"amount_2"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"when": {
|
||||
"properties": {
|
||||
"kind": {
|
||||
"const": "credit"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"kind"
|
||||
]
|
||||
},
|
||||
"then": {
|
||||
"properties": {
|
||||
"cvv": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"cvv"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"mutually_exclusive": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"cases": [
|
||||
{
|
||||
"when": {
|
||||
"properties": {
|
||||
"type": {
|
||||
"const": "A"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type"
|
||||
]
|
||||
},
|
||||
"then": {
|
||||
"properties": {
|
||||
"field_a": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"field_a"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"when": {
|
||||
"properties": {
|
||||
"type": {
|
||||
"const": "B"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type"
|
||||
]
|
||||
},
|
||||
"then": {
|
||||
"properties": {
|
||||
"field_b": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"field_b"
|
||||
]
|
||||
},
|
||||
"else": {
|
||||
"properties": {
|
||||
"fallback_b": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"fallback_b"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"nested_fallbacks": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"tier": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"cases": [
|
||||
{
|
||||
"when": {
|
||||
"properties": {
|
||||
"tier": {
|
||||
"const": "1"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"tier"
|
||||
]
|
||||
},
|
||||
"then": {
|
||||
"properties": {
|
||||
"basic": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"basic"
|
||||
]
|
||||
},
|
||||
"else": {
|
||||
"cases": [
|
||||
{
|
||||
"when": {
|
||||
"properties": {
|
||||
"tier": {
|
||||
"const": "2"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"tier"
|
||||
]
|
||||
},
|
||||
"then": {
|
||||
"properties": {
|
||||
"standard": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"standard"
|
||||
]
|
||||
},
|
||||
"else": {
|
||||
"properties": {
|
||||
"premium": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"premium"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"missing_when": {
|
||||
"type": "object",
|
||||
"cases": [
|
||||
{
|
||||
"else": {
|
||||
"properties": {
|
||||
"unconditional": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"unconditional"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "Fires only the first rule successfully",
|
||||
"data": {
|
||||
"status": "unverified",
|
||||
"amount_1": 1,
|
||||
"amount_2": 2
|
||||
},
|
||||
"schema_id": "parallel_rules",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Fires both independent parallel rules flawlessly",
|
||||
"data": {
|
||||
"status": "unverified",
|
||||
"kind": "credit",
|
||||
"amount_1": 1,
|
||||
"amount_2": 2,
|
||||
"cvv": 123
|
||||
},
|
||||
"schema_id": "parallel_rules",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Catches errors triggered concurrently by multiple independent blocked rules",
|
||||
"data": {
|
||||
"status": "unverified",
|
||||
"kind": "credit"
|
||||
},
|
||||
"schema_id": "parallel_rules",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false,
|
||||
"errors": [
|
||||
{
|
||||
"code": "REQUIRED_FIELD_MISSING",
|
||||
"details": {
|
||||
"path": "amount_1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"code": "REQUIRED_FIELD_MISSING",
|
||||
"details": {
|
||||
"path": "amount_2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"code": "REQUIRED_FIELD_MISSING",
|
||||
"details": {
|
||||
"path": "cvv"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "STRICT_PROPERTY_VIOLATION throws if an un-triggered then property is submitted",
|
||||
"data": {
|
||||
"status": "verified",
|
||||
"cvv": 123
|
||||
},
|
||||
"schema_id": "parallel_rules",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false,
|
||||
"errors": [
|
||||
{
|
||||
"code": "STRICT_PROPERTY_VIOLATION",
|
||||
"details": {
|
||||
"path": "cvv"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Successfully routes mutually exclusive properties seamlessly",
|
||||
"data": {
|
||||
"type": "A",
|
||||
"field_a": 1,
|
||||
"fallback_b": 2
|
||||
},
|
||||
"schema_id": "mutually_exclusive",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Nested fallbacks execute seamlessly",
|
||||
"data": {
|
||||
"tier": "3",
|
||||
"premium": 1
|
||||
},
|
||||
"schema_id": "nested_fallbacks",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "A case without a when executes its else indiscriminately",
|
||||
"data": {
|
||||
"unconditional": 1
|
||||
},
|
||||
"schema_id": "missing_when",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "A case without a when throws if else unconditionally requires field",
|
||||
"data": {},
|
||||
"schema_id": "missing_when",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false,
|
||||
"errors": [
|
||||
{
|
||||
"code": "REQUIRED_FIELD_MISSING",
|
||||
"details": {
|
||||
"path": "unconditional"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@ -2,12 +2,11 @@
|
||||
{
|
||||
"description": "const validation",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"const": 2,
|
||||
"$id": "const_0_0"
|
||||
"schemas": {
|
||||
"const_0_0": {
|
||||
"const": 2
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -42,8 +41,8 @@
|
||||
{
|
||||
"description": "const with object",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"const_1_0": {
|
||||
"const": {
|
||||
"foo": "bar",
|
||||
"baz": "bax"
|
||||
@ -51,10 +50,9 @@
|
||||
"properties": {
|
||||
"foo": {},
|
||||
"baz": {}
|
||||
},
|
||||
"$id": "const_1_0"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -109,16 +107,15 @@
|
||||
{
|
||||
"description": "const with array",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"const_2_0": {
|
||||
"const": [
|
||||
{
|
||||
"foo": "bar"
|
||||
}
|
||||
],
|
||||
"$id": "const_2_0"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -163,12 +160,11 @@
|
||||
{
|
||||
"description": "const with null",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"const": null,
|
||||
"$id": "const_3_0"
|
||||
"schemas": {
|
||||
"const_3_0": {
|
||||
"const": null
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -194,12 +190,11 @@
|
||||
{
|
||||
"description": "const with false does not match 0",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"const": false,
|
||||
"$id": "const_4_0"
|
||||
"schemas": {
|
||||
"const_4_0": {
|
||||
"const": false
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -234,12 +229,11 @@
|
||||
{
|
||||
"description": "const with true does not match 1",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"const": true,
|
||||
"$id": "const_5_0"
|
||||
"schemas": {
|
||||
"const_5_0": {
|
||||
"const": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -274,14 +268,13 @@
|
||||
{
|
||||
"description": "const with [false] does not match [0]",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"const_6_0": {
|
||||
"const": [
|
||||
false
|
||||
],
|
||||
"$id": "const_6_0"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -322,14 +315,13 @@
|
||||
{
|
||||
"description": "const with [true] does not match [1]",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"const_7_0": {
|
||||
"const": [
|
||||
true
|
||||
],
|
||||
"$id": "const_7_0"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -370,14 +362,13 @@
|
||||
{
|
||||
"description": "const with {\"a\": false} does not match {\"a\": 0}",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"const_8_0": {
|
||||
"const": {
|
||||
"a": false
|
||||
},
|
||||
"$id": "const_8_0"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -418,14 +409,13 @@
|
||||
{
|
||||
"description": "const with {\"a\": true} does not match {\"a\": 1}",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"const_9_0": {
|
||||
"const": {
|
||||
"a": true
|
||||
},
|
||||
"$id": "const_9_0"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -466,12 +456,11 @@
|
||||
{
|
||||
"description": "const with 0 does not match other zero-like types",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"const": 0,
|
||||
"$id": "const_10_0"
|
||||
"schemas": {
|
||||
"const_10_0": {
|
||||
"const": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -533,12 +522,11 @@
|
||||
{
|
||||
"description": "const with 1 does not match true",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"const": 1,
|
||||
"$id": "const_11_0"
|
||||
"schemas": {
|
||||
"const_11_0": {
|
||||
"const": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -573,12 +561,11 @@
|
||||
{
|
||||
"description": "const with -2.0 matches integer and float types",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"const": -2,
|
||||
"$id": "const_12_0"
|
||||
"schemas": {
|
||||
"const_12_0": {
|
||||
"const": -2
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -631,12 +618,11 @@
|
||||
{
|
||||
"description": "float and integers are equal up to 64-bit representation limits",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"const": 9007199254740992,
|
||||
"$id": "const_13_0"
|
||||
"schemas": {
|
||||
"const_13_0": {
|
||||
"const": 9007199254740992
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -680,12 +666,11 @@
|
||||
{
|
||||
"description": "nul characters in strings",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"const": "hello\u0000there",
|
||||
"$id": "const_14_0"
|
||||
"schemas": {
|
||||
"const_14_0": {
|
||||
"const": "hello\u0000there"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -711,13 +696,12 @@
|
||||
{
|
||||
"description": "characters with the same visual representation but different codepoint",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"const_15_0": {
|
||||
"const": "μ",
|
||||
"$comment": "U+03BC",
|
||||
"$id": "const_15_0"
|
||||
"$comment": "U+03BC"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -745,13 +729,12 @@
|
||||
{
|
||||
"description": "characters with the same visual representation, but different number of codepoints",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"const_16_0": {
|
||||
"const": "ä",
|
||||
"$comment": "U+00E4",
|
||||
"$id": "const_16_0"
|
||||
"$comment": "U+00E4"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -779,15 +762,14 @@
|
||||
{
|
||||
"description": "extensible: true allows extra properties in const object match",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"const_17_0": {
|
||||
"const": {
|
||||
"a": 1
|
||||
},
|
||||
"extensible": true,
|
||||
"$id": "const_17_0"
|
||||
"extensible": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
|
||||
@ -2,15 +2,14 @@
|
||||
{
|
||||
"description": "contains keyword validation",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"contains_0_0": {
|
||||
"contains": {
|
||||
"minimum": 5
|
||||
},
|
||||
"items": true,
|
||||
"$id": "contains_0_0"
|
||||
"items": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -89,15 +88,14 @@
|
||||
{
|
||||
"description": "contains keyword with const keyword",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"contains_1_0": {
|
||||
"contains": {
|
||||
"const": 5
|
||||
},
|
||||
"items": true,
|
||||
"$id": "contains_1_0"
|
||||
"items": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -146,12 +144,11 @@
|
||||
{
|
||||
"description": "contains keyword with boolean schema true",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"contains": true,
|
||||
"$id": "contains_2_0"
|
||||
"schemas": {
|
||||
"contains_2_0": {
|
||||
"contains": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -179,12 +176,11 @@
|
||||
{
|
||||
"description": "contains keyword with boolean schema false",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"contains": false,
|
||||
"$id": "contains_3_0"
|
||||
"schemas": {
|
||||
"contains_3_0": {
|
||||
"contains": false
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -221,17 +217,16 @@
|
||||
{
|
||||
"description": "items + contains",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"contains_4_0": {
|
||||
"items": {
|
||||
"multipleOf": 2
|
||||
},
|
||||
"contains": {
|
||||
"multipleOf": 3
|
||||
},
|
||||
"$id": "contains_4_0"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -289,15 +284,14 @@
|
||||
{
|
||||
"description": "contains with false if subschema",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"contains_5_0": {
|
||||
"contains": {
|
||||
"if": false,
|
||||
"else": true
|
||||
},
|
||||
"$id": "contains_5_0"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -325,14 +319,13 @@
|
||||
{
|
||||
"description": "contains with null instance elements",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"contains_6_0": {
|
||||
"contains": {
|
||||
"type": "null"
|
||||
},
|
||||
"$id": "contains_6_0"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -351,15 +344,14 @@
|
||||
{
|
||||
"description": "extensible: true allows non-matching items in contains",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"contains_7_0": {
|
||||
"contains": {
|
||||
"const": 1
|
||||
},
|
||||
"extensible": true,
|
||||
"$id": "contains_7_0"
|
||||
"extensible": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -379,14 +371,13 @@
|
||||
{
|
||||
"description": "strict by default: non-matching items in contains are invalid",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"contains_8_0": {
|
||||
"contains": {
|
||||
"const": 1
|
||||
},
|
||||
"$id": "contains_8_0"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
|
||||
@ -2,12 +2,11 @@
|
||||
{
|
||||
"description": "validation of string-encoded content based on media type",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"contentMediaType": "application/json",
|
||||
"$id": "content_0_0"
|
||||
"schemas": {
|
||||
"content_0_0": {
|
||||
"contentMediaType": "application/json"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -42,12 +41,11 @@
|
||||
{
|
||||
"description": "validation of binary string-encoding",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"contentEncoding": "base64",
|
||||
"$id": "content_1_0"
|
||||
"schemas": {
|
||||
"content_1_0": {
|
||||
"contentEncoding": "base64"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -82,13 +80,12 @@
|
||||
{
|
||||
"description": "validation of binary-encoded media type documents",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"content_2_0": {
|
||||
"contentMediaType": "application/json",
|
||||
"contentEncoding": "base64",
|
||||
"$id": "content_2_0"
|
||||
"contentEncoding": "base64"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -132,8 +129,8 @@
|
||||
{
|
||||
"description": "validation of binary-encoded media type documents with schema",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"content_3_0": {
|
||||
"contentMediaType": "application/json",
|
||||
"contentEncoding": "base64",
|
||||
"contentSchema": {
|
||||
@ -149,10 +146,9 @@
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"$id": "content_3_0"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
|
||||
@ -15,20 +15,19 @@
|
||||
"variations": [
|
||||
"org"
|
||||
],
|
||||
"schemas": [
|
||||
{
|
||||
"$id": "full.org",
|
||||
"schemas": {
|
||||
"full.org": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"missing_users": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "full.user"
|
||||
"type": "full.user"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "22222222-2222-2222-2222-222222222222",
|
||||
@ -42,13 +41,12 @@
|
||||
"variations": [
|
||||
"user"
|
||||
],
|
||||
"schemas": [
|
||||
{
|
||||
"$id": "full.user",
|
||||
"schemas": {
|
||||
"full.user": {
|
||||
"type": "object",
|
||||
"properties": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"relations": []
|
||||
@ -84,20 +82,19 @@
|
||||
"variations": [
|
||||
"parent"
|
||||
],
|
||||
"schemas": [
|
||||
{
|
||||
"$id": "full.parent",
|
||||
"schemas": {
|
||||
"full.parent": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"children": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "full.child"
|
||||
"type": "full.child"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "22222222-2222-2222-2222-222222222222",
|
||||
@ -111,13 +108,12 @@
|
||||
"variations": [
|
||||
"child"
|
||||
],
|
||||
"schemas": [
|
||||
{
|
||||
"$id": "full.child",
|
||||
"schemas": {
|
||||
"full.child": {
|
||||
"type": "object",
|
||||
"properties": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"relations": [
|
||||
@ -167,20 +163,19 @@
|
||||
"variations": [
|
||||
"invoice"
|
||||
],
|
||||
"schemas": [
|
||||
{
|
||||
"$id": "full.invoice",
|
||||
"schemas": {
|
||||
"full.invoice": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"activities": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "full.activity"
|
||||
"type": "full.activity"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "22222222-2222-2222-2222-222222222222",
|
||||
@ -194,13 +189,12 @@
|
||||
"variations": [
|
||||
"activity"
|
||||
],
|
||||
"schemas": [
|
||||
{
|
||||
"$id": "full.activity",
|
||||
"schemas": {
|
||||
"full.activity": {
|
||||
"type": "object",
|
||||
"properties": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"relations": [
|
||||
@ -274,20 +268,19 @@
|
||||
"variations": [
|
||||
"actor"
|
||||
],
|
||||
"schemas": [
|
||||
{
|
||||
"$id": "full.actor",
|
||||
"schemas": {
|
||||
"full.actor": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"ambiguous_edge": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "empty.junction"
|
||||
"type": "empty.junction"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "22222222-2222-2222-2222-222222222222",
|
||||
@ -301,13 +294,12 @@
|
||||
"variations": [
|
||||
"junction"
|
||||
],
|
||||
"schemas": [
|
||||
{
|
||||
"$id": "empty.junction",
|
||||
"schemas": {
|
||||
"empty.junction": {
|
||||
"type": "object",
|
||||
"properties": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"relations": [
|
||||
|
||||
@ -2,10 +2,9 @@
|
||||
{
|
||||
"description": "single dependency (required)",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"schema1": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "schema1",
|
||||
"dependencies": {
|
||||
"bar": [
|
||||
"foo"
|
||||
@ -13,7 +12,7 @@
|
||||
},
|
||||
"extensible": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -93,16 +92,15 @@
|
||||
{
|
||||
"description": "empty dependents",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"schema2": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "schema2",
|
||||
"dependencies": {
|
||||
"bar": []
|
||||
},
|
||||
"extensible": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -139,10 +137,9 @@
|
||||
{
|
||||
"description": "multiple dependents required",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"schema3": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "schema3",
|
||||
"dependencies": {
|
||||
"quux": [
|
||||
"foo",
|
||||
@ -151,7 +148,7 @@
|
||||
},
|
||||
"extensible": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -228,10 +225,9 @@
|
||||
{
|
||||
"description": "dependencies with escaped characters",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"schema4": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "schema4",
|
||||
"dependencies": {
|
||||
"foo\nbar": [
|
||||
"foo\rbar"
|
||||
@ -242,7 +238,7 @@
|
||||
},
|
||||
"extensible": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -297,10 +293,9 @@
|
||||
{
|
||||
"description": "extensible: true allows extra properties in dependentRequired",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"schema5": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "schema5",
|
||||
"dependencies": {
|
||||
"bar": [
|
||||
"foo"
|
||||
@ -308,7 +303,7 @@
|
||||
},
|
||||
"extensible": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -329,10 +324,9 @@
|
||||
{
|
||||
"description": "single dependency (schemas, STRICT)",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"schema_schema1": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "schema_schema1",
|
||||
"properties": {
|
||||
"foo": true,
|
||||
"bar": true
|
||||
@ -350,7 +344,7 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -451,10 +445,9 @@
|
||||
{
|
||||
"description": "single dependency (schemas, EXTENSIBLE)",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"schema_schema2": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "schema_schema2",
|
||||
"properties": {
|
||||
"foo": true,
|
||||
"bar": true
|
||||
@ -473,7 +466,7 @@
|
||||
},
|
||||
"extensible": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -492,10 +485,9 @@
|
||||
{
|
||||
"description": "boolean subschemas",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"schema_schema3": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "schema_schema3",
|
||||
"properties": {
|
||||
"foo": true,
|
||||
"bar": true
|
||||
@ -505,7 +497,7 @@
|
||||
"bar": false
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -556,10 +548,9 @@
|
||||
{
|
||||
"description": "dependencies with escaped characters",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"schema_schema4": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "schema_schema4",
|
||||
"properties": {
|
||||
"foo\tbar": true,
|
||||
"foo'bar": true,
|
||||
@ -579,7 +570,7 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -637,10 +628,9 @@
|
||||
{
|
||||
"description": "dependent subschema incompatible with root (STRICT)",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"schema_schema5": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "schema_schema5",
|
||||
"properties": {
|
||||
"foo": {},
|
||||
"baz": true
|
||||
@ -653,7 +643,7 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -711,10 +701,9 @@
|
||||
{
|
||||
"description": "dependent subschema incompatible with root (EXTENSIBLE)",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"schema_schema6": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "schema_schema6",
|
||||
"properties": {
|
||||
"foo": {},
|
||||
"baz": true
|
||||
@ -729,7 +718,7 @@
|
||||
},
|
||||
"extensible": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
|
||||
@ -2,8 +2,8 @@
|
||||
{
|
||||
"description": "empty string is valid for all types (except const)",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"emptyString_0_0": {
|
||||
"properties": {
|
||||
"obj": {
|
||||
"type": "object"
|
||||
@ -36,10 +36,9 @@
|
||||
"con_empty": {
|
||||
"const": ""
|
||||
}
|
||||
},
|
||||
"$id": "emptyString_0_0"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -142,7 +141,9 @@
|
||||
"errors": [
|
||||
{
|
||||
"code": "CONST_VIOLATED",
|
||||
"details": { "path": "con" }
|
||||
"details": {
|
||||
"path": "con"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@ -2,16 +2,15 @@
|
||||
{
|
||||
"description": "simple enum validation",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"enum_0_0": {
|
||||
"enum": [
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"$id": "enum_0_0"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -37,8 +36,8 @@
|
||||
{
|
||||
"description": "heterogeneous enum validation",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"enum_1_0": {
|
||||
"enum": [
|
||||
6,
|
||||
"foo",
|
||||
@ -50,10 +49,9 @@
|
||||
],
|
||||
"properties": {
|
||||
"foo": {}
|
||||
},
|
||||
"$id": "enum_1_0"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -113,15 +111,14 @@
|
||||
{
|
||||
"description": "heterogeneous enum-with-null validation",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"enum_2_0": {
|
||||
"enum": [
|
||||
6,
|
||||
null
|
||||
],
|
||||
"$id": "enum_2_0"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -156,8 +153,8 @@
|
||||
{
|
||||
"description": "enums in properties",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"enum_3_0": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"foo": {
|
||||
@ -173,10 +170,9 @@
|
||||
},
|
||||
"required": [
|
||||
"bar"
|
||||
],
|
||||
"$id": "enum_3_0"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -251,15 +247,14 @@
|
||||
{
|
||||
"description": "enum with escaped characters",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"enum_4_0": {
|
||||
"enum": [
|
||||
"foo\nbar",
|
||||
"foo\rbar"
|
||||
],
|
||||
"$id": "enum_4_0"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -294,14 +289,13 @@
|
||||
{
|
||||
"description": "enum with false does not match 0",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"enum_5_0": {
|
||||
"enum": [
|
||||
false
|
||||
],
|
||||
"$id": "enum_5_0"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -336,16 +330,15 @@
|
||||
{
|
||||
"description": "enum with [false] does not match [0]",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"enum_6_0": {
|
||||
"enum": [
|
||||
[
|
||||
false
|
||||
]
|
||||
],
|
||||
"$id": "enum_6_0"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -386,14 +379,13 @@
|
||||
{
|
||||
"description": "enum with true does not match 1",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"enum_7_0": {
|
||||
"enum": [
|
||||
true
|
||||
],
|
||||
"$id": "enum_7_0"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -428,16 +420,15 @@
|
||||
{
|
||||
"description": "enum with [true] does not match [1]",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"enum_8_0": {
|
||||
"enum": [
|
||||
[
|
||||
true
|
||||
]
|
||||
],
|
||||
"$id": "enum_8_0"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -478,14 +469,13 @@
|
||||
{
|
||||
"description": "enum with 0 does not match false",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"enum_9_0": {
|
||||
"enum": [
|
||||
0
|
||||
],
|
||||
"$id": "enum_9_0"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -520,16 +510,15 @@
|
||||
{
|
||||
"description": "enum with [0] does not match [false]",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"enum_10_0": {
|
||||
"enum": [
|
||||
[
|
||||
0
|
||||
]
|
||||
],
|
||||
"$id": "enum_10_0"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -570,14 +559,13 @@
|
||||
{
|
||||
"description": "enum with 1 does not match true",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"enum_11_0": {
|
||||
"enum": [
|
||||
1
|
||||
],
|
||||
"$id": "enum_11_0"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -612,16 +600,15 @@
|
||||
{
|
||||
"description": "enum with [1] does not match [true]",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"enum_12_0": {
|
||||
"enum": [
|
||||
[
|
||||
1
|
||||
]
|
||||
],
|
||||
"$id": "enum_12_0"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -662,14 +649,13 @@
|
||||
{
|
||||
"description": "nul characters in strings",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"enum_13_0": {
|
||||
"enum": [
|
||||
"hello\u0000there"
|
||||
],
|
||||
"$id": "enum_13_0"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -695,17 +681,16 @@
|
||||
{
|
||||
"description": "extensible: true allows extra properties in enum object match",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"enum_14_0": {
|
||||
"enum": [
|
||||
{
|
||||
"foo": 1
|
||||
}
|
||||
],
|
||||
"extensible": true,
|
||||
"$id": "enum_14_0"
|
||||
"extensible": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
|
||||
@ -2,12 +2,11 @@
|
||||
{
|
||||
"description": "exclusiveMaximum validation",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"exclusiveMaximum": 3,
|
||||
"$id": "exclusiveMaximum_0_0"
|
||||
"schemas": {
|
||||
"exclusiveMaximum_0_0": {
|
||||
"exclusiveMaximum": 3
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
|
||||
@ -2,12 +2,11 @@
|
||||
{
|
||||
"description": "exclusiveMinimum validation",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"exclusiveMinimum": 1.1,
|
||||
"$id": "exclusiveMinimum_0_0"
|
||||
"schemas": {
|
||||
"exclusiveMinimum_0_0": {
|
||||
"exclusiveMinimum": 1.1
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
|
||||
@ -1,217 +0,0 @@
|
||||
[
|
||||
{
|
||||
"description": "Entity families via pure $ref graph",
|
||||
"database": {
|
||||
"types": [
|
||||
{
|
||||
"name": "entity",
|
||||
"variations": [
|
||||
"entity",
|
||||
"organization",
|
||||
"person"
|
||||
],
|
||||
"schemas": [
|
||||
{
|
||||
"$id": "entity",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"$id": "light.entity",
|
||||
"$ref": "entity"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "organization",
|
||||
"variations": [
|
||||
"organization",
|
||||
"person"
|
||||
],
|
||||
"schemas": [
|
||||
{
|
||||
"$id": "organization",
|
||||
"$ref": "entity",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "person",
|
||||
"variations": [
|
||||
"person"
|
||||
],
|
||||
"schemas": [
|
||||
{
|
||||
"$id": "person",
|
||||
"$ref": "organization",
|
||||
"properties": {
|
||||
"first_name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"$id": "light.person",
|
||||
"$ref": "light.entity"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"puncs": [
|
||||
{
|
||||
"name": "get_entities",
|
||||
"schemas": [
|
||||
{
|
||||
"$id": "get_entities.response",
|
||||
"$family": "entity"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "get_light_entities",
|
||||
"schemas": [
|
||||
{
|
||||
"$id": "get_light_entities.response",
|
||||
"$family": "light.entity"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "Family matches base entity",
|
||||
"schema_id": "get_entities.response",
|
||||
"data": {
|
||||
"id": "1",
|
||||
"type": "entity"
|
||||
},
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Family matches descendant person",
|
||||
"schema_id": "get_entities.response",
|
||||
"data": {
|
||||
"id": "2",
|
||||
"type": "person",
|
||||
"name": "ACME",
|
||||
"first_name": "John"
|
||||
},
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Graph family matches light.entity",
|
||||
"schema_id": "get_light_entities.response",
|
||||
"data": {
|
||||
"id": "3",
|
||||
"type": "entity"
|
||||
},
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Graph family matches light.person (because it $refs light.entity)",
|
||||
"schema_id": "get_light_entities.response",
|
||||
"data": {
|
||||
"id": "4",
|
||||
"type": "person"
|
||||
},
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Graph family excludes organization (missing light. schema that $refs light.entity)",
|
||||
"schema_id": "get_light_entities.response",
|
||||
"data": {
|
||||
"id": "5",
|
||||
"type": "organization",
|
||||
"name": "ACME"
|
||||
},
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false,
|
||||
"errors": [
|
||||
{
|
||||
"code": "NO_FAMILY_MATCH",
|
||||
"details": { "path": "" }
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Ad-hoc non-entity families (using normal json-schema object structures)",
|
||||
"database": {
|
||||
"puncs": [
|
||||
{
|
||||
"name": "get_widgets",
|
||||
"schemas": [
|
||||
{
|
||||
"$id": "widget",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"widget_type": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"$id": "special_widget",
|
||||
"$ref": "widget",
|
||||
"properties": {
|
||||
"special_feature": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"$id": "get_widgets.response",
|
||||
"$family": "widget"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "Ad-hoc family matches strictly by shape (no magic variations for base schemas)",
|
||||
"schema_id": "get_widgets.response",
|
||||
"data": {
|
||||
"id": "1",
|
||||
"widget_type": "special",
|
||||
"special_feature": "yes"
|
||||
},
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@ -2,12 +2,11 @@
|
||||
{
|
||||
"description": "validation of date-time strings",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"format": "date-time",
|
||||
"$id": "format_0_0"
|
||||
"schemas": {
|
||||
"format_0_0": {
|
||||
"format": "date-time"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -249,12 +248,11 @@
|
||||
{
|
||||
"description": "validation of date strings",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"format": "date",
|
||||
"$id": "format_1_0"
|
||||
"schemas": {
|
||||
"format_1_0": {
|
||||
"format": "date"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -694,12 +692,11 @@
|
||||
{
|
||||
"description": "validation of duration strings",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"format": "duration",
|
||||
"$id": "format_2_0"
|
||||
"schemas": {
|
||||
"format_2_0": {
|
||||
"format": "duration"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -941,12 +938,11 @@
|
||||
{
|
||||
"description": "\\a is not an ECMA 262 control escape",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"format": "regex",
|
||||
"$id": "format_3_0"
|
||||
"schemas": {
|
||||
"format_3_0": {
|
||||
"format": "regex"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -963,12 +959,11 @@
|
||||
{
|
||||
"description": "validation of e-mail addresses",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"format": "email",
|
||||
"$id": "format_4_0"
|
||||
"schemas": {
|
||||
"format_4_0": {
|
||||
"format": "email"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -1192,12 +1187,11 @@
|
||||
{
|
||||
"description": "validation of host names",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"format": "hostname",
|
||||
"$id": "format_5_0"
|
||||
"schemas": {
|
||||
"format_5_0": {
|
||||
"format": "hostname"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -1422,12 +1416,11 @@
|
||||
{
|
||||
"description": "validation of A-label (punycode) host names",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"format": "hostname",
|
||||
"$id": "format_6_0"
|
||||
"schemas": {
|
||||
"format_6_0": {
|
||||
"format": "hostname"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -1803,12 +1796,11 @@
|
||||
{
|
||||
"description": "validation of an internationalized e-mail addresses",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"format": "idn-email",
|
||||
"$id": "format_7_0"
|
||||
"schemas": {
|
||||
"format_7_0": {
|
||||
"format": "idn-email"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -1906,12 +1898,11 @@
|
||||
{
|
||||
"description": "validation of internationalized host names",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"format": "idn-hostname",
|
||||
"$id": "format_8_0"
|
||||
"schemas": {
|
||||
"format_8_0": {
|
||||
"format": "idn-hostname"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -2479,12 +2470,11 @@
|
||||
}
|
||||
],
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"format": "idn-hostname",
|
||||
"$id": "format_9_0"
|
||||
"schemas": {
|
||||
"format_9_0": {
|
||||
"format": "idn-hostname"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -2672,12 +2662,11 @@
|
||||
{
|
||||
"description": "validation of IP addresses",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"format": "ipv4",
|
||||
"$id": "format_10_0"
|
||||
"schemas": {
|
||||
"format_10_0": {
|
||||
"format": "ipv4"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -2830,12 +2819,11 @@
|
||||
{
|
||||
"description": "validation of IPv6 addresses",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"format": "ipv6",
|
||||
"$id": "format_11_0"
|
||||
"schemas": {
|
||||
"format_11_0": {
|
||||
"format": "ipv6"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -3203,12 +3191,11 @@
|
||||
{
|
||||
"description": "validation of IRI References",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"format": "iri-reference",
|
||||
"$id": "format_12_0"
|
||||
"schemas": {
|
||||
"format_12_0": {
|
||||
"format": "iri-reference"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -3333,12 +3320,11 @@
|
||||
{
|
||||
"description": "validation of IRIs",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"format": "iri",
|
||||
"$id": "format_13_0"
|
||||
"schemas": {
|
||||
"format_13_0": {
|
||||
"format": "iri"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -3481,12 +3467,11 @@
|
||||
{
|
||||
"description": "validation of JSON-pointers (JSON String Representation)",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"format": "json-pointer",
|
||||
"$id": "format_14_0"
|
||||
"schemas": {
|
||||
"format_14_0": {
|
||||
"format": "json-pointer"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -3836,12 +3821,11 @@
|
||||
{
|
||||
"description": "validation of regular expressions",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"format": "regex",
|
||||
"$id": "format_15_0"
|
||||
"schemas": {
|
||||
"format_15_0": {
|
||||
"format": "regex"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -3921,12 +3905,11 @@
|
||||
{
|
||||
"description": "validation of Relative JSON Pointers (RJP)",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"format": "relative-json-pointer",
|
||||
"$id": "format_16_0"
|
||||
"schemas": {
|
||||
"format_16_0": {
|
||||
"format": "relative-json-pointer"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -4096,12 +4079,11 @@
|
||||
{
|
||||
"description": "validation of time strings",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"format": "time",
|
||||
"$id": "format_17_0"
|
||||
"schemas": {
|
||||
"format_17_0": {
|
||||
"format": "time"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -4523,12 +4505,11 @@
|
||||
{
|
||||
"description": "unknown format",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"format": "unknown",
|
||||
"$id": "format_18_0"
|
||||
"schemas": {
|
||||
"format_18_0": {
|
||||
"format": "unknown"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -4599,12 +4580,11 @@
|
||||
{
|
||||
"description": "validation of URI References",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"format": "uri-reference",
|
||||
"$id": "format_19_0"
|
||||
"schemas": {
|
||||
"format_19_0": {
|
||||
"format": "uri-reference"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -4747,12 +4727,11 @@
|
||||
{
|
||||
"description": "format: uri-template",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"format": "uri-template",
|
||||
"$id": "format_20_0"
|
||||
"schemas": {
|
||||
"format_20_0": {
|
||||
"format": "uri-template"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -4850,12 +4829,11 @@
|
||||
{
|
||||
"description": "validation of URIs",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"format": "uri",
|
||||
"$id": "format_21_0"
|
||||
"schemas": {
|
||||
"format_21_0": {
|
||||
"format": "uri"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -5187,12 +5165,11 @@
|
||||
{
|
||||
"description": "uuid format",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"format": "uuid",
|
||||
"$id": "format_22_0"
|
||||
"schemas": {
|
||||
"format_22_0": {
|
||||
"format": "uuid"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -5398,12 +5375,11 @@
|
||||
{
|
||||
"description": "period format",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"format": "period",
|
||||
"$id": "format_23_0"
|
||||
"schemas": {
|
||||
"format_23_0": {
|
||||
"format": "period"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
|
||||
@ -1,594 +0,0 @@
|
||||
[
|
||||
{
|
||||
"description": "ignore if without then or else",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"if": {
|
||||
"const": 0
|
||||
},
|
||||
"$id": "if-then-else_0_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "valid when valid against lone if",
|
||||
"data": 0,
|
||||
"schema_id": "if-then-else_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "valid when invalid against lone if",
|
||||
"data": "hello",
|
||||
"schema_id": "if-then-else_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "ignore then without if",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"then": {
|
||||
"const": 0
|
||||
},
|
||||
"$id": "if-then-else_1_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "valid when valid against lone then",
|
||||
"data": 0,
|
||||
"schema_id": "if-then-else_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "valid when invalid against lone then",
|
||||
"data": "hello",
|
||||
"schema_id": "if-then-else_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "ignore else without if",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"else": {
|
||||
"const": 0
|
||||
},
|
||||
"$id": "if-then-else_2_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "valid when valid against lone else",
|
||||
"data": 0,
|
||||
"schema_id": "if-then-else_2_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "valid when invalid against lone else",
|
||||
"data": "hello",
|
||||
"schema_id": "if-then-else_2_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "if and then without else",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"if": {
|
||||
"exclusiveMaximum": 0
|
||||
},
|
||||
"then": {
|
||||
"minimum": -10
|
||||
},
|
||||
"$id": "if-then-else_3_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "valid through then",
|
||||
"data": -1,
|
||||
"schema_id": "if-then-else_3_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "invalid through then",
|
||||
"data": -100,
|
||||
"schema_id": "if-then-else_3_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "valid when if test fails",
|
||||
"data": 3,
|
||||
"schema_id": "if-then-else_3_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "if and else without then",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"if": {
|
||||
"exclusiveMaximum": 0
|
||||
},
|
||||
"else": {
|
||||
"multipleOf": 2
|
||||
},
|
||||
"$id": "if-then-else_4_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "valid when if test passes",
|
||||
"data": -1,
|
||||
"schema_id": "if-then-else_4_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "valid through else",
|
||||
"data": 4,
|
||||
"schema_id": "if-then-else_4_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "invalid through else",
|
||||
"data": 3,
|
||||
"schema_id": "if-then-else_4_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "validate against correct branch, then vs else",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"if": {
|
||||
"exclusiveMaximum": 0
|
||||
},
|
||||
"then": {
|
||||
"minimum": -10
|
||||
},
|
||||
"else": {
|
||||
"multipleOf": 2
|
||||
},
|
||||
"$id": "if-then-else_5_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "valid through then",
|
||||
"data": -1,
|
||||
"schema_id": "if-then-else_5_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "invalid through then",
|
||||
"data": -100,
|
||||
"schema_id": "if-then-else_5_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "valid through else",
|
||||
"data": 4,
|
||||
"schema_id": "if-then-else_5_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "invalid through else",
|
||||
"data": 3,
|
||||
"schema_id": "if-then-else_5_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "non-interference across combined schemas",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
"if": {
|
||||
"exclusiveMaximum": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"then": {
|
||||
"minimum": -10
|
||||
}
|
||||
},
|
||||
{
|
||||
"else": {
|
||||
"multipleOf": 2
|
||||
}
|
||||
}
|
||||
],
|
||||
"$id": "if-then-else_6_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "valid, but would have been invalid through then",
|
||||
"data": -100,
|
||||
"schema_id": "if-then-else_6_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "valid, but would have been invalid through else",
|
||||
"data": 3,
|
||||
"schema_id": "if-then-else_6_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "if with boolean schema true",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"if": true,
|
||||
"then": {
|
||||
"const": "then"
|
||||
},
|
||||
"else": {
|
||||
"const": "else"
|
||||
},
|
||||
"$id": "if-then-else_7_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "boolean schema true in if always chooses the then path (valid)",
|
||||
"data": "then",
|
||||
"schema_id": "if-then-else_7_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "boolean schema true in if always chooses the then path (invalid)",
|
||||
"data": "else",
|
||||
"schema_id": "if-then-else_7_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "if with boolean schema false",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"if": false,
|
||||
"then": {
|
||||
"const": "then"
|
||||
},
|
||||
"else": {
|
||||
"const": "else"
|
||||
},
|
||||
"$id": "if-then-else_8_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "boolean schema false in if always chooses the else path (invalid)",
|
||||
"data": "then",
|
||||
"schema_id": "if-then-else_8_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "boolean schema false in if always chooses the else path (valid)",
|
||||
"data": "else",
|
||||
"schema_id": "if-then-else_8_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "if appears at the end when serialized (keyword processing sequence)",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"then": {
|
||||
"const": "yes"
|
||||
},
|
||||
"else": {
|
||||
"const": "other"
|
||||
},
|
||||
"if": {
|
||||
"maxLength": 4
|
||||
},
|
||||
"$id": "if-then-else_9_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "yes redirects to then and passes",
|
||||
"data": "yes",
|
||||
"schema_id": "if-then-else_9_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "other redirects to else and passes",
|
||||
"data": "other",
|
||||
"schema_id": "if-then-else_9_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "no redirects to then and fails",
|
||||
"data": "no",
|
||||
"schema_id": "if-then-else_9_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "invalid redirects to else and fails",
|
||||
"data": "invalid",
|
||||
"schema_id": "if-then-else_9_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "then: false fails when condition matches",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"if": {
|
||||
"const": 1
|
||||
},
|
||||
"then": false,
|
||||
"$id": "if-then-else_10_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "matches if → then=false → invalid",
|
||||
"data": 1,
|
||||
"schema_id": "if-then-else_10_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "does not match if → then ignored → valid",
|
||||
"data": 2,
|
||||
"schema_id": "if-then-else_10_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "else: false fails when condition does not match",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"if": {
|
||||
"const": 1
|
||||
},
|
||||
"else": false,
|
||||
"$id": "if-then-else_11_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "matches if → else ignored → valid",
|
||||
"data": 1,
|
||||
"schema_id": "if-then-else_11_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "does not match if → else executes → invalid",
|
||||
"data": 2,
|
||||
"schema_id": "if-then-else_11_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "extensible: true allows extra properties in if-then-else",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"if": {
|
||||
"properties": {
|
||||
"foo": {
|
||||
"const": 1
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"foo"
|
||||
]
|
||||
},
|
||||
"then": {
|
||||
"properties": {
|
||||
"bar": {
|
||||
"const": 2
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"bar"
|
||||
]
|
||||
},
|
||||
"extensible": true,
|
||||
"$id": "if-then-else_12_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "extra property is valid (matches if and then)",
|
||||
"data": {
|
||||
"foo": 1,
|
||||
"bar": 2,
|
||||
"extra": "prop"
|
||||
},
|
||||
"schema_id": "if-then-else_12_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "strict by default with if-then properties",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"if": {
|
||||
"properties": {
|
||||
"foo": {
|
||||
"const": 1
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"foo"
|
||||
]
|
||||
},
|
||||
"then": {
|
||||
"properties": {
|
||||
"bar": {
|
||||
"const": 2
|
||||
}
|
||||
}
|
||||
},
|
||||
"$id": "if-then-else_13_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "valid match (foo + bar)",
|
||||
"data": {
|
||||
"foo": 1,
|
||||
"bar": 2
|
||||
},
|
||||
"schema_id": "if-then-else_13_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "fails on extra property z explicitly",
|
||||
"data": {
|
||||
"foo": 1,
|
||||
"bar": 2,
|
||||
"z": 3
|
||||
},
|
||||
"schema_id": "if-then-else_13_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
340
fixtures/invoice.json
Normal file
340
fixtures/invoice.json
Normal file
@ -0,0 +1,340 @@
|
||||
[
|
||||
{
|
||||
"description": "Invoice Attachment Reproducer",
|
||||
"database": {
|
||||
"puncs": [
|
||||
{
|
||||
"name": "get_invoice",
|
||||
"schemas": {
|
||||
"get_invoice.response": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "invoice"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"enums": [],
|
||||
"relations": [
|
||||
{
|
||||
"id": "10000000-0000-0000-0000-000000000001",
|
||||
"type": "relation",
|
||||
"constraint": "fk_attachment_attachable_entity",
|
||||
"source_type": "attachment",
|
||||
"source_columns": [
|
||||
"attachable_id",
|
||||
"attachable_type"
|
||||
],
|
||||
"destination_type": "entity",
|
||||
"destination_columns": [
|
||||
"id",
|
||||
"type"
|
||||
],
|
||||
"prefix": null
|
||||
}
|
||||
],
|
||||
"types": [
|
||||
{
|
||||
"name": "entity",
|
||||
"schemas": {
|
||||
"entity": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
"type": {
|
||||
"type": "string"
|
||||
},
|
||||
"archived": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"created_at": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"hierarchy": [
|
||||
"entity"
|
||||
],
|
||||
"variations": [
|
||||
"entity",
|
||||
"activity",
|
||||
"invoice",
|
||||
"attachment"
|
||||
],
|
||||
"fields": [
|
||||
"id",
|
||||
"type",
|
||||
"archived",
|
||||
"created_at"
|
||||
],
|
||||
"grouped_fields": {
|
||||
"entity": [
|
||||
"id",
|
||||
"type",
|
||||
"archived",
|
||||
"created_at"
|
||||
]
|
||||
},
|
||||
"field_types": {
|
||||
"id": "uuid",
|
||||
"type": "text",
|
||||
"archived": "boolean",
|
||||
"created_at": "timestamptz"
|
||||
},
|
||||
"lookup_fields": [],
|
||||
"historical": false,
|
||||
"notify": false,
|
||||
"relationship": false
|
||||
},
|
||||
{
|
||||
"name": "activity",
|
||||
"schemas": {
|
||||
"activity": {
|
||||
"type": "entity",
|
||||
"properties": {
|
||||
"start_date": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"hierarchy": [
|
||||
"activity",
|
||||
"entity"
|
||||
],
|
||||
"variations": [
|
||||
"activity",
|
||||
"invoice"
|
||||
],
|
||||
"fields": [
|
||||
"id",
|
||||
"type",
|
||||
"archived",
|
||||
"created_at",
|
||||
"start_date"
|
||||
],
|
||||
"grouped_fields": {
|
||||
"entity": [
|
||||
"id",
|
||||
"type",
|
||||
"archived",
|
||||
"created_at"
|
||||
],
|
||||
"activity": [
|
||||
"start_date"
|
||||
]
|
||||
},
|
||||
"field_types": {
|
||||
"id": "uuid",
|
||||
"type": "text",
|
||||
"archived": "boolean",
|
||||
"created_at": "timestamptz",
|
||||
"start_date": "timestamptz"
|
||||
},
|
||||
"lookup_fields": [],
|
||||
"historical": false,
|
||||
"notify": false,
|
||||
"relationship": false
|
||||
},
|
||||
{
|
||||
"name": "invoice",
|
||||
"schemas": {
|
||||
"invoice": {
|
||||
"type": "activity",
|
||||
"properties": {
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
"attachments": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "attachment"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"hierarchy": [
|
||||
"invoice",
|
||||
"activity",
|
||||
"entity"
|
||||
],
|
||||
"variations": [
|
||||
"invoice"
|
||||
],
|
||||
"fields": [
|
||||
"id",
|
||||
"type",
|
||||
"archived",
|
||||
"created_at",
|
||||
"start_date",
|
||||
"status"
|
||||
],
|
||||
"grouped_fields": {
|
||||
"entity": [
|
||||
"id",
|
||||
"type",
|
||||
"archived",
|
||||
"created_at"
|
||||
],
|
||||
"activity": [
|
||||
"start_date"
|
||||
],
|
||||
"invoice": [
|
||||
"status"
|
||||
]
|
||||
},
|
||||
"field_types": {
|
||||
"id": "uuid",
|
||||
"type": "text",
|
||||
"archived": "boolean",
|
||||
"created_at": "timestamptz",
|
||||
"start_date": "timestamptz",
|
||||
"status": "text"
|
||||
},
|
||||
"lookup_fields": [],
|
||||
"historical": false,
|
||||
"notify": false,
|
||||
"relationship": false
|
||||
},
|
||||
{
|
||||
"name": "attachment",
|
||||
"schemas": {
|
||||
"attachment": {
|
||||
"type": "entity",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"attachable_id": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
"attachable_type": {
|
||||
"type": "string"
|
||||
},
|
||||
"kind": {
|
||||
"type": "string"
|
||||
},
|
||||
"file": {
|
||||
"type": "string"
|
||||
},
|
||||
"data": {
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"hierarchy": [
|
||||
"attachment",
|
||||
"entity"
|
||||
],
|
||||
"variations": [
|
||||
"attachment"
|
||||
],
|
||||
"fields": [
|
||||
"id",
|
||||
"type",
|
||||
"archived",
|
||||
"created_at",
|
||||
"attachable_id",
|
||||
"attachable_type",
|
||||
"kind",
|
||||
"file",
|
||||
"data",
|
||||
"name"
|
||||
],
|
||||
"grouped_fields": {
|
||||
"entity": [
|
||||
"id",
|
||||
"type",
|
||||
"archived",
|
||||
"created_at"
|
||||
],
|
||||
"attachment": [
|
||||
"attachable_id",
|
||||
"attachable_type",
|
||||
"kind",
|
||||
"file",
|
||||
"data",
|
||||
"name"
|
||||
]
|
||||
},
|
||||
"field_types": {
|
||||
"id": "uuid",
|
||||
"type": "text",
|
||||
"archived": "boolean",
|
||||
"created_at": "timestamptz",
|
||||
"attachable_id": "uuid",
|
||||
"attachable_type": "text",
|
||||
"kind": "text",
|
||||
"file": "text",
|
||||
"data": "jsonb",
|
||||
"name": "text"
|
||||
},
|
||||
"lookup_fields": [],
|
||||
"historical": false,
|
||||
"notify": false,
|
||||
"relationship": false
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "Invoice with an empty array of attachments",
|
||||
"schema_id": "get_invoice.response",
|
||||
"data": {
|
||||
"id": "11111111-1111-1111-1111-111111111111",
|
||||
"type": "invoice",
|
||||
"archived": false,
|
||||
"created_at": "2023-01-01T00:00:00Z",
|
||||
"status": "draft",
|
||||
"attachments": []
|
||||
},
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Invoice with a valid attachment with null data",
|
||||
"schema_id": "get_invoice.response",
|
||||
"data": {
|
||||
"id": "11111111-1111-1111-1111-111111111111",
|
||||
"type": "invoice",
|
||||
"archived": false,
|
||||
"created_at": "2023-01-01T00:00:00Z",
|
||||
"status": "draft",
|
||||
"attachments": [
|
||||
{
|
||||
"id": "22222222-2222-2222-2222-222222222222",
|
||||
"type": "attachment",
|
||||
"archived": false,
|
||||
"created_at": "2023-01-01T00:00:00Z",
|
||||
"name": "receipt",
|
||||
"attachable_id": "11111111-1111-1111-1111-111111111111",
|
||||
"attachable_type": "invoice",
|
||||
"kind": "document",
|
||||
"file": "path/to/doc.pdf"
|
||||
}
|
||||
]
|
||||
},
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@ -2,14 +2,13 @@
|
||||
{
|
||||
"description": "a schema given for items",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"items_0_0": {
|
||||
"items": {
|
||||
"type": "integer"
|
||||
},
|
||||
"$id": "items_0_0"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -65,12 +64,11 @@
|
||||
{
|
||||
"description": "items with boolean schema (true)",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"items": true,
|
||||
"$id": "items_1_0"
|
||||
"schemas": {
|
||||
"items_1_0": {
|
||||
"items": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -100,12 +98,11 @@
|
||||
{
|
||||
"description": "items with boolean schema (false)",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"items": false,
|
||||
"$id": "items_2_0"
|
||||
"schemas": {
|
||||
"items_2_0": {
|
||||
"items": false
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -135,44 +132,41 @@
|
||||
{
|
||||
"description": "items and subitems",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"items_3_0": {
|
||||
"type": "array",
|
||||
"items": false,
|
||||
"prefixItems": [
|
||||
{
|
||||
"$ref": "item"
|
||||
"type": "item"
|
||||
},
|
||||
{
|
||||
"$ref": "item"
|
||||
"type": "item"
|
||||
},
|
||||
{
|
||||
"$ref": "item"
|
||||
}
|
||||
],
|
||||
"$id": "items_3_0"
|
||||
},
|
||||
{
|
||||
"$id": "item",
|
||||
"type": "array",
|
||||
"items": false,
|
||||
"prefixItems": [
|
||||
{
|
||||
"$ref": "sub-item"
|
||||
},
|
||||
{
|
||||
"$ref": "sub-item"
|
||||
"type": "item"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"$id": "sub-item",
|
||||
"item": {
|
||||
"type": "array",
|
||||
"items": false,
|
||||
"prefixItems": [
|
||||
{
|
||||
"type": "sub-item"
|
||||
},
|
||||
{
|
||||
"type": "sub-item"
|
||||
}
|
||||
]
|
||||
},
|
||||
"sub-item": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"foo"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -374,8 +368,8 @@
|
||||
{
|
||||
"description": "nested items",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"items_4_0": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "array",
|
||||
@ -388,10 +382,9 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"$id": "items_4_0"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -507,17 +500,16 @@
|
||||
{
|
||||
"description": "prefixItems with no additional items allowed",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"items_5_0": {
|
||||
"prefixItems": [
|
||||
{},
|
||||
{},
|
||||
{}
|
||||
],
|
||||
"items": false,
|
||||
"$id": "items_5_0"
|
||||
"items": false
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -584,8 +576,8 @@
|
||||
{
|
||||
"description": "items does not look in applicators, valid case",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"items_6_0": {
|
||||
"allOf": [
|
||||
{
|
||||
"prefixItems": [
|
||||
@ -597,10 +589,9 @@
|
||||
],
|
||||
"items": {
|
||||
"minimum": 5
|
||||
},
|
||||
"$id": "items_6_0"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -632,8 +623,8 @@
|
||||
{
|
||||
"description": "prefixItems validation adjusts the starting index for items",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"items_7_0": {
|
||||
"prefixItems": [
|
||||
{
|
||||
"type": "string"
|
||||
@ -641,10 +632,9 @@
|
||||
],
|
||||
"items": {
|
||||
"type": "integer"
|
||||
},
|
||||
"$id": "items_7_0"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -677,15 +667,14 @@
|
||||
{
|
||||
"description": "items with heterogeneous array",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"items_8_0": {
|
||||
"prefixItems": [
|
||||
{}
|
||||
],
|
||||
"items": false,
|
||||
"$id": "items_8_0"
|
||||
"items": false
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -717,14 +706,13 @@
|
||||
{
|
||||
"description": "items with null instance elements",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"items_9_0": {
|
||||
"items": {
|
||||
"type": "null"
|
||||
},
|
||||
"$id": "items_9_0"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -743,13 +731,12 @@
|
||||
{
|
||||
"description": "extensible: true allows extra items (when items is false)",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"items_10_0": {
|
||||
"items": false,
|
||||
"extensible": true,
|
||||
"$id": "items_10_0"
|
||||
"extensible": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -768,15 +755,14 @@
|
||||
{
|
||||
"description": "extensible: true allows extra properties for items",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"items_11_0": {
|
||||
"items": {
|
||||
"minimum": 5
|
||||
},
|
||||
"extensible": true,
|
||||
"$id": "items_11_0"
|
||||
"extensible": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -807,13 +793,12 @@
|
||||
{
|
||||
"description": "array: simple extensible array",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"items_12_0": {
|
||||
"type": "array",
|
||||
"extensible": true,
|
||||
"$id": "items_12_0"
|
||||
"extensible": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -842,13 +827,12 @@
|
||||
{
|
||||
"description": "array: strict array",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"items_13_0": {
|
||||
"type": "array",
|
||||
"extensible": false,
|
||||
"$id": "items_13_0"
|
||||
"extensible": false
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -876,15 +860,14 @@
|
||||
{
|
||||
"description": "array: items extensible",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"items_14_0": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"extensible": true
|
||||
},
|
||||
"$id": "items_14_0"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -914,16 +897,15 @@
|
||||
{
|
||||
"description": "array: items strict",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"items_15_0": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"extensible": false
|
||||
},
|
||||
"$id": "items_15_0"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
|
||||
@ -2,13 +2,12 @@
|
||||
{
|
||||
"description": "maxContains without contains is ignored",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"maxContains_0_0": {
|
||||
"maxContains": 1,
|
||||
"extensible": true,
|
||||
"$id": "maxContains_0_0"
|
||||
"extensible": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -39,16 +38,15 @@
|
||||
{
|
||||
"description": "maxContains with contains",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"maxContains_1_0": {
|
||||
"contains": {
|
||||
"const": 1
|
||||
},
|
||||
"maxContains": 1,
|
||||
"extensible": true,
|
||||
"$id": "maxContains_1_0"
|
||||
"extensible": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -113,16 +111,15 @@
|
||||
{
|
||||
"description": "maxContains with contains, value with a decimal",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"maxContains_2_0": {
|
||||
"contains": {
|
||||
"const": 1
|
||||
},
|
||||
"maxContains": 1,
|
||||
"extensible": true,
|
||||
"$id": "maxContains_2_0"
|
||||
"extensible": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -153,17 +150,16 @@
|
||||
{
|
||||
"description": "minContains < maxContains",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"maxContains_3_0": {
|
||||
"contains": {
|
||||
"const": 1
|
||||
},
|
||||
"minContains": 1,
|
||||
"maxContains": 3,
|
||||
"extensible": true,
|
||||
"$id": "maxContains_3_0"
|
||||
"extensible": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -206,16 +202,15 @@
|
||||
{
|
||||
"description": "extensible: true allows non-matching items in maxContains",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"maxContains_4_0": {
|
||||
"contains": {
|
||||
"const": 1
|
||||
},
|
||||
"maxContains": 1,
|
||||
"extensible": true,
|
||||
"$id": "maxContains_4_0"
|
||||
"extensible": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
|
||||
@ -2,13 +2,12 @@
|
||||
{
|
||||
"description": "maxItems validation",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"maxItems_0_0": {
|
||||
"maxItems": 2,
|
||||
"extensible": true,
|
||||
"$id": "maxItems_0_0"
|
||||
"extensible": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -61,13 +60,12 @@
|
||||
{
|
||||
"description": "maxItems validation with a decimal",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"maxItems_1_0": {
|
||||
"maxItems": 2,
|
||||
"extensible": true,
|
||||
"$id": "maxItems_1_0"
|
||||
"extensible": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -99,13 +97,12 @@
|
||||
{
|
||||
"description": "extensible: true allows extra items in maxItems (but counted)",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"maxItems_2_0": {
|
||||
"maxItems": 2,
|
||||
"extensible": true,
|
||||
"$id": "maxItems_2_0"
|
||||
"extensible": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
|
||||
@ -2,12 +2,11 @@
|
||||
{
|
||||
"description": "maxLength validation",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"maxLength": 2,
|
||||
"$id": "maxLength_0_0"
|
||||
"schemas": {
|
||||
"maxLength_0_0": {
|
||||
"maxLength": 2
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -60,12 +59,11 @@
|
||||
{
|
||||
"description": "maxLength validation with a decimal",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"maxLength": 2,
|
||||
"$id": "maxLength_1_0"
|
||||
"schemas": {
|
||||
"maxLength_1_0": {
|
||||
"maxLength": 2
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
|
||||
@ -2,13 +2,12 @@
|
||||
{
|
||||
"description": "maxProperties validation",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"maxProperties_0_0": {
|
||||
"maxProperties": 2,
|
||||
"extensible": true,
|
||||
"$id": "maxProperties_0_0"
|
||||
"extensible": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -83,13 +82,12 @@
|
||||
{
|
||||
"description": "maxProperties validation with a decimal",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"maxProperties_1_0": {
|
||||
"maxProperties": 2,
|
||||
"extensible": true,
|
||||
"$id": "maxProperties_1_0"
|
||||
"extensible": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -121,13 +119,12 @@
|
||||
{
|
||||
"description": "maxProperties = 0 means the object is empty",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"maxProperties_2_0": {
|
||||
"maxProperties": 0,
|
||||
"extensible": true,
|
||||
"$id": "maxProperties_2_0"
|
||||
"extensible": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -155,13 +152,12 @@
|
||||
{
|
||||
"description": "extensible: true allows extra properties in maxProperties (though maxProperties still counts them!)",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"maxProperties_3_0": {
|
||||
"maxProperties": 2,
|
||||
"extensible": true,
|
||||
"$id": "maxProperties_3_0"
|
||||
"extensible": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
|
||||
@ -2,12 +2,11 @@
|
||||
{
|
||||
"description": "maximum validation",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"maximum": 3,
|
||||
"$id": "maximum_0_0"
|
||||
"schemas": {
|
||||
"maximum_0_0": {
|
||||
"maximum": 3
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -51,12 +50,11 @@
|
||||
{
|
||||
"description": "maximum validation with unsigned integer",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"maximum": 300,
|
||||
"$id": "maximum_1_0"
|
||||
"schemas": {
|
||||
"maximum_1_0": {
|
||||
"maximum": 300
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
|
||||
@ -2,25 +2,23 @@
|
||||
{
|
||||
"description": "merging: properties accumulate",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"$id": "base_0",
|
||||
"schemas": {
|
||||
"base_0": {
|
||||
"properties": {
|
||||
"base_prop": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"$ref": "base_0",
|
||||
"merge_0_0": {
|
||||
"type": "base_0",
|
||||
"properties": {
|
||||
"child_prop": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"$id": "merge_0_0"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -48,7 +46,9 @@
|
||||
"errors": [
|
||||
{
|
||||
"code": "INVALID_TYPE",
|
||||
"details": { "path": "base_prop" }
|
||||
"details": {
|
||||
"path": "base_prop"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -58,9 +58,8 @@
|
||||
{
|
||||
"description": "merging: required fields accumulate",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"$id": "base_1",
|
||||
"schemas": {
|
||||
"base_1": {
|
||||
"properties": {
|
||||
"a": {
|
||||
"type": "string"
|
||||
@ -70,8 +69,8 @@
|
||||
"a"
|
||||
]
|
||||
},
|
||||
{
|
||||
"$ref": "base_1",
|
||||
"merge_1_0": {
|
||||
"type": "base_1",
|
||||
"properties": {
|
||||
"b": {
|
||||
"type": "string"
|
||||
@ -79,10 +78,9 @@
|
||||
},
|
||||
"required": [
|
||||
"b"
|
||||
],
|
||||
"$id": "merge_1_0"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -109,7 +107,9 @@
|
||||
"errors": [
|
||||
{
|
||||
"code": "REQUIRED_FIELD_MISSING",
|
||||
"details": { "path": "a" }
|
||||
"details": {
|
||||
"path": "a"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -126,7 +126,9 @@
|
||||
"errors": [
|
||||
{
|
||||
"code": "REQUIRED_FIELD_MISSING",
|
||||
"details": { "path": "b" }
|
||||
"details": {
|
||||
"path": "b"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -136,9 +138,8 @@
|
||||
{
|
||||
"description": "merging: dependencies accumulate",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"$id": "base_2",
|
||||
"schemas": {
|
||||
"base_2": {
|
||||
"properties": {
|
||||
"trigger": {
|
||||
"type": "string"
|
||||
@ -153,8 +154,8 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"$ref": "base_2",
|
||||
"merge_2_0": {
|
||||
"type": "base_2",
|
||||
"properties": {
|
||||
"child_dep": {
|
||||
"type": "string"
|
||||
@ -164,10 +165,9 @@
|
||||
"trigger": [
|
||||
"child_dep"
|
||||
]
|
||||
},
|
||||
"$id": "merge_2_0"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -196,7 +196,9 @@
|
||||
"errors": [
|
||||
{
|
||||
"code": "DEPENDENCY_MISSING",
|
||||
"details": { "path": "" }
|
||||
"details": {
|
||||
"path": ""
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -214,7 +216,9 @@
|
||||
"errors": [
|
||||
{
|
||||
"code": "DEPENDENCY_MISSING",
|
||||
"details": { "path": "" }
|
||||
"details": {
|
||||
"path": ""
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -224,9 +228,8 @@
|
||||
{
|
||||
"description": "merging: form and display do NOT merge",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"$id": "base_3",
|
||||
"schemas": {
|
||||
"base_3": {
|
||||
"properties": {
|
||||
"a": {
|
||||
"type": "string"
|
||||
@ -240,8 +243,8 @@
|
||||
"b"
|
||||
]
|
||||
},
|
||||
{
|
||||
"$ref": "base_3",
|
||||
"merge_3_0": {
|
||||
"type": "base_3",
|
||||
"properties": {
|
||||
"c": {
|
||||
"type": "string"
|
||||
@ -249,10 +252,9 @@
|
||||
},
|
||||
"form": [
|
||||
"c"
|
||||
],
|
||||
"$id": "merge_3_0"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
|
||||
5607
fixtures/merger.json
5607
fixtures/merger.json
File diff suppressed because it is too large
Load Diff
@ -2,13 +2,12 @@
|
||||
{
|
||||
"description": "minContains without contains is ignored",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"minContains_0_0": {
|
||||
"minContains": 1,
|
||||
"extensible": true,
|
||||
"$id": "minContains_0_0"
|
||||
"extensible": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -36,16 +35,15 @@
|
||||
{
|
||||
"description": "minContains=1 with contains",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"minContains_1_0": {
|
||||
"contains": {
|
||||
"const": 1
|
||||
},
|
||||
"minContains": 1,
|
||||
"extensible": true,
|
||||
"$id": "minContains_1_0"
|
||||
"extensible": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -108,16 +106,15 @@
|
||||
{
|
||||
"description": "minContains=2 with contains",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"minContains_2_0": {
|
||||
"contains": {
|
||||
"const": 1
|
||||
},
|
||||
"minContains": 2,
|
||||
"extensible": true,
|
||||
"$id": "minContains_2_0"
|
||||
"extensible": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -195,16 +192,15 @@
|
||||
{
|
||||
"description": "minContains=2 with contains with a decimal value",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"minContains_3_0": {
|
||||
"contains": {
|
||||
"const": 1
|
||||
},
|
||||
"minContains": 2,
|
||||
"extensible": true,
|
||||
"$id": "minContains_3_0"
|
||||
"extensible": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -235,17 +231,16 @@
|
||||
{
|
||||
"description": "maxContains = minContains",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"minContains_4_0": {
|
||||
"contains": {
|
||||
"const": 1
|
||||
},
|
||||
"maxContains": 2,
|
||||
"minContains": 2,
|
||||
"extensible": true,
|
||||
"$id": "minContains_4_0"
|
||||
"extensible": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -298,17 +293,16 @@
|
||||
{
|
||||
"description": "maxContains < minContains",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"minContains_5_0": {
|
||||
"contains": {
|
||||
"const": 1
|
||||
},
|
||||
"maxContains": 1,
|
||||
"minContains": 3,
|
||||
"extensible": true,
|
||||
"$id": "minContains_5_0"
|
||||
"extensible": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -361,16 +355,15 @@
|
||||
{
|
||||
"description": "minContains = 0",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"minContains_6_0": {
|
||||
"contains": {
|
||||
"const": 1
|
||||
},
|
||||
"minContains": 0,
|
||||
"extensible": true,
|
||||
"$id": "minContains_6_0"
|
||||
"extensible": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -398,17 +391,16 @@
|
||||
{
|
||||
"description": "minContains = 0 with maxContains",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"minContains_7_0": {
|
||||
"contains": {
|
||||
"const": 1
|
||||
},
|
||||
"minContains": 0,
|
||||
"maxContains": 1,
|
||||
"extensible": true,
|
||||
"$id": "minContains_7_0"
|
||||
"extensible": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -448,16 +440,15 @@
|
||||
{
|
||||
"description": "extensible: true allows non-matching items in minContains",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"minContains_8_0": {
|
||||
"contains": {
|
||||
"const": 1
|
||||
},
|
||||
"minContains": 1,
|
||||
"extensible": true,
|
||||
"$id": "minContains_8_0"
|
||||
"extensible": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
|
||||
@ -2,13 +2,12 @@
|
||||
{
|
||||
"description": "minItems validation",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"minItems_0_0": {
|
||||
"minItems": 1,
|
||||
"extensible": true,
|
||||
"$id": "minItems_0_0"
|
||||
"extensible": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -57,13 +56,12 @@
|
||||
{
|
||||
"description": "minItems validation with a decimal",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"minItems_1_0": {
|
||||
"minItems": 1,
|
||||
"extensible": true,
|
||||
"$id": "minItems_1_0"
|
||||
"extensible": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -92,13 +90,12 @@
|
||||
{
|
||||
"description": "extensible: true allows extra items in minItems",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"minItems_2_0": {
|
||||
"minItems": 1,
|
||||
"extensible": true,
|
||||
"$id": "minItems_2_0"
|
||||
"extensible": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
|
||||
@ -2,12 +2,11 @@
|
||||
{
|
||||
"description": "minLength validation",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"minLength": 2,
|
||||
"$id": "minLength_0_0"
|
||||
"schemas": {
|
||||
"minLength_0_0": {
|
||||
"minLength": 2
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -60,12 +59,11 @@
|
||||
{
|
||||
"description": "minLength validation with a decimal",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"minLength": 2,
|
||||
"$id": "minLength_1_0"
|
||||
"schemas": {
|
||||
"minLength_1_0": {
|
||||
"minLength": 2
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
|
||||
@ -2,13 +2,12 @@
|
||||
{
|
||||
"description": "minProperties validation",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"minProperties_0_0": {
|
||||
"minProperties": 1,
|
||||
"extensible": true,
|
||||
"$id": "minProperties_0_0"
|
||||
"extensible": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -75,13 +74,12 @@
|
||||
{
|
||||
"description": "minProperties validation with a decimal",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"minProperties_1_0": {
|
||||
"minProperties": 1,
|
||||
"extensible": true,
|
||||
"$id": "minProperties_1_0"
|
||||
"extensible": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -110,13 +108,12 @@
|
||||
{
|
||||
"description": "extensible: true allows extra properties in minProperties",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"minProperties_2_0": {
|
||||
"minProperties": 1,
|
||||
"extensible": true,
|
||||
"$id": "minProperties_2_0"
|
||||
"extensible": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
|
||||
@ -2,12 +2,11 @@
|
||||
{
|
||||
"description": "minimum validation",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"minimum": 1.1,
|
||||
"$id": "minimum_0_0"
|
||||
"schemas": {
|
||||
"minimum_0_0": {
|
||||
"minimum": 1.1
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -51,12 +50,11 @@
|
||||
{
|
||||
"description": "minimum validation with signed integer",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"minimum": -2,
|
||||
"$id": "minimum_1_0"
|
||||
"schemas": {
|
||||
"minimum_1_0": {
|
||||
"minimum": -2
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
|
||||
@ -2,12 +2,11 @@
|
||||
{
|
||||
"description": "by int",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"multipleOf": 2,
|
||||
"$id": "multipleOf_0_0"
|
||||
"schemas": {
|
||||
"multipleOf_0_0": {
|
||||
"multipleOf": 2
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -42,12 +41,11 @@
|
||||
{
|
||||
"description": "by number",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"multipleOf": 1.5,
|
||||
"$id": "multipleOf_1_0"
|
||||
"schemas": {
|
||||
"multipleOf_1_0": {
|
||||
"multipleOf": 1.5
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -82,12 +80,11 @@
|
||||
{
|
||||
"description": "by small number",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"multipleOf": 0.0001,
|
||||
"$id": "multipleOf_2_0"
|
||||
"schemas": {
|
||||
"multipleOf_2_0": {
|
||||
"multipleOf": 0.0001
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -113,13 +110,12 @@
|
||||
{
|
||||
"description": "small multiple of large integer",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"multipleOf_3_0": {
|
||||
"type": "integer",
|
||||
"multipleOf": 1e-8,
|
||||
"$id": "multipleOf_3_0"
|
||||
"multipleOf": 1e-08
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
|
||||
@ -2,14 +2,13 @@
|
||||
{
|
||||
"description": "not",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"not_0_0": {
|
||||
"not": {
|
||||
"type": "integer"
|
||||
},
|
||||
"$id": "not_0_0"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -35,17 +34,16 @@
|
||||
{
|
||||
"description": "not multiple types",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"not_1_0": {
|
||||
"not": {
|
||||
"type": [
|
||||
"integer",
|
||||
"boolean"
|
||||
]
|
||||
},
|
||||
"$id": "not_1_0"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -80,8 +78,8 @@
|
||||
{
|
||||
"description": "not more complex schema",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"not_2_0": {
|
||||
"not": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@ -90,10 +88,9 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"extensible": true,
|
||||
"$id": "not_2_0"
|
||||
"extensible": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -132,16 +129,15 @@
|
||||
{
|
||||
"description": "forbidden property",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"not_3_0": {
|
||||
"properties": {
|
||||
"foo": {
|
||||
"not": {}
|
||||
}
|
||||
},
|
||||
"$id": "not_3_0"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -170,12 +166,11 @@
|
||||
{
|
||||
"description": "forbid everything with empty schema",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"not": {},
|
||||
"$id": "not_4_0"
|
||||
"schemas": {
|
||||
"not_4_0": {
|
||||
"not": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -268,12 +263,11 @@
|
||||
{
|
||||
"description": "forbid everything with boolean schema true",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"not": true,
|
||||
"$id": "not_5_0"
|
||||
"schemas": {
|
||||
"not_5_0": {
|
||||
"not": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -366,13 +360,12 @@
|
||||
{
|
||||
"description": "allow everything with boolean schema false",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"not_6_0": {
|
||||
"not": false,
|
||||
"extensible": true,
|
||||
"$id": "not_6_0"
|
||||
"extensible": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -465,14 +458,13 @@
|
||||
{
|
||||
"description": "double negation",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"not_7_0": {
|
||||
"not": {
|
||||
"not": {}
|
||||
},
|
||||
"$id": "not_7_0"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -489,15 +481,14 @@
|
||||
{
|
||||
"description": "extensible: true allows extra properties in not",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"not_8_0": {
|
||||
"not": {
|
||||
"type": "integer"
|
||||
},
|
||||
"extensible": true,
|
||||
"$id": "not_8_0"
|
||||
"extensible": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -516,14 +507,13 @@
|
||||
{
|
||||
"description": "extensible: false (default) forbids extra properties in not",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"not_9_0": {
|
||||
"not": {
|
||||
"type": "integer"
|
||||
},
|
||||
"$id": "not_9_0"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -542,8 +532,8 @@
|
||||
{
|
||||
"description": "property next to not (extensible: true)",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"not_10_0": {
|
||||
"properties": {
|
||||
"bar": {
|
||||
"type": "string"
|
||||
@ -552,10 +542,9 @@
|
||||
"not": {
|
||||
"type": "integer"
|
||||
},
|
||||
"extensible": true,
|
||||
"$id": "not_10_0"
|
||||
"extensible": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -575,8 +564,8 @@
|
||||
{
|
||||
"description": "property next to not (extensible: false)",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"not_11_0": {
|
||||
"properties": {
|
||||
"bar": {
|
||||
"type": "string"
|
||||
@ -584,10 +573,9 @@
|
||||
},
|
||||
"not": {
|
||||
"type": "integer"
|
||||
},
|
||||
"$id": "not_11_0"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
|
||||
183
fixtures/objectTypes.json
Normal file
183
fixtures/objectTypes.json
Normal file
@ -0,0 +1,183 @@
|
||||
[
|
||||
{
|
||||
"description": "Strict Inheritance",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"parent_type": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"a": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"a"
|
||||
]
|
||||
},
|
||||
"child_type": {
|
||||
"type": "parent_type",
|
||||
"properties": {
|
||||
"b": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "valid child inherits properties and strictness",
|
||||
"schema_id": "child_type",
|
||||
"data": {
|
||||
"a": 1,
|
||||
"b": 2
|
||||
},
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "missing inherited required property fails",
|
||||
"schema_id": "child_type",
|
||||
"data": {
|
||||
"b": 2
|
||||
},
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "additional properties fail due to inherited strictness",
|
||||
"schema_id": "child_type",
|
||||
"data": {
|
||||
"a": 1,
|
||||
"b": 2,
|
||||
"c": 3
|
||||
},
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Local Shadowing (Composition & Proxies)",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"budget": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"max": {
|
||||
"type": "integer",
|
||||
"maximum": 100
|
||||
}
|
||||
}
|
||||
},
|
||||
"custom_budget": {
|
||||
"type": "budget",
|
||||
"properties": {
|
||||
"max": {
|
||||
"type": "integer",
|
||||
"maximum": 50
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "shadowing override applies (50 is locally allowed)",
|
||||
"schema_id": "custom_budget",
|
||||
"data": {
|
||||
"max": 40
|
||||
},
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "shadowing override applies natively, rejecting 60 even though parent allowed 100",
|
||||
"schema_id": "custom_budget",
|
||||
"data": {
|
||||
"max": 60
|
||||
},
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Primitive Array Shorthand (Optionality)",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"invoice": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"amount": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"amount"
|
||||
]
|
||||
},
|
||||
"request": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"inv": {
|
||||
"type": [
|
||||
"invoice",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "valid object passes shorthand inheritance check",
|
||||
"schema_id": "request",
|
||||
"data": {
|
||||
"inv": {
|
||||
"amount": 5
|
||||
}
|
||||
},
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "null passes shorthand inheritance check",
|
||||
"schema_id": "request",
|
||||
"data": {
|
||||
"inv": null
|
||||
},
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "invalid object fails inner constraints safely",
|
||||
"schema_id": "request",
|
||||
"data": {
|
||||
"inv": {
|
||||
"amount": "string"
|
||||
}
|
||||
},
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@ -1,670 +0,0 @@
|
||||
[
|
||||
{
|
||||
"description": "oneOf",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"minimum": 2
|
||||
}
|
||||
],
|
||||
"$id": "oneOf_0_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "first oneOf valid",
|
||||
"data": 1,
|
||||
"schema_id": "oneOf_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "second oneOf valid",
|
||||
"data": 2.5,
|
||||
"schema_id": "oneOf_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "both oneOf valid",
|
||||
"data": 3,
|
||||
"schema_id": "oneOf_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "neither oneOf valid",
|
||||
"data": 1.5,
|
||||
"schema_id": "oneOf_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "oneOf with base schema",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"type": "string",
|
||||
"oneOf": [
|
||||
{
|
||||
"minLength": 2
|
||||
},
|
||||
{
|
||||
"maxLength": 4
|
||||
}
|
||||
],
|
||||
"$id": "oneOf_1_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "mismatch base schema",
|
||||
"data": 3,
|
||||
"schema_id": "oneOf_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "one oneOf valid",
|
||||
"data": "foobar",
|
||||
"schema_id": "oneOf_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "both oneOf valid",
|
||||
"data": "foo",
|
||||
"schema_id": "oneOf_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "oneOf with boolean schemas, all true",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"oneOf": [
|
||||
true,
|
||||
true,
|
||||
true
|
||||
],
|
||||
"$id": "oneOf_2_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "any value is invalid",
|
||||
"data": "foo",
|
||||
"schema_id": "oneOf_2_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "oneOf with boolean schemas, one true",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"oneOf": [
|
||||
true,
|
||||
false,
|
||||
false
|
||||
],
|
||||
"$id": "oneOf_3_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "any value is valid",
|
||||
"data": "foo",
|
||||
"schema_id": "oneOf_3_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "oneOf with boolean schemas, more than one true",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"oneOf": [
|
||||
true,
|
||||
true,
|
||||
false
|
||||
],
|
||||
"$id": "oneOf_4_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "any value is invalid",
|
||||
"data": "foo",
|
||||
"schema_id": "oneOf_4_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "oneOf with boolean schemas, all false",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"oneOf": [
|
||||
false,
|
||||
false,
|
||||
false
|
||||
],
|
||||
"$id": "oneOf_5_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "any value is invalid",
|
||||
"data": "foo",
|
||||
"schema_id": "oneOf_5_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "oneOf complex types",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"oneOf": [
|
||||
{
|
||||
"properties": {
|
||||
"bar": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"bar"
|
||||
]
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"foo"
|
||||
]
|
||||
}
|
||||
],
|
||||
"$id": "oneOf_6_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "first oneOf valid (complex)",
|
||||
"data": {
|
||||
"bar": 2
|
||||
},
|
||||
"schema_id": "oneOf_6_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "second oneOf valid (complex)",
|
||||
"data": {
|
||||
"foo": "baz"
|
||||
},
|
||||
"schema_id": "oneOf_6_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "both oneOf valid (complex)",
|
||||
"data": {
|
||||
"foo": "baz",
|
||||
"bar": 2
|
||||
},
|
||||
"schema_id": "oneOf_6_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "neither oneOf valid (complex)",
|
||||
"data": {
|
||||
"foo": 2,
|
||||
"bar": "quux"
|
||||
},
|
||||
"schema_id": "oneOf_6_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "oneOf with empty schema",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "number"
|
||||
},
|
||||
{}
|
||||
],
|
||||
"$id": "oneOf_7_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "one valid - valid",
|
||||
"data": "foo",
|
||||
"schema_id": "oneOf_7_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "both valid - invalid",
|
||||
"data": 123,
|
||||
"schema_id": "oneOf_7_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "oneOf with required",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"foo": true,
|
||||
"bar": true,
|
||||
"baz": true
|
||||
},
|
||||
"oneOf": [
|
||||
{
|
||||
"required": [
|
||||
"foo",
|
||||
"bar"
|
||||
]
|
||||
},
|
||||
{
|
||||
"required": [
|
||||
"foo",
|
||||
"baz"
|
||||
]
|
||||
}
|
||||
],
|
||||
"$id": "oneOf_8_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "both invalid - invalid",
|
||||
"data": {
|
||||
"bar": 2
|
||||
},
|
||||
"schema_id": "oneOf_8_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "first valid - valid",
|
||||
"data": {
|
||||
"foo": 1,
|
||||
"bar": 2
|
||||
},
|
||||
"schema_id": "oneOf_8_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "second valid - valid",
|
||||
"data": {
|
||||
"foo": 1,
|
||||
"baz": 3
|
||||
},
|
||||
"schema_id": "oneOf_8_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "both valid - invalid",
|
||||
"data": {
|
||||
"foo": 1,
|
||||
"bar": 2,
|
||||
"baz": 3
|
||||
},
|
||||
"schema_id": "oneOf_8_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "extra property invalid (strict)",
|
||||
"data": {
|
||||
"foo": 1,
|
||||
"bar": 2,
|
||||
"extra": 3
|
||||
},
|
||||
"schema_id": "oneOf_8_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "oneOf with required (extensible)",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"type": "object",
|
||||
"extensible": true,
|
||||
"oneOf": [
|
||||
{
|
||||
"required": [
|
||||
"foo",
|
||||
"bar"
|
||||
]
|
||||
},
|
||||
{
|
||||
"required": [
|
||||
"foo",
|
||||
"baz"
|
||||
]
|
||||
}
|
||||
],
|
||||
"$id": "oneOf_9_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "both invalid - invalid",
|
||||
"data": {
|
||||
"bar": 2
|
||||
},
|
||||
"schema_id": "oneOf_9_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "first valid - valid",
|
||||
"data": {
|
||||
"foo": 1,
|
||||
"bar": 2
|
||||
},
|
||||
"schema_id": "oneOf_9_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "second valid - valid",
|
||||
"data": {
|
||||
"foo": 1,
|
||||
"baz": 3
|
||||
},
|
||||
"schema_id": "oneOf_9_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "both valid - invalid",
|
||||
"data": {
|
||||
"foo": 1,
|
||||
"bar": 2,
|
||||
"baz": 3
|
||||
},
|
||||
"schema_id": "oneOf_9_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "extra properties are valid (extensible)",
|
||||
"data": {
|
||||
"foo": 1,
|
||||
"bar": 2,
|
||||
"extra": "value"
|
||||
},
|
||||
"schema_id": "oneOf_9_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "oneOf with missing optional property",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"oneOf": [
|
||||
{
|
||||
"properties": {
|
||||
"bar": true,
|
||||
"baz": true
|
||||
},
|
||||
"required": [
|
||||
"bar"
|
||||
]
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"foo": true
|
||||
},
|
||||
"required": [
|
||||
"foo"
|
||||
]
|
||||
}
|
||||
],
|
||||
"$id": "oneOf_10_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "first oneOf valid",
|
||||
"data": {
|
||||
"bar": 8
|
||||
},
|
||||
"schema_id": "oneOf_10_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "second oneOf valid",
|
||||
"data": {
|
||||
"foo": "foo"
|
||||
},
|
||||
"schema_id": "oneOf_10_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "both oneOf valid",
|
||||
"data": {
|
||||
"foo": "foo",
|
||||
"bar": 8
|
||||
},
|
||||
"schema_id": "oneOf_10_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "neither oneOf valid",
|
||||
"data": {
|
||||
"baz": "quux"
|
||||
},
|
||||
"schema_id": "oneOf_10_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "nested oneOf, to check validation semantics",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"oneOf": [
|
||||
{
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"$id": "oneOf_11_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "null is valid",
|
||||
"data": null,
|
||||
"schema_id": "oneOf_11_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "anything non-null is invalid",
|
||||
"data": 123,
|
||||
"schema_id": "oneOf_11_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "extensible: true allows extra properties in oneOf",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"oneOf": [
|
||||
{
|
||||
"properties": {
|
||||
"bar": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"bar"
|
||||
]
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"foo"
|
||||
]
|
||||
}
|
||||
],
|
||||
"extensible": true,
|
||||
"$id": "oneOf_12_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "extra property is valid (matches first option)",
|
||||
"data": {
|
||||
"bar": 2,
|
||||
"extra": "prop"
|
||||
},
|
||||
"schema_id": "oneOf_12_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@ -2,9 +2,8 @@
|
||||
{
|
||||
"description": "Hybrid Array Pathing",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"$id": "hybrid_pathing",
|
||||
"schemas": {
|
||||
"hybrid_pathing": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"primitives": {
|
||||
@ -69,7 +68,7 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -123,7 +122,9 @@
|
||||
"errors": [
|
||||
{
|
||||
"code": "INVALID_TYPE",
|
||||
"details": { "path": "primitives/1" }
|
||||
"details": {
|
||||
"path": "primitives/1"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -147,7 +148,15 @@
|
||||
"errors": [
|
||||
{
|
||||
"code": "REQUIRED_FIELD_MISSING",
|
||||
"details": { "path": "ad_hoc_objects/1/name" }
|
||||
"details": {
|
||||
"path": "ad_hoc_objects/1/name"
|
||||
}
|
||||
},
|
||||
{
|
||||
"code": "STRICT_PROPERTY_VIOLATION",
|
||||
"details": {
|
||||
"path": "ad_hoc_objects/1/age"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -173,7 +182,9 @@
|
||||
"errors": [
|
||||
{
|
||||
"code": "MINIMUM_VIOLATED",
|
||||
"details": { "path": "entities/entity-beta/value" }
|
||||
"details": {
|
||||
"path": "entities/entity-beta/value"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -204,7 +215,184 @@
|
||||
"errors": [
|
||||
{
|
||||
"code": "INVALID_TYPE",
|
||||
"details": { "path": "deep_entities/parent-omega/nested/child-beta/flag" }
|
||||
"details": {
|
||||
"path": "deep_entities/parent-omega/nested/child-beta/flag"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Ad-Hoc Union Pathing",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"ad_hoc_pathing": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"metadata_bubbles": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "integer"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "oneOf arrays natively support disjoint primitive types dynamically mapped into topological paths natively",
|
||||
"data": {
|
||||
"metadata_bubbles": [
|
||||
100,
|
||||
"metadata string",
|
||||
true
|
||||
]
|
||||
},
|
||||
"schema_id": "ad_hoc_pathing",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false,
|
||||
"errors": [
|
||||
{
|
||||
"code": "NO_ONEOF_MATCH",
|
||||
"details": {
|
||||
"path": "metadata_bubbles/2"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Polymorphic Family Pathing",
|
||||
"database": {
|
||||
"types": [
|
||||
{
|
||||
"name": "widget",
|
||||
"variations": [
|
||||
"widget"
|
||||
],
|
||||
"schemas": {
|
||||
"widget": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"stock.widget": {
|
||||
"type": "widget",
|
||||
"properties": {
|
||||
"kind": {
|
||||
"type": "string"
|
||||
},
|
||||
"amount": {
|
||||
"type": "integer"
|
||||
},
|
||||
"details": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"nested_metric": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"nested_metric"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"amount",
|
||||
"details"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"schemas": {
|
||||
"family_pathing": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"table_families": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$family": "widget"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
|
||||
{
|
||||
"description": "families mechanically map physical variants directly onto topological uuid array paths",
|
||||
"data": {
|
||||
"table_families": [
|
||||
{
|
||||
"id": "widget-1",
|
||||
"type": "widget",
|
||||
"kind": "stock",
|
||||
"amount": 500,
|
||||
"details": {
|
||||
"nested_metric": 42.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "widget-2",
|
||||
"type": "widget",
|
||||
"kind": "stock",
|
||||
"amount": "not_an_int",
|
||||
"details": {
|
||||
"stray_child": true
|
||||
},
|
||||
"unexpected_root_prop": "hi"
|
||||
}
|
||||
]
|
||||
},
|
||||
"schema_id": "family_pathing",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false,
|
||||
"errors": [
|
||||
{
|
||||
"code": "INVALID_TYPE",
|
||||
"details": {
|
||||
"path": "table_families/widget-2/amount"
|
||||
}
|
||||
},
|
||||
{
|
||||
"code": "REQUIRED_FIELD_MISSING",
|
||||
"details": {
|
||||
"path": "table_families/widget-2/details/nested_metric"
|
||||
}
|
||||
},
|
||||
{
|
||||
"code": "STRICT_PROPERTY_VIOLATION",
|
||||
"details": {
|
||||
"path": "table_families/widget-2/details/stray_child"
|
||||
}
|
||||
},
|
||||
{
|
||||
"code": "STRICT_PROPERTY_VIOLATION",
|
||||
"details": {
|
||||
"path": "table_families/widget-2/unexpected_root_prop"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@ -2,12 +2,11 @@
|
||||
{
|
||||
"description": "pattern validation",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"pattern": "^a*$",
|
||||
"$id": "pattern_0_0"
|
||||
"schemas": {
|
||||
"pattern_0_0": {
|
||||
"pattern": "^a*$"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -87,12 +86,11 @@
|
||||
{
|
||||
"description": "pattern is not anchored",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"pattern": "a+",
|
||||
"$id": "pattern_1_0"
|
||||
"schemas": {
|
||||
"pattern_1_0": {
|
||||
"pattern": "a+"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
|
||||
@ -2,17 +2,16 @@
|
||||
{
|
||||
"description": "patternProperties validates properties matching a regex",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"patternProperties_0_0": {
|
||||
"patternProperties": {
|
||||
"f.*o": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"items": {},
|
||||
"$id": "patternProperties_0_0"
|
||||
"items": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -108,8 +107,8 @@
|
||||
{
|
||||
"description": "multiple simultaneous patternProperties are validated",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"patternProperties_1_0": {
|
||||
"patternProperties": {
|
||||
"a*": {
|
||||
"type": "integer"
|
||||
@ -117,10 +116,9 @@
|
||||
"aaa*": {
|
||||
"maximum": 20
|
||||
}
|
||||
},
|
||||
"$id": "patternProperties_1_0"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -196,8 +194,8 @@
|
||||
{
|
||||
"description": "regexes are not anchored by default and are case sensitive",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"patternProperties_2_0": {
|
||||
"patternProperties": {
|
||||
"[0-9]{2,}": {
|
||||
"type": "boolean"
|
||||
@ -206,10 +204,9 @@
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"extensible": true,
|
||||
"$id": "patternProperties_2_0"
|
||||
"extensible": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -261,15 +258,14 @@
|
||||
{
|
||||
"description": "patternProperties with boolean schemas",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"patternProperties_3_0": {
|
||||
"patternProperties": {
|
||||
"f.*": true,
|
||||
"b.*": false
|
||||
},
|
||||
"$id": "patternProperties_3_0"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -331,16 +327,15 @@
|
||||
{
|
||||
"description": "patternProperties with null valued instance properties",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"patternProperties_4_0": {
|
||||
"patternProperties": {
|
||||
"^.*bar$": {
|
||||
"type": "null"
|
||||
}
|
||||
},
|
||||
"$id": "patternProperties_4_0"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -359,17 +354,16 @@
|
||||
{
|
||||
"description": "extensible: true allows extra properties NOT matching pattern",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"patternProperties_5_0": {
|
||||
"patternProperties": {
|
||||
"f.*o": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"extensible": true,
|
||||
"$id": "patternProperties_5_0"
|
||||
"extensible": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
|
||||
640
fixtures/polymorphism.json
Normal file
640
fixtures/polymorphism.json
Normal file
@ -0,0 +1,640 @@
|
||||
[
|
||||
{
|
||||
"description": "Vertical $family Routing (Across Tables)",
|
||||
"database": {
|
||||
"types": [
|
||||
{
|
||||
"name": "entity",
|
||||
"variations": [
|
||||
"entity",
|
||||
"organization",
|
||||
"person",
|
||||
"bot"
|
||||
],
|
||||
"schemas": {
|
||||
"entity": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "organization",
|
||||
"variations": [
|
||||
"organization",
|
||||
"person"
|
||||
],
|
||||
"schemas": {
|
||||
"organization": {
|
||||
"type": "entity",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "person",
|
||||
"variations": [
|
||||
"person"
|
||||
],
|
||||
"schemas": {
|
||||
"person": {
|
||||
"type": "organization",
|
||||
"properties": {
|
||||
"first_name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "bot",
|
||||
"variations": [
|
||||
"bot"
|
||||
],
|
||||
"schemas": {
|
||||
"bot": {
|
||||
"type": "entity",
|
||||
"properties": {
|
||||
"model": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"schemas": {
|
||||
"family_entity": {
|
||||
"$family": "entity"
|
||||
}
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "Matches base entity",
|
||||
"schema_id": "family_entity",
|
||||
"data": {
|
||||
"type": "entity",
|
||||
"id": "1"
|
||||
},
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Matches descendant person natively",
|
||||
"schema_id": "family_entity",
|
||||
"data": {
|
||||
"type": "person",
|
||||
"id": "2",
|
||||
"first_name": "Bob"
|
||||
},
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Missing type fails out immediately",
|
||||
"schema_id": "family_entity",
|
||||
"data": {
|
||||
"id": "3",
|
||||
"first_name": "Bob"
|
||||
},
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false,
|
||||
"errors": [
|
||||
{
|
||||
"code": "MISSING_TYPE",
|
||||
"details": {
|
||||
"path": ""
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Alias matching failure",
|
||||
"schema_id": "family_entity",
|
||||
"data": {
|
||||
"type": "alien",
|
||||
"id": "4"
|
||||
},
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false,
|
||||
"errors": [
|
||||
{
|
||||
"code": "NO_FAMILY_MATCH",
|
||||
"details": {
|
||||
"path": ""
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Matrix $family Routing (Vertical + Horizontal Intersections)",
|
||||
"database": {
|
||||
"types": [
|
||||
{
|
||||
"name": "entity",
|
||||
"variations": [
|
||||
"entity",
|
||||
"organization",
|
||||
"person",
|
||||
"bot"
|
||||
],
|
||||
"schemas": {
|
||||
"entity": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"light.entity": {
|
||||
"type": "entity",
|
||||
"properties": {
|
||||
"kind": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "organization",
|
||||
"variations": [
|
||||
"organization",
|
||||
"person"
|
||||
],
|
||||
"schemas": {
|
||||
"organization": {
|
||||
"type": "entity"
|
||||
},
|
||||
"light.organization": {
|
||||
"type": "light.entity"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "person",
|
||||
"variations": [
|
||||
"person"
|
||||
],
|
||||
"schemas": {
|
||||
"person": {
|
||||
"type": "organization"
|
||||
},
|
||||
"light.person": {
|
||||
"type": "light.organization"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "bot",
|
||||
"variations": [
|
||||
"bot"
|
||||
],
|
||||
"schemas": {
|
||||
"bot": {
|
||||
"type": "entity"
|
||||
},
|
||||
"light.bot": {
|
||||
"type": "light.entity"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"schemas": {
|
||||
"family_light_org": {
|
||||
"$family": "light.organization"
|
||||
}
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "Matches light.organization exact matrix target",
|
||||
"schema_id": "family_light_org",
|
||||
"data": {
|
||||
"type": "organization",
|
||||
"kind": "light"
|
||||
},
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Matches descendant light.person through matrix evaluation",
|
||||
"schema_id": "family_light_org",
|
||||
"data": {
|
||||
"type": "person",
|
||||
"kind": "light"
|
||||
},
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Structurally fails to route bot (bot is not descendant of organization)",
|
||||
"schema_id": "family_light_org",
|
||||
"data": {
|
||||
"type": "bot",
|
||||
"kind": "light"
|
||||
},
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false,
|
||||
"errors": [
|
||||
{
|
||||
"code": "NO_FAMILY_MATCH",
|
||||
"details": {
|
||||
"path": ""
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Horizontal $family Routing (Virtual Variations)",
|
||||
"database": {
|
||||
"types": [
|
||||
{
|
||||
"name": "widget",
|
||||
"variations": [
|
||||
"widget"
|
||||
],
|
||||
"schemas": {
|
||||
"widget": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"stock.widget": {
|
||||
"type": "widget",
|
||||
"properties": {
|
||||
"kind": {
|
||||
"type": "string"
|
||||
},
|
||||
"amount": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"super_stock.widget": {
|
||||
"type": "stock.widget",
|
||||
"properties": {
|
||||
"super_amount": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"schemas": {
|
||||
"family_widget": {
|
||||
"$family": "widget"
|
||||
},
|
||||
"family_stock_widget": {
|
||||
"$family": "stock.widget"
|
||||
}
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "Base widget resolves stock widget horizontally",
|
||||
"schema_id": "family_widget",
|
||||
"data": {
|
||||
"type": "widget",
|
||||
"kind": "stock",
|
||||
"amount": 5
|
||||
},
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Base widget resolves nested super stock widget natively via descendants crawl",
|
||||
"schema_id": "family_widget",
|
||||
"data": {
|
||||
"type": "widget",
|
||||
"kind": "super_stock",
|
||||
"amount": 5,
|
||||
"super_amount": 10
|
||||
},
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "stock.widget explicit family resolves nested super stock via fast path",
|
||||
"schema_id": "family_stock_widget",
|
||||
"data": {
|
||||
"type": "widget",
|
||||
"kind": "super_stock",
|
||||
"amount": 5,
|
||||
"super_amount": 10
|
||||
},
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "stock.widget fails when presented an invalid payload constraint",
|
||||
"schema_id": "family_stock_widget",
|
||||
"data": {
|
||||
"type": "widget",
|
||||
"kind": "super_stock",
|
||||
"amount": 5,
|
||||
"super_amount": "not_an_int"
|
||||
},
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false,
|
||||
"errors": [
|
||||
{
|
||||
"code": "INVALID_TYPE",
|
||||
"details": {
|
||||
"path": "super_amount"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Strict oneOf Punc Pointers (Tagged Unions)",
|
||||
"database": {
|
||||
"types": [
|
||||
{
|
||||
"name": "entity",
|
||||
"variations": [
|
||||
"entity",
|
||||
"person",
|
||||
"bot"
|
||||
],
|
||||
"schemas": {
|
||||
"entity": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "person",
|
||||
"variations": [
|
||||
"person"
|
||||
],
|
||||
"schemas": {
|
||||
"person": {
|
||||
"type": "entity"
|
||||
},
|
||||
"full.person": {
|
||||
"type": "person",
|
||||
"properties": {
|
||||
"kind": {
|
||||
"type": "string"
|
||||
},
|
||||
"age": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"age"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "bot",
|
||||
"variations": [
|
||||
"bot"
|
||||
],
|
||||
"schemas": {
|
||||
"bot": {
|
||||
"type": "entity"
|
||||
},
|
||||
"full.bot": {
|
||||
"type": "bot",
|
||||
"properties": {
|
||||
"kind": {
|
||||
"type": "string"
|
||||
},
|
||||
"version": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"version"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"schemas": {
|
||||
"oneOf_union": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "full.person"
|
||||
},
|
||||
{
|
||||
"type": "full.bot"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "Throws MISSING_TYPE if discriminator matches neither",
|
||||
"schema_id": "oneOf_union",
|
||||
"data": {
|
||||
"kind": "full",
|
||||
"age": 5
|
||||
},
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false,
|
||||
"errors": [
|
||||
{
|
||||
"code": "MISSING_TYPE",
|
||||
"details": {
|
||||
"path": ""
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Golden match throws pure structural error exclusively on person",
|
||||
"schema_id": "oneOf_union",
|
||||
"data": {
|
||||
"type": "person",
|
||||
"kind": "full",
|
||||
"age": "five"
|
||||
},
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false,
|
||||
"errors": [
|
||||
{
|
||||
"code": "INVALID_TYPE",
|
||||
"details": {
|
||||
"path": "age"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Golden matches perfectly",
|
||||
"schema_id": "oneOf_union",
|
||||
"data": {
|
||||
"type": "person",
|
||||
"kind": "full",
|
||||
"age": 5
|
||||
},
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Fails nicely with NO_ONEOF_MATCH",
|
||||
"schema_id": "oneOf_union",
|
||||
"data": {
|
||||
"type": "alien",
|
||||
"kind": "full",
|
||||
"age": 5
|
||||
},
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false,
|
||||
"errors": [
|
||||
{
|
||||
"code": "NO_ONEOF_MATCH",
|
||||
"details": {
|
||||
"path": ""
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "JSONB Field Bubble oneOf Discrimination (Promoted IDs)",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"metadata": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"invoice.metadata": {
|
||||
"type": "metadata",
|
||||
"properties": {
|
||||
"invoice_id": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"invoice_id"
|
||||
]
|
||||
},
|
||||
"payment.metadata": {
|
||||
"type": "metadata",
|
||||
"properties": {
|
||||
"payment_id": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"payment_id"
|
||||
]
|
||||
},
|
||||
"oneOf_bubble": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "invoice.metadata"
|
||||
},
|
||||
{
|
||||
"type": "payment.metadata"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "Extracts golden match natively from explicit JSONB type discriminator",
|
||||
"schema_id": "oneOf_bubble",
|
||||
"data": {
|
||||
"type": "invoice.metadata",
|
||||
"invoice_id": "nan"
|
||||
},
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false,
|
||||
"errors": [
|
||||
{
|
||||
"code": "INVALID_TYPE",
|
||||
"details": {
|
||||
"path": "invoice_id"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Valid payload succeeds perfectly in JSONB universe",
|
||||
"schema_id": "oneOf_bubble",
|
||||
"data": {
|
||||
"type": "payment.metadata",
|
||||
"payment_id": 123
|
||||
},
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@ -2,8 +2,8 @@
|
||||
{
|
||||
"description": "a schema given for prefixItems",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"prefixItems_0_0": {
|
||||
"prefixItems": [
|
||||
{
|
||||
"type": "integer"
|
||||
@ -11,10 +11,9 @@
|
||||
{
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"$id": "prefixItems_0_0"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -92,15 +91,14 @@
|
||||
{
|
||||
"description": "prefixItems with boolean schemas",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"prefixItems_1_0": {
|
||||
"prefixItems": [
|
||||
true,
|
||||
false
|
||||
],
|
||||
"$id": "prefixItems_1_0"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -140,17 +138,16 @@
|
||||
{
|
||||
"description": "additional items are allowed by default",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"prefixItems_2_0": {
|
||||
"prefixItems": [
|
||||
{
|
||||
"type": "integer"
|
||||
}
|
||||
],
|
||||
"extensible": true,
|
||||
"$id": "prefixItems_2_0"
|
||||
"extensible": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -171,16 +168,15 @@
|
||||
{
|
||||
"description": "prefixItems with null instance elements",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"prefixItems_3_0": {
|
||||
"prefixItems": [
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"$id": "prefixItems_3_0"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -199,17 +195,16 @@
|
||||
{
|
||||
"description": "extensible: true allows extra items with prefixItems",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"prefixItems_4_0": {
|
||||
"prefixItems": [
|
||||
{
|
||||
"type": "integer"
|
||||
}
|
||||
],
|
||||
"extensible": true,
|
||||
"$id": "prefixItems_4_0"
|
||||
"extensible": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
|
||||
@ -2,12 +2,11 @@
|
||||
{
|
||||
"description": "integer type matches integers",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"type": "integer",
|
||||
"$id": "type_0_0"
|
||||
"schemas": {
|
||||
"type_0_0": {
|
||||
"type": "integer"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -96,12 +95,11 @@
|
||||
{
|
||||
"description": "number type matches numbers",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"type": "number",
|
||||
"$id": "type_1_0"
|
||||
"schemas": {
|
||||
"type_1_0": {
|
||||
"type": "number"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -190,12 +188,11 @@
|
||||
{
|
||||
"description": "string type matches strings",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"type": "string",
|
||||
"$id": "type_2_0"
|
||||
"schemas": {
|
||||
"type_2_0": {
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -284,12 +281,11 @@
|
||||
{
|
||||
"description": "object type matches objects",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"type": "object",
|
||||
"$id": "type_3_0"
|
||||
"schemas": {
|
||||
"type_3_0": {
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -360,12 +356,11 @@
|
||||
{
|
||||
"description": "array type matches arrays",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"type": "array",
|
||||
"$id": "type_4_0"
|
||||
"schemas": {
|
||||
"type_4_0": {
|
||||
"type": "array"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -436,12 +431,11 @@
|
||||
{
|
||||
"description": "boolean type matches booleans",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"type": "boolean",
|
||||
"$id": "type_5_0"
|
||||
"schemas": {
|
||||
"type_5_0": {
|
||||
"type": "boolean"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -539,12 +533,11 @@
|
||||
{
|
||||
"description": "null type matches only the null object",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"type": "null",
|
||||
"$id": "type_6_0"
|
||||
"schemas": {
|
||||
"type_6_0": {
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -642,15 +635,14 @@
|
||||
{
|
||||
"description": "multiple types can be specified in an array",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"type_7_0": {
|
||||
"type": [
|
||||
"integer",
|
||||
"string"
|
||||
],
|
||||
"$id": "type_7_0"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -721,14 +713,13 @@
|
||||
{
|
||||
"description": "type as array with one item",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"type_8_0": {
|
||||
"type": [
|
||||
"string"
|
||||
],
|
||||
"$id": "type_8_0"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -754,16 +745,15 @@
|
||||
{
|
||||
"description": "type: array or object",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"type_9_0": {
|
||||
"type": [
|
||||
"array",
|
||||
"object"
|
||||
],
|
||||
"items": {},
|
||||
"$id": "type_9_0"
|
||||
"items": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -820,17 +810,16 @@
|
||||
{
|
||||
"description": "type: array, object or null",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"type_10_0": {
|
||||
"type": [
|
||||
"array",
|
||||
"object",
|
||||
"null"
|
||||
],
|
||||
"items": {},
|
||||
"$id": "type_10_0"
|
||||
"items": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -887,13 +876,12 @@
|
||||
{
|
||||
"description": "extensible: true allows extra properties",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"type_11_0": {
|
||||
"type": "object",
|
||||
"extensible": true,
|
||||
"$id": "type_11_0"
|
||||
"extensible": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -2,8 +2,8 @@
|
||||
{
|
||||
"description": "object properties validation",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"properties_0_0": {
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "integer"
|
||||
@ -11,10 +11,9 @@
|
||||
"bar": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"$id": "properties_0_0"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -85,15 +84,14 @@
|
||||
{
|
||||
"description": "properties with boolean schema",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"properties_1_0": {
|
||||
"properties": {
|
||||
"foo": true,
|
||||
"bar": false
|
||||
},
|
||||
"$id": "properties_1_0"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -144,8 +142,8 @@
|
||||
{
|
||||
"description": "properties with escaped characters",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"properties_2_0": {
|
||||
"properties": {
|
||||
"foo\nbar": {
|
||||
"type": "number"
|
||||
@ -165,10 +163,9 @@
|
||||
"foo\fbar": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"$id": "properties_2_0"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -208,16 +205,15 @@
|
||||
{
|
||||
"description": "properties with null valued instance properties",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"properties_3_0": {
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "null"
|
||||
}
|
||||
},
|
||||
"$id": "properties_3_0"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -237,8 +233,8 @@
|
||||
"description": "properties whose names are Javascript object property names",
|
||||
"comment": "Ensure JS implementations don't universally consider e.g. __proto__ to always be present in an object.",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"properties_4_0": {
|
||||
"properties": {
|
||||
"__proto__": {
|
||||
"type": "number"
|
||||
@ -253,10 +249,9 @@
|
||||
"constructor": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"$id": "properties_4_0"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -343,17 +338,16 @@
|
||||
{
|
||||
"description": "extensible: true allows extra properties",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"properties_5_0": {
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"extensible": true,
|
||||
"$id": "properties_5_0"
|
||||
"extensible": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -373,16 +367,15 @@
|
||||
{
|
||||
"description": "strict by default: extra properties invalid",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"properties_6_0": {
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"$id": "properties_6_0"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -402,8 +395,8 @@
|
||||
{
|
||||
"description": "inheritance: nested object inherits strictness from strict parent",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"properties_7_0": {
|
||||
"properties": {
|
||||
"nested": {
|
||||
"properties": {
|
||||
@ -412,10 +405,9 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"$id": "properties_7_0"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -437,8 +429,8 @@
|
||||
{
|
||||
"description": "override: nested object allows extra properties if extensible: true",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"properties_8_0": {
|
||||
"properties": {
|
||||
"nested": {
|
||||
"extensible": true,
|
||||
@ -448,10 +440,9 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"$id": "properties_8_0"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -473,8 +464,8 @@
|
||||
{
|
||||
"description": "inheritance: nested object inherits looseness from loose parent",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"properties_9_0": {
|
||||
"extensible": true,
|
||||
"properties": {
|
||||
"nested": {
|
||||
@ -484,10 +475,9 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"$id": "properties_9_0"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -509,8 +499,8 @@
|
||||
{
|
||||
"description": "override: nested object enforces strictness if extensible: false",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"properties_10_0": {
|
||||
"extensible": true,
|
||||
"properties": {
|
||||
"nested": {
|
||||
@ -521,10 +511,9 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"$id": "properties_10_0"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -546,8 +535,8 @@
|
||||
{
|
||||
"description": "arrays: inline items inherit strictness from strict parent",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"properties_11_0": {
|
||||
"properties": {
|
||||
"list": {
|
||||
"type": "array",
|
||||
@ -559,10 +548,9 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"$id": "properties_11_0"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -586,8 +574,8 @@
|
||||
{
|
||||
"description": "arrays: inline items inherit looseness from loose parent",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"properties_12_0": {
|
||||
"extensible": true,
|
||||
"properties": {
|
||||
"list": {
|
||||
@ -600,10 +588,9 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"$id": "properties_12_0"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
|
||||
@ -2,15 +2,14 @@
|
||||
{
|
||||
"description": "propertyNames validation",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"propertyNames_0_0": {
|
||||
"propertyNames": {
|
||||
"maxLength": 3
|
||||
},
|
||||
"extensible": true,
|
||||
"$id": "propertyNames_0_0"
|
||||
"extensible": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -83,15 +82,14 @@
|
||||
{
|
||||
"description": "propertyNames validation with pattern",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"propertyNames_1_0": {
|
||||
"propertyNames": {
|
||||
"pattern": "^a+$"
|
||||
},
|
||||
"extensible": true,
|
||||
"$id": "propertyNames_1_0"
|
||||
"extensible": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -132,13 +130,12 @@
|
||||
{
|
||||
"description": "propertyNames with boolean schema true",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"propertyNames_2_0": {
|
||||
"propertyNames": true,
|
||||
"extensible": true,
|
||||
"$id": "propertyNames_2_0"
|
||||
"extensible": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -166,13 +163,12 @@
|
||||
{
|
||||
"description": "propertyNames with boolean schema false",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"propertyNames_3_0": {
|
||||
"propertyNames": false,
|
||||
"extensible": true,
|
||||
"$id": "propertyNames_3_0"
|
||||
"extensible": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -200,15 +196,14 @@
|
||||
{
|
||||
"description": "propertyNames with const",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"propertyNames_4_0": {
|
||||
"propertyNames": {
|
||||
"const": "foo"
|
||||
},
|
||||
"extensible": true,
|
||||
"$id": "propertyNames_4_0"
|
||||
"extensible": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -247,18 +242,17 @@
|
||||
{
|
||||
"description": "propertyNames with enum",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"propertyNames_5_0": {
|
||||
"propertyNames": {
|
||||
"enum": [
|
||||
"foo",
|
||||
"bar"
|
||||
]
|
||||
},
|
||||
"extensible": true,
|
||||
"$id": "propertyNames_5_0"
|
||||
"extensible": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -309,15 +303,14 @@
|
||||
{
|
||||
"description": "extensible: true allows extra properties (checked by propertyNames)",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"propertyNames_6_0": {
|
||||
"propertyNames": {
|
||||
"maxLength": 3
|
||||
},
|
||||
"extensible": true,
|
||||
"$id": "propertyNames_6_0"
|
||||
"extensible": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,929 +0,0 @@
|
||||
[
|
||||
{
|
||||
"description": "nested refs",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"$ref": "c_212",
|
||||
"$id": "ref_4_0"
|
||||
},
|
||||
{
|
||||
"$id": "a_212",
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"$id": "b_212",
|
||||
"$ref": "a_212"
|
||||
},
|
||||
{
|
||||
"$id": "c_212",
|
||||
"$ref": "b_212"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "nested ref valid",
|
||||
"data": 5,
|
||||
"schema_id": "ref_4_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "nested ref invalid",
|
||||
"data": "a",
|
||||
"schema_id": "ref_4_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "ref applies alongside sibling keywords",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"properties": {
|
||||
"foo": {
|
||||
"$ref": "reffed_248",
|
||||
"maxItems": 2
|
||||
}
|
||||
},
|
||||
"$id": "ref_5_0"
|
||||
},
|
||||
{
|
||||
"$id": "reffed_248",
|
||||
"type": "array"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "ref valid, maxItems valid",
|
||||
"data": {
|
||||
"foo": []
|
||||
},
|
||||
"schema_id": "ref_5_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "ref valid, maxItems invalid",
|
||||
"data": {
|
||||
"foo": [
|
||||
1,
|
||||
2,
|
||||
3
|
||||
]
|
||||
},
|
||||
"schema_id": "ref_5_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "ref invalid",
|
||||
"data": {
|
||||
"foo": "string"
|
||||
},
|
||||
"schema_id": "ref_5_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "property named $ref that is not a reference",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"properties": {
|
||||
"$ref": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"$id": "ref_6_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "property named $ref valid",
|
||||
"data": {
|
||||
"$ref": "a"
|
||||
},
|
||||
"schema_id": "ref_6_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "property named $ref invalid",
|
||||
"data": {
|
||||
"$ref": 2
|
||||
},
|
||||
"schema_id": "ref_6_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "property named $ref, containing an actual $ref",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"properties": {
|
||||
"$ref": {
|
||||
"$ref": "is-string_344"
|
||||
}
|
||||
},
|
||||
"$id": "ref_7_0"
|
||||
},
|
||||
{
|
||||
"$id": "is-string_344",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "property named $ref valid",
|
||||
"data": {
|
||||
"$ref": "a"
|
||||
},
|
||||
"schema_id": "ref_7_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "property named $ref invalid",
|
||||
"data": {
|
||||
"$ref": 2
|
||||
},
|
||||
"schema_id": "ref_7_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "$ref to boolean schema true",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"$ref": "bool_378",
|
||||
"$id": "ref_8_0"
|
||||
},
|
||||
{
|
||||
"$id": "bool_378",
|
||||
"extensible": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "any value is valid",
|
||||
"data": "foo",
|
||||
"schema_id": "ref_8_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "$ref to boolean schema false",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"$ref": "bool_400",
|
||||
"$id": "ref_9_0"
|
||||
},
|
||||
{
|
||||
"$id": "bool_400",
|
||||
"extensible": false,
|
||||
"not": {}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "any value is invalid",
|
||||
"data": "foo",
|
||||
"schema_id": "ref_9_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "refs with quote",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"properties": {
|
||||
"foo\"bar": {
|
||||
"$ref": "foo%22bar_550"
|
||||
}
|
||||
},
|
||||
"$id": "ref_11_0"
|
||||
},
|
||||
{
|
||||
"$id": "foo%22bar_550",
|
||||
"type": "number"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "object with numbers is valid",
|
||||
"data": {
|
||||
"foo\"bar": 1
|
||||
},
|
||||
"schema_id": "ref_11_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "object with strings is invalid",
|
||||
"data": {
|
||||
"foo\"bar": "1"
|
||||
},
|
||||
"schema_id": "ref_11_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "$ref boundary resets to loose",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"$ref": "target_1465",
|
||||
"$id": "ref_35_0"
|
||||
},
|
||||
{
|
||||
"$id": "target_1465",
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "extra property in ref target is invalid (strict by default)",
|
||||
"data": {
|
||||
"foo": "bar",
|
||||
"extra": 1
|
||||
},
|
||||
"schema_id": "ref_35_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "$ref target can enforce strictness",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"$ref": "target_1496",
|
||||
"$id": "ref_36_0"
|
||||
},
|
||||
{
|
||||
"$id": "target_1496",
|
||||
"extensible": false,
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "extra property in ref target is invalid",
|
||||
"data": {
|
||||
"foo": "bar",
|
||||
"extra": 1
|
||||
},
|
||||
"schema_id": "ref_36_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "strictness: boundary reset at $ref",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"extensible": true,
|
||||
"properties": {
|
||||
"inline_child": {
|
||||
"properties": {
|
||||
"a": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ref_child": {
|
||||
"$ref": "strict_node_1544"
|
||||
},
|
||||
"extensible_ref_child": {
|
||||
"$ref": "extensible_node_1551"
|
||||
}
|
||||
},
|
||||
"$id": "ref_37_0"
|
||||
},
|
||||
{
|
||||
"$id": "strict_node_1544",
|
||||
"properties": {
|
||||
"b": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"$id": "extensible_node_1551",
|
||||
"extensible": true,
|
||||
"properties": {
|
||||
"c": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "inline child inherits looseness",
|
||||
"data": {
|
||||
"inline_child": {
|
||||
"a": 1,
|
||||
"extra": 2
|
||||
}
|
||||
},
|
||||
"schema_id": "ref_37_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "ref child resets to strict (default)",
|
||||
"data": {
|
||||
"ref_child": {
|
||||
"b": 1,
|
||||
"extra": 2
|
||||
}
|
||||
},
|
||||
"schema_id": "ref_37_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "ref child with explicit extensible=true is loose",
|
||||
"data": {
|
||||
"extensible_ref_child": {
|
||||
"c": 1,
|
||||
"extra": 2
|
||||
}
|
||||
},
|
||||
"schema_id": "ref_37_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "arrays: ref items inherit strictness (reset at boundary)",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"properties": {
|
||||
"list": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "strict_node_1614"
|
||||
}
|
||||
}
|
||||
},
|
||||
"$id": "ref_38_0"
|
||||
},
|
||||
{
|
||||
"$id": "strict_node_1614",
|
||||
"properties": {
|
||||
"a": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "ref item with extra property is invalid (strict by default)",
|
||||
"data": {
|
||||
"list": [
|
||||
{
|
||||
"a": 1,
|
||||
"extra": 2
|
||||
}
|
||||
]
|
||||
},
|
||||
"schema_id": "ref_38_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "implicit keyword shadowing",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"$ref": "parent_1648",
|
||||
"properties": {
|
||||
"type": {
|
||||
"const": "child"
|
||||
},
|
||||
"age": {
|
||||
"minimum": 15
|
||||
}
|
||||
},
|
||||
"$id": "ref_39_0"
|
||||
},
|
||||
{
|
||||
"$id": "parent_1648",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"const": "parent"
|
||||
},
|
||||
"age": {
|
||||
"minimum": 10,
|
||||
"maximum": 20
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type",
|
||||
"age"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "child type overrides parent type",
|
||||
"data": {
|
||||
"type": "child",
|
||||
"age": 15
|
||||
},
|
||||
"schema_id": "ref_39_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "parent type is now invalid (shadowed)",
|
||||
"data": {
|
||||
"type": "parent",
|
||||
"age": 15
|
||||
},
|
||||
"schema_id": "ref_39_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "child min age (15) is enforced",
|
||||
"data": {
|
||||
"type": "child",
|
||||
"age": 12
|
||||
},
|
||||
"schema_id": "ref_39_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "parent max age (20) is shadowed (replaced) by child definition",
|
||||
"data": {
|
||||
"type": "child",
|
||||
"age": 21
|
||||
},
|
||||
"schema_id": "ref_39_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Entities extending entities (Physical Birth)",
|
||||
"database": {
|
||||
"types": [
|
||||
{
|
||||
"name": "entity",
|
||||
"variations": [
|
||||
"entity",
|
||||
"organization",
|
||||
"person"
|
||||
],
|
||||
"schemas": [
|
||||
{
|
||||
"$id": "entity",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "organization",
|
||||
"variations": [
|
||||
"organization",
|
||||
"person"
|
||||
],
|
||||
"schemas": [
|
||||
{
|
||||
"$id": "organization",
|
||||
"$ref": "entity",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "person",
|
||||
"variations": [
|
||||
"person"
|
||||
],
|
||||
"schemas": [
|
||||
{
|
||||
"$id": "person",
|
||||
"$ref": "organization",
|
||||
"properties": {
|
||||
"first_name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"puncs": [
|
||||
{
|
||||
"name": "save_org",
|
||||
"schemas": [
|
||||
{
|
||||
"$id": "save_org.request",
|
||||
"$ref": "organization"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "Valid person against organization schema (implicit type allowance from physical hierarchy)",
|
||||
"schema_id": "save_org.request",
|
||||
"data": {
|
||||
"id": "1",
|
||||
"type": "person",
|
||||
"name": "ACME"
|
||||
},
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Valid organization against organization schema",
|
||||
"schema_id": "save_org.request",
|
||||
"data": {
|
||||
"id": "2",
|
||||
"type": "organization",
|
||||
"name": "ACME"
|
||||
},
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Invalid entity against organization schema (ancestor not allowed)",
|
||||
"schema_id": "save_org.request",
|
||||
"data": {
|
||||
"id": "3",
|
||||
"type": "entity"
|
||||
},
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false,
|
||||
"errors": [
|
||||
{
|
||||
"code": "CONST_VIOLATED",
|
||||
"details": { "path": "type" }
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Viral Infection: Ad-hocs inheriting entity boundaries via $ref",
|
||||
"database": {
|
||||
"types": [
|
||||
{
|
||||
"name": "entity",
|
||||
"variations": [
|
||||
"entity",
|
||||
"person"
|
||||
],
|
||||
"schemas": [
|
||||
{
|
||||
"$id": "entity",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "person",
|
||||
"variations": [
|
||||
"person"
|
||||
],
|
||||
"schemas": [
|
||||
{
|
||||
"$id": "person",
|
||||
"$ref": "entity",
|
||||
"properties": {
|
||||
"first_name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"$id": "light.person",
|
||||
"$ref": "entity",
|
||||
"properties": {
|
||||
"first_name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"puncs": [
|
||||
{
|
||||
"name": "save_person_light",
|
||||
"schemas": [
|
||||
{
|
||||
"$id": "save_person_light.request",
|
||||
"$ref": "light.person",
|
||||
"properties": {
|
||||
"extra_request_field": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "Valid person against ad-hoc request schema (request virally inherited person variations)",
|
||||
"schema_id": "save_person_light.request",
|
||||
"data": {
|
||||
"id": "1",
|
||||
"type": "person",
|
||||
"first_name": "John",
|
||||
"extra_request_field": "test"
|
||||
},
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Invalid entity against ad-hoc request schema (viral inheritance enforces person boundary)",
|
||||
"schema_id": "save_person_light.request",
|
||||
"data": {
|
||||
"id": "1",
|
||||
"type": "entity",
|
||||
"first_name": "John"
|
||||
},
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false,
|
||||
"errors": [
|
||||
{
|
||||
"code": "CONST_VIOLATED",
|
||||
"details": { "path": "type" }
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Ad-hocs extending ad-hocs (No type property)",
|
||||
"database": {
|
||||
"puncs": [
|
||||
{
|
||||
"name": "save_address",
|
||||
"schemas": [
|
||||
{
|
||||
"$id": "address",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"street": {
|
||||
"type": "string"
|
||||
},
|
||||
"city": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"$id": "us_address",
|
||||
"$ref": "address",
|
||||
"properties": {
|
||||
"state": {
|
||||
"type": "string"
|
||||
},
|
||||
"zip": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"$id": "save_address.request",
|
||||
"$ref": "us_address"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "Valid us_address",
|
||||
"schema_id": "save_address.request",
|
||||
"data": {
|
||||
"street": "123 Main",
|
||||
"city": "Anytown",
|
||||
"state": "CA",
|
||||
"zip": "12345"
|
||||
},
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Invalid base address against us_address",
|
||||
"schema_id": "save_address.request",
|
||||
"data": {
|
||||
"street": "123 Main",
|
||||
"city": "Anytown"
|
||||
},
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Ad-hocs extending ad-hocs (with string type property, no magic)",
|
||||
"database": {
|
||||
"puncs": [
|
||||
{
|
||||
"name": "save_config",
|
||||
"schemas": [
|
||||
{
|
||||
"$id": "config_base",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"const": "config_base"
|
||||
},
|
||||
"setting": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"$id": "config_advanced",
|
||||
"$ref": "config_base",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"const": "config_advanced"
|
||||
},
|
||||
"advanced_setting": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"$id": "save_config.request",
|
||||
"$ref": "config_base"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "Valid config_base against config_base",
|
||||
"schema_id": "save_config.request",
|
||||
"data": {
|
||||
"type": "config_base",
|
||||
"setting": "on"
|
||||
},
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Invalid config_advanced against config_base (no type magic, const is strictly 'config_base')",
|
||||
"schema_id": "save_config.request",
|
||||
"data": {
|
||||
"type": "config_advanced",
|
||||
"setting": "on",
|
||||
"advanced_setting": "off"
|
||||
},
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@ -2,18 +2,17 @@
|
||||
{
|
||||
"description": "required validation",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"required_0_0": {
|
||||
"properties": {
|
||||
"foo": {},
|
||||
"bar": {}
|
||||
},
|
||||
"required": [
|
||||
"foo"
|
||||
],
|
||||
"$id": "required_0_0"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -88,14 +87,13 @@
|
||||
{
|
||||
"description": "required default validation",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"required_1_0": {
|
||||
"properties": {
|
||||
"foo": {}
|
||||
},
|
||||
"$id": "required_1_0"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -112,15 +110,14 @@
|
||||
{
|
||||
"description": "required with empty array",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"required_2_0": {
|
||||
"properties": {
|
||||
"foo": {}
|
||||
},
|
||||
"required": [],
|
||||
"$id": "required_2_0"
|
||||
"required": []
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -137,8 +134,8 @@
|
||||
{
|
||||
"description": "required with escaped characters",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"required_3_0": {
|
||||
"required": [
|
||||
"foo\nbar",
|
||||
"foo\"bar",
|
||||
@ -147,10 +144,9 @@
|
||||
"foo\tbar",
|
||||
"foo\fbar"
|
||||
],
|
||||
"extensible": true,
|
||||
"$id": "required_3_0"
|
||||
"extensible": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -187,17 +183,16 @@
|
||||
"description": "required properties whose names are Javascript object property names",
|
||||
"comment": "Ensure JS implementations don't universally consider e.g. __proto__ to always be present in an object.",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"required_4_0": {
|
||||
"required": [
|
||||
"__proto__",
|
||||
"toString",
|
||||
"constructor"
|
||||
],
|
||||
"extensible": true,
|
||||
"$id": "required_4_0"
|
||||
"extensible": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -284,15 +279,14 @@
|
||||
{
|
||||
"description": "extensible: true allows extra properties in required",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"required_5_0": {
|
||||
"required": [
|
||||
"foo"
|
||||
],
|
||||
"extensible": true,
|
||||
"$id": "required_5_0"
|
||||
"extensible": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
|
||||
@ -2,13 +2,12 @@
|
||||
{
|
||||
"description": "uniqueItems validation",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"uniqueItems_0_0": {
|
||||
"uniqueItems": true,
|
||||
"extensible": true,
|
||||
"$id": "uniqueItems_0_0"
|
||||
"extensible": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -469,8 +468,8 @@
|
||||
{
|
||||
"description": "uniqueItems with an array of items",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"uniqueItems_1_0": {
|
||||
"prefixItems": [
|
||||
{
|
||||
"type": "boolean"
|
||||
@ -480,10 +479,9 @@
|
||||
}
|
||||
],
|
||||
"uniqueItems": true,
|
||||
"extensible": true,
|
||||
"$id": "uniqueItems_1_0"
|
||||
"extensible": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -595,8 +593,8 @@
|
||||
{
|
||||
"description": "uniqueItems with an array of items and additionalItems=false",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"uniqueItems_2_0": {
|
||||
"prefixItems": [
|
||||
{
|
||||
"type": "boolean"
|
||||
@ -606,10 +604,9 @@
|
||||
}
|
||||
],
|
||||
"uniqueItems": true,
|
||||
"items": false,
|
||||
"$id": "uniqueItems_2_0"
|
||||
"items": false
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -678,13 +675,12 @@
|
||||
{
|
||||
"description": "uniqueItems=false validation",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"uniqueItems_3_0": {
|
||||
"uniqueItems": false,
|
||||
"extensible": true,
|
||||
"$id": "uniqueItems_3_0"
|
||||
"extensible": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -924,8 +920,8 @@
|
||||
{
|
||||
"description": "uniqueItems=false with an array of items",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"uniqueItems_4_0": {
|
||||
"prefixItems": [
|
||||
{
|
||||
"type": "boolean"
|
||||
@ -935,10 +931,9 @@
|
||||
}
|
||||
],
|
||||
"uniqueItems": false,
|
||||
"extensible": true,
|
||||
"$id": "uniqueItems_4_0"
|
||||
"extensible": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -1050,8 +1045,8 @@
|
||||
{
|
||||
"description": "uniqueItems=false with an array of items and additionalItems=false",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"uniqueItems_5_0": {
|
||||
"prefixItems": [
|
||||
{
|
||||
"type": "boolean"
|
||||
@ -1061,10 +1056,9 @@
|
||||
}
|
||||
],
|
||||
"uniqueItems": false,
|
||||
"items": false,
|
||||
"$id": "uniqueItems_5_0"
|
||||
"items": false
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -1133,13 +1127,12 @@
|
||||
{
|
||||
"description": "extensible: true allows extra items in uniqueItems",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"schemas": {
|
||||
"uniqueItems_6_0": {
|
||||
"uniqueItems": true,
|
||||
"extensible": true,
|
||||
"$id": "uniqueItems_6_0"
|
||||
"extensible": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
use crate::database::schema::Schema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||
#[serde(default)]
|
||||
@ -8,5 +9,6 @@ pub struct Enum {
|
||||
pub module: String,
|
||||
pub source: String,
|
||||
pub values: Vec<String>,
|
||||
pub schemas: Vec<Schema>,
|
||||
#[serde(default)]
|
||||
pub schemas: std::collections::BTreeMap<String, Arc<Schema>>,
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@ pub mod executors;
|
||||
pub mod formats;
|
||||
pub mod page;
|
||||
pub mod punc;
|
||||
pub mod object;
|
||||
pub mod relation;
|
||||
pub mod schema;
|
||||
pub mod r#type;
|
||||
@ -23,7 +24,8 @@ use punc::Punc;
|
||||
use relation::Relation;
|
||||
use schema::Schema;
|
||||
use serde_json::Value;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use r#type::Type;
|
||||
|
||||
pub struct Database {
|
||||
@ -31,9 +33,7 @@ pub struct Database {
|
||||
pub types: HashMap<String, Type>,
|
||||
pub puncs: HashMap<String, Punc>,
|
||||
pub relations: HashMap<String, Relation>,
|
||||
pub schemas: HashMap<String, Schema>,
|
||||
pub descendants: HashMap<String, Vec<String>>,
|
||||
pub depths: HashMap<String, usize>,
|
||||
pub schemas: HashMap<String, Arc<Schema>>,
|
||||
pub executor: Box<dyn DatabaseExecutor + Send + Sync>,
|
||||
}
|
||||
|
||||
@ -45,8 +45,6 @@ impl Database {
|
||||
relations: HashMap::new(),
|
||||
puncs: HashMap::new(),
|
||||
schemas: HashMap::new(),
|
||||
descendants: HashMap::new(),
|
||||
depths: HashMap::new(),
|
||||
#[cfg(not(test))]
|
||||
executor: Box::new(SpiExecutor::new()),
|
||||
#[cfg(test)]
|
||||
@ -127,22 +125,16 @@ impl Database {
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(arr) = val.get("schemas").and_then(|v| v.as_array()) {
|
||||
for (i, item) in arr.iter().enumerate() {
|
||||
if let Some(map) = val.get("schemas").and_then(|v| v.as_object()) {
|
||||
for (key, item) in map.iter() {
|
||||
match serde_json::from_value::<Schema>(item.clone()) {
|
||||
Ok(mut schema) => {
|
||||
let id = schema
|
||||
.obj
|
||||
.id
|
||||
.clone()
|
||||
.unwrap_or_else(|| format!("schema_{}", i));
|
||||
schema.obj.id = Some(id.clone());
|
||||
db.schemas.insert(id, schema);
|
||||
Ok(schema) => {
|
||||
db.schemas.insert(key.clone(), Arc::new(schema));
|
||||
}
|
||||
Err(e) => {
|
||||
errors.push(crate::drop::Error {
|
||||
code: "DATABASE_SCHEMA_PARSE_FAILED".to_string(),
|
||||
message: format!("Failed to parse database schema: {}", e),
|
||||
message: format!("Failed to parse database schema key '{}': {}", key, e),
|
||||
details: crate::drop::ErrorDetails::default(),
|
||||
});
|
||||
}
|
||||
@ -187,19 +179,21 @@ impl Database {
|
||||
|
||||
pub fn compile(&mut self, errors: &mut Vec<crate::drop::Error>) {
|
||||
let mut harvested = Vec::new();
|
||||
for schema in self.schemas.values_mut() {
|
||||
schema.collect_schemas(None, &mut harvested, errors);
|
||||
for (id, schema_arc) in &self.schemas {
|
||||
crate::database::schema::Schema::collect_schemas(schema_arc, id, id.clone(), &mut harvested, errors);
|
||||
}
|
||||
for (id, schema_arc) in harvested {
|
||||
self.schemas.insert(id, schema_arc);
|
||||
}
|
||||
self.schemas.extend(harvested);
|
||||
|
||||
self.collect_schemas(errors);
|
||||
self.collect_depths();
|
||||
self.collect_descendants();
|
||||
|
||||
// Mathematically evaluate all property inheritances, formats, schemas, and foreign key edges topographically over OnceLocks
|
||||
let mut visited = std::collections::HashSet::new();
|
||||
for schema in self.schemas.values() {
|
||||
schema.compile(self, &mut visited, errors);
|
||||
for (id, schema_arc) in &self.schemas {
|
||||
// First compile pass initializes exact structural root_id mapping to resolve DB constraints
|
||||
let root_id = id.split('/').next().unwrap_or(id);
|
||||
schema_arc.as_ref().compile(self, root_id, id.clone(), &mut visited, errors);
|
||||
}
|
||||
}
|
||||
|
||||
@ -209,86 +203,204 @@ impl Database {
|
||||
// Pass 1: Extract all Schemas structurally off top level definitions into the master registry.
|
||||
// Validate every node recursively via string filters natively!
|
||||
for type_def in self.types.values() {
|
||||
for mut schema in type_def.schemas.clone() {
|
||||
schema.collect_schemas(None, &mut to_insert, errors);
|
||||
for (id, schema_arc) in &type_def.schemas {
|
||||
to_insert.push((id.clone(), Arc::clone(schema_arc)));
|
||||
crate::database::schema::Schema::collect_schemas(schema_arc, id, id.clone(), &mut to_insert, errors);
|
||||
}
|
||||
}
|
||||
for punc_def in self.puncs.values() {
|
||||
for mut schema in punc_def.schemas.clone() {
|
||||
schema.collect_schemas(None, &mut to_insert, errors);
|
||||
for (id, schema_arc) in &punc_def.schemas {
|
||||
to_insert.push((id.clone(), Arc::clone(schema_arc)));
|
||||
crate::database::schema::Schema::collect_schemas(schema_arc, id, id.clone(), &mut to_insert, errors);
|
||||
}
|
||||
}
|
||||
for enum_def in self.enums.values() {
|
||||
for mut schema in enum_def.schemas.clone() {
|
||||
schema.collect_schemas(None, &mut to_insert, errors);
|
||||
for (id, schema_arc) in &enum_def.schemas {
|
||||
to_insert.push((id.clone(), Arc::clone(schema_arc)));
|
||||
crate::database::schema::Schema::collect_schemas(schema_arc, id, id.clone(), &mut to_insert, errors);
|
||||
}
|
||||
}
|
||||
|
||||
for (id, schema) in to_insert {
|
||||
self.schemas.insert(id, schema);
|
||||
for (id, schema_arc) in to_insert {
|
||||
self.schemas.insert(id, schema_arc);
|
||||
}
|
||||
}
|
||||
|
||||
fn collect_depths(&mut self) {
|
||||
let mut depths: HashMap<String, usize> = HashMap::new();
|
||||
let schema_ids: Vec<String> = self.schemas.keys().cloned().collect();
|
||||
/// Inspects the Postgres pg_constraint relations catalog to securely identify
|
||||
/// the precise Foreign Key connecting a parent and child hierarchy path.
|
||||
pub fn resolve_relation<'a>(
|
||||
&'a self,
|
||||
parent_type: &str,
|
||||
child_type: &str,
|
||||
prop_name: &str,
|
||||
relative_keys: Option<&Vec<String>>,
|
||||
is_array: bool,
|
||||
schema_id: Option<&str>,
|
||||
path: &str,
|
||||
errors: &mut Vec<crate::drop::Error>,
|
||||
) -> Option<(&'a crate::database::relation::Relation, bool)> {
|
||||
// Enforce graph locality by ensuring we don't accidentally crawl to pure structural entity boundaries
|
||||
if parent_type == "entity" && child_type == "entity" {
|
||||
return None;
|
||||
}
|
||||
|
||||
for id in schema_ids {
|
||||
let mut current_id = id.clone();
|
||||
let mut depth = 0;
|
||||
let mut visited = HashSet::new();
|
||||
let p_def = self.types.get(parent_type)?;
|
||||
let c_def = self.types.get(child_type)?;
|
||||
|
||||
while let Some(schema) = self.schemas.get(¤t_id) {
|
||||
if !visited.insert(current_id.clone()) {
|
||||
break; // Cycle detected
|
||||
}
|
||||
if let Some(ref_str) = &schema.obj.r#ref {
|
||||
current_id = ref_str.clone();
|
||||
depth += 1;
|
||||
} else {
|
||||
let mut matching_rels = Vec::new();
|
||||
let mut directions = Vec::new();
|
||||
|
||||
// Scour the complete catalog for any Edge matching the inheritance scope of the two objects
|
||||
// This automatically binds polymorphic structures (e.g. recognizing a relationship targeting User
|
||||
// also natively binds instances specifically typed as Person).
|
||||
let mut all_rels: Vec<&crate::database::relation::Relation> = self.relations.values().collect();
|
||||
all_rels.sort_by(|a, b| a.constraint.cmp(&b.constraint));
|
||||
|
||||
for rel in all_rels {
|
||||
let mut is_forward =
|
||||
p_def.hierarchy.contains(&rel.source_type) && c_def.hierarchy.contains(&rel.destination_type);
|
||||
let is_reverse =
|
||||
p_def.hierarchy.contains(&rel.destination_type) && c_def.hierarchy.contains(&rel.source_type);
|
||||
|
||||
// Structural Cardinality Filtration:
|
||||
// If the schema requires a collection (Array), it is mathematically impossible for a pure
|
||||
// Forward scalar edge (where the parent holds exactly one UUID pointer) to fulfill a One-to-Many request.
|
||||
// Thus, if it's an array, we fully reject pure Forward edges and only accept Reverse edges (or Junction edges).
|
||||
if is_array && is_forward && !is_reverse {
|
||||
is_forward = false;
|
||||
}
|
||||
|
||||
if is_forward {
|
||||
matching_rels.push(rel);
|
||||
directions.push(true);
|
||||
} else if is_reverse {
|
||||
matching_rels.push(rel);
|
||||
directions.push(false);
|
||||
}
|
||||
}
|
||||
|
||||
// Abort relation discovery early if no hierarchical inheritance match was found
|
||||
if matching_rels.is_empty() {
|
||||
let mut details = crate::drop::ErrorDetails {
|
||||
path: path.to_string(),
|
||||
..Default::default()
|
||||
};
|
||||
if let Some(sid) = schema_id {
|
||||
details.schema = Some(sid.to_string());
|
||||
}
|
||||
|
||||
errors.push(crate::drop::Error {
|
||||
code: "EDGE_MISSING".to_string(),
|
||||
message: format!(
|
||||
"No database relation exists between '{}' and '{}' for property '{}'",
|
||||
parent_type, child_type, prop_name
|
||||
),
|
||||
details,
|
||||
});
|
||||
return None;
|
||||
}
|
||||
|
||||
// Ideal State: The objects only share a solitary structural relation, resolving ambiguity instantly.
|
||||
if matching_rels.len() == 1 {
|
||||
return Some((matching_rels[0], directions[0]));
|
||||
}
|
||||
|
||||
let mut chosen_idx = 0;
|
||||
let mut resolved = false;
|
||||
|
||||
// Exact Prefix Disambiguation: Determine if the database specifically names this constraint
|
||||
// directly mapping to the JSON Schema property name (e.g., `fk_{child}_{property_name}`)
|
||||
for (i, rel) in matching_rels.iter().enumerate() {
|
||||
if let Some(prefix) = &rel.prefix {
|
||||
if prop_name.starts_with(prefix)
|
||||
|| prefix.starts_with(prop_name)
|
||||
|| prefix.replace("_", "") == prop_name.replace("_", "")
|
||||
{
|
||||
chosen_idx = i;
|
||||
resolved = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
depths.insert(id, depth);
|
||||
}
|
||||
self.depths = depths;
|
||||
}
|
||||
|
||||
fn collect_descendants(&mut self) {
|
||||
let mut direct_refs: HashMap<String, Vec<String>> = HashMap::new();
|
||||
for (id, schema) in &self.schemas {
|
||||
if let Some(ref_str) = &schema.obj.r#ref {
|
||||
direct_refs
|
||||
.entry(ref_str.clone())
|
||||
.or_default()
|
||||
.push(id.clone());
|
||||
// Complex Subgraph Resolution: The database contains multiple equally explicit foreign key constraints
|
||||
// linking these objects (such as pointing to `source` and `target` in Many-to-Many junction models).
|
||||
if !resolved && relative_keys.is_some() {
|
||||
// Twin Deduction Pass 1: We inspect the exact properties structurally defined inside the compiled payload
|
||||
// to observe which explicit relation arrow the child payload natively consumes.
|
||||
let keys = relative_keys.unwrap();
|
||||
let mut consumed_rel_idx = None;
|
||||
for (i, rel) in matching_rels.iter().enumerate() {
|
||||
if let Some(prefix) = &rel.prefix {
|
||||
if keys.contains(prefix) {
|
||||
consumed_rel_idx = Some(i);
|
||||
break; // Found the routing edge explicitly consumed by the schema payload
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Cache exhaustive descendants matrix for generic $family string lookups natively
|
||||
let mut descendants = HashMap::new();
|
||||
for id in self.schemas.keys() {
|
||||
let mut desc_set = HashSet::new();
|
||||
Self::collect_descendants_recursively(id, &direct_refs, &mut desc_set);
|
||||
let mut desc_vec: Vec<String> = desc_set.into_iter().collect();
|
||||
desc_vec.sort();
|
||||
// Twin Deduction Pass 2: Knowing which arrow points outbound, we can mathematically deduce its twin
|
||||
// providing the reverse ownership on the same junction boundary must be the incoming Edge to the parent.
|
||||
if let Some(used_idx) = consumed_rel_idx {
|
||||
let used_rel = matching_rels[used_idx];
|
||||
let mut twin_ids = Vec::new();
|
||||
for (i, rel) in matching_rels.iter().enumerate() {
|
||||
if i != used_idx
|
||||
&& rel.source_type == used_rel.source_type
|
||||
&& rel.destination_type == used_rel.destination_type
|
||||
&& rel.prefix.is_some()
|
||||
{
|
||||
twin_ids.push(i);
|
||||
}
|
||||
}
|
||||
|
||||
descendants.insert(id.clone(), desc_vec);
|
||||
}
|
||||
self.descendants = descendants;
|
||||
}
|
||||
|
||||
fn collect_descendants_recursively(
|
||||
target: &str,
|
||||
direct_refs: &std::collections::HashMap<String, Vec<String>>,
|
||||
descendants: &mut std::collections::HashSet<String>,
|
||||
) {
|
||||
if let Some(children) = direct_refs.get(target) {
|
||||
for child in children {
|
||||
if descendants.insert(child.clone()) {
|
||||
Self::collect_descendants_recursively(child, direct_refs, descendants);
|
||||
if twin_ids.len() == 1 {
|
||||
chosen_idx = twin_ids[0];
|
||||
resolved = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Implicit Base Fallback: If no complex explicit paths resolve, but exactly one relation
|
||||
// sits entirely naked (without a constraint prefix), it must be the core structural parent ownership.
|
||||
if !resolved {
|
||||
let mut null_prefix_ids = Vec::new();
|
||||
for (i, rel) in matching_rels.iter().enumerate() {
|
||||
if rel.prefix.is_none() {
|
||||
null_prefix_ids.push(i);
|
||||
}
|
||||
}
|
||||
if null_prefix_ids.len() == 1 {
|
||||
chosen_idx = null_prefix_ids[0];
|
||||
resolved = true;
|
||||
}
|
||||
}
|
||||
|
||||
// If we exhausted all mathematical deduction pathways and STILL cannot isolate a single edge,
|
||||
// we must abort rather than silently guessing. Returning None prevents arbitrary SQL generation
|
||||
// and forces a clean structural error for the architect.
|
||||
if !resolved {
|
||||
let mut details = crate::drop::ErrorDetails {
|
||||
path: path.to_string(),
|
||||
context: serde_json::to_value(&matching_rels).ok(),
|
||||
cause: Some("Multiple conflicting constraints found matching prefixes".to_string()),
|
||||
..Default::default()
|
||||
};
|
||||
if let Some(sid) = schema_id {
|
||||
details.schema = Some(sid.to_string());
|
||||
}
|
||||
|
||||
errors.push(crate::drop::Error {
|
||||
code: "AMBIGUOUS_TYPE_RELATIONS".to_string(),
|
||||
message: format!(
|
||||
"Ambiguous database relation between '{}' and '{}' for property '{}'",
|
||||
parent_type, child_type, prop_name
|
||||
),
|
||||
details,
|
||||
});
|
||||
return None;
|
||||
}
|
||||
|
||||
Some((matching_rels[chosen_idx], directions[chosen_idx]))
|
||||
}
|
||||
}
|
||||
|
||||
320
src/database/object.rs
Normal file
320
src/database/object.rs
Normal file
@ -0,0 +1,320 @@
|
||||
use crate::database::schema::Schema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
use std::collections::BTreeMap;
|
||||
use std::sync::Arc;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Case {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub when: Option<Arc<Schema>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub then: Option<Arc<Schema>>,
|
||||
#[serde(rename = "else")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub else_: Option<Arc<Schema>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||
pub struct SchemaObject {
|
||||
// Core Schema Keywords
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub description: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub title: Option<String>,
|
||||
#[serde(default)] // Allow missing type
|
||||
#[serde(rename = "type")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub type_: Option<SchemaTypeOrArray>, // Handles string or array of strings
|
||||
|
||||
// Object Keywords
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub properties: Option<BTreeMap<String, Arc<Schema>>>,
|
||||
#[serde(rename = "patternProperties")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub pattern_properties: Option<BTreeMap<String, Arc<Schema>>>,
|
||||
#[serde(rename = "additionalProperties")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub additional_properties: Option<Arc<Schema>>,
|
||||
#[serde(rename = "$family")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub family: Option<String>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub required: Option<Vec<String>>,
|
||||
|
||||
// dependencies can be schema dependencies or property dependencies
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub dependencies: Option<BTreeMap<String, Dependency>>,
|
||||
|
||||
// Array Keywords
|
||||
#[serde(rename = "items")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub items: Option<Arc<Schema>>,
|
||||
#[serde(rename = "prefixItems")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub prefix_items: Option<Vec<Arc<Schema>>>,
|
||||
|
||||
// String Validation
|
||||
#[serde(rename = "minLength")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub min_length: Option<f64>,
|
||||
#[serde(rename = "maxLength")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub max_length: Option<f64>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub pattern: Option<String>,
|
||||
|
||||
// Array Validation
|
||||
#[serde(rename = "minItems")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub min_items: Option<f64>,
|
||||
#[serde(rename = "maxItems")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub max_items: Option<f64>,
|
||||
#[serde(rename = "uniqueItems")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub unique_items: Option<bool>,
|
||||
#[serde(rename = "contains")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub contains: Option<Arc<Schema>>,
|
||||
#[serde(rename = "minContains")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub min_contains: Option<f64>,
|
||||
#[serde(rename = "maxContains")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub max_contains: Option<f64>,
|
||||
|
||||
// Object Validation
|
||||
#[serde(rename = "minProperties")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub min_properties: Option<f64>,
|
||||
#[serde(rename = "maxProperties")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub max_properties: Option<f64>,
|
||||
#[serde(rename = "propertyNames")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub property_names: Option<Arc<Schema>>,
|
||||
|
||||
// Numeric Validation
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub format: Option<String>,
|
||||
#[serde(rename = "enum")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub enum_: Option<Vec<Value>>, // `enum` is a reserved keyword in Rust
|
||||
#[serde(
|
||||
default,
|
||||
rename = "const",
|
||||
deserialize_with = "crate::database::object::deserialize_some"
|
||||
)]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub const_: Option<Value>,
|
||||
|
||||
// Numeric Validation
|
||||
#[serde(rename = "multipleOf")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub multiple_of: Option<f64>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub minimum: Option<f64>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub maximum: Option<f64>,
|
||||
#[serde(rename = "exclusiveMinimum")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub exclusive_minimum: Option<f64>,
|
||||
#[serde(rename = "exclusiveMaximum")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub exclusive_maximum: Option<f64>,
|
||||
|
||||
// Combining Keywords
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub cases: Option<Vec<Case>>,
|
||||
#[serde(rename = "oneOf")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub one_of: Option<Vec<Arc<Schema>>>,
|
||||
#[serde(rename = "not")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub not: Option<Arc<Schema>>,
|
||||
|
||||
// Custom Vocabularies
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub form: Option<Vec<String>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub display: Option<Vec<String>>,
|
||||
#[serde(rename = "enumNames")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub enum_names: Option<Vec<String>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub control: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub actions: Option<BTreeMap<String, Action>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub computer: Option<String>,
|
||||
#[serde(default)]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub extensible: Option<bool>,
|
||||
|
||||
#[serde(rename = "compiledProperties")]
|
||||
#[serde(skip_deserializing)]
|
||||
#[serde(skip_serializing_if = "crate::database::object::is_once_lock_vec_empty")]
|
||||
#[serde(serialize_with = "crate::database::object::serialize_once_lock")]
|
||||
pub compiled_property_names: OnceLock<Vec<String>>,
|
||||
|
||||
#[serde(skip)]
|
||||
pub compiled_properties: OnceLock<BTreeMap<String, Arc<Schema>>>,
|
||||
|
||||
#[serde(rename = "compiledDiscriminator")]
|
||||
#[serde(skip_deserializing)]
|
||||
#[serde(skip_serializing_if = "crate::database::object::is_once_lock_string_empty")]
|
||||
#[serde(serialize_with = "crate::database::object::serialize_once_lock")]
|
||||
pub compiled_discriminator: OnceLock<String>,
|
||||
|
||||
#[serde(rename = "compiledOptions")]
|
||||
#[serde(skip_deserializing)]
|
||||
#[serde(skip_serializing_if = "crate::database::object::is_once_lock_map_empty")]
|
||||
#[serde(serialize_with = "crate::database::object::serialize_once_lock")]
|
||||
pub compiled_options: OnceLock<BTreeMap<String, (Option<usize>, Option<String>)>>,
|
||||
|
||||
#[serde(rename = "compiledEdges")]
|
||||
#[serde(skip_deserializing)]
|
||||
#[serde(skip_serializing_if = "crate::database::object::is_once_lock_map_empty")]
|
||||
#[serde(serialize_with = "crate::database::object::serialize_once_lock")]
|
||||
pub compiled_edges: OnceLock<BTreeMap<String, crate::database::edge::Edge>>,
|
||||
|
||||
#[serde(skip)]
|
||||
pub compiled_format: OnceLock<CompiledFormat>,
|
||||
#[serde(skip)]
|
||||
pub compiled_pattern: OnceLock<CompiledRegex>,
|
||||
#[serde(skip)]
|
||||
pub compiled_pattern_properties: OnceLock<Vec<(CompiledRegex, Arc<Schema>)>>,
|
||||
}
|
||||
|
||||
/// Represents a compiled format validator
|
||||
#[derive(Clone)]
|
||||
pub enum CompiledFormat {
|
||||
Func(fn(&serde_json::Value) -> Result<(), Box<dyn std::error::Error + Send + Sync>>),
|
||||
Regex(regex::Regex),
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for CompiledFormat {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
CompiledFormat::Func(_) => write!(f, "CompiledFormat::Func(...)"),
|
||||
CompiledFormat::Regex(r) => write!(f, "CompiledFormat::Regex({:?})", r),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A wrapper for compiled regex patterns
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CompiledRegex(pub regex::Regex);
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum SchemaTypeOrArray {
|
||||
Single(String),
|
||||
Multiple(Vec<String>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Action {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub navigate: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub punc: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum Dependency {
|
||||
Props(Vec<String>),
|
||||
Schema(Arc<Schema>),
|
||||
}
|
||||
|
||||
pub fn serialize_once_lock<T: serde::Serialize, S: serde::Serializer>(
|
||||
lock: &OnceLock<T>,
|
||||
serializer: S,
|
||||
) -> Result<S::Ok, S::Error> {
|
||||
if let Some(val) = lock.get() {
|
||||
val.serialize(serializer)
|
||||
} else {
|
||||
serializer.serialize_none()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_once_lock_map_empty<K, V>(lock: &OnceLock<std::collections::BTreeMap<K, V>>) -> bool {
|
||||
lock.get().map_or(true, |m| m.is_empty())
|
||||
}
|
||||
|
||||
pub fn is_once_lock_vec_empty<T>(lock: &OnceLock<Vec<T>>) -> bool {
|
||||
lock.get().map_or(true, |v| v.is_empty())
|
||||
}
|
||||
|
||||
pub fn is_once_lock_string_empty(lock: &OnceLock<String>) -> bool {
|
||||
lock.get().map_or(true, |s| s.is_empty())
|
||||
}
|
||||
|
||||
// Schema mirrors the Go Punc Generator's schema struct for consistency.
|
||||
// It is an order-preserving representation of a JSON Schema.
|
||||
pub fn deserialize_some<'de, D>(deserializer: D) -> Result<Option<Value>, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let v = Value::deserialize(deserializer)?;
|
||||
Ok(Some(v))
|
||||
}
|
||||
|
||||
pub fn is_primitive_type(t: &str) -> bool {
|
||||
matches!(
|
||||
t,
|
||||
"string" | "number" | "integer" | "boolean" | "object" | "array" | "null"
|
||||
)
|
||||
}
|
||||
|
||||
impl SchemaObject {
|
||||
pub fn get_discriminator_value(&self, dim: &str, schema_id: &str) -> Option<String> {
|
||||
let is_split = self
|
||||
.compiled_properties
|
||||
.get()
|
||||
.map_or(false, |p| p.contains_key("kind"));
|
||||
|
||||
if dim == "kind" {
|
||||
let base = schema_id.split('/').last().unwrap_or(schema_id);
|
||||
if let Some(idx) = base.rfind('.') {
|
||||
return Some(base[..idx].to_string());
|
||||
}
|
||||
}
|
||||
|
||||
if dim == "type" {
|
||||
let base = schema_id.split('/').last().unwrap_or(schema_id);
|
||||
if is_split {
|
||||
return Some(base.split('.').next_back().unwrap_or(base).to_string());
|
||||
} else {
|
||||
return Some(base.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn requires_uuid_path(&self, db: &crate::database::Database) -> bool {
|
||||
// 1. Explicitly defines "id" either directly or via inheritance/extension?
|
||||
if self
|
||||
.compiled_properties
|
||||
.get()
|
||||
.map_or(false, |p| p.contains_key("id"))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// 2. Implicit table-backed rule: Does its $family boundary map directly to the global database catalog?
|
||||
if let Some(family) = &self.family {
|
||||
let base = family.split('.').next_back().unwrap_or(family);
|
||||
if db.types.contains_key(base) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,7 @@
|
||||
use crate::database::page::Page;
|
||||
use crate::database::schema::Schema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||
#[serde(default)]
|
||||
@ -16,5 +17,5 @@ pub struct Punc {
|
||||
pub get: Option<String>,
|
||||
pub page: Option<Page>,
|
||||
#[serde(default)]
|
||||
pub schemas: Vec<Schema>,
|
||||
pub schemas: std::collections::BTreeMap<String, Arc<Schema>>,
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -2,6 +2,7 @@ use std::collections::HashSet;
|
||||
|
||||
use crate::database::schema::Schema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::sync::Arc;
|
||||
use serde_json::Value;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||
@ -38,5 +39,5 @@ pub struct Type {
|
||||
pub default_fields: Vec<String>,
|
||||
pub field_types: Option<Value>,
|
||||
#[serde(default)]
|
||||
pub schemas: Vec<Schema>,
|
||||
pub schemas: std::collections::BTreeMap<String, Arc<Schema>>,
|
||||
}
|
||||
|
||||
@ -25,7 +25,7 @@ impl Merger {
|
||||
let mut notifications_queue = Vec::new();
|
||||
|
||||
let target_schema = match self.db.schemas.get(schema_id) {
|
||||
Some(s) => Arc::new(s.clone()),
|
||||
Some(s) => Arc::clone(s),
|
||||
None => {
|
||||
return crate::drop::Drop::with_errors(vec![crate::drop::Error {
|
||||
code: "MERGE_FAILED".to_string(),
|
||||
@ -131,13 +131,43 @@ impl Merger {
|
||||
|
||||
pub(crate) fn merge_internal(
|
||||
&self,
|
||||
schema: Arc<crate::database::schema::Schema>,
|
||||
mut schema: Arc<crate::database::schema::Schema>,
|
||||
data: Value,
|
||||
notifications: &mut Vec<String>,
|
||||
) -> Result<Value, String> {
|
||||
match data {
|
||||
Value::Array(items) => self.merge_array(schema, items, notifications),
|
||||
Value::Object(map) => self.merge_object(schema, map, notifications),
|
||||
Value::Object(map) => {
|
||||
if let Some(options) = schema.obj.compiled_options.get() {
|
||||
if let Some(disc) = schema.obj.compiled_discriminator.get() {
|
||||
let val = map.get(disc).and_then(|v| v.as_str());
|
||||
if let Some(v) = val {
|
||||
if let Some((idx_opt, target_id_opt)) = options.get(v) {
|
||||
if let Some(target_id) = target_id_opt {
|
||||
if let Some(target_schema) = self.db.schemas.get(target_id) {
|
||||
schema = Arc::clone(target_schema);
|
||||
} else {
|
||||
return Err(format!("Polymorphic mapped target '{}' not found in database registry", target_id));
|
||||
}
|
||||
} else if let Some(idx) = idx_opt {
|
||||
if let Some(target_schema) = schema.obj.one_of.as_ref().and_then(|options| options.get(*idx)) {
|
||||
schema = Arc::clone(target_schema);
|
||||
} else {
|
||||
return Err(format!("Polymorphic index target '{}' not found in local oneOf array", idx));
|
||||
}
|
||||
} else {
|
||||
return Err(format!("Polymorphic mapped target has no path"));
|
||||
}
|
||||
} else {
|
||||
return Err(format!("Polymorphic discriminator {}='{}' matched no compiled options", disc, v));
|
||||
}
|
||||
} else {
|
||||
return Err(format!("Polymorphic merging failed: missing required discriminator '{}'", disc));
|
||||
}
|
||||
}
|
||||
}
|
||||
self.merge_object(schema, map, notifications)
|
||||
},
|
||||
_ => Err("Invalid merge payload: root must be an Object or Array".to_string()),
|
||||
}
|
||||
}
|
||||
@ -149,7 +179,7 @@ impl Merger {
|
||||
notifications: &mut Vec<String>,
|
||||
) -> Result<Value, String> {
|
||||
let mut item_schema = schema.clone();
|
||||
if let Some(crate::database::schema::SchemaTypeOrArray::Single(t)) = &schema.obj.type_ {
|
||||
if let Some(crate::database::object::SchemaTypeOrArray::Single(t)) = &schema.obj.type_ {
|
||||
if t == "array" {
|
||||
if let Some(items_def) = &schema.obj.items {
|
||||
item_schema = items_def.clone();
|
||||
@ -195,7 +225,7 @@ impl Merger {
|
||||
for (k, v) in obj {
|
||||
// Always retain system and unmapped core fields natively implicitly mapped to the Postgres tables
|
||||
if k == "id" || k == "type" || k == "created" {
|
||||
entity_fields.insert(k.clone(), v.clone());
|
||||
entity_fields.insert(k, v);
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -214,18 +244,18 @@ impl Merger {
|
||||
_ => "field", // Malformed edge data?
|
||||
};
|
||||
if typeof_v == "object" {
|
||||
entity_objects.insert(k.clone(), (v.clone(), prop_schema.clone()));
|
||||
entity_objects.insert(k, (v, prop_schema.clone()));
|
||||
} else if typeof_v == "array" {
|
||||
entity_arrays.insert(k.clone(), (v.clone(), prop_schema.clone()));
|
||||
entity_arrays.insert(k, (v, prop_schema.clone()));
|
||||
} else {
|
||||
entity_fields.insert(k.clone(), v.clone());
|
||||
entity_fields.insert(k, v);
|
||||
}
|
||||
} else {
|
||||
// Not an edge! It's a raw Postgres column (e.g., JSONB, text[])
|
||||
entity_fields.insert(k.clone(), v.clone());
|
||||
entity_fields.insert(k, v);
|
||||
}
|
||||
} else if type_def.fields.contains(&k) {
|
||||
entity_fields.insert(k.clone(), v.clone());
|
||||
entity_fields.insert(k, v);
|
||||
}
|
||||
}
|
||||
|
||||
@ -369,7 +399,7 @@ impl Merger {
|
||||
);
|
||||
|
||||
let mut item_schema = rel_schema.clone();
|
||||
if let Some(crate::database::schema::SchemaTypeOrArray::Single(t)) =
|
||||
if let Some(crate::database::object::SchemaTypeOrArray::Single(t)) =
|
||||
&rel_schema.obj.type_
|
||||
{
|
||||
if t == "array" {
|
||||
@ -504,7 +534,7 @@ impl Merger {
|
||||
|
||||
entity_change_kind = Some("create".to_string());
|
||||
|
||||
let mut new_fields = changes.clone();
|
||||
let mut new_fields = changes;
|
||||
new_fields.insert("id".to_string(), id_val);
|
||||
new_fields.insert("type".to_string(), Value::String(type_name.to_string()));
|
||||
new_fields.insert("created_by".to_string(), Value::String(user_id.to_string()));
|
||||
@ -544,7 +574,7 @@ impl Merger {
|
||||
Some("update".to_string())
|
||||
};
|
||||
|
||||
let mut new_fields = changes.clone();
|
||||
let mut new_fields = changes;
|
||||
new_fields.insert(
|
||||
"id".to_string(),
|
||||
entity_fetched.as_ref().unwrap().get("id").unwrap().clone(),
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
use crate::database::Database;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct Compiler<'a> {
|
||||
pub db: &'a Database,
|
||||
pub filter_keys: &'a [String],
|
||||
@ -16,6 +17,8 @@ pub struct Node<'a> {
|
||||
pub property_name: Option<String>,
|
||||
pub depth: usize,
|
||||
pub ast_path: String,
|
||||
pub is_polymorphic_branch: bool,
|
||||
pub schema_id: Option<String>,
|
||||
}
|
||||
|
||||
impl<'a> Compiler<'a> {
|
||||
@ -27,7 +30,7 @@ impl<'a> Compiler<'a> {
|
||||
.get(schema_id)
|
||||
.ok_or_else(|| format!("Schema not found: {}", schema_id))?;
|
||||
|
||||
let target_schema = std::sync::Arc::new(schema.clone());
|
||||
let target_schema = std::sync::Arc::clone(schema);
|
||||
|
||||
let mut compiler = Compiler {
|
||||
db: &self.db,
|
||||
@ -44,10 +47,12 @@ impl<'a> Compiler<'a> {
|
||||
property_name: None,
|
||||
depth: 0,
|
||||
ast_path: String::new(),
|
||||
is_polymorphic_branch: false,
|
||||
schema_id: Some(schema_id.to_string()),
|
||||
};
|
||||
|
||||
let (sql, _) = compiler.compile_node(node)?;
|
||||
Ok(sql)
|
||||
Ok(format!("(SELECT jsonb_strip_nulls({}))", sql))
|
||||
}
|
||||
|
||||
/// Recursively walks the schema AST emitting native PostgreSQL jsonb mapping
|
||||
@ -55,7 +60,7 @@ impl<'a> Compiler<'a> {
|
||||
fn compile_node(&mut self, node: Node<'a>) -> Result<(String, String), String> {
|
||||
// Determine the base schema type (could be an array, object, or literal)
|
||||
match &node.schema.obj.type_ {
|
||||
Some(crate::database::schema::SchemaTypeOrArray::Single(t)) if t == "array" => {
|
||||
Some(crate::database::object::SchemaTypeOrArray::Single(t)) if t == "array" => {
|
||||
self.compile_array(node)
|
||||
}
|
||||
_ => self.compile_reference(node),
|
||||
@ -63,17 +68,31 @@ impl<'a> Compiler<'a> {
|
||||
}
|
||||
|
||||
fn compile_array(&mut self, node: Node<'a>) -> Result<(String, String), String> {
|
||||
// 1. Array of DB Entities (`$ref` or `$family` pointing to a table limit)
|
||||
if let Some(items) = &node.schema.obj.items {
|
||||
let mut resolved_type = None;
|
||||
if let Some(family_target) = items.obj.family.as_ref() {
|
||||
let base_type_name = family_target
|
||||
.split('.')
|
||||
.next_back()
|
||||
.unwrap_or(family_target);
|
||||
resolved_type = self.db.types.get(base_type_name);
|
||||
} else if let Some(base_type_name) = items.obj.identifier() {
|
||||
resolved_type = self.db.types.get(&base_type_name);
|
||||
if let Some(sid) = &node.schema_id {
|
||||
resolved_type = self
|
||||
.db
|
||||
.types
|
||||
.get(&sid.split('.').next_back().unwrap_or(sid).to_string());
|
||||
}
|
||||
|
||||
if resolved_type.is_none() {
|
||||
if let Some(family_target) = items.obj.family.as_ref() {
|
||||
let base_type_name = family_target
|
||||
.split('.')
|
||||
.next_back()
|
||||
.unwrap_or(family_target);
|
||||
resolved_type = self.db.types.get(base_type_name);
|
||||
} else if let Some(crate::database::object::SchemaTypeOrArray::Single(t)) = &items.obj.type_
|
||||
{
|
||||
if !crate::database::object::is_primitive_type(t) {
|
||||
resolved_type = self
|
||||
.db
|
||||
.types
|
||||
.get(&t.split('.').next_back().unwrap_or(t).to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(type_def) = resolved_type {
|
||||
@ -102,55 +121,62 @@ impl<'a> Compiler<'a> {
|
||||
// Determine if this schema represents a Database Entity
|
||||
let mut resolved_type = None;
|
||||
|
||||
if let Some(family_target) = node.schema.obj.family.as_ref() {
|
||||
resolved_type = self.db.types.get(family_target);
|
||||
} else if let Some(base_type_name) = node.schema.obj.identifier() {
|
||||
if let Some(sid) = &node.schema_id {
|
||||
let base_type_name = sid.split('.').next_back().unwrap_or(sid).to_string();
|
||||
resolved_type = self.db.types.get(&base_type_name);
|
||||
}
|
||||
|
||||
if resolved_type.is_none() {
|
||||
if let Some(family_target) = node.schema.obj.family.as_ref() {
|
||||
let base_type_name = family_target
|
||||
.split('.')
|
||||
.next_back()
|
||||
.unwrap_or(family_target);
|
||||
resolved_type = self.db.types.get(base_type_name);
|
||||
} else if let Some(crate::database::object::SchemaTypeOrArray::Single(t)) =
|
||||
&node.schema.obj.type_
|
||||
{
|
||||
if !crate::database::object::is_primitive_type(t) {
|
||||
let base_type_name = t.split('.').next_back().unwrap_or(t).to_string();
|
||||
resolved_type = self.db.types.get(&base_type_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(type_def) = resolved_type {
|
||||
return self.compile_entity(type_def, node.clone(), false);
|
||||
}
|
||||
|
||||
// Handle Direct Refs
|
||||
if let Some(ref_id) = &node.schema.obj.r#ref {
|
||||
// If it's just an ad-hoc struct ref, we should resolve it
|
||||
if let Some(target_schema) = self.db.schemas.get(ref_id) {
|
||||
let mut ref_node = node.clone();
|
||||
ref_node.schema = std::sync::Arc::new(target_schema.clone());
|
||||
return self.compile_node(ref_node);
|
||||
// Handle Direct Refs via type pointer
|
||||
if let Some(crate::database::object::SchemaTypeOrArray::Single(t)) = &node.schema.obj.type_ {
|
||||
if !crate::database::object::is_primitive_type(t) {
|
||||
// If it's just an ad-hoc struct ref, we should resolve it
|
||||
if let Some(target_schema) = self.db.schemas.get(t) {
|
||||
let mut ref_node = node.clone();
|
||||
ref_node.schema = Arc::clone(target_schema);
|
||||
ref_node.schema_id = Some(t.clone());
|
||||
return self.compile_node(ref_node);
|
||||
}
|
||||
return Err(format!("Unresolved schema type pointer: {}", t));
|
||||
}
|
||||
return Err(format!("Unresolved $ref: {}", ref_id));
|
||||
}
|
||||
// Handle $family Polymorphism fallbacks for relations
|
||||
if let Some(family_target) = &node.schema.obj.family {
|
||||
let mut all_targets = vec![family_target.clone()];
|
||||
if let Some(descendants) = self.db.descendants.get(family_target) {
|
||||
all_targets.extend(descendants.clone());
|
||||
// Handle Polymorphism fallbacks for relations
|
||||
if node.schema.obj.family.is_some() || node.schema.obj.one_of.is_some() {
|
||||
if let Some(options) = node.schema.obj.compiled_options.get() {
|
||||
if options.len() == 1 {
|
||||
let (_, target_opt) = options.values().next().unwrap();
|
||||
if let Some(target_id) = target_opt {
|
||||
let mut bypass_schema = crate::database::schema::Schema::default();
|
||||
bypass_schema.obj.type_ = Some(crate::database::object::SchemaTypeOrArray::Single(
|
||||
target_id.clone(),
|
||||
));
|
||||
let mut bypass_node = node.clone();
|
||||
bypass_node.schema = std::sync::Arc::new(bypass_schema);
|
||||
return self.compile_node(bypass_node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if all_targets.len() == 1 {
|
||||
let mut bypass_schema = crate::database::schema::Schema::default();
|
||||
bypass_schema.obj.r#ref = Some(all_targets[0].clone());
|
||||
let mut bypass_node = node.clone();
|
||||
bypass_node.schema = std::sync::Arc::new(bypass_schema);
|
||||
return self.compile_node(bypass_node);
|
||||
}
|
||||
|
||||
all_targets.sort();
|
||||
let mut family_schemas = Vec::new();
|
||||
for variation in &all_targets {
|
||||
let mut ref_schema = crate::database::schema::Schema::default();
|
||||
ref_schema.obj.r#ref = Some(variation.clone());
|
||||
family_schemas.push(std::sync::Arc::new(ref_schema));
|
||||
}
|
||||
|
||||
return self.compile_one_of(&family_schemas, node);
|
||||
}
|
||||
|
||||
// Handle oneOf Polymorphism fallbacks for relations
|
||||
if let Some(one_of) = &node.schema.obj.one_of {
|
||||
return self.compile_one_of(one_of, node.clone());
|
||||
return self.compile_one_of(node);
|
||||
}
|
||||
|
||||
// Just an inline object definition?
|
||||
@ -177,17 +203,28 @@ impl<'a> Compiler<'a> {
|
||||
) -> Result<(String, String), String> {
|
||||
let (table_aliases, from_clauses) = self.compile_from_clause(r#type);
|
||||
|
||||
// 2. Map properties and build jsonb_build_object args
|
||||
let mut select_args = self.compile_select_clause(r#type, &table_aliases, node.clone())?;
|
||||
let jsonb_obj_sql = if node.schema.obj.family.is_some() || node.schema.obj.one_of.is_some() {
|
||||
let base_alias = table_aliases
|
||||
.get(&r#type.name)
|
||||
.cloned()
|
||||
.unwrap_or_else(|| node.parent_alias.to_string());
|
||||
|
||||
// 2.5 Inject polymorphism directly into the query object
|
||||
let mut poly_args = self.compile_polymorphism_select(r#type, &table_aliases, node.clone())?;
|
||||
select_args.append(&mut poly_args);
|
||||
let mut case_node = node.clone();
|
||||
case_node.parent_alias = base_alias.clone();
|
||||
let arc_aliases = std::sync::Arc::new(table_aliases.clone());
|
||||
case_node.parent_type_aliases = Some(arc_aliases);
|
||||
case_node.parent_type = Some(r#type);
|
||||
|
||||
let jsonb_obj_sql = if select_args.is_empty() {
|
||||
"jsonb_build_object()".to_string()
|
||||
let (case_sql, _) = self.compile_one_of(case_node)?;
|
||||
case_sql
|
||||
} else {
|
||||
format!("jsonb_build_object({})", select_args.join(", "))
|
||||
let select_args = self.compile_select_clause(r#type, &table_aliases, node.clone())?;
|
||||
|
||||
if select_args.is_empty() {
|
||||
"jsonb_build_object()".to_string()
|
||||
} else {
|
||||
format!("jsonb_build_object({})", select_args.join(", "))
|
||||
}
|
||||
};
|
||||
|
||||
// 3. Build WHERE clauses
|
||||
@ -216,90 +253,6 @@ impl<'a> Compiler<'a> {
|
||||
))
|
||||
}
|
||||
|
||||
fn compile_polymorphism_select(
|
||||
&mut self,
|
||||
r#type: &'a crate::database::r#type::Type,
|
||||
table_aliases: &std::collections::HashMap<String, String>,
|
||||
node: Node<'a>,
|
||||
) -> Result<Vec<String>, String> {
|
||||
let mut select_args = Vec::new();
|
||||
|
||||
if let Some(family_target) = node.schema.obj.family.as_ref() {
|
||||
let family_prefix = family_target.rfind('.').map(|idx| &family_target[..idx]);
|
||||
|
||||
let mut all_targets = vec![family_target.clone()];
|
||||
if let Some(descendants) = self.db.descendants.get(family_target) {
|
||||
all_targets.extend(descendants.clone());
|
||||
}
|
||||
|
||||
// Filter targets to EXACTLY match the family_target prefix
|
||||
let mut final_targets = Vec::new();
|
||||
for target in all_targets {
|
||||
let target_prefix = target.rfind('.').map(|idx| &target[..idx]);
|
||||
if target_prefix == family_prefix {
|
||||
final_targets.push(target);
|
||||
}
|
||||
}
|
||||
|
||||
final_targets.sort();
|
||||
final_targets.dedup();
|
||||
|
||||
if final_targets.len() == 1 {
|
||||
let variation = &final_targets[0];
|
||||
if let Some(target_schema) = self.db.schemas.get(variation) {
|
||||
let mut bypass_node = node.clone();
|
||||
bypass_node.schema = std::sync::Arc::new(target_schema.clone());
|
||||
|
||||
let mut bypassed_args = self.compile_select_clause(r#type, table_aliases, bypass_node)?;
|
||||
select_args.append(&mut bypassed_args);
|
||||
} else {
|
||||
return Err(format!("Could not find schema for variation {}", variation));
|
||||
}
|
||||
} else {
|
||||
let mut family_schemas = Vec::new();
|
||||
|
||||
for variation in &final_targets {
|
||||
if let Some(target_schema) = self.db.schemas.get(variation) {
|
||||
family_schemas.push(std::sync::Arc::new(target_schema.clone()));
|
||||
} else {
|
||||
return Err(format!(
|
||||
"Could not find schema metadata for variation {}",
|
||||
variation
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
let base_alias = table_aliases
|
||||
.get(&r#type.name)
|
||||
.cloned()
|
||||
.unwrap_or_else(|| node.parent_alias.to_string());
|
||||
select_args.push(format!("'id', {}.id", base_alias));
|
||||
let mut case_node = node.clone();
|
||||
case_node.parent_alias = base_alias.clone();
|
||||
let arc_aliases = std::sync::Arc::new(table_aliases.clone());
|
||||
case_node.parent_type_aliases = Some(arc_aliases);
|
||||
|
||||
let (case_sql, _) = self.compile_one_of(&family_schemas, case_node)?;
|
||||
select_args.push(format!("'type', {}", case_sql));
|
||||
}
|
||||
} else if let Some(one_of) = &node.schema.obj.one_of {
|
||||
let base_alias = table_aliases
|
||||
.get(&r#type.name)
|
||||
.cloned()
|
||||
.unwrap_or_else(|| node.parent_alias.to_string());
|
||||
select_args.push(format!("'id', {}.id", base_alias));
|
||||
let mut case_node = node.clone();
|
||||
case_node.parent_alias = base_alias.clone();
|
||||
let arc_aliases = std::sync::Arc::new(table_aliases.clone());
|
||||
case_node.parent_type_aliases = Some(arc_aliases);
|
||||
|
||||
let (case_sql, _) = self.compile_one_of(one_of, case_node)?;
|
||||
select_args.push(format!("'type', {}", case_sql));
|
||||
}
|
||||
|
||||
Ok(select_args)
|
||||
}
|
||||
|
||||
fn compile_object(
|
||||
&mut self,
|
||||
props: &std::collections::BTreeMap<String, std::sync::Arc<crate::database::schema::Schema>>,
|
||||
@ -329,32 +282,95 @@ impl<'a> Compiler<'a> {
|
||||
Ok((combined, "object".to_string()))
|
||||
}
|
||||
|
||||
fn compile_one_of(
|
||||
&mut self,
|
||||
schemas: &[Arc<crate::database::schema::Schema>],
|
||||
node: Node<'a>,
|
||||
) -> Result<(String, String), String> {
|
||||
fn compile_one_of(&mut self, node: Node<'a>) -> Result<(String, String), String> {
|
||||
let mut case_statements = Vec::new();
|
||||
|
||||
let options = node
|
||||
.schema
|
||||
.obj
|
||||
.compiled_options
|
||||
.get()
|
||||
.ok_or("Missing compiled options for polymorphism")?;
|
||||
let disc = node
|
||||
.schema
|
||||
.obj
|
||||
.compiled_discriminator
|
||||
.get()
|
||||
.ok_or("Missing compiled discriminator for polymorphism")?;
|
||||
|
||||
let type_col = if let Some(prop) = &node.property_name {
|
||||
format!("{}_type", prop)
|
||||
format!("{}_{}", prop, disc)
|
||||
} else {
|
||||
"type".to_string()
|
||||
disc.to_string()
|
||||
};
|
||||
|
||||
for option_schema in schemas {
|
||||
if let Some(base_type_name) = option_schema.obj.identifier() {
|
||||
// Generate the nested SQL for this specific target type
|
||||
let mut child_node = node.clone();
|
||||
child_node.schema = std::sync::Arc::clone(option_schema);
|
||||
let (val_sql, _) = self.compile_node(child_node)?;
|
||||
for (disc_val, (idx_opt, target_id_opt)) in options {
|
||||
if let Some(target_id) = target_id_opt {
|
||||
if let Some(target_schema) = self.db.schemas.get(target_id) {
|
||||
let mut child_node = node.clone();
|
||||
child_node.schema = Arc::clone(target_schema);
|
||||
child_node.schema_id = Some(target_id.clone());
|
||||
child_node.is_polymorphic_branch = true;
|
||||
|
||||
case_statements.push(format!(
|
||||
"WHEN {}.{} = '{}' THEN ({})",
|
||||
node.parent_alias, type_col, base_type_name, val_sql
|
||||
));
|
||||
let val_sql =
|
||||
if disc == "kind" && node.parent_type.is_some() && node.parent_type_aliases.is_some() {
|
||||
let aliases_arc = node.parent_type_aliases.as_ref().unwrap();
|
||||
let aliases = aliases_arc.as_ref();
|
||||
let p_type = node.parent_type.unwrap();
|
||||
|
||||
let select_args = self.compile_select_clause(p_type, aliases, child_node.clone())?;
|
||||
|
||||
if select_args.is_empty() {
|
||||
"jsonb_build_object()".to_string()
|
||||
} else {
|
||||
format!("jsonb_build_object({})", select_args.join(", "))
|
||||
}
|
||||
} else {
|
||||
let (sql, _) = self.compile_node(child_node)?;
|
||||
sql
|
||||
};
|
||||
|
||||
case_statements.push(format!(
|
||||
"WHEN {}.{} = '{}' THEN ({})",
|
||||
node.parent_alias, type_col, disc_val, val_sql
|
||||
));
|
||||
}
|
||||
} else if let Some(idx) = idx_opt {
|
||||
if let Some(target_schema) = node
|
||||
.schema
|
||||
.obj
|
||||
.one_of
|
||||
.as_ref()
|
||||
.and_then(|options| options.get(*idx))
|
||||
{
|
||||
let mut child_node = node.clone();
|
||||
child_node.schema = Arc::clone(target_schema);
|
||||
child_node.is_polymorphic_branch = true;
|
||||
|
||||
let val_sql = if disc == "kind" && node.parent_type.is_some() && node.parent_type_aliases.is_some() {
|
||||
let aliases_arc = node.parent_type_aliases.as_ref().unwrap();
|
||||
let aliases = aliases_arc.as_ref();
|
||||
let p_type = node.parent_type.unwrap();
|
||||
|
||||
let select_args = self.compile_select_clause(p_type, aliases, child_node.clone())?;
|
||||
|
||||
if select_args.is_empty() {
|
||||
"jsonb_build_object()".to_string()
|
||||
} else {
|
||||
format!("jsonb_build_object({})", select_args.join(", "))
|
||||
}
|
||||
} else {
|
||||
let (sql, _) = self.compile_node(child_node)?;
|
||||
sql
|
||||
};
|
||||
|
||||
case_statements.push(format!(
|
||||
"WHEN {}.{} = '{}' THEN ({})",
|
||||
node.parent_alias, type_col, disc_val, val_sql
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if case_statements.is_empty() {
|
||||
return Ok(("NULL".to_string(), "string".to_string()));
|
||||
}
|
||||
@ -399,24 +415,33 @@ impl<'a> Compiler<'a> {
|
||||
) -> Result<Vec<String>, String> {
|
||||
let mut select_args = Vec::new();
|
||||
let grouped_fields = r#type.grouped_fields.as_ref().and_then(|v| v.as_object());
|
||||
let merged_props = node.schema.obj.compiled_properties.get().unwrap();
|
||||
let mut sorted_keys: Vec<&String> = merged_props.keys().collect();
|
||||
sorted_keys.sort();
|
||||
|
||||
for prop_key in sorted_keys {
|
||||
let prop_schema = &merged_props[prop_key];
|
||||
let default_props = std::collections::BTreeMap::new();
|
||||
let merged_props = node
|
||||
.schema
|
||||
.obj
|
||||
.compiled_properties
|
||||
.get()
|
||||
.unwrap_or(&default_props);
|
||||
|
||||
for (prop_key, prop_schema) in merged_props {
|
||||
let is_object_or_array = match &prop_schema.obj.type_ {
|
||||
Some(crate::database::schema::SchemaTypeOrArray::Single(s)) => {
|
||||
Some(crate::database::object::SchemaTypeOrArray::Single(s)) => {
|
||||
s == "object" || s == "array"
|
||||
}
|
||||
Some(crate::database::schema::SchemaTypeOrArray::Multiple(v)) => {
|
||||
Some(crate::database::object::SchemaTypeOrArray::Multiple(v)) => {
|
||||
v.contains(&"object".to_string()) || v.contains(&"array".to_string())
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
|
||||
let is_primitive = prop_schema.obj.r#ref.is_none()
|
||||
let is_custom_object_pointer = match &prop_schema.obj.type_ {
|
||||
Some(crate::database::object::SchemaTypeOrArray::Single(s)) => {
|
||||
!crate::database::object::is_primitive_type(s)
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
|
||||
let is_primitive = !is_custom_object_pointer
|
||||
&& !is_object_or_array
|
||||
&& prop_schema.obj.family.is_none()
|
||||
&& prop_schema.obj.one_of.is_none();
|
||||
@ -461,6 +486,8 @@ impl<'a> Compiler<'a> {
|
||||
} else {
|
||||
format!("{}/{}", node.ast_path, prop_key)
|
||||
},
|
||||
is_polymorphic_branch: false,
|
||||
schema_id: None,
|
||||
};
|
||||
|
||||
let (val_sql, val_type) = self.compile_node(child_node)?;
|
||||
@ -500,6 +527,8 @@ impl<'a> Compiler<'a> {
|
||||
|
||||
self.compile_filter_conditions(r#type, type_aliases, &node, &base_alias, &mut where_clauses);
|
||||
self.compile_polymorphic_bounds(r#type, type_aliases, &node, &mut where_clauses);
|
||||
|
||||
let start_len = where_clauses.len();
|
||||
self.compile_relation_conditions(
|
||||
r#type,
|
||||
type_aliases,
|
||||
@ -508,6 +537,14 @@ impl<'a> Compiler<'a> {
|
||||
&mut where_clauses,
|
||||
)?;
|
||||
|
||||
if node.is_polymorphic_branch && where_clauses.len() == start_len {
|
||||
if let Some(parent_aliases) = &node.parent_type_aliases {
|
||||
if let Some(outer_entity_alias) = parent_aliases.get("entity") {
|
||||
where_clauses.push(format!("{}.id = {}.id", entity_alias, outer_entity_alias));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(where_clauses)
|
||||
}
|
||||
|
||||
@ -532,8 +569,12 @@ impl<'a> Compiler<'a> {
|
||||
.unwrap_or(family_target)
|
||||
.to_string(),
|
||||
);
|
||||
} else if let Some(lookup_key) = prop_schema.obj.identifier() {
|
||||
bound_type_name = Some(lookup_key);
|
||||
} else if let Some(crate::database::object::SchemaTypeOrArray::Single(t)) =
|
||||
&prop_schema.obj.type_
|
||||
{
|
||||
if !crate::database::object::is_primitive_type(t) {
|
||||
bound_type_name = Some(t.split('.').next_back().unwrap_or(t).to_string());
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(type_name) = bound_type_name {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -42,27 +42,32 @@ fn test_library_api() {
|
||||
"types": [
|
||||
{
|
||||
"name": "source_schema",
|
||||
"variations": ["source_schema"],
|
||||
"hierarchy": ["source_schema", "entity"],
|
||||
"schemas": [{
|
||||
"$id": "source_schema",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": { "type": "string" },
|
||||
"target": { "$ref": "target_schema" }
|
||||
},
|
||||
"required": ["name"]
|
||||
}]
|
||||
"schemas": {
|
||||
"source_schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": { "type": "string" },
|
||||
"name": { "type": "string" },
|
||||
"target": { "type": "target_schema" }
|
||||
},
|
||||
"required": ["name"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "target_schema",
|
||||
"variations": ["target_schema"],
|
||||
"hierarchy": ["target_schema", "entity"],
|
||||
"schemas": [{
|
||||
"$id": "target_schema",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"value": { "type": "number" }
|
||||
"schemas": {
|
||||
"target_schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"value": { "type": "number" }
|
||||
}
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
@ -84,17 +89,17 @@ fn test_library_api() {
|
||||
"type": "drop",
|
||||
"response": {
|
||||
"source_schema": {
|
||||
"$id": "source_schema",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": { "type": "string" },
|
||||
"name": { "type": "string" },
|
||||
"target": {
|
||||
"$ref": "target_schema",
|
||||
"type": "target_schema",
|
||||
"compiledProperties": ["value"]
|
||||
}
|
||||
},
|
||||
"required": ["name"],
|
||||
"compiledProperties": ["name", "target"],
|
||||
"compiledProperties": ["name", "target", "type"],
|
||||
"compiledEdges": {
|
||||
"target": {
|
||||
"constraint": "fk_test_target",
|
||||
@ -102,8 +107,11 @@ fn test_library_api() {
|
||||
}
|
||||
}
|
||||
},
|
||||
"source_schema/target": {
|
||||
"type": "target_schema",
|
||||
"compiledProperties": ["value"]
|
||||
},
|
||||
"target_schema": {
|
||||
"$id": "target_schema",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"value": { "type": "number" }
|
||||
@ -115,7 +123,7 @@ fn test_library_api() {
|
||||
);
|
||||
|
||||
// 4. Validate Happy Path
|
||||
let happy_drop = jspg_validate("source_schema", JsonB(json!({"name": "Neo"})));
|
||||
let happy_drop = jspg_validate("source_schema", JsonB(json!({"type": "source_schema", "name": "Neo"})));
|
||||
assert_eq!(
|
||||
happy_drop.0,
|
||||
json!({
|
||||
@ -125,7 +133,7 @@ fn test_library_api() {
|
||||
);
|
||||
|
||||
// 5. Validate Unhappy Path
|
||||
let unhappy_drop = jspg_validate("source_schema", JsonB(json!({"wrong": "data"})));
|
||||
let unhappy_drop = jspg_validate("source_schema", JsonB(json!({"type": "source_schema", "wrong": "data"})));
|
||||
assert_eq!(
|
||||
unhappy_drop.0,
|
||||
json!({
|
||||
|
||||
@ -19,6 +19,16 @@ impl Expect {
|
||||
.map(|e| serde_json::to_value(e).unwrap())
|
||||
.collect();
|
||||
|
||||
if expected_errors.len() != actual_values.len() {
|
||||
return Err(format!(
|
||||
"Expected {} errors, but got {}.\nExpected subset: {:?}\nActual full errors: {:?}",
|
||||
expected_errors.len(),
|
||||
actual_values.len(),
|
||||
expected_errors,
|
||||
drop.errors
|
||||
));
|
||||
}
|
||||
|
||||
for (i, expected_val) in expected_errors.iter().enumerate() {
|
||||
let mut matched = false;
|
||||
|
||||
|
||||
@ -93,9 +93,12 @@ impl<'a> ValidationContext<'a> {
|
||||
if i < len {
|
||||
if let Some(child_instance) = arr.get(i) {
|
||||
let mut item_path = self.join_path(&i.to_string());
|
||||
if let Some(obj) = child_instance.as_object() {
|
||||
if let Some(id_str) = obj.get("id").and_then(|v| v.as_str()) {
|
||||
item_path = self.join_path(id_str);
|
||||
let is_topological = sub_schema.obj.requires_uuid_path(self.db);
|
||||
if is_topological {
|
||||
if let Some(obj) = child_instance.as_object() {
|
||||
if let Some(id_str) = obj.get("id").and_then(|v| v.as_str()) {
|
||||
item_path = self.join_path(id_str);
|
||||
}
|
||||
}
|
||||
}
|
||||
let derived = self.derive(
|
||||
@ -116,12 +119,15 @@ impl<'a> ValidationContext<'a> {
|
||||
}
|
||||
|
||||
if let Some(ref items_schema) = self.schema.items {
|
||||
let is_topological = items_schema.obj.requires_uuid_path(self.db);
|
||||
for i in validation_index..len {
|
||||
if let Some(child_instance) = arr.get(i) {
|
||||
let mut item_path = self.join_path(&i.to_string());
|
||||
if let Some(obj) = child_instance.as_object() {
|
||||
if let Some(id_str) = obj.get("id").and_then(|v| v.as_str()) {
|
||||
item_path = self.join_path(id_str);
|
||||
if is_topological {
|
||||
if let Some(obj) = child_instance.as_object() {
|
||||
if let Some(id_str) = obj.get("id").and_then(|v| v.as_str()) {
|
||||
item_path = self.join_path(id_str);
|
||||
}
|
||||
}
|
||||
}
|
||||
let derived = self.derive(
|
||||
|
||||
45
src/validator/rules/cases.rs
Normal file
45
src/validator/rules/cases.rs
Normal file
@ -0,0 +1,45 @@
|
||||
use crate::validator::context::ValidationContext;
|
||||
use crate::validator::error::ValidationError;
|
||||
use crate::validator::result::ValidationResult;
|
||||
|
||||
impl<'a> ValidationContext<'a> {
|
||||
pub(crate) fn validate_cases(
|
||||
&self,
|
||||
result: &mut ValidationResult,
|
||||
) -> Result<bool, ValidationError> {
|
||||
if let Some(cases) = &self.schema.cases {
|
||||
for case in cases {
|
||||
if let Some(ref when_schema) = case.when {
|
||||
let derived_when = self.derive_for_schema(when_schema, true);
|
||||
let when_res = derived_when.validate()?;
|
||||
|
||||
// Evaluates all cases independently.
|
||||
if when_res.is_valid() {
|
||||
result
|
||||
.evaluated_keys
|
||||
.extend(when_res.evaluated_keys.clone());
|
||||
result
|
||||
.evaluated_indices
|
||||
.extend(when_res.evaluated_indices.clone());
|
||||
|
||||
if let Some(ref then_schema) = case.then {
|
||||
let derived_then = self.derive_for_schema(then_schema, true);
|
||||
result.merge(derived_then.validate()?);
|
||||
}
|
||||
} else {
|
||||
if let Some(ref else_schema) = case.else_ {
|
||||
let derived_else = self.derive_for_schema(else_schema, true);
|
||||
result.merge(derived_else.validate()?);
|
||||
}
|
||||
}
|
||||
} else if let Some(ref else_schema) = case.else_ {
|
||||
// A rule with a missing `when` fires the `else` indiscriminately
|
||||
let derived_else = self.derive_for_schema(else_schema, true);
|
||||
result.merge(derived_else.validate()?);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
@ -1,92 +0,0 @@
|
||||
use crate::validator::context::ValidationContext;
|
||||
use crate::validator::error::ValidationError;
|
||||
use crate::validator::result::ValidationResult;
|
||||
|
||||
impl<'a> ValidationContext<'a> {
|
||||
pub(crate) fn validate_combinators(
|
||||
&self,
|
||||
result: &mut ValidationResult,
|
||||
) -> Result<bool, ValidationError> {
|
||||
if let Some(ref all_of) = self.schema.all_of {
|
||||
for sub in all_of {
|
||||
let derived = self.derive_for_schema(sub, true);
|
||||
let res = derived.validate()?;
|
||||
result.merge(res);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(ref one_of) = self.schema.one_of {
|
||||
let mut passed_candidates: Vec<(Option<String>, usize, ValidationResult)> = Vec::new();
|
||||
|
||||
for sub in one_of {
|
||||
let derived = self.derive_for_schema(sub, true);
|
||||
let sub_res = derived.validate()?;
|
||||
if sub_res.is_valid() {
|
||||
let child_id = sub.id.clone();
|
||||
let depth = child_id
|
||||
.as_ref()
|
||||
.and_then(|id| self.db.depths.get(id).copied())
|
||||
.unwrap_or(0);
|
||||
passed_candidates.push((child_id, depth, sub_res));
|
||||
}
|
||||
}
|
||||
|
||||
if passed_candidates.len() == 1 {
|
||||
result.merge(passed_candidates.pop().unwrap().2);
|
||||
} else if passed_candidates.is_empty() {
|
||||
result.errors.push(ValidationError {
|
||||
code: "NO_ONEOF_MATCH".to_string(),
|
||||
message: "Matches none of oneOf schemas".to_string(),
|
||||
path: self.path.to_string(),
|
||||
});
|
||||
} else {
|
||||
// Apply depth heuristic tie-breaker
|
||||
let mut best_depth: Option<usize> = None;
|
||||
let mut ambiguous = false;
|
||||
let mut best_res = None;
|
||||
|
||||
for (_, depth, res) in passed_candidates.into_iter() {
|
||||
if let Some(current_best) = best_depth {
|
||||
if depth > current_best {
|
||||
best_depth = Some(depth);
|
||||
best_res = Some(res);
|
||||
ambiguous = false;
|
||||
} else if depth == current_best {
|
||||
ambiguous = true;
|
||||
}
|
||||
} else {
|
||||
best_depth = Some(depth);
|
||||
best_res = Some(res);
|
||||
}
|
||||
}
|
||||
|
||||
if !ambiguous {
|
||||
if let Some(res) = best_res {
|
||||
result.merge(res);
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
|
||||
result.errors.push(ValidationError {
|
||||
code: "AMBIGUOUS_ONEOF_MATCH".to_string(),
|
||||
message: "Matches multiple oneOf schemas without a clear depth winner".to_string(),
|
||||
path: self.path.to_string(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(ref not_schema) = self.schema.not {
|
||||
let derived = self.derive_for_schema(not_schema, true);
|
||||
let sub_res = derived.validate()?;
|
||||
if sub_res.is_valid() {
|
||||
result.errors.push(ValidationError {
|
||||
code: "NOT_VIOLATED".to_string(),
|
||||
message: "Matched 'not' schema".to_string(),
|
||||
path: self.path.to_string(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
@ -13,7 +13,7 @@ impl<'a> ValidationContext<'a> {
|
||||
|
||||
if let Some(ref type_) = self.schema.type_ {
|
||||
match type_ {
|
||||
crate::database::schema::SchemaTypeOrArray::Single(t) => {
|
||||
crate::database::object::SchemaTypeOrArray::Single(t) => {
|
||||
if !Validator::check_type(t, current) {
|
||||
result.errors.push(ValidationError {
|
||||
code: "INVALID_TYPE".to_string(),
|
||||
@ -22,7 +22,7 @@ impl<'a> ValidationContext<'a> {
|
||||
});
|
||||
}
|
||||
}
|
||||
crate::database::schema::SchemaTypeOrArray::Multiple(types) => {
|
||||
crate::database::object::SchemaTypeOrArray::Multiple(types) => {
|
||||
let mut valid = false;
|
||||
for t in types {
|
||||
if Validator::check_type(t, current) {
|
||||
|
||||
@ -3,30 +3,14 @@ use crate::validator::error::ValidationError;
|
||||
use crate::validator::result::ValidationResult;
|
||||
|
||||
impl<'a> ValidationContext<'a> {
|
||||
pub(crate) fn validate_conditionals(
|
||||
&self,
|
||||
result: &mut ValidationResult,
|
||||
) -> Result<bool, ValidationError> {
|
||||
if let Some(ref if_schema) = self.schema.if_ {
|
||||
let derived_if = self.derive_for_schema(if_schema, true);
|
||||
let if_res = derived_if.validate()?;
|
||||
|
||||
result.evaluated_keys.extend(if_res.evaluated_keys.clone());
|
||||
result
|
||||
.evaluated_indices
|
||||
.extend(if_res.evaluated_indices.clone());
|
||||
|
||||
if if_res.is_valid() {
|
||||
if let Some(ref then_schema) = self.schema.then_ {
|
||||
let derived_then = self.derive_for_schema(then_schema, true);
|
||||
result.merge(derived_then.validate()?);
|
||||
}
|
||||
} else if let Some(ref else_schema) = self.schema.else_ {
|
||||
let derived_else = self.derive_for_schema(else_schema, true);
|
||||
result.merge(derived_else.validate()?);
|
||||
pub(crate) fn validate_extensible(&self, result: &mut ValidationResult) -> Result<bool, ValidationError> {
|
||||
if self.extensible {
|
||||
if let Some(obj) = self.instance.as_object() {
|
||||
result.evaluated_keys.extend(obj.keys().cloned());
|
||||
} else if let Some(arr) = self.instance.as_array() {
|
||||
result.evaluated_indices.extend(0..arr.len());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
@ -40,6 +24,9 @@ impl<'a> ValidationContext<'a> {
|
||||
|
||||
if let Some(obj) = self.instance.as_object() {
|
||||
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) {
|
||||
result.errors.push(ValidationError {
|
||||
code: "STRICT_PROPERTY_VIOLATION".to_string(),
|
||||
@ -10,7 +10,7 @@ impl<'a> ValidationContext<'a> {
|
||||
let current = self.instance;
|
||||
if let Some(compiled_fmt) = self.schema.compiled_format.get() {
|
||||
match compiled_fmt {
|
||||
crate::database::schema::CompiledFormat::Func(f) => {
|
||||
crate::database::object::CompiledFormat::Func(f) => {
|
||||
let should = if let Some(s) = current.as_str() {
|
||||
!s.is_empty()
|
||||
} else {
|
||||
@ -24,7 +24,7 @@ impl<'a> ValidationContext<'a> {
|
||||
});
|
||||
}
|
||||
}
|
||||
crate::database::schema::CompiledFormat::Regex(re) => {
|
||||
crate::database::object::CompiledFormat::Regex(re) => {
|
||||
if let Some(s) = current.as_str()
|
||||
&& !re.is_match(s)
|
||||
{
|
||||
|
||||
@ -3,10 +3,11 @@ use crate::validator::error::ValidationError;
|
||||
use crate::validator::result::ValidationResult;
|
||||
|
||||
pub mod array;
|
||||
pub mod combinators;
|
||||
pub mod conditionals;
|
||||
pub mod cases;
|
||||
pub mod core;
|
||||
pub mod extensible;
|
||||
pub mod format;
|
||||
pub mod not;
|
||||
pub mod numeric;
|
||||
pub mod object;
|
||||
pub mod polymorphism;
|
||||
@ -27,7 +28,7 @@ impl<'a> ValidationContext<'a> {
|
||||
if !self.validate_family(&mut result)? {
|
||||
return Ok(result);
|
||||
}
|
||||
if !self.validate_refs(&mut result)? {
|
||||
if !self.validate_type_inheritance(&mut result)? {
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
@ -42,8 +43,11 @@ impl<'a> ValidationContext<'a> {
|
||||
self.validate_array(&mut result)?;
|
||||
|
||||
// Multipliers & Conditionals
|
||||
self.validate_combinators(&mut result)?;
|
||||
self.validate_conditionals(&mut result)?;
|
||||
if !self.validate_one_of(&mut result)? {
|
||||
return Ok(result);
|
||||
}
|
||||
self.validate_not(&mut result)?;
|
||||
self.validate_cases(&mut result)?;
|
||||
|
||||
// State Tracking
|
||||
self.validate_extensible(&mut result)?;
|
||||
@ -77,15 +81,4 @@ impl<'a> ValidationContext<'a> {
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_extensible(&self, result: &mut ValidationResult) -> Result<bool, ValidationError> {
|
||||
if self.extensible {
|
||||
if let Some(obj) = self.instance.as_object() {
|
||||
result.evaluated_keys.extend(obj.keys().cloned());
|
||||
} else if let Some(arr) = self.instance.as_array() {
|
||||
result.evaluated_indices.extend(0..arr.len());
|
||||
}
|
||||
}
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
|
||||
24
src/validator/rules/not.rs
Normal file
24
src/validator/rules/not.rs
Normal file
@ -0,0 +1,24 @@
|
||||
use crate::validator::context::ValidationContext;
|
||||
use crate::validator::error::ValidationError;
|
||||
use crate::validator::result::ValidationResult;
|
||||
|
||||
impl<'a> ValidationContext<'a> {
|
||||
pub(crate) fn validate_not(
|
||||
&self,
|
||||
result: &mut ValidationResult,
|
||||
) -> Result<bool, ValidationError> {
|
||||
if let Some(ref not_schema) = self.schema.not {
|
||||
let derived = self.derive_for_schema(not_schema, true);
|
||||
let sub_res = derived.validate()?;
|
||||
if sub_res.is_valid() {
|
||||
result.errors.push(ValidationError {
|
||||
code: "NOT_VIOLATED".to_string(),
|
||||
message: "Matched 'not' schema".to_string(),
|
||||
path: self.path.to_string(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
@ -13,34 +13,77 @@ impl<'a> ValidationContext<'a> {
|
||||
) -> Result<bool, ValidationError> {
|
||||
let current = self.instance;
|
||||
if let Some(obj) = current.as_object() {
|
||||
let mut schema_identifier = None;
|
||||
if let Some(crate::database::object::SchemaTypeOrArray::Single(t)) = &self.schema.type_ {
|
||||
if !crate::database::object::is_primitive_type(t) {
|
||||
schema_identifier = Some(t.clone());
|
||||
}
|
||||
}
|
||||
|
||||
// Entity implicit type validation
|
||||
if let Some(schema_identifier) = self.schema.identifier() {
|
||||
// Kick in if the data object has a type field
|
||||
if let Some(type_val) = obj.get("type")
|
||||
&& let Some(type_str) = type_val.as_str()
|
||||
{
|
||||
// Check if the identifier is a global type name
|
||||
if let Some(type_def) = self.db.types.get(&schema_identifier) {
|
||||
// Ensure the instance type is a variation of the global type
|
||||
if type_def.variations.contains(type_str) {
|
||||
// Ensure it passes strict mode
|
||||
result.evaluated_keys.insert("type".to_string());
|
||||
} else {
|
||||
result.errors.push(ValidationError {
|
||||
code: "CONST_VIOLATED".to_string(), // Aligning with original const override errors
|
||||
message: format!(
|
||||
"Type '{}' is not a valid descendant for this entity bound schema",
|
||||
type_str
|
||||
),
|
||||
path: self.join_path("type"),
|
||||
});
|
||||
if let Some(ref schema_identifier_str) = schema_identifier {
|
||||
// We decompose identity string routing inherently
|
||||
let expected_type = schema_identifier_str.split('.').last().unwrap_or(schema_identifier_str);
|
||||
|
||||
// Check if the identifier represents a registered global database entity boundary mathematically
|
||||
if let Some(type_def) = self.db.types.get(expected_type) {
|
||||
if let Some(type_val) = obj.get("type") {
|
||||
if let Some(type_str) = type_val.as_str() {
|
||||
if type_def.variations.contains(type_str) {
|
||||
// The instance is validly declaring a known structural descent
|
||||
result.evaluated_keys.insert("type".to_string());
|
||||
} else {
|
||||
result.errors.push(ValidationError {
|
||||
code: "CONST_VIOLATED".to_string(), // Aligning with original const override errors natively
|
||||
message: format!(
|
||||
"Type '{}' is not a valid descendant for this entity bound schema",
|
||||
type_str
|
||||
),
|
||||
path: self.join_path("type"),
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Ad-Hoc schemas natively use strict schema discriminator strings instead of variation inheritance
|
||||
if type_str == schema_identifier.as_str() {
|
||||
result.evaluated_keys.insert("type".to_string());
|
||||
// Because it's a global entity target, the payload must structurally provide a discriminator natively
|
||||
result.errors.push(ValidationError {
|
||||
code: "MISSING_TYPE".to_string(),
|
||||
message: format!("Schema mechanically requires type discrimination '{}'", expected_type),
|
||||
path: self.path.clone(), // Empty boundary
|
||||
});
|
||||
}
|
||||
|
||||
// If the target mathematically declares a horizontal structural STI variation natively
|
||||
if schema_identifier_str.contains('.') {
|
||||
if obj.get("kind").is_none() {
|
||||
result.errors.push(ValidationError {
|
||||
code: "MISSING_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 {
|
||||
// If it isn't registered globally, it might be a nested Ad-Hoc candidate running via O(1) union routers.
|
||||
// Because they lack manual type property descriptors, we natively shield "type" and "kind" keys from
|
||||
// triggering additionalProperty violations natively IF they precisely correspond to their fast-path boundaries
|
||||
if let Some(type_val) = obj.get("type") {
|
||||
if let Some(type_str) = type_val.as_str() {
|
||||
if type_str == expected_type {
|
||||
result.evaluated_keys.insert("type".to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(kind_val) = obj.get("kind") {
|
||||
if let Some((kind_str, _)) = schema_identifier_str.rsplit_once('.') {
|
||||
if let Some(actual_kind) = kind_val.as_str() {
|
||||
if actual_kind == kind_str {
|
||||
result.evaluated_keys.insert("kind".to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -67,11 +110,19 @@ impl<'a> ValidationContext<'a> {
|
||||
if let Some(ref req) = self.schema.required {
|
||||
for field in req {
|
||||
if !obj.contains_key(field) {
|
||||
result.errors.push(ValidationError {
|
||||
code: "REQUIRED_FIELD_MISSING".to_string(),
|
||||
message: format!("Missing {}", field),
|
||||
path: self.join_path(field),
|
||||
});
|
||||
if field == "type" {
|
||||
result.errors.push(ValidationError {
|
||||
code: "MISSING_TYPE".to_string(),
|
||||
message: "Missing type discriminator".to_string(),
|
||||
path: self.join_path(field),
|
||||
});
|
||||
} else {
|
||||
result.errors.push(ValidationError {
|
||||
code: "REQUIRED_FIELD_MISSING".to_string(),
|
||||
message: format!("Missing {}", field),
|
||||
path: self.join_path(field),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -80,7 +131,7 @@ impl<'a> ValidationContext<'a> {
|
||||
for (prop, dep) in deps {
|
||||
if obj.contains_key(prop) {
|
||||
match dep {
|
||||
crate::database::schema::Dependency::Props(required_props) => {
|
||||
crate::database::object::Dependency::Props(required_props) => {
|
||||
for req_prop in required_props {
|
||||
if !obj.contains_key(req_prop) {
|
||||
result.errors.push(ValidationError {
|
||||
@ -91,7 +142,7 @@ impl<'a> ValidationContext<'a> {
|
||||
}
|
||||
}
|
||||
}
|
||||
crate::database::schema::Dependency::Schema(dep_schema) => {
|
||||
crate::database::object::Dependency::Schema(dep_schema) => {
|
||||
let derived = self.derive_for_schema(dep_schema, false);
|
||||
let dep_res = derived.validate()?;
|
||||
result.evaluated_keys.extend(dep_res.evaluated_keys.clone());
|
||||
@ -110,7 +161,10 @@ impl<'a> ValidationContext<'a> {
|
||||
|
||||
if let Some(child_instance) = obj.get(key) {
|
||||
let new_path = self.join_path(key);
|
||||
let is_ref = sub_schema.r#ref.is_some();
|
||||
let is_ref = match &sub_schema.type_ {
|
||||
Some(crate::database::object::SchemaTypeOrArray::Single(t)) => !crate::database::object::is_primitive_type(t),
|
||||
_ => false,
|
||||
};
|
||||
let next_extensible = if is_ref { false } else { self.extensible };
|
||||
|
||||
let derived = self.derive(
|
||||
@ -121,21 +175,9 @@ impl<'a> ValidationContext<'a> {
|
||||
next_extensible,
|
||||
false,
|
||||
);
|
||||
let mut item_res = derived.validate()?;
|
||||
let item_res = derived.validate()?;
|
||||
|
||||
|
||||
// Entity Bound Implicit Type Interception
|
||||
if key == "type"
|
||||
&& let Some(schema_bound) = sub_schema.identifier()
|
||||
{
|
||||
if let Some(type_def) = self.db.types.get(&schema_bound)
|
||||
&& let Some(instance_type) = child_instance.as_str()
|
||||
&& type_def.variations.contains(instance_type)
|
||||
{
|
||||
item_res
|
||||
.errors
|
||||
.retain(|e| e.code != "CONST_VIOLATED" && e.code != "ENUM_VIOLATED");
|
||||
}
|
||||
}
|
||||
|
||||
result.merge(item_res);
|
||||
result.evaluated_keys.insert(key.to_string());
|
||||
@ -148,7 +190,10 @@ impl<'a> ValidationContext<'a> {
|
||||
for (key, child_instance) in obj {
|
||||
if compiled_re.0.is_match(key) {
|
||||
let new_path = self.join_path(key);
|
||||
let is_ref = sub_schema.r#ref.is_some();
|
||||
let is_ref = match &sub_schema.type_ {
|
||||
Some(crate::database::object::SchemaTypeOrArray::Single(t)) => !crate::database::object::is_primitive_type(t),
|
||||
_ => false,
|
||||
};
|
||||
let next_extensible = if is_ref { false } else { self.extensible };
|
||||
|
||||
let derived = self.derive(
|
||||
@ -187,7 +232,10 @@ impl<'a> ValidationContext<'a> {
|
||||
|
||||
if !locally_matched {
|
||||
let new_path = self.join_path(key);
|
||||
let is_ref = additional_schema.r#ref.is_some();
|
||||
let is_ref = match &additional_schema.type_ {
|
||||
Some(crate::database::object::SchemaTypeOrArray::Single(t)) => !crate::database::object::is_primitive_type(t),
|
||||
_ => false,
|
||||
};
|
||||
let next_extensible = if is_ref { false } else { self.extensible };
|
||||
|
||||
let derived = self.derive(
|
||||
|
||||
@ -13,9 +13,8 @@ impl<'a> ValidationContext<'a> {
|
||||
|| self.schema.required.is_some()
|
||||
|| self.schema.additional_properties.is_some()
|
||||
|| self.schema.items.is_some()
|
||||
|| self.schema.r#ref.is_some()
|
||||
|| self.schema.cases.is_some()
|
||||
|| self.schema.one_of.is_some()
|
||||
|| self.schema.all_of.is_some()
|
||||
|| self.schema.enum_.is_some()
|
||||
|| self.schema.const_.is_some();
|
||||
|
||||
@ -25,102 +24,204 @@ impl<'a> ValidationContext<'a> {
|
||||
message: "$family must be used exclusively without other constraints".to_string(),
|
||||
path: self.path.to_string(),
|
||||
});
|
||||
// Short-circuit: the schema formulation is broken
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(family_target) = &self.schema.family {
|
||||
if let Some(descendants) = self.db.descendants.get(family_target) {
|
||||
// Validate against all descendants simulating strict oneOf logic
|
||||
let mut passed_candidates: Vec<(String, usize, ValidationResult)> = Vec::new();
|
||||
|
||||
// The target itself is also an implicitly valid candidate
|
||||
let mut all_targets = vec![family_target.clone()];
|
||||
all_targets.extend(descendants.clone());
|
||||
|
||||
for child_id in &all_targets {
|
||||
if let Some(child_schema) = self.db.schemas.get(child_id) {
|
||||
let derived = self.derive(
|
||||
child_schema,
|
||||
self.instance,
|
||||
&self.path,
|
||||
self.overrides.clone(),
|
||||
self.extensible,
|
||||
self.reporter, // Inherit parent reporter flag, do not bypass strictness!
|
||||
);
|
||||
|
||||
// Explicitly run validate_scoped to accurately test candidates with strictness checks enabled
|
||||
let res = derived.validate_scoped()?;
|
||||
|
||||
if res.is_valid() {
|
||||
let depth = self.db.depths.get(child_id).copied().unwrap_or(0);
|
||||
passed_candidates.push((child_id.clone(), depth, res));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if passed_candidates.len() == 1 {
|
||||
result.merge(passed_candidates.pop().unwrap().2);
|
||||
} else if passed_candidates.is_empty() {
|
||||
result.errors.push(ValidationError {
|
||||
code: "NO_FAMILY_MATCH".to_string(),
|
||||
message: format!(
|
||||
"Payload did not match any descendants of family '{}'",
|
||||
family_target
|
||||
),
|
||||
path: self.path.to_string(),
|
||||
});
|
||||
} else {
|
||||
// Apply depth heuristic tie-breaker
|
||||
let mut best_depth: Option<usize> = None;
|
||||
let mut ambiguous = false;
|
||||
let mut best_res = None;
|
||||
|
||||
for (_, depth, res) in passed_candidates.into_iter() {
|
||||
if let Some(current_best) = best_depth {
|
||||
if depth > current_best {
|
||||
best_depth = Some(depth);
|
||||
best_res = Some(res);
|
||||
ambiguous = false; // Broke the tie
|
||||
} else if depth == current_best {
|
||||
ambiguous = true; // Tie at the highest level
|
||||
}
|
||||
} else {
|
||||
best_depth = Some(depth);
|
||||
best_res = Some(res);
|
||||
}
|
||||
}
|
||||
|
||||
if !ambiguous {
|
||||
if let Some(res) = best_res {
|
||||
result.merge(res);
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
|
||||
result.errors.push(ValidationError {
|
||||
code: "AMBIGUOUS_FAMILY_MATCH".to_string(),
|
||||
message: format!(
|
||||
"Payload matched multiple descendants of family '{}' without a clear depth winner",
|
||||
family_target
|
||||
),
|
||||
path: self.path.to_string(),
|
||||
});
|
||||
}
|
||||
if self.schema.family.is_some() {
|
||||
if let Some(options) = self.schema.compiled_options.get() {
|
||||
return self.execute_polymorph(options, result);
|
||||
} else {
|
||||
result.errors.push(ValidationError {
|
||||
code: "UNCOMPILED_FAMILY".to_string(),
|
||||
message: "Encountered family block that could not be mapped to deterministic options during db schema compilation.".to_string(),
|
||||
path: self.path.to_string(),
|
||||
});
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
pub(crate) fn validate_refs(
|
||||
pub(crate) fn validate_one_of(
|
||||
&self,
|
||||
result: &mut ValidationResult,
|
||||
) -> Result<bool, ValidationError> {
|
||||
// 1. Core $ref logic relies on the fast O(1) map to allow cycles and proper nesting
|
||||
if let Some(ref_str) = &self.schema.r#ref {
|
||||
if let Some(global_schema) = self.db.schemas.get(ref_str) {
|
||||
if self.schema.one_of.is_some() {
|
||||
if let Some(options) = self.schema.compiled_options.get() {
|
||||
return self.execute_polymorph(options, result);
|
||||
} else {
|
||||
result.errors.push(ValidationError {
|
||||
code: "UNCOMPILED_ONEOF".to_string(),
|
||||
message: "Encountered oneOf block that could not be mapped to deterministic compiled options natively.".to_string(),
|
||||
path: self.path.to_string(),
|
||||
});
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
pub(crate) fn execute_polymorph(
|
||||
&self,
|
||||
options: &std::collections::BTreeMap<String, (Option<usize>, Option<String>)>,
|
||||
result: &mut ValidationResult,
|
||||
) -> Result<bool, ValidationError> {
|
||||
// 1. O(1) Fast-Path Router & Extractor
|
||||
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((idx_opt, target_id_opt)) = options.get(&val) {
|
||||
if let Some(target_id) = target_id_opt {
|
||||
if let Some(target_schema) = self.db.schemas.get(target_id) {
|
||||
let derived = self.derive_for_schema(target_schema.as_ref(), false);
|
||||
let sub_res = derived.validate()?;
|
||||
let is_valid = sub_res.is_valid();
|
||||
result.merge(sub_res);
|
||||
return Ok(is_valid);
|
||||
} else {
|
||||
result.errors.push(ValidationError {
|
||||
code: "MISSING_COMPILED_SCHEMA".to_string(),
|
||||
message: format!(
|
||||
"Polymorphic router target '{}' does not exist in the database schemas map",
|
||||
target_id
|
||||
),
|
||||
path: self.path.to_string(),
|
||||
});
|
||||
return Ok(false);
|
||||
}
|
||||
} else if let Some(idx) = idx_opt {
|
||||
if let Some(target_schema) = self
|
||||
.schema
|
||||
.one_of
|
||||
.as_ref()
|
||||
.and_then(|options| options.get(*idx))
|
||||
{
|
||||
let derived = self.derive_for_schema(target_schema.as_ref(), false);
|
||||
let sub_res = derived.validate()?;
|
||||
let is_valid = sub_res.is_valid();
|
||||
result.merge(sub_res);
|
||||
return Ok(is_valid);
|
||||
} else {
|
||||
result.errors.push(ValidationError {
|
||||
code: "MISSING_COMPILED_SCHEMA".to_string(),
|
||||
message: format!(
|
||||
"Polymorphic index target '{}' does not exist in the local oneOf array",
|
||||
idx
|
||||
),
|
||||
path: self.path.to_string(),
|
||||
});
|
||||
return Ok(false);
|
||||
}
|
||||
} else {
|
||||
return Ok(false);
|
||||
}
|
||||
} else {
|
||||
let disc_msg = if let Some(d) = self.schema.compiled_discriminator.get() {
|
||||
format!("discriminator {}='{}'", d, val)
|
||||
} else {
|
||||
format!("structural JSON base primitive '{}'", val)
|
||||
};
|
||||
result.errors.push(ValidationError {
|
||||
code: if self.schema.family.is_some() {
|
||||
"NO_FAMILY_MATCH".to_string()
|
||||
} else {
|
||||
"NO_ONEOF_MATCH".to_string()
|
||||
},
|
||||
message: format!(
|
||||
"Payload matched no candidate boundaries based on its {}",
|
||||
disc_msg
|
||||
),
|
||||
path: self.path.to_string(),
|
||||
});
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn validate_type_inheritance(
|
||||
&self,
|
||||
result: &mut ValidationResult,
|
||||
) -> Result<bool, ValidationError> {
|
||||
// Core inheritance logic replaces legacy routing
|
||||
let payload_primitive = match self.instance {
|
||||
serde_json::Value::Null => "null",
|
||||
serde_json::Value::Bool(_) => "boolean",
|
||||
serde_json::Value::Number(n) => {
|
||||
if n.is_i64() || n.is_u64() {
|
||||
"integer"
|
||||
} else {
|
||||
"number"
|
||||
}
|
||||
}
|
||||
serde_json::Value::String(_) => "string",
|
||||
serde_json::Value::Array(_) => "array",
|
||||
serde_json::Value::Object(_) => "object",
|
||||
};
|
||||
|
||||
let mut custom_types = Vec::new();
|
||||
match &self.schema.type_ {
|
||||
Some(crate::database::object::SchemaTypeOrArray::Single(t)) => {
|
||||
if !crate::database::object::is_primitive_type(t) {
|
||||
custom_types.push(t.clone());
|
||||
}
|
||||
}
|
||||
Some(crate::database::object::SchemaTypeOrArray::Multiple(arr)) => {
|
||||
if arr.contains(&payload_primitive.to_string())
|
||||
|| (payload_primitive == "integer" && arr.contains(&"number".to_string()))
|
||||
{
|
||||
// It natively matched a primitive in the array options, skip forcing custom proxy fallback
|
||||
} else {
|
||||
for t in arr {
|
||||
if !crate::database::object::is_primitive_type(t) {
|
||||
custom_types.push(t.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
|
||||
for t in custom_types {
|
||||
if let Some(global_schema) = self.db.schemas.get(&t) {
|
||||
let mut new_overrides = self.overrides.clone();
|
||||
if let Some(props) = &self.schema.properties {
|
||||
new_overrides.extend(props.keys().map(|k| k.to_string()));
|
||||
@ -132,16 +233,16 @@ impl<'a> ValidationContext<'a> {
|
||||
&self.path,
|
||||
new_overrides,
|
||||
self.extensible,
|
||||
true,
|
||||
true, // Reporter mode
|
||||
);
|
||||
shadow.root = global_schema;
|
||||
result.merge(shadow.validate()?);
|
||||
} else {
|
||||
result.errors.push(ValidationError {
|
||||
code: "REF_RESOLUTION_FAILED".to_string(),
|
||||
code: "INHERITANCE_RESOLUTION_FAILED".to_string(),
|
||||
message: format!(
|
||||
"Reference pointer to '{}' was not found in schema registry",
|
||||
ref_str
|
||||
"Inherited entity pointer '{}' was not found in schema registry",
|
||||
t
|
||||
),
|
||||
path: self.path.to_string(),
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user