Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 120f488d93 | |||
| 3928174fe7 | |||
| f450f8ab8b | |||
| 8ebf6a69bf | |||
| 3a4c53dc7d | |||
| 69bd726b25 | |||
| c2267b68d8 | |||
| f58d1a32a3 | |||
| d9f4a90225 | |||
| 509194b55f | |||
| 87a845e85a | |||
| 8175b10a97 | |||
| 0b072d66e7 | |||
| 41649766db | |||
| 61a8c5eed7 | |||
| 77af67aef5 | |||
| cd85a8a2c3 | |||
| d3cb72a5e2 | |||
| 57baa389b6 |
34
GEMINI.md
34
GEMINI.md
@ -13,7 +13,7 @@ JSPG operates by deeply integrating the JSON Schema Draft 2020-12 specification
|
||||
1. **Draft 2020-12 Based**: Attempt to adhere to the official JSON Schema Draft 2020-12 specification, while heavily augmenting it for strict structural typing.
|
||||
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.
|
||||
4. **Structural Inheritance**: Support object-oriented schema design via Implicit Keyword Shadowing and virtual `family` references natively mapped to Postgres table constraints.
|
||||
5. **Reactive Beats**: Provide ultra-fast natively generated flat payloads mapping directly to the Dart topological state for dynamic websocket reactivity.
|
||||
|
||||
### Concurrency & Threading ("Immutable Graphs")
|
||||
@ -55,8 +55,8 @@ In Punc, polymorphic targets like explicit tagged unions or STI (Single Table In
|
||||
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.
|
||||
* **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:
|
||||
@ -72,7 +72,7 @@ For example, a schema registered under the exact key `"light.person"` inside the
|
||||
|
||||
* **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.
|
||||
* **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"`).
|
||||
@ -81,24 +81,24 @@ Punc completely abandons the standard JSON Schema `$ref` keyword. Instead, it ov
|
||||
* **Primitive Array Shorthand (Optionality)**: The `type` array syntax is heavily optimized for nullable fields. Defining `"type": ["budget", "null"]` natively builds a nullable strict, generating `Budget? budget;` in Dart. You can freely mix primitives like `["string", "number", "null"]`.
|
||||
* **Strict Array Constraint**: To explicitly prevent mathematically ambiguous Multiple Inheritance, a `type` array is strictly constrained to at most **ONE** Custom Object Pointer. Defining `"type": ["person", "organization"]` will intentionally trigger a fatal database compilation error natively instructing developers to build a proper tagged union (`oneOf`) instead.
|
||||
|
||||
### Polymorphism (`$family` and `oneOf`)
|
||||
### Polymorphism (`family` and `oneOf`)
|
||||
Polymorphism is how an object boundary can dynamically take on entirely different shapes based on the payload provided at runtime. Punc utilizes the static database metadata generated from Postgres (`db.types`) to enforce these boundaries deterministically, rather than relying on ambiguous tree-traversals.
|
||||
|
||||
* **`$family` (Target-Based Polymorphism)**: An explicit Punc compiler macro instructing the engine to resolve dynamic options against the registered database `types` variations or its inner schema registry. It uses the exact physical constraints of the database to build SQL and validation routes.
|
||||
* **`family` (Target-Based Polymorphism)**: An explicit Punc compiler macro instructing the engine to resolve dynamic options against the registered database `types` variations or its inner schema registry. It uses the exact physical constraints of the database to build SQL and validation routes.
|
||||
* **Scenario A: Global Tables (Vertical Routing)**
|
||||
* *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.
|
||||
* *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" }`
|
||||
* *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.
|
||||
* *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.
|
||||
* **`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.
|
||||
@ -187,10 +187,10 @@ The Validator provides strict, schema-driven evaluation for the "Punc" architect
|
||||
JSPG implements specific extensions to the Draft 2020-12 standard to support the Punc architecture's object-oriented needs while heavily optimizing for zero-runtime lookups.
|
||||
|
||||
* **Caching Strategy**: The Validator caches the pre-compiled `Database` registry in memory upon initialization (`jspg_setup`). This registry holds the comprehensive graph of schema boundaries, Types, ENUMs, and Foreign Key relationships, acting as the Single Source of Truth for all validation operations without polling Postgres.
|
||||
* **Discriminator Fast Paths & Extraction**: When executing a polymorphic node (`oneOf` or `$family`), the engine statically analyzes the incoming JSON payload for the literal `type` and `kind` string coordinates. It routes the evaluation specifically to matching candidates in $O(1)$ while returning `MISSING_TYPE` ultimata directly.
|
||||
* **Discriminator Fast Paths & Extraction**: When executing a polymorphic node (`oneOf` or `family`), the engine statically analyzes the incoming JSON payload for the literal `type` and `kind` string coordinates. It routes the evaluation specifically to matching candidates in $O(1)$ while returning `MISSING_TYPE` ultimata directly.
|
||||
* **Missing Type Ultimatum**: If an entity logically requires a discriminator and the JSON payload omits it, JSPG short-circuits branch execution entirely, bubbling a single, perfectly-pathed `MISSING_TYPE` error back to the UI natively to prevent confusing cascading failures.
|
||||
* **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.
|
||||
* **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.
|
||||
|
||||
---
|
||||
|
||||
@ -234,11 +234,11 @@ The Queryer transforms Postgres into a pre-compiled Semantic Query Engine, desig
|
||||
* **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 (`> `, `>=`, `<`, `<=`).
|
||||
* **Array Inclusion**: `{"$in": [values]}`, `{"$nin": [values]}` use native `jsonb_array_elements_text()` bindings to enforce `IN` and `NOT IN` logic without runtime SQL injection risks.
|
||||
* **Array Inclusion**: `{"$of": [values]}`, `{"$nof": [values]}` use native `jsonb_array_elements_text()` bindings to enforce `IN` and `NOT IN` logic without runtime SQL injection risks.
|
||||
* **Text Matching (ILIKE)**: Evaluates `$eq` or `$ne` against string fields containing the `%` character natively into Postgres `ILIKE` and `NOT ILIKE` partial substring matches.
|
||||
* **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.
|
||||
* **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 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.
|
||||
|
||||
|
||||
104
add_test.py
104
add_test.py
@ -1,104 +0,0 @@
|
||||
import json
|
||||
|
||||
def load_json(path):
|
||||
with open(path, 'r') as f:
|
||||
return json.load(f)
|
||||
|
||||
def save_json(path, data):
|
||||
with open(path, 'w') as f:
|
||||
json.dump(data, f, indent=2)
|
||||
|
||||
def add_invoice(data):
|
||||
# Add 'invoice' type
|
||||
types = data[0]['database']['types']
|
||||
|
||||
# Check if invoice already exists
|
||||
if any(t.get('name') == 'invoice' for t in types):
|
||||
return
|
||||
|
||||
types.append({
|
||||
"name": "invoice",
|
||||
"hierarchy": ["invoice", "entity"],
|
||||
"primary_key": ["id"],
|
||||
"field_types": {
|
||||
"id": "uuid",
|
||||
"number": "text",
|
||||
"metadata": "jsonb"
|
||||
},
|
||||
"schemas": {
|
||||
"invoice": {
|
||||
"type": "entity",
|
||||
"properties": {
|
||||
"id": { "type": "string" },
|
||||
"number": { "type": "string" },
|
||||
"metadata": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"internal_note": { "type": "string" },
|
||||
"customer_snapshot": { "type": "entity" },
|
||||
"related_rules": {
|
||||
"type": "array",
|
||||
"items": { "type": "governance_rule" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
def process_merger():
|
||||
data = load_json('fixtures/merger.json')
|
||||
add_invoice(data)
|
||||
|
||||
# Add test
|
||||
data[0]['tests'].append({
|
||||
"name": "Insert invoice with deep jsonb metadata",
|
||||
"schema": "invoice",
|
||||
"payload": {
|
||||
"number": "INV-1001",
|
||||
"metadata": {
|
||||
"internal_note": "Confidential",
|
||||
"customer_snapshot": {
|
||||
"id": "00000000-0000-0000-0000-000000000000",
|
||||
"type": "person",
|
||||
"first_name": "John"
|
||||
},
|
||||
"related_rules": [
|
||||
{
|
||||
"id": "11111111-1111-1111-1111-111111111111"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"expect": {
|
||||
"sql": [
|
||||
[
|
||||
"INSERT INTO agreego.invoice (metadata, number, id) VALUES ($1, $2, gen_random_uuid()) ON CONFLICT (id) DO UPDATE SET metadata = EXCLUDED.metadata, number = EXCLUDED.number RETURNING id, type",
|
||||
{"metadata": {"customer_snapshot": {"first_name": "John", "id": "00000000-0000-0000-0000-000000000000", "type": "person"}, "internal_note": "Confidential", "related_rules": [{"id": "11111111-1111-1111-1111-111111111111"}]}, "number": "INV-1001"}
|
||||
]
|
||||
]
|
||||
}
|
||||
})
|
||||
save_json('fixtures/merger.json', data)
|
||||
|
||||
def process_queryer():
|
||||
data = load_json('fixtures/queryer.json')
|
||||
add_invoice(data)
|
||||
|
||||
data[0]['tests'].append({
|
||||
"name": "Query invoice with complex JSONB metadata field extraction",
|
||||
"schema": "invoice",
|
||||
"query": {
|
||||
"extract": ["id", "number", "metadata"],
|
||||
"conditions": []
|
||||
},
|
||||
"expect": {
|
||||
"sql": "SELECT jsonb_build_object('id', t1.id, 'metadata', t1.metadata, 'number', t1.number) FROM agreego.invoice t1 WHERE (t1.id IS NOT NULL)",
|
||||
"params": {}
|
||||
}
|
||||
})
|
||||
save_json('fixtures/queryer.json', data)
|
||||
|
||||
process_merger()
|
||||
process_queryer()
|
||||
152
append_test.py
152
append_test.py
@ -1,152 +0,0 @@
|
||||
import json
|
||||
|
||||
path = "fixtures/database.json"
|
||||
|
||||
with open(path, "r") as f:
|
||||
data = json.load(f)
|
||||
|
||||
new_test = {
|
||||
"description": "Schema Promotion Accuracy Test - -- One Database to Rule Them All --",
|
||||
"database": {
|
||||
"puncs": [],
|
||||
"enums": [],
|
||||
"relations": [],
|
||||
"types": [
|
||||
{
|
||||
"id": "t1",
|
||||
"type": "type",
|
||||
"name": "person",
|
||||
"module": "core",
|
||||
"source": "person",
|
||||
"hierarchy": ["person"],
|
||||
"variations": ["person", "student"],
|
||||
"schemas": {
|
||||
"full.person": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {"type": "string"},
|
||||
"name": {"type": "string"},
|
||||
"email": {
|
||||
"$family": "email_address"
|
||||
},
|
||||
"generic_bubble": {
|
||||
"type": "some_bubble"
|
||||
},
|
||||
"ad_hoc_bubble": {
|
||||
"type": "some_bubble",
|
||||
"properties": {
|
||||
"extra_inline_feature": {"type": "string"}
|
||||
}
|
||||
},
|
||||
"tags": {
|
||||
"type": "array",
|
||||
"items": {"type": "string"}
|
||||
},
|
||||
"standard_relations": {
|
||||
"type": "array",
|
||||
"items": {"type": "contact"}
|
||||
},
|
||||
"extended_relations": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "contact",
|
||||
"properties": {
|
||||
"target": {"type": "email_address"}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"student.person": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {"type": "string"},
|
||||
"kind": {"type": "string"},
|
||||
"school": {"type": "string"}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "t2",
|
||||
"type": "type",
|
||||
"name": "email_address",
|
||||
"module": "core",
|
||||
"source": "email_address",
|
||||
"hierarchy": ["email_address"],
|
||||
"variations": ["email_address"],
|
||||
"schemas": {
|
||||
"light.email_address": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"address": {"type": "string"}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "t3",
|
||||
"type": "type",
|
||||
"name": "contact",
|
||||
"module": "core",
|
||||
"source": "contact",
|
||||
"hierarchy": ["contact"],
|
||||
"variations": ["contact"],
|
||||
"schemas": {
|
||||
"full.contact": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {"type": "string"}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "t4",
|
||||
"type": "type",
|
||||
"name": "some_bubble",
|
||||
"module": "core",
|
||||
"source": "some_bubble",
|
||||
"hierarchy": ["some_bubble"],
|
||||
"variations": ["some_bubble"],
|
||||
"schemas": {
|
||||
"some_bubble": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"bubble_prop": {"type": "string"}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "Assert exact topological schema promotion paths",
|
||||
"action": "compile",
|
||||
"expect": {
|
||||
"success": True,
|
||||
"schemas": [
|
||||
"ad_hoc_bubble",
|
||||
"email_address",
|
||||
"extended_relations",
|
||||
"extended_relations/target",
|
||||
"full.contact",
|
||||
"full.person",
|
||||
"full.person/ad_hoc_bubble",
|
||||
"full.person/extended_relations",
|
||||
"full.person/extended_relations/target",
|
||||
"light.email_address",
|
||||
"person",
|
||||
"some_bubble",
|
||||
"student.person"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
data.append(new_test)
|
||||
with open(path, "w") as f:
|
||||
json.dump(data, f, indent=2)
|
||||
|
||||
@ -1,34 +0,0 @@
|
||||
import json
|
||||
|
||||
path = "fixtures/database.json"
|
||||
|
||||
with open(path, "r") as f:
|
||||
data = json.load(f)
|
||||
|
||||
test_case = data[-1]
|
||||
# Get full.person object properties
|
||||
props = test_case["database"]["types"][0]["schemas"]["full.person"]["properties"]
|
||||
|
||||
# Find extended_relations target and add properties!
|
||||
target_ref = props["extended_relations"]["items"]["properties"]["target"]
|
||||
target_ref["properties"] = {
|
||||
"extra_3rd_level": {"type": "string"}
|
||||
}
|
||||
|
||||
# The target is now an ad-hoc composition itself!
|
||||
# We expect `full.person/extended_relations/target` to be globally promoted.
|
||||
|
||||
test_case["tests"][0]["expect"]["schemas"] = [
|
||||
"full.contact",
|
||||
"full.person",
|
||||
"full.person/ad_hoc_bubble",
|
||||
"full.person/extended_relations",
|
||||
"full.person/extended_relations/target", # BOOM! Right here, 3 levels deep!
|
||||
"light.email_address",
|
||||
"some_bubble",
|
||||
"student.person"
|
||||
]
|
||||
|
||||
with open(path, "w") as f:
|
||||
json.dump(data, f, indent=2)
|
||||
|
||||
@ -1,22 +0,0 @@
|
||||
import json
|
||||
|
||||
path = "fixtures/database.json"
|
||||
|
||||
with open(path, "r") as f:
|
||||
data = json.load(f)
|
||||
|
||||
test_case = data[-1]
|
||||
test_case["tests"][0]["expect"]["schemas"] = [
|
||||
"full.contact",
|
||||
"full.person",
|
||||
"full.person/ad_hoc_bubble",
|
||||
"full.person/extended_relations",
|
||||
"full.person/extended_relations/items",
|
||||
"light.email_address",
|
||||
"some_bubble",
|
||||
"student.person"
|
||||
]
|
||||
|
||||
with open(path, "w") as f:
|
||||
json.dump(data, f, indent=2)
|
||||
|
||||
87
fix_test.py
87
fix_test.py
@ -1,87 +0,0 @@
|
||||
import json
|
||||
|
||||
def load_json(path):
|
||||
with open(path, 'r') as f:
|
||||
return json.load(f)
|
||||
|
||||
def save_json(path, data):
|
||||
with open(path, 'w') as f:
|
||||
json.dump(data, f, indent=4)
|
||||
|
||||
def fix_merger():
|
||||
data = load_json('fixtures/merger.json')
|
||||
last_test = data[0]['tests'][-1]
|
||||
|
||||
# Check if the last test is our bad one
|
||||
if "name" in last_test and last_test["name"] == "Insert invoice with deep jsonb metadata":
|
||||
new_test = {
|
||||
"description": last_test["name"],
|
||||
"action": "merge",
|
||||
"schema_id": last_test["schema"],
|
||||
"data": last_test["payload"],
|
||||
"expect": {
|
||||
"success": True,
|
||||
"sql": [
|
||||
[
|
||||
"INSERT INTO agreego.invoice (",
|
||||
" \"metadata\",",
|
||||
" \"number\",",
|
||||
" entity_id,",
|
||||
" id,",
|
||||
" type",
|
||||
")",
|
||||
"VALUES (",
|
||||
" '{",
|
||||
" \"customer_snapshot\":{",
|
||||
" \"first_name\":\"John\",",
|
||||
" \"id\":\"00000000-0000-0000-0000-000000000000\",",
|
||||
" \"type\":\"person\"",
|
||||
" },",
|
||||
" \"internal_note\":\"Confidential\",",
|
||||
" \"related_rules\":[",
|
||||
" {",
|
||||
" \"id\":\"11111111-1111-1111-1111-111111111111\"",
|
||||
" }",
|
||||
" ]",
|
||||
" }',",
|
||||
" 'INV-1001',",
|
||||
" NULL,",
|
||||
" '{{uuid}}',",
|
||||
" 'invoice'",
|
||||
")"
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
data[0]['tests'][-1] = new_test
|
||||
save_json('fixtures/merger.json', data)
|
||||
|
||||
def fix_queryer():
|
||||
data = load_json('fixtures/queryer.json')
|
||||
last_test = data[0]['tests'][-1]
|
||||
|
||||
if "name" in last_test and last_test["name"] == "Query invoice with complex JSONB metadata field extraction":
|
||||
new_test = {
|
||||
"description": last_test["name"],
|
||||
"action": "query",
|
||||
"schema_id": last_test["schema"],
|
||||
"expect": {
|
||||
"success": True,
|
||||
"sql": [
|
||||
[
|
||||
"(SELECT jsonb_strip_nulls(jsonb_build_object(",
|
||||
" 'id', invoice_1.id,",
|
||||
" 'metadata', invoice_1.metadata,",
|
||||
" 'number', invoice_1.number,",
|
||||
" 'type', invoice_1.type",
|
||||
"))",
|
||||
"FROM agreego.invoice invoice_1)"
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
data[0]['tests'][-1] = new_test
|
||||
save_json('fixtures/queryer.json', data)
|
||||
|
||||
fix_merger()
|
||||
fix_queryer()
|
||||
@ -2,21 +2,26 @@
|
||||
{
|
||||
"description": "additionalProperties validates properties not matched by properties",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"schema1": {
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "string"
|
||||
},
|
||||
"bar": {
|
||||
"type": "number"
|
||||
"types": [
|
||||
{
|
||||
"name": "schema1",
|
||||
"schemas": {
|
||||
"schema1": {
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "string"
|
||||
},
|
||||
"bar": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"additionalProperties": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -61,19 +66,24 @@
|
||||
{
|
||||
"description": "extensible: true with additionalProperties still validates structure",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"additionalProperties_1_0": {
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "string"
|
||||
"types": [
|
||||
{
|
||||
"name": "additionalProperties_1_0",
|
||||
"schemas": {
|
||||
"additionalProperties_1_0": {
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"extensible": true,
|
||||
"additionalProperties": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"extensible": true,
|
||||
"additionalProperties": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -106,21 +116,26 @@
|
||||
{
|
||||
"description": "complex additionalProperties with object and array items",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"schema3": {
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
"types": [
|
||||
{
|
||||
"name": "schema3",
|
||||
"schemas": {
|
||||
"schema3": {
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
|
||||
@ -2,9 +2,14 @@
|
||||
{
|
||||
"description": "boolean schema 'true'",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"booleanSchema_0_0": {}
|
||||
}
|
||||
"types": [
|
||||
{
|
||||
"name": "booleanSchema_0_0",
|
||||
"schemas": {
|
||||
"booleanSchema_0_0": {}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -97,11 +102,16 @@
|
||||
{
|
||||
"description": "boolean schema 'false'",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"booleanSchema_1_0": {
|
||||
"not": {}
|
||||
"types": [
|
||||
{
|
||||
"name": "booleanSchema_1_0",
|
||||
"schemas": {
|
||||
"booleanSchema_1_0": {
|
||||
"not": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
|
||||
@ -2,218 +2,238 @@
|
||||
{
|
||||
"description": "Multi-Paradigm Declarative Cases",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"parallel_rules": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
"kind": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"cases": [
|
||||
{
|
||||
"when": {
|
||||
"properties": {
|
||||
"status": {
|
||||
"const": "unverified"
|
||||
}
|
||||
"types": [
|
||||
{
|
||||
"name": "parallel_rules",
|
||||
"schemas": {
|
||||
"parallel_rules": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
"required": [
|
||||
"status"
|
||||
]
|
||||
"kind": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"then": {
|
||||
"properties": {
|
||||
"amount_1": {
|
||||
"type": "number"
|
||||
"cases": [
|
||||
{
|
||||
"when": {
|
||||
"properties": {
|
||||
"status": {
|
||||
"const": "unverified"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"status"
|
||||
]
|
||||
},
|
||||
"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"
|
||||
}
|
||||
"then": {
|
||||
"properties": {
|
||||
"amount_1": {
|
||||
"type": "number"
|
||||
},
|
||||
"required": [
|
||||
"tier"
|
||||
]
|
||||
"amount_2": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"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": [
|
||||
"amount_1",
|
||||
"amount_2"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"unconditional"
|
||||
]
|
||||
}
|
||||
{
|
||||
"when": {
|
||||
"properties": {
|
||||
"kind": {
|
||||
"const": "credit"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"kind"
|
||||
]
|
||||
},
|
||||
"then": {
|
||||
"properties": {
|
||||
"cvv": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"cvv"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "mutually_exclusive",
|
||||
"schemas": {
|
||||
"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"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "nested_fallbacks",
|
||||
"schemas": {
|
||||
"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"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "missing_when",
|
||||
"schemas": {
|
||||
"missing_when": {
|
||||
"type": "object",
|
||||
"cases": [
|
||||
{
|
||||
"else": {
|
||||
"properties": {
|
||||
"unconditional": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"unconditional"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
|
||||
@ -2,11 +2,16 @@
|
||||
{
|
||||
"description": "const validation",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"const_0_0": {
|
||||
"const": 2
|
||||
"types": [
|
||||
{
|
||||
"name": "const_0_0",
|
||||
"schemas": {
|
||||
"const_0_0": {
|
||||
"const": 2
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -41,18 +46,23 @@
|
||||
{
|
||||
"description": "const with object",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"const_1_0": {
|
||||
"const": {
|
||||
"foo": "bar",
|
||||
"baz": "bax"
|
||||
},
|
||||
"properties": {
|
||||
"foo": {},
|
||||
"baz": {}
|
||||
"types": [
|
||||
{
|
||||
"name": "const_1_0",
|
||||
"schemas": {
|
||||
"const_1_0": {
|
||||
"const": {
|
||||
"foo": "bar",
|
||||
"baz": "bax"
|
||||
},
|
||||
"properties": {
|
||||
"foo": {},
|
||||
"baz": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -107,15 +117,20 @@
|
||||
{
|
||||
"description": "const with array",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"const_2_0": {
|
||||
"const": [
|
||||
{
|
||||
"foo": "bar"
|
||||
"types": [
|
||||
{
|
||||
"name": "const_2_0",
|
||||
"schemas": {
|
||||
"const_2_0": {
|
||||
"const": [
|
||||
{
|
||||
"foo": "bar"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -160,11 +175,16 @@
|
||||
{
|
||||
"description": "const with null",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"const_3_0": {
|
||||
"const": null
|
||||
"types": [
|
||||
{
|
||||
"name": "const_3_0",
|
||||
"schemas": {
|
||||
"const_3_0": {
|
||||
"const": null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -190,11 +210,16 @@
|
||||
{
|
||||
"description": "const with false does not match 0",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"const_4_0": {
|
||||
"const": false
|
||||
"types": [
|
||||
{
|
||||
"name": "const_4_0",
|
||||
"schemas": {
|
||||
"const_4_0": {
|
||||
"const": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -229,11 +254,16 @@
|
||||
{
|
||||
"description": "const with true does not match 1",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"const_5_0": {
|
||||
"const": true
|
||||
"types": [
|
||||
{
|
||||
"name": "const_5_0",
|
||||
"schemas": {
|
||||
"const_5_0": {
|
||||
"const": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -268,13 +298,18 @@
|
||||
{
|
||||
"description": "const with [false] does not match [0]",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"const_6_0": {
|
||||
"const": [
|
||||
false
|
||||
]
|
||||
"types": [
|
||||
{
|
||||
"name": "const_6_0",
|
||||
"schemas": {
|
||||
"const_6_0": {
|
||||
"const": [
|
||||
false
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -315,13 +350,18 @@
|
||||
{
|
||||
"description": "const with [true] does not match [1]",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"const_7_0": {
|
||||
"const": [
|
||||
true
|
||||
]
|
||||
"types": [
|
||||
{
|
||||
"name": "const_7_0",
|
||||
"schemas": {
|
||||
"const_7_0": {
|
||||
"const": [
|
||||
true
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -362,13 +402,18 @@
|
||||
{
|
||||
"description": "const with {\"a\": false} does not match {\"a\": 0}",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"const_8_0": {
|
||||
"const": {
|
||||
"a": false
|
||||
"types": [
|
||||
{
|
||||
"name": "const_8_0",
|
||||
"schemas": {
|
||||
"const_8_0": {
|
||||
"const": {
|
||||
"a": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -409,13 +454,18 @@
|
||||
{
|
||||
"description": "const with {\"a\": true} does not match {\"a\": 1}",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"const_9_0": {
|
||||
"const": {
|
||||
"a": true
|
||||
"types": [
|
||||
{
|
||||
"name": "const_9_0",
|
||||
"schemas": {
|
||||
"const_9_0": {
|
||||
"const": {
|
||||
"a": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -456,11 +506,16 @@
|
||||
{
|
||||
"description": "const with 0 does not match other zero-like types",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"const_10_0": {
|
||||
"const": 0
|
||||
"types": [
|
||||
{
|
||||
"name": "const_10_0",
|
||||
"schemas": {
|
||||
"const_10_0": {
|
||||
"const": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -522,11 +577,16 @@
|
||||
{
|
||||
"description": "const with 1 does not match true",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"const_11_0": {
|
||||
"const": 1
|
||||
"types": [
|
||||
{
|
||||
"name": "const_11_0",
|
||||
"schemas": {
|
||||
"const_11_0": {
|
||||
"const": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -561,11 +621,16 @@
|
||||
{
|
||||
"description": "const with -2.0 matches integer and float types",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"const_12_0": {
|
||||
"const": -2
|
||||
"types": [
|
||||
{
|
||||
"name": "const_12_0",
|
||||
"schemas": {
|
||||
"const_12_0": {
|
||||
"const": -2
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -618,11 +683,16 @@
|
||||
{
|
||||
"description": "float and integers are equal up to 64-bit representation limits",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"const_13_0": {
|
||||
"const": 9007199254740992
|
||||
"types": [
|
||||
{
|
||||
"name": "const_13_0",
|
||||
"schemas": {
|
||||
"const_13_0": {
|
||||
"const": 9007199254740992
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -666,11 +736,16 @@
|
||||
{
|
||||
"description": "nul characters in strings",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"const_14_0": {
|
||||
"const": "hello\u0000there"
|
||||
"types": [
|
||||
{
|
||||
"name": "const_14_0",
|
||||
"schemas": {
|
||||
"const_14_0": {
|
||||
"const": "hello\u0000there"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -696,17 +771,22 @@
|
||||
{
|
||||
"description": "characters with the same visual representation but different codepoint",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"const_15_0": {
|
||||
"const": "μ",
|
||||
"$comment": "U+03BC"
|
||||
"types": [
|
||||
{
|
||||
"name": "const_15_0",
|
||||
"schemas": {
|
||||
"const_15_0": {
|
||||
"const": "\u03bc",
|
||||
"$comment": "U+03BC"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "character uses the same codepoint",
|
||||
"data": "μ",
|
||||
"data": "\u03bc",
|
||||
"comment": "U+03BC",
|
||||
"schema_id": "const_15_0",
|
||||
"action": "validate",
|
||||
@ -716,7 +796,7 @@
|
||||
},
|
||||
{
|
||||
"description": "character looks the same but uses a different codepoint",
|
||||
"data": "µ",
|
||||
"data": "\u00b5",
|
||||
"comment": "U+00B5",
|
||||
"schema_id": "const_15_0",
|
||||
"action": "validate",
|
||||
@ -729,17 +809,22 @@
|
||||
{
|
||||
"description": "characters with the same visual representation, but different number of codepoints",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"const_16_0": {
|
||||
"const": "ä",
|
||||
"$comment": "U+00E4"
|
||||
"types": [
|
||||
{
|
||||
"name": "const_16_0",
|
||||
"schemas": {
|
||||
"const_16_0": {
|
||||
"const": "\u00e4",
|
||||
"$comment": "U+00E4"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "character uses the same codepoint",
|
||||
"data": "ä",
|
||||
"data": "\u00e4",
|
||||
"comment": "U+00E4",
|
||||
"schema_id": "const_16_0",
|
||||
"action": "validate",
|
||||
@ -749,7 +834,7 @@
|
||||
},
|
||||
{
|
||||
"description": "character looks the same but uses combining marks",
|
||||
"data": "ä",
|
||||
"data": "a\u0308",
|
||||
"comment": "a, U+0308",
|
||||
"schema_id": "const_16_0",
|
||||
"action": "validate",
|
||||
@ -762,14 +847,19 @@
|
||||
{
|
||||
"description": "extensible: true allows extra properties in const object match",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"const_17_0": {
|
||||
"const": {
|
||||
"a": 1
|
||||
},
|
||||
"extensible": true
|
||||
"types": [
|
||||
{
|
||||
"name": "const_17_0",
|
||||
"schemas": {
|
||||
"const_17_0": {
|
||||
"const": {
|
||||
"a": 1
|
||||
},
|
||||
"extensible": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
|
||||
@ -2,14 +2,19 @@
|
||||
{
|
||||
"description": "contains keyword validation",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"contains_0_0": {
|
||||
"contains": {
|
||||
"minimum": 5
|
||||
},
|
||||
"items": true
|
||||
"types": [
|
||||
{
|
||||
"name": "contains_0_0",
|
||||
"schemas": {
|
||||
"contains_0_0": {
|
||||
"contains": {
|
||||
"minimum": 5
|
||||
},
|
||||
"items": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -88,14 +93,19 @@
|
||||
{
|
||||
"description": "contains keyword with const keyword",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"contains_1_0": {
|
||||
"contains": {
|
||||
"const": 5
|
||||
},
|
||||
"items": true
|
||||
"types": [
|
||||
{
|
||||
"name": "contains_1_0",
|
||||
"schemas": {
|
||||
"contains_1_0": {
|
||||
"contains": {
|
||||
"const": 5
|
||||
},
|
||||
"items": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -144,11 +154,16 @@
|
||||
{
|
||||
"description": "contains keyword with boolean schema true",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"contains_2_0": {
|
||||
"contains": true
|
||||
"types": [
|
||||
{
|
||||
"name": "contains_2_0",
|
||||
"schemas": {
|
||||
"contains_2_0": {
|
||||
"contains": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -176,11 +191,16 @@
|
||||
{
|
||||
"description": "contains keyword with boolean schema false",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"contains_3_0": {
|
||||
"contains": false
|
||||
"types": [
|
||||
{
|
||||
"name": "contains_3_0",
|
||||
"schemas": {
|
||||
"contains_3_0": {
|
||||
"contains": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -217,16 +237,21 @@
|
||||
{
|
||||
"description": "items + contains",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"contains_4_0": {
|
||||
"items": {
|
||||
"multipleOf": 2
|
||||
},
|
||||
"contains": {
|
||||
"multipleOf": 3
|
||||
"types": [
|
||||
{
|
||||
"name": "contains_4_0",
|
||||
"schemas": {
|
||||
"contains_4_0": {
|
||||
"items": {
|
||||
"multipleOf": 2
|
||||
},
|
||||
"contains": {
|
||||
"multipleOf": 3
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -284,14 +309,19 @@
|
||||
{
|
||||
"description": "contains with false if subschema",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"contains_5_0": {
|
||||
"contains": {
|
||||
"if": false,
|
||||
"else": true
|
||||
"types": [
|
||||
{
|
||||
"name": "contains_5_0",
|
||||
"schemas": {
|
||||
"contains_5_0": {
|
||||
"contains": {
|
||||
"if": false,
|
||||
"else": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -319,13 +349,18 @@
|
||||
{
|
||||
"description": "contains with null instance elements",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"contains_6_0": {
|
||||
"contains": {
|
||||
"type": "null"
|
||||
"types": [
|
||||
{
|
||||
"name": "contains_6_0",
|
||||
"schemas": {
|
||||
"contains_6_0": {
|
||||
"contains": {
|
||||
"type": "null"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -344,14 +379,19 @@
|
||||
{
|
||||
"description": "extensible: true allows non-matching items in contains",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"contains_7_0": {
|
||||
"contains": {
|
||||
"const": 1
|
||||
},
|
||||
"extensible": true
|
||||
"types": [
|
||||
{
|
||||
"name": "contains_7_0",
|
||||
"schemas": {
|
||||
"contains_7_0": {
|
||||
"contains": {
|
||||
"const": 1
|
||||
},
|
||||
"extensible": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -371,13 +411,18 @@
|
||||
{
|
||||
"description": "strict by default: non-matching items in contains are invalid",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"contains_8_0": {
|
||||
"contains": {
|
||||
"const": 1
|
||||
"types": [
|
||||
{
|
||||
"name": "contains_8_0",
|
||||
"schemas": {
|
||||
"contains_8_0": {
|
||||
"contains": {
|
||||
"const": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
|
||||
@ -2,11 +2,16 @@
|
||||
{
|
||||
"description": "validation of string-encoded content based on media type",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"content_0_0": {
|
||||
"contentMediaType": "application/json"
|
||||
"types": [
|
||||
{
|
||||
"name": "content_0_0",
|
||||
"schemas": {
|
||||
"content_0_0": {
|
||||
"contentMediaType": "application/json"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -41,11 +46,16 @@
|
||||
{
|
||||
"description": "validation of binary string-encoding",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"content_1_0": {
|
||||
"contentEncoding": "base64"
|
||||
"types": [
|
||||
{
|
||||
"name": "content_1_0",
|
||||
"schemas": {
|
||||
"content_1_0": {
|
||||
"contentEncoding": "base64"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -80,12 +90,17 @@
|
||||
{
|
||||
"description": "validation of binary-encoded media type documents",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"content_2_0": {
|
||||
"contentMediaType": "application/json",
|
||||
"contentEncoding": "base64"
|
||||
"types": [
|
||||
{
|
||||
"name": "content_2_0",
|
||||
"schemas": {
|
||||
"content_2_0": {
|
||||
"contentMediaType": "application/json",
|
||||
"contentEncoding": "base64"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -129,26 +144,31 @@
|
||||
{
|
||||
"description": "validation of binary-encoded media type documents with schema",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"content_3_0": {
|
||||
"contentMediaType": "application/json",
|
||||
"contentEncoding": "base64",
|
||||
"contentSchema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"foo"
|
||||
],
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "string"
|
||||
},
|
||||
"boo": {
|
||||
"type": "integer"
|
||||
"types": [
|
||||
{
|
||||
"name": "content_3_0",
|
||||
"schemas": {
|
||||
"content_3_0": {
|
||||
"contentMediaType": "application/json",
|
||||
"contentEncoding": "base64",
|
||||
"contentSchema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"foo"
|
||||
],
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "string"
|
||||
},
|
||||
"boo": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
|
||||
@ -515,7 +515,7 @@
|
||||
"type": "string"
|
||||
},
|
||||
"email": {
|
||||
"$family": "email_address"
|
||||
"family": "email_address"
|
||||
},
|
||||
"generic_bubble": {
|
||||
"type": "some_bubble"
|
||||
@ -651,16 +651,21 @@
|
||||
"action": "compile",
|
||||
"expect": {
|
||||
"success": true,
|
||||
"schemas": [
|
||||
"full.contact",
|
||||
"full.person",
|
||||
"full.person/ad_hoc_bubble",
|
||||
"full.person/extended_relations",
|
||||
"full.person/extended_relations/target",
|
||||
"light.email_address",
|
||||
"some_bubble",
|
||||
"student.person"
|
||||
]
|
||||
"schemas": {
|
||||
"full.contact": {},
|
||||
"full.contact.filter": {},
|
||||
"full.person": {},
|
||||
"full.person.filter": {},
|
||||
"full.person/ad_hoc_bubble": {},
|
||||
"full.person/extended_relations": {},
|
||||
"full.person/extended_relations/target": {},
|
||||
"light.email_address": {},
|
||||
"light.email_address.filter": {},
|
||||
"some_bubble": {},
|
||||
"some_bubble.filter": {},
|
||||
"student.person": {},
|
||||
"student.person.filter": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
@ -919,11 +924,14 @@
|
||||
"action": "compile",
|
||||
"expect": {
|
||||
"success": true,
|
||||
"schemas": [
|
||||
"entity",
|
||||
"invoice",
|
||||
"invoice_line"
|
||||
]
|
||||
"schemas": {
|
||||
"entity": {},
|
||||
"entity.filter": {},
|
||||
"invoice": {},
|
||||
"invoice.filter": {},
|
||||
"invoice_line": {},
|
||||
"invoice_line.filter": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@ -2,17 +2,22 @@
|
||||
{
|
||||
"description": "single dependency (required)",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"schema1": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"dependencies": {
|
||||
"bar": [
|
||||
"foo"
|
||||
]
|
||||
},
|
||||
"extensible": true
|
||||
"types": [
|
||||
{
|
||||
"name": "schema1",
|
||||
"schemas": {
|
||||
"schema1": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"dependencies": {
|
||||
"bar": [
|
||||
"foo"
|
||||
]
|
||||
},
|
||||
"extensible": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -92,15 +97,20 @@
|
||||
{
|
||||
"description": "empty dependents",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"schema2": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"dependencies": {
|
||||
"bar": []
|
||||
},
|
||||
"extensible": true
|
||||
"types": [
|
||||
{
|
||||
"name": "schema2",
|
||||
"schemas": {
|
||||
"schema2": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"dependencies": {
|
||||
"bar": []
|
||||
},
|
||||
"extensible": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -137,18 +147,23 @@
|
||||
{
|
||||
"description": "multiple dependents required",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"schema3": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"dependencies": {
|
||||
"quux": [
|
||||
"foo",
|
||||
"bar"
|
||||
]
|
||||
},
|
||||
"extensible": true
|
||||
"types": [
|
||||
{
|
||||
"name": "schema3",
|
||||
"schemas": {
|
||||
"schema3": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"dependencies": {
|
||||
"quux": [
|
||||
"foo",
|
||||
"bar"
|
||||
]
|
||||
},
|
||||
"extensible": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -225,20 +240,25 @@
|
||||
{
|
||||
"description": "dependencies with escaped characters",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"schema4": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"dependencies": {
|
||||
"foo\nbar": [
|
||||
"foo\rbar"
|
||||
],
|
||||
"foo\"bar": [
|
||||
"foo'bar"
|
||||
]
|
||||
},
|
||||
"extensible": true
|
||||
"types": [
|
||||
{
|
||||
"name": "schema4",
|
||||
"schemas": {
|
||||
"schema4": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"dependencies": {
|
||||
"foo\nbar": [
|
||||
"foo\rbar"
|
||||
],
|
||||
"foo\"bar": [
|
||||
"foo'bar"
|
||||
]
|
||||
},
|
||||
"extensible": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -293,17 +313,22 @@
|
||||
{
|
||||
"description": "extensible: true allows extra properties in dependentRequired",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"schema5": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"dependencies": {
|
||||
"bar": [
|
||||
"foo"
|
||||
]
|
||||
},
|
||||
"extensible": true
|
||||
"types": [
|
||||
{
|
||||
"name": "schema5",
|
||||
"schemas": {
|
||||
"schema5": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"dependencies": {
|
||||
"bar": [
|
||||
"foo"
|
||||
]
|
||||
},
|
||||
"extensible": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -324,27 +349,32 @@
|
||||
{
|
||||
"description": "single dependency (schemas, STRICT)",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"schema_schema1": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"properties": {
|
||||
"foo": true,
|
||||
"bar": true
|
||||
},
|
||||
"dependencies": {
|
||||
"bar": {
|
||||
"types": [
|
||||
{
|
||||
"name": "schema_schema1",
|
||||
"schemas": {
|
||||
"schema_schema1": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "integer"
|
||||
},
|
||||
"foo": true,
|
||||
"bar": true
|
||||
},
|
||||
"dependencies": {
|
||||
"bar": {
|
||||
"type": "integer"
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "integer"
|
||||
},
|
||||
"bar": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -445,28 +475,33 @@
|
||||
{
|
||||
"description": "single dependency (schemas, EXTENSIBLE)",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"schema_schema2": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"properties": {
|
||||
"foo": true,
|
||||
"bar": true
|
||||
},
|
||||
"dependencies": {
|
||||
"bar": {
|
||||
"types": [
|
||||
{
|
||||
"name": "schema_schema2",
|
||||
"schemas": {
|
||||
"schema_schema2": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "integer"
|
||||
},
|
||||
"foo": true,
|
||||
"bar": true
|
||||
},
|
||||
"dependencies": {
|
||||
"bar": {
|
||||
"type": "integer"
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "integer"
|
||||
},
|
||||
"bar": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"extensible": true
|
||||
}
|
||||
},
|
||||
"extensible": true
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -485,19 +520,24 @@
|
||||
{
|
||||
"description": "boolean subschemas",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"schema_schema3": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"properties": {
|
||||
"foo": true,
|
||||
"bar": true
|
||||
},
|
||||
"dependencies": {
|
||||
"foo": true,
|
||||
"bar": false
|
||||
"types": [
|
||||
{
|
||||
"name": "schema_schema3",
|
||||
"schemas": {
|
||||
"schema_schema3": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"properties": {
|
||||
"foo": true,
|
||||
"bar": true
|
||||
},
|
||||
"dependencies": {
|
||||
"foo": true,
|
||||
"bar": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -548,29 +588,34 @@
|
||||
{
|
||||
"description": "dependencies with escaped characters",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"schema_schema4": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"properties": {
|
||||
"foo\tbar": true,
|
||||
"foo'bar": true,
|
||||
"a": true,
|
||||
"b": true,
|
||||
"c": true
|
||||
},
|
||||
"dependencies": {
|
||||
"foo\tbar": {
|
||||
"minProperties": 4,
|
||||
"extensible": true
|
||||
},
|
||||
"foo'bar": {
|
||||
"required": [
|
||||
"foo\"bar"
|
||||
]
|
||||
"types": [
|
||||
{
|
||||
"name": "schema_schema4",
|
||||
"schemas": {
|
||||
"schema_schema4": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"properties": {
|
||||
"foo\tbar": true,
|
||||
"foo'bar": true,
|
||||
"a": true,
|
||||
"b": true,
|
||||
"c": true
|
||||
},
|
||||
"dependencies": {
|
||||
"foo\tbar": {
|
||||
"minProperties": 4,
|
||||
"extensible": true
|
||||
},
|
||||
"foo'bar": {
|
||||
"required": [
|
||||
"foo\"bar"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -628,22 +673,27 @@
|
||||
{
|
||||
"description": "dependent subschema incompatible with root (STRICT)",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"schema_schema5": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"properties": {
|
||||
"foo": {},
|
||||
"baz": true
|
||||
},
|
||||
"dependencies": {
|
||||
"foo": {
|
||||
"types": [
|
||||
{
|
||||
"name": "schema_schema5",
|
||||
"schemas": {
|
||||
"schema_schema5": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"properties": {
|
||||
"bar": {}
|
||||
"foo": {},
|
||||
"baz": true
|
||||
},
|
||||
"dependencies": {
|
||||
"foo": {
|
||||
"properties": {
|
||||
"bar": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -701,24 +751,29 @@
|
||||
{
|
||||
"description": "dependent subschema incompatible with root (EXTENSIBLE)",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"schema_schema6": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"properties": {
|
||||
"foo": {},
|
||||
"baz": true
|
||||
},
|
||||
"dependencies": {
|
||||
"foo": {
|
||||
"types": [
|
||||
{
|
||||
"name": "schema_schema6",
|
||||
"schemas": {
|
||||
"schema_schema6": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"properties": {
|
||||
"bar": {}
|
||||
"foo": {},
|
||||
"baz": true
|
||||
},
|
||||
"additionalProperties": false
|
||||
"dependencies": {
|
||||
"foo": {
|
||||
"properties": {
|
||||
"bar": {}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"extensible": true
|
||||
}
|
||||
},
|
||||
"extensible": true
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
|
||||
@ -2,43 +2,48 @@
|
||||
{
|
||||
"description": "empty string is valid for all types (except const)",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"emptyString_0_0": {
|
||||
"properties": {
|
||||
"obj": {
|
||||
"type": "object"
|
||||
},
|
||||
"arr": {
|
||||
"type": "array"
|
||||
},
|
||||
"str": {
|
||||
"type": "string"
|
||||
},
|
||||
"int": {
|
||||
"type": "integer"
|
||||
},
|
||||
"num": {
|
||||
"type": "number"
|
||||
},
|
||||
"bool": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"nul": {
|
||||
"type": "null"
|
||||
},
|
||||
"fmt": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
"con": {
|
||||
"const": "value"
|
||||
},
|
||||
"con_empty": {
|
||||
"const": ""
|
||||
"types": [
|
||||
{
|
||||
"name": "emptyString_0_0",
|
||||
"schemas": {
|
||||
"emptyString_0_0": {
|
||||
"properties": {
|
||||
"obj": {
|
||||
"type": "object"
|
||||
},
|
||||
"arr": {
|
||||
"type": "array"
|
||||
},
|
||||
"str": {
|
||||
"type": "string"
|
||||
},
|
||||
"int": {
|
||||
"type": "integer"
|
||||
},
|
||||
"num": {
|
||||
"type": "number"
|
||||
},
|
||||
"bool": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"nul": {
|
||||
"type": "null"
|
||||
},
|
||||
"fmt": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
"con": {
|
||||
"const": "value"
|
||||
},
|
||||
"con_empty": {
|
||||
"const": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
|
||||
@ -2,15 +2,20 @@
|
||||
{
|
||||
"description": "simple enum validation",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"enum_0_0": {
|
||||
"enum": [
|
||||
1,
|
||||
2,
|
||||
3
|
||||
]
|
||||
"types": [
|
||||
{
|
||||
"name": "enum_0_0",
|
||||
"schemas": {
|
||||
"enum_0_0": {
|
||||
"enum": [
|
||||
1,
|
||||
2,
|
||||
3
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -36,22 +41,27 @@
|
||||
{
|
||||
"description": "heterogeneous enum validation",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"enum_1_0": {
|
||||
"enum": [
|
||||
6,
|
||||
"foo",
|
||||
[],
|
||||
true,
|
||||
{
|
||||
"foo": 12
|
||||
"types": [
|
||||
{
|
||||
"name": "enum_1_0",
|
||||
"schemas": {
|
||||
"enum_1_0": {
|
||||
"enum": [
|
||||
6,
|
||||
"foo",
|
||||
[],
|
||||
true,
|
||||
{
|
||||
"foo": 12
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"foo": {}
|
||||
}
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"foo": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -111,14 +121,19 @@
|
||||
{
|
||||
"description": "heterogeneous enum-with-null validation",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"enum_2_0": {
|
||||
"enum": [
|
||||
6,
|
||||
null
|
||||
]
|
||||
"types": [
|
||||
{
|
||||
"name": "enum_2_0",
|
||||
"schemas": {
|
||||
"enum_2_0": {
|
||||
"enum": [
|
||||
6,
|
||||
null
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -153,26 +168,31 @@
|
||||
{
|
||||
"description": "enums in properties",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"enum_3_0": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"foo": {
|
||||
"enum": [
|
||||
"foo"
|
||||
]
|
||||
},
|
||||
"bar": {
|
||||
"enum": [
|
||||
"types": [
|
||||
{
|
||||
"name": "enum_3_0",
|
||||
"schemas": {
|
||||
"enum_3_0": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"foo": {
|
||||
"enum": [
|
||||
"foo"
|
||||
]
|
||||
},
|
||||
"bar": {
|
||||
"enum": [
|
||||
"bar"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"bar"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"bar"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -247,14 +267,19 @@
|
||||
{
|
||||
"description": "enum with escaped characters",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"enum_4_0": {
|
||||
"enum": [
|
||||
"foo\nbar",
|
||||
"foo\rbar"
|
||||
]
|
||||
"types": [
|
||||
{
|
||||
"name": "enum_4_0",
|
||||
"schemas": {
|
||||
"enum_4_0": {
|
||||
"enum": [
|
||||
"foo\nbar",
|
||||
"foo\rbar"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -289,13 +314,18 @@
|
||||
{
|
||||
"description": "enum with false does not match 0",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"enum_5_0": {
|
||||
"enum": [
|
||||
false
|
||||
]
|
||||
"types": [
|
||||
{
|
||||
"name": "enum_5_0",
|
||||
"schemas": {
|
||||
"enum_5_0": {
|
||||
"enum": [
|
||||
false
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -330,15 +360,20 @@
|
||||
{
|
||||
"description": "enum with [false] does not match [0]",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"enum_6_0": {
|
||||
"enum": [
|
||||
[
|
||||
false
|
||||
]
|
||||
]
|
||||
"types": [
|
||||
{
|
||||
"name": "enum_6_0",
|
||||
"schemas": {
|
||||
"enum_6_0": {
|
||||
"enum": [
|
||||
[
|
||||
false
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -379,13 +414,18 @@
|
||||
{
|
||||
"description": "enum with true does not match 1",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"enum_7_0": {
|
||||
"enum": [
|
||||
true
|
||||
]
|
||||
"types": [
|
||||
{
|
||||
"name": "enum_7_0",
|
||||
"schemas": {
|
||||
"enum_7_0": {
|
||||
"enum": [
|
||||
true
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -420,15 +460,20 @@
|
||||
{
|
||||
"description": "enum with [true] does not match [1]",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"enum_8_0": {
|
||||
"enum": [
|
||||
[
|
||||
true
|
||||
]
|
||||
]
|
||||
"types": [
|
||||
{
|
||||
"name": "enum_8_0",
|
||||
"schemas": {
|
||||
"enum_8_0": {
|
||||
"enum": [
|
||||
[
|
||||
true
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -469,13 +514,18 @@
|
||||
{
|
||||
"description": "enum with 0 does not match false",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"enum_9_0": {
|
||||
"enum": [
|
||||
0
|
||||
]
|
||||
"types": [
|
||||
{
|
||||
"name": "enum_9_0",
|
||||
"schemas": {
|
||||
"enum_9_0": {
|
||||
"enum": [
|
||||
0
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -510,15 +560,20 @@
|
||||
{
|
||||
"description": "enum with [0] does not match [false]",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"enum_10_0": {
|
||||
"enum": [
|
||||
[
|
||||
0
|
||||
]
|
||||
]
|
||||
"types": [
|
||||
{
|
||||
"name": "enum_10_0",
|
||||
"schemas": {
|
||||
"enum_10_0": {
|
||||
"enum": [
|
||||
[
|
||||
0
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -559,13 +614,18 @@
|
||||
{
|
||||
"description": "enum with 1 does not match true",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"enum_11_0": {
|
||||
"enum": [
|
||||
1
|
||||
]
|
||||
"types": [
|
||||
{
|
||||
"name": "enum_11_0",
|
||||
"schemas": {
|
||||
"enum_11_0": {
|
||||
"enum": [
|
||||
1
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -600,15 +660,20 @@
|
||||
{
|
||||
"description": "enum with [1] does not match [true]",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"enum_12_0": {
|
||||
"enum": [
|
||||
[
|
||||
1
|
||||
]
|
||||
]
|
||||
"types": [
|
||||
{
|
||||
"name": "enum_12_0",
|
||||
"schemas": {
|
||||
"enum_12_0": {
|
||||
"enum": [
|
||||
[
|
||||
1
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -649,13 +714,18 @@
|
||||
{
|
||||
"description": "nul characters in strings",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"enum_13_0": {
|
||||
"enum": [
|
||||
"hello\u0000there"
|
||||
]
|
||||
"types": [
|
||||
{
|
||||
"name": "enum_13_0",
|
||||
"schemas": {
|
||||
"enum_13_0": {
|
||||
"enum": [
|
||||
"hello\u0000there"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -681,16 +751,21 @@
|
||||
{
|
||||
"description": "extensible: true allows extra properties in enum object match",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"enum_14_0": {
|
||||
"enum": [
|
||||
{
|
||||
"foo": 1
|
||||
"types": [
|
||||
{
|
||||
"name": "enum_14_0",
|
||||
"schemas": {
|
||||
"enum_14_0": {
|
||||
"enum": [
|
||||
{
|
||||
"foo": 1
|
||||
}
|
||||
],
|
||||
"extensible": true
|
||||
}
|
||||
],
|
||||
"extensible": true
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
|
||||
@ -2,11 +2,16 @@
|
||||
{
|
||||
"description": "exclusiveMaximum validation",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"exclusiveMaximum_0_0": {
|
||||
"exclusiveMaximum": 3
|
||||
"types": [
|
||||
{
|
||||
"name": "exclusiveMaximum_0_0",
|
||||
"schemas": {
|
||||
"exclusiveMaximum_0_0": {
|
||||
"exclusiveMaximum": 3
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
|
||||
@ -2,11 +2,16 @@
|
||||
{
|
||||
"description": "exclusiveMinimum validation",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"exclusiveMinimum_0_0": {
|
||||
"exclusiveMinimum": 1.1
|
||||
"types": [
|
||||
{
|
||||
"name": "exclusiveMinimum_0_0",
|
||||
"schemas": {
|
||||
"exclusiveMinimum_0_0": {
|
||||
"exclusiveMinimum": 1.1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
|
||||
354
fixtures/filter.json
Normal file
354
fixtures/filter.json
Normal file
@ -0,0 +1,354 @@
|
||||
[
|
||||
{
|
||||
"description": "Filter Synthesis Object-Oriented Composition",
|
||||
"database": {
|
||||
"puncs": [],
|
||||
"enums": [],
|
||||
"relations": [
|
||||
{
|
||||
"id": "rel1",
|
||||
"type": "relation",
|
||||
"constraint": "fk_person_billing_address",
|
||||
"source_type": "person",
|
||||
"source_columns": [
|
||||
"billing_address_id"
|
||||
],
|
||||
"destination_type": "address",
|
||||
"destination_columns": [
|
||||
"id"
|
||||
],
|
||||
"prefix": "billing_address"
|
||||
}
|
||||
],
|
||||
"types": [
|
||||
{
|
||||
"id": "type1",
|
||||
"type": "type",
|
||||
"name": "person",
|
||||
"module": "core",
|
||||
"source": "person",
|
||||
"hierarchy": [
|
||||
"person"
|
||||
],
|
||||
"variations": [
|
||||
"person"
|
||||
],
|
||||
"schemas": {
|
||||
"person": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"first_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"age": {
|
||||
"type": "integer"
|
||||
},
|
||||
"billing_address": {
|
||||
"type": "address"
|
||||
},
|
||||
"birth_date": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"tags": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"ad_hoc": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "type2",
|
||||
"type": "type",
|
||||
"name": "address",
|
||||
"module": "core",
|
||||
"source": "address",
|
||||
"hierarchy": [
|
||||
"address"
|
||||
],
|
||||
"variations": [
|
||||
"address"
|
||||
],
|
||||
"schemas": {
|
||||
"address": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"city": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "type3",
|
||||
"type": "type",
|
||||
"name": "search",
|
||||
"module": "core",
|
||||
"source": "search",
|
||||
"hierarchy": [
|
||||
"search"
|
||||
],
|
||||
"variations": [
|
||||
"search"
|
||||
],
|
||||
"schemas": {
|
||||
"search": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"filter": {
|
||||
"type": "filter"
|
||||
}
|
||||
}
|
||||
},
|
||||
"filter": {
|
||||
"type": "object"
|
||||
},
|
||||
"condition": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"kind": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"string.condition": {
|
||||
"type": "condition",
|
||||
"properties": {
|
||||
"$eq": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"integer.condition": {
|
||||
"type": "condition",
|
||||
"properties": {
|
||||
"$eq": {
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"date.condition": {
|
||||
"type": "condition",
|
||||
"properties": {
|
||||
"$eq": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "Assert filter generation map accurately represents strongly typed conditions natively.",
|
||||
"action": "compile",
|
||||
"expect": {
|
||||
"success": true,
|
||||
"schemas": {
|
||||
"person": {},
|
||||
"person.filter": {
|
||||
"type": "filter",
|
||||
"compiledPropertyNames": [
|
||||
"$and",
|
||||
"$or",
|
||||
"age",
|
||||
"billing_address",
|
||||
"birth_date",
|
||||
"first_name"
|
||||
],
|
||||
"properties": {
|
||||
"$and": {
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
],
|
||||
"items": {
|
||||
"compiledPropertyNames": [
|
||||
"$and",
|
||||
"$or",
|
||||
"age",
|
||||
"billing_address",
|
||||
"birth_date",
|
||||
"first_name"
|
||||
],
|
||||
"type": "person.filter"
|
||||
}
|
||||
},
|
||||
"$or": {
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
],
|
||||
"items": {
|
||||
"compiledPropertyNames": [
|
||||
"$and",
|
||||
"$or",
|
||||
"age",
|
||||
"billing_address",
|
||||
"birth_date",
|
||||
"first_name"
|
||||
],
|
||||
"type": "person.filter"
|
||||
}
|
||||
},
|
||||
"first_name": {
|
||||
"type": [
|
||||
"string.condition",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"age": {
|
||||
"type": [
|
||||
"integer.condition",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"billing_address": {
|
||||
"type": [
|
||||
"address.filter",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"birth_date": {
|
||||
"type": [
|
||||
"date.condition",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"address": {},
|
||||
"address.filter": {
|
||||
"type": "filter",
|
||||
"compiledPropertyNames": [
|
||||
"$and",
|
||||
"$or",
|
||||
"city"
|
||||
],
|
||||
"properties": {
|
||||
"$and": {
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
],
|
||||
"items": {
|
||||
"compiledPropertyNames": [
|
||||
"$and",
|
||||
"$or",
|
||||
"city"
|
||||
],
|
||||
"type": "address.filter"
|
||||
}
|
||||
},
|
||||
"$or": {
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
],
|
||||
"items": {
|
||||
"compiledPropertyNames": [
|
||||
"$and",
|
||||
"$or",
|
||||
"city"
|
||||
],
|
||||
"type": "address.filter"
|
||||
}
|
||||
},
|
||||
"city": {
|
||||
"type": [
|
||||
"string.condition",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"filter": {},
|
||||
"condition": {},
|
||||
"string.condition": {},
|
||||
"integer.condition": {},
|
||||
"date.condition": {},
|
||||
"search": {},
|
||||
"search.filter": {
|
||||
"type": "filter",
|
||||
"compiledPropertyNames": [
|
||||
"$and",
|
||||
"$or",
|
||||
"filter",
|
||||
"name"
|
||||
],
|
||||
"properties": {
|
||||
"$and": {
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
],
|
||||
"items": {
|
||||
"compiledPropertyNames": [
|
||||
"$and",
|
||||
"$or",
|
||||
"filter",
|
||||
"name"
|
||||
],
|
||||
"type": "search.filter"
|
||||
}
|
||||
},
|
||||
"$or": {
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
],
|
||||
"items": {
|
||||
"compiledPropertyNames": [
|
||||
"$and",
|
||||
"$or",
|
||||
"filter",
|
||||
"name"
|
||||
],
|
||||
"type": "search.filter"
|
||||
}
|
||||
},
|
||||
"filter": {
|
||||
"type": [
|
||||
"filter.filter",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"name": {
|
||||
"type": [
|
||||
"string.condition",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
File diff suppressed because it is too large
Load Diff
@ -2,13 +2,18 @@
|
||||
{
|
||||
"description": "a schema given for items",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"items_0_0": {
|
||||
"items": {
|
||||
"type": "integer"
|
||||
"types": [
|
||||
{
|
||||
"name": "items_0_0",
|
||||
"schemas": {
|
||||
"items_0_0": {
|
||||
"items": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -64,11 +69,16 @@
|
||||
{
|
||||
"description": "items with boolean schema (true)",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"items_1_0": {
|
||||
"items": true
|
||||
"types": [
|
||||
{
|
||||
"name": "items_1_0",
|
||||
"schemas": {
|
||||
"items_1_0": {
|
||||
"items": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -98,11 +108,16 @@
|
||||
{
|
||||
"description": "items with boolean schema (false)",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"items_2_0": {
|
||||
"items": false
|
||||
"types": [
|
||||
{
|
||||
"name": "items_2_0",
|
||||
"schemas": {
|
||||
"items_2_0": {
|
||||
"items": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -132,41 +147,56 @@
|
||||
{
|
||||
"description": "items and subitems",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"items_3_0": {
|
||||
"type": "array",
|
||||
"items": false,
|
||||
"prefixItems": [
|
||||
{
|
||||
"type": "item"
|
||||
},
|
||||
{
|
||||
"type": "item"
|
||||
},
|
||||
{
|
||||
"type": "item"
|
||||
"types": [
|
||||
{
|
||||
"name": "items_3_0",
|
||||
"schemas": {
|
||||
"items_3_0": {
|
||||
"type": "array",
|
||||
"items": false,
|
||||
"prefixItems": [
|
||||
{
|
||||
"type": "item"
|
||||
},
|
||||
{
|
||||
"type": "item"
|
||||
},
|
||||
{
|
||||
"type": "item"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"item": {
|
||||
"type": "array",
|
||||
"items": false,
|
||||
"prefixItems": [
|
||||
{
|
||||
"type": "sub-item"
|
||||
},
|
||||
{
|
||||
"type": "sub-item"
|
||||
{
|
||||
"name": "item",
|
||||
"schemas": {
|
||||
"item": {
|
||||
"type": "array",
|
||||
"items": false,
|
||||
"prefixItems": [
|
||||
{
|
||||
"type": "sub-item"
|
||||
},
|
||||
{
|
||||
"type": "sub-item"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"sub-item": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"foo"
|
||||
]
|
||||
{
|
||||
"name": "sub-item",
|
||||
"schemas": {
|
||||
"sub-item": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"foo"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -368,23 +398,28 @@
|
||||
{
|
||||
"description": "nested items",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"items_4_0": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"types": [
|
||||
{
|
||||
"name": "items_4_0",
|
||||
"schemas": {
|
||||
"items_4_0": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "number"
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -500,16 +535,21 @@
|
||||
{
|
||||
"description": "prefixItems with no additional items allowed",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"items_5_0": {
|
||||
"prefixItems": [
|
||||
{},
|
||||
{},
|
||||
{}
|
||||
],
|
||||
"items": false
|
||||
"types": [
|
||||
{
|
||||
"name": "items_5_0",
|
||||
"schemas": {
|
||||
"items_5_0": {
|
||||
"prefixItems": [
|
||||
{},
|
||||
{},
|
||||
{}
|
||||
],
|
||||
"items": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -576,22 +616,27 @@
|
||||
{
|
||||
"description": "items does not look in applicators, valid case",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"items_6_0": {
|
||||
"allOf": [
|
||||
{
|
||||
"prefixItems": [
|
||||
"types": [
|
||||
{
|
||||
"name": "items_6_0",
|
||||
"schemas": {
|
||||
"items_6_0": {
|
||||
"allOf": [
|
||||
{
|
||||
"minimum": 3
|
||||
"prefixItems": [
|
||||
{
|
||||
"minimum": 3
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
],
|
||||
"items": {
|
||||
"minimum": 5
|
||||
}
|
||||
}
|
||||
],
|
||||
"items": {
|
||||
"minimum": 5
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -623,18 +668,23 @@
|
||||
{
|
||||
"description": "prefixItems validation adjusts the starting index for items",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"items_7_0": {
|
||||
"prefixItems": [
|
||||
{
|
||||
"type": "string"
|
||||
"types": [
|
||||
{
|
||||
"name": "items_7_0",
|
||||
"schemas": {
|
||||
"items_7_0": {
|
||||
"prefixItems": [
|
||||
{
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"items": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
],
|
||||
"items": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -667,14 +717,19 @@
|
||||
{
|
||||
"description": "items with heterogeneous array",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"items_8_0": {
|
||||
"prefixItems": [
|
||||
{}
|
||||
],
|
||||
"items": false
|
||||
"types": [
|
||||
{
|
||||
"name": "items_8_0",
|
||||
"schemas": {
|
||||
"items_8_0": {
|
||||
"prefixItems": [
|
||||
{}
|
||||
],
|
||||
"items": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -706,13 +761,18 @@
|
||||
{
|
||||
"description": "items with null instance elements",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"items_9_0": {
|
||||
"items": {
|
||||
"type": "null"
|
||||
"types": [
|
||||
{
|
||||
"name": "items_9_0",
|
||||
"schemas": {
|
||||
"items_9_0": {
|
||||
"items": {
|
||||
"type": "null"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -731,12 +791,17 @@
|
||||
{
|
||||
"description": "extensible: true allows extra items (when items is false)",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"items_10_0": {
|
||||
"items": false,
|
||||
"extensible": true
|
||||
"types": [
|
||||
{
|
||||
"name": "items_10_0",
|
||||
"schemas": {
|
||||
"items_10_0": {
|
||||
"items": false,
|
||||
"extensible": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -755,14 +820,19 @@
|
||||
{
|
||||
"description": "extensible: true allows extra properties for items",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"items_11_0": {
|
||||
"items": {
|
||||
"minimum": 5
|
||||
},
|
||||
"extensible": true
|
||||
"types": [
|
||||
{
|
||||
"name": "items_11_0",
|
||||
"schemas": {
|
||||
"items_11_0": {
|
||||
"items": {
|
||||
"minimum": 5
|
||||
},
|
||||
"extensible": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -793,12 +863,17 @@
|
||||
{
|
||||
"description": "array: simple extensible array",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"items_12_0": {
|
||||
"type": "array",
|
||||
"extensible": true
|
||||
"types": [
|
||||
{
|
||||
"name": "items_12_0",
|
||||
"schemas": {
|
||||
"items_12_0": {
|
||||
"type": "array",
|
||||
"extensible": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -827,12 +902,17 @@
|
||||
{
|
||||
"description": "array: strict array",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"items_13_0": {
|
||||
"type": "array",
|
||||
"extensible": false
|
||||
"types": [
|
||||
{
|
||||
"name": "items_13_0",
|
||||
"schemas": {
|
||||
"items_13_0": {
|
||||
"type": "array",
|
||||
"extensible": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -860,14 +940,19 @@
|
||||
{
|
||||
"description": "array: items extensible",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"items_14_0": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"extensible": true
|
||||
"types": [
|
||||
{
|
||||
"name": "items_14_0",
|
||||
"schemas": {
|
||||
"items_14_0": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"extensible": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -897,15 +982,20 @@
|
||||
{
|
||||
"description": "array: items strict",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"items_15_0": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"extensible": false
|
||||
"types": [
|
||||
{
|
||||
"name": "items_15_0",
|
||||
"schemas": {
|
||||
"items_15_0": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"extensible": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
|
||||
@ -2,12 +2,17 @@
|
||||
{
|
||||
"description": "maxContains without contains is ignored",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"maxContains_0_0": {
|
||||
"maxContains": 1,
|
||||
"extensible": true
|
||||
"types": [
|
||||
{
|
||||
"name": "maxContains_0_0",
|
||||
"schemas": {
|
||||
"maxContains_0_0": {
|
||||
"maxContains": 1,
|
||||
"extensible": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -38,15 +43,20 @@
|
||||
{
|
||||
"description": "maxContains with contains",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"maxContains_1_0": {
|
||||
"contains": {
|
||||
"const": 1
|
||||
},
|
||||
"maxContains": 1,
|
||||
"extensible": true
|
||||
"types": [
|
||||
{
|
||||
"name": "maxContains_1_0",
|
||||
"schemas": {
|
||||
"maxContains_1_0": {
|
||||
"contains": {
|
||||
"const": 1
|
||||
},
|
||||
"maxContains": 1,
|
||||
"extensible": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -111,15 +121,20 @@
|
||||
{
|
||||
"description": "maxContains with contains, value with a decimal",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"maxContains_2_0": {
|
||||
"contains": {
|
||||
"const": 1
|
||||
},
|
||||
"maxContains": 1,
|
||||
"extensible": true
|
||||
"types": [
|
||||
{
|
||||
"name": "maxContains_2_0",
|
||||
"schemas": {
|
||||
"maxContains_2_0": {
|
||||
"contains": {
|
||||
"const": 1
|
||||
},
|
||||
"maxContains": 1,
|
||||
"extensible": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -150,16 +165,21 @@
|
||||
{
|
||||
"description": "minContains < maxContains",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"maxContains_3_0": {
|
||||
"contains": {
|
||||
"const": 1
|
||||
},
|
||||
"minContains": 1,
|
||||
"maxContains": 3,
|
||||
"extensible": true
|
||||
"types": [
|
||||
{
|
||||
"name": "maxContains_3_0",
|
||||
"schemas": {
|
||||
"maxContains_3_0": {
|
||||
"contains": {
|
||||
"const": 1
|
||||
},
|
||||
"minContains": 1,
|
||||
"maxContains": 3,
|
||||
"extensible": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -202,15 +222,20 @@
|
||||
{
|
||||
"description": "extensible: true allows non-matching items in maxContains",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"maxContains_4_0": {
|
||||
"contains": {
|
||||
"const": 1
|
||||
},
|
||||
"maxContains": 1,
|
||||
"extensible": true
|
||||
"types": [
|
||||
{
|
||||
"name": "maxContains_4_0",
|
||||
"schemas": {
|
||||
"maxContains_4_0": {
|
||||
"contains": {
|
||||
"const": 1
|
||||
},
|
||||
"maxContains": 1,
|
||||
"extensible": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
|
||||
@ -2,12 +2,17 @@
|
||||
{
|
||||
"description": "maxItems validation",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"maxItems_0_0": {
|
||||
"maxItems": 2,
|
||||
"extensible": true
|
||||
"types": [
|
||||
{
|
||||
"name": "maxItems_0_0",
|
||||
"schemas": {
|
||||
"maxItems_0_0": {
|
||||
"maxItems": 2,
|
||||
"extensible": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -60,12 +65,17 @@
|
||||
{
|
||||
"description": "maxItems validation with a decimal",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"maxItems_1_0": {
|
||||
"maxItems": 2,
|
||||
"extensible": true
|
||||
"types": [
|
||||
{
|
||||
"name": "maxItems_1_0",
|
||||
"schemas": {
|
||||
"maxItems_1_0": {
|
||||
"maxItems": 2,
|
||||
"extensible": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -97,12 +107,17 @@
|
||||
{
|
||||
"description": "extensible: true allows extra items in maxItems (but counted)",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"maxItems_2_0": {
|
||||
"maxItems": 2,
|
||||
"extensible": true
|
||||
"types": [
|
||||
{
|
||||
"name": "maxItems_2_0",
|
||||
"schemas": {
|
||||
"maxItems_2_0": {
|
||||
"maxItems": 2,
|
||||
"extensible": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
|
||||
@ -2,11 +2,16 @@
|
||||
{
|
||||
"description": "maxLength validation",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"maxLength_0_0": {
|
||||
"maxLength": 2
|
||||
"types": [
|
||||
{
|
||||
"name": "maxLength_0_0",
|
||||
"schemas": {
|
||||
"maxLength_0_0": {
|
||||
"maxLength": 2
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -47,7 +52,7 @@
|
||||
},
|
||||
{
|
||||
"description": "two graphemes is long enough",
|
||||
"data": "💩💩",
|
||||
"data": "\ud83d\udca9\ud83d\udca9",
|
||||
"schema_id": "maxLength_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
@ -59,11 +64,16 @@
|
||||
{
|
||||
"description": "maxLength validation with a decimal",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"maxLength_1_0": {
|
||||
"maxLength": 2
|
||||
"types": [
|
||||
{
|
||||
"name": "maxLength_1_0",
|
||||
"schemas": {
|
||||
"maxLength_1_0": {
|
||||
"maxLength": 2
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
|
||||
@ -2,12 +2,17 @@
|
||||
{
|
||||
"description": "maxProperties validation",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"maxProperties_0_0": {
|
||||
"maxProperties": 2,
|
||||
"extensible": true
|
||||
"types": [
|
||||
{
|
||||
"name": "maxProperties_0_0",
|
||||
"schemas": {
|
||||
"maxProperties_0_0": {
|
||||
"maxProperties": 2,
|
||||
"extensible": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -82,12 +87,17 @@
|
||||
{
|
||||
"description": "maxProperties validation with a decimal",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"maxProperties_1_0": {
|
||||
"maxProperties": 2,
|
||||
"extensible": true
|
||||
"types": [
|
||||
{
|
||||
"name": "maxProperties_1_0",
|
||||
"schemas": {
|
||||
"maxProperties_1_0": {
|
||||
"maxProperties": 2,
|
||||
"extensible": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -119,12 +129,17 @@
|
||||
{
|
||||
"description": "maxProperties = 0 means the object is empty",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"maxProperties_2_0": {
|
||||
"maxProperties": 0,
|
||||
"extensible": true
|
||||
"types": [
|
||||
{
|
||||
"name": "maxProperties_2_0",
|
||||
"schemas": {
|
||||
"maxProperties_2_0": {
|
||||
"maxProperties": 0,
|
||||
"extensible": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -152,12 +167,17 @@
|
||||
{
|
||||
"description": "extensible: true allows extra properties in maxProperties (though maxProperties still counts them!)",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"maxProperties_3_0": {
|
||||
"maxProperties": 2,
|
||||
"extensible": true
|
||||
"types": [
|
||||
{
|
||||
"name": "maxProperties_3_0",
|
||||
"schemas": {
|
||||
"maxProperties_3_0": {
|
||||
"maxProperties": 2,
|
||||
"extensible": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
|
||||
@ -2,11 +2,16 @@
|
||||
{
|
||||
"description": "maximum validation",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"maximum_0_0": {
|
||||
"maximum": 3
|
||||
"types": [
|
||||
{
|
||||
"name": "maximum_0_0",
|
||||
"schemas": {
|
||||
"maximum_0_0": {
|
||||
"maximum": 3
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -50,11 +55,16 @@
|
||||
{
|
||||
"description": "maximum validation with unsigned integer",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"maximum_1_0": {
|
||||
"maximum": 300
|
||||
"types": [
|
||||
{
|
||||
"name": "maximum_1_0",
|
||||
"schemas": {
|
||||
"maximum_1_0": {
|
||||
"maximum": 300
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
|
||||
@ -2,23 +2,33 @@
|
||||
{
|
||||
"description": "merging: properties accumulate",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"base_0": {
|
||||
"properties": {
|
||||
"base_prop": {
|
||||
"type": "string"
|
||||
"types": [
|
||||
{
|
||||
"name": "base_0",
|
||||
"schemas": {
|
||||
"base_0": {
|
||||
"properties": {
|
||||
"base_prop": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"merge_0_0": {
|
||||
"type": "base_0",
|
||||
"properties": {
|
||||
"child_prop": {
|
||||
"type": "string"
|
||||
{
|
||||
"name": "merge_0_0",
|
||||
"schemas": {
|
||||
"merge_0_0": {
|
||||
"type": "base_0",
|
||||
"properties": {
|
||||
"child_prop": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -58,29 +68,39 @@
|
||||
{
|
||||
"description": "merging: required fields accumulate",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"base_1": {
|
||||
"properties": {
|
||||
"a": {
|
||||
"type": "string"
|
||||
"types": [
|
||||
{
|
||||
"name": "base_1",
|
||||
"schemas": {
|
||||
"base_1": {
|
||||
"properties": {
|
||||
"a": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"a"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"a"
|
||||
]
|
||||
}
|
||||
},
|
||||
"merge_1_0": {
|
||||
"type": "base_1",
|
||||
"properties": {
|
||||
"b": {
|
||||
"type": "string"
|
||||
{
|
||||
"name": "merge_1_0",
|
||||
"schemas": {
|
||||
"merge_1_0": {
|
||||
"type": "base_1",
|
||||
"properties": {
|
||||
"b": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"b"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"b"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -138,36 +158,46 @@
|
||||
{
|
||||
"description": "merging: dependencies accumulate",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"base_2": {
|
||||
"properties": {
|
||||
"trigger": {
|
||||
"type": "string"
|
||||
},
|
||||
"base_dep": {
|
||||
"type": "string"
|
||||
"types": [
|
||||
{
|
||||
"name": "base_2",
|
||||
"schemas": {
|
||||
"base_2": {
|
||||
"properties": {
|
||||
"trigger": {
|
||||
"type": "string"
|
||||
},
|
||||
"base_dep": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"trigger": [
|
||||
"base_dep"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"trigger": [
|
||||
"base_dep"
|
||||
]
|
||||
}
|
||||
},
|
||||
"merge_2_0": {
|
||||
"type": "base_2",
|
||||
"properties": {
|
||||
"child_dep": {
|
||||
"type": "string"
|
||||
{
|
||||
"name": "merge_2_0",
|
||||
"schemas": {
|
||||
"merge_2_0": {
|
||||
"type": "base_2",
|
||||
"properties": {
|
||||
"child_dep": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"trigger": [
|
||||
"child_dep"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"trigger": [
|
||||
"child_dep"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -228,33 +258,43 @@
|
||||
{
|
||||
"description": "merging: form and display do NOT merge",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"base_3": {
|
||||
"properties": {
|
||||
"a": {
|
||||
"type": "string"
|
||||
},
|
||||
"b": {
|
||||
"type": "string"
|
||||
"types": [
|
||||
{
|
||||
"name": "base_3",
|
||||
"schemas": {
|
||||
"base_3": {
|
||||
"properties": {
|
||||
"a": {
|
||||
"type": "string"
|
||||
},
|
||||
"b": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"form": [
|
||||
"a",
|
||||
"b"
|
||||
]
|
||||
}
|
||||
},
|
||||
"form": [
|
||||
"a",
|
||||
"b"
|
||||
]
|
||||
}
|
||||
},
|
||||
"merge_3_0": {
|
||||
"type": "base_3",
|
||||
"properties": {
|
||||
"c": {
|
||||
"type": "string"
|
||||
{
|
||||
"name": "merge_3_0",
|
||||
"schemas": {
|
||||
"merge_3_0": {
|
||||
"type": "base_3",
|
||||
"properties": {
|
||||
"c": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"form": [
|
||||
"c"
|
||||
]
|
||||
}
|
||||
},
|
||||
"form": [
|
||||
"c"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
|
||||
@ -869,6 +869,106 @@
|
||||
"notify": true,
|
||||
"relationship": false
|
||||
},
|
||||
{
|
||||
"name": "account",
|
||||
"hierarchy": [
|
||||
"account",
|
||||
"entity"
|
||||
],
|
||||
"fields": [
|
||||
"id",
|
||||
"type",
|
||||
"name",
|
||||
"archived",
|
||||
"created_at",
|
||||
"created_by",
|
||||
"modified_at",
|
||||
"modified_by",
|
||||
"kind",
|
||||
"routing_number",
|
||||
"card_number"
|
||||
],
|
||||
"grouped_fields": {
|
||||
"entity": [
|
||||
"id",
|
||||
"type",
|
||||
"name",
|
||||
"archived",
|
||||
"created_at",
|
||||
"created_by",
|
||||
"modified_at",
|
||||
"modified_by"
|
||||
],
|
||||
"account": [
|
||||
"id",
|
||||
"type",
|
||||
"kind",
|
||||
"routing_number",
|
||||
"card_number"
|
||||
]
|
||||
},
|
||||
"field_types": {
|
||||
"id": "uuid",
|
||||
"type": "text",
|
||||
"archived": "boolean",
|
||||
"name": "text",
|
||||
"created_at": "timestamptz",
|
||||
"created_by": "uuid",
|
||||
"modified_at": "timestamptz",
|
||||
"modified_by": "uuid",
|
||||
"kind": "text",
|
||||
"routing_number": "text",
|
||||
"card_number": "text"
|
||||
},
|
||||
"schemas": {
|
||||
"account": {
|
||||
"type": "entity",
|
||||
"properties": {
|
||||
"kind": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"cases": [
|
||||
{
|
||||
"when": {
|
||||
"properties": {
|
||||
"kind": {
|
||||
"const": "checking"
|
||||
}
|
||||
}
|
||||
},
|
||||
"then": {
|
||||
"properties": {
|
||||
"routing_number": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"when": {
|
||||
"properties": {
|
||||
"kind": {
|
||||
"const": "credit"
|
||||
}
|
||||
}
|
||||
},
|
||||
"then": {
|
||||
"properties": {
|
||||
"card_number": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"lookup_fields": [],
|
||||
"historical": true,
|
||||
"notify": true,
|
||||
"relationship": false
|
||||
},
|
||||
{
|
||||
"name": "invoice",
|
||||
"schemas": {
|
||||
@ -3107,6 +3207,103 @@
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Insert account with checking kind and routing number",
|
||||
"action": "merge",
|
||||
"schema_id": "account",
|
||||
"data": {
|
||||
"id": "11111111-2222-3333-4444-555555555555",
|
||||
"type": "account",
|
||||
"kind": "checking",
|
||||
"routing_number": "123456789"
|
||||
},
|
||||
"expect": {
|
||||
"success": true,
|
||||
"sql": [
|
||||
[
|
||||
"SELECT to_jsonb(t1.*) || to_jsonb(t2.*)",
|
||||
"FROM agreego.\"account\" t1",
|
||||
"LEFT JOIN agreego.\"entity\" t2 ON t2.id = t1.id",
|
||||
"WHERE t1.id = '11111111-2222-3333-4444-555555555555'"
|
||||
],
|
||||
[
|
||||
"INSERT INTO agreego.\"entity\" (",
|
||||
" \"created_at\",",
|
||||
" \"created_by\",",
|
||||
" \"id\",",
|
||||
" \"modified_at\",",
|
||||
" \"modified_by\",",
|
||||
" \"type\"",
|
||||
")",
|
||||
"VALUES (",
|
||||
" '{{timestamp}}',",
|
||||
" '00000000-0000-0000-0000-000000000000',",
|
||||
" '11111111-2222-3333-4444-555555555555',",
|
||||
" '{{timestamp}}',",
|
||||
" '00000000-0000-0000-0000-000000000000',",
|
||||
" 'account'",
|
||||
")"
|
||||
],
|
||||
[
|
||||
"INSERT INTO agreego.\"account\" (",
|
||||
" \"id\",",
|
||||
" \"kind\",",
|
||||
" \"routing_number\",",
|
||||
" \"type\"",
|
||||
")",
|
||||
"VALUES (",
|
||||
" '11111111-2222-3333-4444-555555555555',",
|
||||
" 'checking',",
|
||||
" '123456789',",
|
||||
" 'account'",
|
||||
")"
|
||||
],
|
||||
[
|
||||
"INSERT INTO agreego.change (",
|
||||
" \"old\",",
|
||||
" \"new\",",
|
||||
" entity_id,",
|
||||
" id,",
|
||||
" kind,",
|
||||
" modified_at,",
|
||||
" modified_by",
|
||||
")",
|
||||
"VALUES (",
|
||||
" NULL,",
|
||||
" '{",
|
||||
" \"kind\":\"checking\",",
|
||||
" \"routing_number\":\"123456789\",",
|
||||
" \"type\":\"account\"",
|
||||
" }',",
|
||||
" '11111111-2222-3333-4444-555555555555',",
|
||||
" '{{uuid}}',",
|
||||
" 'create',",
|
||||
" '{{timestamp}}',",
|
||||
" '00000000-0000-0000-0000-000000000000'",
|
||||
")"
|
||||
],
|
||||
[
|
||||
"SELECT pg_notify('entity', '{",
|
||||
" \"complete\":{",
|
||||
" \"created_at\":\"{{timestamp}}\",",
|
||||
" \"created_by\":\"00000000-0000-0000-0000-000000000000\",",
|
||||
" \"id\":\"11111111-2222-3333-4444-555555555555\",",
|
||||
" \"kind\":\"checking\",",
|
||||
" \"modified_at\":\"{{timestamp}}\",",
|
||||
" \"modified_by\":\"00000000-0000-0000-0000-000000000000\",",
|
||||
" \"routing_number\":\"123456789\",",
|
||||
" \"type\":\"account\"",
|
||||
" },",
|
||||
" \"new\":{",
|
||||
" \"kind\":\"checking\",",
|
||||
" \"routing_number\":\"123456789\",",
|
||||
" \"type\":\"account\"",
|
||||
" }",
|
||||
" }')"
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@ -2,12 +2,17 @@
|
||||
{
|
||||
"description": "minContains without contains is ignored",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"minContains_0_0": {
|
||||
"minContains": 1,
|
||||
"extensible": true
|
||||
"types": [
|
||||
{
|
||||
"name": "minContains_0_0",
|
||||
"schemas": {
|
||||
"minContains_0_0": {
|
||||
"minContains": 1,
|
||||
"extensible": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -35,15 +40,20 @@
|
||||
{
|
||||
"description": "minContains=1 with contains",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"minContains_1_0": {
|
||||
"contains": {
|
||||
"const": 1
|
||||
},
|
||||
"minContains": 1,
|
||||
"extensible": true
|
||||
"types": [
|
||||
{
|
||||
"name": "minContains_1_0",
|
||||
"schemas": {
|
||||
"minContains_1_0": {
|
||||
"contains": {
|
||||
"const": 1
|
||||
},
|
||||
"minContains": 1,
|
||||
"extensible": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -106,15 +116,20 @@
|
||||
{
|
||||
"description": "minContains=2 with contains",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"minContains_2_0": {
|
||||
"contains": {
|
||||
"const": 1
|
||||
},
|
||||
"minContains": 2,
|
||||
"extensible": true
|
||||
"types": [
|
||||
{
|
||||
"name": "minContains_2_0",
|
||||
"schemas": {
|
||||
"minContains_2_0": {
|
||||
"contains": {
|
||||
"const": 1
|
||||
},
|
||||
"minContains": 2,
|
||||
"extensible": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -192,15 +207,20 @@
|
||||
{
|
||||
"description": "minContains=2 with contains with a decimal value",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"minContains_3_0": {
|
||||
"contains": {
|
||||
"const": 1
|
||||
},
|
||||
"minContains": 2,
|
||||
"extensible": true
|
||||
"types": [
|
||||
{
|
||||
"name": "minContains_3_0",
|
||||
"schemas": {
|
||||
"minContains_3_0": {
|
||||
"contains": {
|
||||
"const": 1
|
||||
},
|
||||
"minContains": 2,
|
||||
"extensible": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -231,16 +251,21 @@
|
||||
{
|
||||
"description": "maxContains = minContains",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"minContains_4_0": {
|
||||
"contains": {
|
||||
"const": 1
|
||||
},
|
||||
"maxContains": 2,
|
||||
"minContains": 2,
|
||||
"extensible": true
|
||||
"types": [
|
||||
{
|
||||
"name": "minContains_4_0",
|
||||
"schemas": {
|
||||
"minContains_4_0": {
|
||||
"contains": {
|
||||
"const": 1
|
||||
},
|
||||
"maxContains": 2,
|
||||
"minContains": 2,
|
||||
"extensible": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -293,16 +318,21 @@
|
||||
{
|
||||
"description": "maxContains < minContains",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"minContains_5_0": {
|
||||
"contains": {
|
||||
"const": 1
|
||||
},
|
||||
"maxContains": 1,
|
||||
"minContains": 3,
|
||||
"extensible": true
|
||||
"types": [
|
||||
{
|
||||
"name": "minContains_5_0",
|
||||
"schemas": {
|
||||
"minContains_5_0": {
|
||||
"contains": {
|
||||
"const": 1
|
||||
},
|
||||
"maxContains": 1,
|
||||
"minContains": 3,
|
||||
"extensible": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -355,15 +385,20 @@
|
||||
{
|
||||
"description": "minContains = 0",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"minContains_6_0": {
|
||||
"contains": {
|
||||
"const": 1
|
||||
},
|
||||
"minContains": 0,
|
||||
"extensible": true
|
||||
"types": [
|
||||
{
|
||||
"name": "minContains_6_0",
|
||||
"schemas": {
|
||||
"minContains_6_0": {
|
||||
"contains": {
|
||||
"const": 1
|
||||
},
|
||||
"minContains": 0,
|
||||
"extensible": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -391,16 +426,21 @@
|
||||
{
|
||||
"description": "minContains = 0 with maxContains",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"minContains_7_0": {
|
||||
"contains": {
|
||||
"const": 1
|
||||
},
|
||||
"minContains": 0,
|
||||
"maxContains": 1,
|
||||
"extensible": true
|
||||
"types": [
|
||||
{
|
||||
"name": "minContains_7_0",
|
||||
"schemas": {
|
||||
"minContains_7_0": {
|
||||
"contains": {
|
||||
"const": 1
|
||||
},
|
||||
"minContains": 0,
|
||||
"maxContains": 1,
|
||||
"extensible": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -440,15 +480,20 @@
|
||||
{
|
||||
"description": "extensible: true allows non-matching items in minContains",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"minContains_8_0": {
|
||||
"contains": {
|
||||
"const": 1
|
||||
},
|
||||
"minContains": 1,
|
||||
"extensible": true
|
||||
"types": [
|
||||
{
|
||||
"name": "minContains_8_0",
|
||||
"schemas": {
|
||||
"minContains_8_0": {
|
||||
"contains": {
|
||||
"const": 1
|
||||
},
|
||||
"minContains": 1,
|
||||
"extensible": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
|
||||
@ -2,12 +2,17 @@
|
||||
{
|
||||
"description": "minItems validation",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"minItems_0_0": {
|
||||
"minItems": 1,
|
||||
"extensible": true
|
||||
"types": [
|
||||
{
|
||||
"name": "minItems_0_0",
|
||||
"schemas": {
|
||||
"minItems_0_0": {
|
||||
"minItems": 1,
|
||||
"extensible": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -56,12 +61,17 @@
|
||||
{
|
||||
"description": "minItems validation with a decimal",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"minItems_1_0": {
|
||||
"minItems": 1,
|
||||
"extensible": true
|
||||
"types": [
|
||||
{
|
||||
"name": "minItems_1_0",
|
||||
"schemas": {
|
||||
"minItems_1_0": {
|
||||
"minItems": 1,
|
||||
"extensible": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -90,12 +100,17 @@
|
||||
{
|
||||
"description": "extensible: true allows extra items in minItems",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"minItems_2_0": {
|
||||
"minItems": 1,
|
||||
"extensible": true
|
||||
"types": [
|
||||
{
|
||||
"name": "minItems_2_0",
|
||||
"schemas": {
|
||||
"minItems_2_0": {
|
||||
"minItems": 1,
|
||||
"extensible": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
|
||||
@ -2,11 +2,16 @@
|
||||
{
|
||||
"description": "minLength validation",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"minLength_0_0": {
|
||||
"minLength": 2
|
||||
"types": [
|
||||
{
|
||||
"name": "minLength_0_0",
|
||||
"schemas": {
|
||||
"minLength_0_0": {
|
||||
"minLength": 2
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -47,7 +52,7 @@
|
||||
},
|
||||
{
|
||||
"description": "one grapheme is not long enough",
|
||||
"data": "💩",
|
||||
"data": "\ud83d\udca9",
|
||||
"schema_id": "minLength_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
@ -59,11 +64,16 @@
|
||||
{
|
||||
"description": "minLength validation with a decimal",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"minLength_1_0": {
|
||||
"minLength": 2
|
||||
"types": [
|
||||
{
|
||||
"name": "minLength_1_0",
|
||||
"schemas": {
|
||||
"minLength_1_0": {
|
||||
"minLength": 2
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
|
||||
@ -2,12 +2,17 @@
|
||||
{
|
||||
"description": "minProperties validation",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"minProperties_0_0": {
|
||||
"minProperties": 1,
|
||||
"extensible": true
|
||||
"types": [
|
||||
{
|
||||
"name": "minProperties_0_0",
|
||||
"schemas": {
|
||||
"minProperties_0_0": {
|
||||
"minProperties": 1,
|
||||
"extensible": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -74,12 +79,17 @@
|
||||
{
|
||||
"description": "minProperties validation with a decimal",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"minProperties_1_0": {
|
||||
"minProperties": 1,
|
||||
"extensible": true
|
||||
"types": [
|
||||
{
|
||||
"name": "minProperties_1_0",
|
||||
"schemas": {
|
||||
"minProperties_1_0": {
|
||||
"minProperties": 1,
|
||||
"extensible": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -108,12 +118,17 @@
|
||||
{
|
||||
"description": "extensible: true allows extra properties in minProperties",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"minProperties_2_0": {
|
||||
"minProperties": 1,
|
||||
"extensible": true
|
||||
"types": [
|
||||
{
|
||||
"name": "minProperties_2_0",
|
||||
"schemas": {
|
||||
"minProperties_2_0": {
|
||||
"minProperties": 1,
|
||||
"extensible": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
|
||||
@ -2,11 +2,16 @@
|
||||
{
|
||||
"description": "minimum validation",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"minimum_0_0": {
|
||||
"minimum": 1.1
|
||||
"types": [
|
||||
{
|
||||
"name": "minimum_0_0",
|
||||
"schemas": {
|
||||
"minimum_0_0": {
|
||||
"minimum": 1.1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -50,11 +55,16 @@
|
||||
{
|
||||
"description": "minimum validation with signed integer",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"minimum_1_0": {
|
||||
"minimum": -2
|
||||
"types": [
|
||||
{
|
||||
"name": "minimum_1_0",
|
||||
"schemas": {
|
||||
"minimum_1_0": {
|
||||
"minimum": -2
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
|
||||
@ -2,11 +2,16 @@
|
||||
{
|
||||
"description": "by int",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"multipleOf_0_0": {
|
||||
"multipleOf": 2
|
||||
"types": [
|
||||
{
|
||||
"name": "multipleOf_0_0",
|
||||
"schemas": {
|
||||
"multipleOf_0_0": {
|
||||
"multipleOf": 2
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -41,11 +46,16 @@
|
||||
{
|
||||
"description": "by number",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"multipleOf_1_0": {
|
||||
"multipleOf": 1.5
|
||||
"types": [
|
||||
{
|
||||
"name": "multipleOf_1_0",
|
||||
"schemas": {
|
||||
"multipleOf_1_0": {
|
||||
"multipleOf": 1.5
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -80,11 +90,16 @@
|
||||
{
|
||||
"description": "by small number",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"multipleOf_2_0": {
|
||||
"multipleOf": 0.0001
|
||||
"types": [
|
||||
{
|
||||
"name": "multipleOf_2_0",
|
||||
"schemas": {
|
||||
"multipleOf_2_0": {
|
||||
"multipleOf": 0.0001
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -110,12 +125,17 @@
|
||||
{
|
||||
"description": "small multiple of large integer",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"multipleOf_3_0": {
|
||||
"type": "integer",
|
||||
"multipleOf": 1e-08
|
||||
"types": [
|
||||
{
|
||||
"name": "multipleOf_3_0",
|
||||
"schemas": {
|
||||
"multipleOf_3_0": {
|
||||
"type": "integer",
|
||||
"multipleOf": 1e-08
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
|
||||
@ -2,13 +2,18 @@
|
||||
{
|
||||
"description": "not",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"not_0_0": {
|
||||
"not": {
|
||||
"type": "integer"
|
||||
"types": [
|
||||
{
|
||||
"name": "not_0_0",
|
||||
"schemas": {
|
||||
"not_0_0": {
|
||||
"not": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -34,16 +39,21 @@
|
||||
{
|
||||
"description": "not multiple types",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"not_1_0": {
|
||||
"not": {
|
||||
"type": [
|
||||
"integer",
|
||||
"boolean"
|
||||
]
|
||||
"types": [
|
||||
{
|
||||
"name": "not_1_0",
|
||||
"schemas": {
|
||||
"not_1_0": {
|
||||
"not": {
|
||||
"type": [
|
||||
"integer",
|
||||
"boolean"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -78,19 +88,24 @@
|
||||
{
|
||||
"description": "not more complex schema",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"not_2_0": {
|
||||
"not": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "string"
|
||||
}
|
||||
"types": [
|
||||
{
|
||||
"name": "not_2_0",
|
||||
"schemas": {
|
||||
"not_2_0": {
|
||||
"not": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"extensible": true
|
||||
}
|
||||
},
|
||||
"extensible": true
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -129,15 +144,20 @@
|
||||
{
|
||||
"description": "forbidden property",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"not_3_0": {
|
||||
"properties": {
|
||||
"foo": {
|
||||
"not": {}
|
||||
"types": [
|
||||
{
|
||||
"name": "not_3_0",
|
||||
"schemas": {
|
||||
"not_3_0": {
|
||||
"properties": {
|
||||
"foo": {
|
||||
"not": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -166,11 +186,16 @@
|
||||
{
|
||||
"description": "forbid everything with empty schema",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"not_4_0": {
|
||||
"not": {}
|
||||
"types": [
|
||||
{
|
||||
"name": "not_4_0",
|
||||
"schemas": {
|
||||
"not_4_0": {
|
||||
"not": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -263,11 +288,16 @@
|
||||
{
|
||||
"description": "forbid everything with boolean schema true",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"not_5_0": {
|
||||
"not": true
|
||||
"types": [
|
||||
{
|
||||
"name": "not_5_0",
|
||||
"schemas": {
|
||||
"not_5_0": {
|
||||
"not": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -360,12 +390,17 @@
|
||||
{
|
||||
"description": "allow everything with boolean schema false",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"not_6_0": {
|
||||
"not": false,
|
||||
"extensible": true
|
||||
"types": [
|
||||
{
|
||||
"name": "not_6_0",
|
||||
"schemas": {
|
||||
"not_6_0": {
|
||||
"not": false,
|
||||
"extensible": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -458,13 +493,18 @@
|
||||
{
|
||||
"description": "double negation",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"not_7_0": {
|
||||
"not": {
|
||||
"not": {}
|
||||
"types": [
|
||||
{
|
||||
"name": "not_7_0",
|
||||
"schemas": {
|
||||
"not_7_0": {
|
||||
"not": {
|
||||
"not": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -481,14 +521,19 @@
|
||||
{
|
||||
"description": "extensible: true allows extra properties in not",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"not_8_0": {
|
||||
"not": {
|
||||
"type": "integer"
|
||||
},
|
||||
"extensible": true
|
||||
"types": [
|
||||
{
|
||||
"name": "not_8_0",
|
||||
"schemas": {
|
||||
"not_8_0": {
|
||||
"not": {
|
||||
"type": "integer"
|
||||
},
|
||||
"extensible": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -507,13 +552,18 @@
|
||||
{
|
||||
"description": "extensible: false (default) forbids extra properties in not",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"not_9_0": {
|
||||
"not": {
|
||||
"type": "integer"
|
||||
"types": [
|
||||
{
|
||||
"name": "not_9_0",
|
||||
"schemas": {
|
||||
"not_9_0": {
|
||||
"not": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -532,19 +582,24 @@
|
||||
{
|
||||
"description": "property next to not (extensible: true)",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"not_10_0": {
|
||||
"properties": {
|
||||
"bar": {
|
||||
"type": "string"
|
||||
"types": [
|
||||
{
|
||||
"name": "not_10_0",
|
||||
"schemas": {
|
||||
"not_10_0": {
|
||||
"properties": {
|
||||
"bar": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"not": {
|
||||
"type": "integer"
|
||||
},
|
||||
"extensible": true
|
||||
}
|
||||
},
|
||||
"not": {
|
||||
"type": "integer"
|
||||
},
|
||||
"extensible": true
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -564,18 +619,23 @@
|
||||
{
|
||||
"description": "property next to not (extensible: false)",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"not_11_0": {
|
||||
"properties": {
|
||||
"bar": {
|
||||
"type": "string"
|
||||
"types": [
|
||||
{
|
||||
"name": "not_11_0",
|
||||
"schemas": {
|
||||
"not_11_0": {
|
||||
"properties": {
|
||||
"bar": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"not": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"not": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
|
||||
@ -2,27 +2,37 @@
|
||||
{
|
||||
"description": "Strict Inheritance",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"parent_type": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"a": {
|
||||
"type": "integer"
|
||||
"types": [
|
||||
{
|
||||
"name": "parent_type",
|
||||
"schemas": {
|
||||
"parent_type": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"a": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"a"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"a"
|
||||
]
|
||||
}
|
||||
},
|
||||
"child_type": {
|
||||
"type": "parent_type",
|
||||
"properties": {
|
||||
"b": {
|
||||
"type": "integer"
|
||||
{
|
||||
"name": "child_type",
|
||||
"schemas": {
|
||||
"child_type": {
|
||||
"type": "parent_type",
|
||||
"properties": {
|
||||
"b": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -66,26 +76,36 @@
|
||||
{
|
||||
"description": "Local Shadowing (Composition & Proxies)",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"budget": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"max": {
|
||||
"type": "integer",
|
||||
"maximum": 100
|
||||
"types": [
|
||||
{
|
||||
"name": "budget",
|
||||
"schemas": {
|
||||
"budget": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"max": {
|
||||
"type": "integer",
|
||||
"maximum": 100
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"custom_budget": {
|
||||
"type": "budget",
|
||||
"properties": {
|
||||
"max": {
|
||||
"type": "integer",
|
||||
"maximum": 50
|
||||
{
|
||||
"name": "custom_budget",
|
||||
"schemas": {
|
||||
"custom_budget": {
|
||||
"type": "budget",
|
||||
"properties": {
|
||||
"max": {
|
||||
"type": "integer",
|
||||
"maximum": 50
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -115,30 +135,40 @@
|
||||
{
|
||||
"description": "Primitive Array Shorthand (Optionality)",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"invoice": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"amount": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"amount"
|
||||
]
|
||||
},
|
||||
"request": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"inv": {
|
||||
"type": [
|
||||
"invoice",
|
||||
"null"
|
||||
"types": [
|
||||
{
|
||||
"name": "invoice",
|
||||
"schemas": {
|
||||
"invoice": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"amount": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"amount"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "request",
|
||||
"schemas": {
|
||||
"request": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"inv": {
|
||||
"type": [
|
||||
"invoice",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
|
||||
@ -2,63 +2,68 @@
|
||||
{
|
||||
"description": "Hybrid Array Pathing",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"hybrid_pathing": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"primitives": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"ad_hoc_objects": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"types": [
|
||||
{
|
||||
"name": "hybrid_pathing",
|
||||
"schemas": {
|
||||
"hybrid_pathing": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"primitives": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
]
|
||||
}
|
||||
},
|
||||
"entities": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"value": {
|
||||
"type": "number",
|
||||
"minimum": 10
|
||||
"ad_hoc_objects": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"deep_entities": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"nested": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"flag": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"entities": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"value": {
|
||||
"type": "number",
|
||||
"minimum": 10
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"deep_entities": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"nested": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"flag": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -68,7 +73,7 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -227,26 +232,31 @@
|
||||
{
|
||||
"description": "Ad-Hoc Union Pathing",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"ad_hoc_pathing": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"metadata_bubbles": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "integer"
|
||||
"types": [
|
||||
{
|
||||
"name": "ad_hoc_pathing",
|
||||
"schemas": {
|
||||
"ad_hoc_pathing": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"metadata_bubbles": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "integer"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -277,9 +287,22 @@
|
||||
{
|
||||
"description": "Polymorphic Family Pathing",
|
||||
"database": {
|
||||
"relations": [
|
||||
{
|
||||
"id": "11111111-1111-1111-1111-111111111111",
|
||||
"type": "relation",
|
||||
"constraint": "fk_family_pathing_table_families_widget",
|
||||
"source_type": "widget",
|
||||
"source_columns": ["family_pathing_id"],
|
||||
"destination_type": "family_pathing",
|
||||
"destination_columns": ["id"],
|
||||
"prefix": "table_families"
|
||||
}
|
||||
],
|
||||
"types": [
|
||||
{
|
||||
"name": "widget",
|
||||
"hierarchy": ["widget"],
|
||||
"variations": [
|
||||
"widget"
|
||||
],
|
||||
@ -322,24 +345,27 @@
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"schemas": {
|
||||
"family_pathing": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"table_families": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$family": "widget"
|
||||
},
|
||||
{
|
||||
"name": "family_pathing",
|
||||
"hierarchy": ["family_pathing"],
|
||||
"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": {
|
||||
|
||||
@ -2,11 +2,16 @@
|
||||
{
|
||||
"description": "pattern validation",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"pattern_0_0": {
|
||||
"pattern": "^a*$"
|
||||
"types": [
|
||||
{
|
||||
"name": "pattern_0_0",
|
||||
"schemas": {
|
||||
"pattern_0_0": {
|
||||
"pattern": "^a*$"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -86,11 +91,16 @@
|
||||
{
|
||||
"description": "pattern is not anchored",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"pattern_1_0": {
|
||||
"pattern": "a+"
|
||||
"types": [
|
||||
{
|
||||
"name": "pattern_1_0",
|
||||
"schemas": {
|
||||
"pattern_1_0": {
|
||||
"pattern": "a+"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
|
||||
@ -2,16 +2,21 @@
|
||||
{
|
||||
"description": "patternProperties validates properties matching a regex",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"patternProperties_0_0": {
|
||||
"patternProperties": {
|
||||
"f.*o": {
|
||||
"type": "integer"
|
||||
"types": [
|
||||
{
|
||||
"name": "patternProperties_0_0",
|
||||
"schemas": {
|
||||
"patternProperties_0_0": {
|
||||
"patternProperties": {
|
||||
"f.*o": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"items": {}
|
||||
}
|
||||
},
|
||||
"items": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -107,18 +112,23 @@
|
||||
{
|
||||
"description": "multiple simultaneous patternProperties are validated",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"patternProperties_1_0": {
|
||||
"patternProperties": {
|
||||
"a*": {
|
||||
"type": "integer"
|
||||
},
|
||||
"aaa*": {
|
||||
"maximum": 20
|
||||
"types": [
|
||||
{
|
||||
"name": "patternProperties_1_0",
|
||||
"schemas": {
|
||||
"patternProperties_1_0": {
|
||||
"patternProperties": {
|
||||
"a*": {
|
||||
"type": "integer"
|
||||
},
|
||||
"aaa*": {
|
||||
"maximum": 20
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -194,19 +204,24 @@
|
||||
{
|
||||
"description": "regexes are not anchored by default and are case sensitive",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"patternProperties_2_0": {
|
||||
"patternProperties": {
|
||||
"[0-9]{2,}": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"X_": {
|
||||
"type": "string"
|
||||
"types": [
|
||||
{
|
||||
"name": "patternProperties_2_0",
|
||||
"schemas": {
|
||||
"patternProperties_2_0": {
|
||||
"patternProperties": {
|
||||
"[0-9]{2,}": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"X_": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"extensible": true
|
||||
}
|
||||
},
|
||||
"extensible": true
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -258,14 +273,19 @@
|
||||
{
|
||||
"description": "patternProperties with boolean schemas",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"patternProperties_3_0": {
|
||||
"patternProperties": {
|
||||
"f.*": true,
|
||||
"b.*": false
|
||||
"types": [
|
||||
{
|
||||
"name": "patternProperties_3_0",
|
||||
"schemas": {
|
||||
"patternProperties_3_0": {
|
||||
"patternProperties": {
|
||||
"f.*": true,
|
||||
"b.*": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -327,15 +347,20 @@
|
||||
{
|
||||
"description": "patternProperties with null valued instance properties",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"patternProperties_4_0": {
|
||||
"patternProperties": {
|
||||
"^.*bar$": {
|
||||
"type": "null"
|
||||
"types": [
|
||||
{
|
||||
"name": "patternProperties_4_0",
|
||||
"schemas": {
|
||||
"patternProperties_4_0": {
|
||||
"patternProperties": {
|
||||
"^.*bar$": {
|
||||
"type": "null"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -354,16 +379,21 @@
|
||||
{
|
||||
"description": "extensible: true allows extra properties NOT matching pattern",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"patternProperties_5_0": {
|
||||
"patternProperties": {
|
||||
"f.*o": {
|
||||
"type": "integer"
|
||||
"types": [
|
||||
{
|
||||
"name": "patternProperties_5_0",
|
||||
"schemas": {
|
||||
"patternProperties_5_0": {
|
||||
"patternProperties": {
|
||||
"f.*o": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"extensible": true
|
||||
}
|
||||
},
|
||||
"extensible": true
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[
|
||||
{
|
||||
"description": "Vertical $family Routing (Across Tables)",
|
||||
"description": "Vertical family Routing (Across Tables)",
|
||||
"database": {
|
||||
"types": [
|
||||
{
|
||||
@ -73,13 +73,16 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "family_entity",
|
||||
"schemas": {
|
||||
"family_entity": {
|
||||
"family": "entity"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"schemas": {
|
||||
"family_entity": {
|
||||
"$family": "entity"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -150,7 +153,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Matrix $family Routing (Vertical + Horizontal Intersections)",
|
||||
"description": "Matrix family Routing (Vertical + Horizontal Intersections)",
|
||||
"database": {
|
||||
"types": [
|
||||
{
|
||||
@ -222,13 +225,16 @@
|
||||
"type": "light.entity"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "family_light_org",
|
||||
"schemas": {
|
||||
"family_light_org": {
|
||||
"family": "light.organization"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"schemas": {
|
||||
"family_light_org": {
|
||||
"$family": "light.organization"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -278,7 +284,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Horizontal $family Routing (Virtual Variations)",
|
||||
"description": "Horizontal family Routing (Virtual Variations)",
|
||||
"database": {
|
||||
"types": [
|
||||
{
|
||||
@ -315,16 +321,24 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"schemas": {
|
||||
"family_widget": {
|
||||
"$family": "widget"
|
||||
},
|
||||
"family_stock_widget": {
|
||||
"$family": "stock.widget"
|
||||
{
|
||||
"name": "family_widget",
|
||||
"schemas": {
|
||||
"family_widget": {
|
||||
"family": "widget"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "family_stock_widget",
|
||||
"schemas": {
|
||||
"family_stock_widget": {
|
||||
"family": "stock.widget"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -463,20 +477,23 @@
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"schemas": {
|
||||
"oneOf_union": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "full.person"
|
||||
},
|
||||
{
|
||||
"type": "full.bot"
|
||||
},
|
||||
{
|
||||
"name": "oneOf_union",
|
||||
"schemas": {
|
||||
"oneOf_union": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "full.person"
|
||||
},
|
||||
{
|
||||
"type": "full.bot"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -559,48 +576,58 @@
|
||||
{
|
||||
"description": "JSONB Field Bubble oneOf Discrimination (Promoted IDs)",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"metadata": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string"
|
||||
"types": [
|
||||
{
|
||||
"name": "metadata",
|
||||
"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"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"invoice.metadata": {
|
||||
"type": "metadata",
|
||||
"properties": {
|
||||
"invoice_id": {
|
||||
"type": "integer"
|
||||
{
|
||||
"name": "oneOf_bubble",
|
||||
"schemas": {
|
||||
"oneOf_bubble": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "invoice.metadata"
|
||||
},
|
||||
{
|
||||
"type": "payment.metadata"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"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": [
|
||||
{
|
||||
@ -636,5 +663,118 @@
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "STI Projections (Lacking Kind Discriminator Definitions)",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
},
|
||||
"projected.widget": {
|
||||
"type": "widget",
|
||||
"properties": {
|
||||
"alias": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "stock_widget_validation",
|
||||
"schemas": {
|
||||
"stock_widget_validation": {
|
||||
"type": "stock.widget"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "projected_widget_validation",
|
||||
"schemas": {
|
||||
"projected_widget_validation": {
|
||||
"type": "projected.widget"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "stock.widget securely expects kind when configured",
|
||||
"schema_id": "stock_widget_validation",
|
||||
"data": {
|
||||
"type": "widget",
|
||||
"amount": 5
|
||||
},
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false,
|
||||
"errors": [
|
||||
{
|
||||
"code": "MISSING_KIND",
|
||||
"details": {
|
||||
"path": ""
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "projected.widget seamlessly bypasses kind expectation when excluded from schema",
|
||||
"schema_id": "projected_widget_validation",
|
||||
"data": {
|
||||
"type": "widget",
|
||||
"alias": "Test Projection"
|
||||
},
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "projected.widget securely fails if user erroneously provides extra kind property",
|
||||
"schema_id": "projected_widget_validation",
|
||||
"data": {
|
||||
"type": "widget",
|
||||
"alias": "Test Projection",
|
||||
"kind": "projected"
|
||||
},
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false,
|
||||
"errors": [
|
||||
{
|
||||
"code": "STRICT_PROPERTY_VIOLATION",
|
||||
"details": {
|
||||
"path": "kind"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@ -2,18 +2,23 @@
|
||||
{
|
||||
"description": "a schema given for prefixItems",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"prefixItems_0_0": {
|
||||
"prefixItems": [
|
||||
{
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
"types": [
|
||||
{
|
||||
"name": "prefixItems_0_0",
|
||||
"schemas": {
|
||||
"prefixItems_0_0": {
|
||||
"prefixItems": [
|
||||
{
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -91,14 +96,19 @@
|
||||
{
|
||||
"description": "prefixItems with boolean schemas",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"prefixItems_1_0": {
|
||||
"prefixItems": [
|
||||
true,
|
||||
false
|
||||
]
|
||||
"types": [
|
||||
{
|
||||
"name": "prefixItems_1_0",
|
||||
"schemas": {
|
||||
"prefixItems_1_0": {
|
||||
"prefixItems": [
|
||||
true,
|
||||
false
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -138,16 +148,21 @@
|
||||
{
|
||||
"description": "additional items are allowed by default",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"prefixItems_2_0": {
|
||||
"prefixItems": [
|
||||
{
|
||||
"type": "integer"
|
||||
"types": [
|
||||
{
|
||||
"name": "prefixItems_2_0",
|
||||
"schemas": {
|
||||
"prefixItems_2_0": {
|
||||
"prefixItems": [
|
||||
{
|
||||
"type": "integer"
|
||||
}
|
||||
],
|
||||
"extensible": true
|
||||
}
|
||||
],
|
||||
"extensible": true
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -168,15 +183,20 @@
|
||||
{
|
||||
"description": "prefixItems with null instance elements",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"prefixItems_3_0": {
|
||||
"prefixItems": [
|
||||
{
|
||||
"type": "null"
|
||||
"types": [
|
||||
{
|
||||
"name": "prefixItems_3_0",
|
||||
"schemas": {
|
||||
"prefixItems_3_0": {
|
||||
"prefixItems": [
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -195,16 +215,21 @@
|
||||
{
|
||||
"description": "extensible: true allows extra items with prefixItems",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"prefixItems_4_0": {
|
||||
"prefixItems": [
|
||||
{
|
||||
"type": "integer"
|
||||
"types": [
|
||||
{
|
||||
"name": "prefixItems_4_0",
|
||||
"schemas": {
|
||||
"prefixItems_4_0": {
|
||||
"prefixItems": [
|
||||
{
|
||||
"type": "integer"
|
||||
}
|
||||
],
|
||||
"extensible": true
|
||||
}
|
||||
],
|
||||
"extensible": true
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
|
||||
@ -2,11 +2,16 @@
|
||||
{
|
||||
"description": "integer type matches integers",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"type_0_0": {
|
||||
"type": "integer"
|
||||
"types": [
|
||||
{
|
||||
"name": "type_0_0",
|
||||
"schemas": {
|
||||
"type_0_0": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -95,11 +100,16 @@
|
||||
{
|
||||
"description": "number type matches numbers",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"type_1_0": {
|
||||
"type": "number"
|
||||
"types": [
|
||||
{
|
||||
"name": "type_1_0",
|
||||
"schemas": {
|
||||
"type_1_0": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -188,11 +198,16 @@
|
||||
{
|
||||
"description": "string type matches strings",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"type_2_0": {
|
||||
"type": "string"
|
||||
"types": [
|
||||
{
|
||||
"name": "type_2_0",
|
||||
"schemas": {
|
||||
"type_2_0": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -281,11 +296,16 @@
|
||||
{
|
||||
"description": "object type matches objects",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"type_3_0": {
|
||||
"type": "object"
|
||||
"types": [
|
||||
{
|
||||
"name": "type_3_0",
|
||||
"schemas": {
|
||||
"type_3_0": {
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -356,11 +376,16 @@
|
||||
{
|
||||
"description": "array type matches arrays",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"type_4_0": {
|
||||
"type": "array"
|
||||
"types": [
|
||||
{
|
||||
"name": "type_4_0",
|
||||
"schemas": {
|
||||
"type_4_0": {
|
||||
"type": "array"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -431,11 +456,16 @@
|
||||
{
|
||||
"description": "boolean type matches booleans",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"type_5_0": {
|
||||
"type": "boolean"
|
||||
"types": [
|
||||
{
|
||||
"name": "type_5_0",
|
||||
"schemas": {
|
||||
"type_5_0": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -533,11 +563,16 @@
|
||||
{
|
||||
"description": "null type matches only the null object",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"type_6_0": {
|
||||
"type": "null"
|
||||
"types": [
|
||||
{
|
||||
"name": "type_6_0",
|
||||
"schemas": {
|
||||
"type_6_0": {
|
||||
"type": "null"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -635,14 +670,19 @@
|
||||
{
|
||||
"description": "multiple types can be specified in an array",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"type_7_0": {
|
||||
"type": [
|
||||
"integer",
|
||||
"string"
|
||||
]
|
||||
"types": [
|
||||
{
|
||||
"name": "type_7_0",
|
||||
"schemas": {
|
||||
"type_7_0": {
|
||||
"type": [
|
||||
"integer",
|
||||
"string"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -713,13 +753,18 @@
|
||||
{
|
||||
"description": "type as array with one item",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"type_8_0": {
|
||||
"type": [
|
||||
"string"
|
||||
]
|
||||
"types": [
|
||||
{
|
||||
"name": "type_8_0",
|
||||
"schemas": {
|
||||
"type_8_0": {
|
||||
"type": [
|
||||
"string"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -745,15 +790,20 @@
|
||||
{
|
||||
"description": "type: array or object",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"type_9_0": {
|
||||
"type": [
|
||||
"array",
|
||||
"object"
|
||||
],
|
||||
"items": {}
|
||||
"types": [
|
||||
{
|
||||
"name": "type_9_0",
|
||||
"schemas": {
|
||||
"type_9_0": {
|
||||
"type": [
|
||||
"array",
|
||||
"object"
|
||||
],
|
||||
"items": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -810,16 +860,21 @@
|
||||
{
|
||||
"description": "type: array, object or null",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"type_10_0": {
|
||||
"type": [
|
||||
"array",
|
||||
"object",
|
||||
"null"
|
||||
],
|
||||
"items": {}
|
||||
"types": [
|
||||
{
|
||||
"name": "type_10_0",
|
||||
"schemas": {
|
||||
"type_10_0": {
|
||||
"type": [
|
||||
"array",
|
||||
"object",
|
||||
"null"
|
||||
],
|
||||
"items": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -876,12 +931,17 @@
|
||||
{
|
||||
"description": "extensible: true allows extra properties",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"type_11_0": {
|
||||
"type": "object",
|
||||
"extensible": true
|
||||
"types": [
|
||||
{
|
||||
"name": "type_11_0",
|
||||
"schemas": {
|
||||
"type_11_0": {
|
||||
"type": "object",
|
||||
"extensible": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
|
||||
@ -2,18 +2,23 @@
|
||||
{
|
||||
"description": "object properties validation",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"properties_0_0": {
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "integer"
|
||||
},
|
||||
"bar": {
|
||||
"type": "string"
|
||||
"types": [
|
||||
{
|
||||
"name": "properties_0_0",
|
||||
"schemas": {
|
||||
"properties_0_0": {
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "integer"
|
||||
},
|
||||
"bar": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -84,14 +89,19 @@
|
||||
{
|
||||
"description": "properties with boolean schema",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"properties_1_0": {
|
||||
"properties": {
|
||||
"foo": true,
|
||||
"bar": false
|
||||
"types": [
|
||||
{
|
||||
"name": "properties_1_0",
|
||||
"schemas": {
|
||||
"properties_1_0": {
|
||||
"properties": {
|
||||
"foo": true,
|
||||
"bar": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -142,30 +152,35 @@
|
||||
{
|
||||
"description": "properties with escaped characters",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"properties_2_0": {
|
||||
"properties": {
|
||||
"foo\nbar": {
|
||||
"type": "number"
|
||||
},
|
||||
"foo\"bar": {
|
||||
"type": "number"
|
||||
},
|
||||
"foo\\bar": {
|
||||
"type": "number"
|
||||
},
|
||||
"foo\rbar": {
|
||||
"type": "number"
|
||||
},
|
||||
"foo\tbar": {
|
||||
"type": "number"
|
||||
},
|
||||
"foo\fbar": {
|
||||
"type": "number"
|
||||
"types": [
|
||||
{
|
||||
"name": "properties_2_0",
|
||||
"schemas": {
|
||||
"properties_2_0": {
|
||||
"properties": {
|
||||
"foo\nbar": {
|
||||
"type": "number"
|
||||
},
|
||||
"foo\"bar": {
|
||||
"type": "number"
|
||||
},
|
||||
"foo\\bar": {
|
||||
"type": "number"
|
||||
},
|
||||
"foo\rbar": {
|
||||
"type": "number"
|
||||
},
|
||||
"foo\tbar": {
|
||||
"type": "number"
|
||||
},
|
||||
"foo\fbar": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -205,15 +220,20 @@
|
||||
{
|
||||
"description": "properties with null valued instance properties",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"properties_3_0": {
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "null"
|
||||
"types": [
|
||||
{
|
||||
"name": "properties_3_0",
|
||||
"schemas": {
|
||||
"properties_3_0": {
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "null"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -233,25 +253,30 @@
|
||||
"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": {
|
||||
"properties_4_0": {
|
||||
"properties": {
|
||||
"__proto__": {
|
||||
"type": "number"
|
||||
},
|
||||
"toString": {
|
||||
"types": [
|
||||
{
|
||||
"name": "properties_4_0",
|
||||
"schemas": {
|
||||
"properties_4_0": {
|
||||
"properties": {
|
||||
"length": {
|
||||
"type": "string"
|
||||
"__proto__": {
|
||||
"type": "number"
|
||||
},
|
||||
"toString": {
|
||||
"properties": {
|
||||
"length": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"constructor": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
},
|
||||
"constructor": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -338,16 +363,21 @@
|
||||
{
|
||||
"description": "extensible: true allows extra properties",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"properties_5_0": {
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "integer"
|
||||
"types": [
|
||||
{
|
||||
"name": "properties_5_0",
|
||||
"schemas": {
|
||||
"properties_5_0": {
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"extensible": true
|
||||
}
|
||||
},
|
||||
"extensible": true
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -367,15 +397,20 @@
|
||||
{
|
||||
"description": "strict by default: extra properties invalid",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"properties_6_0": {
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "string"
|
||||
"types": [
|
||||
{
|
||||
"name": "properties_6_0",
|
||||
"schemas": {
|
||||
"properties_6_0": {
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -395,19 +430,24 @@
|
||||
{
|
||||
"description": "inheritance: nested object inherits strictness from strict parent",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"properties_7_0": {
|
||||
"properties": {
|
||||
"nested": {
|
||||
"types": [
|
||||
{
|
||||
"name": "properties_7_0",
|
||||
"schemas": {
|
||||
"properties_7_0": {
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "string"
|
||||
"nested": {
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -429,20 +469,25 @@
|
||||
{
|
||||
"description": "override: nested object allows extra properties if extensible: true",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"properties_8_0": {
|
||||
"properties": {
|
||||
"nested": {
|
||||
"extensible": true,
|
||||
"types": [
|
||||
{
|
||||
"name": "properties_8_0",
|
||||
"schemas": {
|
||||
"properties_8_0": {
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "string"
|
||||
"nested": {
|
||||
"extensible": true,
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -464,20 +509,25 @@
|
||||
{
|
||||
"description": "inheritance: nested object inherits looseness from loose parent",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"properties_9_0": {
|
||||
"extensible": true,
|
||||
"properties": {
|
||||
"nested": {
|
||||
"types": [
|
||||
{
|
||||
"name": "properties_9_0",
|
||||
"schemas": {
|
||||
"properties_9_0": {
|
||||
"extensible": true,
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "string"
|
||||
"nested": {
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -499,21 +549,26 @@
|
||||
{
|
||||
"description": "override: nested object enforces strictness if extensible: false",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"properties_10_0": {
|
||||
"extensible": true,
|
||||
"properties": {
|
||||
"nested": {
|
||||
"extensible": false,
|
||||
"types": [
|
||||
{
|
||||
"name": "properties_10_0",
|
||||
"schemas": {
|
||||
"properties_10_0": {
|
||||
"extensible": true,
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "string"
|
||||
"nested": {
|
||||
"extensible": false,
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -535,22 +590,27 @@
|
||||
{
|
||||
"description": "arrays: inline items inherit strictness from strict parent",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"properties_11_0": {
|
||||
"properties": {
|
||||
"list": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "string"
|
||||
"types": [
|
||||
{
|
||||
"name": "properties_11_0",
|
||||
"schemas": {
|
||||
"properties_11_0": {
|
||||
"properties": {
|
||||
"list": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -574,23 +634,28 @@
|
||||
{
|
||||
"description": "arrays: inline items inherit looseness from loose parent",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"properties_12_0": {
|
||||
"extensible": true,
|
||||
"properties": {
|
||||
"list": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "string"
|
||||
"types": [
|
||||
{
|
||||
"name": "properties_12_0",
|
||||
"schemas": {
|
||||
"properties_12_0": {
|
||||
"extensible": true,
|
||||
"properties": {
|
||||
"list": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
|
||||
@ -2,14 +2,19 @@
|
||||
{
|
||||
"description": "propertyNames validation",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"propertyNames_0_0": {
|
||||
"propertyNames": {
|
||||
"maxLength": 3
|
||||
},
|
||||
"extensible": true
|
||||
"types": [
|
||||
{
|
||||
"name": "propertyNames_0_0",
|
||||
"schemas": {
|
||||
"propertyNames_0_0": {
|
||||
"propertyNames": {
|
||||
"maxLength": 3
|
||||
},
|
||||
"extensible": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -82,14 +87,19 @@
|
||||
{
|
||||
"description": "propertyNames validation with pattern",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"propertyNames_1_0": {
|
||||
"propertyNames": {
|
||||
"pattern": "^a+$"
|
||||
},
|
||||
"extensible": true
|
||||
"types": [
|
||||
{
|
||||
"name": "propertyNames_1_0",
|
||||
"schemas": {
|
||||
"propertyNames_1_0": {
|
||||
"propertyNames": {
|
||||
"pattern": "^a+$"
|
||||
},
|
||||
"extensible": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -130,12 +140,17 @@
|
||||
{
|
||||
"description": "propertyNames with boolean schema true",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"propertyNames_2_0": {
|
||||
"propertyNames": true,
|
||||
"extensible": true
|
||||
"types": [
|
||||
{
|
||||
"name": "propertyNames_2_0",
|
||||
"schemas": {
|
||||
"propertyNames_2_0": {
|
||||
"propertyNames": true,
|
||||
"extensible": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -163,12 +178,17 @@
|
||||
{
|
||||
"description": "propertyNames with boolean schema false",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"propertyNames_3_0": {
|
||||
"propertyNames": false,
|
||||
"extensible": true
|
||||
"types": [
|
||||
{
|
||||
"name": "propertyNames_3_0",
|
||||
"schemas": {
|
||||
"propertyNames_3_0": {
|
||||
"propertyNames": false,
|
||||
"extensible": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -196,14 +216,19 @@
|
||||
{
|
||||
"description": "propertyNames with const",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"propertyNames_4_0": {
|
||||
"propertyNames": {
|
||||
"const": "foo"
|
||||
},
|
||||
"extensible": true
|
||||
"types": [
|
||||
{
|
||||
"name": "propertyNames_4_0",
|
||||
"schemas": {
|
||||
"propertyNames_4_0": {
|
||||
"propertyNames": {
|
||||
"const": "foo"
|
||||
},
|
||||
"extensible": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -242,17 +267,22 @@
|
||||
{
|
||||
"description": "propertyNames with enum",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"propertyNames_5_0": {
|
||||
"propertyNames": {
|
||||
"enum": [
|
||||
"foo",
|
||||
"bar"
|
||||
]
|
||||
},
|
||||
"extensible": true
|
||||
"types": [
|
||||
{
|
||||
"name": "propertyNames_5_0",
|
||||
"schemas": {
|
||||
"propertyNames_5_0": {
|
||||
"propertyNames": {
|
||||
"enum": [
|
||||
"foo",
|
||||
"bar"
|
||||
]
|
||||
},
|
||||
"extensible": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -303,14 +333,19 @@
|
||||
{
|
||||
"description": "extensible: true allows extra properties (checked by propertyNames)",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"propertyNames_6_0": {
|
||||
"propertyNames": {
|
||||
"maxLength": 3
|
||||
},
|
||||
"extensible": true
|
||||
"types": [
|
||||
{
|
||||
"name": "propertyNames_6_0",
|
||||
"schemas": {
|
||||
"propertyNames_6_0": {
|
||||
"propertyNames": {
|
||||
"maxLength": 3
|
||||
},
|
||||
"extensible": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
|
||||
@ -17,7 +17,7 @@
|
||||
"get_organizations.response": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$family": "organization"
|
||||
"family": "organization"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -26,7 +26,7 @@
|
||||
"name": "get_light_organization",
|
||||
"schemas": {
|
||||
"get_light_organization.response": {
|
||||
"$family": "light.organization"
|
||||
"family": "light.organization"
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -34,7 +34,7 @@
|
||||
"name": "get_full_organization",
|
||||
"schemas": {
|
||||
"get_full_organization.response": {
|
||||
"$family": "full.organization"
|
||||
"family": "full.organization"
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -55,7 +55,7 @@
|
||||
"get_widgets.response": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$family": "widget"
|
||||
"family": "widget"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -924,6 +924,91 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "account",
|
||||
"hierarchy": [
|
||||
"account",
|
||||
"entity"
|
||||
],
|
||||
"fields": [
|
||||
"id",
|
||||
"type",
|
||||
"kind",
|
||||
"archived",
|
||||
"created_at",
|
||||
"routing_number",
|
||||
"card_number"
|
||||
],
|
||||
"grouped_fields": {
|
||||
"entity": [
|
||||
"id",
|
||||
"type",
|
||||
"archived",
|
||||
"created_at"
|
||||
],
|
||||
"account": [
|
||||
"kind",
|
||||
"routing_number",
|
||||
"card_number"
|
||||
]
|
||||
},
|
||||
"field_types": {
|
||||
"id": "uuid",
|
||||
"type": "text",
|
||||
"kind": "text",
|
||||
"archived": "boolean",
|
||||
"created_at": "timestamptz",
|
||||
"routing_number": "text",
|
||||
"card_number": "text"
|
||||
},
|
||||
"variations": [
|
||||
"account"
|
||||
],
|
||||
"schemas": {
|
||||
"account": {
|
||||
"type": "entity",
|
||||
"properties": {
|
||||
"kind": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"cases": [
|
||||
{
|
||||
"when": {
|
||||
"properties": {
|
||||
"kind": {
|
||||
"const": "checking"
|
||||
}
|
||||
}
|
||||
},
|
||||
"then": {
|
||||
"properties": {
|
||||
"routing_number": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"when": {
|
||||
"properties": {
|
||||
"kind": {
|
||||
"const": "credit"
|
||||
}
|
||||
}
|
||||
},
|
||||
"then": {
|
||||
"properties": {
|
||||
"card_number": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "invoice",
|
||||
"schemas": {
|
||||
@ -1110,14 +1195,14 @@
|
||||
"description": "Simple entity select with multiple filters",
|
||||
"action": "query",
|
||||
"schema_id": "entity",
|
||||
"filters": {
|
||||
"filter": {
|
||||
"id": {
|
||||
"$eq": "123e4567-e89b-12d3-a456-426614174000",
|
||||
"$ne": "123e4567-e89b-12d3-a456-426614174001",
|
||||
"$in": [
|
||||
"$of": [
|
||||
"123e4567-e89b-12d3-a456-426614174000"
|
||||
],
|
||||
"$nin": [
|
||||
"$nof": [
|
||||
"123e4567-e89b-12d3-a456-426614174001"
|
||||
]
|
||||
},
|
||||
@ -1156,9 +1241,9 @@
|
||||
" AND entity_1.created_at <= ($7#>>'{}')::timestamptz",
|
||||
" AND entity_1.created_at != ($8#>>'{}')::timestamptz",
|
||||
" AND entity_1.id = ($9#>>'{}')::uuid",
|
||||
" AND entity_1.id IN (SELECT value::uuid FROM jsonb_array_elements_text(($10#>>'{}')::jsonb))",
|
||||
" AND entity_1.id != ($11#>>'{}')::uuid",
|
||||
" AND entity_1.id NOT IN (SELECT value::uuid FROM jsonb_array_elements_text(($12#>>'{}')::jsonb))",
|
||||
" AND entity_1.id != ($10#>>'{}')::uuid",
|
||||
" AND entity_1.id NOT IN (SELECT value::uuid FROM jsonb_array_elements_text(($11#>>'{}')::jsonb))",
|
||||
" AND entity_1.id IN (SELECT value::uuid FROM jsonb_array_elements_text(($12#>>'{}')::jsonb))",
|
||||
")))"
|
||||
]
|
||||
]
|
||||
@ -1358,19 +1443,19 @@
|
||||
"description": "Person select on full schema with filters",
|
||||
"action": "query",
|
||||
"schema_id": "full.person",
|
||||
"filters": {
|
||||
"filter": {
|
||||
"age": {
|
||||
"$eq": 30,
|
||||
"$gt": 20,
|
||||
"$gte": 20,
|
||||
"$in": [
|
||||
"$of": [
|
||||
30,
|
||||
40
|
||||
],
|
||||
"$lt": 50,
|
||||
"$lte": 50,
|
||||
"$ne": 25,
|
||||
"$nin": [
|
||||
"$nof": [
|
||||
1,
|
||||
2
|
||||
]
|
||||
@ -1396,24 +1481,24 @@
|
||||
"$eq": "Jane%",
|
||||
"$gt": "A",
|
||||
"$gte": "A",
|
||||
"$in": [
|
||||
"$of": [
|
||||
"Jane",
|
||||
"John"
|
||||
],
|
||||
"$lt": "Z",
|
||||
"$lte": "Z",
|
||||
"$ne": "Doe",
|
||||
"$nin": [
|
||||
"$nof": [
|
||||
"Bob"
|
||||
]
|
||||
},
|
||||
"id": {
|
||||
"$eq": "00000000-0000-0000-0000-000000000001",
|
||||
"$in": [
|
||||
"$of": [
|
||||
"00000000-0000-0000-0000-000000000001"
|
||||
],
|
||||
"$ne": "00000000-0000-0000-0000-000000000002",
|
||||
"$nin": [
|
||||
"$nof": [
|
||||
"00000000-0000-0000-0000-000000000002"
|
||||
]
|
||||
},
|
||||
@ -1592,11 +1677,11 @@
|
||||
" AND person_1.age = ($1#>>'{}')::numeric",
|
||||
" AND person_1.age > ($2#>>'{}')::numeric",
|
||||
" AND person_1.age >= ($3#>>'{}')::numeric",
|
||||
" AND person_1.age IN (SELECT value::numeric FROM jsonb_array_elements_text(($4#>>'{}')::jsonb))",
|
||||
" AND person_1.age < ($5#>>'{}')::numeric",
|
||||
" AND person_1.age <= ($6#>>'{}')::numeric",
|
||||
" AND person_1.age != ($7#>>'{}')::numeric",
|
||||
" AND person_1.age NOT IN (SELECT value::numeric FROM jsonb_array_elements_text(($8#>>'{}')::jsonb))",
|
||||
" AND person_1.age < ($4#>>'{}')::numeric",
|
||||
" AND person_1.age <= ($5#>>'{}')::numeric",
|
||||
" AND person_1.age != ($6#>>'{}')::numeric",
|
||||
" AND person_1.age NOT IN (SELECT value::numeric FROM jsonb_array_elements_text(($7#>>'{}')::jsonb))",
|
||||
" AND person_1.age IN (SELECT value::numeric FROM jsonb_array_elements_text(($8#>>'{}')::jsonb))",
|
||||
" AND entity_3.archived = ($9#>>'{}')::boolean",
|
||||
" AND entity_3.archived != ($10#>>'{}')::boolean",
|
||||
" AND entity_3.created_at = ($12#>>'{}')::timestamptz",
|
||||
@ -1608,15 +1693,15 @@
|
||||
" AND person_1.first_name ILIKE $18#>>'{}'",
|
||||
" AND person_1.first_name > ($19#>>'{}')",
|
||||
" AND person_1.first_name >= ($20#>>'{}')",
|
||||
" AND person_1.first_name IN (SELECT value FROM jsonb_array_elements_text(($21#>>'{}')::jsonb))",
|
||||
" AND person_1.first_name < ($22#>>'{}')",
|
||||
" AND person_1.first_name <= ($23#>>'{}')",
|
||||
" AND person_1.first_name NOT ILIKE $24#>>'{}'",
|
||||
" AND person_1.first_name NOT IN (SELECT value FROM jsonb_array_elements_text(($25#>>'{}')::jsonb))",
|
||||
" AND person_1.first_name < ($21#>>'{}')",
|
||||
" AND person_1.first_name <= ($22#>>'{}')",
|
||||
" AND person_1.first_name NOT ILIKE $23#>>'{}'",
|
||||
" AND person_1.first_name NOT IN (SELECT value FROM jsonb_array_elements_text(($24#>>'{}')::jsonb))",
|
||||
" AND person_1.first_name IN (SELECT value FROM jsonb_array_elements_text(($25#>>'{}')::jsonb))",
|
||||
" AND entity_3.id = ($26#>>'{}')::uuid",
|
||||
" AND entity_3.id IN (SELECT value::uuid FROM jsonb_array_elements_text(($27#>>'{}')::jsonb))",
|
||||
" AND entity_3.id != ($28#>>'{}')::uuid",
|
||||
" AND entity_3.id NOT IN (SELECT value::uuid FROM jsonb_array_elements_text(($29#>>'{}')::jsonb))",
|
||||
" AND entity_3.id != ($27#>>'{}')::uuid",
|
||||
" AND entity_3.id NOT IN (SELECT value::uuid FROM jsonb_array_elements_text(($28#>>'{}')::jsonb))",
|
||||
" AND entity_3.id IN (SELECT value::uuid FROM jsonb_array_elements_text(($29#>>'{}')::jsonb))",
|
||||
" AND person_1.last_name ILIKE $30#>>'{}'",
|
||||
" AND person_1.last_name NOT ILIKE $31#>>'{}')))"
|
||||
]
|
||||
@ -2149,6 +2234,30 @@
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Account select on full schema with cases fields",
|
||||
"action": "query",
|
||||
"schema_id": "account",
|
||||
"expect": {
|
||||
"success": true,
|
||||
"sql": [
|
||||
[
|
||||
"(SELECT jsonb_strip_nulls((SELECT jsonb_build_object(",
|
||||
" 'archived', entity_2.archived,",
|
||||
" 'card_number', account_1.card_number,",
|
||||
" 'created_at', entity_2.created_at,",
|
||||
" 'id', entity_2.id,",
|
||||
" 'kind', account_1.kind,",
|
||||
" 'routing_number', account_1.routing_number,",
|
||||
" 'type', entity_2.type",
|
||||
")",
|
||||
"FROM agreego.account account_1",
|
||||
"JOIN agreego.entity entity_2 ON entity_2.id = account_1.id",
|
||||
"WHERE NOT entity_2.archived)))"
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@ -2,17 +2,22 @@
|
||||
{
|
||||
"description": "required validation",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"required_0_0": {
|
||||
"properties": {
|
||||
"foo": {},
|
||||
"bar": {}
|
||||
},
|
||||
"required": [
|
||||
"foo"
|
||||
]
|
||||
"types": [
|
||||
{
|
||||
"name": "required_0_0",
|
||||
"schemas": {
|
||||
"required_0_0": {
|
||||
"properties": {
|
||||
"foo": {},
|
||||
"bar": {}
|
||||
},
|
||||
"required": [
|
||||
"foo"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -87,13 +92,18 @@
|
||||
{
|
||||
"description": "required default validation",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"required_1_0": {
|
||||
"properties": {
|
||||
"foo": {}
|
||||
"types": [
|
||||
{
|
||||
"name": "required_1_0",
|
||||
"schemas": {
|
||||
"required_1_0": {
|
||||
"properties": {
|
||||
"foo": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -110,14 +120,19 @@
|
||||
{
|
||||
"description": "required with empty array",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"required_2_0": {
|
||||
"properties": {
|
||||
"foo": {}
|
||||
},
|
||||
"required": []
|
||||
"types": [
|
||||
{
|
||||
"name": "required_2_0",
|
||||
"schemas": {
|
||||
"required_2_0": {
|
||||
"properties": {
|
||||
"foo": {}
|
||||
},
|
||||
"required": []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -134,19 +149,24 @@
|
||||
{
|
||||
"description": "required with escaped characters",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"required_3_0": {
|
||||
"required": [
|
||||
"foo\nbar",
|
||||
"foo\"bar",
|
||||
"foo\\bar",
|
||||
"foo\rbar",
|
||||
"foo\tbar",
|
||||
"foo\fbar"
|
||||
],
|
||||
"extensible": true
|
||||
"types": [
|
||||
{
|
||||
"name": "required_3_0",
|
||||
"schemas": {
|
||||
"required_3_0": {
|
||||
"required": [
|
||||
"foo\nbar",
|
||||
"foo\"bar",
|
||||
"foo\\bar",
|
||||
"foo\rbar",
|
||||
"foo\tbar",
|
||||
"foo\fbar"
|
||||
],
|
||||
"extensible": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -183,16 +203,21 @@
|
||||
"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": {
|
||||
"required_4_0": {
|
||||
"required": [
|
||||
"__proto__",
|
||||
"toString",
|
||||
"constructor"
|
||||
],
|
||||
"extensible": true
|
||||
"types": [
|
||||
{
|
||||
"name": "required_4_0",
|
||||
"schemas": {
|
||||
"required_4_0": {
|
||||
"required": [
|
||||
"__proto__",
|
||||
"toString",
|
||||
"constructor"
|
||||
],
|
||||
"extensible": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -279,14 +304,19 @@
|
||||
{
|
||||
"description": "extensible: true allows extra properties in required",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"required_5_0": {
|
||||
"required": [
|
||||
"foo"
|
||||
],
|
||||
"extensible": true
|
||||
"types": [
|
||||
{
|
||||
"name": "required_5_0",
|
||||
"schemas": {
|
||||
"required_5_0": {
|
||||
"required": [
|
||||
"foo"
|
||||
],
|
||||
"extensible": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
|
||||
@ -2,12 +2,17 @@
|
||||
{
|
||||
"description": "uniqueItems validation",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"uniqueItems_0_0": {
|
||||
"uniqueItems": true,
|
||||
"extensible": true
|
||||
"types": [
|
||||
{
|
||||
"name": "uniqueItems_0_0",
|
||||
"schemas": {
|
||||
"uniqueItems_0_0": {
|
||||
"uniqueItems": true,
|
||||
"extensible": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -468,20 +473,25 @@
|
||||
{
|
||||
"description": "uniqueItems with an array of items",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"uniqueItems_1_0": {
|
||||
"prefixItems": [
|
||||
{
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"type": "boolean"
|
||||
"types": [
|
||||
{
|
||||
"name": "uniqueItems_1_0",
|
||||
"schemas": {
|
||||
"uniqueItems_1_0": {
|
||||
"prefixItems": [
|
||||
{
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"type": "boolean"
|
||||
}
|
||||
],
|
||||
"uniqueItems": true,
|
||||
"extensible": true
|
||||
}
|
||||
],
|
||||
"uniqueItems": true,
|
||||
"extensible": true
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -593,20 +603,25 @@
|
||||
{
|
||||
"description": "uniqueItems with an array of items and additionalItems=false",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"uniqueItems_2_0": {
|
||||
"prefixItems": [
|
||||
{
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"type": "boolean"
|
||||
"types": [
|
||||
{
|
||||
"name": "uniqueItems_2_0",
|
||||
"schemas": {
|
||||
"uniqueItems_2_0": {
|
||||
"prefixItems": [
|
||||
{
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"type": "boolean"
|
||||
}
|
||||
],
|
||||
"uniqueItems": true,
|
||||
"items": false
|
||||
}
|
||||
],
|
||||
"uniqueItems": true,
|
||||
"items": false
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -675,12 +690,17 @@
|
||||
{
|
||||
"description": "uniqueItems=false validation",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"uniqueItems_3_0": {
|
||||
"uniqueItems": false,
|
||||
"extensible": true
|
||||
"types": [
|
||||
{
|
||||
"name": "uniqueItems_3_0",
|
||||
"schemas": {
|
||||
"uniqueItems_3_0": {
|
||||
"uniqueItems": false,
|
||||
"extensible": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -920,20 +940,25 @@
|
||||
{
|
||||
"description": "uniqueItems=false with an array of items",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"uniqueItems_4_0": {
|
||||
"prefixItems": [
|
||||
{
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"type": "boolean"
|
||||
"types": [
|
||||
{
|
||||
"name": "uniqueItems_4_0",
|
||||
"schemas": {
|
||||
"uniqueItems_4_0": {
|
||||
"prefixItems": [
|
||||
{
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"type": "boolean"
|
||||
}
|
||||
],
|
||||
"uniqueItems": false,
|
||||
"extensible": true
|
||||
}
|
||||
],
|
||||
"uniqueItems": false,
|
||||
"extensible": true
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -1045,20 +1070,25 @@
|
||||
{
|
||||
"description": "uniqueItems=false with an array of items and additionalItems=false",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"uniqueItems_5_0": {
|
||||
"prefixItems": [
|
||||
{
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"type": "boolean"
|
||||
"types": [
|
||||
{
|
||||
"name": "uniqueItems_5_0",
|
||||
"schemas": {
|
||||
"uniqueItems_5_0": {
|
||||
"prefixItems": [
|
||||
{
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"type": "boolean"
|
||||
}
|
||||
],
|
||||
"uniqueItems": false,
|
||||
"items": false
|
||||
}
|
||||
],
|
||||
"uniqueItems": false,
|
||||
"items": false
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
@ -1127,12 +1157,17 @@
|
||||
{
|
||||
"description": "extensible: true allows extra items in uniqueItems",
|
||||
"database": {
|
||||
"schemas": {
|
||||
"uniqueItems_6_0": {
|
||||
"uniqueItems": true,
|
||||
"extensible": true
|
||||
"types": [
|
||||
{
|
||||
"name": "uniqueItems_6_0",
|
||||
"schemas": {
|
||||
"uniqueItems_6_0": {
|
||||
"uniqueItems": true,
|
||||
"extensible": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
|
||||
23
log.txt
Normal file
23
log.txt
Normal file
@ -0,0 +1,23 @@
|
||||
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.60s
|
||||
Running unittests src/lib.rs (target/debug/deps/jspg-d3f18ff3a7e2b386)
|
||||
|
||||
running 1 test
|
||||
test tests::test_library_api ... FAILED
|
||||
|
||||
failures:
|
||||
|
||||
---- tests::test_library_api stdout ----
|
||||
|
||||
thread 'tests::test_library_api' (110325727) panicked at src/tests/mod.rs:86:3:
|
||||
assertion `left == right` failed
|
||||
left: Object {"response": Object {"enums": Object {}, "puncs": Object {}, "relations": Object {"fk_test_target": Object {"constraint": String("fk_test_target"), "destination_columns": Array [String("id")], "destination_type": String("target_schema"), "id": String("11111111-1111-1111-1111-111111111111"), "prefix": String("target"), "source_columns": Array [String("target_id")], "source_type": String("source_schema"), "type": String("relation")}}, "types": Object {"source_schema": Object {"default_fields": Array [], "field_types": Null, "fields": Array [], "grouped_fields": Null, "hierarchy": Array [String("source_schema"), String("entity")], "historical": Bool(false), "id": String(""), "longevity": Null, "lookup_fields": Array [], "module": String(""), "name": String("source_schema"), "notify": Bool(false), "null_fields": Array [], "ownable": Bool(false), "relationship": Bool(false), "schemas": Object {"source_schema": Object {"compiledEdges": Object {"target": Object {"constraint": String("fk_test_target"), "forward": Bool(true)}}, "compiledPropertyNames": Array [String("name"), String("target"), String("type")], "properties": Object {"name": Object {"type": String("string")}, "target": Object {"compiledPropertyNames": Array [String("value")], "type": String("target_schema")}, "type": Object {"type": String("string")}}, "required": Array [String("name")], "type": String("object")}, "source_schema.filter": Object {"compiledPropertyNames": Array [String("$and"), String("$or"), String("name"), String("target"), String("type")], "properties": Object {"$and": Object {"items": Object {"type": String("source_schema.filter")}, "type": Array [String("array"), String("null")]}, "$or": Object {"items": Object {"type": String("source_schema.filter")}, "type": Array [String("array"), String("null")]}, "name": Object {"type": Array [String("string.condition"), String("null")]}, "target": Object {"type": Array [String("target_schema.filter"), String("null")]}, "type": Object {"type": Array [String("string.condition"), String("null")]}}, "type": String("filter")}}, "sensitive": Bool(false), "source": String(""), "type": String(""), "variations": Array [String("source_schema")]}, "target_schema": Object {"default_fields": Array [], "field_types": Null, "fields": Array [], "grouped_fields": Null, "hierarchy": Array [String("target_schema"), String("entity")], "historical": Bool(false), "id": String(""), "longevity": Null, "lookup_fields": Array [], "module": String(""), "name": String("target_schema"), "notify": Bool(false), "null_fields": Array [], "ownable": Bool(false), "relationship": Bool(false), "schemas": Object {"target_schema": Object {"compiledPropertyNames": Array [String("value")], "properties": Object {"value": Object {"type": String("number")}}, "type": String("object")}, "target_schema.filter": Object {"compiledPropertyNames": Array [String("$and"), String("$or"), String("value")], "properties": Object {"$and": Object {"items": Object {"type": String("target_schema.filter")}, "type": Array [String("array"), String("null")]}, "$or": Object {"items": Object {"type": String("target_schema.filter")}, "type": Array [String("array"), String("null")]}, "value": Object {"type": Array [String("number.condition"), String("null")]}}, "type": String("filter")}}, "sensitive": Bool(false), "source": String(""), "type": String(""), "variations": Array [String("target_schema")]}}}, "type": String("drop")}
|
||||
right: Object {"response": Object {"enums": Object {}, "puncs": Object {}, "relations": Object {"fk_test_target": Object {"constraint": String("fk_test_target"), "destination_columns": Array [String("id")], "destination_type": String("target_schema"), "id": String("11111111-1111-1111-1111-111111111111"), "prefix": String("target"), "source_columns": Array [String("target_id")], "source_type": String("source_schema"), "type": String("relation")}}, "types": Object {"source_schema": Object {"default_fields": Array [], "field_types": Null, "fields": Array [], "grouped_fields": Null, "hierarchy": Array [String("source_schema"), String("entity")], "historical": Bool(false), "id": String(""), "longevity": Null, "lookup_fields": Array [], "module": String(""), "name": String("source_schema"), "notify": Bool(false), "null_fields": Array [], "ownable": Bool(false), "relationship": Bool(false), "schemas": Object {"source_schema": Object {"compiledEdges": Object {"target": Object {"constraint": String("fk_test_target"), "forward": Bool(true)}}, "compiledPropertyNames": Array [String("name"), String("target"), String("type")], "properties": Object {"name": Object {"type": String("string")}, "target": Object {"compiledPropertyNames": Array [String("value")], "type": String("target_schema")}, "type": Object {"type": String("string")}}, "required": Array [String("name")], "type": String("object")}, "source_schema.filter": Object {"compiledPropertyNames": Array [String("$and"), String("$or"), String("name"), String("target"), String("type")], "properties": Object {"$and": Object {"items": Object {"compiledPropertyNames": Array [String("$and"), String("$or"), String("name"), String("target"), String("type")], "type": String("source_schema.filter")}, "type": Array [String("array"), String("null")]}, "$or": Object {"items": Object {"compiledPropertyNames": Array [String("$and"), String("$or"), String("name"), String("target"), String("type")], "type": String("source_schema.filter")}, "type": Array [String("array"), String("null")]}, "name": Object {"type": Array [String("string.condition"), String("null")]}, "target": Object {"type": Array [String("target_schema.filter"), String("null")]}, "type": Object {"type": Array [String("string.condition"), String("null")]}}, "type": String("filter")}}, "sensitive": Bool(false), "source": String(""), "type": String(""), "variations": Array [String("source_schema")]}, "target_schema": Object {"default_fields": Array [], "field_types": Null, "fields": Array [], "grouped_fields": Null, "hierarchy": Array [String("target_schema"), String("entity")], "historical": Bool(false), "id": String(""), "longevity": Null, "lookup_fields": Array [], "module": String(""), "name": String("target_schema"), "notify": Bool(false), "null_fields": Array [], "ownable": Bool(false), "relationship": Bool(false), "schemas": Object {"target_schema": Object {"compiledPropertyNames": Array [String("value")], "properties": Object {"value": Object {"type": String("number")}}, "type": String("object")}, "target_schema.filter": Object {"compiledPropertyNames": Array [String("$and"), String("$or"), String("value")], "properties": Object {"$and": Object {"items": Object {"compiledPropertyNames": Array [String("$and"), String("$or"), String("value")], "type": String("target_schema.filter")}, "type": Array [String("array"), String("null")]}, "$or": Object {"items": Object {"compiledPropertyNames": Array [String("$and"), String("$or"), String("value")], "type": String("target_schema.filter")}, "type": Array [String("array"), String("null")]}, "value": Object {"type": Array [String("number.condition"), String("null")]}}, "type": String("filter")}}, "sensitive": Bool(false), "source": String(""), "type": String(""), "variations": Array [String("target_schema")]}}}, "type": String("drop")}
|
||||
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
|
||||
|
||||
|
||||
failures:
|
||||
tests::test_library_api
|
||||
|
||||
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 1357 filtered out; finished in 0.00s
|
||||
|
||||
error: test failed, to rerun pass `--lib`
|
||||
23
log_test.txt
Normal file
23
log_test.txt
Normal file
@ -0,0 +1,23 @@
|
||||
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.35s
|
||||
Running unittests src/lib.rs (target/debug/deps/jspg-d3f18ff3a7e2b386)
|
||||
|
||||
running 1 test
|
||||
test tests::test_library_api ... FAILED
|
||||
|
||||
failures:
|
||||
|
||||
---- tests::test_library_api stdout ----
|
||||
|
||||
thread 'tests::test_library_api' (110334696) panicked at src/tests/mod.rs:86:3:
|
||||
assertion `left == right` failed
|
||||
left: Object {"response": Object {"enums": Object {}, "puncs": Object {}, "relations": Object {"fk_test_target": Object {"constraint": String("fk_test_target"), "destination_columns": Array [String("id")], "destination_type": String("target_schema"), "id": String("11111111-1111-1111-1111-111111111111"), "prefix": String("target"), "source_columns": Array [String("target_id")], "source_type": String("source_schema"), "type": String("relation")}}, "types": Object {"source_schema": Object {"default_fields": Array [], "field_types": Null, "fields": Array [], "grouped_fields": Null, "hierarchy": Array [String("source_schema"), String("entity")], "historical": Bool(false), "id": String(""), "longevity": Null, "lookup_fields": Array [], "module": String(""), "name": String("source_schema"), "notify": Bool(false), "null_fields": Array [], "ownable": Bool(false), "relationship": Bool(false), "schemas": Object {"source_schema": Object {"compiledEdges": Object {"target": Object {"constraint": String("fk_test_target"), "forward": Bool(true)}}, "compiledPropertyNames": Array [String("name"), String("target"), String("type")], "properties": Object {"name": Object {"type": String("string")}, "target": Object {"compiledPropertyNames": Array [String("value")], "type": String("target_schema")}, "type": Object {"type": String("string")}}, "required": Array [String("name")], "type": String("object")}, "source_schema.filter": Object {"compiledPropertyNames": Array [String("$and"), String("$or"), String("name"), String("target"), String("type")], "properties": Object {"$and": Object {"items": Object {"compiledPropertyNames": Array [String("$and"), String("$or"), String("name"), String("target"), String("type")], "type": String("source_schema.filter")}, "type": Array [String("array"), String("null")]}, "$or": Object {"items": Object {"compiledPropertyNames": Array [String("$and"), String("$or"), String("name"), String("target"), String("type")], "type": String("source_schema.filter")}, "type": Array [String("array"), String("null")]}, "name": Object {"type": Array [String("string.condition"), String("null")]}, "target": Object {"type": Array [String("target_schema.filter"), String("null")]}, "type": Object {"type": Array [String("string.condition"), String("null")]}}, "type": String("filter")}}, "sensitive": Bool(false), "source": String(""), "type": String(""), "variations": Array [String("source_schema")]}, "target_schema": Object {"default_fields": Array [], "field_types": Null, "fields": Array [], "grouped_fields": Null, "hierarchy": Array [String("target_schema"), String("entity")], "historical": Bool(false), "id": String(""), "longevity": Null, "lookup_fields": Array [], "module": String(""), "name": String("target_schema"), "notify": Bool(false), "null_fields": Array [], "ownable": Bool(false), "relationship": Bool(false), "schemas": Object {"target_schema": Object {"compiledPropertyNames": Array [String("value")], "properties": Object {"value": Object {"type": String("number")}}, "type": String("object")}, "target_schema.filter": Object {"compiledPropertyNames": Array [String("$and"), String("$or"), String("value")], "properties": Object {"$and": Object {"items": Object {"compiledPropertyNames": Array [String("$and"), String("$or"), String("value")], "type": String("target_schema.filter")}, "type": Array [String("array"), String("null")]}, "$or": Object {"items": Object {"compiledPropertyNames": Array [String("$and"), String("$or"), String("value")], "type": String("target_schema.filter")}, "type": Array [String("array"), String("null")]}, "value": Object {"type": Array [String("number.condition"), String("null")]}}, "type": String("filter")}}, "sensitive": Bool(false), "source": String(""), "type": String(""), "variations": Array [String("target_schema")]}}}, "type": String("drop")}
|
||||
right: Object {"response": Object {"enums": Object {}, "puncs": Object {}, "relations": Object {"fk_test_target": Object {"constraint": String("fk_test_target"), "destination_columns": Array [String("id")], "destination_type": String("target_schema"), "id": String("11111111-1111-1111-1111-111111111111"), "prefix": String("target"), "source_columns": Array [String("target_id")], "source_type": String("source_schema"), "type": String("relation")}}, "types": Object {"source_schema": Object {"default_fields": Array [], "field_types": Null, "fields": Array [], "grouped_fields": Null, "hierarchy": Array [String("source_schema"), String("entity")], "historical": Bool(false), "id": String(""), "longevity": Null, "lookup_fields": Array [], "module": String(""), "name": String("source_schema"), "notify": Bool(false), "null_fields": Array [], "ownable": Bool(false), "relationship": Bool(false), "schemas": Object {"source_schema": Object {"compiledEdges": Object {"target": Object {"constraint": String("fk_test_target"), "forward": Bool(true)}}, "compiledPropertyNames": Array [String("name"), String("target"), String("type")], "properties": Object {"name": Object {"type": String("string")}, "target": Object {"compiledPropertyNames": Array [String("value")], "type": String("target_schema")}, "type": Object {"type": String("string")}}, "required": Array [String("name")], "type": String("object")}, "source_schema.filter": Object {"compiledPropertyNames": Array [String("$and"), String("$or"), String("name"), String("target"), String("type")], "properties": Object {"$and": Object {"items": Object {"type": String("source_schema.filter")}, "type": Array [String("array"), String("null")]}, "$or": Object {"items": Object {"type": String("source_schema.filter")}, "type": Array [String("array"), String("null")]}, "name": Object {"type": Array [String("string.condition"), String("null")]}, "target": Object {"type": Array [String("target_schema.filter"), String("null")]}, "type": Object {"type": Array [String("string.condition"), String("null")]}}, "type": String("filter")}}, "sensitive": Bool(false), "source": String(""), "type": String(""), "variations": Array [String("source_schema")]}, "target_schema": Object {"default_fields": Array [], "field_types": Null, "fields": Array [], "grouped_fields": Null, "hierarchy": Array [String("target_schema"), String("entity")], "historical": Bool(false), "id": String(""), "longevity": Null, "lookup_fields": Array [], "module": String(""), "name": String("target_schema"), "notify": Bool(false), "null_fields": Array [], "ownable": Bool(false), "relationship": Bool(false), "schemas": Object {"target_schema": Object {"compiledPropertyNames": Array [String("value")], "properties": Object {"value": Object {"type": String("number")}}, "type": String("object")}, "target_schema.filter": Object {"compiledPropertyNames": Array [String("$and"), String("$or"), String("value")], "properties": Object {"$and": Object {"items": Object {"type": String("target_schema.filter")}, "type": Array [String("array"), String("null")]}, "$or": Object {"items": Object {"type": String("target_schema.filter")}, "type": Array [String("array"), String("null")]}, "value": Object {"type": Array [String("number.condition"), String("null")]}}, "type": String("filter")}}, "sensitive": Bool(false), "source": String(""), "type": String(""), "variations": Array [String("target_schema")]}}}, "type": String("drop")}
|
||||
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
|
||||
|
||||
|
||||
failures:
|
||||
tests::test_library_api
|
||||
|
||||
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 1357 filtered out; finished in 0.00s
|
||||
|
||||
error: test failed, to rerun pass `--lib`
|
||||
19
scratch.rs
19
scratch.rs
@ -1,19 +0,0 @@
|
||||
use cellular_jspg::database::{Database, object::SchemaTypeOrArray};
|
||||
use cellular_jspg::tests::fixtures::get_queryer_db;
|
||||
|
||||
fn main() {
|
||||
let db_json = get_queryer_db();
|
||||
let db = Database::from_json(&db_json).unwrap();
|
||||
let keys: Vec<_> = db.schemas.keys().collect();
|
||||
println!("Found schemas: {}", keys.len());
|
||||
let mut found = false;
|
||||
for k in keys {
|
||||
if k.contains("email_addresses") {
|
||||
println!("Contains email_addresses: {}", k);
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
println!("No email_addresses found at all!");
|
||||
}
|
||||
}
|
||||
163
src/database/compile/collection.rs
Normal file
163
src/database/compile/collection.rs
Normal file
@ -0,0 +1,163 @@
|
||||
use crate::database::schema::Schema;
|
||||
use std::sync::Arc;
|
||||
|
||||
impl Schema {
|
||||
#[allow(unused_variables)]
|
||||
pub(crate) fn validate_identifier(
|
||||
id: &str,
|
||||
field_name: &str,
|
||||
root_id: &str,
|
||||
path: &str,
|
||||
errors: &mut Vec<crate::drop::Error>,
|
||||
) {
|
||||
#[cfg(not(test))]
|
||||
for c in id.chars() {
|
||||
if !c.is_ascii_lowercase() && !c.is_ascii_digit() && c != '_' && c != '.' {
|
||||
errors.push(crate::drop::Error {
|
||||
code: "INVALID_IDENTIFIER".to_string(),
|
||||
message: format!(
|
||||
"Invalid character '{}' in JSON Schema '{}' property: '{}'. Identifiers must exclusively contain [a-z0-9_.]",
|
||||
c, field_name, id
|
||||
),
|
||||
details: crate::drop::ErrorDetails {
|
||||
path: Some(path.to_string()),
|
||||
schema: Some(root_id.to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn collect_schemas(
|
||||
schema_arc: &Arc<Schema>,
|
||||
root_id: &str,
|
||||
path: String,
|
||||
to_insert: &mut Vec<(String, Arc<Schema>)>,
|
||||
errors: &mut Vec<crate::drop::Error>,
|
||||
) {
|
||||
if let Some(crate::database::object::SchemaTypeOrArray::Single(t)) = &schema_arc.obj.type_ {
|
||||
if t == "array" {
|
||||
if let Some(items) = &schema_arc.obj.items {
|
||||
if let Some(crate::database::object::SchemaTypeOrArray::Single(it)) = &items.obj.type_ {
|
||||
if !crate::database::object::is_primitive_type(it) {
|
||||
if items.obj.properties.is_some() || items.obj.cases.is_some() {
|
||||
to_insert.push((path.clone(), Arc::clone(schema_arc)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if !crate::database::object::is_primitive_type(t) {
|
||||
Self::validate_identifier(t, "type", root_id, &path, errors);
|
||||
|
||||
// Is this an explicit inline ad-hoc composition?
|
||||
if schema_arc.obj.properties.is_some() || schema_arc.obj.cases.is_some() {
|
||||
to_insert.push((path.clone(), Arc::clone(schema_arc)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(family) = &schema_arc.obj.family {
|
||||
Self::validate_identifier(family, "family", root_id, &path, errors);
|
||||
}
|
||||
|
||||
Self::collect_child_schemas(schema_arc, root_id, path, to_insert, errors);
|
||||
}
|
||||
|
||||
pub fn collect_child_schemas(
|
||||
schema_arc: &Arc<Schema>,
|
||||
root_id: &str,
|
||||
path: String,
|
||||
to_insert: &mut Vec<(String, Arc<Schema>)>,
|
||||
errors: &mut Vec<crate::drop::Error>,
|
||||
) {
|
||||
if let Some(props) = &schema_arc.obj.properties {
|
||||
for (k, v) in props.iter() {
|
||||
let next_path = format!("{}/{}", path, k);
|
||||
Self::collect_schemas(v, root_id, next_path, to_insert, errors);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(pattern_props) = &schema_arc.obj.pattern_properties {
|
||||
for (k, v) in pattern_props.iter() {
|
||||
let next_path = format!("{}/{}", path, k);
|
||||
Self::collect_schemas(v, root_id, next_path, to_insert, errors);
|
||||
}
|
||||
}
|
||||
|
||||
let mut map_arr = |arr: &Vec<Arc<Schema>>, sub: &str| {
|
||||
for (i, v) in arr.iter().enumerate() {
|
||||
Self::collect_schemas(
|
||||
v,
|
||||
root_id,
|
||||
format!("{}/{}/{}", path, sub, i),
|
||||
to_insert,
|
||||
errors,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(arr) = &schema_arc.obj.prefix_items {
|
||||
map_arr(arr, "prefixItems");
|
||||
}
|
||||
|
||||
if let Some(arr) = &schema_arc.obj.one_of {
|
||||
map_arr(arr, "oneOf");
|
||||
}
|
||||
|
||||
let mut map_opt = |opt: &Option<Arc<Schema>>, pass_path: bool, sub: &str| {
|
||||
if let Some(v) = opt {
|
||||
if pass_path {
|
||||
// Arrays explicitly push their wrapper natively.
|
||||
// 'items' becomes a transparent conduit, bypassing self-promotion and skipping the '/items' suffix.
|
||||
Self::collect_child_schemas(v, root_id, path.clone(), to_insert, errors);
|
||||
} else {
|
||||
Self::collect_child_schemas(v, root_id, format!("{}/{}", path, sub), to_insert, errors);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
map_opt(
|
||||
&schema_arc.obj.additional_properties,
|
||||
false,
|
||||
"additionalProperties",
|
||||
);
|
||||
map_opt(&schema_arc.obj.items, true, "items");
|
||||
map_opt(&schema_arc.obj.not, false, "not");
|
||||
map_opt(&schema_arc.obj.contains, false, "contains");
|
||||
map_opt(&schema_arc.obj.property_names, false, "propertyNames");
|
||||
|
||||
if let Some(cases) = &schema_arc.obj.cases {
|
||||
for (i, c) in cases.iter().enumerate() {
|
||||
if let Some(when) = &c.when {
|
||||
Self::collect_schemas(
|
||||
when,
|
||||
root_id,
|
||||
format!("{}/cases/{}/when", path, i),
|
||||
to_insert,
|
||||
errors,
|
||||
);
|
||||
}
|
||||
if let Some(then) = &c.then {
|
||||
Self::collect_schemas(
|
||||
then,
|
||||
root_id,
|
||||
format!("{}/cases/{}/then", path, i),
|
||||
to_insert,
|
||||
errors,
|
||||
);
|
||||
}
|
||||
if let Some(else_) = &c.else_ {
|
||||
Self::collect_schemas(
|
||||
else_,
|
||||
root_id,
|
||||
format!("{}/cases/{}/else", path, i),
|
||||
to_insert,
|
||||
errors,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
128
src/database/compile/edges.rs
Normal file
128
src/database/compile/edges.rs
Normal file
@ -0,0 +1,128 @@
|
||||
use crate::database::schema::Schema;
|
||||
|
||||
impl Schema {
|
||||
/// Dynamically infers and compiles all structural database relationships between this Schema
|
||||
/// and its nested children. This functions recursively traverses the JSON Schema abstract syntax
|
||||
/// tree, identifies physical PostgreSQL table boundaries, and locks the resulting relation
|
||||
/// constraint paths directly onto the `compiled_edges` map in O(1) memory.
|
||||
pub fn compile_edges(
|
||||
&self,
|
||||
db: &crate::database::Database,
|
||||
root_id: &str,
|
||||
path: &str,
|
||||
props: &std::collections::BTreeMap<String, std::sync::Arc<Schema>>,
|
||||
errors: &mut Vec<crate::drop::Error>,
|
||||
) -> std::collections::BTreeMap<String, crate::database::edge::Edge> {
|
||||
let mut schema_edges = std::collections::BTreeMap::new();
|
||||
|
||||
// Determine the physical Database Table Name this schema structurally represents
|
||||
// Plucks the polymorphic discriminator via dot-notation (e.g. extracting "person" from "full.person")
|
||||
let mut parent_type_name = None;
|
||||
|
||||
if let Some(family) = &self.obj.family {
|
||||
// 1. Explicit horizontal routing
|
||||
parent_type_name = Some(family.split('.').next_back().unwrap_or(family).to_string());
|
||||
} else if path == root_id {
|
||||
// 2. Root nodes trust their exact registry footprint
|
||||
let base_type_name = path.split('.').next_back().unwrap_or(path).to_string();
|
||||
if db.types.contains_key(&base_type_name) {
|
||||
parent_type_name = Some(base_type_name);
|
||||
}
|
||||
} else if let Some(crate::database::object::SchemaTypeOrArray::Single(t)) = &self.obj.type_ {
|
||||
// 3. Nested graphs trust their explicit struct pointer reference
|
||||
if !crate::database::object::is_primitive_type(t) {
|
||||
parent_type_name = Some(t.split('.').next_back().unwrap_or(t).to_string());
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(p_type) = parent_type_name {
|
||||
// Proceed only if the resolved table physically exists within the Postgres Type hierarchy
|
||||
if let Some(type_def) = db.types.get(&p_type) {
|
||||
// Iterate over all discovered schema boundaries mapped inside the object
|
||||
for (prop_name, prop_schema) in props {
|
||||
let mut child_type_name = None;
|
||||
let mut target_schema = prop_schema.clone();
|
||||
let mut is_array = false;
|
||||
|
||||
// Structurally unpack the inner target entity if the object maps to an array list
|
||||
if let Some(crate::database::object::SchemaTypeOrArray::Single(t)) =
|
||||
&prop_schema.obj.type_
|
||||
{
|
||||
if t == "array" {
|
||||
is_array = true;
|
||||
if let Some(items) = &prop_schema.obj.items {
|
||||
target_schema = items.clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Determine the physical Postgres table backing the nested child schema recursively
|
||||
if let Some(family) = &target_schema.obj.family {
|
||||
child_type_name = Some(family.split('.').next_back().unwrap_or(family).to_string());
|
||||
} else if let Some(crate::database::object::SchemaTypeOrArray::Single(t)) =
|
||||
&target_schema.obj.type_
|
||||
{
|
||||
if !crate::database::object::is_primitive_type(t) {
|
||||
child_type_name = Some(t.split('.').next_back().unwrap_or(t).to_string());
|
||||
}
|
||||
} else if let Some(arr) = &target_schema.obj.one_of {
|
||||
if let Some(first) = arr.first() {
|
||||
if let Some(crate::database::object::SchemaTypeOrArray::Single(t)) = &first.obj.type_
|
||||
{
|
||||
if !crate::database::object::is_primitive_type(t) {
|
||||
child_type_name = Some(t.split('.').next_back().unwrap_or(t).to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(c_type) = child_type_name {
|
||||
// Skip edge compilation for JSONB columns — they store data inline, not relationally.
|
||||
// The physical column type from field_types is the single source of truth.
|
||||
if let Some(ft) = type_def
|
||||
.field_types
|
||||
.as_ref()
|
||||
.and_then(|v| v.get(prop_name.as_str()))
|
||||
.and_then(|v| v.as_str())
|
||||
{
|
||||
if ft == "jsonb" {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if db.types.contains_key(&c_type) {
|
||||
// Ensure the child Schema's AST has accurately compiled its own physical property keys so we can
|
||||
// inject them securely for Many-to-Many Twin Deduction disambiguation matching.
|
||||
target_schema.compile(db, root_id, format!("{}/{}", path, prop_name), errors);
|
||||
|
||||
if let Some(compiled_target_props) = target_schema.obj.compiled_properties.get() {
|
||||
let keys_for_ambiguity: Vec<String> =
|
||||
compiled_target_props.keys().cloned().collect();
|
||||
|
||||
// Interrogate the Database catalog graph to discover the exact Foreign Key Constraint connecting the components
|
||||
if let Some((relation, is_forward)) = db.resolve_relation(
|
||||
&p_type,
|
||||
&c_type,
|
||||
prop_name,
|
||||
Some(&keys_for_ambiguity),
|
||||
is_array,
|
||||
Some(root_id),
|
||||
&format!("{}/{}", path, prop_name),
|
||||
errors,
|
||||
) {
|
||||
schema_edges.insert(
|
||||
prop_name.clone(),
|
||||
crate::database::edge::Edge {
|
||||
constraint: relation.constraint.clone(),
|
||||
forward: is_forward,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
schema_edges
|
||||
}
|
||||
}
|
||||
130
src/database/compile/filter.rs
Normal file
130
src/database/compile/filter.rs
Normal file
@ -0,0 +1,130 @@
|
||||
use crate::database::Database;
|
||||
use crate::database::object::{SchemaObject, SchemaTypeOrArray};
|
||||
use crate::database::schema::Schema;
|
||||
use std::collections::BTreeMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
impl Schema {
|
||||
pub fn compile_filter(
|
||||
&self,
|
||||
_db: &Database,
|
||||
root_id: &str,
|
||||
_errors: &mut Vec<crate::drop::Error>,
|
||||
) -> Option<Schema> {
|
||||
if let Some(props) = self.obj.compiled_properties.get() {
|
||||
let mut filter_props = BTreeMap::new();
|
||||
for (key, child) in props {
|
||||
if let Some(mut filter_type) = Self::resolve_filter_type(child) {
|
||||
filter_type.push("null".to_string());
|
||||
|
||||
let mut child_obj = SchemaObject::default();
|
||||
child_obj.type_ = Some(SchemaTypeOrArray::Multiple(filter_type));
|
||||
|
||||
filter_props.insert(
|
||||
key.clone(),
|
||||
Arc::new(Schema {
|
||||
obj: child_obj,
|
||||
always_fail: false,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if !filter_props.is_empty() {
|
||||
let root_filter_type = format!("{}.filter", root_id);
|
||||
|
||||
let mut and_obj = SchemaObject::default();
|
||||
and_obj.type_ = Some(SchemaTypeOrArray::Multiple(vec![
|
||||
"array".to_string(),
|
||||
"null".to_string(),
|
||||
]));
|
||||
and_obj.items = Some(Arc::new(Schema {
|
||||
obj: SchemaObject {
|
||||
type_: Some(SchemaTypeOrArray::Single(root_filter_type.clone())),
|
||||
..Default::default()
|
||||
},
|
||||
always_fail: false,
|
||||
}));
|
||||
filter_props.insert(
|
||||
"$and".to_string(),
|
||||
Arc::new(Schema {
|
||||
obj: and_obj,
|
||||
always_fail: false,
|
||||
}),
|
||||
);
|
||||
|
||||
let mut or_obj = SchemaObject::default();
|
||||
or_obj.type_ = Some(SchemaTypeOrArray::Multiple(vec![
|
||||
"array".to_string(),
|
||||
"null".to_string(),
|
||||
]));
|
||||
or_obj.items = Some(Arc::new(Schema {
|
||||
obj: SchemaObject {
|
||||
type_: Some(SchemaTypeOrArray::Single(root_filter_type.clone())),
|
||||
..Default::default()
|
||||
},
|
||||
always_fail: false,
|
||||
}));
|
||||
filter_props.insert(
|
||||
"$or".to_string(),
|
||||
Arc::new(Schema {
|
||||
obj: or_obj,
|
||||
always_fail: false,
|
||||
}),
|
||||
);
|
||||
|
||||
let mut wrapper_obj = SchemaObject::default();
|
||||
// Conceptually link this directly into the STI lineage of the base `filter` object
|
||||
wrapper_obj.type_ = Some(SchemaTypeOrArray::Single("filter".to_string()));
|
||||
wrapper_obj.properties = Some(filter_props);
|
||||
|
||||
return Some(Schema {
|
||||
obj: wrapper_obj,
|
||||
always_fail: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn resolve_filter_type(schema: &Arc<Schema>) -> Option<Vec<String>> {
|
||||
if let Some(type_) = &schema.obj.type_ {
|
||||
match type_ {
|
||||
SchemaTypeOrArray::Single(t) => {
|
||||
return Self::map_filter_string(t, schema);
|
||||
}
|
||||
SchemaTypeOrArray::Multiple(types) => {
|
||||
for t in types {
|
||||
if t != "null" {
|
||||
return Self::map_filter_string(t, schema);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn map_filter_string(t: &str, schema: &Arc<Schema>) -> Option<Vec<String>> {
|
||||
match t {
|
||||
"string" => {
|
||||
if let Some(fmt) = &schema.obj.format {
|
||||
if fmt == "date-time" {
|
||||
return Some(vec!["date.condition".to_string()]);
|
||||
}
|
||||
}
|
||||
Some(vec!["string.condition".to_string()])
|
||||
}
|
||||
"integer" => Some(vec!["integer.condition".to_string()]),
|
||||
"number" => Some(vec!["number.condition".to_string()]),
|
||||
"boolean" => Some(vec!["boolean.condition".to_string()]),
|
||||
"object" => None, // Inline structures are ignored in Composed References
|
||||
"array" => None, // We don't filter primitive arrays or map complex arrays yet
|
||||
"null" => None,
|
||||
custom => {
|
||||
// Assume anything else is a Relational cross-boundary that already has its own .filter dynamically built
|
||||
Some(vec![format!("{}.filter", custom)])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
175
src/database/compile/mod.rs
Normal file
175
src/database/compile/mod.rs
Normal file
@ -0,0 +1,175 @@
|
||||
pub mod collection;
|
||||
pub mod edges;
|
||||
pub mod filter;
|
||||
pub mod polymorphism;
|
||||
|
||||
use crate::database::schema::Schema;
|
||||
|
||||
impl Schema {
|
||||
pub fn compile(
|
||||
&self,
|
||||
db: &crate::database::Database,
|
||||
root_id: &str,
|
||||
path: String,
|
||||
errors: &mut Vec<crate::drop::Error>,
|
||||
) {
|
||||
if self.obj.compiled_properties.get().is_some() {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(format_str) = &self.obj.format {
|
||||
if let Some(fmt) = crate::database::formats::FORMATS.get(format_str.as_str()) {
|
||||
let _ = self
|
||||
.obj
|
||||
.compiled_format
|
||||
.set(crate::database::object::CompiledFormat::Func(fmt.func));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(pattern_str) = &self.obj.pattern {
|
||||
if let Ok(re) = regex::Regex::new(pattern_str) {
|
||||
let _ = self
|
||||
.obj
|
||||
.compiled_pattern
|
||||
.set(crate::database::object::CompiledRegex(re));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(pattern_props) = &self.obj.pattern_properties {
|
||||
let mut compiled = Vec::new();
|
||||
for (k, v) in pattern_props {
|
||||
if let Ok(re) = regex::Regex::new(k) {
|
||||
compiled.push((crate::database::object::CompiledRegex(re), v.clone()));
|
||||
}
|
||||
}
|
||||
if !compiled.is_empty() {
|
||||
let _ = self.obj.compiled_pattern_properties.set(compiled);
|
||||
}
|
||||
}
|
||||
|
||||
let mut props = std::collections::BTreeMap::new();
|
||||
|
||||
// 1. Resolve INHERITANCE dependencies first
|
||||
if let Some(crate::database::object::SchemaTypeOrArray::Single(t)) = &self.obj.type_ {
|
||||
if !crate::database::object::is_primitive_type(t) {
|
||||
if let Some(parent) = db.get_scoped_schema(crate::database::realm::SchemaRealm::Type, t) {
|
||||
parent.as_ref().compile(db, t, t.clone(), errors);
|
||||
if let Some(p_props) = parent.obj.compiled_properties.get() {
|
||||
props.extend(p_props.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(crate::database::object::SchemaTypeOrArray::Multiple(types)) = &self.obj.type_ {
|
||||
let mut custom_type_count = 0;
|
||||
for t in types {
|
||||
if !crate::database::object::is_primitive_type(t) {
|
||||
custom_type_count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if custom_type_count > 1 {
|
||||
errors.push(crate::drop::Error {
|
||||
code: "MULTIPLE_INHERITANCE_PROHIBITED".to_string(),
|
||||
message: format!(
|
||||
"Schema attempts to extend multiple custom object pointers in its type array {:?}. Use 'oneOf' for polymorphism and tagged unions.",
|
||||
types
|
||||
),
|
||||
details: crate::drop::ErrorDetails {
|
||||
path: Some(path.clone()),
|
||||
schema: Some(root_id.to_string()),
|
||||
..Default::default()
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
for t in types {
|
||||
if !crate::database::object::is_primitive_type(t) {
|
||||
if let Some(parent) = db.get_scoped_schema(crate::database::realm::SchemaRealm::Type, t) {
|
||||
parent.as_ref().compile(db, t, t.clone(), errors);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Add local properties
|
||||
if let Some(local_props) = &self.obj.properties {
|
||||
for (k, v) in local_props {
|
||||
props.insert(k.clone(), v.clone());
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Add cases conditionally-defined properties recursively
|
||||
if let Some(cases) = &self.obj.cases {
|
||||
for (i, c) in cases.iter().enumerate() {
|
||||
if let Some(child) = &c.when {
|
||||
child.compile(db, root_id, format!("{}/cases/{}/when", path, i), errors);
|
||||
}
|
||||
if let Some(child) = &c.then {
|
||||
child.compile(db, root_id, format!("{}/cases/{}/then", path, i), errors);
|
||||
if let Some(t_props) = child.obj.compiled_properties.get() {
|
||||
props.extend(t_props.clone());
|
||||
}
|
||||
}
|
||||
if let Some(child) = &c.else_ {
|
||||
child.compile(db, root_id, format!("{}/cases/{}/else", path, i), errors);
|
||||
if let Some(e_props) = child.obj.compiled_properties.get() {
|
||||
props.extend(e_props.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Set the OnceLock!
|
||||
let _ = self.obj.compiled_properties.set(props.clone());
|
||||
let mut names: Vec<String> = props.keys().cloned().collect();
|
||||
names.sort();
|
||||
let _ = self.obj.compiled_property_names.set(names);
|
||||
|
||||
// 5. Compute Edges natively
|
||||
let schema_edges = self.compile_edges(db, root_id, &path, &props, errors);
|
||||
let _ = self.obj.compiled_edges.set(schema_edges);
|
||||
|
||||
// 5. Build our inline children properties recursively NOW! (Depth-first search)
|
||||
if let Some(local_props) = &self.obj.properties {
|
||||
for (k, child) in local_props {
|
||||
child.compile(db, root_id, format!("{}/{}", path, k), errors);
|
||||
}
|
||||
}
|
||||
if let Some(items) = &self.obj.items {
|
||||
items.compile(db, root_id, format!("{}/items", path), errors);
|
||||
}
|
||||
if let Some(pattern_props) = &self.obj.pattern_properties {
|
||||
for (k, child) in pattern_props {
|
||||
child.compile(db, root_id, format!("{}/{}", path, k), errors);
|
||||
}
|
||||
}
|
||||
if let Some(additional_props) = &self.obj.additional_properties {
|
||||
additional_props.compile(
|
||||
db,
|
||||
root_id,
|
||||
format!("{}/additionalProperties", path),
|
||||
errors,
|
||||
);
|
||||
}
|
||||
if let Some(one_of) = &self.obj.one_of {
|
||||
for (i, child) in one_of.iter().enumerate() {
|
||||
child.compile(db, root_id, format!("{}/oneOf/{}", path, i), errors);
|
||||
}
|
||||
}
|
||||
if let Some(arr) = &self.obj.prefix_items {
|
||||
for (i, child) in arr.iter().enumerate() {
|
||||
child.compile(db, root_id, format!("{}/prefixItems/{}", path, i), errors);
|
||||
}
|
||||
}
|
||||
if let Some(child) = &self.obj.not {
|
||||
child.compile(db, root_id, format!("{}/not", path), errors);
|
||||
}
|
||||
if let Some(child) = &self.obj.contains {
|
||||
child.compile(db, root_id, format!("{}/contains", path), errors);
|
||||
}
|
||||
|
||||
self.compile_polymorphism(db, root_id, &path, errors);
|
||||
}
|
||||
}
|
||||
153
src/database/compile/polymorphism.rs
Normal file
153
src/database/compile/polymorphism.rs
Normal file
@ -0,0 +1,153 @@
|
||||
use crate::database::schema::Schema;
|
||||
|
||||
impl Schema {
|
||||
pub fn compile_polymorphism(
|
||||
&self,
|
||||
db: &crate::database::Database,
|
||||
root_id: &str,
|
||||
path: &str,
|
||||
errors: &mut Vec<crate::drop::Error>,
|
||||
) {
|
||||
let mut options = std::collections::BTreeMap::new();
|
||||
let mut strategy = String::new();
|
||||
|
||||
if let Some(family) = &self.obj.family {
|
||||
let family_base = family.split('.').next_back().unwrap_or(family).to_string();
|
||||
let family_prefix = family
|
||||
.strip_suffix(&family_base)
|
||||
.unwrap_or("")
|
||||
.trim_end_matches('.');
|
||||
|
||||
if let Some(type_def) = db.types.get(&family_base) {
|
||||
if type_def.variations.len() > 1 && type_def.variations.iter().any(|v| v != &family_base) {
|
||||
// Scenario A / B: Table Variations
|
||||
strategy = "type".to_string();
|
||||
for var in &type_def.variations {
|
||||
let target_id = if family_prefix.is_empty() {
|
||||
var.to_string()
|
||||
} else {
|
||||
format!("{}.{}", family_prefix, var)
|
||||
};
|
||||
|
||||
if db.get_scoped_schema(crate::database::realm::SchemaRealm::Type, &target_id).is_some() {
|
||||
options.insert(var.to_string(), (None, Some(target_id)));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Scenario C: Single Table Inheritance (Horizontal)
|
||||
strategy = "kind".to_string();
|
||||
|
||||
let suffix = format!(".{}", family_base);
|
||||
|
||||
for (id, schema) in &type_def.schemas {
|
||||
if id.ends_with(&suffix) || id == &family_base {
|
||||
if let Some(kind_val) = schema.obj.get_discriminator_value("kind", id) {
|
||||
options.insert(kind_val, (None, Some(id.to_string())));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if let Some(one_of) = &self.obj.one_of {
|
||||
let mut type_vals = std::collections::HashSet::new();
|
||||
let mut kind_vals = std::collections::HashSet::new();
|
||||
let mut disjoint_base = true;
|
||||
let mut structural_types = std::collections::HashSet::new();
|
||||
|
||||
for c in one_of {
|
||||
let mut child_id = String::new();
|
||||
let mut child_is_primitive = false;
|
||||
|
||||
if let Some(crate::database::object::SchemaTypeOrArray::Single(t)) = &c.obj.type_ {
|
||||
if crate::database::object::is_primitive_type(t) {
|
||||
child_is_primitive = true;
|
||||
structural_types.insert(t.clone());
|
||||
} else {
|
||||
child_id = t.clone();
|
||||
structural_types.insert("object".to_string());
|
||||
}
|
||||
} else {
|
||||
disjoint_base = false;
|
||||
}
|
||||
|
||||
if !child_is_primitive {
|
||||
if let Some(t_val) = c.obj.get_discriminator_value("type", &child_id) {
|
||||
type_vals.insert(t_val);
|
||||
}
|
||||
if let Some(k_val) = c.obj.get_discriminator_value("kind", &child_id) {
|
||||
kind_vals.insert(k_val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if disjoint_base && structural_types.len() == one_of.len() {
|
||||
strategy = "".to_string();
|
||||
for (i, c) in one_of.iter().enumerate() {
|
||||
if let Some(crate::database::object::SchemaTypeOrArray::Single(t)) = &c.obj.type_ {
|
||||
if crate::database::object::is_primitive_type(t) {
|
||||
options.insert(t.clone(), (Some(i), None));
|
||||
} else {
|
||||
options.insert("object".to_string(), (Some(i), None));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
strategy = if type_vals.len() > 1 && type_vals.len() == one_of.len() {
|
||||
"type".to_string()
|
||||
} else if kind_vals.len() > 1 && kind_vals.len() == one_of.len() {
|
||||
"kind".to_string()
|
||||
} else {
|
||||
"".to_string()
|
||||
};
|
||||
|
||||
if strategy.is_empty() {
|
||||
errors.push(crate::drop::Error {
|
||||
code: "AMBIGUOUS_POLYMORPHISM".to_string(),
|
||||
message: format!("oneOf boundaries must map mathematically unique 'type' or 'kind' discriminators, or strictly contain disjoint primitive types."),
|
||||
details: crate::drop::ErrorDetails {
|
||||
path: Some(path.to_string()),
|
||||
schema: Some(root_id.to_string()),
|
||||
..Default::default()
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
for (i, c) in one_of.iter().enumerate() {
|
||||
let mut child_id = String::new();
|
||||
if let Some(crate::database::object::SchemaTypeOrArray::Single(t)) = &c.obj.type_ {
|
||||
if !crate::database::object::is_primitive_type(t) {
|
||||
child_id = t.clone();
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(val) = c.obj.get_discriminator_value(&strategy, &child_id) {
|
||||
if options.contains_key(&val) {
|
||||
errors.push(crate::drop::Error {
|
||||
code: "POLYMORPHIC_COLLISION".to_string(),
|
||||
message: format!("Polymorphic boundary defines multiple candidates mapped to the identical discriminator value '{}'.", val),
|
||||
details: crate::drop::ErrorDetails {
|
||||
path: Some(path.to_string()),
|
||||
schema: Some(root_id.to_string()),
|
||||
..Default::default()
|
||||
}
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
options.insert(val, (Some(i), None));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
if !options.is_empty() {
|
||||
if !strategy.is_empty() {
|
||||
let _ = self.obj.compiled_discriminator.set(strategy);
|
||||
}
|
||||
let _ = self.obj.compiled_options.set(options);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,3 +1,4 @@
|
||||
pub mod compile;
|
||||
pub mod edge;
|
||||
pub mod r#enum;
|
||||
pub mod executors;
|
||||
@ -5,12 +6,11 @@ pub mod formats;
|
||||
pub mod object;
|
||||
pub mod page;
|
||||
pub mod punc;
|
||||
pub mod realm;
|
||||
pub mod relation;
|
||||
pub mod schema;
|
||||
pub mod r#type;
|
||||
|
||||
// External mock exports inside the executor sub-folder
|
||||
|
||||
use r#enum::Enum;
|
||||
use executors::DatabaseExecutor;
|
||||
|
||||
@ -21,6 +21,7 @@ use executors::pgrx::SpiExecutor;
|
||||
use executors::mock::MockExecutor;
|
||||
|
||||
use punc::Punc;
|
||||
use realm::SchemaRealm;
|
||||
use relation::Relation;
|
||||
use schema::Schema;
|
||||
use serde_json::Value;
|
||||
@ -28,12 +29,13 @@ use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use r#type::Type;
|
||||
|
||||
#[derive(serde::Serialize)]
|
||||
pub struct Database {
|
||||
pub enums: HashMap<String, Enum>,
|
||||
pub types: HashMap<String, Type>,
|
||||
pub puncs: HashMap<String, Punc>,
|
||||
pub relations: HashMap<String, Relation>,
|
||||
pub schemas: HashMap<String, Arc<Schema>>,
|
||||
#[serde(skip)]
|
||||
pub executor: Box<dyn DatabaseExecutor + Send + Sync>,
|
||||
}
|
||||
|
||||
@ -44,7 +46,6 @@ impl Database {
|
||||
types: HashMap::new(),
|
||||
relations: HashMap::new(),
|
||||
puncs: HashMap::new(),
|
||||
schemas: HashMap::new(),
|
||||
#[cfg(not(test))]
|
||||
executor: Box::new(SpiExecutor::new()),
|
||||
#[cfg(test)]
|
||||
@ -153,26 +154,6 @@ impl Database {
|
||||
}
|
||||
}
|
||||
|
||||
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(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 key '{}': {}", key, e),
|
||||
details: crate::drop::ErrorDetails {
|
||||
context: Some(serde_json::json!(key)),
|
||||
..Default::default()
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
db.compile(&mut errors);
|
||||
let drop = if errors.is_empty() {
|
||||
crate::drop::Drop::success()
|
||||
@ -209,77 +190,171 @@ impl Database {
|
||||
}
|
||||
|
||||
pub fn compile(&mut self, errors: &mut Vec<crate::drop::Error>) {
|
||||
let mut harvested = Vec::new();
|
||||
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.collect_schemas(errors);
|
||||
|
||||
// Mathematically evaluate all property inheritances, formats, schemas, and foreign key edges topographically over OnceLocks
|
||||
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(), errors);
|
||||
// Formally evaluate properties with strict 3-pass Ordered Graph execution natively
|
||||
for (_, enum_def) in &self.enums {
|
||||
for (schema_id, schema_arc) in &enum_def.schemas {
|
||||
let root_id = schema_id.split('/').next().unwrap_or(schema_id);
|
||||
schema_arc.as_ref().compile(self, root_id, schema_id.clone(), errors);
|
||||
}
|
||||
}
|
||||
for (_, type_def) in &self.types {
|
||||
for (schema_id, schema_arc) in &type_def.schemas {
|
||||
let root_id = schema_id.split('/').next().unwrap_or(schema_id);
|
||||
schema_arc.as_ref().compile(self, root_id, schema_id.clone(), errors);
|
||||
}
|
||||
}
|
||||
for (_, punc_def) in &self.puncs {
|
||||
for (schema_id, schema_arc) in &punc_def.schemas {
|
||||
let root_id = schema_id.split('/').next().unwrap_or(schema_id);
|
||||
schema_arc.as_ref().compile(self, root_id, schema_id.clone(), errors);
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 2: Synthesize Composed Filter References
|
||||
let mut filter_schemas = Vec::new();
|
||||
for (type_name, type_def) in &self.types {
|
||||
for (id, schema_arc) in &type_def.schemas {
|
||||
// Only run synthesis on actual structured, table-backed boundaries. Exclude subschemas!
|
||||
let base_name = id.split('.').last().unwrap_or(id);
|
||||
let is_table_backed = base_name == type_def.name;
|
||||
if is_table_backed && !id.contains('/') {
|
||||
if let Some(filter_schema) = schema_arc.compile_filter(self, id, errors) {
|
||||
filter_schemas.push((
|
||||
type_name.clone(),
|
||||
format!("{}.filter", id),
|
||||
Arc::new(filter_schema),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut filter_ids = Vec::new();
|
||||
for (type_name, id, filter_arc) in filter_schemas {
|
||||
filter_ids.push((type_name.clone(), id.clone()));
|
||||
if let Some(t) = self.types.get_mut(&type_name) {
|
||||
t.schemas.insert(id, filter_arc);
|
||||
}
|
||||
}
|
||||
|
||||
// Now actively compile the newly injected filters to lock all nested compose references natively
|
||||
for (type_name, id) in filter_ids {
|
||||
if let Some(filter_arc) = self.types.get(&type_name).and_then(|t| t.schemas.get(&id)).cloned() {
|
||||
let root_id = id.split('/').next().unwrap_or(&id);
|
||||
filter_arc
|
||||
.as_ref()
|
||||
.compile(self, root_id, id.clone(), errors);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn collect_schemas(&mut self, errors: &mut Vec<crate::drop::Error>) {
|
||||
let mut to_insert = Vec::new();
|
||||
let mut type_insert = Vec::new();
|
||||
let mut punc_insert = Vec::new();
|
||||
let mut enum_insert = Vec::new();
|
||||
|
||||
// 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 (type_name, type_def) in &self.types {
|
||||
for (id, schema_arc) in &type_def.schemas {
|
||||
to_insert.push((id.clone(), Arc::clone(schema_arc)));
|
||||
let mut local_insert = Vec::new();
|
||||
crate::database::schema::Schema::collect_schemas(
|
||||
schema_arc,
|
||||
id,
|
||||
id.clone(),
|
||||
&mut to_insert,
|
||||
errors,
|
||||
);
|
||||
}
|
||||
}
|
||||
for punc_def in self.puncs.values() {
|
||||
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 (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,
|
||||
&mut local_insert,
|
||||
errors,
|
||||
);
|
||||
for entry in &local_insert {
|
||||
type_insert.push((type_name.clone(), entry.0.clone(), Arc::clone(&entry.1)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (id, schema_arc) in to_insert {
|
||||
self.schemas.insert(id, schema_arc);
|
||||
for (punc_name, punc_def) in &self.puncs {
|
||||
for (id, schema_arc) in &punc_def.schemas {
|
||||
let mut local_insert = Vec::new();
|
||||
crate::database::schema::Schema::collect_schemas(
|
||||
schema_arc,
|
||||
id,
|
||||
id.clone(),
|
||||
&mut local_insert,
|
||||
errors,
|
||||
);
|
||||
for entry in &local_insert {
|
||||
punc_insert.push((punc_name.clone(), entry.0.clone(), Arc::clone(&entry.1)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (enum_name, enum_def) in &self.enums {
|
||||
for (id, schema_arc) in &enum_def.schemas {
|
||||
let mut local_insert = Vec::new();
|
||||
crate::database::schema::Schema::collect_schemas(
|
||||
schema_arc,
|
||||
id,
|
||||
id.clone(),
|
||||
&mut local_insert,
|
||||
errors,
|
||||
);
|
||||
for entry in &local_insert {
|
||||
enum_insert.push((enum_name.clone(), entry.0.clone(), Arc::clone(&entry.1)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Apply local scopes
|
||||
for (origin_name, id, schema_arc) in type_insert {
|
||||
if let Some(t) = self.types.get_mut(&origin_name) {
|
||||
t.schemas.insert(id, schema_arc);
|
||||
}
|
||||
}
|
||||
for (origin_name, id, schema_arc) in punc_insert {
|
||||
if let Some(p) = self.puncs.get_mut(&origin_name) {
|
||||
p.schemas.insert(id, schema_arc);
|
||||
}
|
||||
}
|
||||
for (origin_name, id, schema_arc) in enum_insert {
|
||||
if let Some(e) = self.enums.get_mut(&origin_name) {
|
||||
e.schemas.insert(id, schema_arc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_scoped_schema(&self, realm: SchemaRealm, schema_id: &str) -> Option<Arc<Schema>> {
|
||||
// Punc Realm natively maps mathematically to `.request` and `.response` shapes
|
||||
if realm == SchemaRealm::Punc {
|
||||
if schema_id.ends_with(".request") || schema_id.ends_with(".response") {
|
||||
let punc_name = schema_id
|
||||
.trim_end_matches(".request")
|
||||
.trim_end_matches(".response");
|
||||
return self.puncs.get(punc_name).and_then(|p| p.schemas.get(schema_id).cloned());
|
||||
}
|
||||
}
|
||||
|
||||
let clean_id = schema_id.trim_end_matches(".filter");
|
||||
let root_id = clean_id.split('/').next().unwrap_or(clean_id);
|
||||
let base_name = root_id.split('.').next_back().unwrap_or(root_id);
|
||||
|
||||
// Puncs and Types can lookup Table boundaries
|
||||
if realm == SchemaRealm::Type || realm == SchemaRealm::Punc {
|
||||
if let Some(type_def) = self.types.get(base_name) {
|
||||
if let Some(schema) = type_def.schemas.get(schema_id) {
|
||||
return Some(schema.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// All realms can intrinsically look up enumerations
|
||||
if let Some(enum_def) = self.enums.get(base_name) {
|
||||
if let Some(schema) = enum_def.schemas.get(schema_id) {
|
||||
return Some(schema.clone());
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Inspects the Postgres pg_constraint relations catalog to securely identify
|
||||
|
||||
@ -37,7 +37,7 @@ pub struct SchemaObject {
|
||||
#[serde(rename = "additionalProperties")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub additional_properties: Option<Arc<Schema>>,
|
||||
#[serde(rename = "$family")]
|
||||
#[serde(rename = "family")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub family: Option<String>,
|
||||
|
||||
@ -154,12 +154,15 @@ pub struct SchemaObject {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub extensible: Option<bool>,
|
||||
|
||||
#[serde(rename = "compiledProperties")]
|
||||
// Contains ALL structural fields perfectly flattened from the ENTIRE Database inheritance tree (e.g. `entity` fields like `id`) as well as local fields hidden inside conditional `cases` blocks.
|
||||
// This JSON exported array gives clients absolute deterministic visibility to O(1) validation and masking bounds without duplicating structural memory.
|
||||
#[serde(rename = "compiledPropertyNames")]
|
||||
#[serde(skip_deserializing)]
|
||||
#[serde(skip_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>>,
|
||||
|
||||
// Internal structural representation caching active AST Node maps. Unlike the Go framework counterpart, the JSPG implementation DOES natively include ALL ancestral inheritance boundary schemas because it compiles locally against the raw database graph.
|
||||
#[serde(skip)]
|
||||
pub compiled_properties: OnceLock<BTreeMap<String, Arc<Schema>>>,
|
||||
|
||||
@ -307,7 +310,7 @@ impl SchemaObject {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 2. Implicit table-backed rule: Does its $family boundary map directly to the global database catalog?
|
||||
// 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) {
|
||||
|
||||
@ -15,6 +15,7 @@ pub struct Punc {
|
||||
pub public: bool,
|
||||
pub form: bool,
|
||||
pub get: Option<String>,
|
||||
pub save: Option<String>,
|
||||
pub page: Option<Page>,
|
||||
#[serde(default)]
|
||||
pub schemas: std::collections::BTreeMap<String, Arc<Schema>>,
|
||||
|
||||
6
src/database/realm.rs
Normal file
6
src/database/realm.rs
Normal file
@ -0,0 +1,6 @@
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum SchemaRealm {
|
||||
Enum,
|
||||
Type,
|
||||
Punc,
|
||||
}
|
||||
@ -3,6 +3,8 @@ use serde::{Deserialize, Serialize};
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||
#[serde(default)]
|
||||
pub struct Relation {
|
||||
pub id: String,
|
||||
pub r#type: String,
|
||||
pub constraint: String,
|
||||
pub source_type: String,
|
||||
pub source_columns: Vec<String>,
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
use crate::database::object::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Default)]
|
||||
pub struct Schema {
|
||||
#[serde(flatten)]
|
||||
@ -22,629 +22,6 @@ impl std::ops::DerefMut for Schema {
|
||||
}
|
||||
}
|
||||
|
||||
impl Schema {
|
||||
pub fn compile(
|
||||
&self,
|
||||
db: &crate::database::Database,
|
||||
root_id: &str,
|
||||
path: String,
|
||||
errors: &mut Vec<crate::drop::Error>,
|
||||
) {
|
||||
if self.obj.compiled_properties.get().is_some() {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(format_str) = &self.obj.format {
|
||||
if let Some(fmt) = crate::database::formats::FORMATS.get(format_str.as_str()) {
|
||||
let _ = self
|
||||
.obj
|
||||
.compiled_format
|
||||
.set(crate::database::object::CompiledFormat::Func(fmt.func));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(pattern_str) = &self.obj.pattern {
|
||||
if let Ok(re) = regex::Regex::new(pattern_str) {
|
||||
let _ = self
|
||||
.obj
|
||||
.compiled_pattern
|
||||
.set(crate::database::object::CompiledRegex(re));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(pattern_props) = &self.obj.pattern_properties {
|
||||
let mut compiled = Vec::new();
|
||||
for (k, v) in pattern_props {
|
||||
if let Ok(re) = regex::Regex::new(k) {
|
||||
compiled.push((crate::database::object::CompiledRegex(re), v.clone()));
|
||||
}
|
||||
}
|
||||
if !compiled.is_empty() {
|
||||
let _ = self.obj.compiled_pattern_properties.set(compiled);
|
||||
}
|
||||
}
|
||||
|
||||
let mut props = std::collections::BTreeMap::new();
|
||||
|
||||
// 1. Resolve INHERITANCE dependencies first
|
||||
if let Some(crate::database::object::SchemaTypeOrArray::Single(t)) = &self.obj.type_ {
|
||||
if !crate::database::object::is_primitive_type(t) {
|
||||
if let Some(parent) = db.schemas.get(t) {
|
||||
parent.as_ref().compile(db, t, t.clone(), errors);
|
||||
if let Some(p_props) = parent.obj.compiled_properties.get() {
|
||||
props.extend(p_props.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(crate::database::object::SchemaTypeOrArray::Multiple(types)) = &self.obj.type_ {
|
||||
let mut custom_type_count = 0;
|
||||
for t in types {
|
||||
if !crate::database::object::is_primitive_type(t) {
|
||||
custom_type_count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if custom_type_count > 1 {
|
||||
errors.push(crate::drop::Error {
|
||||
code: "MULTIPLE_INHERITANCE_PROHIBITED".to_string(),
|
||||
message: format!(
|
||||
"Schema attempts to extend multiple custom object pointers in its type array {:?}. Use 'oneOf' for polymorphism and tagged unions.",
|
||||
types
|
||||
),
|
||||
details: crate::drop::ErrorDetails {
|
||||
path: Some(path.clone()),
|
||||
schema: Some(root_id.to_string()),
|
||||
..Default::default()
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
for t in types {
|
||||
if !crate::database::object::is_primitive_type(t) {
|
||||
if let Some(parent) = db.schemas.get(t) {
|
||||
parent.as_ref().compile(db, t, t.clone(), errors);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Add local properties
|
||||
if let Some(local_props) = &self.obj.properties {
|
||||
for (k, v) in local_props {
|
||||
props.insert(k.clone(), v.clone());
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Set the OnceLock!
|
||||
let _ = self.obj.compiled_properties.set(props.clone());
|
||||
let mut names: Vec<String> = props.keys().cloned().collect();
|
||||
names.sort();
|
||||
let _ = self.obj.compiled_property_names.set(names);
|
||||
|
||||
// 4. Compute Edges natively
|
||||
let schema_edges = self.compile_edges(db, root_id, &path, &props, errors);
|
||||
let _ = self.obj.compiled_edges.set(schema_edges);
|
||||
|
||||
// 5. Build our inline children properties recursively NOW! (Depth-first search)
|
||||
if let Some(local_props) = &self.obj.properties {
|
||||
for (k, child) in local_props {
|
||||
child.compile(db, root_id, format!("{}/{}", path, k), errors);
|
||||
}
|
||||
}
|
||||
if let Some(items) = &self.obj.items {
|
||||
items.compile(db, root_id, format!("{}/items", path), errors);
|
||||
}
|
||||
if let Some(pattern_props) = &self.obj.pattern_properties {
|
||||
for (k, child) in pattern_props {
|
||||
child.compile(db, root_id, format!("{}/{}", path, k), errors);
|
||||
}
|
||||
}
|
||||
if let Some(additional_props) = &self.obj.additional_properties {
|
||||
additional_props.compile(
|
||||
db,
|
||||
root_id,
|
||||
format!("{}/additionalProperties", path),
|
||||
errors,
|
||||
);
|
||||
}
|
||||
if let Some(one_of) = &self.obj.one_of {
|
||||
for (i, child) in one_of.iter().enumerate() {
|
||||
child.compile(
|
||||
db,
|
||||
root_id,
|
||||
format!("{}/oneOf/{}", path, i),
|
||||
errors,
|
||||
);
|
||||
}
|
||||
}
|
||||
if let Some(arr) = &self.obj.prefix_items {
|
||||
for (i, child) in arr.iter().enumerate() {
|
||||
child.compile(
|
||||
db,
|
||||
root_id,
|
||||
format!("{}/prefixItems/{}", path, i),
|
||||
errors,
|
||||
);
|
||||
}
|
||||
}
|
||||
if let Some(child) = &self.obj.not {
|
||||
child.compile(db, root_id, format!("{}/not", path), errors);
|
||||
}
|
||||
if let Some(child) = &self.obj.contains {
|
||||
child.compile(db, root_id, format!("{}/contains", path), errors);
|
||||
}
|
||||
if let Some(cases) = &self.obj.cases {
|
||||
for (i, c) in cases.iter().enumerate() {
|
||||
if let Some(child) = &c.when {
|
||||
child.compile(
|
||||
db,
|
||||
root_id,
|
||||
format!("{}/cases/{}/when", path, i),
|
||||
errors,
|
||||
);
|
||||
}
|
||||
if let Some(child) = &c.then {
|
||||
child.compile(
|
||||
db,
|
||||
root_id,
|
||||
format!("{}/cases/{}/then", path, i),
|
||||
errors,
|
||||
);
|
||||
}
|
||||
if let Some(child) = &c.else_ {
|
||||
child.compile(
|
||||
db,
|
||||
root_id,
|
||||
format!("{}/cases/{}/else", path, i),
|
||||
errors,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.compile_polymorphism(db, root_id, &path, errors);
|
||||
}
|
||||
|
||||
/// Dynamically infers and compiles all structural database relationships between this Schema
|
||||
/// and its nested children. This functions recursively traverses the JSON Schema abstract syntax
|
||||
/// tree, identifies physical PostgreSQL table boundaries, and locks the resulting relation
|
||||
/// constraint paths directly onto the `compiled_edges` map in O(1) memory.
|
||||
pub fn compile_edges(
|
||||
&self,
|
||||
db: &crate::database::Database,
|
||||
root_id: &str,
|
||||
path: &str,
|
||||
props: &std::collections::BTreeMap<String, std::sync::Arc<Schema>>,
|
||||
errors: &mut Vec<crate::drop::Error>,
|
||||
) -> std::collections::BTreeMap<String, crate::database::edge::Edge> {
|
||||
let mut schema_edges = std::collections::BTreeMap::new();
|
||||
|
||||
// Determine the physical Database Table Name this schema structurally represents
|
||||
// Plucks the polymorphic discriminator via dot-notation (e.g. extracting "person" from "full.person")
|
||||
let mut parent_type_name = None;
|
||||
|
||||
if let Some(family) = &self.obj.family {
|
||||
// 1. Explicit horizontal routing
|
||||
parent_type_name = Some(family.split('.').next_back().unwrap_or(family).to_string());
|
||||
} else if path == root_id {
|
||||
// 2. Root nodes trust their exact registry footprint
|
||||
let base_type_name = path.split('.').next_back().unwrap_or(path).to_string();
|
||||
if db.types.contains_key(&base_type_name) {
|
||||
parent_type_name = Some(base_type_name);
|
||||
}
|
||||
} else if let Some(crate::database::object::SchemaTypeOrArray::Single(t)) = &self.obj.type_ {
|
||||
// 3. Nested graphs trust their explicit struct pointer reference
|
||||
if !crate::database::object::is_primitive_type(t) {
|
||||
parent_type_name = Some(t.split('.').next_back().unwrap_or(t).to_string());
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(p_type) = parent_type_name {
|
||||
// Proceed only if the resolved table physically exists within the Postgres Type hierarchy
|
||||
if let Some(type_def) = db.types.get(&p_type) {
|
||||
// Iterate over all discovered schema boundaries mapped inside the object
|
||||
for (prop_name, prop_schema) in props {
|
||||
let mut child_type_name = None;
|
||||
let mut target_schema = prop_schema.clone();
|
||||
let mut is_array = false;
|
||||
|
||||
// Structurally unpack the inner target entity if the object maps to an array list
|
||||
if let Some(crate::database::object::SchemaTypeOrArray::Single(t)) =
|
||||
&prop_schema.obj.type_
|
||||
{
|
||||
if t == "array" {
|
||||
is_array = true;
|
||||
if let Some(items) = &prop_schema.obj.items {
|
||||
target_schema = items.clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Determine the physical Postgres table backing the nested child schema recursively
|
||||
if let Some(family) = &target_schema.obj.family {
|
||||
child_type_name = Some(family.split('.').next_back().unwrap_or(family).to_string());
|
||||
} else if let Some(crate::database::object::SchemaTypeOrArray::Single(t)) =
|
||||
&target_schema.obj.type_
|
||||
{
|
||||
if !crate::database::object::is_primitive_type(t) {
|
||||
child_type_name = Some(t.split('.').next_back().unwrap_or(t).to_string());
|
||||
}
|
||||
} else if let Some(arr) = &target_schema.obj.one_of {
|
||||
if let Some(first) = arr.first() {
|
||||
if let Some(crate::database::object::SchemaTypeOrArray::Single(t)) = &first.obj.type_
|
||||
{
|
||||
if !crate::database::object::is_primitive_type(t) {
|
||||
child_type_name = Some(t.split('.').next_back().unwrap_or(t).to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(c_type) = child_type_name {
|
||||
// Skip edge compilation for JSONB columns — they store data inline, not relationally.
|
||||
// The physical column type from field_types is the single source of truth.
|
||||
if let Some(ft) = type_def.field_types.as_ref()
|
||||
.and_then(|v| v.get(prop_name.as_str()))
|
||||
.and_then(|v| v.as_str())
|
||||
{
|
||||
if ft == "jsonb" {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if db.types.contains_key(&c_type) {
|
||||
// Ensure the child Schema's AST has accurately compiled its own physical property keys so we can
|
||||
// inject them securely for Many-to-Many Twin Deduction disambiguation matching.
|
||||
target_schema.compile(
|
||||
db,
|
||||
root_id,
|
||||
format!("{}/{}", path, prop_name),
|
||||
errors,
|
||||
);
|
||||
|
||||
if let Some(compiled_target_props) = target_schema.obj.compiled_properties.get() {
|
||||
let keys_for_ambiguity: Vec<String> =
|
||||
compiled_target_props.keys().cloned().collect();
|
||||
|
||||
// Interrogate the Database catalog graph to discover the exact Foreign Key Constraint connecting the components
|
||||
if let Some((relation, is_forward)) = db.resolve_relation(
|
||||
&p_type,
|
||||
&c_type,
|
||||
prop_name,
|
||||
Some(&keys_for_ambiguity),
|
||||
is_array,
|
||||
Some(root_id),
|
||||
&format!("{}/{}", path, prop_name),
|
||||
errors,
|
||||
) {
|
||||
schema_edges.insert(
|
||||
prop_name.clone(),
|
||||
crate::database::edge::Edge {
|
||||
constraint: relation.constraint.clone(),
|
||||
forward: is_forward,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
schema_edges
|
||||
}
|
||||
|
||||
pub fn compile_polymorphism(
|
||||
&self,
|
||||
db: &crate::database::Database,
|
||||
root_id: &str,
|
||||
path: &str,
|
||||
errors: &mut Vec<crate::drop::Error>,
|
||||
) {
|
||||
let mut options = std::collections::BTreeMap::new();
|
||||
let mut strategy = String::new();
|
||||
|
||||
if let Some(family) = &self.obj.family {
|
||||
let family_base = family.split('.').next_back().unwrap_or(family).to_string();
|
||||
let family_prefix = family
|
||||
.strip_suffix(&family_base)
|
||||
.unwrap_or("")
|
||||
.trim_end_matches('.');
|
||||
|
||||
if let Some(type_def) = db.types.get(&family_base) {
|
||||
if type_def.variations.len() > 1 && type_def.variations.iter().any(|v| v != &family_base) {
|
||||
// Scenario A / B: Table Variations
|
||||
strategy = "type".to_string();
|
||||
for var in &type_def.variations {
|
||||
let target_id = if family_prefix.is_empty() {
|
||||
var.to_string()
|
||||
} else {
|
||||
format!("{}.{}", family_prefix, var)
|
||||
};
|
||||
|
||||
if db.schemas.contains_key(&target_id) {
|
||||
options.insert(var.to_string(), (None, Some(target_id)));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Scenario C: Single Table Inheritance (Horizontal)
|
||||
strategy = "kind".to_string();
|
||||
|
||||
let suffix = format!(".{}", family_base);
|
||||
|
||||
for (id, schema) in &type_def.schemas {
|
||||
if id.ends_with(&suffix) || id == &family_base {
|
||||
if let Some(kind_val) = schema.obj.get_discriminator_value("kind", id) {
|
||||
options.insert(kind_val, (None, Some(id.to_string())));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if let Some(one_of) = &self.obj.one_of {
|
||||
let mut type_vals = std::collections::HashSet::new();
|
||||
let mut kind_vals = std::collections::HashSet::new();
|
||||
let mut disjoint_base = true;
|
||||
let mut structural_types = std::collections::HashSet::new();
|
||||
|
||||
for c in one_of {
|
||||
let mut child_id = String::new();
|
||||
let mut child_is_primitive = false;
|
||||
|
||||
if let Some(crate::database::object::SchemaTypeOrArray::Single(t)) = &c.obj.type_ {
|
||||
if crate::database::object::is_primitive_type(t) {
|
||||
child_is_primitive = true;
|
||||
structural_types.insert(t.clone());
|
||||
} else {
|
||||
child_id = t.clone();
|
||||
structural_types.insert("object".to_string());
|
||||
}
|
||||
} else {
|
||||
disjoint_base = false;
|
||||
}
|
||||
|
||||
if !child_is_primitive {
|
||||
if let Some(t_val) = c.obj.get_discriminator_value("type", &child_id) {
|
||||
type_vals.insert(t_val);
|
||||
}
|
||||
if let Some(k_val) = c.obj.get_discriminator_value("kind", &child_id) {
|
||||
kind_vals.insert(k_val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if disjoint_base && structural_types.len() == one_of.len() {
|
||||
strategy = "".to_string();
|
||||
for (i, c) in one_of.iter().enumerate() {
|
||||
if let Some(crate::database::object::SchemaTypeOrArray::Single(t)) = &c.obj.type_ {
|
||||
if crate::database::object::is_primitive_type(t) {
|
||||
options.insert(t.clone(), (Some(i), None));
|
||||
} else {
|
||||
options.insert("object".to_string(), (Some(i), None));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
strategy = if type_vals.len() > 1 && type_vals.len() == one_of.len() {
|
||||
"type".to_string()
|
||||
} else if kind_vals.len() > 1 && kind_vals.len() == one_of.len() {
|
||||
"kind".to_string()
|
||||
} else {
|
||||
"".to_string()
|
||||
};
|
||||
|
||||
if strategy.is_empty() {
|
||||
errors.push(crate::drop::Error {
|
||||
code: "AMBIGUOUS_POLYMORPHISM".to_string(),
|
||||
message: format!("oneOf boundaries must map mathematically unique 'type' or 'kind' discriminators, or strictly contain disjoint primitive types."),
|
||||
details: crate::drop::ErrorDetails {
|
||||
path: Some(path.to_string()),
|
||||
schema: Some(root_id.to_string()),
|
||||
..Default::default()
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
for (i, c) in one_of.iter().enumerate() {
|
||||
let mut child_id = String::new();
|
||||
if let Some(crate::database::object::SchemaTypeOrArray::Single(t)) = &c.obj.type_ {
|
||||
if !crate::database::object::is_primitive_type(t) {
|
||||
child_id = t.clone();
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(val) = c.obj.get_discriminator_value(&strategy, &child_id) {
|
||||
if options.contains_key(&val) {
|
||||
errors.push(crate::drop::Error {
|
||||
code: "POLYMORPHIC_COLLISION".to_string(),
|
||||
message: format!("Polymorphic boundary defines multiple candidates mapped to the identical discriminator value '{}'.", val),
|
||||
details: crate::drop::ErrorDetails {
|
||||
path: Some(path.to_string()),
|
||||
schema: Some(root_id.to_string()),
|
||||
..Default::default()
|
||||
}
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
options.insert(val, (Some(i), None));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
if !options.is_empty() {
|
||||
if !strategy.is_empty() {
|
||||
let _ = self.obj.compiled_discriminator.set(strategy);
|
||||
}
|
||||
let _ = self.obj.compiled_options.set(options);
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
fn validate_identifier(
|
||||
id: &str,
|
||||
field_name: &str,
|
||||
root_id: &str,
|
||||
path: &str,
|
||||
errors: &mut Vec<crate::drop::Error>,
|
||||
) {
|
||||
#[cfg(not(test))]
|
||||
for c in id.chars() {
|
||||
if !c.is_ascii_lowercase() && !c.is_ascii_digit() && c != '_' && c != '.' {
|
||||
errors.push(crate::drop::Error {
|
||||
code: "INVALID_IDENTIFIER".to_string(),
|
||||
message: format!(
|
||||
"Invalid character '{}' in JSON Schema '{}' property: '{}'. Identifiers must exclusively contain [a-z0-9_.]",
|
||||
c, field_name, id
|
||||
),
|
||||
details: crate::drop::ErrorDetails {
|
||||
path: Some(path.to_string()),
|
||||
schema: Some(root_id.to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn collect_schemas(
|
||||
schema_arc: &Arc<Schema>,
|
||||
root_id: &str,
|
||||
path: String,
|
||||
to_insert: &mut Vec<(String, Arc<Schema>)>,
|
||||
errors: &mut Vec<crate::drop::Error>,
|
||||
) {
|
||||
if let Some(crate::database::object::SchemaTypeOrArray::Single(t)) = &schema_arc.obj.type_ {
|
||||
if t == "array" {
|
||||
if let Some(items) = &schema_arc.obj.items {
|
||||
if let Some(crate::database::object::SchemaTypeOrArray::Single(it)) = &items.obj.type_ {
|
||||
if !crate::database::object::is_primitive_type(it) {
|
||||
if items.obj.properties.is_some() || items.obj.cases.is_some() {
|
||||
to_insert.push((path.clone(), Arc::clone(schema_arc)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if !crate::database::object::is_primitive_type(t) {
|
||||
Self::validate_identifier(t, "type", root_id, &path, errors);
|
||||
|
||||
// Is this an explicit inline ad-hoc composition?
|
||||
if schema_arc.obj.properties.is_some() || schema_arc.obj.cases.is_some() {
|
||||
to_insert.push((path.clone(), Arc::clone(schema_arc)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(family) = &schema_arc.obj.family {
|
||||
Self::validate_identifier(family, "$family", root_id, &path, errors);
|
||||
}
|
||||
|
||||
Self::collect_child_schemas(schema_arc, root_id, path, to_insert, errors);
|
||||
}
|
||||
|
||||
pub fn collect_child_schemas(
|
||||
schema_arc: &Arc<Schema>,
|
||||
root_id: &str,
|
||||
path: String,
|
||||
to_insert: &mut Vec<(String, Arc<Schema>)>,
|
||||
errors: &mut Vec<crate::drop::Error>,
|
||||
) {
|
||||
if let Some(props) = &schema_arc.obj.properties {
|
||||
for (k, v) in props.iter() {
|
||||
let next_path = format!("{}/{}", path, k);
|
||||
Self::collect_schemas(v, root_id, next_path, to_insert, errors);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(pattern_props) = &schema_arc.obj.pattern_properties {
|
||||
for (k, v) in pattern_props.iter() {
|
||||
let next_path = format!("{}/{}", path, k);
|
||||
Self::collect_schemas(v, root_id, next_path, to_insert, errors);
|
||||
}
|
||||
}
|
||||
|
||||
let mut map_arr = |arr: &Vec<Arc<Schema>>, sub: &str| {
|
||||
for (i, v) in arr.iter().enumerate() {
|
||||
Self::collect_schemas(
|
||||
v,
|
||||
root_id,
|
||||
format!("{}/{}/{}", path, sub, i),
|
||||
to_insert,
|
||||
errors,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(arr) = &schema_arc.obj.prefix_items {
|
||||
map_arr(arr, "prefixItems");
|
||||
}
|
||||
|
||||
if let Some(arr) = &schema_arc.obj.one_of {
|
||||
map_arr(arr, "oneOf");
|
||||
}
|
||||
|
||||
let mut map_opt = |opt: &Option<Arc<Schema>>, pass_path: bool, sub: &str| {
|
||||
if let Some(v) = opt {
|
||||
if pass_path {
|
||||
// Arrays explicitly push their wrapper natively.
|
||||
// 'items' becomes a transparent conduit, bypassing self-promotion and skipping the '/items' suffix.
|
||||
Self::collect_child_schemas(v, root_id, path.clone(), to_insert, errors);
|
||||
} else {
|
||||
Self::collect_child_schemas(v, root_id, format!("{}/{}", path, sub), to_insert, errors);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
map_opt(
|
||||
&schema_arc.obj.additional_properties,
|
||||
false,
|
||||
"additionalProperties",
|
||||
);
|
||||
map_opt(&schema_arc.obj.items, true, "items");
|
||||
map_opt(&schema_arc.obj.not, false, "not");
|
||||
map_opt(&schema_arc.obj.contains, false, "contains");
|
||||
map_opt(&schema_arc.obj.property_names, false, "propertyNames");
|
||||
|
||||
if let Some(cases) = &schema_arc.obj.cases {
|
||||
for (i, c) in cases.iter().enumerate() {
|
||||
if let Some(when) = &c.when {
|
||||
Self::collect_schemas(
|
||||
when,
|
||||
root_id,
|
||||
format!("{}/cases/{}/when", path, i),
|
||||
to_insert,
|
||||
errors,
|
||||
);
|
||||
}
|
||||
if let Some(then) = &c.then {
|
||||
Self::collect_schemas(
|
||||
then,
|
||||
root_id,
|
||||
format!("{}/cases/{}/then", path, i),
|
||||
to_insert,
|
||||
errors,
|
||||
);
|
||||
}
|
||||
if let Some(else_) = &c.else_ {
|
||||
Self::collect_schemas(
|
||||
else_,
|
||||
root_id,
|
||||
format!("{}/cases/{}/else", path, i),
|
||||
to_insert,
|
||||
errors,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for Schema {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
|
||||
10
src/lib.rs
10
src/lib.rs
@ -72,7 +72,7 @@ pub fn jspg_merge(schema_id: &str, data: JsonB) -> JsonB {
|
||||
}
|
||||
|
||||
#[cfg_attr(not(test), pg_extern)]
|
||||
pub fn jspg_query(schema_id: &str, filters: Option<JsonB>) -> JsonB {
|
||||
pub fn jspg_query(schema_id: &str, filter: Option<JsonB>) -> JsonB {
|
||||
let engine_opt = {
|
||||
let lock = GLOBAL_JSPG.read().unwrap();
|
||||
lock.clone()
|
||||
@ -82,7 +82,7 @@ pub fn jspg_query(schema_id: &str, filters: Option<JsonB>) -> JsonB {
|
||||
Some(engine) => {
|
||||
let drop = engine
|
||||
.queryer
|
||||
.query(schema_id, filters.as_ref().map(|f| &f.0));
|
||||
.query(schema_id, filter.as_ref().map(|f| &f.0));
|
||||
JsonB(serde_json::to_value(drop).unwrap())
|
||||
}
|
||||
None => jspg_failure(),
|
||||
@ -109,7 +109,7 @@ pub fn jspg_validate(schema_id: &str, instance: JsonB) -> JsonB {
|
||||
}
|
||||
|
||||
#[cfg_attr(not(test), pg_extern)]
|
||||
pub fn jspg_schemas() -> JsonB {
|
||||
pub fn jspg_database() -> JsonB {
|
||||
let engine_opt = {
|
||||
let lock = GLOBAL_JSPG.read().unwrap();
|
||||
lock.clone()
|
||||
@ -117,9 +117,9 @@ pub fn jspg_schemas() -> JsonB {
|
||||
|
||||
match engine_opt {
|
||||
Some(engine) => {
|
||||
let schemas_json = serde_json::to_value(&engine.database.schemas)
|
||||
let database_json = serde_json::to_value(&engine.database)
|
||||
.unwrap_or(serde_json::Value::Object(serde_json::Map::new()));
|
||||
let drop = crate::drop::Drop::success_with_val(schemas_json);
|
||||
let drop = crate::drop::Drop::success_with_val(database_json);
|
||||
JsonB(serde_json::to_value(drop).unwrap())
|
||||
}
|
||||
None => jspg_failure(),
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
pub mod cache;
|
||||
|
||||
use crate::database::Database;
|
||||
use crate::database::realm::SchemaRealm;
|
||||
use crate::database::r#type::Type;
|
||||
use serde_json::Value;
|
||||
use std::sync::Arc;
|
||||
@ -24,8 +25,8 @@ impl Merger {
|
||||
pub fn merge(&self, schema_id: &str, data: Value) -> crate::drop::Drop {
|
||||
let mut notifications_queue = Vec::new();
|
||||
|
||||
let target_schema = match self.db.schemas.get(schema_id) {
|
||||
Some(s) => Arc::clone(s),
|
||||
let target_schema = match self.db.get_scoped_schema(SchemaRealm::Type, schema_id) {
|
||||
Some(s) => Arc::clone(&s),
|
||||
None => {
|
||||
return crate::drop::Drop::with_errors(vec![crate::drop::Error {
|
||||
code: "MERGE_FAILED".to_string(),
|
||||
@ -144,30 +145,49 @@ impl Merger {
|
||||
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);
|
||||
if let Some(target_schema) =
|
||||
self.db.get_scoped_schema(SchemaRealm::Type, target_id)
|
||||
{
|
||||
schema = target_schema.clone();
|
||||
} else {
|
||||
return Err(format!("Polymorphic mapped target '{}' not found in database registry", target_id));
|
||||
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);
|
||||
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));
|
||||
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));
|
||||
return Err(format!(
|
||||
"Polymorphic discriminator {}='{}' matched no compiled options",
|
||||
disc, v
|
||||
));
|
||||
}
|
||||
} else {
|
||||
return Err(format!("Polymorphic merging failed: missing required discriminator '{}'", disc));
|
||||
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()),
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
use crate::database::Database;
|
||||
use crate::database::realm::SchemaRealm;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct Compiler<'a> {
|
||||
@ -24,13 +25,18 @@ pub struct Node<'a> {
|
||||
impl<'a> Compiler<'a> {
|
||||
/// Compiles a JSON schema into a nested PostgreSQL query returning JSONB
|
||||
pub fn compile(&self, schema_id: &str, filter_keys: &[String]) -> Result<String, String> {
|
||||
let realm = if schema_id.ends_with(".request") || schema_id.ends_with(".response") {
|
||||
SchemaRealm::Punc
|
||||
} else {
|
||||
SchemaRealm::Type
|
||||
};
|
||||
|
||||
let schema = self
|
||||
.db
|
||||
.schemas
|
||||
.get(schema_id)
|
||||
.get_scoped_schema(realm, schema_id)
|
||||
.ok_or_else(|| format!("Schema not found: {}", schema_id))?;
|
||||
|
||||
let target_schema = std::sync::Arc::clone(schema);
|
||||
let target_schema = schema;
|
||||
|
||||
let mut compiler = Compiler {
|
||||
db: &self.db,
|
||||
@ -151,9 +157,9 @@ impl<'a> Compiler<'a> {
|
||||
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) {
|
||||
if let Some(target_schema) = self.db.get_scoped_schema(SchemaRealm::Type, t) {
|
||||
let mut ref_node = node.clone();
|
||||
ref_node.schema = Arc::clone(target_schema);
|
||||
ref_node.schema = target_schema.clone();
|
||||
ref_node.schema_id = Some(t.clone());
|
||||
return self.compile_node(ref_node);
|
||||
}
|
||||
@ -306,9 +312,9 @@ impl<'a> Compiler<'a> {
|
||||
|
||||
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) {
|
||||
if let Some(target_schema) = self.db.get_scoped_schema(SchemaRealm::Type, target_id) {
|
||||
let mut child_node = node.clone();
|
||||
child_node.schema = Arc::clone(target_schema);
|
||||
child_node.schema = target_schema.clone();
|
||||
child_node.schema_id = Some(target_id.clone());
|
||||
child_node.is_polymorphic_branch = true;
|
||||
|
||||
@ -717,8 +723,8 @@ impl<'a> Compiler<'a> {
|
||||
let param_index = i + 1;
|
||||
let p_val = format!("${}#>>'{{}}'", param_index);
|
||||
|
||||
if op == "$in" || op == "$nin" {
|
||||
let sql_op = if op == "$in" { "IN" } else { "NOT IN" };
|
||||
if op == "$of" || op == "$nof" {
|
||||
let sql_op = if op == "$of" { "IN" } else { "NOT IN" };
|
||||
let subquery = format!(
|
||||
"(SELECT value{} FROM jsonb_array_elements_text(({})::jsonb))",
|
||||
cast, p_val
|
||||
|
||||
@ -21,9 +21,9 @@ impl Queryer {
|
||||
pub fn query(
|
||||
&self,
|
||||
schema_id: &str,
|
||||
filters: Option<&serde_json::Value>,
|
||||
filter: Option<&serde_json::Value>,
|
||||
) -> crate::drop::Drop {
|
||||
let filters_map = filters.and_then(|f| f.as_object());
|
||||
let filters_map = filter.and_then(|f| f.as_object());
|
||||
|
||||
// 1. Process filters into structured $op keys and linear values
|
||||
let (filter_keys, args) = match self.parse_filter_entries(filters_map) {
|
||||
@ -35,7 +35,7 @@ impl Queryer {
|
||||
details: crate::drop::ErrorDetails {
|
||||
path: None, // filters apply to the root query
|
||||
cause: Some(msg),
|
||||
context: filters.cloned(),
|
||||
context: filter.cloned(),
|
||||
schema: Some(schema_id.to_string()),
|
||||
},
|
||||
}]);
|
||||
|
||||
@ -533,6 +533,12 @@ fn test_unique_items_6_1() {
|
||||
crate::tests::runner::run_test_case(&path, 6, 1).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_filter_0_0() {
|
||||
let path = format!("{}/fixtures/filter.json", env!("CARGO_MANIFEST_DIR"));
|
||||
crate::tests::runner::run_test_case(&path, 0, 0).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_min_items_0_0() {
|
||||
let path = format!("{}/fixtures/minItems.json", env!("CARGO_MANIFEST_DIR"));
|
||||
@ -1457,6 +1463,12 @@ fn test_queryer_0_13() {
|
||||
crate::tests::runner::run_test_case(&path, 0, 13).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_queryer_0_14() {
|
||||
let path = format!("{}/fixtures/queryer.json", env!("CARGO_MANIFEST_DIR"));
|
||||
crate::tests::runner::run_test_case(&path, 0, 14).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_polymorphism_0_0() {
|
||||
let path = format!("{}/fixtures/polymorphism.json", env!("CARGO_MANIFEST_DIR"));
|
||||
@ -1559,6 +1571,24 @@ fn test_polymorphism_4_1() {
|
||||
crate::tests::runner::run_test_case(&path, 4, 1).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_polymorphism_5_0() {
|
||||
let path = format!("{}/fixtures/polymorphism.json", env!("CARGO_MANIFEST_DIR"));
|
||||
crate::tests::runner::run_test_case(&path, 5, 0).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_polymorphism_5_1() {
|
||||
let path = format!("{}/fixtures/polymorphism.json", env!("CARGO_MANIFEST_DIR"));
|
||||
crate::tests::runner::run_test_case(&path, 5, 1).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_polymorphism_5_2() {
|
||||
let path = format!("{}/fixtures/polymorphism.json", env!("CARGO_MANIFEST_DIR"));
|
||||
crate::tests::runner::run_test_case(&path, 5, 2).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_not_0_0() {
|
||||
let path = format!("{}/fixtures/not.json", env!("CARGO_MANIFEST_DIR"));
|
||||
@ -8104,3 +8134,9 @@ fn test_merger_0_13() {
|
||||
let path = format!("{}/fixtures/merger.json", env!("CARGO_MANIFEST_DIR"));
|
||||
crate::tests::runner::run_test_case(&path, 0, 13).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_merger_0_14() {
|
||||
let path = format!("{}/fixtures/merger.json", env!("CARGO_MANIFEST_DIR"));
|
||||
crate::tests::runner::run_test_case(&path, 0, 14).unwrap();
|
||||
}
|
||||
|
||||
156
src/tests/mod.rs
156
src/tests/mod.rs
@ -81,38 +81,144 @@ fn test_library_api() {
|
||||
})
|
||||
);
|
||||
|
||||
// 3. Validate jspg_schemas
|
||||
let schemas_drop = jspg_schemas();
|
||||
// 3. Validate jspg_database mapping natively!
|
||||
let db_drop = jspg_database();
|
||||
assert_eq!(
|
||||
schemas_drop.0,
|
||||
db_drop.0,
|
||||
json!({
|
||||
"type": "drop",
|
||||
"response": {
|
||||
"source_schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": { "type": "string" },
|
||||
"name": { "type": "string" },
|
||||
"target": {
|
||||
"type": "target_schema",
|
||||
"compiledProperties": ["value"]
|
||||
}
|
||||
},
|
||||
"required": ["name"],
|
||||
"compiledProperties": ["name", "target", "type"],
|
||||
"compiledEdges": {
|
||||
"target": {
|
||||
"constraint": "fk_test_target",
|
||||
"forward": true
|
||||
}
|
||||
"enums": {},
|
||||
"puncs": {},
|
||||
"relations": {
|
||||
"fk_test_target": {
|
||||
"id": "11111111-1111-1111-1111-111111111111",
|
||||
"type": "relation",
|
||||
"constraint": "fk_test_target",
|
||||
"destination_columns": ["id"],
|
||||
"destination_type": "target_schema",
|
||||
"prefix": "target",
|
||||
"source_columns": ["target_id"],
|
||||
"source_type": "source_schema"
|
||||
}
|
||||
},
|
||||
"target_schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"value": { "type": "number" }
|
||||
"types": {
|
||||
"source_schema": {
|
||||
"default_fields": [],
|
||||
"field_types": null,
|
||||
"fields": [],
|
||||
"grouped_fields": null,
|
||||
"hierarchy": ["source_schema", "entity"],
|
||||
"historical": false,
|
||||
"id": "",
|
||||
"longevity": null,
|
||||
"lookup_fields": [],
|
||||
"module": "",
|
||||
"name": "source_schema",
|
||||
"notify": false,
|
||||
"null_fields": [],
|
||||
"ownable": false,
|
||||
"relationship": false,
|
||||
"schemas": {
|
||||
"source_schema": {
|
||||
"compiledEdges": {
|
||||
"target": {
|
||||
"constraint": "fk_test_target",
|
||||
"forward": true
|
||||
}
|
||||
},
|
||||
"compiledPropertyNames": ["name", "target", "type"],
|
||||
"properties": {
|
||||
"name": { "type": "string" },
|
||||
"target": {
|
||||
"compiledPropertyNames": ["value"],
|
||||
"type": "target_schema"
|
||||
},
|
||||
"type": { "type": "string" }
|
||||
},
|
||||
"required": ["name"],
|
||||
"type": "object"
|
||||
},
|
||||
"source_schema.filter": {
|
||||
"compiledPropertyNames": ["$and", "$or", "name", "target", "type"],
|
||||
"properties": {
|
||||
"$and": {
|
||||
"type": ["array", "null"],
|
||||
"items": {
|
||||
"compiledPropertyNames": ["$and", "$or", "name", "target", "type"],
|
||||
"type": "source_schema.filter"
|
||||
}
|
||||
},
|
||||
"$or": {
|
||||
"type": ["array", "null"],
|
||||
"items": {
|
||||
"compiledPropertyNames": ["$and", "$or", "name", "target", "type"],
|
||||
"type": "source_schema.filter"
|
||||
}
|
||||
},
|
||||
"name": { "type": ["string.condition", "null"] },
|
||||
"target": { "type": ["target_schema.filter", "null"] },
|
||||
"type": { "type": ["string.condition", "null"] }
|
||||
},
|
||||
"type": "filter"
|
||||
}
|
||||
},
|
||||
"sensitive": false,
|
||||
"source": "",
|
||||
"type": "",
|
||||
"variations": ["source_schema"]
|
||||
},
|
||||
"compiledProperties": ["value"]
|
||||
"target_schema": {
|
||||
"default_fields": [],
|
||||
"field_types": null,
|
||||
"fields": [],
|
||||
"grouped_fields": null,
|
||||
"hierarchy": ["target_schema", "entity"],
|
||||
"historical": false,
|
||||
"id": "",
|
||||
"longevity": null,
|
||||
"lookup_fields": [],
|
||||
"module": "",
|
||||
"name": "target_schema",
|
||||
"notify": false,
|
||||
"null_fields": [],
|
||||
"ownable": false,
|
||||
"relationship": false,
|
||||
"schemas": {
|
||||
"target_schema": {
|
||||
"compiledPropertyNames": ["value"],
|
||||
"properties": {
|
||||
"value": { "type": "number" }
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"target_schema.filter": {
|
||||
"compiledPropertyNames": ["$and", "$or", "value"],
|
||||
"properties": {
|
||||
"$and": {
|
||||
"type": ["array", "null"],
|
||||
"items": {
|
||||
"compiledPropertyNames": ["$and", "$or", "value"],
|
||||
"type": "target_schema.filter"
|
||||
}
|
||||
},
|
||||
"$or": {
|
||||
"type": ["array", "null"],
|
||||
"items": {
|
||||
"compiledPropertyNames": ["$and", "$or", "value"],
|
||||
"type": "target_schema.filter"
|
||||
}
|
||||
},
|
||||
"value": { "type": ["number.condition", "null"] }
|
||||
},
|
||||
"type": "filter"
|
||||
}
|
||||
},
|
||||
"sensitive": false,
|
||||
"source": "",
|
||||
"type": "",
|
||||
"variations": ["target_schema"]
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@ -17,7 +17,7 @@ pub struct Case {
|
||||
|
||||
// For Query
|
||||
#[serde(default)]
|
||||
pub filters: Option<serde_json::Value>,
|
||||
pub filter: Option<serde_json::Value>,
|
||||
|
||||
// For Merge & Validate
|
||||
#[serde(default)]
|
||||
@ -64,12 +64,6 @@ impl Case {
|
||||
let validator = Validator::new(db);
|
||||
|
||||
let schema_id = &self.schema_id;
|
||||
if !validator.db.schemas.contains_key(schema_id) {
|
||||
return Err(format!(
|
||||
"Missing Schema: Cannot find schema ID '{}'",
|
||||
schema_id
|
||||
));
|
||||
}
|
||||
|
||||
let test_data = self.data.clone().unwrap_or(Value::Null);
|
||||
let result = validator.validate(schema_id, &test_data);
|
||||
@ -122,7 +116,7 @@ impl Case {
|
||||
use crate::queryer::Queryer;
|
||||
let queryer = Queryer::new(db.clone());
|
||||
|
||||
let result = queryer.query(&self.schema_id, self.filters.as_ref());
|
||||
let result = queryer.query(&self.schema_id, self.filter.as_ref());
|
||||
|
||||
let return_val = if let Some(expect) = &self.expect {
|
||||
if let Err(e) = expect.assert_drop(&result) {
|
||||
|
||||
@ -20,5 +20,5 @@ pub struct Expect {
|
||||
#[serde(default)]
|
||||
pub sql: Option<Vec<SqlExpectation>>,
|
||||
#[serde(default)]
|
||||
pub schemas: Option<Vec<String>>,
|
||||
pub schemas: Option<std::collections::HashMap<String, serde_json::Value>>,
|
||||
}
|
||||
|
||||
@ -3,13 +3,22 @@ use std::sync::Arc;
|
||||
|
||||
impl Expect {
|
||||
pub fn assert_schemas(&self, db: &Arc<crate::database::Database>) -> Result<(), String> {
|
||||
if let Some(expected_schemas) = &self.schemas {
|
||||
if let Some(expected_map) = &self.schemas {
|
||||
// Collect actual schemas and sort
|
||||
let mut actual: Vec<String> = db.schemas.keys().cloned().collect();
|
||||
let mut actual: Vec<String> = Vec::new();
|
||||
for type_def in db.types.values() {
|
||||
actual.extend(type_def.schemas.keys().cloned());
|
||||
}
|
||||
for punc_def in db.puncs.values() {
|
||||
actual.extend(punc_def.schemas.keys().cloned());
|
||||
}
|
||||
for enum_def in db.enums.values() {
|
||||
actual.extend(enum_def.schemas.keys().cloned());
|
||||
}
|
||||
actual.sort();
|
||||
|
||||
// Collect expected schemas and sort
|
||||
let mut expected: Vec<String> = expected_schemas.clone();
|
||||
let mut expected: Vec<String> = expected_map.keys().cloned().collect();
|
||||
expected.sort();
|
||||
|
||||
if actual != expected {
|
||||
@ -21,6 +30,28 @@ impl Expect {
|
||||
actual
|
||||
));
|
||||
}
|
||||
|
||||
for (key, expected_val) in expected_map {
|
||||
if expected_val.is_object() && expected_val.as_object().unwrap().is_empty() {
|
||||
continue; // A `{}` means we just wanted to test it was collected/promoted, skip deep match
|
||||
}
|
||||
let schema_realm = if key.ends_with(".request") || key.ends_with(".response") {
|
||||
crate::database::realm::SchemaRealm::Punc
|
||||
} else {
|
||||
crate::database::realm::SchemaRealm::Type
|
||||
};
|
||||
let actual_ast = db.get_scoped_schema(schema_realm, key).unwrap();
|
||||
let actual_val = serde_json::to_value(actual_ast).unwrap();
|
||||
|
||||
if actual_val != *expected_val {
|
||||
return Err(format!(
|
||||
"Detailed Schema Match Failure for '{}'!\n\nExpected:\n{}\n\nActual:\n{}",
|
||||
key,
|
||||
serde_json::to_string_pretty(expected_val).unwrap(),
|
||||
serde_json::to_string_pretty(&actual_val).unwrap()
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -10,6 +10,7 @@ pub use error::ValidationError;
|
||||
pub use result::ValidationResult;
|
||||
|
||||
use crate::database::Database;
|
||||
use crate::database::realm::SchemaRealm;
|
||||
use crate::validator::rules::util::is_integer;
|
||||
use serde_json::Value;
|
||||
use std::sync::Arc;
|
||||
@ -23,10 +24,6 @@ impl Validator {
|
||||
Self { db }
|
||||
}
|
||||
|
||||
pub fn get_schema_ids(&self) -> Vec<String> {
|
||||
self.db.schemas.keys().cloned().collect()
|
||||
}
|
||||
|
||||
pub fn check_type(t: &str, val: &Value) -> bool {
|
||||
if let Value::String(s) = val
|
||||
&& s.is_empty()
|
||||
@ -46,11 +43,17 @@ impl Validator {
|
||||
}
|
||||
|
||||
pub fn validate(&self, schema_id: &str, instance: &Value) -> crate::drop::Drop {
|
||||
if let Some(schema) = self.db.schemas.get(schema_id) {
|
||||
let schema_opt = if schema_id.ends_with(".request") || schema_id.ends_with(".response") {
|
||||
self.db.get_scoped_schema(SchemaRealm::Punc, schema_id)
|
||||
} else {
|
||||
self.db.get_scoped_schema(SchemaRealm::Type, schema_id)
|
||||
};
|
||||
|
||||
if let Some(schema) = schema_opt {
|
||||
let ctx = ValidationContext::new(
|
||||
&self.db,
|
||||
schema,
|
||||
schema,
|
||||
&schema,
|
||||
&schema,
|
||||
instance,
|
||||
HashSet::new(),
|
||||
false,
|
||||
|
||||
@ -24,9 +24,6 @@ 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(),
|
||||
|
||||
@ -23,10 +23,13 @@ impl<'a> ValidationContext<'a> {
|
||||
// Entity implicit type validation
|
||||
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);
|
||||
|
||||
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_def) = self.db.types.get(expected_type).filter(|t| !t.variations.is_empty()) {
|
||||
if let Some(type_val) = obj.get("type") {
|
||||
if let Some(type_str) = type_val.as_str() {
|
||||
if type_def.variations.contains(type_str) {
|
||||
@ -47,21 +50,33 @@ impl<'a> ValidationContext<'a> {
|
||||
// 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),
|
||||
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());
|
||||
let requires_kind = self
|
||||
.schema
|
||||
.compiled_properties
|
||||
.get()
|
||||
.map_or(false, |p| p.contains_key("kind"));
|
||||
|
||||
if requires_kind {
|
||||
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 {
|
||||
@ -69,20 +84,20 @@ impl<'a> ValidationContext<'a> {
|
||||
// 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(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());
|
||||
}
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -162,7 +177,9 @@ impl<'a> ValidationContext<'a> {
|
||||
if let Some(child_instance) = obj.get(key) {
|
||||
let new_path = self.join_path(key);
|
||||
let is_ref = match &sub_schema.type_ {
|
||||
Some(crate::database::object::SchemaTypeOrArray::Single(t)) => !crate::database::object::is_primitive_type(t),
|
||||
Some(crate::database::object::SchemaTypeOrArray::Single(t)) => {
|
||||
!crate::database::object::is_primitive_type(t)
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
let next_extensible = if is_ref { false } else { self.extensible };
|
||||
@ -177,8 +194,6 @@ impl<'a> ValidationContext<'a> {
|
||||
);
|
||||
let item_res = derived.validate()?;
|
||||
|
||||
|
||||
|
||||
result.merge(item_res);
|
||||
result.evaluated_keys.insert(key.to_string());
|
||||
}
|
||||
@ -191,7 +206,9 @@ impl<'a> ValidationContext<'a> {
|
||||
if compiled_re.0.is_match(key) {
|
||||
let new_path = self.join_path(key);
|
||||
let is_ref = match &sub_schema.type_ {
|
||||
Some(crate::database::object::SchemaTypeOrArray::Single(t)) => !crate::database::object::is_primitive_type(t),
|
||||
Some(crate::database::object::SchemaTypeOrArray::Single(t)) => {
|
||||
!crate::database::object::is_primitive_type(t)
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
let next_extensible = if is_ref { false } else { self.extensible };
|
||||
@ -220,7 +237,8 @@ impl<'a> ValidationContext<'a> {
|
||||
{
|
||||
locally_matched = true;
|
||||
}
|
||||
if !locally_matched && let Some(compiled_pp) = self.schema.compiled_pattern_properties.get()
|
||||
if !locally_matched
|
||||
&& let Some(compiled_pp) = self.schema.compiled_pattern_properties.get()
|
||||
{
|
||||
for (compiled_re, _) in compiled_pp {
|
||||
if compiled_re.0.is_match(key) {
|
||||
@ -233,7 +251,9 @@ impl<'a> ValidationContext<'a> {
|
||||
if !locally_matched {
|
||||
let new_path = self.join_path(key);
|
||||
let is_ref = match &additional_schema.type_ {
|
||||
Some(crate::database::object::SchemaTypeOrArray::Single(t)) => !crate::database::object::is_primitive_type(t),
|
||||
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 };
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
use crate::validator::context::ValidationContext;
|
||||
use crate::validator::error::ValidationError;
|
||||
use crate::validator::result::ValidationResult;
|
||||
use crate::database::realm::SchemaRealm;
|
||||
|
||||
impl<'a> ValidationContext<'a> {
|
||||
pub(crate) fn validate_family(
|
||||
@ -21,7 +22,7 @@ impl<'a> ValidationContext<'a> {
|
||||
if conflicts {
|
||||
result.errors.push(ValidationError {
|
||||
code: "INVALID_SCHEMA".to_string(),
|
||||
message: "$family must be used exclusively without other constraints".to_string(),
|
||||
message: "family must be used exclusively without other constraints".to_string(),
|
||||
path: self.path.to_string(),
|
||||
});
|
||||
return Ok(false);
|
||||
@ -99,8 +100,8 @@ impl<'a> ValidationContext<'a> {
|
||||
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);
|
||||
if let Some(target_schema) = self.db.get_scoped_schema(SchemaRealm::Type, target_id) {
|
||||
let derived = self.derive_for_schema(&target_schema, false);
|
||||
let sub_res = derived.validate()?;
|
||||
let is_valid = sub_res.is_valid();
|
||||
result.merge(sub_res);
|
||||
@ -221,21 +222,21 @@ impl<'a> ValidationContext<'a> {
|
||||
}
|
||||
|
||||
for t in custom_types {
|
||||
if let Some(global_schema) = self.db.schemas.get(&t) {
|
||||
if let Some(global_schema) = self.db.get_scoped_schema(SchemaRealm::Type, &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()));
|
||||
}
|
||||
|
||||
let mut shadow = self.derive(
|
||||
global_schema,
|
||||
&global_schema,
|
||||
self.instance,
|
||||
&self.path,
|
||||
new_overrides,
|
||||
self.extensible,
|
||||
true, // Reporter mode
|
||||
);
|
||||
shadow.root = global_schema;
|
||||
shadow.root = &global_schema;
|
||||
result.merge(shadow.validate()?);
|
||||
} else {
|
||||
result.errors.push(ValidationError {
|
||||
|
||||
81
test_failures.log
Normal file
81
test_failures.log
Normal file
@ -0,0 +1,81 @@
|
||||
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.43s
|
||||
Running unittests src/lib.rs (target/debug/deps/jspg-d3f18ff3a7e2b386)
|
||||
|
||||
running 11 tests
|
||||
test tests::test_minimum_0_2 ... ok
|
||||
test tests::test_minimum_1_4 ... ok
|
||||
test tests::test_minimum_1_0 ... FAILED
|
||||
test tests::test_minimum_1_1 ... FAILED
|
||||
test tests::test_minimum_0_3 ... FAILED
|
||||
test tests::test_minimum_1_5 ... ok
|
||||
test tests::test_minimum_1_3 ... FAILED
|
||||
test tests::test_minimum_0_0 ... FAILED
|
||||
test tests::test_minimum_0_1 ... FAILED
|
||||
test tests::test_minimum_1_2 ... FAILED
|
||||
test tests::test_minimum_1_6 ... FAILED
|
||||
|
||||
failures:
|
||||
|
||||
---- tests::test_minimum_1_0 stdout ----
|
||||
TEST VALIDATE ERROR FOR 'negative above the minimum is valid': Expected success: true, Got: false. Actual Errors: [Error { code: "SCHEMA_NOT_FOUND", message: "Schema minimum_1_0 not found", details: ErrorDetails { path: Some("/"), cause: None, context: None, schema: None } }]
|
||||
|
||||
thread 'tests::test_minimum_1_0' (110318318) panicked at src/tests/fixtures.rs:3503:54:
|
||||
called `Result::unwrap()` on an `Err` value: "[minimum validation with signed integer] Validate Test 'negative above the minimum is valid' failed. Error: Expected success: true, Got: false. Actual Errors: [Error { code: \"SCHEMA_NOT_FOUND\", message: \"Schema minimum_1_0 not found\", details: ErrorDetails { path: Some(\"/\"), cause: None, context: None, schema: None } }]"
|
||||
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
|
||||
|
||||
---- tests::test_minimum_1_1 stdout ----
|
||||
TEST VALIDATE ERROR FOR 'positive above the minimum is valid': Expected success: true, Got: false. Actual Errors: [Error { code: "SCHEMA_NOT_FOUND", message: "Schema minimum_1_0 not found", details: ErrorDetails { path: Some("/"), cause: None, context: None, schema: None } }]
|
||||
|
||||
thread 'tests::test_minimum_1_1' (110318319) panicked at src/tests/fixtures.rs:3509:54:
|
||||
called `Result::unwrap()` on an `Err` value: "[minimum validation with signed integer] Validate Test 'positive above the minimum is valid' failed. Error: Expected success: true, Got: false. Actual Errors: [Error { code: \"SCHEMA_NOT_FOUND\", message: \"Schema minimum_1_0 not found\", details: ErrorDetails { path: Some(\"/\"), cause: None, context: None, schema: None } }]"
|
||||
|
||||
---- tests::test_minimum_0_3 stdout ----
|
||||
TEST VALIDATE ERROR FOR 'ignores non-numbers': Expected success: true, Got: false. Actual Errors: [Error { code: "SCHEMA_NOT_FOUND", message: "Schema minimum_0_0 not found", details: ErrorDetails { path: Some("/"), cause: None, context: None, schema: None } }]
|
||||
|
||||
thread 'tests::test_minimum_0_3' (110318317) panicked at src/tests/fixtures.rs:3497:54:
|
||||
called `Result::unwrap()` on an `Err` value: "[minimum validation] Validate Test 'ignores non-numbers' failed. Error: Expected success: true, Got: false. Actual Errors: [Error { code: \"SCHEMA_NOT_FOUND\", message: \"Schema minimum_0_0 not found\", details: ErrorDetails { path: Some(\"/\"), cause: None, context: None, schema: None } }]"
|
||||
|
||||
---- tests::test_minimum_1_3 stdout ----
|
||||
TEST VALIDATE ERROR FOR 'boundary point with float is valid': Expected success: true, Got: false. Actual Errors: [Error { code: "SCHEMA_NOT_FOUND", message: "Schema minimum_1_0 not found", details: ErrorDetails { path: Some("/"), cause: None, context: None, schema: None } }]
|
||||
|
||||
thread 'tests::test_minimum_1_3' (110318321) panicked at src/tests/fixtures.rs:3521:54:
|
||||
called `Result::unwrap()` on an `Err` value: "[minimum validation with signed integer] Validate Test 'boundary point with float is valid' failed. Error: Expected success: true, Got: false. Actual Errors: [Error { code: \"SCHEMA_NOT_FOUND\", message: \"Schema minimum_1_0 not found\", details: ErrorDetails { path: Some(\"/\"), cause: None, context: None, schema: None } }]"
|
||||
|
||||
---- tests::test_minimum_0_0 stdout ----
|
||||
TEST VALIDATE ERROR FOR 'above the minimum is valid': Expected success: true, Got: false. Actual Errors: [Error { code: "SCHEMA_NOT_FOUND", message: "Schema minimum_0_0 not found", details: ErrorDetails { path: Some("/"), cause: None, context: None, schema: None } }]
|
||||
|
||||
thread 'tests::test_minimum_0_0' (110318314) panicked at src/tests/fixtures.rs:3479:54:
|
||||
called `Result::unwrap()` on an `Err` value: "[minimum validation] Validate Test 'above the minimum is valid' failed. Error: Expected success: true, Got: false. Actual Errors: [Error { code: \"SCHEMA_NOT_FOUND\", message: \"Schema minimum_0_0 not found\", details: ErrorDetails { path: Some(\"/\"), cause: None, context: None, schema: None } }]"
|
||||
|
||||
---- tests::test_minimum_0_1 stdout ----
|
||||
TEST VALIDATE ERROR FOR 'boundary point is valid': Expected success: true, Got: false. Actual Errors: [Error { code: "SCHEMA_NOT_FOUND", message: "Schema minimum_0_0 not found", details: ErrorDetails { path: Some("/"), cause: None, context: None, schema: None } }]
|
||||
|
||||
thread 'tests::test_minimum_0_1' (110318315) panicked at src/tests/fixtures.rs:3485:54:
|
||||
called `Result::unwrap()` on an `Err` value: "[minimum validation] Validate Test 'boundary point is valid' failed. Error: Expected success: true, Got: false. Actual Errors: [Error { code: \"SCHEMA_NOT_FOUND\", message: \"Schema minimum_0_0 not found\", details: ErrorDetails { path: Some(\"/\"), cause: None, context: None, schema: None } }]"
|
||||
|
||||
---- tests::test_minimum_1_2 stdout ----
|
||||
TEST VALIDATE ERROR FOR 'boundary point is valid': Expected success: true, Got: false. Actual Errors: [Error { code: "SCHEMA_NOT_FOUND", message: "Schema minimum_1_0 not found", details: ErrorDetails { path: Some("/"), cause: None, context: None, schema: None } }]
|
||||
|
||||
thread 'tests::test_minimum_1_2' (110318320) panicked at src/tests/fixtures.rs:3515:54:
|
||||
called `Result::unwrap()` on an `Err` value: "[minimum validation with signed integer] Validate Test 'boundary point is valid' failed. Error: Expected success: true, Got: false. Actual Errors: [Error { code: \"SCHEMA_NOT_FOUND\", message: \"Schema minimum_1_0 not found\", details: ErrorDetails { path: Some(\"/\"), cause: None, context: None, schema: None } }]"
|
||||
|
||||
---- tests::test_minimum_1_6 stdout ----
|
||||
TEST VALIDATE ERROR FOR 'ignores non-numbers': Expected success: true, Got: false. Actual Errors: [Error { code: "SCHEMA_NOT_FOUND", message: "Schema minimum_1_0 not found", details: ErrorDetails { path: Some("/"), cause: None, context: None, schema: None } }]
|
||||
|
||||
thread 'tests::test_minimum_1_6' (110318324) panicked at src/tests/fixtures.rs:3539:54:
|
||||
called `Result::unwrap()` on an `Err` value: "[minimum validation with signed integer] Validate Test 'ignores non-numbers' failed. Error: Expected success: true, Got: false. Actual Errors: [Error { code: \"SCHEMA_NOT_FOUND\", message: \"Schema minimum_1_0 not found\", details: ErrorDetails { path: Some(\"/\"), cause: None, context: None, schema: None } }]"
|
||||
|
||||
|
||||
failures:
|
||||
tests::test_minimum_0_0
|
||||
tests::test_minimum_0_1
|
||||
tests::test_minimum_0_3
|
||||
tests::test_minimum_1_0
|
||||
tests::test_minimum_1_1
|
||||
tests::test_minimum_1_2
|
||||
tests::test_minimum_1_3
|
||||
tests::test_minimum_1_6
|
||||
|
||||
test result: FAILED. 3 passed; 8 failed; 0 ignored; 0 measured; 1347 filtered out; finished in 0.00s
|
||||
|
||||
error: test failed, to rerun pass `--lib`
|
||||
23
test_merge.log
Normal file
23
test_merge.log
Normal file
@ -0,0 +1,23 @@
|
||||
Compiling jspg v0.1.0 (/Users/awgneo/Repositories/thoughtpatterns/cellular/jspg)
|
||||
Finished `test` profile [unoptimized + debuginfo] target(s) in 7.59s
|
||||
Running unittests src/lib.rs (target/debug/deps/jspg-d3f18ff3a7e2b386)
|
||||
|
||||
running 1 test
|
||||
test tests::test_merge_0_0 ... FAILED
|
||||
|
||||
failures:
|
||||
|
||||
---- tests::test_merge_0_0 stdout ----
|
||||
TEST VALIDATE ERROR FOR 'valid with both properties': Expected success: true, Got: false. Actual Errors: [Error { code: "MISSING_TYPE", message: "Schema mechanically requires type discrimination 'base_0'", details: ErrorDetails { path: Some(""), cause: None, context: None, schema: None } }]
|
||||
|
||||
thread 'tests::test_merge_0_0' (110369726) panicked at src/tests/fixtures.rs:4307:54:
|
||||
called `Result::unwrap()` on an `Err` value: "[merging: properties accumulate] Validate Test 'valid with both properties' failed. Error: Expected success: true, Got: false. Actual Errors: [Error { code: \"MISSING_TYPE\", message: \"Schema mechanically requires type discrimination 'base_0'\", details: ErrorDetails { path: Some(\"\"), cause: None, context: None, schema: None } }]"
|
||||
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
|
||||
|
||||
|
||||
failures:
|
||||
tests::test_merge_0_0
|
||||
|
||||
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 1357 filtered out; finished in 0.00s
|
||||
|
||||
error: test failed, to rerun pass `--lib`
|
||||
24
wipe_test.py
24
wipe_test.py
@ -1,24 +0,0 @@
|
||||
import json
|
||||
|
||||
def load_json(path):
|
||||
with open(path, 'r') as f:
|
||||
return json.load(f)
|
||||
|
||||
def save_json(path, data):
|
||||
with open(path, 'w') as f:
|
||||
json.dump(data, f, indent=4)
|
||||
|
||||
def fix_merger():
|
||||
data = load_json('fixtures/merger.json')
|
||||
last_test = data[0]['tests'][-1]
|
||||
last_test["expect"]["sql"] = []
|
||||
save_json('fixtures/merger.json', data)
|
||||
|
||||
def fix_queryer():
|
||||
data = load_json('fixtures/queryer.json')
|
||||
last_test = data[0]['tests'][-1]
|
||||
last_test["expect"]["sql"] = []
|
||||
save_json('fixtures/queryer.json', data)
|
||||
|
||||
fix_merger()
|
||||
fix_queryer()
|
||||
Reference in New Issue
Block a user