Compare commits
43 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f7163e2689 | |||
| 091007006d | |||
| 3d66a7fc3c | |||
| e1314496dd | |||
| 70a27b430d | |||
| e078b8a74b | |||
| c2c0e62c2d | |||
| ebb97b3509 | |||
| 5d18847f32 | |||
| 4a33e29628 | |||
| d8fc286e94 | |||
| 507dc6d780 | |||
| e340039a30 | |||
| 08768e3d42 | |||
| 6c9e6575ce | |||
| 5d11c4c92c | |||
| 25239d635b | |||
| 3bec6a6102 | |||
| 6444b300b3 | |||
| c529c8b8ea | |||
| 2f15ae3d41 | |||
| f8528aa85e | |||
| b6f383e700 | |||
| db5183930d | |||
| 6de75ba525 | |||
| 6632570712 | |||
| d4347072f2 | |||
| 290464adc1 | |||
| d6deaa0b0f | |||
| 6a275e1d90 | |||
| 797a0a5460 | |||
| cfcb259eab | |||
| f666e608da | |||
| 732034bbc7 | |||
| 5b183a1aba | |||
| be1367930d | |||
| 44ba3e0e18 | |||
| e692fc52ee | |||
| 641f7b5d92 | |||
| c007d7d479 | |||
| 2c74d0a1a6 | |||
| 44be75f5d4 | |||
| 1c08a8f2b8 |
@ -8,9 +8,11 @@ Read over this entire workflow and commit to every section of work in a task lis
|
||||
|
||||
Please analyze the files and directories and do not use cat, find, or the terminal to discover or read in any of these files. Analyze every file mentioned. If a directory is mentioned or a /*, please analyze the directory, every single file at its root, and recursively analyze every subdirectory and every single file in every subdirectory to capture not just critical files, but the entirety of what is requested. I state again, DO NOT just review a cherry picking of files in any folder or wildcard specified. Review 100% of all files discovered recursively!
|
||||
|
||||
Section 1: Documentation
|
||||
Section 1: Various Documentation
|
||||
|
||||
- GEMINI.md at the root
|
||||
- GEMINI.md at the root (JSPG doc)
|
||||
- api/punc/GEMINI.md
|
||||
- api/punc/generator/GEMINI.md
|
||||
|
||||
Section 2: Flow file for cmd interface
|
||||
|
||||
@ -22,7 +24,14 @@ Section 3: Source
|
||||
|
||||
Section 4: Test Fixtures
|
||||
|
||||
- Just review some of the *.json files in tests/fixtures/*
|
||||
Just review some of the *.json files in fixtures/ at the root
|
||||
|
||||
- fixtures/allOf.json
|
||||
- fixtures/ref.json
|
||||
- fixtures/contains.json
|
||||
- fixtures/queryer.json
|
||||
- fixtures/merger.json
|
||||
- fixtures/merge.json
|
||||
|
||||
Section 5: Build
|
||||
|
||||
@ -36,14 +45,19 @@ Section 7: Some PUNC Syntax
|
||||
|
||||
Now, review some punc type and enum source in the api project with api/ these files:
|
||||
|
||||
- punc/sql/tables.sql
|
||||
- punc/sql/domains.sql
|
||||
- punc/sql/indexes.sql
|
||||
- punc/sql/functions/entity.sql
|
||||
- punc/sql/functions/puncs.sql
|
||||
- punc/sql/puncs/entity.sql
|
||||
- punc/sql/puncs/persons.sql
|
||||
- punc/sql/puncs/puncs.sql
|
||||
- punc/sql/puncs/job.sql
|
||||
- api/punc/sql/tables.sql
|
||||
- api/punc/sql/domains.sql
|
||||
- api/punc/sql/indexes.sql
|
||||
- api/punc/sql/functions/entity.sql
|
||||
- api/punc/sql/functions/puncs.sql
|
||||
- api/punc/sql/puncs/entity.sql
|
||||
- api/punc/sql/puncs/persons.sql
|
||||
- api/punc/sql/puncs/puncs.sql
|
||||
- api/punc/sql/puncs/job.sql
|
||||
- api/contact/sql/tables.sql
|
||||
- api/contact/sql/domains.sql
|
||||
- api/contact/sql/indexes.sql
|
||||
- api/contact/sql/domains.sql
|
||||
- api/contact/sql/puncs/contacts.sql
|
||||
|
||||
Now you are ready to help me work on this extension.
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,4 +1,5 @@
|
||||
/target
|
||||
/package
|
||||
.env
|
||||
/src/tests.rs
|
||||
/src/tests.rs
|
||||
/pgrx-develop
|
||||
5
.vscode/extensions.json
vendored
Normal file
5
.vscode/extensions.json
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"rust-lang.rust-analyzer"
|
||||
]
|
||||
}
|
||||
163
Cargo.lock
generated
163
Cargo.lock
generated
@ -55,6 +55,15 @@ version = "1.0.101"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea"
|
||||
|
||||
[[package]]
|
||||
name = "ar_archive_writer"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7eb93bbb63b9c227414f6eb3a0adfddca591a8ce1e9b60661bb08969b87e340b"
|
||||
dependencies = [
|
||||
"object",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.89"
|
||||
@ -347,6 +356,30 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-channel"
|
||||
version = "0.5.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-epoch"
|
||||
version = "0.9.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.7"
|
||||
@ -357,6 +390,20 @@ dependencies = [
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dashmap"
|
||||
version = "6.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crossbeam-utils",
|
||||
"hashbrown 0.14.5",
|
||||
"lock_api",
|
||||
"once_cell",
|
||||
"parking_lot_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.10.7"
|
||||
@ -589,6 +636,12 @@ version = "1.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b43ede17f21864e81be2fa654110bf1e793774238d86ef8555c37e6519c0403"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.14.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.15.5"
|
||||
@ -815,11 +868,13 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"chrono",
|
||||
"dashmap",
|
||||
"fluent-uri",
|
||||
"idna",
|
||||
"indexmap",
|
||||
"json-pointer",
|
||||
"lazy_static",
|
||||
"moka",
|
||||
"once_cell",
|
||||
"percent-encoding",
|
||||
"pgrx",
|
||||
@ -828,8 +883,10 @@ dependencies = [
|
||||
"regex-syntax",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sqlparser",
|
||||
"url",
|
||||
"uuid",
|
||||
"xxhash-rust",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -930,6 +987,23 @@ dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "moka"
|
||||
version = "0.12.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85f8024e1c8e71c778968af91d43700ce1d11b219d127d79fb2934153b82b42b"
|
||||
dependencies = [
|
||||
"crossbeam-channel",
|
||||
"crossbeam-epoch",
|
||||
"crossbeam-utils",
|
||||
"equivalent",
|
||||
"parking_lot",
|
||||
"portable-atomic",
|
||||
"smallvec",
|
||||
"tagptr",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "7.1.3"
|
||||
@ -976,6 +1050,15 @@ dependencies = [
|
||||
"objc2-core-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.37.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.3"
|
||||
@ -1208,6 +1291,12 @@ version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||
|
||||
[[package]]
|
||||
name = "portable-atomic"
|
||||
version = "1.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49"
|
||||
|
||||
[[package]]
|
||||
name = "postgres"
|
||||
version = "0.19.12"
|
||||
@ -1307,6 +1396,16 @@ dependencies = [
|
||||
"unarray",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "psm"
|
||||
version = "0.1.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3852766467df634d74f0b2d7819bf8dc483a0eb2e3b0f50f756f9cfe8b0d18d8"
|
||||
dependencies = [
|
||||
"ar_archive_writer",
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-error"
|
||||
version = "1.2.3"
|
||||
@ -1372,6 +1471,26 @@ dependencies = [
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "recursive"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0786a43debb760f491b1bc0269fe5e84155353c67482b9e60d0cfb596054b43e"
|
||||
dependencies = [
|
||||
"recursive-proc-macro-impl",
|
||||
"stacker",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "recursive-proc-macro-impl"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "76009fbe0614077fc1a2ce255e3a1881a2e3a3527097d5dc6d8212c585e7e38b"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.5.18"
|
||||
@ -1599,12 +1718,35 @@ dependencies = [
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sqlparser"
|
||||
version = "0.61.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dbf5ea8d4d7c808e1af1cbabebca9a2abe603bcefc22294c5b95018d53200cb7"
|
||||
dependencies = [
|
||||
"log",
|
||||
"recursive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "stable_deref_trait"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
|
||||
|
||||
[[package]]
|
||||
name = "stacker"
|
||||
version = "0.1.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08d74a23609d509411d10e2176dc2a4346e3b4aea2e7b1869f19fdedbc71c013"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"psm",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "stringprep"
|
||||
version = "0.1.5"
|
||||
@ -1666,6 +1808,12 @@ dependencies = [
|
||||
"windows",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tagptr"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417"
|
||||
|
||||
[[package]]
|
||||
name = "tap"
|
||||
version = "1.0.1"
|
||||
@ -2247,6 +2395,15 @@ dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.59.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.60.2"
|
||||
@ -2503,6 +2660,12 @@ dependencies = [
|
||||
"tap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xxhash-rust"
|
||||
version = "0.8.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fdd20c5420375476fbd4394763288da7eb0cc0b8c11deed431a91562af7335d3"
|
||||
|
||||
[[package]]
|
||||
name = "yoke"
|
||||
version = "0.8.1"
|
||||
|
||||
@ -20,6 +20,10 @@ uuid = { version = "1.20.0", features = ["v4", "serde"] }
|
||||
chrono = { version = "0.4.43", features = ["serde"] }
|
||||
json-pointer = "0.3.4"
|
||||
indexmap = { version = "2.13.0", features = ["serde"] }
|
||||
moka = { version = "0.12.14", features = ["sync"] }
|
||||
xxhash-rust = { version = "0.8.15", features = ["xxh64"] }
|
||||
dashmap = "6.1.0"
|
||||
sqlparser = "0.61.0"
|
||||
|
||||
[dev-dependencies]
|
||||
pgrx-tests = "0.16.1"
|
||||
|
||||
279
GEMINI.md
279
GEMINI.md
@ -1,159 +1,144 @@
|
||||
# JSPG: JSON Schema Postgres
|
||||
|
||||
**JSPG** is a high-performance PostgreSQL extension for in-memory JSON Schema validation, specifically targeting **Draft 2020-12**.
|
||||
**JSPG** is a high-performance PostgreSQL extension written in Rust (using `pgrx`) that transforms Postgres into a pre-compiled Semantic Engine. It serves as the core engine for the "Punc" architecture, where the database is the single source of truth for all data models, API contracts, validations, and reactive queries.
|
||||
|
||||
It is designed to serve as the validation engine for the "Punc" architecture, where the database is the single source of truth for all data models and API contracts.
|
||||
## 1. Overview & Architecture
|
||||
|
||||
## 🎯 Goals
|
||||
JSPG operates by deeply integrating the JSON Schema Draft 2020-12 specification directly into the Postgres session lifecycle. It is built around three core pillars:
|
||||
* **Validator**: In-memory, near-instant JSON structural validation and type polymorphism routing.
|
||||
* **Merger**: Automatically traverse and UPSERT deeply nested JSON graphs into normalized relational tables.
|
||||
* **Queryer**: Compile JSON Schemas into static, cached SQL SPI `SELECT` plans for fetching full entities or isolated "Stems".
|
||||
|
||||
### 🎯 Goals
|
||||
1. **Draft 2020-12 Compliance**: Attempt to adhere to the official JSON Schema Draft 2020-12 specification.
|
||||
2. **Ultra-Fast Validation**: Compile schemas into an optimized in-memory representation for near-instant validation during high-throughput workloads.
|
||||
3. **Connection-Bound Caching**: Leverage the PostgreSQL session lifecycle to maintain a per-connection schema cache, eliminating the need for repetitive parsing.
|
||||
4. **Structural Inheritance**: Support object-oriented schema design via Implicit Keyword Shadowing and virtual `$family` references.
|
||||
5. **Punc Integration**: validation is aware of the "Punc" context (request/response) and can validate `cue` objects efficiently.
|
||||
|
||||
## 🔌 API Reference
|
||||
|
||||
The extension exposes the following functions to PostgreSQL:
|
||||
|
||||
### `cache_json_schemas(enums jsonb, types jsonb, puncs jsonb) -> jsonb`
|
||||
|
||||
Loads and compiles the entire schema registry into the session's memory, atomically replacing the previous validator.
|
||||
|
||||
* **Inputs**:
|
||||
* `enums`: Array of enum definitions.
|
||||
* `types`: Array of type definitions (core entities).
|
||||
* `puncs`: Array of punc (function) definitions with request/response schemas.
|
||||
* **Behavior**:
|
||||
* Parses all inputs into an internal schema graph.
|
||||
* Resolves all internal references (`$ref`).
|
||||
* Generates virtual union schemas for type hierarchies referenced via `$family`.
|
||||
* Compiles schemas into validators.
|
||||
* **Returns**: `{"response": "success"}` or an error object.
|
||||
|
||||
### `mask_json_schema(schema_id text, instance jsonb) -> jsonb`
|
||||
|
||||
Validates a JSON instance and returns a new JSON object with unknown properties removed (pruned) based on the schema.
|
||||
|
||||
* **Inputs**:
|
||||
* `schema_id`: The `$id` of the schema to mask against.
|
||||
* `instance`: The JSON data to mask.
|
||||
* **Returns**:
|
||||
* On success: A `Drop` containing the **masked data**.
|
||||
* On failure: A `Drop` containing validation errors.
|
||||
|
||||
### `validate_json_schema(schema_id text, instance jsonb) -> jsonb`
|
||||
|
||||
Validates a JSON instance against a pre-compiled schema.
|
||||
|
||||
* **Inputs**:
|
||||
* `schema_id`: The `$id` of the schema to validate against (e.g., `person`, `save_person.request`).
|
||||
* `instance`: The JSON data to validate.
|
||||
* **Returns**:
|
||||
* On success: `{"response": "success"}`
|
||||
* On failure: A JSON object containing structured errors (e.g., `{"errors": [...]}`).
|
||||
|
||||
### `json_schema_cached(schema_id text) -> bool`
|
||||
|
||||
Checks if a specific schema ID is currently present in the cache.
|
||||
|
||||
### `clear_json_schemas() -> jsonb`
|
||||
|
||||
Clears the current session's schema cache, freeing memory.
|
||||
|
||||
### `show_json_schemas() -> jsonb`
|
||||
|
||||
Returns a debug dump of the currently cached schemas (for development/debugging).
|
||||
|
||||
## ✨ Custom Features & Deviations
|
||||
|
||||
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.
|
||||
|
||||
### 1. Polymorphism & Referencing (`$ref`, `$family`, and Native Types)
|
||||
|
||||
JSPG replaces the complex, dynamic reference resolution logic of standard JSON Schema (e.g., `$defs`, relative URIs, `$dynamicRef`, `$dynamicAnchor`, `anyOf`) with a strict, explicitly structured global `$id` system. This powers predictable code generation and blazing-fast runtime validation.
|
||||
|
||||
#### A. Global `$id` Conventions & Schema Buckets
|
||||
Every schema is part of a flat, globally addressable namespace. However, where a schema is defined in the database determines its physical boundaries:
|
||||
* **Types (Entities)**: Schemas defined within a Postgres `type` represent entities. The `$id` must be exactly the type name (`person`) or suffixed (`full.person`). All schemas in this bucket receive strict Native Type Discrimination based on the physical table hierarchy.
|
||||
* **Puncs (APIs)**: Schemas defined within a `punc` are ad-hoc containers. The `$id` must be exactly `[punc_name].request` or `[punc_name].response`. They are never entities themselves.
|
||||
* **Enums (Domains)**: Schemas defined within an `enum` represent enum definitions. The `$id` must be exactly the enum name (`job_status`) or suffixed (`short.job_status`).
|
||||
|
||||
#### B. Native Type Discrimination (The `variations` Property)
|
||||
Because `jspg` knows which schemas are Entities based on their origin bucket (Types), it securely and implicitly manages the `"type"` property by attaching `compiled_variations`.
|
||||
If a schema originates in the `user` bucket, the validator does *not* rigidly require `{"type": "user"}`. Instead, it queries the physical Postgres type inheritance graph (e.g. `[entity, organization, user]`) and allows the JSON to be `{"type": "person"}` or `{"type": "bot"}` automatically, enabling seamless API polymorphism.
|
||||
|
||||
#### C. Structural Inheritance & Viral Infection (`$ref`)
|
||||
`$ref` is used exclusively for structural inheritance.
|
||||
* **Viral Infection**: If an anonymous schema or an ad-hoc schema (like a Punc Request) `$ref`s a strict Entity schema (like `person.light`), it *virally inherits* the `compiled_variations` of that target. This means a Punc request instantly gains the exact polymorphic security boundaries of the Entity it points to.
|
||||
* **`$ref` never creates a Union.** When you use `$ref`, you are asking for a single, concrete struct/shape.
|
||||
|
||||
#### D. Shape Polymorphism & Virtual Unions (`$family`)
|
||||
To support polymorphic API contracts (e.g., heterogeneous arrays of generic widgets) without manually writing massive `oneOf` blocks, JSPG provides the `$family` macro.
|
||||
While `$ref` defines rigid structure, `$family` relies on an abstract **Descendants Graph**.
|
||||
|
||||
During compilation, `jspg` temporarily tracks every `$ref` pointer globally to build a reverse-lookup graph of "Descendants". It also calculates the **Inheritance Depth** of every schema (how far removed it is from the root entity).
|
||||
When `{"$family": "widget"}` is encountered, JSPG:
|
||||
1. Locates the `widget` schema in the Descendants graph.
|
||||
2. Expands the macro by finding *every* schema in the entire database that structurally `$ref`s `widget`, directly or indirectly (e.g., `stock.widget`, an anonymous object, etc.).
|
||||
3. Evaluates the incoming JSON against **every** descendant schema in that family *strictly*.
|
||||
|
||||
If you request `{"$family": "light.widget"}`, it simply evaluates all schemas that `$ref` the generic abstract `light.widget` interface.
|
||||
|
||||
#### E. Strict Matches & The Depth Heuristic
|
||||
JSPG strictly enforces that polymorphic structures (`oneOf`, or a `$family` expansion) match **exactly one** valid schema permutation. It does not support fuzzy matching (`anyOf`).
|
||||
If a JSON payload matches more than one schema in a union (which happens frequently due to implicit inheritance where an object might technically satisfy the requirements of both `entity` and `user`), JSPG automatically applies the **Depth Heuristic Tie-Breaker**:
|
||||
* It looks up the pre-calculated Inheritance Depth for all valid passing candidates.
|
||||
* It selects the candidate that is **deepest** in the inheritance tree (the most explicitly defined descendant).
|
||||
* If multiple passing candidates tie at the exact same depth level, an `AMBIGUOUS` error is thrown, forcing the developer to supply a more precise type discriminator or payload.
|
||||
|
||||
This cleanly separates **Database Physics** (derived from the Postgres `Types` bucket and viral `$ref` inheritance) from **Structural Polymorphism** (derived purely from the abstract `$ref` tree).
|
||||
|
||||
### 2. Strict by Default & Extensibility
|
||||
JSPG enforces a "Secure by Default" philosophy. All schemas are treated as if `unevaluatedProperties: false` (and `unevaluatedItems: false`) is set, unless explicitly overridden.
|
||||
|
||||
* **Strictness**: By default, any property or array item in the instance data that is not explicitly defined in the schema causes a validation error. This prevents clients from sending undeclared fields or extra array elements.
|
||||
* **Extensibility (`extensible: true`)**: To allow a free-for-all of additional, undefined properties or extra array items, you must add `"extensible": true` to the schema. This globally disables the strictness check for that object or array, useful for types designed to be completely open.
|
||||
* **Structured Additional Properties (`additionalProperties: {...}`)**: Instead of a boolean free-for-all, you can define `additionalProperties` as a schema object (e.g., `{"type": "string"}`). This maintains strictness (no arbitrary keys) but allows any extra keys as long as their values match the defined structure.
|
||||
* **Ref Boundaries**: Strictness is reset when crossing `$ref` boundaries. The referenced schema's strictness is determined by its own definition (strict by default unless `extensible: true`), ignoring the caller's state.
|
||||
* **Inheritance**: Strictness is inherited. A schema extending a strict parent will also be strict unless it declares itself `extensible: true`. Conversely, a schema extending a loose parent will also be loose unless it declares itself `extensible: false`.
|
||||
|
||||
### 3. Implicit Keyword Shadowing
|
||||
Standard JSON Schema composition (`allOf`) is additive (Intersection), meaning constraints can only be tightened, not replaced. However, JSPG treats `$ref` differently when it appears alongside other properties to support object-oriented inheritance.
|
||||
|
||||
* **Inheritance (`$ref` + properties)**: When a schema uses `$ref` and defines its own properties, JSPG implements Smart Merge (or Shadowing). If a property is defined in the current schema, its constraints take precedence over the inherited constraints for that specific keyword.
|
||||
* **Example**: If Entity defines `type: { const: "entity" }` and Person (which refs Entity) defines `type: { const: "person" }`, validation passes for "person". The local const shadows the inherited const.
|
||||
* **Granularity**: Shadowing is per-keyword. If Entity defined `type: { const: "entity", minLength: 5 }`, Person would shadow `const` but still inherit `minLength: 5`.
|
||||
* **Composition (`allOf`)**: When using `allOf`, standard intersection rules apply. No shadowing occurs; all constraints from all branches must pass. This is used for mixins or interfaces.
|
||||
|
||||
### 4. Format Leniency for Empty Strings
|
||||
To simplify frontend form logic, the format validators for `uuid`, `date-time`, and `email` explicitly allow empty strings (`""`). This treats an empty string as "present but unset" rather than "invalid format".
|
||||
|
||||
## 🏗️ Architecture
|
||||
|
||||
The extension is written in Rust using `pgrx` and structures its schema parser to mirror the Punc Generator's design:
|
||||
|
||||
* **Single `Schema` Struct**: A unified struct representing the exact layout of a JSON Schema object, including standard keywords and custom vocabularies (`form`, `display`, etc.).
|
||||
* **Compiler Phase**: schema JSONs are parsed into this struct, linked (references resolved), and then compiled into an efficient validation tree.
|
||||
* **Validation Phase**: The compiled validators traverse the JSON instance using `serde_json::Value`.
|
||||
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.
|
||||
5. **Reactive Beats**: Provide natively generated "Stems" (isolated payload fragments) for dynamic websocket reactivity.
|
||||
|
||||
### Concurrency & Threading ("Immutable Graphs")
|
||||
|
||||
To support high-throughput validation while allowing for runtime schema updates (e.g., during development or hot-reloading), JSPG uses an **Atomic Swap** pattern based on 100% immutable schemas.
|
||||
|
||||
To support high-throughput operations while allowing for runtime updates (e.g., during hot-reloading), JSPG uses an **Atomic Swap** pattern:
|
||||
1. **Parser Phase**: Schema JSONs are parsed into ordered `Schema` structs.
|
||||
2. **Compiler Phase**: The database iterates all parsed schemas and pre-computes native optimization maps:
|
||||
* **Descendants Map**: A reverse `$ref` lookup graph for instant `$family` resolution.
|
||||
* **Depths Map**: The `$ref` lineage distance of every schema for heuristic tie-breaking.
|
||||
* **Variations Map**: The Native Type inheritance hierarchy.
|
||||
3. **Immutable Validator**: The `Validator` struct immutably owns the `Database` registry and all its global maps. Once created, a validator instance (and its registry) never changes. Schemas themselves are completely frozen; `$ref` strings are resolved dynamically at runtime using the pre-computed O(1) maps, eliminating the need to physically mutate or link pointers across structures.
|
||||
4. **Global Pointer**: A global `RwLock<Option<Arc<Validator>>>` holds the current active validator.
|
||||
5. **Lock-Free Reads**: Validation requests acquire a read lock just long enough to clone the `Arc` (incrementing a reference count), then release the lock immediately. Validation proceeds on the snapshot, ensuring no blocking during schema updates.
|
||||
6. **Atomic Updates**: When schemas are reloaded (`cache_json_schemas`), a new `Registry` and `Validator` are built entirely on the stack. The global pointer is then atomically swapped to the new instance under a write lock.
|
||||
2. **Compiler Phase**: The database iterates all parsed schemas and pre-computes native optimization maps (Descendants Map, Depths Map, Variations Map).
|
||||
3. **Immutable Validator**: The `Validator` struct immutably owns the `Database` registry and all its global maps. Schemas themselves are completely frozen; `$ref` strings are resolved dynamically at runtime using pre-computed O(1) maps.
|
||||
4. **Lock-Free Reads**: Incoming operations acquire a read lock just long enough to clone the `Arc` inside an `RwLock<Option<Arc<Validator>>>`, ensuring zero blocking during schema updates.
|
||||
|
||||
## 🧪 Testing
|
||||
---
|
||||
|
||||
Testing is driven by standard Rust unit tests that load JSON fixtures.
|
||||
## 2. Validator
|
||||
|
||||
* **Isolation**: Each test file runs with its own isolated `Registry` and `Validator` instance, created on the stack. This eliminates global state interference and allows tests to run in parallel.
|
||||
* **Fixtures**: The tests are located in `tests/fixtures/*.json` and are executed via `cargo test`.
|
||||
The Validator provides strict, schema-driven evaluation for the "Punc" architecture.
|
||||
|
||||
### API Reference
|
||||
* `jspg_setup(database jsonb) -> jsonb`: Loads and compiles the entire registry (types, enums, puncs, relations) atomically.
|
||||
* `mask_json_schema(schema_id text, instance jsonb) -> jsonb`: Validates and prunes unknown properties dynamically, returning masked data.
|
||||
* `jspg_validate(schema_id text, instance jsonb) -> jsonb`: Returns boolean-like success or structured errors.
|
||||
* `jspg_teardown() -> jsonb`: Clears the current session's schema cache.
|
||||
|
||||
### Custom Features & Deviations
|
||||
JSPG implements specific extensions to the Draft 2020-12 standard to support the Punc architecture's object-oriented needs while heavily optimizing for zero-runtime lookups.
|
||||
|
||||
* **Caching Strategy**: The Validator caches the pre-compiled `Database` registry in memory upon initialization (`jspg_setup`). This registry holds the comprehensive graph of schema boundaries, Types, ENUMs, and Foreign Key relationships, acting as the Single Source of Truth for all validation operations without polling Postgres.
|
||||
|
||||
#### A. Polymorphism & Referencing (`$ref`, `$family`, and Native Types)
|
||||
* **Native Type Discrimination (`variations`)**: Schemas defined inside a Postgres `type` are Entities. The validator securely and implicitly manages their `"type"` property. If an entity inherits from `user`, incoming JSON can safely define `{"type": "person"}` without errors, thanks to `compiled_variations` inheritance.
|
||||
* **Structural Inheritance & Viral Infection (`$ref`)**: `$ref` is used exclusively for structural inheritance, *never* for union creation. A Punc request schema that `$ref`s an Entity virally inherits all physical database polymorphism rules for that target.
|
||||
* **Shape Polymorphism (`$family`)**: Auto-expands polymorphic API lists based on an abstract **Descendants Graph**. If `{"$family": "widget"}` is used, the Validator dynamically identifies *every* schema in the registry that `$ref`s `widget` (e.g., `stock.widget`, `task.widget`) and evaluates the JSON against all of them.
|
||||
* **Strict Matches & Depth Heuristic**: Polymorphic structures MUST match exactly **one** schema permutation. If multiple inherited struct permutations pass, JSPG applies the **Depth Heuristic Tie-Breaker**, selecting the candidate deepest in the inheritance tree.
|
||||
|
||||
#### B. Dot-Notation Schema Resolution & Database Mapping
|
||||
* **The Dot Convention**: When a schema represents a specific variation or shape of an underlying physical database `Type` (e.g., a "summary" of a "person"), its `$id` must adhere to a dot-notation suffix convention (e.g., `summary.person` or `full.person`).
|
||||
* **Entity Resolution**: The framework (Validator, Queryer, Merger) dynamically determines the backing PostgreSQL table structure by splitting the schema's `$id` (or `$ref`) by `.` and extracting the **last segment** (`next_back()`). If the last segment matches a known Database Type (like `person`), the framework natively applies that table's inheritance rules, variations, and physical foreign keys to the schema graph, regardless of the prefix.
|
||||
|
||||
#### C. Strict by Default & Extensibility
|
||||
* **Strictness**: By default, any property not explicitly defined in the schema causes a validation error (effectively enforcing `additionalProperties: false` globally).
|
||||
* **Extensibility (`extensible: true`)**: To allow a free-for-all of undefined properties, schemas must explicitly declare `"extensible": true`.
|
||||
* **Structured Additional Properties**: If `additionalProperties: {...}` is defined as a schema, arbitrary keys are allowed so long as their values match the defined type constraint.
|
||||
* **Inheritance Boundaries**: Strictness resets when crossing `$ref` boundaries. A schema extending a strict parent remains strict unless it explicitly overrides with `"extensible": true`.
|
||||
|
||||
#### D. Implicit Keyword Shadowing
|
||||
* **Inheritance (`$ref` + properties)**: Unlike standard JSON Schema, when a schema uses `$ref` alongside local properties, JSPG implements **Smart Merge**. Local constraints natively take precedence over (shadow) inherited constraints for the same keyword.
|
||||
* *Example*: If `entity` has `type: {const: "entity"}`, but `person` defines `type: {const: "person"}`, the local `person` const cleanly overrides the inherited one.
|
||||
* **Composition (`allOf`)**: When evaluating `allOf`, standard intersection rules apply seamlessly. No shadowing occurs, meaning all constraints from all branches must pass.
|
||||
|
||||
#### E. Format Leniency for Empty Strings
|
||||
To simplify frontend form validation, format validators specifically for `uuid`, `date-time`, and `email` explicitly allow empty strings (`""`), treating them as "present but unset".
|
||||
|
||||
---
|
||||
|
||||
|
||||
## 3. Merger
|
||||
|
||||
The Merger provides an automated, high-performance graph synchronization engine via the `jspg_merge(cue JSONB)` API. It orchestrates the complex mapping of nested JSON objects into normalized Postgres relational tables, honoring all inheritance and graph constraints.
|
||||
|
||||
### Core Features
|
||||
|
||||
* **Caching Strategy**: The Merger leverages the `Validator`'s in-memory `Database` registry to instantly resolve Foreign Key mapping graphs. It additionally utilizes the concurrent `GLOBAL_JSPG` application memory (`DashMap`) to cache statically constructed SQL `SELECT` strings used during deduplication (`lk_`) and difference tracking calculations.
|
||||
* **Deep Graph Merging**: The Merger walks arbitrary levels of deeply nested JSON schemas (e.g. tracking an `order`, its `customer`, and an array of its `lines`). It intelligently discovers the correct parent-to-child or child-to-parent Foreign Keys stored in the registry and automatically maps the UUIDs across the relationships during UPSERT.
|
||||
* **Prefix Foreign Key Matching**: Handles scenario where multiple relations point to the same table by using database Foreign Key constraint prefixes (`fk_`). For example, if a schema has `shipping_address` and `billing_address`, the merger resolves against `fk_shipping_address_entity` vs `fk_billing_address_entity` automatically to correctly route object properties.
|
||||
* **Dynamic Deduplication & Lookups**: If a nested object is provided without an `id`, the Merger utilizes Postgres `lk_` index constraints defined in the schema registry (e.g. `lk_person` mapped to `first_name` and `last_name`). It dynamically queries these unique matching constraints to discover the correct UUID to perform an UPDATE, preventing data duplication.
|
||||
* **Hierarchical Table Inheritance**: The Punc system uses distributed table inheritance (e.g. `person` inherits `user` inherits `organization` inherits `entity`). The Merger splits the incoming JSON payload and performs atomic row updates across *all* relevant tables in the lineage map.
|
||||
* **The Archive Paradigm**: Data is never deleted in the Punc system. The Merger securely enforces referential integrity by toggling the `archived` Boolean flag on the base `entity` table rather than issuing SQL `DELETE` commands.
|
||||
* **Change Tracking & Reactivity**: The Merger diffs the incoming JSON against the existing database row (utilizing static, `DashMap`-cached `lk_` SELECT string templates). Every detected change is recorded into the `agreego.change` audit table, tracking the user mapping. It then natively uses `pg_notify` to broadcast a completely flat row-level diff out to the Go WebSocket server for O(1) routing.
|
||||
* **Flat Structural Beats (Unidirectional Flow)**: The Merger purposefully DOES NOT trace or hydrate outbound Foreign Keys or nested parent structures during writes. It emits completely flat, mathematically perfect structural deltas via `pg_notify` representing only the exact Postgres rows that changed. This guarantees the write-path remains O(1) lightning fast. It is the strict responsibility of the upstream Punc Framework (the Go `Speaker`) to intercept these flat beats, evaluate them against active Websocket Schema Topologies, and dynamically issue targeted `jspg_query` reads to hydrate the exact contextual subgraphs required by listening clients.
|
||||
* **Pre-Order Notification Traversal**: To support proper topological hydration on the upstream Go Framework, the Merger decouples the `pg_notify` execution from the physical database write execution. The engine collects structural changes and explicitly fires `pg_notify` SQL statements in strict **Pre-Order** (Parent -> Relations -> Children). This guarantees that WebSocket clients receive the parent entity `Beat` prior to any nested child entities, ensuring stable unidirectional data flows without hydration race conditions.
|
||||
* **Many-to-Many Graph Edge Management**: Operates seamlessly with the global `agreego.relationship` table, allowing the system to represent and merge arbitrary reified M:M relationships directionally between any two entities.
|
||||
* **Sparse Updates**: Empty JSON strings `""` are directly bound as explicit SQL `NULL` directives to clear data, whilst omitted (missing) properties skip UPDATE execution entirely, ensuring partial UI submissions do not wipe out sibling fields.
|
||||
* **Unified Return Structure**: To eliminate UI hydration race conditions and multi-user duplication, `jspg_merge` explicitly strips the response graph and returns only the root `{ "id": "uuid" }` (or an array of IDs for list insertions). External APIs can then explicitly call read APIs to fetch the resulting graph, while the UI relies 100% implicitly on the flat `pg_notify` pipeline for reactive state synchronization.
|
||||
* **Decoupled SQL Generation**: Because Writes (INSERT/UPDATE) are inherently highly dynamic based on partial payload structures, the Merger generates raw SQL strings dynamically per execution without caching, guaranteeing a minimal memory footprint while scaling optimally.
|
||||
|
||||
---
|
||||
|
||||
## 4. Queryer
|
||||
|
||||
The Queryer transforms Postgres into a pre-compiled Semantic Query Engine via the `jspg_query(schema_id text, cue jsonb)` API, designed to serve the exact shape of Punc responses directly via SQL.
|
||||
|
||||
### Core Features
|
||||
|
||||
* **Caching Strategy (DashMap SQL Caching)**: The Queryer securely caches its compiled, static SQL string templates per schema permutation inside the `GLOBAL_JSPG` concurrent `DashMap`. This eliminates recursive AST schema crawling on consecutive requests. Furthermore, it evaluates the strings via Postgres SPI (Server Programming Interface) Prepared Statements, leveraging native database caching of execution plans for extreme performance.
|
||||
* **Schema-to-SQL Compilation**: Compiles JSON Schema ASTs spanning deep arrays directly into static, pre-planned SQL multi-JOIN queries. This explicitly features the `Smart Merge` evaluation engine which natively translates properties through `allOf` and `$ref` inheritances, mapping JSON fields specifically to their physical database table aliases during translation.
|
||||
* **Dynamic Filtering**: Binds parameters natively through `cue.filters` objects. The queryer enforces a strict, structured, MongoDB-style operator syntax to map incoming JSON request paths directly to their originating structural table columns.
|
||||
* **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.
|
||||
* **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.
|
||||
* **Multi-Table Branching**: If the Physical Table is a parent to other tables (e.g. `organization` has variations `["organization", "bot", "person"]`), the compiler generates a dynamic `CASE WHEN type = '...' THEN ...` query, expanding into `JOIN`s for each variation.
|
||||
* **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.
|
||||
|
||||
### The Stem Engine
|
||||
|
||||
Rather than over-fetching heavy Entity payloads and trimming them, Punc Framework Websockets depend on isolated subgraphs defined as **Stems**.
|
||||
A `Stem` is a declaration of an **Entity Type boundary** that exists somewhere within the compiled JSON Schema graph, expressed using **`gjson` multipath syntax** (e.g., `contacts.#.phone_numbers.#`).
|
||||
|
||||
Because `pg_notify` (Beats) fire rigidly from physical Postgres tables (e.g. `{"type": "phone_number"}`), the Go Framework only ever needs to know: "Does the schema `with_contacts.person` contain the `phone_number` Entity anywhere inside its tree, and if so, what is the gjson path to iterate its payload?"
|
||||
|
||||
* **Initialization:** During startup (`jspg_stems()`), the database crawls all Schemas and maps out every physical Entity Type it references. It builds a highly optimized `HashMap<String, HashMap<String, Arc<Stem>>>` providing strictly `O(1)` memory lookups mapping `Schema ID -> { Stem Path -> Entity Type }`.
|
||||
* **GJSON Pathing:** Unlike standard JSON Pointers, stems utilize `.#` array iterator syntax. The Go web server consumes this native path (e.g. `lines.#`) across the raw Postgres JSON byte payload, extracting all active UUIDs in one massive sub-millisecond sweep without unmarshaling Go ASTs.
|
||||
* **Polymorphic Condition Selectors:** When trailing paths would otherwise collide because of abstract polymorphic type definitions (e.g., a `target` property bounded by a `oneOf` taking either `phone_number` or `email_address`), JSPG natively appends evaluated `gjson` type conditions into the path (e.g. `contacts.#.target#(type=="phone_number")`). This guarantees `O(1)` key uniqueness in the HashMap while retaining extreme array extraction speeds natively without runtime AST evaluation.
|
||||
* **Identifier Prioritization:** When determining if a nested object boundary is an Entity, JSPG natively prioritizes defined `$id` tags over `$ref` inheritance pointers to prevent polymorphic boundaries from devolving into their generic base classes.
|
||||
* **Cyclical Deduplication:** Because Punc relationships often reference back on themselves via deeply nested classes, the Stem Engine applies intelligent path deduplication. If the active `current_path` already ends with the target entity string, it traverses the inheritance properties without appending the entity to the stem path again, eliminating infinite powerset loops.
|
||||
* **Relationship Path Squashing:** When calculating string paths structurally, JSPG intentionally **omits** properties natively named `target` or `source` if they belong to a native database `relationship` table override.
|
||||
* **The Go Router**: The Golang Punc framework uses this exact mapping to register WebSocket Beat frequencies exclusively on the Entity types discovered.
|
||||
* **The Queryer Execution**: When the Go framework asks JSPG to hydrate a partial `phone_number` stem for the `with_contacts.person` schema, instead of jumping through string paths, the SQL Compiler simply reaches into the Schema's AST using the `phone_number` Type string, pulls out exactly that entity's mapping rules, and returns a fully correlated `SELECT` block! This natively handles nested array properties injected via `oneOf` or array references efficiently bypassing runtime powerset expansion.
|
||||
* **Performance:** These Stem execution structures are fully statically compiled via SPI and map perfectly to `O(1)` real-time routing logic on the application tier.
|
||||
|
||||
## 5. Testing & Execution Architecture
|
||||
|
||||
JSPG implements a strict separation of concerns to bypass the need to boot a full PostgreSQL cluster for unit and integration testing. Because `pgrx::spi::Spi` directly links to PostgreSQL C-headers, building the library with `cargo test` on macOS natively normally results in fatal `dyld` crashes.
|
||||
|
||||
To solve this, JSPG introduces the `DatabaseExecutor` trait inside `src/database/executors/`:
|
||||
|
||||
* **`SpiExecutor` (`pgrx.rs`)**: The production evaluator that is conditionally compiled (`#[cfg(not(test))]`). It unwraps standard `pgrx::spi` connections to the database.
|
||||
* **`MockExecutor` (`mock.rs`)**: The testing evaluator that is conditionally compiled (`#[cfg(test)]`). It absorbs SQL calls and captures parameter bindings in memory without executing them.
|
||||
|
||||
### Universal Test Harness (`src/tests/`)
|
||||
JSPG abandons the standard `cargo pgrx test` model in favor of native OS testing for a >1000x speed increase (`~0.05s` execution).
|
||||
|
||||
1. **JSON Fixtures**: All core interactions are defined abstractly as JSON arrays in `fixtures/`. Each file contains suites of `TestCase` objects with an `action` flag (`compile`, `validate`, `merge`, `query`).
|
||||
2. **`build.rs` Generator**: The build script traverses the JSON fixtures, extracts their structural identities, and generates standard `#[test]` blocks into `src/tests/fixtures.rs`.
|
||||
3. **Modular Test Dispatcher**: The `src/tests/types/` module deserializes the abstract JSON test payloads into `Suite`, `Case`, and `Expect` data structures.
|
||||
* The `compile` action natively asserts the exact output shape of `jspg_stems`, allowing structural and relationship mapping logic to be tested purely through JSON without writing brute-force manual tests in Rust.
|
||||
4. **Unit Context Execution**: When `cargo test` executes, the runner iterates the JSON payloads. Because the tests run natively inside the module via `#cfg(test)`, the Rust compiler globally erases `pgrx` C-linkage, instantiates the `MockExecutor`, and allows for pure structural evaluation of complex database logic completely in memory in parallel.
|
||||
|
||||
64
build.rs
64
build.rs
@ -21,22 +21,15 @@ fn to_safe_identifier(name: &str) -> String {
|
||||
}
|
||||
|
||||
fn main() {
|
||||
println!("cargo:rerun-if-changed=tests/fixtures");
|
||||
println!("cargo:rerun-if-changed=fixtures");
|
||||
println!("cargo:rerun-if-changed=Cargo.toml");
|
||||
|
||||
// File 1: src/tests/fixtures.rs for #[pg_test]
|
||||
let pg_dest_path = Path::new("src/tests/fixtures.rs");
|
||||
let mut pg_file = File::create(pg_dest_path).unwrap();
|
||||
|
||||
// File 2: tests/fixtures.rs for standard #[test] integration
|
||||
let std_dest_path = Path::new("tests/fixtures.rs");
|
||||
// File: src/tests/fixtures.rs for standard #[test] integration
|
||||
let std_dest_path = Path::new("src/tests/fixtures.rs");
|
||||
let mut std_file = File::create(std_dest_path).unwrap();
|
||||
|
||||
// Write headers
|
||||
writeln!(std_file, "use jspg::validator::util;").unwrap();
|
||||
|
||||
// Walk tests/fixtures directly
|
||||
let fixtures_path = "tests/fixtures";
|
||||
let fixtures_path = "fixtures";
|
||||
if Path::new(fixtures_path).exists() {
|
||||
for entry in fs::read_dir(fixtures_path).unwrap() {
|
||||
let entry = entry.unwrap();
|
||||
@ -70,52 +63,33 @@ fn main() {
|
||||
.unwrap()
|
||||
.as_array()
|
||||
.expect("Tests must be an array");
|
||||
let safe_filename = to_safe_identifier(file_name);
|
||||
for (t_idx, test) in tests.iter().enumerate() {
|
||||
let t_obj = test.as_object().expect("Test case must be an object");
|
||||
if !t_obj.contains_key("description")
|
||||
|| !t_obj.contains_key("data")
|
||||
|| !t_obj.contains_key("valid")
|
||||
|| !t_obj.contains_key("schema_id")
|
||||
{
|
||||
if !t_obj.contains_key("description") {
|
||||
panic!(
|
||||
"File {} suite {} test {} is missing required case fields (description, data, valid, schema_id)",
|
||||
"File {} suite {} test {} is missing required case fields (description)",
|
||||
file_name, i, t_idx
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Use deterministic names: test_{filename}_{index}
|
||||
let safe_filename = to_safe_identifier(file_name);
|
||||
let fn_name = format!("test_{}_{}", safe_filename, i);
|
||||
// Use deterministic names: test_{filename}_{suite_idx}_{test_idx}
|
||||
let fn_name = format!("test_{}_{}_{}", safe_filename, i, t_idx);
|
||||
|
||||
// Write to src/tests.rs (PG Test)
|
||||
// CARGO_MANIFEST_DIR is used to find the absolute path to fixtures at runtime
|
||||
write!(
|
||||
pg_file,
|
||||
r#"
|
||||
#[pg_test]
|
||||
fn {}() {{
|
||||
let path = format!("{{}}/tests/fixtures/{}.json", env!("CARGO_MANIFEST_DIR"));
|
||||
crate::validator::util::run_test_file_at_index(&path, {}).unwrap();
|
||||
}}
|
||||
"#,
|
||||
fn_name, file_name, i
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Write to tests/tests.rs (Std Test)
|
||||
write!(
|
||||
std_file,
|
||||
r#"
|
||||
// Write to src/tests/fixtures.rs (Std Test)
|
||||
write!(
|
||||
std_file,
|
||||
r#"
|
||||
#[test]
|
||||
fn {}() {{
|
||||
let path = format!("{{}}/tests/fixtures/{}.json", env!("CARGO_MANIFEST_DIR"));
|
||||
util::run_test_file_at_index(&path, {}).unwrap();
|
||||
let path = format!("{{}}/fixtures/{}.json", env!("CARGO_MANIFEST_DIR"));
|
||||
crate::tests::runner::run_test_case(&path, {}, {}).unwrap();
|
||||
}}
|
||||
"#,
|
||||
fn_name, file_name, i
|
||||
)
|
||||
.unwrap();
|
||||
fn_name, file_name, i, t_idx
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
176
fixtures/additionalProperties.json
Normal file
176
fixtures/additionalProperties.json
Normal file
@ -0,0 +1,176 @@
|
||||
[
|
||||
{
|
||||
"description": "additionalProperties validates properties not matched by properties",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"$id": "schema1",
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "string"
|
||||
},
|
||||
"bar": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"additionalProperties": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "defined properties are valid",
|
||||
"data": {
|
||||
"foo": "value",
|
||||
"bar": 123
|
||||
},
|
||||
"schema_id": "schema1",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "additional property matching schema is valid",
|
||||
"data": {
|
||||
"foo": "value",
|
||||
"is_active": true,
|
||||
"hidden": false
|
||||
},
|
||||
"schema_id": "schema1",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "additional property not matching schema is invalid",
|
||||
"data": {
|
||||
"foo": "value",
|
||||
"is_active": 1
|
||||
},
|
||||
"schema_id": "schema1",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "extensible: true with additionalProperties still validates structure",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"extensible": true,
|
||||
"additionalProperties": {
|
||||
"type": "integer"
|
||||
},
|
||||
"$id": "additionalProperties_1_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "additional property matching schema is valid",
|
||||
"data": {
|
||||
"foo": "hello",
|
||||
"count": 5,
|
||||
"age": 42
|
||||
},
|
||||
"schema_id": "additionalProperties_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "additional property not matching schema is invalid despite extensible: true",
|
||||
"data": {
|
||||
"foo": "hello",
|
||||
"count": "five"
|
||||
},
|
||||
"schema_id": "additionalProperties_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "complex additionalProperties with object and array items",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"$id": "schema3",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "valid array of strings",
|
||||
"data": {
|
||||
"type": "my_type",
|
||||
"group_a": [
|
||||
"field1",
|
||||
"field2"
|
||||
],
|
||||
"group_b": [
|
||||
"field3"
|
||||
]
|
||||
},
|
||||
"schema_id": "schema3",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "invalid array of integers",
|
||||
"data": {
|
||||
"type": "my_type",
|
||||
"group_a": [
|
||||
1,
|
||||
2
|
||||
]
|
||||
},
|
||||
"schema_id": "schema3",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "invalid non-array type",
|
||||
"data": {
|
||||
"type": "my_type",
|
||||
"group_a": "field1"
|
||||
},
|
||||
"schema_id": "schema3",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
677
fixtures/allOf.json
Normal file
677
fixtures/allOf.json
Normal file
@ -0,0 +1,677 @@
|
||||
[
|
||||
{
|
||||
"description": "allOf",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
"properties": {
|
||||
"bar": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"bar"
|
||||
]
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"foo"
|
||||
]
|
||||
}
|
||||
],
|
||||
"$id": "allOf_0_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "allOf",
|
||||
"data": {
|
||||
"foo": "baz",
|
||||
"bar": 2
|
||||
},
|
||||
"schema_id": "allOf_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "mismatch second",
|
||||
"data": {
|
||||
"foo": "baz"
|
||||
},
|
||||
"schema_id": "allOf_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "mismatch first",
|
||||
"data": {
|
||||
"bar": 2
|
||||
},
|
||||
"schema_id": "allOf_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "wrong type",
|
||||
"data": {
|
||||
"foo": "baz",
|
||||
"bar": "quux"
|
||||
},
|
||||
"schema_id": "allOf_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "allOf with base schema",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"properties": {
|
||||
"bar": {
|
||||
"type": "integer"
|
||||
},
|
||||
"baz": {},
|
||||
"foo": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"bar"
|
||||
],
|
||||
"allOf": [
|
||||
{
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"foo"
|
||||
]
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"baz": {
|
||||
"type": "null"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"baz"
|
||||
]
|
||||
}
|
||||
],
|
||||
"$id": "allOf_1_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "valid",
|
||||
"data": {
|
||||
"foo": "quux",
|
||||
"bar": 2,
|
||||
"baz": null
|
||||
},
|
||||
"schema_id": "allOf_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "mismatch base schema",
|
||||
"data": {
|
||||
"foo": "quux",
|
||||
"baz": null
|
||||
},
|
||||
"schema_id": "allOf_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "mismatch first allOf",
|
||||
"data": {
|
||||
"bar": 2,
|
||||
"baz": null
|
||||
},
|
||||
"schema_id": "allOf_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "mismatch second allOf",
|
||||
"data": {
|
||||
"foo": "quux",
|
||||
"bar": 2
|
||||
},
|
||||
"schema_id": "allOf_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "mismatch both",
|
||||
"data": {
|
||||
"bar": 2
|
||||
},
|
||||
"schema_id": "allOf_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "allOf simple types",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
"maximum": 30
|
||||
},
|
||||
{
|
||||
"minimum": 20
|
||||
}
|
||||
],
|
||||
"$id": "allOf_2_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "valid",
|
||||
"data": 25,
|
||||
"schema_id": "allOf_2_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "mismatch one",
|
||||
"data": 35,
|
||||
"schema_id": "allOf_2_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "allOf with boolean schemas, all true",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"allOf": [
|
||||
true,
|
||||
true
|
||||
],
|
||||
"$id": "allOf_3_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "any value is valid",
|
||||
"data": "foo",
|
||||
"schema_id": "allOf_3_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "allOf with boolean schemas, some false",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"allOf": [
|
||||
true,
|
||||
false
|
||||
],
|
||||
"$id": "allOf_4_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "any value is invalid",
|
||||
"data": "foo",
|
||||
"schema_id": "allOf_4_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "allOf with boolean schemas, all false",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"allOf": [
|
||||
false,
|
||||
false
|
||||
],
|
||||
"$id": "allOf_5_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "any value is invalid",
|
||||
"data": "foo",
|
||||
"schema_id": "allOf_5_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "allOf with one empty schema",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"allOf": [
|
||||
{}
|
||||
],
|
||||
"$id": "allOf_6_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "any data is valid",
|
||||
"data": 1,
|
||||
"schema_id": "allOf_6_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "allOf with two empty schemas",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"allOf": [
|
||||
{},
|
||||
{}
|
||||
],
|
||||
"$id": "allOf_7_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "any data is valid",
|
||||
"data": 1,
|
||||
"schema_id": "allOf_7_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "allOf with the first empty schema",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"allOf": [
|
||||
{},
|
||||
{
|
||||
"type": "number"
|
||||
}
|
||||
],
|
||||
"$id": "allOf_8_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "number is valid",
|
||||
"data": 1,
|
||||
"schema_id": "allOf_8_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "string is invalid",
|
||||
"data": "foo",
|
||||
"schema_id": "allOf_8_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "allOf with the last empty schema",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
"type": "number"
|
||||
},
|
||||
{}
|
||||
],
|
||||
"$id": "allOf_9_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "number is valid",
|
||||
"data": 1,
|
||||
"schema_id": "allOf_9_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "string is invalid",
|
||||
"data": "foo",
|
||||
"schema_id": "allOf_9_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "nested allOf, to check validation semantics",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"$id": "allOf_10_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "null is valid",
|
||||
"data": null,
|
||||
"schema_id": "allOf_10_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "anything non-null is invalid",
|
||||
"data": 123,
|
||||
"schema_id": "allOf_10_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "extensible: true allows extra properties in allOf",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
"properties": {
|
||||
"bar": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"bar"
|
||||
]
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"foo"
|
||||
]
|
||||
}
|
||||
],
|
||||
"extensible": true,
|
||||
"$id": "allOf_12_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "extra property is valid",
|
||||
"data": {
|
||||
"foo": "baz",
|
||||
"bar": 2,
|
||||
"qux": 3
|
||||
},
|
||||
"schema_id": "allOf_12_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "strict by default with allOf properties",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
"properties": {
|
||||
"foo": {
|
||||
"const": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"bar": {
|
||||
"const": 2
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"$id": "allOf_13_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "validates merged properties",
|
||||
"data": {
|
||||
"foo": 1,
|
||||
"bar": 2
|
||||
},
|
||||
"schema_id": "allOf_13_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "fails on extra property z explicitly",
|
||||
"data": {
|
||||
"foo": 1,
|
||||
"bar": 2,
|
||||
"z": 3
|
||||
},
|
||||
"schema_id": "allOf_13_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "allOf with nested extensible: true (partial looseness)",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
"properties": {
|
||||
"foo": {
|
||||
"const": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"extensible": true,
|
||||
"properties": {
|
||||
"bar": {
|
||||
"const": 2
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"$id": "allOf_14_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "extensible subschema doesn't make root extensible if root is strict",
|
||||
"data": {
|
||||
"foo": 1,
|
||||
"bar": 2,
|
||||
"z": 3
|
||||
},
|
||||
"schema_id": "allOf_14_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "strictness: allOf composition with strict refs",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "partA"
|
||||
},
|
||||
{
|
||||
"$ref": "partB"
|
||||
}
|
||||
],
|
||||
"$id": "allOf_15_0"
|
||||
},
|
||||
{
|
||||
"$id": "partA",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"$id": "partB",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "merged instance is valid",
|
||||
"data": {
|
||||
"id": "1",
|
||||
"name": "Me"
|
||||
},
|
||||
"schema_id": "allOf_15_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "extra property is invalid (root is strict)",
|
||||
"data": {
|
||||
"id": "1",
|
||||
"name": "Me",
|
||||
"extra": 1
|
||||
},
|
||||
"schema_id": "allOf_15_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "partA mismatch is invalid",
|
||||
"data": {
|
||||
"id": 1,
|
||||
"name": "Me"
|
||||
},
|
||||
"schema_id": "allOf_15_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
197
fixtures/booleanSchema.json
Normal file
197
fixtures/booleanSchema.json
Normal file
@ -0,0 +1,197 @@
|
||||
[
|
||||
{
|
||||
"description": "boolean schema 'true'",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"$id": "booleanSchema_0_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "number is valid",
|
||||
"data": 1,
|
||||
"schema_id": "booleanSchema_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "string is valid",
|
||||
"data": "foo",
|
||||
"schema_id": "booleanSchema_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "boolean true is valid",
|
||||
"data": true,
|
||||
"schema_id": "booleanSchema_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "boolean false is valid",
|
||||
"data": false,
|
||||
"schema_id": "booleanSchema_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "null is valid",
|
||||
"data": null,
|
||||
"schema_id": "booleanSchema_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "object is valid",
|
||||
"data": {
|
||||
"foo": "bar"
|
||||
},
|
||||
"schema_id": "booleanSchema_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "empty object is valid",
|
||||
"data": {},
|
||||
"schema_id": "booleanSchema_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "array is valid",
|
||||
"data": [
|
||||
"foo"
|
||||
],
|
||||
"schema_id": "booleanSchema_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "empty array is valid",
|
||||
"data": [],
|
||||
"schema_id": "booleanSchema_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "boolean schema 'false'",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"not": {},
|
||||
"$id": "booleanSchema_1_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "number is invalid",
|
||||
"data": 1,
|
||||
"schema_id": "booleanSchema_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "string is invalid",
|
||||
"data": "foo",
|
||||
"schema_id": "booleanSchema_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "boolean true is invalid",
|
||||
"data": true,
|
||||
"schema_id": "booleanSchema_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "boolean false is invalid",
|
||||
"data": false,
|
||||
"schema_id": "booleanSchema_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "null is invalid",
|
||||
"data": null,
|
||||
"schema_id": "booleanSchema_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "object is invalid",
|
||||
"data": {
|
||||
"foo": "bar"
|
||||
},
|
||||
"schema_id": "booleanSchema_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "empty object is invalid",
|
||||
"data": {},
|
||||
"schema_id": "booleanSchema_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "array is invalid",
|
||||
"data": [
|
||||
"foo"
|
||||
],
|
||||
"schema_id": "booleanSchema_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "empty array is invalid",
|
||||
"data": [],
|
||||
"schema_id": "booleanSchema_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
818
fixtures/const.json
Normal file
818
fixtures/const.json
Normal file
@ -0,0 +1,818 @@
|
||||
[
|
||||
{
|
||||
"description": "const validation",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"const": 2,
|
||||
"$id": "const_0_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "same value is valid",
|
||||
"data": 2,
|
||||
"schema_id": "const_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "another value is invalid",
|
||||
"data": 5,
|
||||
"schema_id": "const_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "another type is invalid",
|
||||
"data": "a",
|
||||
"schema_id": "const_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "const with object",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"const": {
|
||||
"foo": "bar",
|
||||
"baz": "bax"
|
||||
},
|
||||
"properties": {
|
||||
"foo": {},
|
||||
"baz": {}
|
||||
},
|
||||
"$id": "const_1_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "same object is valid",
|
||||
"data": {
|
||||
"foo": "bar",
|
||||
"baz": "bax"
|
||||
},
|
||||
"schema_id": "const_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "same object with different property order is valid",
|
||||
"data": {
|
||||
"baz": "bax",
|
||||
"foo": "bar"
|
||||
},
|
||||
"schema_id": "const_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "another object is invalid",
|
||||
"data": {
|
||||
"foo": "bar"
|
||||
},
|
||||
"schema_id": "const_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "another type is invalid",
|
||||
"data": [
|
||||
1,
|
||||
2
|
||||
],
|
||||
"schema_id": "const_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "const with array",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"const": [
|
||||
{
|
||||
"foo": "bar"
|
||||
}
|
||||
],
|
||||
"$id": "const_2_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "same array is valid",
|
||||
"data": [
|
||||
{
|
||||
"foo": "bar"
|
||||
}
|
||||
],
|
||||
"schema_id": "const_2_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "another array item is invalid",
|
||||
"data": [
|
||||
2
|
||||
],
|
||||
"schema_id": "const_2_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "array with additional items is invalid",
|
||||
"data": [
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"schema_id": "const_2_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "const with null",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"const": null,
|
||||
"$id": "const_3_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "null is valid",
|
||||
"data": null,
|
||||
"schema_id": "const_3_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "not null is invalid",
|
||||
"data": 0,
|
||||
"schema_id": "const_3_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "const with false does not match 0",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"const": false,
|
||||
"$id": "const_4_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "false is valid",
|
||||
"data": false,
|
||||
"schema_id": "const_4_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "integer zero is invalid",
|
||||
"data": 0,
|
||||
"schema_id": "const_4_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "float zero is invalid",
|
||||
"data": 0,
|
||||
"schema_id": "const_4_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "const with true does not match 1",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"const": true,
|
||||
"$id": "const_5_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "true is valid",
|
||||
"data": true,
|
||||
"schema_id": "const_5_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "integer one is invalid",
|
||||
"data": 1,
|
||||
"schema_id": "const_5_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "float one is invalid",
|
||||
"data": 1,
|
||||
"schema_id": "const_5_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "const with [false] does not match [0]",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"const": [
|
||||
false
|
||||
],
|
||||
"$id": "const_6_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "[false] is valid",
|
||||
"data": [
|
||||
false
|
||||
],
|
||||
"schema_id": "const_6_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "[0] is invalid",
|
||||
"data": [
|
||||
0
|
||||
],
|
||||
"schema_id": "const_6_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "[0.0] is invalid",
|
||||
"data": [
|
||||
0
|
||||
],
|
||||
"schema_id": "const_6_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "const with [true] does not match [1]",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"const": [
|
||||
true
|
||||
],
|
||||
"$id": "const_7_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "[true] is valid",
|
||||
"data": [
|
||||
true
|
||||
],
|
||||
"schema_id": "const_7_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "[1] is invalid",
|
||||
"data": [
|
||||
1
|
||||
],
|
||||
"schema_id": "const_7_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "[1.0] is invalid",
|
||||
"data": [
|
||||
1
|
||||
],
|
||||
"schema_id": "const_7_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "const with {\"a\": false} does not match {\"a\": 0}",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"const": {
|
||||
"a": false
|
||||
},
|
||||
"$id": "const_8_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "{\"a\": false} is valid",
|
||||
"data": {
|
||||
"a": false
|
||||
},
|
||||
"schema_id": "const_8_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "{\"a\": 0} is invalid",
|
||||
"data": {
|
||||
"a": 0
|
||||
},
|
||||
"schema_id": "const_8_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "{\"a\": 0.0} is invalid",
|
||||
"data": {
|
||||
"a": 0
|
||||
},
|
||||
"schema_id": "const_8_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "const with {\"a\": true} does not match {\"a\": 1}",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"const": {
|
||||
"a": true
|
||||
},
|
||||
"$id": "const_9_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "{\"a\": true} is valid",
|
||||
"data": {
|
||||
"a": true
|
||||
},
|
||||
"schema_id": "const_9_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "{\"a\": 1} is invalid",
|
||||
"data": {
|
||||
"a": 1
|
||||
},
|
||||
"schema_id": "const_9_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "{\"a\": 1.0} is invalid",
|
||||
"data": {
|
||||
"a": 1
|
||||
},
|
||||
"schema_id": "const_9_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "const with 0 does not match other zero-like types",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"const": 0,
|
||||
"$id": "const_10_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "false is invalid",
|
||||
"data": false,
|
||||
"schema_id": "const_10_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "integer zero is valid",
|
||||
"data": 0,
|
||||
"schema_id": "const_10_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "float zero is valid",
|
||||
"data": 0,
|
||||
"schema_id": "const_10_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "empty object is invalid",
|
||||
"data": {},
|
||||
"schema_id": "const_10_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "empty array is invalid",
|
||||
"data": [],
|
||||
"schema_id": "const_10_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "empty string is invalid",
|
||||
"data": "",
|
||||
"schema_id": "const_10_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "const with 1 does not match true",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"const": 1,
|
||||
"$id": "const_11_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "true is invalid",
|
||||
"data": true,
|
||||
"schema_id": "const_11_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "integer one is valid",
|
||||
"data": 1,
|
||||
"schema_id": "const_11_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "float one is valid",
|
||||
"data": 1,
|
||||
"schema_id": "const_11_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "const with -2.0 matches integer and float types",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"const": -2,
|
||||
"$id": "const_12_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "integer -2 is valid",
|
||||
"data": -2,
|
||||
"schema_id": "const_12_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "integer 2 is invalid",
|
||||
"data": 2,
|
||||
"schema_id": "const_12_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "float -2.0 is valid",
|
||||
"data": -2,
|
||||
"schema_id": "const_12_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "float 2.0 is invalid",
|
||||
"data": 2,
|
||||
"schema_id": "const_12_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "float -2.00001 is invalid",
|
||||
"data": -2.00001,
|
||||
"schema_id": "const_12_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "float and integers are equal up to 64-bit representation limits",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"const": 9007199254740992,
|
||||
"$id": "const_13_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "integer is valid",
|
||||
"data": 9007199254740992,
|
||||
"schema_id": "const_13_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "integer minus one is invalid",
|
||||
"data": 9007199254740991,
|
||||
"schema_id": "const_13_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "float is valid",
|
||||
"data": 9007199254740992,
|
||||
"schema_id": "const_13_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "float minus one is invalid",
|
||||
"data": 9007199254740991,
|
||||
"schema_id": "const_13_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "nul characters in strings",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"const": "hello\u0000there",
|
||||
"$id": "const_14_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "match string with nul",
|
||||
"data": "hello\u0000there",
|
||||
"schema_id": "const_14_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "do not match string lacking nul",
|
||||
"data": "hellothere",
|
||||
"schema_id": "const_14_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "characters with the same visual representation but different codepoint",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"const": "μ",
|
||||
"$comment": "U+03BC",
|
||||
"$id": "const_15_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "character uses the same codepoint",
|
||||
"data": "μ",
|
||||
"comment": "U+03BC",
|
||||
"schema_id": "const_15_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "character looks the same but uses a different codepoint",
|
||||
"data": "µ",
|
||||
"comment": "U+00B5",
|
||||
"schema_id": "const_15_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "characters with the same visual representation, but different number of codepoints",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"const": "ä",
|
||||
"$comment": "U+00E4",
|
||||
"$id": "const_16_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "character uses the same codepoint",
|
||||
"data": "ä",
|
||||
"comment": "U+00E4",
|
||||
"schema_id": "const_16_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "character looks the same but uses combining marks",
|
||||
"data": "ä",
|
||||
"comment": "a, U+0308",
|
||||
"schema_id": "const_16_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "extensible: true allows extra properties in const object match",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"const": {
|
||||
"a": 1
|
||||
},
|
||||
"extensible": true,
|
||||
"$id": "const_17_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "extra property ignored during strict check, but const check still applies (mismatch)",
|
||||
"data": {
|
||||
"a": 1,
|
||||
"b": 2
|
||||
},
|
||||
"schema_id": "const_17_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "extra property match in const (this is effectively impossible if data has extra props not in const, it implicitly fails const check unless we assume const check ignored extra props? No, const check is strict. So this test is just to show strictness passes.)",
|
||||
"data": {
|
||||
"a": 1
|
||||
},
|
||||
"schema_id": "const_17_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
418
fixtures/contains.json
Normal file
418
fixtures/contains.json
Normal file
@ -0,0 +1,418 @@
|
||||
[
|
||||
{
|
||||
"description": "contains keyword validation",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"contains": {
|
||||
"minimum": 5
|
||||
},
|
||||
"items": true,
|
||||
"$id": "contains_0_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "array with item matching schema (5) is valid (items: true)",
|
||||
"data": [
|
||||
3,
|
||||
4,
|
||||
5
|
||||
],
|
||||
"schema_id": "contains_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "array with item matching schema (6) is valid (items: true)",
|
||||
"data": [
|
||||
3,
|
||||
4,
|
||||
6
|
||||
],
|
||||
"schema_id": "contains_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "array with two items matching schema (5, 6) is valid (items: true)",
|
||||
"data": [
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6
|
||||
],
|
||||
"schema_id": "contains_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "array without items matching schema is invalid",
|
||||
"data": [
|
||||
2,
|
||||
3,
|
||||
4
|
||||
],
|
||||
"schema_id": "contains_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "empty array is invalid",
|
||||
"data": [],
|
||||
"schema_id": "contains_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "not array is valid",
|
||||
"data": {},
|
||||
"schema_id": "contains_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "contains keyword with const keyword",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"contains": {
|
||||
"const": 5
|
||||
},
|
||||
"items": true,
|
||||
"$id": "contains_1_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "array with item 5 is valid (items: true)",
|
||||
"data": [
|
||||
3,
|
||||
4,
|
||||
5
|
||||
],
|
||||
"schema_id": "contains_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "array with two items 5 is valid (items: true)",
|
||||
"data": [
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
5
|
||||
],
|
||||
"schema_id": "contains_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "array without item 5 is invalid",
|
||||
"data": [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4
|
||||
],
|
||||
"schema_id": "contains_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "contains keyword with boolean schema true",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"contains": true,
|
||||
"$id": "contains_2_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "any non-empty array is valid",
|
||||
"data": [
|
||||
"foo"
|
||||
],
|
||||
"schema_id": "contains_2_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "empty array is invalid",
|
||||
"data": [],
|
||||
"schema_id": "contains_2_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "contains keyword with boolean schema false",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"contains": false,
|
||||
"$id": "contains_3_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "any non-empty array is invalid",
|
||||
"data": [
|
||||
"foo"
|
||||
],
|
||||
"schema_id": "contains_3_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "empty array is invalid",
|
||||
"data": [],
|
||||
"schema_id": "contains_3_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "non-arrays are valid",
|
||||
"data": "contains does not apply to strings",
|
||||
"schema_id": "contains_3_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "items + contains",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"items": {
|
||||
"multipleOf": 2
|
||||
},
|
||||
"contains": {
|
||||
"multipleOf": 3
|
||||
},
|
||||
"$id": "contains_4_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "matches items, does not match contains",
|
||||
"data": [
|
||||
2,
|
||||
4,
|
||||
8
|
||||
],
|
||||
"schema_id": "contains_4_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "does not match items, matches contains",
|
||||
"data": [
|
||||
3,
|
||||
6,
|
||||
9
|
||||
],
|
||||
"schema_id": "contains_4_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "matches both items and contains",
|
||||
"data": [
|
||||
6,
|
||||
12
|
||||
],
|
||||
"schema_id": "contains_4_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "matches neither items nor contains",
|
||||
"data": [
|
||||
1,
|
||||
5
|
||||
],
|
||||
"schema_id": "contains_4_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "contains with false if subschema",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"contains": {
|
||||
"if": false,
|
||||
"else": true
|
||||
},
|
||||
"$id": "contains_5_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "any non-empty array is valid",
|
||||
"data": [
|
||||
"foo"
|
||||
],
|
||||
"schema_id": "contains_5_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "empty array is invalid",
|
||||
"data": [],
|
||||
"schema_id": "contains_5_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "contains with null instance elements",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"contains": {
|
||||
"type": "null"
|
||||
},
|
||||
"$id": "contains_6_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "allows null items",
|
||||
"data": [
|
||||
null
|
||||
],
|
||||
"schema_id": "contains_6_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "extensible: true allows non-matching items in contains",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"contains": {
|
||||
"const": 1
|
||||
},
|
||||
"extensible": true,
|
||||
"$id": "contains_7_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "extra items acceptable",
|
||||
"data": [
|
||||
1,
|
||||
2
|
||||
],
|
||||
"schema_id": "contains_7_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "strict by default: non-matching items in contains are invalid",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"contains": {
|
||||
"const": 1
|
||||
},
|
||||
"$id": "contains_8_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "extra items cause failure",
|
||||
"data": [
|
||||
1,
|
||||
2
|
||||
],
|
||||
"schema_id": "contains_8_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "only matching items is valid",
|
||||
"data": [
|
||||
1,
|
||||
1
|
||||
],
|
||||
"schema_id": "contains_8_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
232
fixtures/content.json
Normal file
232
fixtures/content.json
Normal file
@ -0,0 +1,232 @@
|
||||
[
|
||||
{
|
||||
"description": "validation of string-encoded content based on media type",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"contentMediaType": "application/json",
|
||||
"$id": "content_0_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "a valid JSON document",
|
||||
"data": "{\"foo\": \"bar\"}",
|
||||
"schema_id": "content_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "an invalid JSON document; validates true",
|
||||
"data": "{:}",
|
||||
"schema_id": "content_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "ignores non-strings",
|
||||
"data": 100,
|
||||
"schema_id": "content_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "validation of binary string-encoding",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"contentEncoding": "base64",
|
||||
"$id": "content_1_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "a valid base64 string",
|
||||
"data": "eyJmb28iOiAiYmFyIn0K",
|
||||
"schema_id": "content_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "an invalid base64 string (% is not a valid character); validates true",
|
||||
"data": "eyJmb28iOi%iYmFyIn0K",
|
||||
"schema_id": "content_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "ignores non-strings",
|
||||
"data": 100,
|
||||
"schema_id": "content_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "validation of binary-encoded media type documents",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"contentMediaType": "application/json",
|
||||
"contentEncoding": "base64",
|
||||
"$id": "content_2_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "a valid base64-encoded JSON document",
|
||||
"data": "eyJmb28iOiAiYmFyIn0K",
|
||||
"schema_id": "content_2_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "a validly-encoded invalid JSON document; validates true",
|
||||
"data": "ezp9Cg==",
|
||||
"schema_id": "content_2_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "an invalid base64 string that is valid JSON; validates true",
|
||||
"data": "{}",
|
||||
"schema_id": "content_2_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "ignores non-strings",
|
||||
"data": 100,
|
||||
"schema_id": "content_2_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "validation of binary-encoded media type documents with schema",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"contentMediaType": "application/json",
|
||||
"contentEncoding": "base64",
|
||||
"contentSchema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"foo"
|
||||
],
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "string"
|
||||
},
|
||||
"boo": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"$id": "content_3_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "a valid base64-encoded JSON document",
|
||||
"data": "eyJmb28iOiAiYmFyIn0K",
|
||||
"schema_id": "content_3_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "another valid base64-encoded JSON document",
|
||||
"data": "eyJib28iOiAyMCwgImZvbyI6ICJiYXoifQ==",
|
||||
"schema_id": "content_3_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "an invalid base64-encoded JSON document; validates true",
|
||||
"data": "eyJib28iOiAyMH0=",
|
||||
"schema_id": "content_3_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "an empty object as a base64-encoded JSON document; validates true",
|
||||
"data": "e30=",
|
||||
"schema_id": "content_3_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "an empty array as a base64-encoded JSON document",
|
||||
"data": "W10=",
|
||||
"schema_id": "content_3_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "a validly-encoded invalid JSON document; validates true",
|
||||
"data": "ezp9Cg==",
|
||||
"schema_id": "content_3_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "an invalid base64 string that is valid JSON; validates true",
|
||||
"data": "{}",
|
||||
"schema_id": "content_3_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "ignores non-strings",
|
||||
"data": 100,
|
||||
"schema_id": "content_3_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
748
fixtures/dependencies.json
Normal file
748
fixtures/dependencies.json
Normal file
@ -0,0 +1,748 @@
|
||||
[
|
||||
{
|
||||
"description": "single dependency (required)",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "schema1",
|
||||
"dependencies": {
|
||||
"bar": [
|
||||
"foo"
|
||||
]
|
||||
},
|
||||
"extensible": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "neither",
|
||||
"data": {},
|
||||
"schema_id": "schema1",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "nondependant",
|
||||
"data": {
|
||||
"foo": 1
|
||||
},
|
||||
"schema_id": "schema1",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "with dependency",
|
||||
"data": {
|
||||
"foo": 1,
|
||||
"bar": 2
|
||||
},
|
||||
"schema_id": "schema1",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "missing dependency",
|
||||
"data": {
|
||||
"bar": 2
|
||||
},
|
||||
"schema_id": "schema1",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "ignores arrays",
|
||||
"data": [
|
||||
"bar"
|
||||
],
|
||||
"schema_id": "schema1",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "ignores strings",
|
||||
"data": "foobar",
|
||||
"schema_id": "schema1",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "ignores other non-objects",
|
||||
"data": 12,
|
||||
"schema_id": "schema1",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "empty dependents",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "schema2",
|
||||
"dependencies": {
|
||||
"bar": []
|
||||
},
|
||||
"extensible": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "empty object",
|
||||
"data": {},
|
||||
"schema_id": "schema2",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "object with one property",
|
||||
"data": {
|
||||
"bar": 2
|
||||
},
|
||||
"schema_id": "schema2",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "non-object is valid",
|
||||
"data": 1,
|
||||
"schema_id": "schema2",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "multiple dependents required",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "schema3",
|
||||
"dependencies": {
|
||||
"quux": [
|
||||
"foo",
|
||||
"bar"
|
||||
]
|
||||
},
|
||||
"extensible": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "neither",
|
||||
"data": {},
|
||||
"schema_id": "schema3",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "nondependants",
|
||||
"data": {
|
||||
"foo": 1,
|
||||
"bar": 2
|
||||
},
|
||||
"schema_id": "schema3",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "with dependencies",
|
||||
"data": {
|
||||
"foo": 1,
|
||||
"bar": 2,
|
||||
"quux": 3
|
||||
},
|
||||
"schema_id": "schema3",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "missing dependency",
|
||||
"data": {
|
||||
"foo": 1,
|
||||
"quux": 2
|
||||
},
|
||||
"schema_id": "schema3",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "missing other dependency",
|
||||
"data": {
|
||||
"bar": 1,
|
||||
"quux": 2
|
||||
},
|
||||
"schema_id": "schema3",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "missing both dependencies",
|
||||
"data": {
|
||||
"quux": 1
|
||||
},
|
||||
"schema_id": "schema3",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "dependencies with escaped characters",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "schema4",
|
||||
"dependencies": {
|
||||
"foo\nbar": [
|
||||
"foo\rbar"
|
||||
],
|
||||
"foo\"bar": [
|
||||
"foo'bar"
|
||||
]
|
||||
},
|
||||
"extensible": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "CRLF",
|
||||
"data": {
|
||||
"foo\nbar": 1,
|
||||
"foo\rbar": 2
|
||||
},
|
||||
"schema_id": "schema4",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "quoted quotes",
|
||||
"data": {
|
||||
"foo'bar": 1,
|
||||
"foo\"bar": 2
|
||||
},
|
||||
"schema_id": "schema4",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "CRLF missing dependent",
|
||||
"data": {
|
||||
"foo\nbar": 1,
|
||||
"foo": 2
|
||||
},
|
||||
"schema_id": "schema4",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "quoted quotes missing dependent",
|
||||
"data": {
|
||||
"foo\"bar": 2
|
||||
},
|
||||
"schema_id": "schema4",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "extensible: true allows extra properties in dependentRequired",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "schema5",
|
||||
"dependencies": {
|
||||
"bar": [
|
||||
"foo"
|
||||
]
|
||||
},
|
||||
"extensible": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "extra property is valid",
|
||||
"data": {
|
||||
"foo": 1,
|
||||
"bar": 2,
|
||||
"baz": 3
|
||||
},
|
||||
"schema_id": "schema5",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "single dependency (schemas, STRICT)",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "schema_schema1",
|
||||
"properties": {
|
||||
"foo": true,
|
||||
"bar": true
|
||||
},
|
||||
"dependencies": {
|
||||
"bar": {
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "integer"
|
||||
},
|
||||
"bar": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "valid",
|
||||
"data": {
|
||||
"foo": 1,
|
||||
"bar": 2
|
||||
},
|
||||
"schema_id": "schema_schema1",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "no dependency",
|
||||
"data": {
|
||||
"foo": "quux"
|
||||
},
|
||||
"schema_id": "schema_schema1",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "wrong type",
|
||||
"data": {
|
||||
"foo": "quux",
|
||||
"bar": 2
|
||||
},
|
||||
"schema_id": "schema_schema1",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "wrong type other",
|
||||
"data": {
|
||||
"foo": 2,
|
||||
"bar": "quux"
|
||||
},
|
||||
"schema_id": "schema_schema1",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "wrong type both",
|
||||
"data": {
|
||||
"foo": "quux",
|
||||
"bar": "quux"
|
||||
},
|
||||
"schema_id": "schema_schema1",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "ignores arrays (invalid in strict mode)",
|
||||
"data": [
|
||||
"bar"
|
||||
],
|
||||
"schema_id": "schema_schema1",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false,
|
||||
"errors": [
|
||||
{
|
||||
"code": "STRICT_ITEM_VIOLATION"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "ignores strings",
|
||||
"data": "foobar",
|
||||
"schema_id": "schema_schema1",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "ignores other non-objects",
|
||||
"data": 12,
|
||||
"schema_id": "schema_schema1",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "single dependency (schemas, EXTENSIBLE)",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "schema_schema2",
|
||||
"properties": {
|
||||
"foo": true,
|
||||
"bar": true
|
||||
},
|
||||
"dependencies": {
|
||||
"bar": {
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "integer"
|
||||
},
|
||||
"bar": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"extensible": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "ignores arrays (valid in extensible mode)",
|
||||
"data": [
|
||||
"bar"
|
||||
],
|
||||
"schema_id": "schema_schema2",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "boolean subschemas",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "schema_schema3",
|
||||
"properties": {
|
||||
"foo": true,
|
||||
"bar": true
|
||||
},
|
||||
"dependencies": {
|
||||
"foo": true,
|
||||
"bar": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "object with property having schema true is valid",
|
||||
"data": {
|
||||
"foo": 1
|
||||
},
|
||||
"schema_id": "schema_schema3",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "object with property having schema false is invalid",
|
||||
"data": {
|
||||
"bar": 2
|
||||
},
|
||||
"schema_id": "schema_schema3",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "object with both properties is invalid",
|
||||
"data": {
|
||||
"foo": 1,
|
||||
"bar": 2
|
||||
},
|
||||
"schema_id": "schema_schema3",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "empty object is valid",
|
||||
"data": {},
|
||||
"schema_id": "schema_schema3",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "dependencies with escaped characters",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "schema_schema4",
|
||||
"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": [
|
||||
{
|
||||
"description": "quoted tab",
|
||||
"data": {
|
||||
"foo\tbar": 1,
|
||||
"a": 2,
|
||||
"b": 3,
|
||||
"c": 4
|
||||
},
|
||||
"schema_id": "schema_schema4",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "quoted quote",
|
||||
"data": {
|
||||
"foo'bar": {
|
||||
"foo\"bar": 1
|
||||
}
|
||||
},
|
||||
"schema_id": "schema_schema4",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "quoted tab invalid under dependent schema",
|
||||
"data": {
|
||||
"foo\tbar": 1,
|
||||
"a": 2
|
||||
},
|
||||
"schema_id": "schema_schema4",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "quoted quote invalid under dependent schema",
|
||||
"data": {
|
||||
"foo'bar": 1
|
||||
},
|
||||
"schema_id": "schema_schema4",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "dependent subschema incompatible with root (STRICT)",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "schema_schema5",
|
||||
"properties": {
|
||||
"foo": {},
|
||||
"baz": true
|
||||
},
|
||||
"dependencies": {
|
||||
"foo": {
|
||||
"properties": {
|
||||
"bar": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "matches root",
|
||||
"data": {
|
||||
"foo": 1
|
||||
},
|
||||
"schema_id": "schema_schema5",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "matches dependency (invalid in strict mode - bar not allowed if foo missing)",
|
||||
"data": {
|
||||
"bar": 1
|
||||
},
|
||||
"schema_id": "schema_schema5",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false,
|
||||
"errors": [
|
||||
{
|
||||
"code": "STRICT_PROPERTY_VIOLATION"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "matches both",
|
||||
"data": {
|
||||
"foo": 1,
|
||||
"bar": 2
|
||||
},
|
||||
"schema_id": "schema_schema5",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "no dependency",
|
||||
"data": {
|
||||
"baz": 1
|
||||
},
|
||||
"schema_id": "schema_schema5",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "dependent subschema incompatible with root (EXTENSIBLE)",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "schema_schema6",
|
||||
"properties": {
|
||||
"foo": {},
|
||||
"baz": true
|
||||
},
|
||||
"dependencies": {
|
||||
"foo": {
|
||||
"properties": {
|
||||
"bar": {}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"extensible": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "matches dependency (valid in extensible mode)",
|
||||
"data": {
|
||||
"bar": 1
|
||||
},
|
||||
"schema_id": "schema_schema6",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
163
fixtures/emptyString.json
Normal file
163
fixtures/emptyString.json
Normal file
@ -0,0 +1,163 @@
|
||||
[
|
||||
{
|
||||
"description": "empty string is valid for all types (except const)",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"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": ""
|
||||
}
|
||||
},
|
||||
"$id": "emptyString_0_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "empty string valid for object",
|
||||
"data": {
|
||||
"obj": ""
|
||||
},
|
||||
"schema_id": "emptyString_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "empty string valid for array",
|
||||
"data": {
|
||||
"arr": ""
|
||||
},
|
||||
"schema_id": "emptyString_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "empty string valid for string",
|
||||
"data": {
|
||||
"str": ""
|
||||
},
|
||||
"schema_id": "emptyString_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "empty string valid for integer",
|
||||
"data": {
|
||||
"int": ""
|
||||
},
|
||||
"schema_id": "emptyString_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "empty string valid for number",
|
||||
"data": {
|
||||
"num": ""
|
||||
},
|
||||
"schema_id": "emptyString_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "empty string valid for boolean",
|
||||
"data": {
|
||||
"bool": ""
|
||||
},
|
||||
"schema_id": "emptyString_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "empty string valid for null",
|
||||
"data": {
|
||||
"nul": ""
|
||||
},
|
||||
"schema_id": "emptyString_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "empty string valid for format",
|
||||
"data": {
|
||||
"fmt": ""
|
||||
},
|
||||
"schema_id": "emptyString_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "empty string INVALID for const (unless const is empty string)",
|
||||
"data": {
|
||||
"con": ""
|
||||
},
|
||||
"schema_id": "emptyString_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false,
|
||||
"errors": [
|
||||
{
|
||||
"code": "CONST_VIOLATED",
|
||||
"path": "/con"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "empty string VALID for const if const IS empty string",
|
||||
"data": {
|
||||
"con_empty": ""
|
||||
},
|
||||
"schema_id": "emptyString_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
736
fixtures/enum.json
Normal file
736
fixtures/enum.json
Normal file
@ -0,0 +1,736 @@
|
||||
[
|
||||
{
|
||||
"description": "simple enum validation",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"enum": [
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"$id": "enum_0_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "one of the enum is valid",
|
||||
"data": 1,
|
||||
"schema_id": "enum_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "something else is invalid",
|
||||
"data": 4,
|
||||
"schema_id": "enum_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "heterogeneous enum validation",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"enum": [
|
||||
6,
|
||||
"foo",
|
||||
[],
|
||||
true,
|
||||
{
|
||||
"foo": 12
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"foo": {}
|
||||
},
|
||||
"$id": "enum_1_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "one of the enum is valid",
|
||||
"data": [],
|
||||
"schema_id": "enum_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "something else is invalid",
|
||||
"data": null,
|
||||
"schema_id": "enum_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "objects are deep compared",
|
||||
"data": {
|
||||
"foo": false
|
||||
},
|
||||
"schema_id": "enum_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "valid object matches",
|
||||
"data": {
|
||||
"foo": 12
|
||||
},
|
||||
"schema_id": "enum_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "extra properties in object is invalid",
|
||||
"data": {
|
||||
"foo": 12,
|
||||
"boo": 42
|
||||
},
|
||||
"schema_id": "enum_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "heterogeneous enum-with-null validation",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"enum": [
|
||||
6,
|
||||
null
|
||||
],
|
||||
"$id": "enum_2_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "null is valid",
|
||||
"data": null,
|
||||
"schema_id": "enum_2_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "number is valid",
|
||||
"data": 6,
|
||||
"schema_id": "enum_2_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "something else is invalid",
|
||||
"data": "test",
|
||||
"schema_id": "enum_2_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "enums in properties",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"foo": {
|
||||
"enum": [
|
||||
"foo"
|
||||
]
|
||||
},
|
||||
"bar": {
|
||||
"enum": [
|
||||
"bar"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"bar"
|
||||
],
|
||||
"$id": "enum_3_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "both properties are valid",
|
||||
"data": {
|
||||
"foo": "foo",
|
||||
"bar": "bar"
|
||||
},
|
||||
"schema_id": "enum_3_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "wrong foo value",
|
||||
"data": {
|
||||
"foo": "foot",
|
||||
"bar": "bar"
|
||||
},
|
||||
"schema_id": "enum_3_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "wrong bar value",
|
||||
"data": {
|
||||
"foo": "foo",
|
||||
"bar": "bart"
|
||||
},
|
||||
"schema_id": "enum_3_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "missing optional property is valid",
|
||||
"data": {
|
||||
"bar": "bar"
|
||||
},
|
||||
"schema_id": "enum_3_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "missing required property is invalid",
|
||||
"data": {
|
||||
"foo": "foo"
|
||||
},
|
||||
"schema_id": "enum_3_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "missing all properties is invalid",
|
||||
"data": {},
|
||||
"schema_id": "enum_3_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "enum with escaped characters",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"enum": [
|
||||
"foo\nbar",
|
||||
"foo\rbar"
|
||||
],
|
||||
"$id": "enum_4_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "member 1 is valid",
|
||||
"data": "foo\nbar",
|
||||
"schema_id": "enum_4_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "member 2 is valid",
|
||||
"data": "foo\rbar",
|
||||
"schema_id": "enum_4_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "another string is invalid",
|
||||
"data": "abc",
|
||||
"schema_id": "enum_4_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "enum with false does not match 0",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"enum": [
|
||||
false
|
||||
],
|
||||
"$id": "enum_5_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "false is valid",
|
||||
"data": false,
|
||||
"schema_id": "enum_5_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "integer zero is invalid",
|
||||
"data": 0,
|
||||
"schema_id": "enum_5_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "float zero is invalid",
|
||||
"data": 0,
|
||||
"schema_id": "enum_5_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "enum with [false] does not match [0]",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"enum": [
|
||||
[
|
||||
false
|
||||
]
|
||||
],
|
||||
"$id": "enum_6_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "[false] is valid",
|
||||
"data": [
|
||||
false
|
||||
],
|
||||
"schema_id": "enum_6_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "[0] is invalid",
|
||||
"data": [
|
||||
0
|
||||
],
|
||||
"schema_id": "enum_6_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "[0.0] is invalid",
|
||||
"data": [
|
||||
0
|
||||
],
|
||||
"schema_id": "enum_6_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "enum with true does not match 1",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"enum": [
|
||||
true
|
||||
],
|
||||
"$id": "enum_7_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "true is valid",
|
||||
"data": true,
|
||||
"schema_id": "enum_7_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "integer one is invalid",
|
||||
"data": 1,
|
||||
"schema_id": "enum_7_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "float one is invalid",
|
||||
"data": 1,
|
||||
"schema_id": "enum_7_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "enum with [true] does not match [1]",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"enum": [
|
||||
[
|
||||
true
|
||||
]
|
||||
],
|
||||
"$id": "enum_8_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "[true] is valid",
|
||||
"data": [
|
||||
true
|
||||
],
|
||||
"schema_id": "enum_8_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "[1] is invalid",
|
||||
"data": [
|
||||
1
|
||||
],
|
||||
"schema_id": "enum_8_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "[1.0] is invalid",
|
||||
"data": [
|
||||
1
|
||||
],
|
||||
"schema_id": "enum_8_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "enum with 0 does not match false",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"enum": [
|
||||
0
|
||||
],
|
||||
"$id": "enum_9_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "false is invalid",
|
||||
"data": false,
|
||||
"schema_id": "enum_9_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "integer zero is valid",
|
||||
"data": 0,
|
||||
"schema_id": "enum_9_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "float zero is valid",
|
||||
"data": 0,
|
||||
"schema_id": "enum_9_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "enum with [0] does not match [false]",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"enum": [
|
||||
[
|
||||
0
|
||||
]
|
||||
],
|
||||
"$id": "enum_10_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "[false] is invalid",
|
||||
"data": [
|
||||
false
|
||||
],
|
||||
"schema_id": "enum_10_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "[0] is valid",
|
||||
"data": [
|
||||
0
|
||||
],
|
||||
"schema_id": "enum_10_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "[0.0] is valid",
|
||||
"data": [
|
||||
0
|
||||
],
|
||||
"schema_id": "enum_10_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "enum with 1 does not match true",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"enum": [
|
||||
1
|
||||
],
|
||||
"$id": "enum_11_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "true is invalid",
|
||||
"data": true,
|
||||
"schema_id": "enum_11_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "integer one is valid",
|
||||
"data": 1,
|
||||
"schema_id": "enum_11_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "float one is valid",
|
||||
"data": 1,
|
||||
"schema_id": "enum_11_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "enum with [1] does not match [true]",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"enum": [
|
||||
[
|
||||
1
|
||||
]
|
||||
],
|
||||
"$id": "enum_12_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "[true] is invalid",
|
||||
"data": [
|
||||
true
|
||||
],
|
||||
"schema_id": "enum_12_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "[1] is valid",
|
||||
"data": [
|
||||
1
|
||||
],
|
||||
"schema_id": "enum_12_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "[1.0] is valid",
|
||||
"data": [
|
||||
1
|
||||
],
|
||||
"schema_id": "enum_12_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "nul characters in strings",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"enum": [
|
||||
"hello\u0000there"
|
||||
],
|
||||
"$id": "enum_13_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "match string with nul",
|
||||
"data": "hello\u0000there",
|
||||
"schema_id": "enum_13_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "do not match string lacking nul",
|
||||
"data": "hellothere",
|
||||
"schema_id": "enum_13_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "extensible: true allows extra properties in enum object match",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"enum": [
|
||||
{
|
||||
"foo": 1
|
||||
}
|
||||
],
|
||||
"extensible": true,
|
||||
"$id": "enum_14_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "extra property ignored during strict check, but enum check still applies (mismatch here)",
|
||||
"data": {
|
||||
"foo": 1,
|
||||
"bar": 2
|
||||
},
|
||||
"schema_id": "enum_14_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "extra property ignored during strict check, enum match succeeds",
|
||||
"data": {
|
||||
"foo": 1
|
||||
},
|
||||
"schema_id": "enum_14_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
51
fixtures/exclusiveMaximum.json
Normal file
51
fixtures/exclusiveMaximum.json
Normal file
@ -0,0 +1,51 @@
|
||||
[
|
||||
{
|
||||
"description": "exclusiveMaximum validation",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"exclusiveMaximum": 3,
|
||||
"$id": "exclusiveMaximum_0_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "below the exclusiveMaximum is valid",
|
||||
"data": 2.2,
|
||||
"schema_id": "exclusiveMaximum_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "boundary point is invalid",
|
||||
"data": 3,
|
||||
"schema_id": "exclusiveMaximum_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "above the exclusiveMaximum is invalid",
|
||||
"data": 3.5,
|
||||
"schema_id": "exclusiveMaximum_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "ignores non-numbers",
|
||||
"data": "x",
|
||||
"schema_id": "exclusiveMaximum_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
51
fixtures/exclusiveMinimum.json
Normal file
51
fixtures/exclusiveMinimum.json
Normal file
@ -0,0 +1,51 @@
|
||||
[
|
||||
{
|
||||
"description": "exclusiveMinimum validation",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"exclusiveMinimum": 1.1,
|
||||
"$id": "exclusiveMinimum_0_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "above the exclusiveMinimum is valid",
|
||||
"data": 1.2,
|
||||
"schema_id": "exclusiveMinimum_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "boundary point is invalid",
|
||||
"data": 1.1,
|
||||
"schema_id": "exclusiveMinimum_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "below the exclusiveMinimum is invalid",
|
||||
"data": 0.6,
|
||||
"schema_id": "exclusiveMinimum_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "ignores non-numbers",
|
||||
"data": "x",
|
||||
"schema_id": "exclusiveMinimum_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
217
fixtures/families.json
Normal file
217
fixtures/families.json
Normal file
@ -0,0 +1,217 @@
|
||||
[
|
||||
{
|
||||
"description": "Entity families via pure $ref graph",
|
||||
"database": {
|
||||
"types": [
|
||||
{
|
||||
"name": "entity",
|
||||
"variations": [
|
||||
"entity",
|
||||
"organization",
|
||||
"person"
|
||||
],
|
||||
"schemas": [
|
||||
{
|
||||
"$id": "entity",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"$id": "light.entity",
|
||||
"$ref": "entity"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "organization",
|
||||
"variations": [
|
||||
"organization",
|
||||
"person"
|
||||
],
|
||||
"schemas": [
|
||||
{
|
||||
"$id": "organization",
|
||||
"$ref": "entity",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "person",
|
||||
"variations": [
|
||||
"person"
|
||||
],
|
||||
"schemas": [
|
||||
{
|
||||
"$id": "person",
|
||||
"$ref": "organization",
|
||||
"properties": {
|
||||
"first_name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"$id": "light.person",
|
||||
"$ref": "light.entity"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"puncs": [
|
||||
{
|
||||
"name": "get_entities",
|
||||
"schemas": [
|
||||
{
|
||||
"$id": "get_entities.response",
|
||||
"$family": "entity"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "get_light_entities",
|
||||
"schemas": [
|
||||
{
|
||||
"$id": "get_light_entities.response",
|
||||
"$family": "light.entity"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "Family matches base entity",
|
||||
"schema_id": "get_entities.response",
|
||||
"data": {
|
||||
"id": "1",
|
||||
"type": "entity"
|
||||
},
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Family matches descendant person",
|
||||
"schema_id": "get_entities.response",
|
||||
"data": {
|
||||
"id": "2",
|
||||
"type": "person",
|
||||
"name": "ACME",
|
||||
"first_name": "John"
|
||||
},
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Graph family matches light.entity",
|
||||
"schema_id": "get_light_entities.response",
|
||||
"data": {
|
||||
"id": "3",
|
||||
"type": "entity"
|
||||
},
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Graph family matches light.person (because it $refs light.entity)",
|
||||
"schema_id": "get_light_entities.response",
|
||||
"data": {
|
||||
"id": "4",
|
||||
"type": "person"
|
||||
},
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Graph family excludes organization (missing light. schema that $refs light.entity)",
|
||||
"schema_id": "get_light_entities.response",
|
||||
"data": {
|
||||
"id": "5",
|
||||
"type": "organization",
|
||||
"name": "ACME"
|
||||
},
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false,
|
||||
"errors": [
|
||||
{
|
||||
"code": "FAMILY_MISMATCH",
|
||||
"path": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Ad-hoc non-entity families (using normal json-schema object structures)",
|
||||
"database": {
|
||||
"puncs": [
|
||||
{
|
||||
"name": "get_widgets",
|
||||
"schemas": [
|
||||
{
|
||||
"$id": "widget",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"widget_type": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"$id": "special_widget",
|
||||
"$ref": "widget",
|
||||
"properties": {
|
||||
"special_feature": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"$id": "get_widgets.response",
|
||||
"$family": "widget"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "Ad-hoc family matches strictly by shape (no magic variations for base schemas)",
|
||||
"schema_id": "get_widgets.response",
|
||||
"data": {
|
||||
"id": "1",
|
||||
"widget_type": "special",
|
||||
"special_feature": "yes"
|
||||
},
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
5456
fixtures/format.json
Normal file
5456
fixtures/format.json
Normal file
File diff suppressed because it is too large
Load Diff
594
fixtures/if-then-else.json
Normal file
594
fixtures/if-then-else.json
Normal file
@ -0,0 +1,594 @@
|
||||
[
|
||||
{
|
||||
"description": "ignore if without then or else",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"if": {
|
||||
"const": 0
|
||||
},
|
||||
"$id": "if-then-else_0_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "valid when valid against lone if",
|
||||
"data": 0,
|
||||
"schema_id": "if-then-else_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "valid when invalid against lone if",
|
||||
"data": "hello",
|
||||
"schema_id": "if-then-else_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "ignore then without if",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"then": {
|
||||
"const": 0
|
||||
},
|
||||
"$id": "if-then-else_1_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "valid when valid against lone then",
|
||||
"data": 0,
|
||||
"schema_id": "if-then-else_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "valid when invalid against lone then",
|
||||
"data": "hello",
|
||||
"schema_id": "if-then-else_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "ignore else without if",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"else": {
|
||||
"const": 0
|
||||
},
|
||||
"$id": "if-then-else_2_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "valid when valid against lone else",
|
||||
"data": 0,
|
||||
"schema_id": "if-then-else_2_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "valid when invalid against lone else",
|
||||
"data": "hello",
|
||||
"schema_id": "if-then-else_2_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "if and then without else",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"if": {
|
||||
"exclusiveMaximum": 0
|
||||
},
|
||||
"then": {
|
||||
"minimum": -10
|
||||
},
|
||||
"$id": "if-then-else_3_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "valid through then",
|
||||
"data": -1,
|
||||
"schema_id": "if-then-else_3_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "invalid through then",
|
||||
"data": -100,
|
||||
"schema_id": "if-then-else_3_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "valid when if test fails",
|
||||
"data": 3,
|
||||
"schema_id": "if-then-else_3_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "if and else without then",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"if": {
|
||||
"exclusiveMaximum": 0
|
||||
},
|
||||
"else": {
|
||||
"multipleOf": 2
|
||||
},
|
||||
"$id": "if-then-else_4_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "valid when if test passes",
|
||||
"data": -1,
|
||||
"schema_id": "if-then-else_4_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "valid through else",
|
||||
"data": 4,
|
||||
"schema_id": "if-then-else_4_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "invalid through else",
|
||||
"data": 3,
|
||||
"schema_id": "if-then-else_4_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "validate against correct branch, then vs else",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"if": {
|
||||
"exclusiveMaximum": 0
|
||||
},
|
||||
"then": {
|
||||
"minimum": -10
|
||||
},
|
||||
"else": {
|
||||
"multipleOf": 2
|
||||
},
|
||||
"$id": "if-then-else_5_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "valid through then",
|
||||
"data": -1,
|
||||
"schema_id": "if-then-else_5_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "invalid through then",
|
||||
"data": -100,
|
||||
"schema_id": "if-then-else_5_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "valid through else",
|
||||
"data": 4,
|
||||
"schema_id": "if-then-else_5_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "invalid through else",
|
||||
"data": 3,
|
||||
"schema_id": "if-then-else_5_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "non-interference across combined schemas",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
"if": {
|
||||
"exclusiveMaximum": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"then": {
|
||||
"minimum": -10
|
||||
}
|
||||
},
|
||||
{
|
||||
"else": {
|
||||
"multipleOf": 2
|
||||
}
|
||||
}
|
||||
],
|
||||
"$id": "if-then-else_6_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "valid, but would have been invalid through then",
|
||||
"data": -100,
|
||||
"schema_id": "if-then-else_6_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "valid, but would have been invalid through else",
|
||||
"data": 3,
|
||||
"schema_id": "if-then-else_6_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "if with boolean schema true",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"if": true,
|
||||
"then": {
|
||||
"const": "then"
|
||||
},
|
||||
"else": {
|
||||
"const": "else"
|
||||
},
|
||||
"$id": "if-then-else_7_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "boolean schema true in if always chooses the then path (valid)",
|
||||
"data": "then",
|
||||
"schema_id": "if-then-else_7_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "boolean schema true in if always chooses the then path (invalid)",
|
||||
"data": "else",
|
||||
"schema_id": "if-then-else_7_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "if with boolean schema false",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"if": false,
|
||||
"then": {
|
||||
"const": "then"
|
||||
},
|
||||
"else": {
|
||||
"const": "else"
|
||||
},
|
||||
"$id": "if-then-else_8_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "boolean schema false in if always chooses the else path (invalid)",
|
||||
"data": "then",
|
||||
"schema_id": "if-then-else_8_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "boolean schema false in if always chooses the else path (valid)",
|
||||
"data": "else",
|
||||
"schema_id": "if-then-else_8_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "if appears at the end when serialized (keyword processing sequence)",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"then": {
|
||||
"const": "yes"
|
||||
},
|
||||
"else": {
|
||||
"const": "other"
|
||||
},
|
||||
"if": {
|
||||
"maxLength": 4
|
||||
},
|
||||
"$id": "if-then-else_9_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "yes redirects to then and passes",
|
||||
"data": "yes",
|
||||
"schema_id": "if-then-else_9_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "other redirects to else and passes",
|
||||
"data": "other",
|
||||
"schema_id": "if-then-else_9_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "no redirects to then and fails",
|
||||
"data": "no",
|
||||
"schema_id": "if-then-else_9_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "invalid redirects to else and fails",
|
||||
"data": "invalid",
|
||||
"schema_id": "if-then-else_9_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "then: false fails when condition matches",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"if": {
|
||||
"const": 1
|
||||
},
|
||||
"then": false,
|
||||
"$id": "if-then-else_10_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "matches if → then=false → invalid",
|
||||
"data": 1,
|
||||
"schema_id": "if-then-else_10_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "does not match if → then ignored → valid",
|
||||
"data": 2,
|
||||
"schema_id": "if-then-else_10_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "else: false fails when condition does not match",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"if": {
|
||||
"const": 1
|
||||
},
|
||||
"else": false,
|
||||
"$id": "if-then-else_11_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "matches if → else ignored → valid",
|
||||
"data": 1,
|
||||
"schema_id": "if-then-else_11_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "does not match if → else executes → invalid",
|
||||
"data": 2,
|
||||
"schema_id": "if-then-else_11_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "extensible: true allows extra properties in if-then-else",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"if": {
|
||||
"properties": {
|
||||
"foo": {
|
||||
"const": 1
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"foo"
|
||||
]
|
||||
},
|
||||
"then": {
|
||||
"properties": {
|
||||
"bar": {
|
||||
"const": 2
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"bar"
|
||||
]
|
||||
},
|
||||
"extensible": true,
|
||||
"$id": "if-then-else_12_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "extra property is valid (matches if and then)",
|
||||
"data": {
|
||||
"foo": 1,
|
||||
"bar": 2,
|
||||
"extra": "prop"
|
||||
},
|
||||
"schema_id": "if-then-else_12_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "strict by default with if-then properties",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"if": {
|
||||
"properties": {
|
||||
"foo": {
|
||||
"const": 1
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"foo"
|
||||
]
|
||||
},
|
||||
"then": {
|
||||
"properties": {
|
||||
"bar": {
|
||||
"const": 2
|
||||
}
|
||||
}
|
||||
},
|
||||
"$id": "if-then-else_13_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "valid match (foo + bar)",
|
||||
"data": {
|
||||
"foo": 1,
|
||||
"bar": 2
|
||||
},
|
||||
"schema_id": "if-then-else_13_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "fails on extra property z explicitly",
|
||||
"data": {
|
||||
"foo": 1,
|
||||
"bar": 2,
|
||||
"z": 3
|
||||
},
|
||||
"schema_id": "if-then-else_13_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
966
fixtures/items.json
Normal file
966
fixtures/items.json
Normal file
@ -0,0 +1,966 @@
|
||||
[
|
||||
{
|
||||
"description": "a schema given for items",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"items": {
|
||||
"type": "integer"
|
||||
},
|
||||
"$id": "items_0_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "valid items",
|
||||
"data": [
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"schema_id": "items_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "wrong type of items",
|
||||
"data": [
|
||||
1,
|
||||
"x"
|
||||
],
|
||||
"schema_id": "items_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "non-arrays are invalid",
|
||||
"data": {
|
||||
"foo": "bar"
|
||||
},
|
||||
"schema_id": "items_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "JavaScript pseudo-arrays are invalid",
|
||||
"data": {
|
||||
"0": "invalid",
|
||||
"length": 1
|
||||
},
|
||||
"schema_id": "items_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "items with boolean schema (true)",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"items": true,
|
||||
"$id": "items_1_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "any array is valid",
|
||||
"data": [
|
||||
1,
|
||||
"foo",
|
||||
true
|
||||
],
|
||||
"schema_id": "items_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "empty array is valid",
|
||||
"data": [],
|
||||
"schema_id": "items_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "items with boolean schema (false)",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"items": false,
|
||||
"$id": "items_2_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "any non-empty array is invalid",
|
||||
"data": [
|
||||
1,
|
||||
"foo",
|
||||
true
|
||||
],
|
||||
"schema_id": "items_2_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "empty array is valid",
|
||||
"data": [],
|
||||
"schema_id": "items_2_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "items and subitems",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"type": "array",
|
||||
"items": false,
|
||||
"prefixItems": [
|
||||
{
|
||||
"$ref": "item"
|
||||
},
|
||||
{
|
||||
"$ref": "item"
|
||||
},
|
||||
{
|
||||
"$ref": "item"
|
||||
}
|
||||
],
|
||||
"$id": "items_3_0"
|
||||
},
|
||||
{
|
||||
"$id": "item",
|
||||
"type": "array",
|
||||
"items": false,
|
||||
"prefixItems": [
|
||||
{
|
||||
"$ref": "sub-item"
|
||||
},
|
||||
{
|
||||
"$ref": "sub-item"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"$id": "sub-item",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"foo"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "valid items",
|
||||
"data": [
|
||||
[
|
||||
{
|
||||
"foo": null
|
||||
},
|
||||
{
|
||||
"foo": null
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"foo": null
|
||||
},
|
||||
{
|
||||
"foo": null
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"foo": null
|
||||
},
|
||||
{
|
||||
"foo": null
|
||||
}
|
||||
]
|
||||
],
|
||||
"schema_id": "items_3_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "too many items",
|
||||
"data": [
|
||||
[
|
||||
{
|
||||
"foo": null
|
||||
},
|
||||
{
|
||||
"foo": null
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"foo": null
|
||||
},
|
||||
{
|
||||
"foo": null
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"foo": null
|
||||
},
|
||||
{
|
||||
"foo": null
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"foo": null
|
||||
},
|
||||
{
|
||||
"foo": null
|
||||
}
|
||||
]
|
||||
],
|
||||
"schema_id": "items_3_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "too many sub-items",
|
||||
"data": [
|
||||
[
|
||||
{
|
||||
"foo": null
|
||||
},
|
||||
{
|
||||
"foo": null
|
||||
},
|
||||
{
|
||||
"foo": null
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"foo": null
|
||||
},
|
||||
{
|
||||
"foo": null
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"foo": null
|
||||
},
|
||||
{
|
||||
"foo": null
|
||||
}
|
||||
]
|
||||
],
|
||||
"schema_id": "items_3_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "wrong item",
|
||||
"data": [
|
||||
{
|
||||
"foo": null
|
||||
},
|
||||
[
|
||||
{
|
||||
"foo": null
|
||||
},
|
||||
{
|
||||
"foo": null
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"foo": null
|
||||
},
|
||||
{
|
||||
"foo": null
|
||||
}
|
||||
]
|
||||
],
|
||||
"schema_id": "items_3_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "wrong sub-item",
|
||||
"data": [
|
||||
[
|
||||
{},
|
||||
{
|
||||
"foo": null
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"foo": null
|
||||
},
|
||||
{
|
||||
"foo": null
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"foo": null
|
||||
},
|
||||
{
|
||||
"foo": null
|
||||
}
|
||||
]
|
||||
],
|
||||
"schema_id": "items_3_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "fewer items is invalid",
|
||||
"data": [
|
||||
[
|
||||
{
|
||||
"foo": null
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"foo": null
|
||||
}
|
||||
]
|
||||
],
|
||||
"schema_id": "items_3_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "nested items",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"$id": "items_4_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "valid nested array",
|
||||
"data": [
|
||||
[
|
||||
[
|
||||
[
|
||||
1
|
||||
]
|
||||
],
|
||||
[
|
||||
[
|
||||
2
|
||||
],
|
||||
[
|
||||
3
|
||||
]
|
||||
]
|
||||
],
|
||||
[
|
||||
[
|
||||
[
|
||||
4
|
||||
],
|
||||
[
|
||||
5
|
||||
],
|
||||
[
|
||||
6
|
||||
]
|
||||
]
|
||||
]
|
||||
],
|
||||
"schema_id": "items_4_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "nested array with invalid type",
|
||||
"data": [
|
||||
[
|
||||
[
|
||||
[
|
||||
"1"
|
||||
]
|
||||
],
|
||||
[
|
||||
[
|
||||
2
|
||||
],
|
||||
[
|
||||
3
|
||||
]
|
||||
]
|
||||
],
|
||||
[
|
||||
[
|
||||
[
|
||||
4
|
||||
],
|
||||
[
|
||||
5
|
||||
],
|
||||
[
|
||||
6
|
||||
]
|
||||
]
|
||||
]
|
||||
],
|
||||
"schema_id": "items_4_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "not deep enough",
|
||||
"data": [
|
||||
[
|
||||
[
|
||||
1
|
||||
],
|
||||
[
|
||||
2
|
||||
],
|
||||
[
|
||||
3
|
||||
]
|
||||
],
|
||||
[
|
||||
[
|
||||
4
|
||||
],
|
||||
[
|
||||
5
|
||||
],
|
||||
[
|
||||
6
|
||||
]
|
||||
]
|
||||
],
|
||||
"schema_id": "items_4_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "prefixItems with no additional items allowed",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"prefixItems": [
|
||||
{},
|
||||
{},
|
||||
{}
|
||||
],
|
||||
"items": false,
|
||||
"$id": "items_5_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "empty array",
|
||||
"data": [],
|
||||
"schema_id": "items_5_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "fewer number of items present (1)",
|
||||
"data": [
|
||||
1
|
||||
],
|
||||
"schema_id": "items_5_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "fewer number of items present (2)",
|
||||
"data": [
|
||||
1,
|
||||
2
|
||||
],
|
||||
"schema_id": "items_5_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "equal number of items present",
|
||||
"data": [
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"schema_id": "items_5_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "additional items are not permitted",
|
||||
"data": [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4
|
||||
],
|
||||
"schema_id": "items_5_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "items does not look in applicators, valid case",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
"prefixItems": [
|
||||
{
|
||||
"minimum": 3
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"items": {
|
||||
"minimum": 5
|
||||
},
|
||||
"$id": "items_6_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "prefixItems in allOf does not constrain items, invalid case",
|
||||
"data": [
|
||||
3,
|
||||
5
|
||||
],
|
||||
"schema_id": "items_6_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "prefixItems in allOf does not constrain items, valid case",
|
||||
"data": [
|
||||
5,
|
||||
5
|
||||
],
|
||||
"schema_id": "items_6_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "prefixItems validation adjusts the starting index for items",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"prefixItems": [
|
||||
{
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"items": {
|
||||
"type": "integer"
|
||||
},
|
||||
"$id": "items_7_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "valid items",
|
||||
"data": [
|
||||
"x",
|
||||
2,
|
||||
3
|
||||
],
|
||||
"schema_id": "items_7_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "wrong type of second item",
|
||||
"data": [
|
||||
"x",
|
||||
"y"
|
||||
],
|
||||
"schema_id": "items_7_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "items with heterogeneous array",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"prefixItems": [
|
||||
{}
|
||||
],
|
||||
"items": false,
|
||||
"$id": "items_8_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "heterogeneous invalid instance",
|
||||
"data": [
|
||||
"foo",
|
||||
"bar",
|
||||
37
|
||||
],
|
||||
"schema_id": "items_8_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "valid instance",
|
||||
"data": [
|
||||
null
|
||||
],
|
||||
"schema_id": "items_8_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "items with null instance elements",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"items": {
|
||||
"type": "null"
|
||||
},
|
||||
"$id": "items_9_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "allows null elements",
|
||||
"data": [
|
||||
null
|
||||
],
|
||||
"schema_id": "items_9_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "extensible: true allows extra items (when items is false)",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"items": false,
|
||||
"extensible": true,
|
||||
"$id": "items_10_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "extra item is valid",
|
||||
"data": [
|
||||
1
|
||||
],
|
||||
"schema_id": "items_10_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "extensible: true allows extra properties for items",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"items": {
|
||||
"minimum": 5
|
||||
},
|
||||
"extensible": true,
|
||||
"$id": "items_11_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "valid item is valid",
|
||||
"data": [
|
||||
5,
|
||||
6
|
||||
],
|
||||
"schema_id": "items_11_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "invalid item (less than min) is invalid even with extensible: true",
|
||||
"data": [
|
||||
4
|
||||
],
|
||||
"schema_id": "items_11_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "array: simple extensible array",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"type": "array",
|
||||
"extensible": true,
|
||||
"$id": "items_12_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "empty array is valid",
|
||||
"data": [],
|
||||
"schema_id": "items_12_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "array with items is valid (extensible)",
|
||||
"data": [
|
||||
1,
|
||||
"foo"
|
||||
],
|
||||
"schema_id": "items_12_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "array: strict array",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"type": "array",
|
||||
"extensible": false,
|
||||
"$id": "items_13_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "empty array is valid",
|
||||
"data": [],
|
||||
"schema_id": "items_13_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "array with items is invalid (strict)",
|
||||
"data": [
|
||||
1
|
||||
],
|
||||
"schema_id": "items_13_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "array: items extensible",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"extensible": true
|
||||
},
|
||||
"$id": "items_14_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "empty array is valid",
|
||||
"data": [],
|
||||
"schema_id": "items_14_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "array with items is valid (items explicitly allowed to be anything extensible)",
|
||||
"data": [
|
||||
1,
|
||||
"foo",
|
||||
{}
|
||||
],
|
||||
"schema_id": "items_14_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "array: items strict",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"extensible": false
|
||||
},
|
||||
"$id": "items_15_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "empty array is valid (empty objects)",
|
||||
"data": [
|
||||
{}
|
||||
],
|
||||
"schema_id": "items_15_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "array with strict object items is valid",
|
||||
"data": [
|
||||
{}
|
||||
],
|
||||
"schema_id": "items_15_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "array with invalid strict object items (extra property)",
|
||||
"data": [
|
||||
{
|
||||
"extra": 1
|
||||
}
|
||||
],
|
||||
"schema_id": "items_15_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
235
fixtures/maxContains.json
Normal file
235
fixtures/maxContains.json
Normal file
@ -0,0 +1,235 @@
|
||||
[
|
||||
{
|
||||
"description": "maxContains without contains is ignored",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"maxContains": 1,
|
||||
"extensible": true,
|
||||
"$id": "maxContains_0_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "one item valid against lone maxContains",
|
||||
"data": [
|
||||
1
|
||||
],
|
||||
"schema_id": "maxContains_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "two items still valid against lone maxContains",
|
||||
"data": [
|
||||
1,
|
||||
2
|
||||
],
|
||||
"schema_id": "maxContains_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "maxContains with contains",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"contains": {
|
||||
"const": 1
|
||||
},
|
||||
"maxContains": 1,
|
||||
"extensible": true,
|
||||
"$id": "maxContains_1_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "empty data",
|
||||
"data": [],
|
||||
"schema_id": "maxContains_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "all elements match, valid maxContains",
|
||||
"data": [
|
||||
1
|
||||
],
|
||||
"schema_id": "maxContains_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "all elements match, invalid maxContains",
|
||||
"data": [
|
||||
1,
|
||||
1
|
||||
],
|
||||
"schema_id": "maxContains_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "some elements match, valid maxContains",
|
||||
"data": [
|
||||
1,
|
||||
2
|
||||
],
|
||||
"schema_id": "maxContains_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "some elements match, invalid maxContains",
|
||||
"data": [
|
||||
1,
|
||||
2,
|
||||
1
|
||||
],
|
||||
"schema_id": "maxContains_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "maxContains with contains, value with a decimal",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"contains": {
|
||||
"const": 1
|
||||
},
|
||||
"maxContains": 1,
|
||||
"extensible": true,
|
||||
"$id": "maxContains_2_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "one element matches, valid maxContains",
|
||||
"data": [
|
||||
1
|
||||
],
|
||||
"schema_id": "maxContains_2_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "too many elements match, invalid maxContains",
|
||||
"data": [
|
||||
1,
|
||||
1
|
||||
],
|
||||
"schema_id": "maxContains_2_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "minContains < maxContains",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"contains": {
|
||||
"const": 1
|
||||
},
|
||||
"minContains": 1,
|
||||
"maxContains": 3,
|
||||
"extensible": true,
|
||||
"$id": "maxContains_3_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "actual < minContains < maxContains",
|
||||
"data": [],
|
||||
"schema_id": "maxContains_3_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "minContains < actual < maxContains",
|
||||
"data": [
|
||||
1,
|
||||
1
|
||||
],
|
||||
"schema_id": "maxContains_3_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "minContains < maxContains < actual",
|
||||
"data": [
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1
|
||||
],
|
||||
"schema_id": "maxContains_3_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "extensible: true allows non-matching items in maxContains",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"contains": {
|
||||
"const": 1
|
||||
},
|
||||
"maxContains": 1,
|
||||
"extensible": true,
|
||||
"$id": "maxContains_4_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "extra items disregarded for maxContains",
|
||||
"data": [
|
||||
1,
|
||||
2
|
||||
],
|
||||
"schema_id": "maxContains_4_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
126
fixtures/maxItems.json
Normal file
126
fixtures/maxItems.json
Normal file
@ -0,0 +1,126 @@
|
||||
[
|
||||
{
|
||||
"description": "maxItems validation",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"maxItems": 2,
|
||||
"extensible": true,
|
||||
"$id": "maxItems_0_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "shorter is valid",
|
||||
"data": [
|
||||
1
|
||||
],
|
||||
"schema_id": "maxItems_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "exact length is valid",
|
||||
"data": [
|
||||
1,
|
||||
2
|
||||
],
|
||||
"schema_id": "maxItems_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "too long is invalid",
|
||||
"data": [
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"schema_id": "maxItems_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "ignores non-arrays",
|
||||
"data": "foobar",
|
||||
"schema_id": "maxItems_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "maxItems validation with a decimal",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"maxItems": 2,
|
||||
"extensible": true,
|
||||
"$id": "maxItems_1_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "shorter is valid",
|
||||
"data": [
|
||||
1
|
||||
],
|
||||
"schema_id": "maxItems_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "too long is invalid",
|
||||
"data": [
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"schema_id": "maxItems_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "extensible: true allows extra items in maxItems (but counted)",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"maxItems": 2,
|
||||
"extensible": true,
|
||||
"$id": "maxItems_2_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "extra item counted towards maxItems",
|
||||
"data": [
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"schema_id": "maxItems_2_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
91
fixtures/maxLength.json
Normal file
91
fixtures/maxLength.json
Normal file
@ -0,0 +1,91 @@
|
||||
[
|
||||
{
|
||||
"description": "maxLength validation",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"maxLength": 2,
|
||||
"$id": "maxLength_0_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "shorter is valid",
|
||||
"data": "f",
|
||||
"schema_id": "maxLength_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "exact length is valid",
|
||||
"data": "fo",
|
||||
"schema_id": "maxLength_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "too long is invalid",
|
||||
"data": "foo",
|
||||
"schema_id": "maxLength_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "ignores non-strings",
|
||||
"data": 100,
|
||||
"schema_id": "maxLength_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "two graphemes is long enough",
|
||||
"data": "💩💩",
|
||||
"schema_id": "maxLength_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "maxLength validation with a decimal",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"maxLength": 2,
|
||||
"$id": "maxLength_1_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "shorter is valid",
|
||||
"data": "f",
|
||||
"schema_id": "maxLength_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "too long is invalid",
|
||||
"data": "foo",
|
||||
"schema_id": "maxLength_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
193
fixtures/maxProperties.json
Normal file
193
fixtures/maxProperties.json
Normal file
@ -0,0 +1,193 @@
|
||||
[
|
||||
{
|
||||
"description": "maxProperties validation",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"maxProperties": 2,
|
||||
"extensible": true,
|
||||
"$id": "maxProperties_0_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "shorter is valid",
|
||||
"data": {
|
||||
"foo": 1
|
||||
},
|
||||
"schema_id": "maxProperties_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "exact length is valid",
|
||||
"data": {
|
||||
"foo": 1,
|
||||
"bar": 2
|
||||
},
|
||||
"schema_id": "maxProperties_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "too long is invalid",
|
||||
"data": {
|
||||
"foo": 1,
|
||||
"bar": 2,
|
||||
"baz": 3
|
||||
},
|
||||
"schema_id": "maxProperties_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "ignores arrays",
|
||||
"data": [
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"schema_id": "maxProperties_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "ignores strings",
|
||||
"data": "foobar",
|
||||
"schema_id": "maxProperties_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "ignores other non-objects",
|
||||
"data": 12,
|
||||
"schema_id": "maxProperties_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "maxProperties validation with a decimal",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"maxProperties": 2,
|
||||
"extensible": true,
|
||||
"$id": "maxProperties_1_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "shorter is valid",
|
||||
"data": {
|
||||
"foo": 1
|
||||
},
|
||||
"schema_id": "maxProperties_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "too long is invalid",
|
||||
"data": {
|
||||
"foo": 1,
|
||||
"bar": 2,
|
||||
"baz": 3
|
||||
},
|
||||
"schema_id": "maxProperties_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "maxProperties = 0 means the object is empty",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"maxProperties": 0,
|
||||
"extensible": true,
|
||||
"$id": "maxProperties_2_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "no properties is valid",
|
||||
"data": {},
|
||||
"schema_id": "maxProperties_2_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "one property is invalid",
|
||||
"data": {
|
||||
"foo": 1
|
||||
},
|
||||
"schema_id": "maxProperties_2_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "extensible: true allows extra properties in maxProperties (though maxProperties still counts them!)",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"maxProperties": 2,
|
||||
"extensible": true,
|
||||
"$id": "maxProperties_3_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "extra property is counted towards maxProperties",
|
||||
"data": {
|
||||
"foo": 1,
|
||||
"bar": 2,
|
||||
"baz": 3
|
||||
},
|
||||
"schema_id": "maxProperties_3_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "extra property is valid if below maxProperties",
|
||||
"data": {
|
||||
"foo": 1
|
||||
},
|
||||
"schema_id": "maxProperties_3_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
100
fixtures/maximum.json
Normal file
100
fixtures/maximum.json
Normal file
@ -0,0 +1,100 @@
|
||||
[
|
||||
{
|
||||
"description": "maximum validation",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"maximum": 3,
|
||||
"$id": "maximum_0_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "below the maximum is valid",
|
||||
"data": 2.6,
|
||||
"schema_id": "maximum_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "boundary point is valid",
|
||||
"data": 3,
|
||||
"schema_id": "maximum_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "above the maximum is invalid",
|
||||
"data": 3.5,
|
||||
"schema_id": "maximum_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "ignores non-numbers",
|
||||
"data": "x",
|
||||
"schema_id": "maximum_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "maximum validation with unsigned integer",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"maximum": 300,
|
||||
"$id": "maximum_1_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "below the maximum is invalid",
|
||||
"data": 299.97,
|
||||
"schema_id": "maximum_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "boundary point integer is valid",
|
||||
"data": 300,
|
||||
"schema_id": "maximum_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "boundary point float is valid",
|
||||
"data": 300,
|
||||
"schema_id": "maximum_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "above the maximum is invalid",
|
||||
"data": 300.5,
|
||||
"schema_id": "maximum_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
274
fixtures/merge.json
Normal file
274
fixtures/merge.json
Normal file
@ -0,0 +1,274 @@
|
||||
[
|
||||
{
|
||||
"description": "merging: properties accumulate",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"$id": "base_0",
|
||||
"properties": {
|
||||
"base_prop": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"$ref": "base_0",
|
||||
"properties": {
|
||||
"child_prop": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"$id": "merge_0_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "valid with both properties",
|
||||
"data": {
|
||||
"base_prop": "a",
|
||||
"child_prop": "b"
|
||||
},
|
||||
"schema_id": "merge_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "invalid when base property has wrong type",
|
||||
"data": {
|
||||
"base_prop": 1,
|
||||
"child_prop": "b"
|
||||
},
|
||||
"schema_id": "merge_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false,
|
||||
"errors": [
|
||||
{
|
||||
"code": "TYPE_MISMATCH",
|
||||
"path": "/base_prop"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "merging: required fields accumulate",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"$id": "base_1",
|
||||
"properties": {
|
||||
"a": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"a"
|
||||
]
|
||||
},
|
||||
{
|
||||
"$ref": "base_1",
|
||||
"properties": {
|
||||
"b": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"b"
|
||||
],
|
||||
"$id": "merge_1_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "valid when both present",
|
||||
"data": {
|
||||
"a": "ok",
|
||||
"b": "ok"
|
||||
},
|
||||
"schema_id": "merge_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "invalid when base required missing",
|
||||
"data": {
|
||||
"b": "ok"
|
||||
},
|
||||
"schema_id": "merge_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false,
|
||||
"errors": [
|
||||
{
|
||||
"code": "REQUIRED_FIELD_MISSING",
|
||||
"path": "/a"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "invalid when child required missing",
|
||||
"data": {
|
||||
"a": "ok"
|
||||
},
|
||||
"schema_id": "merge_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false,
|
||||
"errors": [
|
||||
{
|
||||
"code": "REQUIRED_FIELD_MISSING",
|
||||
"path": "/b"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "merging: dependencies accumulate",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"$id": "base_2",
|
||||
"properties": {
|
||||
"trigger": {
|
||||
"type": "string"
|
||||
},
|
||||
"base_dep": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"trigger": [
|
||||
"base_dep"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"$ref": "base_2",
|
||||
"properties": {
|
||||
"child_dep": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"trigger": [
|
||||
"child_dep"
|
||||
]
|
||||
},
|
||||
"$id": "merge_2_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "valid with all deps",
|
||||
"data": {
|
||||
"trigger": "go",
|
||||
"base_dep": "ok",
|
||||
"child_dep": "ok"
|
||||
},
|
||||
"schema_id": "merge_2_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "invalid missing base dep",
|
||||
"data": {
|
||||
"trigger": "go",
|
||||
"child_dep": "ok"
|
||||
},
|
||||
"schema_id": "merge_2_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false,
|
||||
"errors": [
|
||||
{
|
||||
"code": "DEPENDENCY_FAILED",
|
||||
"path": "/base_dep"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "invalid missing child dep",
|
||||
"data": {
|
||||
"trigger": "go",
|
||||
"base_dep": "ok"
|
||||
},
|
||||
"schema_id": "merge_2_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false,
|
||||
"errors": [
|
||||
{
|
||||
"code": "DEPENDENCY_FAILED",
|
||||
"path": "/child_dep"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "merging: form and display do NOT merge",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"$id": "base_3",
|
||||
"properties": {
|
||||
"a": {
|
||||
"type": "string"
|
||||
},
|
||||
"b": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"form": [
|
||||
"a",
|
||||
"b"
|
||||
]
|
||||
},
|
||||
{
|
||||
"$ref": "base_3",
|
||||
"properties": {
|
||||
"c": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"form": [
|
||||
"c"
|
||||
],
|
||||
"$id": "merge_3_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "child schema validation",
|
||||
"data": {
|
||||
"a": "ok",
|
||||
"b": "ok",
|
||||
"c": "ok"
|
||||
},
|
||||
"comment": "Verifies validator handles the unmerged metadata correctly (ignores it or handles replacement)",
|
||||
"schema_id": "merge_3_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
2061
fixtures/merger.json
Normal file
2061
fixtures/merger.json
Normal file
File diff suppressed because it is too large
Load Diff
477
fixtures/minContains.json
Normal file
477
fixtures/minContains.json
Normal file
@ -0,0 +1,477 @@
|
||||
[
|
||||
{
|
||||
"description": "minContains without contains is ignored",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"minContains": 1,
|
||||
"extensible": true,
|
||||
"$id": "minContains_0_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "one item valid against lone minContains",
|
||||
"data": [
|
||||
1
|
||||
],
|
||||
"schema_id": "minContains_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "zero items still valid against lone minContains",
|
||||
"data": [],
|
||||
"schema_id": "minContains_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "minContains=1 with contains",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"contains": {
|
||||
"const": 1
|
||||
},
|
||||
"minContains": 1,
|
||||
"extensible": true,
|
||||
"$id": "minContains_1_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "empty data",
|
||||
"data": [],
|
||||
"schema_id": "minContains_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "no elements match",
|
||||
"data": [
|
||||
2
|
||||
],
|
||||
"schema_id": "minContains_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "single element matches, valid minContains",
|
||||
"data": [
|
||||
1
|
||||
],
|
||||
"schema_id": "minContains_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "some elements match, valid minContains",
|
||||
"data": [
|
||||
1,
|
||||
2
|
||||
],
|
||||
"schema_id": "minContains_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "all elements match, valid minContains",
|
||||
"data": [
|
||||
1,
|
||||
1
|
||||
],
|
||||
"schema_id": "minContains_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "minContains=2 with contains",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"contains": {
|
||||
"const": 1
|
||||
},
|
||||
"minContains": 2,
|
||||
"extensible": true,
|
||||
"$id": "minContains_2_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "empty data",
|
||||
"data": [],
|
||||
"schema_id": "minContains_2_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "all elements match, invalid minContains",
|
||||
"data": [
|
||||
1
|
||||
],
|
||||
"schema_id": "minContains_2_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "some elements match, invalid minContains",
|
||||
"data": [
|
||||
1,
|
||||
2
|
||||
],
|
||||
"schema_id": "minContains_2_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "all elements match, valid minContains (exactly as needed)",
|
||||
"data": [
|
||||
1,
|
||||
1
|
||||
],
|
||||
"schema_id": "minContains_2_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "all elements match, valid minContains (more than needed)",
|
||||
"data": [
|
||||
1,
|
||||
1,
|
||||
1
|
||||
],
|
||||
"schema_id": "minContains_2_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "some elements match, valid minContains",
|
||||
"data": [
|
||||
1,
|
||||
2,
|
||||
1
|
||||
],
|
||||
"schema_id": "minContains_2_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "minContains=2 with contains with a decimal value",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"contains": {
|
||||
"const": 1
|
||||
},
|
||||
"minContains": 2,
|
||||
"extensible": true,
|
||||
"$id": "minContains_3_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "one element matches, invalid minContains",
|
||||
"data": [
|
||||
1
|
||||
],
|
||||
"schema_id": "minContains_3_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "both elements match, valid minContains",
|
||||
"data": [
|
||||
1,
|
||||
1
|
||||
],
|
||||
"schema_id": "minContains_3_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "maxContains = minContains",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"contains": {
|
||||
"const": 1
|
||||
},
|
||||
"maxContains": 2,
|
||||
"minContains": 2,
|
||||
"extensible": true,
|
||||
"$id": "minContains_4_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "empty data",
|
||||
"data": [],
|
||||
"schema_id": "minContains_4_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "all elements match, invalid minContains",
|
||||
"data": [
|
||||
1
|
||||
],
|
||||
"schema_id": "minContains_4_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "all elements match, invalid maxContains",
|
||||
"data": [
|
||||
1,
|
||||
1,
|
||||
1
|
||||
],
|
||||
"schema_id": "minContains_4_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "all elements match, valid maxContains and minContains",
|
||||
"data": [
|
||||
1,
|
||||
1
|
||||
],
|
||||
"schema_id": "minContains_4_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "maxContains < minContains",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"contains": {
|
||||
"const": 1
|
||||
},
|
||||
"maxContains": 1,
|
||||
"minContains": 3,
|
||||
"extensible": true,
|
||||
"$id": "minContains_5_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "empty data",
|
||||
"data": [],
|
||||
"schema_id": "minContains_5_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "invalid minContains",
|
||||
"data": [
|
||||
1
|
||||
],
|
||||
"schema_id": "minContains_5_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "invalid maxContains",
|
||||
"data": [
|
||||
1,
|
||||
1,
|
||||
1
|
||||
],
|
||||
"schema_id": "minContains_5_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "invalid maxContains and minContains",
|
||||
"data": [
|
||||
1,
|
||||
1
|
||||
],
|
||||
"schema_id": "minContains_5_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "minContains = 0",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"contains": {
|
||||
"const": 1
|
||||
},
|
||||
"minContains": 0,
|
||||
"extensible": true,
|
||||
"$id": "minContains_6_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "empty data",
|
||||
"data": [],
|
||||
"schema_id": "minContains_6_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "minContains = 0 makes contains always pass",
|
||||
"data": [
|
||||
2
|
||||
],
|
||||
"schema_id": "minContains_6_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "minContains = 0 with maxContains",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"contains": {
|
||||
"const": 1
|
||||
},
|
||||
"minContains": 0,
|
||||
"maxContains": 1,
|
||||
"extensible": true,
|
||||
"$id": "minContains_7_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "empty data",
|
||||
"data": [],
|
||||
"schema_id": "minContains_7_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "not more than maxContains",
|
||||
"data": [
|
||||
1
|
||||
],
|
||||
"schema_id": "minContains_7_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "too many",
|
||||
"data": [
|
||||
1,
|
||||
1
|
||||
],
|
||||
"schema_id": "minContains_7_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "extensible: true allows non-matching items in minContains",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"contains": {
|
||||
"const": 1
|
||||
},
|
||||
"minContains": 1,
|
||||
"extensible": true,
|
||||
"$id": "minContains_8_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "extra items disregarded for minContains",
|
||||
"data": [
|
||||
1,
|
||||
2
|
||||
],
|
||||
"schema_id": "minContains_8_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
117
fixtures/minItems.json
Normal file
117
fixtures/minItems.json
Normal file
@ -0,0 +1,117 @@
|
||||
[
|
||||
{
|
||||
"description": "minItems validation",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"minItems": 1,
|
||||
"extensible": true,
|
||||
"$id": "minItems_0_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "longer is valid",
|
||||
"data": [
|
||||
1,
|
||||
2
|
||||
],
|
||||
"schema_id": "minItems_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "exact length is valid",
|
||||
"data": [
|
||||
1
|
||||
],
|
||||
"schema_id": "minItems_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "too short is invalid",
|
||||
"data": [],
|
||||
"schema_id": "minItems_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "ignores non-arrays",
|
||||
"data": "",
|
||||
"schema_id": "minItems_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "minItems validation with a decimal",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"minItems": 1,
|
||||
"extensible": true,
|
||||
"$id": "minItems_1_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "longer is valid",
|
||||
"data": [
|
||||
1,
|
||||
2
|
||||
],
|
||||
"schema_id": "minItems_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "too short is invalid",
|
||||
"data": [],
|
||||
"schema_id": "minItems_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "extensible: true allows extra items in minItems",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"minItems": 1,
|
||||
"extensible": true,
|
||||
"$id": "minItems_2_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "extra item counted towards minItems",
|
||||
"data": [
|
||||
1
|
||||
],
|
||||
"schema_id": "minItems_2_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
91
fixtures/minLength.json
Normal file
91
fixtures/minLength.json
Normal file
@ -0,0 +1,91 @@
|
||||
[
|
||||
{
|
||||
"description": "minLength validation",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"minLength": 2,
|
||||
"$id": "minLength_0_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "longer is valid",
|
||||
"data": "foo",
|
||||
"schema_id": "minLength_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "exact length is valid",
|
||||
"data": "fo",
|
||||
"schema_id": "minLength_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "too short is invalid",
|
||||
"data": "f",
|
||||
"schema_id": "minLength_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "ignores non-strings",
|
||||
"data": 1,
|
||||
"schema_id": "minLength_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "one grapheme is not long enough",
|
||||
"data": "💩",
|
||||
"schema_id": "minLength_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "minLength validation with a decimal",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"minLength": 2,
|
||||
"$id": "minLength_1_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "longer is valid",
|
||||
"data": "foo",
|
||||
"schema_id": "minLength_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "too short is invalid",
|
||||
"data": "f",
|
||||
"schema_id": "minLength_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
135
fixtures/minProperties.json
Normal file
135
fixtures/minProperties.json
Normal file
@ -0,0 +1,135 @@
|
||||
[
|
||||
{
|
||||
"description": "minProperties validation",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"minProperties": 1,
|
||||
"extensible": true,
|
||||
"$id": "minProperties_0_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "longer is valid",
|
||||
"data": {
|
||||
"foo": 1,
|
||||
"bar": 2
|
||||
},
|
||||
"schema_id": "minProperties_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "exact length is valid",
|
||||
"data": {
|
||||
"foo": 1
|
||||
},
|
||||
"schema_id": "minProperties_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "too short is invalid",
|
||||
"data": {},
|
||||
"schema_id": "minProperties_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "ignores arrays",
|
||||
"data": [],
|
||||
"schema_id": "minProperties_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "ignores strings",
|
||||
"data": "",
|
||||
"schema_id": "minProperties_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "ignores other non-objects",
|
||||
"data": 12,
|
||||
"schema_id": "minProperties_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "minProperties validation with a decimal",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"minProperties": 1,
|
||||
"extensible": true,
|
||||
"$id": "minProperties_1_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "longer is valid",
|
||||
"data": {
|
||||
"foo": 1,
|
||||
"bar": 2
|
||||
},
|
||||
"schema_id": "minProperties_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "too short is invalid",
|
||||
"data": {},
|
||||
"schema_id": "minProperties_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "extensible: true allows extra properties in minProperties",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"minProperties": 1,
|
||||
"extensible": true,
|
||||
"$id": "minProperties_2_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "extra property counts towards minProperties",
|
||||
"data": {
|
||||
"foo": 1
|
||||
},
|
||||
"schema_id": "minProperties_2_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
127
fixtures/minimum.json
Normal file
127
fixtures/minimum.json
Normal file
@ -0,0 +1,127 @@
|
||||
[
|
||||
{
|
||||
"description": "minimum validation",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"minimum": 1.1,
|
||||
"$id": "minimum_0_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "above the minimum is valid",
|
||||
"data": 2.6,
|
||||
"schema_id": "minimum_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "boundary point is valid",
|
||||
"data": 1.1,
|
||||
"schema_id": "minimum_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "below the minimum is invalid",
|
||||
"data": 0.6,
|
||||
"schema_id": "minimum_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "ignores non-numbers",
|
||||
"data": "x",
|
||||
"schema_id": "minimum_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "minimum validation with signed integer",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"minimum": -2,
|
||||
"$id": "minimum_1_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "negative above the minimum is valid",
|
||||
"data": -1,
|
||||
"schema_id": "minimum_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "positive above the minimum is valid",
|
||||
"data": 0,
|
||||
"schema_id": "minimum_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "boundary point is valid",
|
||||
"data": -2,
|
||||
"schema_id": "minimum_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "boundary point with float is valid",
|
||||
"data": -2,
|
||||
"schema_id": "minimum_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "float below the minimum is invalid",
|
||||
"data": -2.0001,
|
||||
"schema_id": "minimum_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "int below the minimum is invalid",
|
||||
"data": -3,
|
||||
"schema_id": "minimum_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "ignores non-numbers",
|
||||
"data": "x",
|
||||
"schema_id": "minimum_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
136
fixtures/multipleOf.json
Normal file
136
fixtures/multipleOf.json
Normal file
@ -0,0 +1,136 @@
|
||||
[
|
||||
{
|
||||
"description": "by int",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"multipleOf": 2,
|
||||
"$id": "multipleOf_0_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "int by int",
|
||||
"data": 10,
|
||||
"schema_id": "multipleOf_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "int by int fail",
|
||||
"data": 7,
|
||||
"schema_id": "multipleOf_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "ignores non-numbers",
|
||||
"data": "foo",
|
||||
"schema_id": "multipleOf_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "by number",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"multipleOf": 1.5,
|
||||
"$id": "multipleOf_1_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "zero is multiple of anything",
|
||||
"data": 0,
|
||||
"schema_id": "multipleOf_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "4.5 is multiple of 1.5",
|
||||
"data": 4.5,
|
||||
"schema_id": "multipleOf_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "35 is not multiple of 1.5",
|
||||
"data": 35,
|
||||
"schema_id": "multipleOf_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "by small number",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"multipleOf": 0.0001,
|
||||
"$id": "multipleOf_2_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "0.0075 is multiple of 0.0001",
|
||||
"data": 0.0075,
|
||||
"schema_id": "multipleOf_2_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "0.00751 is not multiple of 0.0001",
|
||||
"data": 0.00751,
|
||||
"schema_id": "multipleOf_2_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "small multiple of large integer",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"type": "integer",
|
||||
"multipleOf": 1e-8,
|
||||
"$id": "multipleOf_3_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "any integer is a multiple of 1e-8",
|
||||
"data": 12391239123,
|
||||
"schema_id": "multipleOf_3_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
618
fixtures/not.json
Normal file
618
fixtures/not.json
Normal file
@ -0,0 +1,618 @@
|
||||
[
|
||||
{
|
||||
"description": "not",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"not": {
|
||||
"type": "integer"
|
||||
},
|
||||
"$id": "not_0_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "allowed",
|
||||
"data": "foo",
|
||||
"schema_id": "not_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "disallowed",
|
||||
"data": 1,
|
||||
"schema_id": "not_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "not multiple types",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"not": {
|
||||
"type": [
|
||||
"integer",
|
||||
"boolean"
|
||||
]
|
||||
},
|
||||
"$id": "not_1_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "valid",
|
||||
"data": "foo",
|
||||
"schema_id": "not_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "mismatch",
|
||||
"data": 1,
|
||||
"schema_id": "not_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "other mismatch",
|
||||
"data": true,
|
||||
"schema_id": "not_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "not more complex schema",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"not": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"extensible": true,
|
||||
"$id": "not_2_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "match",
|
||||
"data": 1,
|
||||
"schema_id": "not_2_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "other match",
|
||||
"data": {
|
||||
"foo": 1
|
||||
},
|
||||
"schema_id": "not_2_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "mismatch",
|
||||
"data": {
|
||||
"foo": "bar"
|
||||
},
|
||||
"schema_id": "not_2_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "forbidden property",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"properties": {
|
||||
"foo": {
|
||||
"not": {}
|
||||
}
|
||||
},
|
||||
"$id": "not_3_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "property present",
|
||||
"data": {
|
||||
"foo": 1,
|
||||
"bar": 2
|
||||
},
|
||||
"schema_id": "not_3_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "empty object is valid",
|
||||
"data": {},
|
||||
"schema_id": "not_3_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "forbid everything with empty schema",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"not": {},
|
||||
"$id": "not_4_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "number is invalid",
|
||||
"data": 1,
|
||||
"schema_id": "not_4_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "string is invalid",
|
||||
"data": "foo",
|
||||
"schema_id": "not_4_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "boolean true is invalid",
|
||||
"data": true,
|
||||
"schema_id": "not_4_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "boolean false is invalid",
|
||||
"data": false,
|
||||
"schema_id": "not_4_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "null is invalid",
|
||||
"data": null,
|
||||
"schema_id": "not_4_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "object is invalid",
|
||||
"data": {
|
||||
"foo": "bar"
|
||||
},
|
||||
"schema_id": "not_4_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "empty object is invalid",
|
||||
"data": {},
|
||||
"schema_id": "not_4_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "array is invalid",
|
||||
"data": [
|
||||
"foo"
|
||||
],
|
||||
"schema_id": "not_4_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "empty array is invalid",
|
||||
"data": [],
|
||||
"schema_id": "not_4_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "forbid everything with boolean schema true",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"not": true,
|
||||
"$id": "not_5_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "number is invalid",
|
||||
"data": 1,
|
||||
"schema_id": "not_5_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "string is invalid",
|
||||
"data": "foo",
|
||||
"schema_id": "not_5_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "boolean true is invalid",
|
||||
"data": true,
|
||||
"schema_id": "not_5_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "boolean false is invalid",
|
||||
"data": false,
|
||||
"schema_id": "not_5_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "null is invalid",
|
||||
"data": null,
|
||||
"schema_id": "not_5_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "object is invalid",
|
||||
"data": {
|
||||
"foo": "bar"
|
||||
},
|
||||
"schema_id": "not_5_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "empty object is invalid",
|
||||
"data": {},
|
||||
"schema_id": "not_5_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "array is invalid",
|
||||
"data": [
|
||||
"foo"
|
||||
],
|
||||
"schema_id": "not_5_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "empty array is invalid",
|
||||
"data": [],
|
||||
"schema_id": "not_5_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "allow everything with boolean schema false",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"not": false,
|
||||
"extensible": true,
|
||||
"$id": "not_6_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "number is valid",
|
||||
"data": 1,
|
||||
"schema_id": "not_6_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "string is valid",
|
||||
"data": "foo",
|
||||
"schema_id": "not_6_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "boolean true is valid",
|
||||
"data": true,
|
||||
"schema_id": "not_6_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "boolean false is valid",
|
||||
"data": false,
|
||||
"schema_id": "not_6_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "null is valid",
|
||||
"data": null,
|
||||
"schema_id": "not_6_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "object is valid",
|
||||
"data": {
|
||||
"foo": "bar"
|
||||
},
|
||||
"schema_id": "not_6_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "empty object is valid",
|
||||
"data": {},
|
||||
"schema_id": "not_6_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "array is valid",
|
||||
"data": [
|
||||
"foo"
|
||||
],
|
||||
"schema_id": "not_6_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "empty array is valid",
|
||||
"data": [],
|
||||
"schema_id": "not_6_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "double negation",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"not": {
|
||||
"not": {}
|
||||
},
|
||||
"$id": "not_7_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "any value is valid",
|
||||
"data": "foo",
|
||||
"schema_id": "not_7_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "extensible: true allows extra properties in not",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"not": {
|
||||
"type": "integer"
|
||||
},
|
||||
"extensible": true,
|
||||
"$id": "not_8_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "extra property is valid (not integer matches)",
|
||||
"data": {
|
||||
"foo": 1
|
||||
},
|
||||
"schema_id": "not_8_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "extensible: false (default) forbids extra properties in not",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"not": {
|
||||
"type": "integer"
|
||||
},
|
||||
"$id": "not_9_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "extra property is invalid due to strictness",
|
||||
"data": {
|
||||
"foo": 1
|
||||
},
|
||||
"schema_id": "not_9_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "property next to not (extensible: true)",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"properties": {
|
||||
"bar": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"not": {
|
||||
"type": "integer"
|
||||
},
|
||||
"extensible": true,
|
||||
"$id": "not_10_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "extra property allowed",
|
||||
"data": {
|
||||
"bar": "baz",
|
||||
"foo": 1
|
||||
},
|
||||
"schema_id": "not_10_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "property next to not (extensible: false)",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"properties": {
|
||||
"bar": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"not": {
|
||||
"type": "integer"
|
||||
},
|
||||
"$id": "not_11_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "extra property forbidden",
|
||||
"data": {
|
||||
"bar": "baz",
|
||||
"foo": 1
|
||||
},
|
||||
"schema_id": "not_11_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "defined property allowed",
|
||||
"data": {
|
||||
"bar": "baz"
|
||||
},
|
||||
"schema_id": "not_11_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
670
fixtures/oneOf.json
Normal file
670
fixtures/oneOf.json
Normal file
@ -0,0 +1,670 @@
|
||||
[
|
||||
{
|
||||
"description": "oneOf",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"minimum": 2
|
||||
}
|
||||
],
|
||||
"$id": "oneOf_0_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "first oneOf valid",
|
||||
"data": 1,
|
||||
"schema_id": "oneOf_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "second oneOf valid",
|
||||
"data": 2.5,
|
||||
"schema_id": "oneOf_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "both oneOf valid",
|
||||
"data": 3,
|
||||
"schema_id": "oneOf_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "neither oneOf valid",
|
||||
"data": 1.5,
|
||||
"schema_id": "oneOf_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "oneOf with base schema",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"type": "string",
|
||||
"oneOf": [
|
||||
{
|
||||
"minLength": 2
|
||||
},
|
||||
{
|
||||
"maxLength": 4
|
||||
}
|
||||
],
|
||||
"$id": "oneOf_1_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "mismatch base schema",
|
||||
"data": 3,
|
||||
"schema_id": "oneOf_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "one oneOf valid",
|
||||
"data": "foobar",
|
||||
"schema_id": "oneOf_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "both oneOf valid",
|
||||
"data": "foo",
|
||||
"schema_id": "oneOf_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "oneOf with boolean schemas, all true",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"oneOf": [
|
||||
true,
|
||||
true,
|
||||
true
|
||||
],
|
||||
"$id": "oneOf_2_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "any value is invalid",
|
||||
"data": "foo",
|
||||
"schema_id": "oneOf_2_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "oneOf with boolean schemas, one true",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"oneOf": [
|
||||
true,
|
||||
false,
|
||||
false
|
||||
],
|
||||
"$id": "oneOf_3_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "any value is valid",
|
||||
"data": "foo",
|
||||
"schema_id": "oneOf_3_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "oneOf with boolean schemas, more than one true",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"oneOf": [
|
||||
true,
|
||||
true,
|
||||
false
|
||||
],
|
||||
"$id": "oneOf_4_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "any value is invalid",
|
||||
"data": "foo",
|
||||
"schema_id": "oneOf_4_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "oneOf with boolean schemas, all false",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"oneOf": [
|
||||
false,
|
||||
false,
|
||||
false
|
||||
],
|
||||
"$id": "oneOf_5_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "any value is invalid",
|
||||
"data": "foo",
|
||||
"schema_id": "oneOf_5_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "oneOf complex types",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"oneOf": [
|
||||
{
|
||||
"properties": {
|
||||
"bar": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"bar"
|
||||
]
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"foo"
|
||||
]
|
||||
}
|
||||
],
|
||||
"$id": "oneOf_6_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "first oneOf valid (complex)",
|
||||
"data": {
|
||||
"bar": 2
|
||||
},
|
||||
"schema_id": "oneOf_6_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "second oneOf valid (complex)",
|
||||
"data": {
|
||||
"foo": "baz"
|
||||
},
|
||||
"schema_id": "oneOf_6_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "both oneOf valid (complex)",
|
||||
"data": {
|
||||
"foo": "baz",
|
||||
"bar": 2
|
||||
},
|
||||
"schema_id": "oneOf_6_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "neither oneOf valid (complex)",
|
||||
"data": {
|
||||
"foo": 2,
|
||||
"bar": "quux"
|
||||
},
|
||||
"schema_id": "oneOf_6_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "oneOf with empty schema",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "number"
|
||||
},
|
||||
{}
|
||||
],
|
||||
"$id": "oneOf_7_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "one valid - valid",
|
||||
"data": "foo",
|
||||
"schema_id": "oneOf_7_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "both valid - invalid",
|
||||
"data": 123,
|
||||
"schema_id": "oneOf_7_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "oneOf with required",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"foo": true,
|
||||
"bar": true,
|
||||
"baz": true
|
||||
},
|
||||
"oneOf": [
|
||||
{
|
||||
"required": [
|
||||
"foo",
|
||||
"bar"
|
||||
]
|
||||
},
|
||||
{
|
||||
"required": [
|
||||
"foo",
|
||||
"baz"
|
||||
]
|
||||
}
|
||||
],
|
||||
"$id": "oneOf_8_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "both invalid - invalid",
|
||||
"data": {
|
||||
"bar": 2
|
||||
},
|
||||
"schema_id": "oneOf_8_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "first valid - valid",
|
||||
"data": {
|
||||
"foo": 1,
|
||||
"bar": 2
|
||||
},
|
||||
"schema_id": "oneOf_8_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "second valid - valid",
|
||||
"data": {
|
||||
"foo": 1,
|
||||
"baz": 3
|
||||
},
|
||||
"schema_id": "oneOf_8_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "both valid - invalid",
|
||||
"data": {
|
||||
"foo": 1,
|
||||
"bar": 2,
|
||||
"baz": 3
|
||||
},
|
||||
"schema_id": "oneOf_8_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "extra property invalid (strict)",
|
||||
"data": {
|
||||
"foo": 1,
|
||||
"bar": 2,
|
||||
"extra": 3
|
||||
},
|
||||
"schema_id": "oneOf_8_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "oneOf with required (extensible)",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"type": "object",
|
||||
"extensible": true,
|
||||
"oneOf": [
|
||||
{
|
||||
"required": [
|
||||
"foo",
|
||||
"bar"
|
||||
]
|
||||
},
|
||||
{
|
||||
"required": [
|
||||
"foo",
|
||||
"baz"
|
||||
]
|
||||
}
|
||||
],
|
||||
"$id": "oneOf_9_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "both invalid - invalid",
|
||||
"data": {
|
||||
"bar": 2
|
||||
},
|
||||
"schema_id": "oneOf_9_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "first valid - valid",
|
||||
"data": {
|
||||
"foo": 1,
|
||||
"bar": 2
|
||||
},
|
||||
"schema_id": "oneOf_9_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "second valid - valid",
|
||||
"data": {
|
||||
"foo": 1,
|
||||
"baz": 3
|
||||
},
|
||||
"schema_id": "oneOf_9_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "both valid - invalid",
|
||||
"data": {
|
||||
"foo": 1,
|
||||
"bar": 2,
|
||||
"baz": 3
|
||||
},
|
||||
"schema_id": "oneOf_9_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "extra properties are valid (extensible)",
|
||||
"data": {
|
||||
"foo": 1,
|
||||
"bar": 2,
|
||||
"extra": "value"
|
||||
},
|
||||
"schema_id": "oneOf_9_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "oneOf with missing optional property",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"oneOf": [
|
||||
{
|
||||
"properties": {
|
||||
"bar": true,
|
||||
"baz": true
|
||||
},
|
||||
"required": [
|
||||
"bar"
|
||||
]
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"foo": true
|
||||
},
|
||||
"required": [
|
||||
"foo"
|
||||
]
|
||||
}
|
||||
],
|
||||
"$id": "oneOf_10_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "first oneOf valid",
|
||||
"data": {
|
||||
"bar": 8
|
||||
},
|
||||
"schema_id": "oneOf_10_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "second oneOf valid",
|
||||
"data": {
|
||||
"foo": "foo"
|
||||
},
|
||||
"schema_id": "oneOf_10_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "both oneOf valid",
|
||||
"data": {
|
||||
"foo": "foo",
|
||||
"bar": 8
|
||||
},
|
||||
"schema_id": "oneOf_10_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "neither oneOf valid",
|
||||
"data": {
|
||||
"baz": "quux"
|
||||
},
|
||||
"schema_id": "oneOf_10_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "nested oneOf, to check validation semantics",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"oneOf": [
|
||||
{
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"$id": "oneOf_11_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "null is valid",
|
||||
"data": null,
|
||||
"schema_id": "oneOf_11_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "anything non-null is invalid",
|
||||
"data": 123,
|
||||
"schema_id": "oneOf_11_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "extensible: true allows extra properties in oneOf",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"oneOf": [
|
||||
{
|
||||
"properties": {
|
||||
"bar": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"bar"
|
||||
]
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"foo"
|
||||
]
|
||||
}
|
||||
],
|
||||
"extensible": true,
|
||||
"$id": "oneOf_12_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "extra property is valid (matches first option)",
|
||||
"data": {
|
||||
"bar": 2,
|
||||
"extra": "prop"
|
||||
},
|
||||
"schema_id": "oneOf_12_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
109
fixtures/pattern.json
Normal file
109
fixtures/pattern.json
Normal file
@ -0,0 +1,109 @@
|
||||
[
|
||||
{
|
||||
"description": "pattern validation",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"pattern": "^a*$",
|
||||
"$id": "pattern_0_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "a matching pattern is valid",
|
||||
"data": "aaa",
|
||||
"schema_id": "pattern_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "a non-matching pattern is invalid",
|
||||
"data": "abc",
|
||||
"schema_id": "pattern_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "ignores booleans",
|
||||
"data": true,
|
||||
"schema_id": "pattern_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "ignores integers",
|
||||
"data": 123,
|
||||
"schema_id": "pattern_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "ignores floats",
|
||||
"data": 1,
|
||||
"schema_id": "pattern_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "ignores objects",
|
||||
"data": {},
|
||||
"schema_id": "pattern_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "ignores arrays",
|
||||
"data": [],
|
||||
"schema_id": "pattern_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "ignores null",
|
||||
"data": null,
|
||||
"schema_id": "pattern_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "pattern is not anchored",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"pattern": "a+",
|
||||
"$id": "pattern_1_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "matches a substring",
|
||||
"data": "xxaayy",
|
||||
"schema_id": "pattern_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
399
fixtures/patternProperties.json
Normal file
399
fixtures/patternProperties.json
Normal file
@ -0,0 +1,399 @@
|
||||
[
|
||||
{
|
||||
"description": "patternProperties validates properties matching a regex",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"patternProperties": {
|
||||
"f.*o": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"items": {},
|
||||
"$id": "patternProperties_0_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "a single valid match is valid",
|
||||
"data": {
|
||||
"foo": 1
|
||||
},
|
||||
"schema_id": "patternProperties_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "multiple valid matches is valid",
|
||||
"data": {
|
||||
"foo": 1,
|
||||
"foooooo": 2
|
||||
},
|
||||
"schema_id": "patternProperties_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "a single invalid match is invalid",
|
||||
"data": {
|
||||
"foo": "bar",
|
||||
"fooooo": 2
|
||||
},
|
||||
"schema_id": "patternProperties_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "multiple invalid matches is invalid",
|
||||
"data": {
|
||||
"foo": "bar",
|
||||
"foooooo": "baz"
|
||||
},
|
||||
"schema_id": "patternProperties_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "ignores arrays",
|
||||
"data": [
|
||||
"foo"
|
||||
],
|
||||
"schema_id": "patternProperties_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "ignores strings",
|
||||
"data": "foo",
|
||||
"schema_id": "patternProperties_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "ignores other non-objects",
|
||||
"data": 12,
|
||||
"schema_id": "patternProperties_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "extra property not matching pattern is INVALID (strict by default)",
|
||||
"data": {
|
||||
"foo": 1,
|
||||
"extra": 2
|
||||
},
|
||||
"schema_id": "patternProperties_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "multiple simultaneous patternProperties are validated",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"patternProperties": {
|
||||
"a*": {
|
||||
"type": "integer"
|
||||
},
|
||||
"aaa*": {
|
||||
"maximum": 20
|
||||
}
|
||||
},
|
||||
"$id": "patternProperties_1_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "a single valid match is valid",
|
||||
"data": {
|
||||
"a": 21
|
||||
},
|
||||
"schema_id": "patternProperties_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "a simultaneous match is valid",
|
||||
"data": {
|
||||
"aaaa": 18
|
||||
},
|
||||
"schema_id": "patternProperties_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "multiple matches is valid",
|
||||
"data": {
|
||||
"a": 21,
|
||||
"aaaa": 18
|
||||
},
|
||||
"schema_id": "patternProperties_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "an invalid due to one is invalid",
|
||||
"data": {
|
||||
"a": "bar"
|
||||
},
|
||||
"schema_id": "patternProperties_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "an invalid due to the other is invalid",
|
||||
"data": {
|
||||
"aaaa": 31
|
||||
},
|
||||
"schema_id": "patternProperties_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "an invalid due to both is invalid",
|
||||
"data": {
|
||||
"aaa": "foo",
|
||||
"aaaa": 31
|
||||
},
|
||||
"schema_id": "patternProperties_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "regexes are not anchored by default and are case sensitive",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"patternProperties": {
|
||||
"[0-9]{2,}": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"X_": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"extensible": true,
|
||||
"$id": "patternProperties_2_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "non recognized members are ignored",
|
||||
"data": {
|
||||
"answer 1": "42"
|
||||
},
|
||||
"schema_id": "patternProperties_2_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "recognized members are accounted for",
|
||||
"data": {
|
||||
"a31b": null
|
||||
},
|
||||
"schema_id": "patternProperties_2_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "regexes are case sensitive",
|
||||
"data": {
|
||||
"a_x_3": 3
|
||||
},
|
||||
"schema_id": "patternProperties_2_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "regexes are case sensitive, 2",
|
||||
"data": {
|
||||
"a_X_3": 3
|
||||
},
|
||||
"schema_id": "patternProperties_2_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "patternProperties with boolean schemas",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"patternProperties": {
|
||||
"f.*": true,
|
||||
"b.*": false
|
||||
},
|
||||
"$id": "patternProperties_3_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "object with property matching schema true is valid",
|
||||
"data": {
|
||||
"foo": 1
|
||||
},
|
||||
"schema_id": "patternProperties_3_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "object with property matching schema false is invalid",
|
||||
"data": {
|
||||
"bar": 2
|
||||
},
|
||||
"schema_id": "patternProperties_3_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "object with both properties is invalid",
|
||||
"data": {
|
||||
"foo": 1,
|
||||
"bar": 2
|
||||
},
|
||||
"schema_id": "patternProperties_3_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "object with a property matching both true and false is invalid",
|
||||
"data": {
|
||||
"foobar": 1
|
||||
},
|
||||
"schema_id": "patternProperties_3_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "empty object is valid",
|
||||
"data": {},
|
||||
"schema_id": "patternProperties_3_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "patternProperties with null valued instance properties",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"patternProperties": {
|
||||
"^.*bar$": {
|
||||
"type": "null"
|
||||
}
|
||||
},
|
||||
"$id": "patternProperties_4_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "allows null values",
|
||||
"data": {
|
||||
"foobar": null
|
||||
},
|
||||
"schema_id": "patternProperties_4_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "extensible: true allows extra properties NOT matching pattern",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"patternProperties": {
|
||||
"f.*o": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"extensible": true,
|
||||
"$id": "patternProperties_5_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "extra property not matching pattern is valid",
|
||||
"data": {
|
||||
"bar": 1
|
||||
},
|
||||
"schema_id": "patternProperties_5_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "property matching pattern MUST still be valid",
|
||||
"data": {
|
||||
"foo": "invalid string"
|
||||
},
|
||||
"schema_id": "patternProperties_5_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
229
fixtures/prefixItems.json
Normal file
229
fixtures/prefixItems.json
Normal file
@ -0,0 +1,229 @@
|
||||
[
|
||||
{
|
||||
"description": "a schema given for prefixItems",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"prefixItems": [
|
||||
{
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"$id": "prefixItems_0_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "correct types",
|
||||
"data": [
|
||||
1,
|
||||
"foo"
|
||||
],
|
||||
"schema_id": "prefixItems_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "wrong types",
|
||||
"data": [
|
||||
"foo",
|
||||
1
|
||||
],
|
||||
"schema_id": "prefixItems_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "incomplete array of items",
|
||||
"data": [
|
||||
1
|
||||
],
|
||||
"schema_id": "prefixItems_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "array with additional items (invalid due to strictness)",
|
||||
"data": [
|
||||
1,
|
||||
"foo",
|
||||
true
|
||||
],
|
||||
"schema_id": "prefixItems_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "empty array",
|
||||
"data": [],
|
||||
"schema_id": "prefixItems_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "JavaScript pseudo-array is valid (invalid due to strict object validation)",
|
||||
"data": {
|
||||
"0": "invalid",
|
||||
"1": "valid",
|
||||
"length": 2
|
||||
},
|
||||
"schema_id": "prefixItems_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "prefixItems with boolean schemas",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"prefixItems": [
|
||||
true,
|
||||
false
|
||||
],
|
||||
"$id": "prefixItems_1_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "array with one item is valid",
|
||||
"data": [
|
||||
1
|
||||
],
|
||||
"schema_id": "prefixItems_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "array with two items is invalid",
|
||||
"data": [
|
||||
1,
|
||||
"foo"
|
||||
],
|
||||
"schema_id": "prefixItems_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "empty array is valid",
|
||||
"data": [],
|
||||
"schema_id": "prefixItems_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "additional items are allowed by default",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"prefixItems": [
|
||||
{
|
||||
"type": "integer"
|
||||
}
|
||||
],
|
||||
"extensible": true,
|
||||
"$id": "prefixItems_2_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "only the first item is validated",
|
||||
"data": [
|
||||
1,
|
||||
"foo",
|
||||
false
|
||||
],
|
||||
"schema_id": "prefixItems_2_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "prefixItems with null instance elements",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"prefixItems": [
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"$id": "prefixItems_3_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "allows null elements",
|
||||
"data": [
|
||||
null
|
||||
],
|
||||
"schema_id": "prefixItems_3_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "extensible: true allows extra items with prefixItems",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"prefixItems": [
|
||||
{
|
||||
"type": "integer"
|
||||
}
|
||||
],
|
||||
"extensible": true,
|
||||
"$id": "prefixItems_4_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "extra item is valid",
|
||||
"data": [
|
||||
1,
|
||||
"foo"
|
||||
],
|
||||
"schema_id": "prefixItems_4_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
627
fixtures/properties.json
Normal file
627
fixtures/properties.json
Normal file
@ -0,0 +1,627 @@
|
||||
[
|
||||
{
|
||||
"description": "object properties validation",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "integer"
|
||||
},
|
||||
"bar": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"$id": "properties_0_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "both properties present and valid is valid",
|
||||
"data": {
|
||||
"foo": 1,
|
||||
"bar": "baz"
|
||||
},
|
||||
"schema_id": "properties_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "one property invalid is invalid",
|
||||
"data": {
|
||||
"foo": 1,
|
||||
"bar": {}
|
||||
},
|
||||
"schema_id": "properties_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "both properties invalid is invalid",
|
||||
"data": {
|
||||
"foo": [],
|
||||
"bar": {}
|
||||
},
|
||||
"schema_id": "properties_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "doesn't invalidate other properties",
|
||||
"data": {},
|
||||
"schema_id": "properties_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "ignores arrays",
|
||||
"data": [],
|
||||
"schema_id": "properties_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "ignores other non-objects",
|
||||
"data": 12,
|
||||
"schema_id": "properties_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "properties with boolean schema",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"properties": {
|
||||
"foo": true,
|
||||
"bar": false
|
||||
},
|
||||
"$id": "properties_1_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "no property present is valid",
|
||||
"data": {},
|
||||
"schema_id": "properties_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "only 'true' property present is valid",
|
||||
"data": {
|
||||
"foo": 1
|
||||
},
|
||||
"schema_id": "properties_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "only 'false' property present is invalid",
|
||||
"data": {
|
||||
"bar": 2
|
||||
},
|
||||
"schema_id": "properties_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "both properties present is invalid",
|
||||
"data": {
|
||||
"foo": 1,
|
||||
"bar": 2
|
||||
},
|
||||
"schema_id": "properties_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "properties with escaped characters",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"$id": "properties_2_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "object with all numbers is valid",
|
||||
"data": {
|
||||
"foo\nbar": 1,
|
||||
"foo\"bar": 1,
|
||||
"foo\\bar": 1,
|
||||
"foo\rbar": 1,
|
||||
"foo\tbar": 1,
|
||||
"foo\fbar": 1
|
||||
},
|
||||
"schema_id": "properties_2_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "object with strings is invalid",
|
||||
"data": {
|
||||
"foo\nbar": "1",
|
||||
"foo\"bar": "1",
|
||||
"foo\\bar": "1",
|
||||
"foo\rbar": "1",
|
||||
"foo\tbar": "1",
|
||||
"foo\fbar": "1"
|
||||
},
|
||||
"schema_id": "properties_2_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "properties with null valued instance properties",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "null"
|
||||
}
|
||||
},
|
||||
"$id": "properties_3_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "allows null values",
|
||||
"data": {
|
||||
"foo": null
|
||||
},
|
||||
"schema_id": "properties_3_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"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": {
|
||||
"__proto__": {
|
||||
"type": "number"
|
||||
},
|
||||
"toString": {
|
||||
"properties": {
|
||||
"length": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"constructor": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"$id": "properties_4_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "ignores arrays",
|
||||
"data": [],
|
||||
"schema_id": "properties_4_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "ignores other non-objects",
|
||||
"data": 12,
|
||||
"schema_id": "properties_4_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "none of the properties mentioned",
|
||||
"data": {},
|
||||
"schema_id": "properties_4_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "__proto__ not valid",
|
||||
"data": {
|
||||
"__proto__": "foo"
|
||||
},
|
||||
"schema_id": "properties_4_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "toString not valid",
|
||||
"data": {
|
||||
"toString": {
|
||||
"length": 37
|
||||
}
|
||||
},
|
||||
"schema_id": "properties_4_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "constructor not valid",
|
||||
"data": {
|
||||
"constructor": {
|
||||
"length": 37
|
||||
}
|
||||
},
|
||||
"schema_id": "properties_4_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "all present and valid",
|
||||
"data": {
|
||||
"__proto__": 12,
|
||||
"toString": {
|
||||
"length": "foo"
|
||||
},
|
||||
"constructor": 37
|
||||
},
|
||||
"schema_id": "properties_4_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "extensible: true allows extra properties",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"extensible": true,
|
||||
"$id": "properties_5_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "extra property is valid",
|
||||
"data": {
|
||||
"foo": 1,
|
||||
"bar": "baz"
|
||||
},
|
||||
"schema_id": "properties_5_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "strict by default: extra properties invalid",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"$id": "properties_6_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "extra property is invalid",
|
||||
"data": {
|
||||
"foo": "bar",
|
||||
"extra": 1
|
||||
},
|
||||
"schema_id": "properties_6_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "inheritance: nested object inherits strictness from strict parent",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"properties": {
|
||||
"nested": {
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"$id": "properties_7_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "nested extra property is invalid",
|
||||
"data": {
|
||||
"nested": {
|
||||
"foo": "bar",
|
||||
"extra": 1
|
||||
}
|
||||
},
|
||||
"schema_id": "properties_7_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "override: nested object allows extra properties if extensible: true",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"properties": {
|
||||
"nested": {
|
||||
"extensible": true,
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"$id": "properties_8_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "nested extra property is valid",
|
||||
"data": {
|
||||
"nested": {
|
||||
"foo": "bar",
|
||||
"extra": 1
|
||||
}
|
||||
},
|
||||
"schema_id": "properties_8_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "inheritance: nested object inherits looseness from loose parent",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"extensible": true,
|
||||
"properties": {
|
||||
"nested": {
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"$id": "properties_9_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "nested extra property is valid",
|
||||
"data": {
|
||||
"nested": {
|
||||
"foo": "bar",
|
||||
"extra": 1
|
||||
}
|
||||
},
|
||||
"schema_id": "properties_9_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "override: nested object enforces strictness if extensible: false",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"extensible": true,
|
||||
"properties": {
|
||||
"nested": {
|
||||
"extensible": false,
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"$id": "properties_10_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "nested extra property is invalid",
|
||||
"data": {
|
||||
"nested": {
|
||||
"foo": "bar",
|
||||
"extra": 1
|
||||
}
|
||||
},
|
||||
"schema_id": "properties_10_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "arrays: inline items inherit strictness from strict parent",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"properties": {
|
||||
"list": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"$id": "properties_11_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "array item with extra property is invalid (strict parent)",
|
||||
"data": {
|
||||
"list": [
|
||||
{
|
||||
"foo": "bar",
|
||||
"extra": 1
|
||||
}
|
||||
]
|
||||
},
|
||||
"schema_id": "properties_11_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "arrays: inline items inherit looseness from loose parent",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"extensible": true,
|
||||
"properties": {
|
||||
"list": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"$id": "properties_12_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "array item with extra property is valid (loose parent)",
|
||||
"data": {
|
||||
"list": [
|
||||
{
|
||||
"foo": "bar",
|
||||
"extra": 1
|
||||
}
|
||||
]
|
||||
},
|
||||
"schema_id": "properties_12_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
347
fixtures/propertyNames.json
Normal file
347
fixtures/propertyNames.json
Normal file
@ -0,0 +1,347 @@
|
||||
[
|
||||
{
|
||||
"description": "propertyNames validation",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"propertyNames": {
|
||||
"maxLength": 3
|
||||
},
|
||||
"extensible": true,
|
||||
"$id": "propertyNames_0_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "all property names valid",
|
||||
"data": {
|
||||
"f": {},
|
||||
"foo": {}
|
||||
},
|
||||
"schema_id": "propertyNames_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "some property names invalid",
|
||||
"data": {
|
||||
"foo": {},
|
||||
"foobar": {}
|
||||
},
|
||||
"schema_id": "propertyNames_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "object without properties is valid",
|
||||
"data": {},
|
||||
"schema_id": "propertyNames_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "ignores arrays",
|
||||
"data": [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4
|
||||
],
|
||||
"schema_id": "propertyNames_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "ignores strings",
|
||||
"data": "foobar",
|
||||
"schema_id": "propertyNames_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "ignores other non-objects",
|
||||
"data": 12,
|
||||
"schema_id": "propertyNames_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "propertyNames validation with pattern",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"propertyNames": {
|
||||
"pattern": "^a+$"
|
||||
},
|
||||
"extensible": true,
|
||||
"$id": "propertyNames_1_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "matching property names valid",
|
||||
"data": {
|
||||
"a": {},
|
||||
"aa": {},
|
||||
"aaa": {}
|
||||
},
|
||||
"schema_id": "propertyNames_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "non-matching property name is invalid",
|
||||
"data": {
|
||||
"aaA": {}
|
||||
},
|
||||
"schema_id": "propertyNames_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "object without properties is valid",
|
||||
"data": {},
|
||||
"schema_id": "propertyNames_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "propertyNames with boolean schema true",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"propertyNames": true,
|
||||
"extensible": true,
|
||||
"$id": "propertyNames_2_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "object with any properties is valid",
|
||||
"data": {
|
||||
"foo": 1
|
||||
},
|
||||
"schema_id": "propertyNames_2_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "empty object is valid",
|
||||
"data": {},
|
||||
"schema_id": "propertyNames_2_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "propertyNames with boolean schema false",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"propertyNames": false,
|
||||
"extensible": true,
|
||||
"$id": "propertyNames_3_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "object with any properties is invalid",
|
||||
"data": {
|
||||
"foo": 1
|
||||
},
|
||||
"schema_id": "propertyNames_3_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "empty object is valid",
|
||||
"data": {},
|
||||
"schema_id": "propertyNames_3_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "propertyNames with const",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"propertyNames": {
|
||||
"const": "foo"
|
||||
},
|
||||
"extensible": true,
|
||||
"$id": "propertyNames_4_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "object with property foo is valid",
|
||||
"data": {
|
||||
"foo": 1
|
||||
},
|
||||
"schema_id": "propertyNames_4_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "object with any other property is invalid",
|
||||
"data": {
|
||||
"bar": 1
|
||||
},
|
||||
"schema_id": "propertyNames_4_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "empty object is valid",
|
||||
"data": {},
|
||||
"schema_id": "propertyNames_4_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "propertyNames with enum",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"propertyNames": {
|
||||
"enum": [
|
||||
"foo",
|
||||
"bar"
|
||||
]
|
||||
},
|
||||
"extensible": true,
|
||||
"$id": "propertyNames_5_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "object with property foo is valid",
|
||||
"data": {
|
||||
"foo": 1
|
||||
},
|
||||
"schema_id": "propertyNames_5_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "object with property foo and bar is valid",
|
||||
"data": {
|
||||
"foo": 1,
|
||||
"bar": 1
|
||||
},
|
||||
"schema_id": "propertyNames_5_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "object with any other property is invalid",
|
||||
"data": {
|
||||
"baz": 1
|
||||
},
|
||||
"schema_id": "propertyNames_5_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "empty object is valid",
|
||||
"data": {},
|
||||
"schema_id": "propertyNames_5_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "extensible: true allows extra properties (checked by propertyNames)",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"propertyNames": {
|
||||
"maxLength": 3
|
||||
},
|
||||
"extensible": true,
|
||||
"$id": "propertyNames_6_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "extra property with valid name is valid",
|
||||
"data": {
|
||||
"foo": 1
|
||||
},
|
||||
"schema_id": "propertyNames_6_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "extra property with invalid name is invalid",
|
||||
"data": {
|
||||
"foobar": 1
|
||||
},
|
||||
"schema_id": "propertyNames_6_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
1638
fixtures/queryer.json
Normal file
1638
fixtures/queryer.json
Normal file
File diff suppressed because it is too large
Load Diff
929
fixtures/ref.json
Normal file
929
fixtures/ref.json
Normal file
@ -0,0 +1,929 @@
|
||||
[
|
||||
{
|
||||
"description": "nested refs",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"$ref": "c_212",
|
||||
"$id": "ref_4_0"
|
||||
},
|
||||
{
|
||||
"$id": "a_212",
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"$id": "b_212",
|
||||
"$ref": "a_212"
|
||||
},
|
||||
{
|
||||
"$id": "c_212",
|
||||
"$ref": "b_212"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "nested ref valid",
|
||||
"data": 5,
|
||||
"schema_id": "ref_4_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "nested ref invalid",
|
||||
"data": "a",
|
||||
"schema_id": "ref_4_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "ref applies alongside sibling keywords",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"properties": {
|
||||
"foo": {
|
||||
"$ref": "reffed_248",
|
||||
"maxItems": 2
|
||||
}
|
||||
},
|
||||
"$id": "ref_5_0"
|
||||
},
|
||||
{
|
||||
"$id": "reffed_248",
|
||||
"type": "array"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "ref valid, maxItems valid",
|
||||
"data": {
|
||||
"foo": []
|
||||
},
|
||||
"schema_id": "ref_5_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "ref valid, maxItems invalid",
|
||||
"data": {
|
||||
"foo": [
|
||||
1,
|
||||
2,
|
||||
3
|
||||
]
|
||||
},
|
||||
"schema_id": "ref_5_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "ref invalid",
|
||||
"data": {
|
||||
"foo": "string"
|
||||
},
|
||||
"schema_id": "ref_5_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "property named $ref that is not a reference",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"properties": {
|
||||
"$ref": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"$id": "ref_6_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "property named $ref valid",
|
||||
"data": {
|
||||
"$ref": "a"
|
||||
},
|
||||
"schema_id": "ref_6_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "property named $ref invalid",
|
||||
"data": {
|
||||
"$ref": 2
|
||||
},
|
||||
"schema_id": "ref_6_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "property named $ref, containing an actual $ref",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"properties": {
|
||||
"$ref": {
|
||||
"$ref": "is-string_344"
|
||||
}
|
||||
},
|
||||
"$id": "ref_7_0"
|
||||
},
|
||||
{
|
||||
"$id": "is-string_344",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "property named $ref valid",
|
||||
"data": {
|
||||
"$ref": "a"
|
||||
},
|
||||
"schema_id": "ref_7_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "property named $ref invalid",
|
||||
"data": {
|
||||
"$ref": 2
|
||||
},
|
||||
"schema_id": "ref_7_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "$ref to boolean schema true",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"$ref": "bool_378",
|
||||
"$id": "ref_8_0"
|
||||
},
|
||||
{
|
||||
"$id": "bool_378",
|
||||
"extensible": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "any value is valid",
|
||||
"data": "foo",
|
||||
"schema_id": "ref_8_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "$ref to boolean schema false",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"$ref": "bool_400",
|
||||
"$id": "ref_9_0"
|
||||
},
|
||||
{
|
||||
"$id": "bool_400",
|
||||
"extensible": false,
|
||||
"not": {}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "any value is invalid",
|
||||
"data": "foo",
|
||||
"schema_id": "ref_9_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "refs with quote",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"properties": {
|
||||
"foo\"bar": {
|
||||
"$ref": "foo%22bar_550"
|
||||
}
|
||||
},
|
||||
"$id": "ref_11_0"
|
||||
},
|
||||
{
|
||||
"$id": "foo%22bar_550",
|
||||
"type": "number"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "object with numbers is valid",
|
||||
"data": {
|
||||
"foo\"bar": 1
|
||||
},
|
||||
"schema_id": "ref_11_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "object with strings is invalid",
|
||||
"data": {
|
||||
"foo\"bar": "1"
|
||||
},
|
||||
"schema_id": "ref_11_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "$ref boundary resets to loose",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"$ref": "target_1465",
|
||||
"$id": "ref_35_0"
|
||||
},
|
||||
{
|
||||
"$id": "target_1465",
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "extra property in ref target is invalid (strict by default)",
|
||||
"data": {
|
||||
"foo": "bar",
|
||||
"extra": 1
|
||||
},
|
||||
"schema_id": "ref_35_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "$ref target can enforce strictness",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"$ref": "target_1496",
|
||||
"$id": "ref_36_0"
|
||||
},
|
||||
{
|
||||
"$id": "target_1496",
|
||||
"extensible": false,
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "extra property in ref target is invalid",
|
||||
"data": {
|
||||
"foo": "bar",
|
||||
"extra": 1
|
||||
},
|
||||
"schema_id": "ref_36_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "strictness: boundary reset at $ref",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"extensible": true,
|
||||
"properties": {
|
||||
"inline_child": {
|
||||
"properties": {
|
||||
"a": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ref_child": {
|
||||
"$ref": "strict_node_1544"
|
||||
},
|
||||
"extensible_ref_child": {
|
||||
"$ref": "extensible_node_1551"
|
||||
}
|
||||
},
|
||||
"$id": "ref_37_0"
|
||||
},
|
||||
{
|
||||
"$id": "strict_node_1544",
|
||||
"properties": {
|
||||
"b": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"$id": "extensible_node_1551",
|
||||
"extensible": true,
|
||||
"properties": {
|
||||
"c": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "inline child inherits looseness",
|
||||
"data": {
|
||||
"inline_child": {
|
||||
"a": 1,
|
||||
"extra": 2
|
||||
}
|
||||
},
|
||||
"schema_id": "ref_37_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "ref child resets to strict (default)",
|
||||
"data": {
|
||||
"ref_child": {
|
||||
"b": 1,
|
||||
"extra": 2
|
||||
}
|
||||
},
|
||||
"schema_id": "ref_37_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "ref child with explicit extensible=true is loose",
|
||||
"data": {
|
||||
"extensible_ref_child": {
|
||||
"c": 1,
|
||||
"extra": 2
|
||||
}
|
||||
},
|
||||
"schema_id": "ref_37_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "arrays: ref items inherit strictness (reset at boundary)",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"properties": {
|
||||
"list": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "strict_node_1614"
|
||||
}
|
||||
}
|
||||
},
|
||||
"$id": "ref_38_0"
|
||||
},
|
||||
{
|
||||
"$id": "strict_node_1614",
|
||||
"properties": {
|
||||
"a": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "ref item with extra property is invalid (strict by default)",
|
||||
"data": {
|
||||
"list": [
|
||||
{
|
||||
"a": 1,
|
||||
"extra": 2
|
||||
}
|
||||
]
|
||||
},
|
||||
"schema_id": "ref_38_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "implicit keyword shadowing",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"$ref": "parent_1648",
|
||||
"properties": {
|
||||
"type": {
|
||||
"const": "child"
|
||||
},
|
||||
"age": {
|
||||
"minimum": 15
|
||||
}
|
||||
},
|
||||
"$id": "ref_39_0"
|
||||
},
|
||||
{
|
||||
"$id": "parent_1648",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"const": "parent"
|
||||
},
|
||||
"age": {
|
||||
"minimum": 10,
|
||||
"maximum": 20
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type",
|
||||
"age"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "child type overrides parent type",
|
||||
"data": {
|
||||
"type": "child",
|
||||
"age": 15
|
||||
},
|
||||
"schema_id": "ref_39_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "parent type is now invalid (shadowed)",
|
||||
"data": {
|
||||
"type": "parent",
|
||||
"age": 15
|
||||
},
|
||||
"schema_id": "ref_39_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "child min age (15) is enforced",
|
||||
"data": {
|
||||
"type": "child",
|
||||
"age": 12
|
||||
},
|
||||
"schema_id": "ref_39_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "parent max age (20) is shadowed (replaced) by child definition",
|
||||
"data": {
|
||||
"type": "child",
|
||||
"age": 21
|
||||
},
|
||||
"schema_id": "ref_39_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Entities extending entities (Physical Birth)",
|
||||
"database": {
|
||||
"types": [
|
||||
{
|
||||
"name": "entity",
|
||||
"variations": [
|
||||
"entity",
|
||||
"organization",
|
||||
"person"
|
||||
],
|
||||
"schemas": [
|
||||
{
|
||||
"$id": "entity",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "organization",
|
||||
"variations": [
|
||||
"organization",
|
||||
"person"
|
||||
],
|
||||
"schemas": [
|
||||
{
|
||||
"$id": "organization",
|
||||
"$ref": "entity",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "person",
|
||||
"variations": [
|
||||
"person"
|
||||
],
|
||||
"schemas": [
|
||||
{
|
||||
"$id": "person",
|
||||
"$ref": "organization",
|
||||
"properties": {
|
||||
"first_name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"puncs": [
|
||||
{
|
||||
"name": "save_org",
|
||||
"schemas": [
|
||||
{
|
||||
"$id": "save_org.request",
|
||||
"$ref": "organization"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "Valid person against organization schema (implicit type allowance from physical hierarchy)",
|
||||
"schema_id": "save_org.request",
|
||||
"data": {
|
||||
"id": "1",
|
||||
"type": "person",
|
||||
"name": "ACME"
|
||||
},
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Valid organization against organization schema",
|
||||
"schema_id": "save_org.request",
|
||||
"data": {
|
||||
"id": "2",
|
||||
"type": "organization",
|
||||
"name": "ACME"
|
||||
},
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Invalid entity against organization schema (ancestor not allowed)",
|
||||
"schema_id": "save_org.request",
|
||||
"data": {
|
||||
"id": "3",
|
||||
"type": "entity"
|
||||
},
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false,
|
||||
"errors": [
|
||||
{
|
||||
"code": "TYPE_MISMATCH",
|
||||
"path": "/type"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Viral Infection: Ad-hocs inheriting entity boundaries via $ref",
|
||||
"database": {
|
||||
"types": [
|
||||
{
|
||||
"name": "entity",
|
||||
"variations": [
|
||||
"entity",
|
||||
"person"
|
||||
],
|
||||
"schemas": [
|
||||
{
|
||||
"$id": "entity",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "person",
|
||||
"variations": [
|
||||
"person"
|
||||
],
|
||||
"schemas": [
|
||||
{
|
||||
"$id": "person",
|
||||
"$ref": "entity",
|
||||
"properties": {
|
||||
"first_name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"$id": "light.person",
|
||||
"$ref": "entity",
|
||||
"properties": {
|
||||
"first_name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"puncs": [
|
||||
{
|
||||
"name": "save_person_light",
|
||||
"schemas": [
|
||||
{
|
||||
"$id": "save_person_light.request",
|
||||
"$ref": "light.person",
|
||||
"properties": {
|
||||
"extra_request_field": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "Valid person against ad-hoc request schema (request virally inherited person variations)",
|
||||
"schema_id": "save_person_light.request",
|
||||
"data": {
|
||||
"id": "1",
|
||||
"type": "person",
|
||||
"first_name": "John",
|
||||
"extra_request_field": "test"
|
||||
},
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Invalid entity against ad-hoc request schema (viral inheritance enforces person boundary)",
|
||||
"schema_id": "save_person_light.request",
|
||||
"data": {
|
||||
"id": "1",
|
||||
"type": "entity",
|
||||
"first_name": "John"
|
||||
},
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false,
|
||||
"errors": [
|
||||
{
|
||||
"code": "TYPE_MISMATCH",
|
||||
"path": "/type"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Ad-hocs extending ad-hocs (No type property)",
|
||||
"database": {
|
||||
"puncs": [
|
||||
{
|
||||
"name": "save_address",
|
||||
"schemas": [
|
||||
{
|
||||
"$id": "address",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"street": {
|
||||
"type": "string"
|
||||
},
|
||||
"city": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"$id": "us_address",
|
||||
"$ref": "address",
|
||||
"properties": {
|
||||
"state": {
|
||||
"type": "string"
|
||||
},
|
||||
"zip": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"$id": "save_address.request",
|
||||
"$ref": "us_address"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "Valid us_address",
|
||||
"schema_id": "save_address.request",
|
||||
"data": {
|
||||
"street": "123 Main",
|
||||
"city": "Anytown",
|
||||
"state": "CA",
|
||||
"zip": "12345"
|
||||
},
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Invalid base address against us_address",
|
||||
"schema_id": "save_address.request",
|
||||
"data": {
|
||||
"street": "123 Main",
|
||||
"city": "Anytown"
|
||||
},
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Ad-hocs extending ad-hocs (with string type property, no magic)",
|
||||
"database": {
|
||||
"puncs": [
|
||||
{
|
||||
"name": "save_config",
|
||||
"schemas": [
|
||||
{
|
||||
"$id": "config_base",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"const": "config_base"
|
||||
},
|
||||
"setting": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"$id": "config_advanced",
|
||||
"$ref": "config_base",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"const": "config_advanced"
|
||||
},
|
||||
"advanced_setting": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"$id": "save_config.request",
|
||||
"$ref": "config_base"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "Valid config_base against config_base",
|
||||
"schema_id": "save_config.request",
|
||||
"data": {
|
||||
"type": "config_base",
|
||||
"setting": "on"
|
||||
},
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Invalid config_advanced against config_base (no type magic, const is strictly 'config_base')",
|
||||
"schema_id": "save_config.request",
|
||||
"data": {
|
||||
"type": "config_advanced",
|
||||
"setting": "on",
|
||||
"advanced_setting": "off"
|
||||
},
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
312
fixtures/required.json
Normal file
312
fixtures/required.json
Normal file
@ -0,0 +1,312 @@
|
||||
[
|
||||
{
|
||||
"description": "required validation",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"properties": {
|
||||
"foo": {},
|
||||
"bar": {}
|
||||
},
|
||||
"required": [
|
||||
"foo"
|
||||
],
|
||||
"$id": "required_0_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "present required property is valid",
|
||||
"data": {
|
||||
"foo": 1
|
||||
},
|
||||
"schema_id": "required_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "non-present required property is invalid",
|
||||
"data": {
|
||||
"bar": 1
|
||||
},
|
||||
"schema_id": "required_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "ignores arrays",
|
||||
"data": [],
|
||||
"schema_id": "required_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "ignores strings",
|
||||
"data": "",
|
||||
"schema_id": "required_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "ignores other non-objects",
|
||||
"data": 12,
|
||||
"schema_id": "required_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "ignores null",
|
||||
"data": null,
|
||||
"schema_id": "required_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "ignores boolean",
|
||||
"data": true,
|
||||
"schema_id": "required_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "required default validation",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"properties": {
|
||||
"foo": {}
|
||||
},
|
||||
"$id": "required_1_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "not required by default",
|
||||
"data": {},
|
||||
"schema_id": "required_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "required with empty array",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"properties": {
|
||||
"foo": {}
|
||||
},
|
||||
"required": [],
|
||||
"$id": "required_2_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "property not required",
|
||||
"data": {},
|
||||
"schema_id": "required_2_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "required with escaped characters",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"required": [
|
||||
"foo\nbar",
|
||||
"foo\"bar",
|
||||
"foo\\bar",
|
||||
"foo\rbar",
|
||||
"foo\tbar",
|
||||
"foo\fbar"
|
||||
],
|
||||
"extensible": true,
|
||||
"$id": "required_3_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "object with all properties present is valid",
|
||||
"data": {
|
||||
"foo\nbar": 1,
|
||||
"foo\"bar": 1,
|
||||
"foo\\bar": 1,
|
||||
"foo\rbar": 1,
|
||||
"foo\tbar": 1,
|
||||
"foo\fbar": 1
|
||||
},
|
||||
"schema_id": "required_3_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "object with some properties missing is invalid",
|
||||
"data": {
|
||||
"foo\nbar": "1",
|
||||
"foo\"bar": "1"
|
||||
},
|
||||
"schema_id": "required_3_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"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": [
|
||||
"__proto__",
|
||||
"toString",
|
||||
"constructor"
|
||||
],
|
||||
"extensible": true,
|
||||
"$id": "required_4_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "ignores arrays",
|
||||
"data": [],
|
||||
"schema_id": "required_4_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "ignores other non-objects",
|
||||
"data": 12,
|
||||
"schema_id": "required_4_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "none of the properties mentioned",
|
||||
"data": {},
|
||||
"schema_id": "required_4_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "__proto__ present",
|
||||
"data": {
|
||||
"__proto__": "foo"
|
||||
},
|
||||
"schema_id": "required_4_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "toString present",
|
||||
"data": {
|
||||
"toString": {
|
||||
"length": 37
|
||||
}
|
||||
},
|
||||
"schema_id": "required_4_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "constructor present",
|
||||
"data": {
|
||||
"constructor": {
|
||||
"length": 37
|
||||
}
|
||||
},
|
||||
"schema_id": "required_4_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "all present",
|
||||
"data": {
|
||||
"__proto__": 12,
|
||||
"toString": {
|
||||
"length": "foo"
|
||||
},
|
||||
"constructor": 37
|
||||
},
|
||||
"schema_id": "required_4_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "extensible: true allows extra properties in required",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"required": [
|
||||
"foo"
|
||||
],
|
||||
"extensible": true,
|
||||
"$id": "required_5_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "extra property is valid",
|
||||
"data": {
|
||||
"foo": 1,
|
||||
"bar": 2
|
||||
},
|
||||
"schema_id": "required_5_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
312
fixtures/stems.json
Normal file
312
fixtures/stems.json
Normal file
@ -0,0 +1,312 @@
|
||||
[
|
||||
{
|
||||
"description": "Stem Engine Unit Tests",
|
||||
"database": {
|
||||
"puncs": [],
|
||||
"enums": [],
|
||||
"relations": [
|
||||
{
|
||||
"id": "rel1",
|
||||
"type": "relation",
|
||||
"constraint": "fk_contact_entity",
|
||||
"source_type": "contact",
|
||||
"source_columns": [
|
||||
"entity_id"
|
||||
],
|
||||
"destination_type": "person",
|
||||
"destination_columns": [
|
||||
"id"
|
||||
],
|
||||
"prefix": null
|
||||
},
|
||||
{
|
||||
"id": "rel2",
|
||||
"type": "relation",
|
||||
"constraint": "fk_relationship_target",
|
||||
"source_type": "relationship",
|
||||
"source_columns": [
|
||||
"target_id",
|
||||
"target_type"
|
||||
],
|
||||
"destination_type": "entity",
|
||||
"destination_columns": [
|
||||
"id",
|
||||
"type"
|
||||
],
|
||||
"prefix": "target"
|
||||
}
|
||||
],
|
||||
"types": [
|
||||
{
|
||||
"name": "entity",
|
||||
"hierarchy": [
|
||||
"entity"
|
||||
],
|
||||
"schemas": [
|
||||
{
|
||||
"$id": "entity",
|
||||
"type": "object",
|
||||
"properties": {}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "person",
|
||||
"hierarchy": [
|
||||
"person",
|
||||
"entity"
|
||||
],
|
||||
"schemas": [
|
||||
{
|
||||
"$id": "person",
|
||||
"$ref": "entity",
|
||||
"properties": {}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "email_address",
|
||||
"hierarchy": [
|
||||
"email_address",
|
||||
"entity"
|
||||
],
|
||||
"schemas": [
|
||||
{
|
||||
"$id": "email_address",
|
||||
"$ref": "entity",
|
||||
"properties": {}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "phone_number",
|
||||
"hierarchy": [
|
||||
"phone_number",
|
||||
"entity"
|
||||
],
|
||||
"schemas": [
|
||||
{
|
||||
"$id": "phone_number",
|
||||
"$ref": "entity",
|
||||
"properties": {}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "relationship",
|
||||
"relationship": true,
|
||||
"hierarchy": [
|
||||
"relationship",
|
||||
"entity"
|
||||
],
|
||||
"schemas": [
|
||||
{
|
||||
"$id": "relationship",
|
||||
"$ref": "entity",
|
||||
"properties": {}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "contact",
|
||||
"relationship": true,
|
||||
"hierarchy": [
|
||||
"contact",
|
||||
"relationship",
|
||||
"entity"
|
||||
],
|
||||
"schemas": [
|
||||
{
|
||||
"$id": "contact",
|
||||
"$ref": "relationship",
|
||||
"properties": {
|
||||
"target": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "phone_number"
|
||||
},
|
||||
{
|
||||
"$ref": "email_address"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "save_person",
|
||||
"schemas": [
|
||||
{
|
||||
"$id": "save_person.response",
|
||||
"$ref": "person",
|
||||
"properties": {
|
||||
"contacts": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "contact"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "correctly squashes deep oneOf refs through array paths",
|
||||
"action": "compile",
|
||||
"expect": {
|
||||
"success": true,
|
||||
"stems": {
|
||||
"contact": {
|
||||
"": {
|
||||
"schema": {
|
||||
"$id": "contact",
|
||||
"$ref": "relationship",
|
||||
"properties": {
|
||||
"target": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "phone_number"
|
||||
},
|
||||
{
|
||||
"$ref": "email_address"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": "contact"
|
||||
},
|
||||
"target#(type==\"email_address\")": {
|
||||
"relation": "target_id",
|
||||
"schema": {
|
||||
"$id": "email_address",
|
||||
"$ref": "entity",
|
||||
"properties": {}
|
||||
},
|
||||
"type": "email_address"
|
||||
},
|
||||
"target#(type==\"phone_number\")": {
|
||||
"relation": "target_id",
|
||||
"schema": {
|
||||
"$id": "phone_number",
|
||||
"$ref": "entity",
|
||||
"properties": {}
|
||||
},
|
||||
"type": "phone_number"
|
||||
}
|
||||
},
|
||||
"email_address": {
|
||||
"": {
|
||||
"schema": {
|
||||
"$id": "email_address",
|
||||
"$ref": "entity",
|
||||
"properties": {}
|
||||
},
|
||||
"type": "email_address"
|
||||
}
|
||||
},
|
||||
"entity": {
|
||||
"": {
|
||||
"schema": {
|
||||
"$id": "entity",
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"type": "entity"
|
||||
}
|
||||
},
|
||||
"person": {
|
||||
"": {
|
||||
"schema": {
|
||||
"$id": "person",
|
||||
"$ref": "entity",
|
||||
"properties": {}
|
||||
},
|
||||
"type": "person"
|
||||
}
|
||||
},
|
||||
"phone_number": {
|
||||
"": {
|
||||
"schema": {
|
||||
"$id": "phone_number",
|
||||
"$ref": "entity",
|
||||
"properties": {}
|
||||
},
|
||||
"type": "phone_number"
|
||||
}
|
||||
},
|
||||
"relationship": {
|
||||
"": {
|
||||
"schema": {
|
||||
"$id": "relationship",
|
||||
"$ref": "entity",
|
||||
"properties": {}
|
||||
},
|
||||
"type": "relationship"
|
||||
}
|
||||
},
|
||||
"save_person.response": {
|
||||
"": {
|
||||
"schema": {
|
||||
"$id": "save_person.response",
|
||||
"$ref": "person",
|
||||
"properties": {
|
||||
"contacts": {
|
||||
"items": {
|
||||
"$ref": "contact"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": "person"
|
||||
},
|
||||
"contacts.#": {
|
||||
"relation": "contacts_id",
|
||||
"schema": {
|
||||
"$id": "contact",
|
||||
"$ref": "relationship",
|
||||
"properties": {
|
||||
"target": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "phone_number"
|
||||
},
|
||||
{
|
||||
"$ref": "email_address"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": "contact"
|
||||
},
|
||||
"contacts.#.target#(type==\"email_address\")": {
|
||||
"relation": "target_id",
|
||||
"schema": {
|
||||
"$id": "email_address",
|
||||
"$ref": "entity",
|
||||
"properties": {}
|
||||
},
|
||||
"type": "email_address"
|
||||
},
|
||||
"contacts.#.target#(type==\"phone_number\")": {
|
||||
"relation": "target_id",
|
||||
"schema": {
|
||||
"$id": "phone_number",
|
||||
"$ref": "entity",
|
||||
"properties": {}
|
||||
},
|
||||
"type": "phone_number"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
912
fixtures/type.json
Normal file
912
fixtures/type.json
Normal file
@ -0,0 +1,912 @@
|
||||
[
|
||||
{
|
||||
"description": "integer type matches integers",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"type": "integer",
|
||||
"$id": "type_0_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "an integer is an integer",
|
||||
"data": 1,
|
||||
"schema_id": "type_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "a float with zero fractional part is an integer",
|
||||
"data": 1,
|
||||
"schema_id": "type_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "a float is not an integer",
|
||||
"data": 1.1,
|
||||
"schema_id": "type_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "a string is not an integer",
|
||||
"data": "foo",
|
||||
"schema_id": "type_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "a string is still not an integer, even if it looks like one",
|
||||
"data": "1",
|
||||
"schema_id": "type_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "an object is not an integer",
|
||||
"data": {},
|
||||
"schema_id": "type_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "an array is not an integer",
|
||||
"data": [],
|
||||
"schema_id": "type_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "a boolean is not an integer",
|
||||
"data": true,
|
||||
"schema_id": "type_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "null is not an integer",
|
||||
"data": null,
|
||||
"schema_id": "type_0_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "number type matches numbers",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"type": "number",
|
||||
"$id": "type_1_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "an integer is a number",
|
||||
"data": 1,
|
||||
"schema_id": "type_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "a float with zero fractional part is a number (and an integer)",
|
||||
"data": 1,
|
||||
"schema_id": "type_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "a float is a number",
|
||||
"data": 1.1,
|
||||
"schema_id": "type_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "a string is not a number",
|
||||
"data": "foo",
|
||||
"schema_id": "type_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "a string is still not a number, even if it looks like one",
|
||||
"data": "1",
|
||||
"schema_id": "type_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "an object is not a number",
|
||||
"data": {},
|
||||
"schema_id": "type_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "an array is not a number",
|
||||
"data": [],
|
||||
"schema_id": "type_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "a boolean is not a number",
|
||||
"data": true,
|
||||
"schema_id": "type_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "null is not a number",
|
||||
"data": null,
|
||||
"schema_id": "type_1_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "string type matches strings",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"type": "string",
|
||||
"$id": "type_2_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "1 is not a string",
|
||||
"data": 1,
|
||||
"schema_id": "type_2_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "a float is not a string",
|
||||
"data": 1.1,
|
||||
"schema_id": "type_2_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "a string is a string",
|
||||
"data": "foo",
|
||||
"schema_id": "type_2_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "a string is still a string, even if it looks like a number",
|
||||
"data": "1",
|
||||
"schema_id": "type_2_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "an empty string is still a string",
|
||||
"data": "",
|
||||
"schema_id": "type_2_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "an object is not a string",
|
||||
"data": {},
|
||||
"schema_id": "type_2_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "an array is not a string",
|
||||
"data": [],
|
||||
"schema_id": "type_2_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "a boolean is not a string",
|
||||
"data": true,
|
||||
"schema_id": "type_2_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "null is not a string",
|
||||
"data": null,
|
||||
"schema_id": "type_2_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "object type matches objects",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"type": "object",
|
||||
"$id": "type_3_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "an integer is not an object",
|
||||
"data": 1,
|
||||
"schema_id": "type_3_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "a float is not an object",
|
||||
"data": 1.1,
|
||||
"schema_id": "type_3_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "a string is not an object",
|
||||
"data": "foo",
|
||||
"schema_id": "type_3_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "an object is an object",
|
||||
"data": {},
|
||||
"schema_id": "type_3_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "an array is not an object",
|
||||
"data": [],
|
||||
"schema_id": "type_3_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "a boolean is not an object",
|
||||
"data": true,
|
||||
"schema_id": "type_3_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "null is not an object",
|
||||
"data": null,
|
||||
"schema_id": "type_3_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "array type matches arrays",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"type": "array",
|
||||
"$id": "type_4_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "an integer is not an array",
|
||||
"data": 1,
|
||||
"schema_id": "type_4_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "a float is not an array",
|
||||
"data": 1.1,
|
||||
"schema_id": "type_4_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "a string is not an array",
|
||||
"data": "foo",
|
||||
"schema_id": "type_4_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "an object is not an array",
|
||||
"data": {},
|
||||
"schema_id": "type_4_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "an array is an array",
|
||||
"data": [],
|
||||
"schema_id": "type_4_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "a boolean is not an array",
|
||||
"data": true,
|
||||
"schema_id": "type_4_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "null is not an array",
|
||||
"data": null,
|
||||
"schema_id": "type_4_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "boolean type matches booleans",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"type": "boolean",
|
||||
"$id": "type_5_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "an integer is not a boolean",
|
||||
"data": 1,
|
||||
"schema_id": "type_5_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "zero is not a boolean",
|
||||
"data": 0,
|
||||
"schema_id": "type_5_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "a float is not a boolean",
|
||||
"data": 1.1,
|
||||
"schema_id": "type_5_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "a string is not a boolean",
|
||||
"data": "foo",
|
||||
"schema_id": "type_5_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "an empty string is a null",
|
||||
"data": "",
|
||||
"schema_id": "type_5_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "an object is not a boolean",
|
||||
"data": {},
|
||||
"schema_id": "type_5_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "an array is not a boolean",
|
||||
"data": [],
|
||||
"schema_id": "type_5_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "true is a boolean",
|
||||
"data": true,
|
||||
"schema_id": "type_5_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "false is a boolean",
|
||||
"data": false,
|
||||
"schema_id": "type_5_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "null is not a boolean",
|
||||
"data": null,
|
||||
"schema_id": "type_5_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "null type matches only the null object",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"type": "null",
|
||||
"$id": "type_6_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "an integer is not null",
|
||||
"data": 1,
|
||||
"schema_id": "type_6_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "a float is not null",
|
||||
"data": 1.1,
|
||||
"schema_id": "type_6_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "zero is not null",
|
||||
"data": 0,
|
||||
"schema_id": "type_6_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "a string is not null",
|
||||
"data": "foo",
|
||||
"schema_id": "type_6_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "an empty string is null",
|
||||
"data": "",
|
||||
"schema_id": "type_6_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "an object is not null",
|
||||
"data": {},
|
||||
"schema_id": "type_6_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "an array is not null",
|
||||
"data": [],
|
||||
"schema_id": "type_6_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "true is not null",
|
||||
"data": true,
|
||||
"schema_id": "type_6_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "false is not null",
|
||||
"data": false,
|
||||
"schema_id": "type_6_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "null is null",
|
||||
"data": null,
|
||||
"schema_id": "type_6_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "multiple types can be specified in an array",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"type": [
|
||||
"integer",
|
||||
"string"
|
||||
],
|
||||
"$id": "type_7_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "an integer is valid",
|
||||
"data": 1,
|
||||
"schema_id": "type_7_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "a string is valid",
|
||||
"data": "foo",
|
||||
"schema_id": "type_7_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "a float is invalid",
|
||||
"data": 1.1,
|
||||
"schema_id": "type_7_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "an object is invalid",
|
||||
"data": {},
|
||||
"schema_id": "type_7_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "an array is invalid",
|
||||
"data": [],
|
||||
"schema_id": "type_7_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "a boolean is invalid",
|
||||
"data": true,
|
||||
"schema_id": "type_7_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "null is invalid",
|
||||
"data": null,
|
||||
"schema_id": "type_7_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "type as array with one item",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"type": [
|
||||
"string"
|
||||
],
|
||||
"$id": "type_8_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "string is valid",
|
||||
"data": "foo",
|
||||
"schema_id": "type_8_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "number is invalid",
|
||||
"data": 123,
|
||||
"schema_id": "type_8_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "type: array or object",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"type": [
|
||||
"array",
|
||||
"object"
|
||||
],
|
||||
"items": {},
|
||||
"$id": "type_9_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "array is valid",
|
||||
"data": [
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"schema_id": "type_9_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "object is valid",
|
||||
"data": {},
|
||||
"schema_id": "type_9_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "number is invalid",
|
||||
"data": 123,
|
||||
"schema_id": "type_9_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "string is invalid",
|
||||
"data": "foo",
|
||||
"schema_id": "type_9_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "null is invalid",
|
||||
"data": null,
|
||||
"schema_id": "type_9_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "type: array, object or null",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"type": [
|
||||
"array",
|
||||
"object",
|
||||
"null"
|
||||
],
|
||||
"items": {},
|
||||
"$id": "type_10_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "array is valid",
|
||||
"data": [
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"schema_id": "type_10_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "object is valid",
|
||||
"data": {},
|
||||
"schema_id": "type_10_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "null is valid",
|
||||
"data": null,
|
||||
"schema_id": "type_10_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "number is invalid",
|
||||
"data": 123,
|
||||
"schema_id": "type_10_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "string is invalid",
|
||||
"data": "foo",
|
||||
"schema_id": "type_10_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "extensible: true allows extra properties",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"type": "object",
|
||||
"extensible": true,
|
||||
"$id": "type_11_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "extra property is valid",
|
||||
"data": {
|
||||
"foo": 1
|
||||
},
|
||||
"schema_id": "type_11_0",
|
||||
"action": "validate",
|
||||
"expect": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
1171
fixtures/uniqueItems.json
Normal file
1171
fixtures/uniqueItems.json
Normal file
File diff suppressed because it is too large
Load Diff
196
src/database/executors/mock.rs
Normal file
196
src/database/executors/mock.rs
Normal file
@ -0,0 +1,196 @@
|
||||
#[cfg(test)]
|
||||
use crate::database::executors::DatabaseExecutor;
|
||||
#[cfg(test)]
|
||||
use regex::Regex;
|
||||
#[cfg(test)]
|
||||
use serde_json::Value;
|
||||
#[cfg(test)]
|
||||
use std::cell::RefCell;
|
||||
|
||||
#[cfg(test)]
|
||||
pub struct MockState {
|
||||
pub captured_queries: Vec<String>,
|
||||
pub query_responses: Vec<Result<Value, String>>,
|
||||
pub execute_responses: Vec<Result<(), String>>,
|
||||
pub mocks: Vec<Value>,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
impl MockState {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
captured_queries: Default::default(),
|
||||
query_responses: Default::default(),
|
||||
execute_responses: Default::default(),
|
||||
mocks: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
thread_local! {
|
||||
pub static MOCK_STATE: RefCell<MockState> = RefCell::new(MockState::new());
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub struct MockExecutor {}
|
||||
|
||||
#[cfg(test)]
|
||||
impl MockExecutor {
|
||||
pub fn new() -> Self {
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
impl DatabaseExecutor for MockExecutor {
|
||||
fn query(&self, sql: &str, _args: Option<&[Value]>) -> Result<Value, String> {
|
||||
println!("DEBUG SQL QUERY: {}", sql);
|
||||
MOCK_STATE.with(|state| {
|
||||
let mut s = state.borrow_mut();
|
||||
s.captured_queries.push(sql.to_string());
|
||||
|
||||
if !s.mocks.is_empty() {
|
||||
if let Some(matches) = parse_and_match_mocks(sql, &s.mocks) {
|
||||
if !matches.is_empty() {
|
||||
return Ok(Value::Array(matches));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if s.query_responses.is_empty() {
|
||||
return Ok(Value::Array(vec![]));
|
||||
}
|
||||
s.query_responses.remove(0)
|
||||
})
|
||||
}
|
||||
|
||||
fn execute(&self, sql: &str, _args: Option<&[Value]>) -> Result<(), String> {
|
||||
println!("DEBUG SQL EXECUTE: {}", sql);
|
||||
MOCK_STATE.with(|state| {
|
||||
let mut s = state.borrow_mut();
|
||||
s.captured_queries.push(sql.to_string());
|
||||
if s.execute_responses.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
s.execute_responses.remove(0)
|
||||
})
|
||||
}
|
||||
|
||||
fn auth_user_id(&self) -> Result<String, String> {
|
||||
Ok("00000000-0000-0000-0000-000000000000".to_string())
|
||||
}
|
||||
|
||||
fn timestamp(&self) -> Result<String, String> {
|
||||
Ok("2026-03-10T00:00:00Z".to_string())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn get_queries(&self) -> Vec<String> {
|
||||
MOCK_STATE.with(|state| state.borrow().captured_queries.clone())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn set_mocks(&self, mocks: Vec<Value>) {
|
||||
MOCK_STATE.with(|state| {
|
||||
state.borrow_mut().mocks = mocks;
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn reset_mocks(&self) {
|
||||
MOCK_STATE.with(|state| {
|
||||
let mut s = state.borrow_mut();
|
||||
s.captured_queries.clear();
|
||||
s.query_responses.clear();
|
||||
s.execute_responses.clear();
|
||||
s.mocks.clear();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn parse_and_match_mocks(sql: &str, mocks: &[Value]) -> Option<Vec<Value>> {
|
||||
let sql_upper = sql.to_uppercase();
|
||||
if !sql_upper.starts_with("SELECT") {
|
||||
return None;
|
||||
}
|
||||
|
||||
// 1. Extract table name
|
||||
let table_regex = Regex::new(r#"(?i)\s+FROM\s+(?:[a-zA-Z_]\w*\.)?"?([a-zA-Z_]\w*)"?"#).ok()?;
|
||||
let table = if let Some(caps) = table_regex.captures(sql) {
|
||||
caps.get(1)?.as_str()
|
||||
} else {
|
||||
return None;
|
||||
};
|
||||
|
||||
// 2. Extract WHERE conditions
|
||||
let mut conditions = Vec::new();
|
||||
if let Some(where_idx) = sql_upper.find(" WHERE ") {
|
||||
let mut where_end = sql_upper.find(" ORDER BY ").unwrap_or(sql.len());
|
||||
if let Some(limit_idx) = sql_upper.find(" LIMIT ") {
|
||||
if limit_idx < where_end {
|
||||
where_end = limit_idx;
|
||||
}
|
||||
}
|
||||
let where_clause = &sql[where_idx + 7..where_end];
|
||||
let and_regex = Regex::new(r"(?i)\s+AND\s+").ok()?;
|
||||
let parts = and_regex.split(where_clause);
|
||||
for part in parts {
|
||||
if let Some(eq_idx) = part.find('=') {
|
||||
let left = part[..eq_idx]
|
||||
.trim()
|
||||
.split('.')
|
||||
.last()
|
||||
.unwrap_or("")
|
||||
.trim_matches('"');
|
||||
let right = part[eq_idx + 1..].trim().trim_matches('\'');
|
||||
conditions.push((left.to_string(), right.to_string()));
|
||||
} else if part.to_uppercase().contains(" IS NULL") {
|
||||
let left = part[..part.to_uppercase().find(" IS NULL").unwrap()]
|
||||
.trim()
|
||||
.split('.')
|
||||
.last()
|
||||
.unwrap_or("")
|
||||
.replace('"', ""); // Remove quotes explicitly
|
||||
conditions.push((left, "null".to_string()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Find matching mocks
|
||||
let mut matches = Vec::new();
|
||||
for mock in mocks {
|
||||
if let Some(mock_obj) = mock.as_object() {
|
||||
if let Some(t) = mock_obj.get("type") {
|
||||
if t.as_str() != Some(table) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
let mut matches_all = true;
|
||||
for (k, v) in &conditions {
|
||||
let mock_val_str = match mock_obj.get(k) {
|
||||
Some(Value::String(s)) => s.clone(),
|
||||
Some(Value::Number(n)) => n.to_string(),
|
||||
Some(Value::Bool(b)) => b.to_string(),
|
||||
Some(Value::Null) => "null".to_string(),
|
||||
_ => {
|
||||
matches_all = false;
|
||||
break;
|
||||
}
|
||||
};
|
||||
if mock_val_str != *v {
|
||||
matches_all = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if matches_all {
|
||||
matches.push(mock.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some(matches)
|
||||
}
|
||||
31
src/database/executors/mod.rs
Normal file
31
src/database/executors/mod.rs
Normal file
@ -0,0 +1,31 @@
|
||||
pub mod mock;
|
||||
|
||||
#[cfg(not(test))]
|
||||
pub mod pgrx;
|
||||
|
||||
use serde_json::Value;
|
||||
|
||||
/// An abstraction over database execution to allow for isolated unit testing
|
||||
/// without a live Postgres SPI connection.
|
||||
pub trait DatabaseExecutor: Send + Sync {
|
||||
/// Executes a query expecting a single JSONB return, representing rows.
|
||||
fn query(&self, sql: &str, args: Option<&[Value]>) -> Result<Value, String>;
|
||||
|
||||
/// Executes an operation (INSERT, UPDATE, DELETE, or pg_notify) that does not return rows.
|
||||
fn execute(&self, sql: &str, args: Option<&[Value]>) -> Result<(), String>;
|
||||
|
||||
/// Returns the current authenticated user's ID
|
||||
fn auth_user_id(&self) -> Result<String, String>;
|
||||
|
||||
/// Returns the current transaction timestamp
|
||||
fn timestamp(&self) -> Result<String, String>;
|
||||
|
||||
#[cfg(test)]
|
||||
fn get_queries(&self) -> Vec<String>;
|
||||
|
||||
#[cfg(test)]
|
||||
fn reset_mocks(&self);
|
||||
|
||||
#[cfg(test)]
|
||||
fn set_mocks(&self, mocks: Vec<Value>);
|
||||
}
|
||||
112
src/database/executors/pgrx.rs
Normal file
112
src/database/executors/pgrx.rs
Normal file
@ -0,0 +1,112 @@
|
||||
use crate::database::executors::DatabaseExecutor;
|
||||
use pgrx::prelude::*;
|
||||
use serde_json::Value;
|
||||
|
||||
/// The production executor that wraps `pgrx::spi::Spi`.
|
||||
pub struct SpiExecutor;
|
||||
|
||||
impl SpiExecutor {
|
||||
pub fn new() -> Self {
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
impl DatabaseExecutor for SpiExecutor {
|
||||
fn query(&self, sql: &str, args: Option<&[Value]>) -> Result<Value, String> {
|
||||
let mut json_args = Vec::new();
|
||||
let mut args_with_oid: Vec<pgrx::datum::DatumWithOid> = Vec::new();
|
||||
if let Some(params) = args {
|
||||
for val in params {
|
||||
json_args.push(pgrx::JsonB(val.clone()));
|
||||
}
|
||||
for j_val in json_args.into_iter() {
|
||||
args_with_oid.push(pgrx::datum::DatumWithOid::from(j_val));
|
||||
}
|
||||
}
|
||||
|
||||
pgrx::PgTryBuilder::new(|| {
|
||||
Spi::connect(|client| {
|
||||
pgrx::notice!("JSPG_SQL: {}", sql);
|
||||
match client.select(sql, Some(args_with_oid.len() as i64), &args_with_oid) {
|
||||
Ok(tup_table) => {
|
||||
let mut results = Vec::new();
|
||||
for row in tup_table {
|
||||
if let Ok(Some(jsonb)) = row.get::<pgrx::JsonB>(1) {
|
||||
results.push(jsonb.0);
|
||||
}
|
||||
}
|
||||
Ok(Value::Array(results))
|
||||
}
|
||||
Err(e) => Err(format!("SPI Query Fetch Failure: {}", e)),
|
||||
}
|
||||
})
|
||||
})
|
||||
.catch_others(|cause| {
|
||||
pgrx::warning!("JSPG Caught Native Postgres Error: {:?}", cause);
|
||||
Err(format!("{:?}", cause))
|
||||
})
|
||||
.execute()
|
||||
}
|
||||
|
||||
fn execute(&self, sql: &str, args: Option<&[Value]>) -> Result<(), String> {
|
||||
let mut json_args = Vec::new();
|
||||
let mut args_with_oid: Vec<pgrx::datum::DatumWithOid> = Vec::new();
|
||||
if let Some(params) = args {
|
||||
for val in params {
|
||||
json_args.push(pgrx::JsonB(val.clone()));
|
||||
}
|
||||
for j_val in json_args.into_iter() {
|
||||
args_with_oid.push(pgrx::datum::DatumWithOid::from(j_val));
|
||||
}
|
||||
}
|
||||
|
||||
pgrx::PgTryBuilder::new(|| {
|
||||
Spi::connect_mut(|client| {
|
||||
pgrx::notice!("JSPG_SQL: {}", sql);
|
||||
match client.update(sql, Some(args_with_oid.len() as i64), &args_with_oid) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(e) => Err(format!("SPI Execution Failure: {}", e)),
|
||||
}
|
||||
})
|
||||
})
|
||||
.catch_others(|cause| {
|
||||
pgrx::warning!("JSPG Caught Native Postgres Error: {:?}", cause);
|
||||
Err(format!("{:?}", cause))
|
||||
})
|
||||
.execute()
|
||||
}
|
||||
|
||||
fn auth_user_id(&self) -> Result<String, String> {
|
||||
Spi::connect(|client| {
|
||||
let mut tup_table = client
|
||||
.select(
|
||||
"SELECT COALESCE(current_setting('auth.user_id', true), 'ffffffff-ffff-ffff-ffff-ffffffffffff')",
|
||||
None,
|
||||
&[],
|
||||
)
|
||||
.map_err(|e| format!("SPI Select Error: {}", e))?;
|
||||
|
||||
let row = tup_table
|
||||
.next()
|
||||
.ok_or("No user id setting returned from context".to_string())?;
|
||||
let user_id: Option<String> = row.get(1).map_err(|e| e.to_string())?;
|
||||
|
||||
user_id.ok_or("Missing user_id".to_string())
|
||||
})
|
||||
}
|
||||
|
||||
fn timestamp(&self) -> Result<String, String> {
|
||||
Spi::connect(|client| {
|
||||
let mut tup_table = client
|
||||
.select("SELECT clock_timestamp()::text", None, &[])
|
||||
.map_err(|e| format!("SPI Select Error: {}", e))?;
|
||||
|
||||
let row = tup_table
|
||||
.next()
|
||||
.ok_or("No clock timestamp returned".to_string())?;
|
||||
let timestamp: Option<String> = row.get(1).map_err(|e| e.to_string())?;
|
||||
|
||||
timestamp.ok_or("Missing timestamp".to_string())
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -1,34 +1,61 @@
|
||||
pub mod r#enum;
|
||||
pub mod executors;
|
||||
pub mod formats;
|
||||
pub mod page;
|
||||
pub mod punc;
|
||||
pub mod relation;
|
||||
pub mod schema;
|
||||
pub mod r#type;
|
||||
|
||||
use crate::database::r#enum::Enum;
|
||||
use crate::database::punc::Punc;
|
||||
use crate::database::schema::Schema;
|
||||
use crate::database::r#type::Type;
|
||||
// External mock exports inside the executor sub-folder
|
||||
|
||||
use r#enum::Enum;
|
||||
use executors::DatabaseExecutor;
|
||||
|
||||
#[cfg(not(test))]
|
||||
use executors::pgrx::SpiExecutor;
|
||||
|
||||
#[cfg(test)]
|
||||
use executors::mock::MockExecutor;
|
||||
|
||||
pub mod stem;
|
||||
use punc::Punc;
|
||||
use relation::Relation;
|
||||
use schema::Schema;
|
||||
use serde_json::Value;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::sync::Arc;
|
||||
use stem::Stem;
|
||||
use r#type::Type;
|
||||
|
||||
pub struct Database {
|
||||
pub enums: HashMap<String, Enum>,
|
||||
pub types: HashMap<String, Type>,
|
||||
pub puncs: HashMap<String, Punc>,
|
||||
pub relations: HashMap<(String, String), Vec<Relation>>,
|
||||
pub schemas: HashMap<String, Schema>,
|
||||
// Map of Schema ID -> { Entity Type -> Target Subschema Arc }
|
||||
pub stems: HashMap<String, HashMap<String, Arc<Stem>>>,
|
||||
pub descendants: HashMap<String, Vec<String>>,
|
||||
pub depths: HashMap<String, usize>,
|
||||
pub executor: Box<dyn DatabaseExecutor + Send + Sync>,
|
||||
}
|
||||
|
||||
impl Database {
|
||||
pub fn new(val: &serde_json::Value) -> Self {
|
||||
pub fn new(val: &serde_json::Value) -> Result<Self, crate::drop::Drop> {
|
||||
let mut db = Self {
|
||||
enums: HashMap::new(),
|
||||
types: HashMap::new(),
|
||||
relations: HashMap::new(),
|
||||
puncs: HashMap::new(),
|
||||
schemas: HashMap::new(),
|
||||
stems: HashMap::new(),
|
||||
descendants: HashMap::new(),
|
||||
depths: HashMap::new(),
|
||||
#[cfg(not(test))]
|
||||
executor: Box::new(SpiExecutor::new()),
|
||||
#[cfg(test)]
|
||||
executor: Box::new(MockExecutor::new()),
|
||||
};
|
||||
|
||||
if let Some(arr) = val.get("enums").and_then(|v| v.as_array()) {
|
||||
@ -47,6 +74,18 @@ impl Database {
|
||||
}
|
||||
}
|
||||
|
||||
let mut raw_relations = Vec::new();
|
||||
if let Some(arr) = val.get("relations").and_then(|v| v.as_array()) {
|
||||
for item in arr {
|
||||
match serde_json::from_value::<Relation>(item.clone()) {
|
||||
Ok(def) => {
|
||||
raw_relations.push(def);
|
||||
}
|
||||
Err(e) => println!("DATABASE RELATION PARSE FAILED: {:?}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(arr) = val.get("puncs").and_then(|v| v.as_array()) {
|
||||
for item in arr {
|
||||
if let Ok(def) = serde_json::from_value::<Punc>(item.clone()) {
|
||||
@ -69,16 +108,44 @@ impl Database {
|
||||
}
|
||||
}
|
||||
|
||||
let _ = db.compile();
|
||||
db
|
||||
db.compile(raw_relations)?;
|
||||
Ok(db)
|
||||
}
|
||||
|
||||
/// Override the default executor for unit testing
|
||||
pub fn with_executor(mut self, executor: Box<dyn DatabaseExecutor + Send + Sync>) -> Self {
|
||||
self.executor = executor;
|
||||
self
|
||||
}
|
||||
|
||||
/// Executes a query expecting a single JSONB array return, representing rows.
|
||||
pub fn query(&self, sql: &str, args: Option<&[Value]>) -> Result<Value, String> {
|
||||
self.executor.query(sql, args)
|
||||
}
|
||||
|
||||
/// Executes an operation (INSERT, UPDATE, DELETE, or pg_notify) that does not return rows.
|
||||
pub fn execute(&self, sql: &str, args: Option<&[Value]>) -> Result<(), String> {
|
||||
self.executor.execute(sql, args)
|
||||
}
|
||||
|
||||
/// Returns the current authenticated user's ID
|
||||
pub fn auth_user_id(&self) -> Result<String, String> {
|
||||
self.executor.auth_user_id()
|
||||
}
|
||||
|
||||
/// Returns the current transaction timestamp
|
||||
pub fn timestamp(&self) -> Result<String, String> {
|
||||
self.executor.timestamp()
|
||||
}
|
||||
|
||||
/// Organizes the graph of the database, compiling regex, format functions, and caching relationships.
|
||||
fn compile(&mut self) -> Result<(), String> {
|
||||
pub fn compile(&mut self, raw_relations: Vec<Relation>) -> Result<(), crate::drop::Drop> {
|
||||
self.collect_schemas();
|
||||
self.collect_depths();
|
||||
self.collect_descendants();
|
||||
self.collect_relations(raw_relations);
|
||||
self.compile_schemas();
|
||||
self.collect_stems()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -161,6 +228,95 @@ impl Database {
|
||||
self.descendants = descendants;
|
||||
}
|
||||
|
||||
fn collect_relations(&mut self, raw_relations: Vec<Relation>) {
|
||||
let mut edges: HashMap<(String, String), Vec<Relation>> = HashMap::new();
|
||||
|
||||
// For every relation, map it across all polymorphic inheritance permutations
|
||||
for relation in raw_relations {
|
||||
if let Some(_source_type_def) = self.types.get(&relation.source_type) {
|
||||
if let Some(_dest_type_def) = self.types.get(&relation.destination_type) {
|
||||
let mut src_descendants = Vec::new();
|
||||
let mut dest_descendants = Vec::new();
|
||||
|
||||
for (t_name, t_def) in &self.types {
|
||||
if t_def.hierarchy.contains(&relation.source_type) {
|
||||
src_descendants.push(t_name.clone());
|
||||
}
|
||||
if t_def.hierarchy.contains(&relation.destination_type) {
|
||||
dest_descendants.push(t_name.clone());
|
||||
}
|
||||
}
|
||||
|
||||
for p_type in &src_descendants {
|
||||
for c_type in &dest_descendants {
|
||||
// Ignore entity <-> entity generic fallbacks, they aren't useful edges
|
||||
if p_type == "entity" && c_type == "entity" {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Forward edge
|
||||
edges
|
||||
.entry((p_type.clone(), c_type.clone()))
|
||||
.or_default()
|
||||
.push(relation.clone());
|
||||
|
||||
// Reverse edge (only if types are different to avoid duplicating self-referential edges like activity parent_id)
|
||||
if p_type != c_type {
|
||||
edges
|
||||
.entry((c_type.clone(), p_type.clone()))
|
||||
.or_default()
|
||||
.push(relation.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
self.relations = edges;
|
||||
}
|
||||
|
||||
pub fn get_relation(
|
||||
&self,
|
||||
parent_type: &str,
|
||||
child_type: &str,
|
||||
prop_name: &str,
|
||||
relative_keys: Option<&Vec<String>>,
|
||||
) -> Option<&Relation> {
|
||||
if let Some(relations) = self
|
||||
.relations
|
||||
.get(&(parent_type.to_string(), child_type.to_string()))
|
||||
{
|
||||
if relations.len() == 1 {
|
||||
return Some(&relations[0]);
|
||||
}
|
||||
|
||||
// Reduce ambiguity with prefix
|
||||
for rel in relations {
|
||||
if let Some(prefix) = &rel.prefix {
|
||||
if prefix == prop_name {
|
||||
return Some(rel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reduce ambiguity by checking if relative payload OMITS the prefix (M:M heuristic)
|
||||
if let Some(keys) = relative_keys {
|
||||
let mut missing_prefix_rels = Vec::new();
|
||||
for rel in relations {
|
||||
if let Some(prefix) = &rel.prefix {
|
||||
if !keys.contains(prefix) {
|
||||
missing_prefix_rels.push(rel);
|
||||
}
|
||||
}
|
||||
}
|
||||
if missing_prefix_rels.len() == 1 {
|
||||
return Some(missing_prefix_rels[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn collect_descendants_recursively(
|
||||
target: &str,
|
||||
direct_refs: &HashMap<String, Vec<String>>,
|
||||
@ -184,4 +340,236 @@ impl Database {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn collect_stems(&mut self) -> Result<(), crate::drop::Drop> {
|
||||
let mut db_stems: HashMap<String, HashMap<String, Arc<Stem>>> = HashMap::new();
|
||||
let mut errors: Vec<crate::drop::Error> = Vec::new();
|
||||
|
||||
let schema_ids: Vec<String> = self.schemas.keys().cloned().collect();
|
||||
for schema_id in schema_ids {
|
||||
if let Some(schema) = self.schemas.get(&schema_id) {
|
||||
let mut inner_map = HashMap::new();
|
||||
Self::discover_stems(
|
||||
self,
|
||||
&schema_id,
|
||||
schema,
|
||||
String::from(""),
|
||||
None,
|
||||
None,
|
||||
false,
|
||||
&mut inner_map,
|
||||
Vec::new(),
|
||||
&mut errors,
|
||||
);
|
||||
if !inner_map.is_empty() {
|
||||
db_stems.insert(schema_id, inner_map);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.stems = db_stems;
|
||||
|
||||
if !errors.is_empty() {
|
||||
return Err(crate::drop::Drop::with_errors(errors));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn discover_stems(
|
||||
db: &Database,
|
||||
root_schema_id: &str,
|
||||
schema: &Schema,
|
||||
current_path: String,
|
||||
parent_type: Option<String>,
|
||||
property_name: Option<String>,
|
||||
is_polymorphic: bool,
|
||||
inner_map: &mut HashMap<String, Arc<Stem>>,
|
||||
seen_entities: Vec<String>,
|
||||
errors: &mut Vec<crate::drop::Error>,
|
||||
) {
|
||||
let mut is_entity = false;
|
||||
let mut entity_type = String::new();
|
||||
|
||||
// First check if the Schema's $id is a native Database Type
|
||||
if let Some(ref id) = schema.obj.id {
|
||||
let parts: Vec<&str> = id.split('.').collect();
|
||||
if let Some(last_seg) = parts.last() {
|
||||
if db.types.contains_key(*last_seg) {
|
||||
is_entity = true;
|
||||
entity_type = last_seg.to_string();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If not found via $id, check the $ref pointer
|
||||
// This allows ad-hoc schemas (like `save_person.response`) to successfully adopt the Type of what they $ref
|
||||
if !is_entity {
|
||||
if let Some(ref r) = schema.obj.r#ref {
|
||||
let parts: Vec<&str> = r.split('.').collect();
|
||||
if let Some(last_seg) = parts.last() {
|
||||
if db.types.contains_key(*last_seg) {
|
||||
is_entity = true;
|
||||
entity_type = last_seg.to_string();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if is_entity {
|
||||
if seen_entities.contains(&entity_type) {
|
||||
return; // Break cyclical schemas!
|
||||
}
|
||||
}
|
||||
|
||||
let mut relation_col = None;
|
||||
if is_entity {
|
||||
if let (Some(pt), Some(prop)) = (&parent_type, &property_name) {
|
||||
let expected_col = format!("{}_id", prop);
|
||||
let mut found = false;
|
||||
|
||||
if let Some(rel) = db.get_relation(pt, &entity_type, prop, None) {
|
||||
if rel.source_columns.contains(&expected_col) {
|
||||
relation_col = Some(expected_col.clone());
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
relation_col = Some(expected_col);
|
||||
}
|
||||
}
|
||||
|
||||
let mut final_path = current_path.clone();
|
||||
if is_polymorphic && !final_path.is_empty() && !final_path.ends_with(&entity_type) {
|
||||
if final_path.ends_with(".#") {
|
||||
final_path = format!("{}(type==\"{}\")", final_path, entity_type);
|
||||
} else {
|
||||
final_path = format!("{}#(type==\"{}\")", final_path, entity_type);
|
||||
}
|
||||
}
|
||||
|
||||
let stem = Stem {
|
||||
r#type: entity_type.clone(),
|
||||
relation: relation_col,
|
||||
schema: Arc::new(schema.clone()),
|
||||
};
|
||||
inner_map.insert(final_path, Arc::new(stem));
|
||||
}
|
||||
|
||||
let next_parent = if is_entity {
|
||||
Some(entity_type.clone())
|
||||
} else {
|
||||
parent_type.clone()
|
||||
};
|
||||
|
||||
let pass_seen = if is_entity {
|
||||
let mut ns = seen_entities.clone();
|
||||
ns.push(entity_type.clone());
|
||||
ns
|
||||
} else {
|
||||
seen_entities.clone()
|
||||
};
|
||||
|
||||
// Properties branch
|
||||
if let Some(props) = &schema.obj.properties {
|
||||
for (k, v) in props {
|
||||
// Standard Property Pathing
|
||||
let next_path = if current_path.is_empty() {
|
||||
k.clone()
|
||||
} else {
|
||||
format!("{}.{}", current_path, k)
|
||||
};
|
||||
|
||||
Self::discover_stems(
|
||||
db,
|
||||
root_schema_id,
|
||||
v,
|
||||
next_path,
|
||||
next_parent.clone(),
|
||||
Some(k.clone()),
|
||||
false,
|
||||
inner_map,
|
||||
pass_seen.clone(),
|
||||
errors,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Array Item branch
|
||||
if let Some(items) = &schema.obj.items {
|
||||
let next_path = if current_path.is_empty() {
|
||||
String::from("#")
|
||||
} else {
|
||||
format!("{}.#", current_path)
|
||||
};
|
||||
|
||||
Self::discover_stems(
|
||||
db,
|
||||
root_schema_id,
|
||||
items,
|
||||
next_path,
|
||||
next_parent.clone(),
|
||||
property_name.clone(),
|
||||
false,
|
||||
inner_map,
|
||||
pass_seen.clone(),
|
||||
errors,
|
||||
);
|
||||
}
|
||||
|
||||
// Follow external reference if we didn't just crawl local properties
|
||||
if schema.obj.properties.is_none() && schema.obj.items.is_none() && schema.obj.one_of.is_none()
|
||||
{
|
||||
if let Some(ref r) = schema.obj.r#ref {
|
||||
if let Some(target_schema) = db.schemas.get(r) {
|
||||
Self::discover_stems(
|
||||
db,
|
||||
root_schema_id,
|
||||
target_schema,
|
||||
current_path.clone(),
|
||||
next_parent.clone(),
|
||||
property_name.clone(),
|
||||
is_polymorphic,
|
||||
inner_map,
|
||||
seen_entities.clone(),
|
||||
errors,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Polymorphism branch
|
||||
if let Some(arr) = &schema.obj.one_of {
|
||||
for v in arr {
|
||||
Self::discover_stems(
|
||||
db,
|
||||
root_schema_id,
|
||||
v.as_ref(),
|
||||
current_path.clone(),
|
||||
next_parent.clone(),
|
||||
property_name.clone(),
|
||||
true,
|
||||
inner_map,
|
||||
pass_seen.clone(),
|
||||
errors,
|
||||
);
|
||||
}
|
||||
}
|
||||
if let Some(arr) = &schema.obj.all_of {
|
||||
for v in arr {
|
||||
Self::discover_stems(
|
||||
db,
|
||||
root_schema_id,
|
||||
v.as_ref(),
|
||||
current_path.clone(),
|
||||
next_parent.clone(),
|
||||
property_name.clone(),
|
||||
is_polymorphic,
|
||||
inner_map,
|
||||
pass_seen.clone(),
|
||||
errors,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
12
src/database/relation.rs
Normal file
12
src/database/relation.rs
Normal file
@ -0,0 +1,12 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||
#[serde(default)]
|
||||
pub struct Relation {
|
||||
pub constraint: String,
|
||||
pub source_type: String,
|
||||
pub source_columns: Vec<String>,
|
||||
pub destination_type: String,
|
||||
pub destination_columns: Vec<String>,
|
||||
pub prefix: Option<String>,
|
||||
}
|
||||
@ -5,119 +5,166 @@ use std::sync::Arc;
|
||||
|
||||
// Schema mirrors the Go Punc Generator's schema struct for consistency.
|
||||
// It is an order-preserving representation of a JSON Schema.
|
||||
pub fn deserialize_some<'de, D>(deserializer: D) -> Result<Option<Value>, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let v = Value::deserialize(deserializer)?;
|
||||
Ok(Some(v))
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||
pub struct SchemaObject {
|
||||
// Core Schema Keywords
|
||||
#[serde(rename = "$id")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub id: Option<String>,
|
||||
#[serde(rename = "$ref")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub r#ref: Option<String>,
|
||||
/*
|
||||
Note: The `Ref` field in the Go struct is a pointer populated by the linker.
|
||||
In Rust, we might handle this differently (e.g., separate lookup or Rc/Arc),
|
||||
so we omit the direct recursive `Ref` field for now and rely on `ref_string`.
|
||||
*/
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub description: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub title: Option<String>,
|
||||
#[serde(default)] // Allow missing type
|
||||
#[serde(rename = "type")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub type_: Option<SchemaTypeOrArray>, // Handles string or array of strings
|
||||
|
||||
// Object Keywords
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub properties: Option<BTreeMap<String, Arc<Schema>>>,
|
||||
#[serde(rename = "patternProperties")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub pattern_properties: Option<BTreeMap<String, Arc<Schema>>>,
|
||||
#[serde(rename = "additionalProperties")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub additional_properties: Option<Arc<Schema>>,
|
||||
#[serde(rename = "$family")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub family: Option<String>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub required: Option<Vec<String>>,
|
||||
|
||||
// dependencies can be schema dependencies or property dependencies
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub dependencies: Option<BTreeMap<String, Dependency>>,
|
||||
|
||||
// Array Keywords
|
||||
#[serde(rename = "items")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub items: Option<Arc<Schema>>,
|
||||
#[serde(rename = "prefixItems")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub prefix_items: Option<Vec<Arc<Schema>>>,
|
||||
|
||||
// String Validation
|
||||
#[serde(rename = "minLength")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub min_length: Option<f64>,
|
||||
#[serde(rename = "maxLength")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub max_length: Option<f64>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub pattern: Option<String>,
|
||||
|
||||
// Array Validation
|
||||
#[serde(rename = "minItems")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub min_items: Option<f64>,
|
||||
#[serde(rename = "maxItems")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub max_items: Option<f64>,
|
||||
#[serde(rename = "uniqueItems")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub unique_items: Option<bool>,
|
||||
#[serde(rename = "contains")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub contains: Option<Arc<Schema>>,
|
||||
#[serde(rename = "minContains")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub min_contains: Option<f64>,
|
||||
#[serde(rename = "maxContains")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub max_contains: Option<f64>,
|
||||
|
||||
// Object Validation
|
||||
#[serde(rename = "minProperties")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub min_properties: Option<f64>,
|
||||
#[serde(rename = "maxProperties")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub max_properties: Option<f64>,
|
||||
#[serde(rename = "propertyNames")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub property_names: Option<Arc<Schema>>,
|
||||
|
||||
// Numeric Validation
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub format: Option<String>,
|
||||
#[serde(rename = "enum")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub enum_: Option<Vec<Value>>, // `enum` is a reserved keyword in Rust
|
||||
#[serde(
|
||||
default,
|
||||
rename = "const",
|
||||
deserialize_with = "crate::validator::util::deserialize_some"
|
||||
deserialize_with = "crate::database::schema::deserialize_some"
|
||||
)]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub const_: Option<Value>,
|
||||
|
||||
// Numeric Validation
|
||||
#[serde(rename = "multipleOf")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub multiple_of: Option<f64>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub minimum: Option<f64>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub maximum: Option<f64>,
|
||||
#[serde(rename = "exclusiveMinimum")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub exclusive_minimum: Option<f64>,
|
||||
#[serde(rename = "exclusiveMaximum")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub exclusive_maximum: Option<f64>,
|
||||
|
||||
// Combining Keywords
|
||||
#[serde(rename = "allOf")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub all_of: Option<Vec<Arc<Schema>>>,
|
||||
#[serde(rename = "anyOf")]
|
||||
pub any_of: Option<Vec<Arc<Schema>>>,
|
||||
#[serde(rename = "oneOf")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub one_of: Option<Vec<Arc<Schema>>>,
|
||||
#[serde(rename = "not")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub not: Option<Arc<Schema>>,
|
||||
#[serde(rename = "if")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub if_: Option<Arc<Schema>>,
|
||||
#[serde(rename = "then")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub then_: Option<Arc<Schema>>,
|
||||
#[serde(rename = "else")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub else_: Option<Arc<Schema>>,
|
||||
|
||||
// Custom Vocabularies
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub form: Option<Vec<String>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub display: Option<Vec<String>>,
|
||||
#[serde(rename = "enumNames")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub enum_names: Option<Vec<String>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub control: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub actions: Option<BTreeMap<String, Action>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub computer: Option<String>,
|
||||
#[serde(default)]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub extensible: Option<bool>,
|
||||
|
||||
#[serde(skip)]
|
||||
@ -238,9 +285,6 @@ impl Schema {
|
||||
if let Some(arr) = &mut self.obj.all_of {
|
||||
map_arr(arr);
|
||||
}
|
||||
if let Some(arr) = &mut self.obj.any_of {
|
||||
map_arr(arr);
|
||||
}
|
||||
if let Some(arr) = &mut self.obj.one_of {
|
||||
map_arr(arr);
|
||||
}
|
||||
@ -300,7 +344,6 @@ impl<'de> Deserialize<'de> for Schema {
|
||||
&& obj.enum_.is_none()
|
||||
&& obj.const_.is_none()
|
||||
&& obj.all_of.is_none()
|
||||
&& obj.any_of.is_none()
|
||||
&& obj.one_of.is_none()
|
||||
&& obj.not.is_none()
|
||||
&& obj.if_.is_none()
|
||||
@ -329,7 +372,9 @@ pub enum SchemaTypeOrArray {
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Action {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub navigate: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub punc: Option<String>,
|
||||
}
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
|
||||
12
src/database/stem.rs
Normal file
12
src/database/stem.rs
Normal file
@ -0,0 +1,12 @@
|
||||
use crate::database::schema::Schema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Stem {
|
||||
pub r#type: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub relation: Option<String>,
|
||||
|
||||
pub schema: Arc<Schema>,
|
||||
}
|
||||
@ -23,7 +23,8 @@ pub struct Type {
|
||||
pub hierarchy: Vec<String>,
|
||||
#[serde(default)]
|
||||
pub variations: HashSet<String>,
|
||||
pub relationship: Option<bool>,
|
||||
#[serde(default)]
|
||||
pub relationship: bool,
|
||||
#[serde(default)]
|
||||
pub fields: Vec<String>,
|
||||
pub grouped_fields: Option<Value>,
|
||||
|
||||
@ -67,6 +67,10 @@ pub struct Error {
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct ErrorDetails {
|
||||
pub path: String,
|
||||
// Extensions can be added here (package, cause, etc)
|
||||
// For now, validator only provides path
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub cause: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub context: Option<Vec<String>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub schema: Option<String>,
|
||||
}
|
||||
|
||||
@ -1,79 +0,0 @@
|
||||
# Entity Engine (jspg)
|
||||
|
||||
## Overview
|
||||
|
||||
This document outlines the architecture for moving the complex, CPU-bound row merging (`merge_entity`) and dynamic querying (`query_entity`) functionality out of PL/pgSQL and directly into the Rust-based `jspg` extension.
|
||||
|
||||
By treating the `jspg` schema registry as the absolute Single Source of Truth, we can leverage Rust and the Postgres query planner (via SPI) to achieve near O(1) execution planning for deeply nested reads, complex relational writes, and partial hydration beats.
|
||||
|
||||
## The Problem
|
||||
|
||||
Historically, `agreego.merge_entity` (PL/pgSQL) handled nested writes by segmenting JSON, resolving types, searching hierarchies, and dynamically concatenating `INSERT`/`UPDATE` statements. `agreego.query_entity` was conceived to do the same for reads (handling base security, inheritance JOINs, and filtering automatically).
|
||||
|
||||
However, this design hits three major limitations:
|
||||
1. **CPU Bound Operations**: PL/pgSQL is comparatively slow at complex string concatenation and massive JSON graph traversals.
|
||||
2. **Query Planning Cache Busting**: Generating massive, dynamic SQL strings prevents Postgres from caching query plans. `EXECUTE dynamic_sql` forces the planner to re-evaluate statistics and execution paths on every function call, leading to extreme latency spikes at scale.
|
||||
3. **The Hydration Beat Problem**: The Punc framework requires fetching specific UI "fragments" (e.g. just the `target` of a specific `contact` array element) to feed WebSockets. Hand-rolling CTEs for every possible sub-tree permutation to serve beats will quickly become unmaintainable.
|
||||
|
||||
## The Solution: Semantic Engine Database
|
||||
|
||||
By migrating `merge_entity` and `query_entity` to `jspg`, we turn the database into a pre-compiled Semantic Engine.
|
||||
|
||||
1. **Schema-to-SQL Compilation**: During the connection lifecycle (`cache_json_schemas()`), `jspg` statically analyzes the JSON Schema AST. It acts as a compiler, translating the schema layout into perfectly optimized, multi-JOIN SQL query strings for *every* node/fragment in the schema.
|
||||
2. **Prepared Statements (SPI)**: `jspg` feeds these computed SQL strings into the Postgres SPI (Server Programming Interface) using `Spi::prepare()`. Postgres calculates the query execution plan *once* and caches it in memory.
|
||||
3. **Instant Execution**: When a Punc needs data, `jspg` retrieves the cached PreparedStatement, securely binds binary parameters, and executes the pre-planned query instantly.
|
||||
|
||||
## Architecture
|
||||
|
||||
### 1. The `cache_json_schemas()` Expansion
|
||||
The initialization function must now ingest `types` and `agreego.relation` data so the internal `Registry` holds the full Relational Graph.
|
||||
|
||||
During schema compilation, if a schema is associated with a database Type, it triggers the **SQL Compiler Phase**:
|
||||
- It builds a table-resolution AST mapping to `JOIN` clauses based on foreign keys.
|
||||
- It translates JSON schema properties to `SELECT jsonb_build_object(...)`.
|
||||
- It generates static SQL for `INSERT`, `UPDATE`, and `SELECT` (including path-based fragment SELECTs).
|
||||
- It calls `Spi::prepare()` to cache these plans inside the Session Context.
|
||||
|
||||
### 2. `agreego.query_entity` (Reads)
|
||||
* **API**: `agreego.query_entity(schema_id TEXT, fragment_path TEXT, cue JSONB)`
|
||||
* **Execution**:
|
||||
* Rust locates the target Schema in memory.
|
||||
* It uses the `fragment_path` (e.g., `/` for a full read, or `/contacts/0/target` for a hydration beat) to fetch the exact PreparedStatement.
|
||||
* It binds variables (Row Level Security IDs, filtering, pagination limit/offset) parsed from the `cue`.
|
||||
* SPI returns the heavily nested, pre-aggregated `JSONB` instantly.
|
||||
|
||||
### 3. Unified Aggregations & Computeds (Schema `query` objects)
|
||||
We replace the concept of a complex string parser (PEL) with native structured JSON JSON objects using the `query` keyword.
|
||||
|
||||
A structured `query` block in the schema:
|
||||
```json
|
||||
"total": {
|
||||
"type": "number",
|
||||
"readOnly": true,
|
||||
"query": {
|
||||
"aggregate": "sum",
|
||||
"source": "lines",
|
||||
"field": "amount"
|
||||
}
|
||||
}
|
||||
```
|
||||
* **Frontend (Dart)**: The Go generator parses the JSON object directly and emits the native UI aggregation code (e.g. `lines.fold(...)`) for instant UI updates before the server responds.
|
||||
* **Backend (jspg)**: The Rust SQL compiler natively deserializes the `query` object into an internal struct. It recognizes the `aggregate` instruction and outputs a Postgres native aggregation: `(SELECT SUM(amount) FROM agreego.invoice_line WHERE invoice_id = t1.id)` as a column in the prepared `SELECT` statement.
|
||||
* **Unification**: The database-calculated value acts as the authoritative truth, synchronizing and correcting the client automatically on the resulting `beat`.
|
||||
|
||||
### 4. `agreego.merge_entity` (Writes)
|
||||
* **API**: `agreego.merge_entity(cue JSONB)`
|
||||
* **Execution**:
|
||||
* Parses the incoming `cue` JSON via `serde_json` at C-like speeds.
|
||||
* Recursively validates and *constructively masks* the tree against the strict schema.
|
||||
* Traverses the relational graph (which is fully loaded in the `jspg` registry).
|
||||
* Binds the new values directly into the cached `INSERT` or `UPDATE` SPI prepared statements for each table in the hierarchy.
|
||||
* Evaluates field differences and natively uses `pg_notify` to fire atomic row-level changes for the Go Beat framework.
|
||||
|
||||
## Roadmap
|
||||
|
||||
1. **Relational Ingestion**: Update `cache_json_schemas` to pass relational metadata (`agreego.relation` rows) into the `jspg` registry cache.
|
||||
2. **The SQL Compiler**: Build the AST-to-String compiler in Rust that reads properties, `$ref`s, and `$family` trees to piece together generic SQL.
|
||||
3. **SPI Caching**: Integrate `Spi::prepare` into the `Validator` creation phase.
|
||||
4. **Rust `merge_entity`**: Port the constructive structural extraction loop from PL/pgSQL to Rust.
|
||||
5. **Rust `query_entity`**: Abstract the query runtime, mapping Punc JSON `filters` arrays to SPI-bound parameters safely.
|
||||
12
src/jspg.rs
12
src/jspg.rs
@ -12,18 +12,18 @@ pub struct Jspg {
|
||||
}
|
||||
|
||||
impl Jspg {
|
||||
pub fn new(database_val: &serde_json::Value) -> Self {
|
||||
let database_instance = Database::new(database_val);
|
||||
pub fn new(database_val: &serde_json::Value) -> Result<Self, crate::drop::Drop> {
|
||||
let database_instance = Database::new(database_val)?;
|
||||
let database = Arc::new(database_instance);
|
||||
let validator = Validator::new(database.clone());
|
||||
let queryer = Queryer::new();
|
||||
let merger = Merger::new();
|
||||
let queryer = Queryer::new(database.clone());
|
||||
let merger = Merger::new(database.clone());
|
||||
|
||||
Self {
|
||||
Ok(Self {
|
||||
database,
|
||||
validator,
|
||||
queryer,
|
||||
merger,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
214
src/lib.rs
214
src/lib.rs
@ -1,7 +1,12 @@
|
||||
#[cfg(not(test))]
|
||||
use pgrx::*;
|
||||
|
||||
#[cfg(not(test))]
|
||||
pg_module_magic!();
|
||||
|
||||
#[cfg(test)]
|
||||
pub struct JsonB(pub serde_json::Value);
|
||||
|
||||
pub mod database;
|
||||
pub mod drop;
|
||||
pub mod jspg;
|
||||
@ -9,7 +14,6 @@ pub mod merger;
|
||||
pub mod queryer;
|
||||
pub mod validator;
|
||||
|
||||
use serde_json::json;
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
@ -21,24 +25,79 @@ lazy_static::lazy_static! {
|
||||
static ref GLOBAL_JSPG: RwLock<Option<Arc<jspg::Jspg>>> = RwLock::new(None);
|
||||
}
|
||||
|
||||
#[pg_extern(strict)]
|
||||
pub fn jspg_cache_database(database: JsonB) -> JsonB {
|
||||
let new_jspg = crate::jspg::Jspg::new(&database.0);
|
||||
let new_arc = Arc::new(new_jspg);
|
||||
|
||||
// 3. ATOMIC SWAP
|
||||
{
|
||||
let mut lock = GLOBAL_JSPG.write().unwrap();
|
||||
*lock = Some(new_arc);
|
||||
}
|
||||
|
||||
let drop = crate::drop::Drop::success();
|
||||
fn jspg_failure() -> JsonB {
|
||||
let error = crate::drop::Error {
|
||||
code: "ENGINE_NOT_INITIALIZED".to_string(),
|
||||
message: "JSPG extension has not been initialized via jspg_setup".to_string(),
|
||||
details: crate::drop::ErrorDetails {
|
||||
path: "".to_string(),
|
||||
cause: None,
|
||||
context: None,
|
||||
schema: None,
|
||||
},
|
||||
};
|
||||
let drop = crate::drop::Drop::with_errors(vec![error]);
|
||||
JsonB(serde_json::to_value(drop).unwrap())
|
||||
}
|
||||
|
||||
#[cfg_attr(not(test), pg_extern(strict))]
|
||||
pub fn jspg_setup(database: JsonB) -> JsonB {
|
||||
match crate::jspg::Jspg::new(&database.0) {
|
||||
Ok(new_jspg) => {
|
||||
let new_arc = Arc::new(new_jspg);
|
||||
|
||||
// 3. ATOMIC SWAP
|
||||
{
|
||||
let mut lock = GLOBAL_JSPG.write().unwrap();
|
||||
*lock = Some(new_arc);
|
||||
}
|
||||
|
||||
let drop = crate::drop::Drop::success();
|
||||
JsonB(serde_json::to_value(drop).unwrap())
|
||||
}
|
||||
Err(drop) => JsonB(serde_json::to_value(drop).unwrap()),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(not(test), pg_extern)]
|
||||
pub fn jspg_merge(data: JsonB) -> JsonB {
|
||||
// Try to acquire a read lock to get a clone of the Engine Arc
|
||||
let engine_opt = {
|
||||
let lock = GLOBAL_JSPG.read().unwrap();
|
||||
lock.clone()
|
||||
};
|
||||
|
||||
match engine_opt {
|
||||
Some(engine) => {
|
||||
let drop = engine.merger.merge(data.0);
|
||||
JsonB(serde_json::to_value(drop).unwrap())
|
||||
}
|
||||
None => jspg_failure(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(not(test), pg_extern)]
|
||||
pub fn jspg_query(schema_id: &str, stem: Option<&str>, filters: Option<JsonB>) -> JsonB {
|
||||
let engine_opt = {
|
||||
let lock = GLOBAL_JSPG.read().unwrap();
|
||||
lock.clone()
|
||||
};
|
||||
|
||||
match engine_opt {
|
||||
Some(engine) => {
|
||||
let drop = engine
|
||||
.queryer
|
||||
.query(schema_id, stem, filters.as_ref().map(|f| &f.0));
|
||||
JsonB(serde_json::to_value(drop).unwrap())
|
||||
}
|
||||
None => jspg_failure(),
|
||||
}
|
||||
}
|
||||
|
||||
// `mask_json_schema` has been removed as the mask architecture is fully replaced by Spi string queries during DB interactions.
|
||||
|
||||
#[pg_extern(strict, parallel_safe)]
|
||||
pub fn validate_json_schema(schema_id: &str, instance: JsonB) -> JsonB {
|
||||
#[cfg_attr(not(test), pg_extern(strict, parallel_safe))]
|
||||
pub fn jspg_validate(schema_id: &str, instance: JsonB) -> JsonB {
|
||||
// 1. Acquire Snapshot
|
||||
let jspg_arc = {
|
||||
let lock = GLOBAL_JSPG.read().unwrap();
|
||||
@ -47,99 +106,56 @@ pub fn validate_json_schema(schema_id: &str, instance: JsonB) -> JsonB {
|
||||
|
||||
// 2. Validate (Lock-Free)
|
||||
if let Some(engine) = jspg_arc {
|
||||
match engine.validator.validate(schema_id, &instance.0) {
|
||||
Ok(result) => {
|
||||
if result.is_valid() {
|
||||
let drop = crate::drop::Drop::success();
|
||||
JsonB(serde_json::to_value(drop).unwrap())
|
||||
} else {
|
||||
let errors: Vec<crate::drop::Error> = result
|
||||
.errors
|
||||
.into_iter()
|
||||
.map(|e| crate::drop::Error {
|
||||
code: e.code,
|
||||
message: e.message,
|
||||
details: crate::drop::ErrorDetails { path: e.path },
|
||||
})
|
||||
.collect();
|
||||
let drop = crate::drop::Drop::with_errors(errors);
|
||||
JsonB(serde_json::to_value(drop).unwrap())
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
let error = crate::drop::Error {
|
||||
code: e.code,
|
||||
message: e.message,
|
||||
details: crate::drop::ErrorDetails { path: e.path },
|
||||
};
|
||||
let drop = crate::drop::Drop::with_errors(vec![error]);
|
||||
JsonB(serde_json::to_value(drop).unwrap())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let error = crate::drop::Error {
|
||||
code: "VALIDATOR_NOT_INITIALIZED".to_string(),
|
||||
message: "The JSPG database has not been cached yet. Run jspg_cache_database()".to_string(),
|
||||
details: crate::drop::ErrorDetails {
|
||||
path: "".to_string(),
|
||||
},
|
||||
};
|
||||
let drop = crate::drop::Drop::with_errors(vec![error]);
|
||||
let drop = engine.validator.validate(schema_id, &instance.0);
|
||||
JsonB(serde_json::to_value(drop).unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
#[pg_extern(strict, parallel_safe)]
|
||||
pub fn json_schema_cached(schema_id: &str) -> bool {
|
||||
if let Some(engine) = GLOBAL_JSPG.read().unwrap().as_ref() {
|
||||
match engine
|
||||
.validator
|
||||
.validate(schema_id, &serde_json::Value::Null)
|
||||
{
|
||||
Err(e) if e.code == "SCHEMA_NOT_FOUND" => false,
|
||||
_ => true,
|
||||
}
|
||||
} else {
|
||||
false
|
||||
jspg_failure()
|
||||
}
|
||||
}
|
||||
|
||||
#[pg_extern(strict)]
|
||||
pub fn clear_json_schemas() -> JsonB {
|
||||
#[cfg_attr(not(test), pg_extern)]
|
||||
pub fn jspg_schemas() -> JsonB {
|
||||
let engine_opt = {
|
||||
let lock = GLOBAL_JSPG.read().unwrap();
|
||||
lock.clone()
|
||||
};
|
||||
|
||||
match engine_opt {
|
||||
Some(engine) => {
|
||||
let schemas_json = serde_json::to_value(&engine.database.schemas)
|
||||
.unwrap_or(serde_json::Value::Object(serde_json::Map::new()));
|
||||
let drop = crate::drop::Drop::success_with_val(schemas_json);
|
||||
JsonB(serde_json::to_value(drop).unwrap())
|
||||
}
|
||||
None => jspg_failure(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(not(test), pg_extern)]
|
||||
pub fn jspg_stems() -> JsonB {
|
||||
let engine_opt = {
|
||||
let lock = GLOBAL_JSPG.read().unwrap();
|
||||
lock.clone()
|
||||
};
|
||||
|
||||
match engine_opt {
|
||||
Some(engine) => {
|
||||
let stems_json = serde_json::to_value(&engine.database.stems)
|
||||
.unwrap_or(serde_json::Value::Object(serde_json::Map::new()));
|
||||
let drop = crate::drop::Drop::success_with_val(stems_json);
|
||||
JsonB(serde_json::to_value(drop).unwrap())
|
||||
}
|
||||
None => jspg_failure(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(not(test), pg_extern(strict))]
|
||||
pub fn jspg_teardown() -> JsonB {
|
||||
let mut lock = GLOBAL_JSPG.write().unwrap();
|
||||
*lock = None;
|
||||
let drop = crate::drop::Drop::success();
|
||||
JsonB(serde_json::to_value(drop).unwrap())
|
||||
}
|
||||
|
||||
#[pg_extern(strict, parallel_safe)]
|
||||
pub fn show_json_schemas() -> JsonB {
|
||||
if let Some(engine) = GLOBAL_JSPG.read().unwrap().as_ref() {
|
||||
let mut keys = engine.validator.get_schema_ids();
|
||||
keys.sort();
|
||||
let drop = crate::drop::Drop::success_with_val(json!(keys));
|
||||
JsonB(serde_json::to_value(drop).unwrap())
|
||||
} else {
|
||||
let drop = crate::drop::Drop::success_with_val(json!([]));
|
||||
JsonB(serde_json::to_value(drop).unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "pg_test"))]
|
||||
#[pg_schema]
|
||||
mod tests {
|
||||
use pgrx::prelude::*;
|
||||
include!("tests/fixtures.rs");
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod pg_test {
|
||||
pub fn setup(_options: Vec<&str>) {
|
||||
// perform any initialization common to all tests
|
||||
}
|
||||
|
||||
pub fn postgresql_conf_options() -> Vec<&'static str> {
|
||||
// return any postgresql.conf settings that are required for your tests
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
pub mod tests;
|
||||
|
||||
24
src/merger/cache.rs
Normal file
24
src/merger/cache.rs
Normal file
@ -0,0 +1,24 @@
|
||||
use dashmap::DashMap;
|
||||
|
||||
pub struct StatementCache {
|
||||
/// Maps a Cache Key (String) -> SQL String (String)
|
||||
statements: DashMap<String, String>,
|
||||
}
|
||||
|
||||
impl StatementCache {
|
||||
pub fn new(_max_capacity: u64) -> Self {
|
||||
Self {
|
||||
statements: DashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Retrieve an existing statement name by key, or None if it missed
|
||||
pub fn get(&self, key: &str) -> Option<String> {
|
||||
self.statements.get(key).map(|v| v.clone())
|
||||
}
|
||||
|
||||
/// Insert a completely verified/compiled statement string into the cache
|
||||
pub fn insert(&self, key: String, sql: String) {
|
||||
self.statements.insert(key, sql);
|
||||
}
|
||||
}
|
||||
@ -1,15 +1,856 @@
|
||||
pub struct Merger {
|
||||
// To be implemented
|
||||
}
|
||||
//! The `merger` module handles executing Postgres SPI directives dynamically based on JSON payloads
|
||||
//! using the structurally isolated schema rules provided by the `Database` registry.
|
||||
|
||||
impl Default for Merger {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
pub mod cache;
|
||||
|
||||
use crate::database::Database;
|
||||
use serde_json::Value;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct Merger {
|
||||
pub db: Arc<Database>,
|
||||
pub cache: cache::StatementCache,
|
||||
}
|
||||
|
||||
impl Merger {
|
||||
pub fn new() -> Self {
|
||||
Self {}
|
||||
pub fn new(db: Arc<Database>) -> Self {
|
||||
Self {
|
||||
db,
|
||||
cache: cache::StatementCache::new(10_000),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn merge(&self, data: Value) -> crate::drop::Drop {
|
||||
let mut notifications_queue = Vec::new();
|
||||
|
||||
let result = self.merge_internal(data, &mut notifications_queue);
|
||||
|
||||
let val_resolved = match result {
|
||||
Ok(val) => val,
|
||||
Err(msg) => {
|
||||
return crate::drop::Drop::with_errors(vec![crate::drop::Error {
|
||||
code: "MERGE_FAILED".to_string(),
|
||||
message: msg,
|
||||
details: crate::drop::ErrorDetails {
|
||||
path: "".to_string(),
|
||||
cause: None,
|
||||
context: None,
|
||||
schema: None,
|
||||
},
|
||||
}]);
|
||||
}
|
||||
};
|
||||
|
||||
// Execute the globally collected, pre-ordered notifications last!
|
||||
for notify_sql in notifications_queue {
|
||||
if let Err(e) = self.db.execute(¬ify_sql, None) {
|
||||
return crate::drop::Drop::with_errors(vec![crate::drop::Error {
|
||||
code: "MERGE_FAILED".to_string(),
|
||||
message: format!("Executor Error in pre-ordered notify: {:?}", e),
|
||||
details: crate::drop::ErrorDetails {
|
||||
path: "".to_string(),
|
||||
cause: None,
|
||||
context: None,
|
||||
schema: None,
|
||||
},
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
let stripped_val = match val_resolved {
|
||||
Value::Object(mut map) => {
|
||||
let mut out = serde_json::Map::new();
|
||||
if let Some(id) = map.remove("id") {
|
||||
out.insert("id".to_string(), id);
|
||||
}
|
||||
Value::Object(out)
|
||||
}
|
||||
Value::Array(arr) => {
|
||||
let mut out_arr = Vec::new();
|
||||
for item in arr {
|
||||
if let Value::Object(mut map) = item {
|
||||
let mut out = serde_json::Map::new();
|
||||
if let Some(id) = map.remove("id") {
|
||||
out.insert("id".to_string(), id);
|
||||
}
|
||||
out_arr.push(Value::Object(out));
|
||||
} else {
|
||||
out_arr.push(Value::Null);
|
||||
}
|
||||
}
|
||||
Value::Array(out_arr)
|
||||
}
|
||||
other => other,
|
||||
};
|
||||
crate::drop::Drop::success_with_val(stripped_val)
|
||||
}
|
||||
|
||||
pub(crate) fn merge_internal(
|
||||
&self,
|
||||
data: Value,
|
||||
notifications: &mut Vec<String>,
|
||||
) -> Result<Value, String> {
|
||||
match data {
|
||||
Value::Array(items) => self.merge_array(items, notifications),
|
||||
Value::Object(map) => self.merge_object(map, notifications),
|
||||
_ => Err("Invalid merge payload: root must be an Object or Array".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
fn merge_array(
|
||||
&self,
|
||||
items: Vec<Value>,
|
||||
notifications: &mut Vec<String>,
|
||||
) -> Result<Value, String> {
|
||||
let mut resolved_items = Vec::new();
|
||||
for item in items {
|
||||
let resolved = self.merge_internal(item, notifications)?;
|
||||
resolved_items.push(resolved);
|
||||
}
|
||||
Ok(Value::Array(resolved_items))
|
||||
}
|
||||
|
||||
fn merge_object(
|
||||
&self,
|
||||
obj: serde_json::Map<String, Value>,
|
||||
notifications: &mut Vec<String>,
|
||||
) -> Result<Value, String> {
|
||||
let queue_start = notifications.len();
|
||||
|
||||
let type_name = match obj.get("type").and_then(|v| v.as_str()) {
|
||||
Some(t) => t.to_string(),
|
||||
None => return Err("Missing required 'type' field on object".to_string()),
|
||||
};
|
||||
|
||||
let type_def = match self.db.types.get(&type_name) {
|
||||
Some(t) => t,
|
||||
None => return Err(format!("Unknown entity type: {}", type_name)),
|
||||
};
|
||||
|
||||
// 1. Segment the entity: fields in type_def.fields are database fields, others are relationships
|
||||
let mut entity_fields = serde_json::Map::new();
|
||||
let mut entity_objects = serde_json::Map::new();
|
||||
let mut entity_arrays = serde_json::Map::new();
|
||||
|
||||
for (k, v) in obj {
|
||||
let is_field = type_def.fields.contains(&k) || k == "created";
|
||||
let typeof_v = match &v {
|
||||
Value::Object(_) => "object",
|
||||
Value::Array(_) => "array",
|
||||
_ => "other",
|
||||
};
|
||||
|
||||
if is_field {
|
||||
entity_fields.insert(k, v);
|
||||
} else if typeof_v == "object" {
|
||||
entity_objects.insert(k, v);
|
||||
} else if typeof_v == "array" {
|
||||
entity_arrays.insert(k, v);
|
||||
}
|
||||
}
|
||||
|
||||
let user_id = self.db.auth_user_id()?;
|
||||
let timestamp = self.db.timestamp()?;
|
||||
|
||||
let mut entity_change_kind = None;
|
||||
let mut entity_fetched = None;
|
||||
|
||||
// 2. Pre-stage the entity (for non-relationships)
|
||||
if !type_def.relationship {
|
||||
let (fields, kind, fetched) =
|
||||
self.stage_entity(entity_fields.clone(), type_def, &user_id, ×tamp)?;
|
||||
entity_fields = fields;
|
||||
entity_change_kind = kind;
|
||||
entity_fetched = fetched;
|
||||
}
|
||||
|
||||
let mut entity_response = serde_json::Map::new();
|
||||
|
||||
// 3. Handle related objects
|
||||
for (relation_name, relative_val) in entity_objects {
|
||||
let mut relative = match relative_val {
|
||||
Value::Object(m) => m,
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
// Attempt to extract relative object type name
|
||||
let relative_type_name = match relative.get("type").and_then(|v| v.as_str()) {
|
||||
Some(t) => t,
|
||||
None => continue,
|
||||
};
|
||||
|
||||
let relative_keys: Vec<String> = relative.keys().cloned().collect();
|
||||
|
||||
// Call central Database O(1) graph logic
|
||||
let relative_relation = self.db.get_relation(
|
||||
&type_def.name,
|
||||
relative_type_name,
|
||||
&relation_name,
|
||||
Some(&relative_keys),
|
||||
);
|
||||
|
||||
if let Some(relation) = relative_relation {
|
||||
let parent_is_source = type_def.hierarchy.contains(&relation.source_type);
|
||||
|
||||
if parent_is_source {
|
||||
// Parent holds FK to Child. Child MUST be generated FIRST.
|
||||
if !relative.contains_key("organization_id") {
|
||||
if let Some(org_id) = entity_fields.get("organization_id") {
|
||||
relative.insert("organization_id".to_string(), org_id.clone());
|
||||
}
|
||||
}
|
||||
|
||||
let merged_relative = match self.merge_internal(Value::Object(relative), notifications)? {
|
||||
Value::Object(m) => m,
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
Self::apply_entity_relation(
|
||||
&mut entity_fields,
|
||||
&relation.source_columns,
|
||||
&relation.destination_columns,
|
||||
&merged_relative,
|
||||
);
|
||||
entity_response.insert(relation_name, Value::Object(merged_relative));
|
||||
} else {
|
||||
// Child holds FK back to Parent.
|
||||
if !relative.contains_key("organization_id") {
|
||||
if let Some(org_id) = entity_fields.get("organization_id") {
|
||||
relative.insert("organization_id".to_string(), org_id.clone());
|
||||
}
|
||||
}
|
||||
|
||||
Self::apply_entity_relation(
|
||||
&mut relative,
|
||||
&relation.source_columns,
|
||||
&relation.destination_columns,
|
||||
&entity_fields,
|
||||
);
|
||||
|
||||
let merged_relative = match self.merge_internal(Value::Object(relative), notifications)? {
|
||||
Value::Object(m) => m,
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
entity_response.insert(relation_name, Value::Object(merged_relative));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Post-stage the entity (for relationships)
|
||||
if type_def.relationship {
|
||||
let (fields, kind, fetched) =
|
||||
self.stage_entity(entity_fields.clone(), type_def, &user_id, ×tamp)?;
|
||||
entity_fields = fields;
|
||||
entity_change_kind = kind;
|
||||
entity_fetched = fetched;
|
||||
}
|
||||
|
||||
// 5. Process the main entity fields
|
||||
self.merge_entity_fields(
|
||||
entity_change_kind.as_deref().unwrap_or(""),
|
||||
&type_name,
|
||||
type_def,
|
||||
&entity_fields,
|
||||
entity_fetched.as_ref(),
|
||||
)?;
|
||||
|
||||
// Add main entity fields to response
|
||||
for (k, v) in &entity_fields {
|
||||
entity_response.insert(k.clone(), v.clone());
|
||||
}
|
||||
|
||||
// 6. Handle related arrays
|
||||
for (relation_name, relative_val) in entity_arrays {
|
||||
let relative_arr = match relative_val {
|
||||
Value::Array(a) => a,
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
if relative_arr.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let first_relative = match &relative_arr[0] {
|
||||
Value::Object(m) => m,
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
// Attempt to extract relative object type name
|
||||
let relative_type_name = match first_relative.get("type").and_then(|v| v.as_str()) {
|
||||
Some(t) => t,
|
||||
None => continue,
|
||||
};
|
||||
|
||||
let relative_keys: Vec<String> = first_relative.keys().cloned().collect();
|
||||
|
||||
// Call central Database O(1) graph logic
|
||||
let relative_relation = self.db.get_relation(
|
||||
&type_def.name,
|
||||
relative_type_name,
|
||||
&relation_name,
|
||||
Some(&relative_keys),
|
||||
);
|
||||
|
||||
if let Some(relation) = relative_relation {
|
||||
let mut relative_responses = Vec::new();
|
||||
for relative_item_val in relative_arr {
|
||||
if let Value::Object(mut relative_item) = relative_item_val {
|
||||
if !relative_item.contains_key("organization_id") {
|
||||
if let Some(org_id) = entity_fields.get("organization_id") {
|
||||
relative_item.insert("organization_id".to_string(), org_id.clone());
|
||||
}
|
||||
}
|
||||
|
||||
Self::apply_entity_relation(
|
||||
&mut relative_item,
|
||||
&relation.source_columns,
|
||||
&relation.destination_columns,
|
||||
&entity_fields,
|
||||
);
|
||||
|
||||
let merged_relative =
|
||||
match self.merge_internal(Value::Object(relative_item), notifications)? {
|
||||
Value::Object(m) => m,
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
relative_responses.push(Value::Object(merged_relative));
|
||||
}
|
||||
}
|
||||
entity_response.insert(relation_name, Value::Array(relative_responses));
|
||||
}
|
||||
}
|
||||
|
||||
// 7. Perform change tracking
|
||||
let notify_sql = self.merge_entity_change(
|
||||
&entity_fields,
|
||||
entity_fetched.as_ref(),
|
||||
entity_change_kind.as_deref(),
|
||||
&user_id,
|
||||
×tamp,
|
||||
)?;
|
||||
|
||||
if let Some(sql) = notify_sql {
|
||||
notifications.insert(queue_start, sql);
|
||||
}
|
||||
|
||||
// Produce the full tree response
|
||||
let mut final_response = serde_json::Map::new();
|
||||
if let Some(fetched) = entity_fetched {
|
||||
for (k, v) in fetched {
|
||||
final_response.insert(k, v);
|
||||
}
|
||||
}
|
||||
for (k, v) in entity_response {
|
||||
final_response.insert(k, v);
|
||||
}
|
||||
|
||||
Ok(Value::Object(final_response))
|
||||
}
|
||||
|
||||
fn stage_entity(
|
||||
&self,
|
||||
mut entity_fields: serde_json::Map<String, Value>,
|
||||
type_def: &crate::database::r#type::Type,
|
||||
user_id: &str,
|
||||
timestamp: &str,
|
||||
) -> Result<
|
||||
(
|
||||
serde_json::Map<String, Value>,
|
||||
Option<String>,
|
||||
Option<serde_json::Map<String, Value>>,
|
||||
),
|
||||
String,
|
||||
> {
|
||||
let type_name = type_def.name.as_str();
|
||||
|
||||
let entity_fetched = self.fetch_entity(&entity_fields, type_def)?;
|
||||
|
||||
let system_keys = vec![
|
||||
"id".to_string(),
|
||||
"type".to_string(),
|
||||
"created_by".to_string(),
|
||||
"modified_by".to_string(),
|
||||
"created_at".to_string(),
|
||||
"modified_at".to_string(),
|
||||
];
|
||||
|
||||
let changes = self.compare_entities(
|
||||
entity_fetched.as_ref(),
|
||||
&entity_fields,
|
||||
&type_def.fields,
|
||||
&system_keys,
|
||||
);
|
||||
|
||||
let mut entity_change_kind = None;
|
||||
|
||||
if entity_fetched.is_none() {
|
||||
let entity_id = entity_fields
|
||||
.get("id")
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("");
|
||||
let id_val = if entity_id.is_empty() {
|
||||
Value::String(uuid::Uuid::new_v4().to_string())
|
||||
} else {
|
||||
Value::String(entity_id.to_string())
|
||||
};
|
||||
|
||||
entity_change_kind = Some("create".to_string());
|
||||
|
||||
let mut new_fields = changes.clone();
|
||||
new_fields.insert("id".to_string(), id_val);
|
||||
new_fields.insert("type".to_string(), Value::String(type_name.to_string()));
|
||||
new_fields.insert("created_by".to_string(), Value::String(user_id.to_string()));
|
||||
new_fields.insert(
|
||||
"created_at".to_string(),
|
||||
Value::String(timestamp.to_string()),
|
||||
);
|
||||
new_fields.insert(
|
||||
"modified_by".to_string(),
|
||||
Value::String(user_id.to_string()),
|
||||
);
|
||||
new_fields.insert(
|
||||
"modified_at".to_string(),
|
||||
Value::String(timestamp.to_string()),
|
||||
);
|
||||
|
||||
entity_fields = new_fields;
|
||||
} else if changes.is_empty() {
|
||||
let mut new_fields = serde_json::Map::new();
|
||||
new_fields.insert(
|
||||
"id".to_string(),
|
||||
entity_fetched.as_ref().unwrap().get("id").unwrap().clone(),
|
||||
);
|
||||
new_fields.insert("type".to_string(), Value::String(type_name.to_string()));
|
||||
|
||||
entity_fields = new_fields;
|
||||
} else {
|
||||
let is_archived = changes
|
||||
.get("archived")
|
||||
.and_then(|v| v.as_bool())
|
||||
.unwrap_or(false);
|
||||
entity_change_kind = if is_archived {
|
||||
Some("delete".to_string())
|
||||
} else {
|
||||
Some("update".to_string())
|
||||
};
|
||||
|
||||
let mut new_fields = changes.clone();
|
||||
new_fields.insert(
|
||||
"id".to_string(),
|
||||
entity_fetched.as_ref().unwrap().get("id").unwrap().clone(),
|
||||
);
|
||||
new_fields.insert("type".to_string(), Value::String(type_name.to_string()));
|
||||
new_fields.insert(
|
||||
"modified_by".to_string(),
|
||||
Value::String(user_id.to_string()),
|
||||
);
|
||||
new_fields.insert(
|
||||
"modified_at".to_string(),
|
||||
Value::String(timestamp.to_string()),
|
||||
);
|
||||
|
||||
entity_fields = new_fields;
|
||||
}
|
||||
|
||||
Ok((entity_fields, entity_change_kind, entity_fetched))
|
||||
}
|
||||
|
||||
fn fetch_entity(
|
||||
&self,
|
||||
entity_fields: &serde_json::Map<String, Value>,
|
||||
entity_type: &crate::database::r#type::Type,
|
||||
) -> Result<Option<serde_json::Map<String, Value>>, String> {
|
||||
let id_val = entity_fields.get("id");
|
||||
let entity_type_name = entity_type.name.as_str();
|
||||
|
||||
let mut lookup_complete = false;
|
||||
if !entity_type.lookup_fields.is_empty() {
|
||||
lookup_complete = true;
|
||||
for column in &entity_type.lookup_fields {
|
||||
match entity_fields.get(column) {
|
||||
Some(Value::Null) | None => {
|
||||
lookup_complete = false;
|
||||
break;
|
||||
}
|
||||
Some(Value::String(s)) if s.is_empty() => {
|
||||
lookup_complete = false;
|
||||
break;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if id_val.is_none() && !lookup_complete {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let fetch_sql_template = if let Some(cached) = self.cache.get(entity_type_name) {
|
||||
cached
|
||||
} else {
|
||||
let mut select_list = String::from("to_jsonb(t1.*)");
|
||||
let mut join_clauses = format!("FROM agreego.\"{}\" t1", entity_type.hierarchy[0]);
|
||||
|
||||
for (i, table_name) in entity_type.hierarchy.iter().enumerate().skip(1) {
|
||||
let t_alias = format!("t{}", i + 1);
|
||||
join_clauses.push_str(&format!(
|
||||
" LEFT JOIN agreego.\"{}\" {} ON {}.id = t1.id",
|
||||
table_name, t_alias, t_alias
|
||||
));
|
||||
select_list.push_str(&format!(" || to_jsonb({}.*)", t_alias));
|
||||
}
|
||||
|
||||
let template = format!("SELECT {} {}", select_list, join_clauses);
|
||||
self
|
||||
.cache
|
||||
.insert(entity_type_name.to_string(), template.clone());
|
||||
template
|
||||
};
|
||||
|
||||
let where_clause = if let Some(id) = id_val {
|
||||
format!("WHERE t1.id = {}", Self::quote_literal(id))
|
||||
} else if lookup_complete {
|
||||
let mut lookup_predicates = Vec::new();
|
||||
|
||||
for column in &entity_type.lookup_fields {
|
||||
let val = entity_fields.get(column).unwrap_or(&Value::Null);
|
||||
if column == "type" {
|
||||
lookup_predicates.push(format!("t1.\"{}\" = {}", column, Self::quote_literal(val)));
|
||||
} else {
|
||||
lookup_predicates.push(format!("\"{}\" = {}", column, Self::quote_literal(val)));
|
||||
}
|
||||
}
|
||||
format!("WHERE {}", lookup_predicates.join(" AND "))
|
||||
} else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let final_sql = format!("{} {}", fetch_sql_template, where_clause);
|
||||
|
||||
let fetched = match self.db.query(&final_sql, None) {
|
||||
Ok(Value::Array(table)) => {
|
||||
if table.len() > 1 {
|
||||
Err(format!(
|
||||
"TOO_MANY_LOOKUP_ROWS: Lookup for {} found too many existing rows",
|
||||
entity_type_name
|
||||
))
|
||||
} else if table.is_empty() {
|
||||
Ok(None)
|
||||
} else {
|
||||
let row = table.first().unwrap();
|
||||
match row {
|
||||
Value::Object(map) => Ok(Some(map.clone())),
|
||||
other => Err(format!("Expected JSON object, got: {:?}", other)),
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(_) => Err("Expected array from query in fetch_entity".to_string()),
|
||||
Err(e) => Err(format!("SPI error in fetch_entity: {:?}", e)),
|
||||
}?;
|
||||
|
||||
Ok(fetched)
|
||||
}
|
||||
|
||||
fn merge_entity_fields(
|
||||
&self,
|
||||
change_kind: &str,
|
||||
entity_type_name: &str,
|
||||
entity_type: &crate::database::r#type::Type,
|
||||
entity_fields: &serde_json::Map<String, Value>,
|
||||
_entity_fetched: Option<&serde_json::Map<String, Value>>,
|
||||
) -> Result<(), String> {
|
||||
if change_kind.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let id_str = match entity_fields.get("id").and_then(|v| v.as_str()) {
|
||||
Some(id) => id,
|
||||
None => return Err("Missing 'id' for merge execution".to_string()),
|
||||
};
|
||||
|
||||
let grouped_fields = match &entity_type.grouped_fields {
|
||||
Some(Value::Object(map)) => map,
|
||||
_ => {
|
||||
return Err(format!(
|
||||
"Grouped fields missing for type {}",
|
||||
entity_type_name
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
let mut execute_order: Vec<String> = entity_type.hierarchy.clone();
|
||||
if change_kind == "create" {
|
||||
execute_order.reverse();
|
||||
}
|
||||
|
||||
for table_name in execute_order {
|
||||
let table_fields = match grouped_fields.get(&table_name).and_then(|v| v.as_array()) {
|
||||
Some(arr) => arr
|
||||
.iter()
|
||||
.filter_map(|v| v.as_str().map(|s| s.to_string()))
|
||||
.collect::<Vec<_>>(),
|
||||
None => continue,
|
||||
};
|
||||
|
||||
let mut entity_pairs = serde_json::Map::new();
|
||||
for (k, v) in entity_fields {
|
||||
if table_fields.contains(k) {
|
||||
entity_pairs.insert(k.clone(), v.clone());
|
||||
}
|
||||
}
|
||||
|
||||
if change_kind == "create" {
|
||||
if !entity_pairs.contains_key("id") && table_fields.contains(&"id".to_string()) {
|
||||
entity_pairs.insert("id".to_string(), Value::String(id_str.to_string()));
|
||||
}
|
||||
if !entity_pairs.contains_key("type") && table_fields.contains(&"type".to_string()) {
|
||||
entity_pairs.insert(
|
||||
"type".to_string(),
|
||||
Value::String(entity_type_name.to_string()),
|
||||
);
|
||||
}
|
||||
|
||||
let mut columns = Vec::new();
|
||||
let mut values = Vec::new();
|
||||
|
||||
let mut sorted_keys: Vec<_> = entity_pairs.keys().cloned().collect();
|
||||
sorted_keys.sort();
|
||||
|
||||
for key in &sorted_keys {
|
||||
columns.push(format!("\"{}\"", key));
|
||||
let val = entity_pairs.get(key).unwrap();
|
||||
if val.as_str() == Some("") {
|
||||
values.push("NULL".to_string());
|
||||
} else {
|
||||
values.push(Self::quote_literal(val));
|
||||
}
|
||||
}
|
||||
|
||||
if columns.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let sql = format!(
|
||||
"INSERT INTO agreego.\"{}\" ({}) VALUES ({})",
|
||||
table_name,
|
||||
columns.join(", "),
|
||||
values.join(", ")
|
||||
);
|
||||
self
|
||||
.db
|
||||
.execute(&sql, None)
|
||||
.map_err(|e| format!("SPI Error in INSERT: {:?}", e))?;
|
||||
} else if change_kind == "update" || change_kind == "delete" {
|
||||
entity_pairs.remove("id");
|
||||
entity_pairs.remove("type");
|
||||
|
||||
if entity_pairs.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut set_clauses = Vec::new();
|
||||
let mut sorted_keys: Vec<_> = entity_pairs.keys().cloned().collect();
|
||||
sorted_keys.sort();
|
||||
|
||||
for key in &sorted_keys {
|
||||
let val = entity_pairs.get(key).unwrap();
|
||||
if val.as_str() == Some("") {
|
||||
set_clauses.push(format!("\"{}\" = NULL", key));
|
||||
} else {
|
||||
set_clauses.push(format!("\"{}\" = {}", key, Self::quote_literal(val)));
|
||||
}
|
||||
}
|
||||
|
||||
let sql = format!(
|
||||
"UPDATE agreego.\"{}\" SET {} WHERE id = {}",
|
||||
table_name,
|
||||
set_clauses.join(", "),
|
||||
Self::quote_literal(&Value::String(id_str.to_string()))
|
||||
);
|
||||
self
|
||||
.db
|
||||
.execute(&sql, None)
|
||||
.map_err(|e| format!("SPI Error in UPDATE: {:?}", e))?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn merge_entity_change(
|
||||
&self,
|
||||
entity_fields: &serde_json::Map<String, Value>,
|
||||
entity_fetched: Option<&serde_json::Map<String, Value>>,
|
||||
entity_change_kind: Option<&str>,
|
||||
user_id: &str,
|
||||
timestamp: &str,
|
||||
) -> Result<Option<String>, String> {
|
||||
let change_kind = match entity_change_kind {
|
||||
Some(k) => k,
|
||||
None => return Ok(None),
|
||||
};
|
||||
|
||||
let id_str = entity_fields.get("id").unwrap();
|
||||
let type_name = entity_fields.get("type").unwrap();
|
||||
|
||||
let mut changes = serde_json::Map::new();
|
||||
let is_update = change_kind == "update" || change_kind == "delete";
|
||||
|
||||
if !is_update {
|
||||
let system_keys = vec![
|
||||
"id".to_string(),
|
||||
"created_by".to_string(),
|
||||
"modified_by".to_string(),
|
||||
"created_at".to_string(),
|
||||
"modified_at".to_string(),
|
||||
];
|
||||
for (k, v) in entity_fields {
|
||||
if !system_keys.contains(k) {
|
||||
changes.insert(k.clone(), v.clone());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let system_keys = vec![
|
||||
"id".to_string(),
|
||||
"type".to_string(),
|
||||
"created_by".to_string(),
|
||||
"modified_by".to_string(),
|
||||
"created_at".to_string(),
|
||||
"modified_at".to_string(),
|
||||
];
|
||||
for (k, v) in entity_fields {
|
||||
if !system_keys.contains(k) {
|
||||
if let Some(fetched) = entity_fetched {
|
||||
let old_val = fetched.get(k).unwrap_or(&Value::Null);
|
||||
if v != old_val {
|
||||
changes.insert(k.clone(), v.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
changes.insert("type".to_string(), type_name.clone());
|
||||
}
|
||||
|
||||
let mut complete = entity_fields.clone();
|
||||
if is_update {
|
||||
if let Some(fetched) = entity_fetched {
|
||||
let mut temp = fetched.clone();
|
||||
for (k, v) in entity_fields {
|
||||
temp.insert(k.clone(), v.clone());
|
||||
}
|
||||
complete = temp;
|
||||
}
|
||||
}
|
||||
|
||||
let mut notification = serde_json::Map::new();
|
||||
notification.insert("complete".to_string(), Value::Object(complete));
|
||||
if is_update {
|
||||
notification.insert("changes".to_string(), Value::Object(changes.clone()));
|
||||
}
|
||||
|
||||
let change_sql = format!(
|
||||
"INSERT INTO agreego.change (changes, entity_id, id, kind, modified_at, modified_by) VALUES ({}, {}, {}, {}, {}, {})",
|
||||
Self::quote_literal(&Value::Object(changes)),
|
||||
Self::quote_literal(id_str),
|
||||
Self::quote_literal(&Value::String(uuid::Uuid::new_v4().to_string())),
|
||||
Self::quote_literal(&Value::String(change_kind.to_string())),
|
||||
Self::quote_literal(&Value::String(timestamp.to_string())),
|
||||
Self::quote_literal(&Value::String(user_id.to_string()))
|
||||
);
|
||||
|
||||
let notify_sql = format!(
|
||||
"SELECT pg_notify('entity', {})",
|
||||
Self::quote_literal(&Value::String(Value::Object(notification).to_string()))
|
||||
);
|
||||
|
||||
self
|
||||
.db
|
||||
.execute(&change_sql, None)
|
||||
.map_err(|e| format!("Executor Error in change: {:?}", e))?;
|
||||
|
||||
Ok(Some(notify_sql))
|
||||
}
|
||||
|
||||
fn compare_entities(
|
||||
&self,
|
||||
fetched_entity: Option<&serde_json::Map<String, Value>>,
|
||||
new_fields: &serde_json::Map<String, Value>,
|
||||
type_fields: &[String],
|
||||
system_keys: &[String],
|
||||
) -> serde_json::Map<String, Value> {
|
||||
let mut changes = serde_json::Map::new();
|
||||
|
||||
if fetched_entity.is_none() {
|
||||
for (k, v) in new_fields {
|
||||
if type_fields.contains(k) && !system_keys.contains(k) {
|
||||
changes.insert(k.clone(), v.clone());
|
||||
}
|
||||
}
|
||||
return changes;
|
||||
}
|
||||
|
||||
let old_map = fetched_entity.unwrap();
|
||||
|
||||
for (k, v) in new_fields {
|
||||
if type_fields.contains(k) && !system_keys.contains(k) {
|
||||
let old_val = old_map.get(k).unwrap_or(&Value::Null);
|
||||
if v != old_val {
|
||||
changes.insert(k.clone(), v.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
changes
|
||||
}
|
||||
|
||||
// Helper Functions
|
||||
|
||||
fn apply_entity_relation(
|
||||
source_entity: &mut serde_json::Map<String, Value>,
|
||||
source_columns: &[String],
|
||||
destination_columns: &[String],
|
||||
destination_entity: &serde_json::Map<String, Value>,
|
||||
) {
|
||||
if source_columns.len() != destination_columns.len() {
|
||||
return;
|
||||
}
|
||||
for i in 0..source_columns.len() {
|
||||
if let Some(dest_val) = destination_entity.get(&destination_columns[i]) {
|
||||
source_entity.insert(source_columns[i].clone(), dest_val.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn quote_literal(val: &Value) -> String {
|
||||
match val {
|
||||
Value::Null => "NULL".to_string(),
|
||||
Value::Bool(b) => {
|
||||
if *b {
|
||||
"true".to_string()
|
||||
} else {
|
||||
"false".to_string()
|
||||
}
|
||||
}
|
||||
Value::Number(n) => {
|
||||
if let Some(f) = n.as_f64() {
|
||||
if f.fract() == 0.0 {
|
||||
return f.trunc().to_string();
|
||||
}
|
||||
}
|
||||
n.to_string()
|
||||
}
|
||||
Value::String(s) => {
|
||||
if s.is_empty() {
|
||||
"NULL".to_string()
|
||||
} else {
|
||||
format!("'{}'", s.replace('\'', "''"))
|
||||
}
|
||||
}
|
||||
_ => format!(
|
||||
"'{}'",
|
||||
serde_json::to_string(val).unwrap().replace('\'', "''")
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
872
src/queryer/compiler.rs
Normal file
872
src/queryer/compiler.rs
Normal file
@ -0,0 +1,872 @@
|
||||
use crate::database::Database;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct SqlCompiler {
|
||||
pub db: Arc<Database>,
|
||||
}
|
||||
|
||||
impl SqlCompiler {
|
||||
pub fn new(db: Arc<Database>) -> Self {
|
||||
Self { db }
|
||||
}
|
||||
|
||||
/// Compiles a JSON schema into a nested PostgreSQL query returning JSONB
|
||||
pub fn compile(
|
||||
&self,
|
||||
schema_id: &str,
|
||||
stem_path: Option<&str>,
|
||||
filter_keys: &[String],
|
||||
) -> Result<String, String> {
|
||||
let schema = self
|
||||
.db
|
||||
.schemas
|
||||
.get(schema_id)
|
||||
.ok_or_else(|| format!("Schema not found: {}", schema_id))?;
|
||||
|
||||
let resolved_arc;
|
||||
let target_schema = if let Some(path) = stem_path.filter(|p| !p.is_empty() && *p != "/") {
|
||||
if let Some(stems_map) = self.db.stems.get(schema_id) {
|
||||
if let Some(stem) = stems_map.get(path) {
|
||||
resolved_arc = stem.schema.clone();
|
||||
} else {
|
||||
return Err(format!(
|
||||
"Stem entity type '{}' not found in schema '{}'",
|
||||
path, schema_id
|
||||
));
|
||||
}
|
||||
} else {
|
||||
return Err(format!(
|
||||
"Stem entity type '{}' not found in schema '{}'",
|
||||
path, schema_id
|
||||
));
|
||||
}
|
||||
resolved_arc.as_ref()
|
||||
} else {
|
||||
schema
|
||||
};
|
||||
|
||||
// We expect the top level to typically be an Object or Array
|
||||
let is_stem_query = stem_path.is_some();
|
||||
let mut alias_counter: usize = 0;
|
||||
let (sql, _) = self.walk_schema(
|
||||
target_schema,
|
||||
"t1",
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
filter_keys,
|
||||
is_stem_query,
|
||||
0,
|
||||
String::new(),
|
||||
&mut alias_counter,
|
||||
)?;
|
||||
Ok(sql)
|
||||
}
|
||||
|
||||
/// Recursively walks the schema AST emitting native PostgreSQL jsonb mapping
|
||||
/// Returns a tuple of (SQL_String, Field_Type)
|
||||
fn walk_schema(
|
||||
&self,
|
||||
schema: &crate::database::schema::Schema,
|
||||
parent_alias: &str,
|
||||
parent_table_aliases: Option<&std::collections::HashMap<String, String>>,
|
||||
parent_type_def: Option<&crate::database::r#type::Type>,
|
||||
prop_name_context: Option<&str>,
|
||||
filter_keys: &[String],
|
||||
is_stem_query: bool,
|
||||
depth: usize,
|
||||
current_path: String,
|
||||
alias_counter: &mut usize,
|
||||
) -> Result<(String, String), String> {
|
||||
// Determine the base schema type (could be an array, object, or literal)
|
||||
match &schema.obj.type_ {
|
||||
Some(crate::database::schema::SchemaTypeOrArray::Single(t)) if t == "array" => {
|
||||
// Handle Arrays:
|
||||
if let Some(items) = &schema.obj.items {
|
||||
let next_path = if current_path.is_empty() {
|
||||
String::from("#")
|
||||
} else {
|
||||
format!("{}.#", current_path)
|
||||
};
|
||||
|
||||
if let Some(ref_id) = &items.obj.r#ref {
|
||||
if let Some(type_def) = self.db.types.get(ref_id) {
|
||||
return self.compile_entity_node(
|
||||
items,
|
||||
type_def,
|
||||
parent_alias,
|
||||
parent_table_aliases,
|
||||
parent_type_def,
|
||||
prop_name_context,
|
||||
true,
|
||||
filter_keys,
|
||||
is_stem_query,
|
||||
depth,
|
||||
next_path,
|
||||
alias_counter,
|
||||
);
|
||||
}
|
||||
}
|
||||
let (item_sql, _) = self.walk_schema(
|
||||
items,
|
||||
parent_alias,
|
||||
parent_table_aliases,
|
||||
parent_type_def,
|
||||
prop_name_context,
|
||||
filter_keys,
|
||||
is_stem_query,
|
||||
depth + 1,
|
||||
next_path,
|
||||
alias_counter,
|
||||
)?;
|
||||
return Ok((
|
||||
format!("(SELECT jsonb_agg({}) FROM TODO)", item_sql),
|
||||
"array".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
Ok((
|
||||
"SELECT jsonb_agg(TODO) FROM TODO".to_string(),
|
||||
"array".to_string(),
|
||||
))
|
||||
}
|
||||
_ => {
|
||||
// Determine if this schema represents a Database Entity
|
||||
let mut resolved_type = None;
|
||||
|
||||
if let Some(family_target) = schema.obj.family.as_ref() {
|
||||
resolved_type = self.db.types.get(family_target);
|
||||
} else if let Some(lookup_key) = schema.obj.id.as_ref().or(schema.obj.r#ref.as_ref()) {
|
||||
let base_type_name = lookup_key.split('.').next_back().unwrap_or("").to_string();
|
||||
resolved_type = self.db.types.get(&base_type_name);
|
||||
}
|
||||
|
||||
if let Some(type_def) = resolved_type {
|
||||
return self.compile_entity_node(
|
||||
schema,
|
||||
type_def,
|
||||
parent_alias,
|
||||
parent_table_aliases,
|
||||
parent_type_def,
|
||||
prop_name_context,
|
||||
false,
|
||||
filter_keys,
|
||||
is_stem_query,
|
||||
depth,
|
||||
current_path,
|
||||
alias_counter,
|
||||
);
|
||||
}
|
||||
|
||||
// Handle Direct Refs
|
||||
if let Some(ref_id) = &schema.obj.r#ref {
|
||||
// If it's just an ad-hoc struct ref, we should resolve it
|
||||
if let Some(target_schema) = self.db.schemas.get(ref_id) {
|
||||
return self.walk_schema(
|
||||
target_schema,
|
||||
parent_alias,
|
||||
parent_table_aliases,
|
||||
parent_type_def,
|
||||
prop_name_context,
|
||||
filter_keys,
|
||||
is_stem_query,
|
||||
depth,
|
||||
current_path,
|
||||
alias_counter,
|
||||
);
|
||||
}
|
||||
return Err(format!("Unresolved $ref: {}", ref_id));
|
||||
}
|
||||
// Handle $family Polymorphism fallbacks for relations
|
||||
if let Some(family_target) = &schema.obj.family {
|
||||
let base_type_name = family_target.split('.').next_back().unwrap_or(family_target).to_string();
|
||||
|
||||
if let Some(type_def) = self.db.types.get(&base_type_name) {
|
||||
if type_def.variations.len() == 1 {
|
||||
let mut bypass_schema = crate::database::schema::Schema::default();
|
||||
bypass_schema.obj.r#ref = Some(family_target.clone());
|
||||
return self.walk_schema(
|
||||
&std::sync::Arc::new(bypass_schema),
|
||||
parent_alias,
|
||||
parent_table_aliases,
|
||||
parent_type_def,
|
||||
prop_name_context,
|
||||
filter_keys,
|
||||
is_stem_query,
|
||||
depth,
|
||||
current_path,
|
||||
alias_counter,
|
||||
);
|
||||
}
|
||||
|
||||
let mut sorted_variations: Vec<String> = type_def.variations.iter().cloned().collect();
|
||||
sorted_variations.sort();
|
||||
|
||||
let mut family_schemas = Vec::new();
|
||||
for variation in &sorted_variations {
|
||||
let mut ref_schema = crate::database::schema::Schema::default();
|
||||
ref_schema.obj.r#ref = Some(variation.clone());
|
||||
family_schemas.push(std::sync::Arc::new(ref_schema));
|
||||
}
|
||||
|
||||
return self.compile_one_of(
|
||||
&family_schemas,
|
||||
parent_alias,
|
||||
parent_table_aliases,
|
||||
parent_type_def,
|
||||
prop_name_context,
|
||||
filter_keys,
|
||||
is_stem_query,
|
||||
depth,
|
||||
current_path,
|
||||
alias_counter,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle oneOf Polymorphism fallbacks for relations
|
||||
if let Some(one_of) = &schema.obj.one_of {
|
||||
return self.compile_one_of(
|
||||
one_of,
|
||||
parent_alias,
|
||||
parent_table_aliases,
|
||||
parent_type_def,
|
||||
prop_name_context,
|
||||
filter_keys,
|
||||
is_stem_query,
|
||||
depth,
|
||||
current_path,
|
||||
alias_counter,
|
||||
);
|
||||
}
|
||||
|
||||
// Just an inline object definition?
|
||||
if let Some(props) = &schema.obj.properties {
|
||||
return self.compile_inline_object(
|
||||
props,
|
||||
parent_alias,
|
||||
parent_table_aliases,
|
||||
parent_type_def,
|
||||
filter_keys,
|
||||
is_stem_query,
|
||||
depth,
|
||||
current_path,
|
||||
alias_counter,
|
||||
);
|
||||
}
|
||||
|
||||
// Literal fallback
|
||||
Ok((
|
||||
format!(
|
||||
"{}.{}",
|
||||
parent_alias,
|
||||
prop_name_context.unwrap_or("unknown_prop")
|
||||
),
|
||||
"string".to_string(),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_merged_properties(
|
||||
&self,
|
||||
schema: &crate::database::schema::Schema,
|
||||
) -> std::collections::BTreeMap<String, Arc<crate::database::schema::Schema>> {
|
||||
let mut props = std::collections::BTreeMap::new();
|
||||
|
||||
if let Some(ref_id) = &schema.obj.r#ref {
|
||||
if let Some(parent_schema) = self.db.schemas.get(ref_id) {
|
||||
props.extend(self.get_merged_properties(parent_schema));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(local_props) = &schema.obj.properties {
|
||||
for (k, v) in local_props {
|
||||
props.insert(k.clone(), v.clone());
|
||||
}
|
||||
}
|
||||
|
||||
props
|
||||
}
|
||||
|
||||
fn compile_entity_node(
|
||||
&self,
|
||||
schema: &crate::database::schema::Schema,
|
||||
type_def: &crate::database::r#type::Type,
|
||||
parent_alias: &str,
|
||||
parent_table_aliases: Option<&std::collections::HashMap<String, String>>,
|
||||
parent_type_def: Option<&crate::database::r#type::Type>,
|
||||
prop_name: Option<&str>,
|
||||
is_array: bool,
|
||||
filter_keys: &[String],
|
||||
is_stem_query: bool,
|
||||
depth: usize,
|
||||
current_path: String,
|
||||
alias_counter: &mut usize,
|
||||
) -> Result<(String, String), String> {
|
||||
// 1. Build FROM clauses and table aliases
|
||||
let (table_aliases, from_clauses) = self.build_hierarchy_from_clauses(type_def, alias_counter);
|
||||
|
||||
// 2. Map properties and build jsonb_build_object args
|
||||
let mut select_args = self.map_properties_to_aliases(
|
||||
schema,
|
||||
type_def,
|
||||
&table_aliases,
|
||||
parent_alias,
|
||||
filter_keys,
|
||||
is_stem_query,
|
||||
depth,
|
||||
¤t_path,
|
||||
alias_counter,
|
||||
)?;
|
||||
|
||||
// 2.5 Inject polymorphism directly into the query object
|
||||
if let Some(family_target) = &schema.obj.family {
|
||||
let base_type_name = family_target.split('.').next_back().unwrap_or(family_target).to_string();
|
||||
|
||||
if let Some(fam_type_def) = self.db.types.get(&base_type_name) {
|
||||
if fam_type_def.variations.len() == 1 {
|
||||
let mut bypass_schema = crate::database::schema::Schema::default();
|
||||
bypass_schema.obj.r#ref = Some(family_target.clone());
|
||||
|
||||
let mut bypassed_args = self.map_properties_to_aliases(
|
||||
&bypass_schema,
|
||||
type_def,
|
||||
&table_aliases,
|
||||
parent_alias,
|
||||
filter_keys,
|
||||
is_stem_query,
|
||||
depth,
|
||||
¤t_path,
|
||||
alias_counter,
|
||||
)?;
|
||||
select_args.append(&mut bypassed_args);
|
||||
} else {
|
||||
let mut family_schemas = Vec::new();
|
||||
let mut sorted_fam_variations: Vec<String> = fam_type_def.variations.iter().cloned().collect();
|
||||
sorted_fam_variations.sort();
|
||||
|
||||
for variation in &sorted_fam_variations {
|
||||
let mut ref_schema = crate::database::schema::Schema::default();
|
||||
ref_schema.obj.r#ref = Some(variation.clone());
|
||||
family_schemas.push(std::sync::Arc::new(ref_schema));
|
||||
}
|
||||
|
||||
let base_alias = table_aliases
|
||||
.get(&type_def.name)
|
||||
.cloned()
|
||||
.unwrap_or_else(|| parent_alias.to_string());
|
||||
select_args.push(format!("'id', {}.id", base_alias));
|
||||
let (case_sql, _) = self.compile_one_of(
|
||||
&family_schemas,
|
||||
&base_alias,
|
||||
Some(&table_aliases),
|
||||
parent_type_def,
|
||||
None,
|
||||
filter_keys,
|
||||
is_stem_query,
|
||||
depth,
|
||||
current_path.clone(),
|
||||
alias_counter,
|
||||
)?;
|
||||
select_args.push(format!("'type', {}", case_sql));
|
||||
}
|
||||
}
|
||||
} else if let Some(one_of) = &schema.obj.one_of {
|
||||
let base_alias = table_aliases
|
||||
.get(&type_def.name)
|
||||
.cloned()
|
||||
.unwrap_or_else(|| parent_alias.to_string());
|
||||
select_args.push(format!("'id', {}.id", base_alias));
|
||||
let (case_sql, _) = self.compile_one_of(
|
||||
one_of,
|
||||
&base_alias,
|
||||
Some(&table_aliases),
|
||||
parent_type_def,
|
||||
None,
|
||||
filter_keys,
|
||||
is_stem_query,
|
||||
depth,
|
||||
current_path.clone(),
|
||||
alias_counter,
|
||||
)?;
|
||||
select_args.push(format!("'type', {}", case_sql));
|
||||
}
|
||||
|
||||
let jsonb_obj_sql = if select_args.is_empty() {
|
||||
"jsonb_build_object()".to_string()
|
||||
} else {
|
||||
format!("jsonb_build_object({})", select_args.join(", "))
|
||||
};
|
||||
|
||||
// 3. Build WHERE clauses
|
||||
let where_clauses = self.build_filter_where_clauses(
|
||||
schema,
|
||||
type_def,
|
||||
&table_aliases,
|
||||
parent_alias,
|
||||
parent_table_aliases,
|
||||
parent_type_def,
|
||||
prop_name,
|
||||
filter_keys,
|
||||
¤t_path,
|
||||
)?;
|
||||
|
||||
let selection = if is_array {
|
||||
format!("COALESCE(jsonb_agg({}), '[]'::jsonb)", jsonb_obj_sql)
|
||||
} else {
|
||||
jsonb_obj_sql
|
||||
};
|
||||
|
||||
let full_sql = format!(
|
||||
"(SELECT {} FROM {} WHERE {})",
|
||||
selection,
|
||||
from_clauses.join(" "),
|
||||
where_clauses.join(" AND ")
|
||||
);
|
||||
|
||||
Ok((
|
||||
full_sql,
|
||||
if is_array {
|
||||
"array".to_string()
|
||||
} else {
|
||||
"object".to_string()
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
fn build_hierarchy_from_clauses(
|
||||
&self,
|
||||
type_def: &crate::database::r#type::Type,
|
||||
alias_counter: &mut usize,
|
||||
) -> (std::collections::HashMap<String, String>, Vec<String>) {
|
||||
let mut table_aliases = std::collections::HashMap::new();
|
||||
let mut from_clauses = Vec::new();
|
||||
|
||||
for (i, table_name) in type_def.hierarchy.iter().enumerate() {
|
||||
*alias_counter += 1;
|
||||
let alias = format!("{}_{}", table_name, alias_counter);
|
||||
table_aliases.insert(table_name.clone(), alias.clone());
|
||||
|
||||
if i == 0 {
|
||||
from_clauses.push(format!("agreego.{} {}", table_name, alias));
|
||||
} else {
|
||||
let prev_alias = format!("{}_{}", type_def.hierarchy[i - 1], *alias_counter - 1);
|
||||
from_clauses.push(format!(
|
||||
"JOIN agreego.{} {} ON {}.id = {}.id",
|
||||
table_name, alias, alias, prev_alias
|
||||
));
|
||||
}
|
||||
}
|
||||
(table_aliases, from_clauses)
|
||||
}
|
||||
|
||||
fn map_properties_to_aliases(
|
||||
&self,
|
||||
schema: &crate::database::schema::Schema,
|
||||
type_def: &crate::database::r#type::Type,
|
||||
table_aliases: &std::collections::HashMap<String, String>,
|
||||
parent_alias: &str,
|
||||
filter_keys: &[String],
|
||||
is_stem_query: bool,
|
||||
depth: usize,
|
||||
current_path: &str,
|
||||
alias_counter: &mut usize,
|
||||
) -> Result<Vec<String>, String> {
|
||||
let mut select_args = Vec::new();
|
||||
let grouped_fields = type_def.grouped_fields.as_ref().and_then(|v| v.as_object());
|
||||
let merged_props = self.get_merged_properties(schema);
|
||||
let mut sorted_keys: Vec<&String> = merged_props.keys().collect();
|
||||
sorted_keys.sort();
|
||||
|
||||
for prop_key in sorted_keys {
|
||||
let prop_schema = &merged_props[prop_key];
|
||||
let mut owner_alias = table_aliases
|
||||
.get("entity")
|
||||
.cloned()
|
||||
.unwrap_or_else(|| format!("{}_t_err", parent_alias));
|
||||
|
||||
if let Some(gf) = grouped_fields {
|
||||
for (t_name, fields_val) in gf {
|
||||
if let Some(fields_arr) = fields_val.as_array() {
|
||||
if fields_arr.iter().any(|v| v.as_str() == Some(prop_key)) {
|
||||
owner_alias = table_aliases
|
||||
.get(t_name)
|
||||
.cloned()
|
||||
.unwrap_or_else(|| parent_alias.to_string());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let is_object_or_array = match &prop_schema.obj.type_ {
|
||||
Some(crate::database::schema::SchemaTypeOrArray::Single(s)) => {
|
||||
s == "object" || s == "array"
|
||||
}
|
||||
Some(crate::database::schema::SchemaTypeOrArray::Multiple(v)) => {
|
||||
v.contains(&"object".to_string()) || v.contains(&"array".to_string())
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
|
||||
let is_primitive = prop_schema.obj.r#ref.is_none()
|
||||
&& prop_schema.obj.items.is_none()
|
||||
&& prop_schema.obj.properties.is_none()
|
||||
&& prop_schema.obj.one_of.is_none()
|
||||
&& !is_object_or_array;
|
||||
|
||||
if is_primitive {
|
||||
if let Some(ft) = type_def.field_types.as_ref().and_then(|v| v.as_object()) {
|
||||
if !ft.contains_key(prop_key) {
|
||||
continue; // Skip frontend virtual properties (e.g. `computer` fields, `created`) missing from physical table fields
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let next_path = if current_path.is_empty() {
|
||||
prop_key.clone()
|
||||
} else {
|
||||
format!("{}.{}", current_path, prop_key)
|
||||
};
|
||||
|
||||
let (val_sql, val_type) = self.walk_schema(
|
||||
prop_schema,
|
||||
&owner_alias,
|
||||
Some(table_aliases),
|
||||
Some(type_def), // Pass current type_def as parent_type_def for child properties
|
||||
Some(prop_key),
|
||||
filter_keys,
|
||||
is_stem_query,
|
||||
depth + 1,
|
||||
next_path,
|
||||
alias_counter,
|
||||
)?;
|
||||
|
||||
if val_type != "abort" {
|
||||
select_args.push(format!("'{}', {}", prop_key, val_sql));
|
||||
}
|
||||
}
|
||||
Ok(select_args)
|
||||
}
|
||||
|
||||
fn build_filter_where_clauses(
|
||||
&self,
|
||||
schema: &crate::database::schema::Schema,
|
||||
type_def: &crate::database::r#type::Type,
|
||||
table_aliases: &std::collections::HashMap<String, String>,
|
||||
parent_alias: &str,
|
||||
parent_table_aliases: Option<&std::collections::HashMap<String, String>>,
|
||||
parent_type_def: Option<&crate::database::r#type::Type>,
|
||||
prop_name: Option<&str>,
|
||||
filter_keys: &[String],
|
||||
current_path: &str,
|
||||
) -> Result<Vec<String>, String> {
|
||||
let base_alias = table_aliases
|
||||
.get(&type_def.name)
|
||||
.cloned()
|
||||
.unwrap_or_else(|| "err".to_string());
|
||||
|
||||
let entity_alias = table_aliases
|
||||
.get("entity")
|
||||
.cloned()
|
||||
.unwrap_or_else(|| base_alias.clone());
|
||||
|
||||
let mut where_clauses = Vec::new();
|
||||
where_clauses.push(format!("NOT {}.archived", entity_alias));
|
||||
|
||||
for (i, filter_key) in filter_keys.iter().enumerate() {
|
||||
let mut parts = filter_key.split(':');
|
||||
let full_field_path = parts.next().unwrap_or(filter_key);
|
||||
let op = parts.next().unwrap_or("$eq");
|
||||
|
||||
let field_name = if current_path.is_empty() {
|
||||
if full_field_path.contains('.') || full_field_path.contains('#') {
|
||||
continue;
|
||||
}
|
||||
full_field_path
|
||||
} else {
|
||||
let prefix = format!("{}.", current_path);
|
||||
if full_field_path.starts_with(&prefix) {
|
||||
let remainder = &full_field_path[prefix.len()..];
|
||||
if remainder.contains('.') || remainder.contains('#') {
|
||||
continue;
|
||||
}
|
||||
remainder
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let mut filter_alias = base_alias.clone();
|
||||
|
||||
if let Some(gf) = type_def.grouped_fields.as_ref().and_then(|v| v.as_object()) {
|
||||
for (t_name, fields_val) in gf {
|
||||
if let Some(fields_arr) = fields_val.as_array() {
|
||||
if fields_arr.iter().any(|v| v.as_str() == Some(field_name)) {
|
||||
filter_alias = table_aliases
|
||||
.get(t_name)
|
||||
.cloned()
|
||||
.unwrap_or_else(|| base_alias.clone());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut is_ilike = false;
|
||||
let mut cast = "";
|
||||
|
||||
if let Some(field_types) = type_def.field_types.as_ref().and_then(|v| v.as_object()) {
|
||||
if let Some(pg_type_val) = field_types.get(field_name) {
|
||||
if let Some(pg_type) = pg_type_val.as_str() {
|
||||
if pg_type == "uuid" {
|
||||
cast = "::uuid";
|
||||
} else if pg_type == "boolean" || pg_type == "bool" {
|
||||
cast = "::boolean";
|
||||
} else if pg_type.contains("timestamp") || pg_type == "timestamptz" || pg_type == "date"
|
||||
{
|
||||
cast = "::timestamptz";
|
||||
} else if pg_type == "numeric"
|
||||
|| pg_type.contains("int")
|
||||
|| pg_type == "real"
|
||||
|| pg_type == "double precision"
|
||||
{
|
||||
cast = "::numeric";
|
||||
} else if pg_type == "text" || pg_type.contains("char") {
|
||||
let mut is_enum = false;
|
||||
if let Some(props) = &schema.obj.properties {
|
||||
if let Some(ps) = props.get(field_name) {
|
||||
is_enum = ps.obj.enum_.is_some();
|
||||
}
|
||||
}
|
||||
if !is_enum {
|
||||
is_ilike = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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" };
|
||||
let subquery = format!(
|
||||
"(SELECT value{} FROM jsonb_array_elements_text(({})::jsonb))",
|
||||
cast, p_val
|
||||
);
|
||||
where_clauses.push(format!(
|
||||
"{}.{} {} {}",
|
||||
filter_alias, field_name, sql_op, subquery
|
||||
));
|
||||
} else {
|
||||
let sql_op = match op {
|
||||
"$eq" => {
|
||||
if is_ilike {
|
||||
"ILIKE"
|
||||
} else {
|
||||
"="
|
||||
}
|
||||
}
|
||||
"$ne" => {
|
||||
if is_ilike {
|
||||
"NOT ILIKE"
|
||||
} else {
|
||||
"!="
|
||||
}
|
||||
}
|
||||
"$gt" => ">",
|
||||
"$gte" => ">=",
|
||||
"$lt" => "<",
|
||||
"$lte" => "<=",
|
||||
_ => {
|
||||
if is_ilike {
|
||||
"ILIKE"
|
||||
} else {
|
||||
"="
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let param_sql = if is_ilike && (op == "$eq" || op == "$ne") {
|
||||
p_val
|
||||
} else {
|
||||
format!("({}){}", p_val, cast)
|
||||
};
|
||||
|
||||
where_clauses.push(format!(
|
||||
"{}.{} {} {}",
|
||||
filter_alias, field_name, sql_op, param_sql
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(prop) = prop_name {
|
||||
// Find what type the parent alias is actually mapping to
|
||||
let mut relation_alias = parent_alias.to_string();
|
||||
|
||||
let mut relation_resolved = false;
|
||||
if let Some(parent_type) = parent_type_def {
|
||||
if let Some(relation) = self
|
||||
.db
|
||||
.get_relation(&parent_type.name, &type_def.name, prop, None)
|
||||
{
|
||||
let source_col = &relation.source_columns[0];
|
||||
let dest_col = &relation.destination_columns[0];
|
||||
|
||||
let mut possible_relation_alias = None;
|
||||
if let Some(pta) = parent_table_aliases {
|
||||
if let Some(a) = pta.get(&relation.source_type) {
|
||||
possible_relation_alias = Some(a.clone());
|
||||
} else if let Some(a) = pta.get(&relation.destination_type) {
|
||||
possible_relation_alias = Some(a.clone());
|
||||
}
|
||||
}
|
||||
if let Some(pa) = possible_relation_alias {
|
||||
relation_alias = pa;
|
||||
}
|
||||
|
||||
// Determine directionality based on the Relation metadata
|
||||
if relation.source_type == parent_type.name
|
||||
|| parent_type.hierarchy.contains(&relation.source_type)
|
||||
{
|
||||
// Parent is the source
|
||||
where_clauses.push(format!(
|
||||
"{}.{} = {}.{}",
|
||||
relation_alias, source_col, base_alias, dest_col
|
||||
));
|
||||
relation_resolved = true;
|
||||
} else if relation.destination_type == parent_type.name
|
||||
|| parent_type.hierarchy.contains(&relation.destination_type)
|
||||
{
|
||||
// Parent is the destination
|
||||
where_clauses.push(format!(
|
||||
"{}.{} = {}.{}",
|
||||
base_alias, source_col, relation_alias, dest_col
|
||||
));
|
||||
relation_resolved = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !relation_resolved {
|
||||
// Fallback heuristics for unmapped polymorphism or abstract models
|
||||
if prop == "target" || prop == "source" {
|
||||
if let Some(pta) = parent_table_aliases {
|
||||
if let Some(a) = pta.get("relationship") {
|
||||
relation_alias = a.clone();
|
||||
}
|
||||
}
|
||||
where_clauses.push(format!(
|
||||
"{}.id = {}.{}_id",
|
||||
base_alias, relation_alias, prop
|
||||
));
|
||||
} else {
|
||||
where_clauses.push(format!("{}.parent_id = {}.id", base_alias, relation_alias));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(where_clauses)
|
||||
}
|
||||
|
||||
fn compile_inline_object(
|
||||
&self,
|
||||
props: &std::collections::BTreeMap<String, std::sync::Arc<crate::database::schema::Schema>>,
|
||||
parent_alias: &str,
|
||||
parent_table_aliases: Option<&std::collections::HashMap<String, String>>,
|
||||
parent_type_def: Option<&crate::database::r#type::Type>,
|
||||
filter_keys: &[String],
|
||||
is_stem_query: bool,
|
||||
depth: usize,
|
||||
current_path: String,
|
||||
alias_counter: &mut usize,
|
||||
) -> Result<(String, String), String> {
|
||||
let mut build_args = Vec::new();
|
||||
for (k, v) in props {
|
||||
let next_path = if current_path.is_empty() {
|
||||
k.clone()
|
||||
} else {
|
||||
format!("{}.{}", current_path, k)
|
||||
};
|
||||
|
||||
let (child_sql, val_type) = self.walk_schema(
|
||||
v,
|
||||
parent_alias,
|
||||
parent_table_aliases,
|
||||
parent_type_def,
|
||||
Some(k),
|
||||
filter_keys,
|
||||
is_stem_query,
|
||||
depth + 1,
|
||||
next_path,
|
||||
alias_counter,
|
||||
)?;
|
||||
if val_type == "abort" {
|
||||
continue;
|
||||
}
|
||||
build_args.push(format!("'{}', {}", k, child_sql));
|
||||
}
|
||||
let combined = format!("jsonb_build_object({})", build_args.join(", "));
|
||||
Ok((combined, "object".to_string()))
|
||||
}
|
||||
|
||||
fn compile_one_of(
|
||||
&self,
|
||||
schemas: &[Arc<crate::database::schema::Schema>],
|
||||
parent_alias: &str,
|
||||
parent_table_aliases: Option<&std::collections::HashMap<String, String>>,
|
||||
parent_type_def: Option<&crate::database::r#type::Type>,
|
||||
prop_name_context: Option<&str>,
|
||||
filter_keys: &[String],
|
||||
is_stem_query: bool,
|
||||
depth: usize,
|
||||
current_path: String,
|
||||
alias_counter: &mut usize,
|
||||
) -> Result<(String, String), String> {
|
||||
let mut case_statements = Vec::new();
|
||||
let type_col = if let Some(prop) = prop_name_context {
|
||||
format!("{}_type", prop)
|
||||
} else {
|
||||
"type".to_string()
|
||||
};
|
||||
|
||||
for option_schema in schemas {
|
||||
if let Some(ref_id) = &option_schema.obj.r#ref {
|
||||
// Find the physical type this ref maps to
|
||||
let base_type_name = ref_id.split('.').next_back().unwrap_or("").to_string();
|
||||
|
||||
// Generate the nested SQL for this specific target type
|
||||
let (val_sql, _) = self.walk_schema(
|
||||
option_schema,
|
||||
parent_alias,
|
||||
parent_table_aliases,
|
||||
parent_type_def,
|
||||
prop_name_context,
|
||||
filter_keys,
|
||||
is_stem_query,
|
||||
depth,
|
||||
current_path.clone(),
|
||||
alias_counter,
|
||||
)?;
|
||||
|
||||
case_statements.push(format!(
|
||||
"WHEN {}.{} = '{}' THEN ({})",
|
||||
parent_alias, type_col, base_type_name, val_sql
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if case_statements.is_empty() {
|
||||
return Ok(("NULL".to_string(), "string".to_string()));
|
||||
}
|
||||
|
||||
case_statements.sort();
|
||||
|
||||
let sql = format!("CASE {} ELSE NULL END", case_statements.join(" "));
|
||||
|
||||
Ok((sql, "object".to_string()))
|
||||
}
|
||||
}
|
||||
@ -1,15 +1,157 @@
|
||||
pub struct Queryer {
|
||||
// To be implemented
|
||||
}
|
||||
use crate::database::Database;
|
||||
use std::sync::Arc;
|
||||
|
||||
impl Default for Queryer {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
pub mod compiler;
|
||||
|
||||
use dashmap::DashMap;
|
||||
|
||||
pub struct Queryer {
|
||||
pub db: Arc<Database>,
|
||||
cache: DashMap<String, String>,
|
||||
}
|
||||
|
||||
impl Queryer {
|
||||
pub fn new() -> Self {
|
||||
Self {}
|
||||
pub fn new(db: Arc<Database>) -> Self {
|
||||
Self {
|
||||
db,
|
||||
cache: DashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn query(
|
||||
&self,
|
||||
schema_id: &str,
|
||||
stem_opt: Option<&str>,
|
||||
filters: Option<&serde_json::Value>,
|
||||
) -> crate::drop::Drop {
|
||||
let filters_map = filters.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) {
|
||||
Ok(res) => res,
|
||||
Err(msg) => {
|
||||
return crate::drop::Drop::with_errors(vec![crate::drop::Error {
|
||||
code: "FILTER_PARSE_FAILED".to_string(),
|
||||
message: msg.clone(),
|
||||
details: crate::drop::ErrorDetails {
|
||||
path: "".to_string(), // filters apply to the root query
|
||||
cause: Some(msg),
|
||||
context: filters.map(|f| vec![f.to_string()]),
|
||||
schema: Some(schema_id.to_string()),
|
||||
},
|
||||
}]);
|
||||
}
|
||||
};
|
||||
|
||||
let stem_key = stem_opt.unwrap_or("/");
|
||||
let cache_key = format!("{}(Stem:{}):{}", schema_id, stem_key, filter_keys.join(","));
|
||||
|
||||
// 2. Fetch from cache or compile
|
||||
let sql = match self.get_or_compile_sql(&cache_key, schema_id, stem_opt, &filter_keys) {
|
||||
Ok(sql) => sql,
|
||||
Err(drop) => return drop,
|
||||
};
|
||||
|
||||
// 3. Execute via Database Executor
|
||||
self.execute_sql(schema_id, &sql, &args)
|
||||
}
|
||||
|
||||
fn parse_filter_entries(
|
||||
&self,
|
||||
filters_map: Option<&serde_json::Map<String, serde_json::Value>>,
|
||||
) -> Result<(Vec<String>, Vec<serde_json::Value>), String> {
|
||||
let mut filter_entries: Vec<(String, serde_json::Value)> = Vec::new();
|
||||
if let Some(fm) = filters_map {
|
||||
for (key, val) in fm {
|
||||
if let Some(obj) = val.as_object() {
|
||||
for (op, op_val) in obj {
|
||||
if !op.starts_with('$') {
|
||||
return Err(format!("Filter operator must start with '$', got: {}", op));
|
||||
}
|
||||
filter_entries.push((format!("{}:{}", key, op), op_val.clone()));
|
||||
}
|
||||
} else {
|
||||
return Err(format!(
|
||||
"Filter for field '{}' must be an object with operators like $eq, $in, etc.",
|
||||
key
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
filter_entries.sort_by(|a, b| a.0.cmp(&b.0));
|
||||
|
||||
let filter_keys: Vec<String> = filter_entries.iter().map(|(k, _)| k.clone()).collect();
|
||||
let args: Vec<serde_json::Value> = filter_entries.into_iter().map(|(_, v)| v).collect();
|
||||
|
||||
Ok((filter_keys, args))
|
||||
}
|
||||
|
||||
fn get_or_compile_sql(
|
||||
&self,
|
||||
cache_key: &str,
|
||||
schema_id: &str,
|
||||
stem_opt: Option<&str>,
|
||||
filter_keys: &[String],
|
||||
) -> Result<String, crate::drop::Drop> {
|
||||
if let Some(cached_sql) = self.cache.get(cache_key) {
|
||||
return Ok(cached_sql.value().clone());
|
||||
}
|
||||
|
||||
let compiler = compiler::SqlCompiler::new(self.db.clone());
|
||||
match compiler.compile(schema_id, stem_opt, filter_keys) {
|
||||
Ok(compiled_sql) => {
|
||||
self
|
||||
.cache
|
||||
.insert(cache_key.to_string(), compiled_sql.clone());
|
||||
Ok(compiled_sql)
|
||||
}
|
||||
Err(e) => Err(crate::drop::Drop::with_errors(vec![crate::drop::Error {
|
||||
code: "QUERY_COMPILATION_FAILED".to_string(),
|
||||
message: e.clone(),
|
||||
details: crate::drop::ErrorDetails {
|
||||
path: "".to_string(),
|
||||
cause: Some(e),
|
||||
context: None,
|
||||
schema: Some(schema_id.to_string()),
|
||||
},
|
||||
}])),
|
||||
}
|
||||
}
|
||||
|
||||
fn execute_sql(
|
||||
&self,
|
||||
schema_id: &str,
|
||||
sql: &str,
|
||||
args: &[serde_json::Value],
|
||||
) -> crate::drop::Drop {
|
||||
match self.db.query(sql, Some(args)) {
|
||||
Ok(serde_json::Value::Array(table)) => {
|
||||
if table.is_empty() {
|
||||
crate::drop::Drop::success_with_val(serde_json::Value::Null)
|
||||
} else {
|
||||
crate::drop::Drop::success_with_val(table.first().unwrap().clone())
|
||||
}
|
||||
}
|
||||
Ok(other) => crate::drop::Drop::with_errors(vec![crate::drop::Error {
|
||||
code: "QUERY_FAILED".to_string(),
|
||||
message: format!("Expected array from generic query, got: {:?}", other),
|
||||
details: crate::drop::ErrorDetails {
|
||||
path: "".to_string(),
|
||||
cause: Some(format!("Expected array, got {}", other)),
|
||||
context: Some(vec![sql.to_string()]),
|
||||
schema: Some(schema_id.to_string()),
|
||||
},
|
||||
}]),
|
||||
Err(e) => crate::drop::Drop::with_errors(vec![crate::drop::Error {
|
||||
code: "QUERY_FAILED".to_string(),
|
||||
message: format!("SPI error in queryer: {}", e),
|
||||
details: crate::drop::ErrorDetails {
|
||||
path: "".to_string(),
|
||||
cause: Some(format!("SPI error in queryer: {}", e)),
|
||||
context: Some(vec![sql.to_string()]),
|
||||
schema: Some(schema_id.to_string()),
|
||||
},
|
||||
}]),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,21 +1,23 @@
|
||||
use ::jspg::*;
|
||||
use pgrx::JsonB;
|
||||
use crate::*;
|
||||
pub mod runner;
|
||||
pub mod types;
|
||||
use serde_json::json;
|
||||
|
||||
// Database module tests moved to src/database/executors/mock.rs
|
||||
|
||||
#[test]
|
||||
fn test_library_api() {
|
||||
// 1. Initially, schemas are not cached.
|
||||
assert!(!json_schema_cached("test_schema"));
|
||||
|
||||
// Expected uninitialized drop format: errors + null response
|
||||
let uninitialized_drop = validate_json_schema("test_schema", JsonB(json!({})));
|
||||
let uninitialized_drop = jspg_validate("test_schema", JsonB(json!({})));
|
||||
assert_eq!(
|
||||
uninitialized_drop.0,
|
||||
json!({
|
||||
"type": "drop",
|
||||
"errors": [{
|
||||
"code": "VALIDATOR_NOT_INITIALIZED",
|
||||
"message": "JSON Schemas have not been cached yet. Run cache_json_schemas()",
|
||||
"code": "ENGINE_NOT_INITIALIZED",
|
||||
"message": "JSPG extension has not been initialized via jspg_setup",
|
||||
"details": { "path": "" }
|
||||
}]
|
||||
})
|
||||
@ -25,6 +27,7 @@ fn test_library_api() {
|
||||
let db_json = json!({
|
||||
"puncs": [],
|
||||
"enums": [],
|
||||
"relations": [],
|
||||
"types": [{
|
||||
"schemas": [{
|
||||
"$id": "test_schema",
|
||||
@ -37,7 +40,7 @@ fn test_library_api() {
|
||||
}]
|
||||
});
|
||||
|
||||
let cache_drop = jspg_cache_database(JsonB(db_json));
|
||||
let cache_drop = jspg_setup(JsonB(db_json));
|
||||
assert_eq!(
|
||||
cache_drop.0,
|
||||
json!({
|
||||
@ -46,20 +49,27 @@ fn test_library_api() {
|
||||
})
|
||||
);
|
||||
|
||||
// 3. Check schemas are cached
|
||||
assert!(json_schema_cached("test_schema"));
|
||||
|
||||
let show_drop = show_json_schemas();
|
||||
// 3. Validate jspg_schemas
|
||||
let schemas_drop = jspg_schemas();
|
||||
assert_eq!(
|
||||
show_drop.0,
|
||||
schemas_drop.0,
|
||||
json!({
|
||||
"type": "drop",
|
||||
"response": ["test_schema"]
|
||||
"response": {
|
||||
"test_schema": {
|
||||
"$id": "test_schema",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": { "type": "string" }
|
||||
},
|
||||
"required": ["name"]
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
// 4. Validate Happy Path
|
||||
let happy_drop = validate_json_schema("test_schema", JsonB(json!({"name": "Neo"})));
|
||||
let happy_drop = jspg_validate("test_schema", JsonB(json!({"name": "Neo"})));
|
||||
assert_eq!(
|
||||
happy_drop.0,
|
||||
json!({
|
||||
@ -69,7 +79,7 @@ fn test_library_api() {
|
||||
);
|
||||
|
||||
// 5. Validate Unhappy Path
|
||||
let unhappy_drop = validate_json_schema("test_schema", JsonB(json!({"wrong": "data"})));
|
||||
let unhappy_drop = jspg_validate("test_schema", JsonB(json!({"wrong": "data"})));
|
||||
assert_eq!(
|
||||
unhappy_drop.0,
|
||||
json!({
|
||||
@ -90,7 +100,7 @@ fn test_library_api() {
|
||||
);
|
||||
|
||||
// 6. Clear Schemas
|
||||
let clear_drop = clear_json_schemas();
|
||||
let clear_drop = jspg_teardown();
|
||||
assert_eq!(
|
||||
clear_drop.0,
|
||||
json!({
|
||||
@ -98,5 +108,6 @@ fn test_library_api() {
|
||||
"response": "success"
|
||||
})
|
||||
);
|
||||
assert!(!json_schema_cached("test_schema"));
|
||||
}
|
||||
|
||||
include!("fixtures.rs");
|
||||
144
src/tests/runner.rs
Normal file
144
src/tests/runner.rs
Normal file
@ -0,0 +1,144 @@
|
||||
use crate::tests::types::Suite;
|
||||
use serde::Deserialize;
|
||||
use serde_json::Value;
|
||||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
use std::sync::{Arc, OnceLock, RwLock};
|
||||
|
||||
pub fn deserialize_some<'de, D>(deserializer: D) -> Result<Option<Value>, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let v = Value::deserialize(deserializer)?;
|
||||
Ok(Some(v))
|
||||
}
|
||||
|
||||
// Type alias for easier reading
|
||||
type CompiledSuite = Arc<Vec<(Suite, Arc<crate::database::Database>)>>;
|
||||
|
||||
// Global cache mapping filename -> Vector of (Parsed JSON suite, Compiled Database)
|
||||
static CACHE: OnceLock<RwLock<HashMap<String, CompiledSuite>>> = OnceLock::new();
|
||||
|
||||
fn get_cached_file(path: &str) -> CompiledSuite {
|
||||
let cache_lock = CACHE.get_or_init(|| RwLock::new(HashMap::new()));
|
||||
|
||||
let file_data = {
|
||||
let read_guard = cache_lock.read().unwrap();
|
||||
read_guard.get(path).cloned()
|
||||
};
|
||||
|
||||
match file_data {
|
||||
Some(data) => data,
|
||||
None => {
|
||||
let mut write_guard = cache_lock.write().unwrap();
|
||||
// double check in case another thread compiled while we waited for lock
|
||||
if let Some(data) = write_guard.get(path) {
|
||||
data.clone()
|
||||
} else {
|
||||
let content =
|
||||
fs::read_to_string(path).unwrap_or_else(|_| panic!("Failed to read file: {}", path));
|
||||
let suites: Vec<Suite> = serde_json::from_str(&content)
|
||||
.unwrap_or_else(|e| panic!("Failed to parse JSON in {}: {}", path, e));
|
||||
|
||||
let mut compiled_suites = Vec::new();
|
||||
for suite in suites {
|
||||
let db_result = crate::database::Database::new(&suite.database);
|
||||
if let Err(drop) = db_result {
|
||||
let error_messages: Vec<String> = drop
|
||||
.errors
|
||||
.into_iter()
|
||||
.map(|e| format!("Error {} at path {}: {}", e.code, e.details.path, e.message))
|
||||
.collect();
|
||||
panic!(
|
||||
"System Setup Compilation failed for {}:\n{}",
|
||||
path,
|
||||
error_messages.join("\n")
|
||||
);
|
||||
}
|
||||
compiled_suites.push((suite, Arc::new(db_result.unwrap())));
|
||||
}
|
||||
|
||||
let new_data = Arc::new(compiled_suites);
|
||||
write_guard.insert(path.to_string(), new_data.clone());
|
||||
new_data
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_test_case(path: &str, suite_idx: usize, case_idx: usize) -> Result<(), String> {
|
||||
let file_data = get_cached_file(path);
|
||||
|
||||
if suite_idx >= file_data.len() {
|
||||
panic!("Suite Index {} out of bounds for file {}", suite_idx, path);
|
||||
}
|
||||
|
||||
let (group, db) = &file_data[suite_idx];
|
||||
|
||||
if case_idx >= group.tests.len() {
|
||||
panic!(
|
||||
"Case Index {} out of bounds for suite {} in file {}",
|
||||
case_idx, suite_idx, path
|
||||
);
|
||||
}
|
||||
|
||||
let test = &group.tests[case_idx];
|
||||
let mut failures = Vec::<String>::new();
|
||||
|
||||
// 4. Run Tests
|
||||
|
||||
match test.action.as_str() {
|
||||
"compile" => {
|
||||
let result = test.run_compile(db.clone());
|
||||
if let Err(e) = result {
|
||||
println!("TEST COMPILE ERROR FOR '{}': {}", test.description, e);
|
||||
failures.push(format!(
|
||||
"[{}] Compile Test '{}' failed. Error: {}",
|
||||
group.description, test.description, e
|
||||
));
|
||||
}
|
||||
}
|
||||
"validate" => {
|
||||
let result = test.run_validate(db.clone());
|
||||
if let Err(e) = result {
|
||||
println!("TEST VALIDATE ERROR FOR '{}': {}", test.description, e);
|
||||
failures.push(format!(
|
||||
"[{}] Validate Test '{}' failed. Error: {}",
|
||||
group.description, test.description, e
|
||||
));
|
||||
}
|
||||
}
|
||||
"merge" => {
|
||||
let result = test.run_merge(db.clone());
|
||||
if let Err(e) = result {
|
||||
println!("TEST MERGE ERROR FOR '{}': {}", test.description, e);
|
||||
failures.push(format!(
|
||||
"[{}] Merge Test '{}' failed. Error: {}",
|
||||
group.description, test.description, e
|
||||
));
|
||||
}
|
||||
}
|
||||
"query" => {
|
||||
let result = test.run_query(db.clone());
|
||||
if let Err(e) = result {
|
||||
println!("TEST QUERY ERROR FOR '{}': {}", test.description, e);
|
||||
failures.push(format!(
|
||||
"[{}] Query Test '{}' failed. Error: {}",
|
||||
group.description, test.description, e
|
||||
));
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
failures.push(format!(
|
||||
"[{}] Unknown action '{}' for test '{}'",
|
||||
group.description, test.action, test.description
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if !failures.is_empty() {
|
||||
return Err(failures.join("\n"));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
189
src/tests/types/case.rs
Normal file
189
src/tests/types/case.rs
Normal file
@ -0,0 +1,189 @@
|
||||
use super::expect::Expect;
|
||||
use crate::database::Database;
|
||||
use serde::Deserialize;
|
||||
use serde_json::Value;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Case {
|
||||
pub description: String,
|
||||
|
||||
#[serde(default = "default_action")]
|
||||
pub action: String, // "validate", "merge", or "query"
|
||||
|
||||
// For Validate & Query
|
||||
#[serde(default)]
|
||||
pub schema_id: String,
|
||||
|
||||
// For Query
|
||||
#[serde(default)]
|
||||
pub stem: Option<String>,
|
||||
|
||||
#[serde(default)]
|
||||
pub filters: Option<serde_json::Value>,
|
||||
|
||||
// For Merge & Validate
|
||||
#[serde(default)]
|
||||
pub data: Option<serde_json::Value>,
|
||||
|
||||
// For Merge & Query mocks
|
||||
#[serde(default)]
|
||||
pub mocks: Option<serde_json::Value>,
|
||||
|
||||
pub expect: Option<Expect>,
|
||||
}
|
||||
|
||||
fn default_action() -> String {
|
||||
"validate".to_string()
|
||||
}
|
||||
|
||||
impl Case {
|
||||
pub fn run_compile(&self, db: Arc<Database>) -> Result<(), String> {
|
||||
let expected_success = self.expect.as_ref().map(|e| e.success).unwrap_or(false);
|
||||
|
||||
// We assume db has already been setup and compiled successfully by runner.rs's `jspg_setup`
|
||||
// We just need to check if there are compilation errors vs expected success
|
||||
let got_success = true; // Setup ensures success unless setup fails, which runner handles
|
||||
|
||||
if expected_success != got_success {
|
||||
return Err(format!(
|
||||
"Expected success: {}, Got: {}",
|
||||
expected_success, got_success
|
||||
));
|
||||
}
|
||||
|
||||
// Assert stems
|
||||
if let Some(expect) = &self.expect {
|
||||
if let Some(expected_stems) = &expect.stems {
|
||||
// Convert the Db stems (HashMap<String, HashMap<String, Arc<Stem>>>) to matching JSON shape
|
||||
let db_stems_json = serde_json::to_value(&db.stems).unwrap();
|
||||
let expect_stems_json = serde_json::to_value(expected_stems).unwrap();
|
||||
|
||||
if db_stems_json != expect_stems_json {
|
||||
let expected_pretty = serde_json::to_string_pretty(&expect_stems_json).unwrap();
|
||||
let got_pretty = serde_json::to_string_pretty(&db_stems_json).unwrap();
|
||||
return Err(format!(
|
||||
"Stem validation failed.\nExpected:\n{}\n\nGot:\n{}",
|
||||
expected_pretty, got_pretty
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn run_validate(&self, db: Arc<Database>) -> Result<(), String> {
|
||||
use crate::validator::Validator;
|
||||
|
||||
let validator = Validator::new(db);
|
||||
|
||||
let expected_success = self.expect.as_ref().map(|e| e.success).unwrap_or(false);
|
||||
|
||||
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);
|
||||
|
||||
let got_valid = result.errors.is_empty();
|
||||
|
||||
if got_valid != expected_success {
|
||||
let error_msg = if result.errors.is_empty() {
|
||||
"None".to_string()
|
||||
} else {
|
||||
format!("{:?}", result.errors)
|
||||
};
|
||||
|
||||
return Err(format!(
|
||||
"Expected: {}, Got: {}. Errors: {}",
|
||||
expected_success, got_valid, error_msg
|
||||
));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn run_merge(&self, db: Arc<Database>) -> Result<(), String> {
|
||||
if let Some(mocks) = &self.mocks {
|
||||
if let Some(arr) = mocks.as_array() {
|
||||
db.executor.set_mocks(arr.clone());
|
||||
}
|
||||
}
|
||||
|
||||
use crate::merger::Merger;
|
||||
let merger = Merger::new(db.clone());
|
||||
|
||||
let test_data = self.data.clone().unwrap_or(Value::Null);
|
||||
let result = merger.merge(test_data);
|
||||
|
||||
let expected_success = self.expect.as_ref().map(|e| e.success).unwrap_or(false);
|
||||
let got_success = result.errors.is_empty();
|
||||
|
||||
let error_msg = if result.errors.is_empty() {
|
||||
"None".to_string()
|
||||
} else {
|
||||
format!("{:?}", result.errors)
|
||||
};
|
||||
|
||||
let return_val = if expected_success != got_success {
|
||||
Err(format!(
|
||||
"Merge Expected: {}, Got: {}. Errors: {}",
|
||||
expected_success, got_success, error_msg
|
||||
))
|
||||
} else if let Some(expect) = &self.expect {
|
||||
let queries = db.executor.get_queries();
|
||||
expect.assert_pattern(&queries)?;
|
||||
expect.assert_sql(&queries)
|
||||
} else {
|
||||
Ok(())
|
||||
};
|
||||
|
||||
db.executor.reset_mocks();
|
||||
return_val
|
||||
}
|
||||
|
||||
pub fn run_query(&self, db: Arc<Database>) -> Result<(), String> {
|
||||
if let Some(mocks) = &self.mocks {
|
||||
if let Some(arr) = mocks.as_array() {
|
||||
db.executor.set_mocks(arr.clone());
|
||||
}
|
||||
}
|
||||
|
||||
use crate::queryer::Queryer;
|
||||
let queryer = Queryer::new(db.clone());
|
||||
|
||||
let stem_opt = self.stem.as_deref();
|
||||
let result = queryer.query(&self.schema_id, stem_opt, self.filters.as_ref());
|
||||
|
||||
let expected_success = self.expect.as_ref().map(|e| e.success).unwrap_or(false);
|
||||
let got_success = result.errors.is_empty();
|
||||
|
||||
let error_msg = if result.errors.is_empty() {
|
||||
"None".to_string()
|
||||
} else {
|
||||
format!("{:?}", result.errors)
|
||||
};
|
||||
|
||||
let return_val = if expected_success != got_success {
|
||||
Err(format!(
|
||||
"Query Expected: {}, Got: {}. Errors: {}",
|
||||
expected_success, got_success, error_msg
|
||||
))
|
||||
} else if let Some(expect) = &self.expect {
|
||||
let queries = db.executor.get_queries();
|
||||
expect.assert_pattern(&queries)?;
|
||||
expect.assert_sql(&queries)
|
||||
} else {
|
||||
Ok(())
|
||||
};
|
||||
|
||||
db.executor.reset_mocks();
|
||||
return_val
|
||||
}
|
||||
}
|
||||
22
src/tests/types/expect/mod.rs
Normal file
22
src/tests/types/expect/mod.rs
Normal file
@ -0,0 +1,22 @@
|
||||
pub mod pattern;
|
||||
pub mod sql;
|
||||
|
||||
use serde::Deserialize;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum SqlExpectation {
|
||||
Single(String),
|
||||
Multi(Vec<String>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Expect {
|
||||
pub success: bool,
|
||||
pub result: Option<serde_json::Value>,
|
||||
pub errors: Option<Vec<serde_json::Value>>,
|
||||
pub stems: Option<HashMap<String, HashMap<String, serde_json::Value>>>,
|
||||
#[serde(default)]
|
||||
pub sql: Option<Vec<SqlExpectation>>,
|
||||
}
|
||||
132
src/tests/types/expect/pattern.rs
Normal file
132
src/tests/types/expect/pattern.rs
Normal file
@ -0,0 +1,132 @@
|
||||
use super::Expect;
|
||||
use regex::Regex;
|
||||
use std::collections::HashMap;
|
||||
|
||||
impl Expect {
|
||||
/// Advanced SQL execution assertion algorithm ported from `assert.go`.
|
||||
/// This compares two arrays of strings, one containing {{uuid:name}} or {{timestamp}} placeholders,
|
||||
/// and the other containing actual executed database queries. It ensures that placeholder UUIDs
|
||||
/// are consistently mapped to the same actual UUIDs across all lines, and strictly validates line-by-line sequences.
|
||||
pub fn assert_pattern(&self, actual: &[String]) -> Result<(), String> {
|
||||
let patterns = match &self.sql {
|
||||
Some(s) => s,
|
||||
None => return Ok(()),
|
||||
};
|
||||
|
||||
if patterns.len() != actual.len() {
|
||||
return Err(format!(
|
||||
"Length mismatch: expected {} SQL executions, got {}.\nActual Execution Log:\n{}",
|
||||
patterns.len(),
|
||||
actual.len(),
|
||||
actual.join("\n")
|
||||
));
|
||||
}
|
||||
|
||||
let ws_re = Regex::new(r"\s+").unwrap();
|
||||
|
||||
let types = HashMap::from([
|
||||
(
|
||||
"uuid",
|
||||
r"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}",
|
||||
),
|
||||
(
|
||||
"timestamp",
|
||||
r"\d{4}-\d{2}-\d{2}(?:[ T])\d{2}:\d{2}:\d{2}(?:\.\d{1,6})?(?:Z|\+\d{2}(?::\d{2})?)?",
|
||||
),
|
||||
("integer", r"-?\d+"),
|
||||
("float", r"-?\d+\.\d+"),
|
||||
("text", r"(?:''|[^'])*"),
|
||||
("json", r"(?:''|[^'])*"),
|
||||
]);
|
||||
|
||||
let mut seen: HashMap<String, String> = HashMap::new();
|
||||
let system_uuid = "00000000-0000-0000-0000-000000000000";
|
||||
|
||||
// Placeholder regex: {{type:name}} or {{type}}
|
||||
let ph_rx = Regex::new(r"\{\{([a-z]+)(?:[:]([^}]+))?\}\}").unwrap();
|
||||
|
||||
let clean_str = |s: &str| -> String {
|
||||
let mut s = ws_re.replace_all(s, " ").into_owned();
|
||||
for token in ["(", ")", ",", "{", "}", "\"", "=", "'"] {
|
||||
s = s.replace(&format!(" {}", token), token);
|
||||
s = s.replace(&format!("{} ", token), token);
|
||||
}
|
||||
s.trim().to_string()
|
||||
};
|
||||
|
||||
for (i, pattern_expect) in patterns.iter().enumerate() {
|
||||
let aline_raw = &actual[i];
|
||||
let aline = clean_str(aline_raw);
|
||||
|
||||
let pattern_str_raw = match pattern_expect {
|
||||
super::SqlExpectation::Single(s) => s.clone(),
|
||||
super::SqlExpectation::Multi(m) => m.join(" "),
|
||||
};
|
||||
|
||||
let pattern_str = clean_str(&pattern_str_raw);
|
||||
|
||||
let mut pp = regex::escape(&pattern_str);
|
||||
pp = pp.replace(r"\{\{", "{{").replace(r"\}\}", "}}");
|
||||
|
||||
let mut cap_names = HashMap::new(); // cg_X -> var_name
|
||||
let mut group_idx = 0;
|
||||
|
||||
let mut final_rx_str = String::new();
|
||||
let mut last_match = 0;
|
||||
|
||||
let pp_clone = pp.clone();
|
||||
for caps in ph_rx.captures_iter(&pp_clone) {
|
||||
let full_match = caps.get(0).unwrap();
|
||||
final_rx_str.push_str(&pp[last_match..full_match.start()]);
|
||||
|
||||
let type_name = caps.get(1).unwrap().as_str();
|
||||
let var_name = caps.get(2).map(|m| m.as_str());
|
||||
|
||||
if let Some(name) = var_name {
|
||||
if let Some(val) = seen.get(name) {
|
||||
final_rx_str.push_str(®ex::escape(val));
|
||||
} else {
|
||||
let type_pattern = types.get(type_name).unwrap_or(&".*?");
|
||||
let cg_name = format!("cg_{}", group_idx);
|
||||
final_rx_str.push_str(&format!("(?P<{}>{})", cg_name, type_pattern));
|
||||
cap_names.insert(cg_name, name.to_string());
|
||||
group_idx += 1;
|
||||
}
|
||||
} else {
|
||||
let type_pattern = types.get(type_name).unwrap_or(&".*?");
|
||||
final_rx_str.push_str(&format!("(?:{})", type_pattern));
|
||||
}
|
||||
|
||||
last_match = full_match.end();
|
||||
}
|
||||
final_rx_str.push_str(&pp[last_match..]);
|
||||
|
||||
let final_rx = match Regex::new(&format!("^{}$", final_rx_str)) {
|
||||
Ok(r) => r,
|
||||
Err(e) => return Err(format!("Bad constructed regex: {} -> {}", final_rx_str, e)),
|
||||
};
|
||||
|
||||
if let Some(captures) = final_rx.captures(&aline) {
|
||||
for (cg_name, var_name) in cap_names {
|
||||
if let Some(m) = captures.name(&cg_name) {
|
||||
let matched_str = m.as_str();
|
||||
if matched_str != system_uuid {
|
||||
seen.insert(var_name, matched_str.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Err(format!(
|
||||
"Line mismatched at execution sequence {}.\nExpected Pattern: {}\nActual SQL: {}\nRegex used: {}\nVariables Mapped: {:?}",
|
||||
i + 1,
|
||||
pattern_str,
|
||||
aline,
|
||||
final_rx_str,
|
||||
seen
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
206
src/tests/types/expect/sql.rs
Normal file
206
src/tests/types/expect/sql.rs
Normal file
@ -0,0 +1,206 @@
|
||||
use super::Expect;
|
||||
use sqlparser::ast::{Expr, Query, SelectItem, Statement, TableFactor};
|
||||
use sqlparser::dialect::PostgreSqlDialect;
|
||||
use sqlparser::parser::Parser;
|
||||
use std::collections::HashSet;
|
||||
|
||||
impl Expect {
|
||||
pub fn assert_sql(&self, actual: &[String]) -> Result<(), String> {
|
||||
for query in actual {
|
||||
if let Err(e) = Self::validate_semantic_sql(query) {
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn validate_semantic_sql(sql: &str) -> Result<(), String> {
|
||||
let dialect = PostgreSqlDialect {};
|
||||
let statements = match Parser::parse_sql(&dialect, sql) {
|
||||
Ok(s) => s,
|
||||
Err(e) => return Err(format!("SQL Syntax Error: {}\nSQL: {}", e, sql)),
|
||||
};
|
||||
|
||||
for statement in statements {
|
||||
Self::validate_statement(&statement, sql)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn validate_statement(stmt: &Statement, original_sql: &str) -> Result<(), String> {
|
||||
match stmt {
|
||||
Statement::Query(query) => Self::validate_query(query, &HashSet::new(), original_sql)?,
|
||||
Statement::Insert(insert) => {
|
||||
if let Some(query) = &insert.source {
|
||||
Self::validate_query(query, &HashSet::new(), original_sql)?
|
||||
}
|
||||
}
|
||||
Statement::Update(update) => {
|
||||
if let Some(expr) = &update.selection {
|
||||
Self::validate_expr(expr, &HashSet::new(), original_sql)?;
|
||||
}
|
||||
}
|
||||
Statement::Delete(delete) => {
|
||||
if let Some(expr) = &delete.selection {
|
||||
Self::validate_expr(expr, &HashSet::new(), original_sql)?;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn validate_query(
|
||||
query: &Query,
|
||||
available_aliases: &HashSet<String>,
|
||||
original_sql: &str,
|
||||
) -> Result<(), String> {
|
||||
if let sqlparser::ast::SetExpr::Select(select) = &*query.body {
|
||||
Self::validate_select(&select, available_aliases, original_sql)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn validate_select(
|
||||
select: &sqlparser::ast::Select,
|
||||
parent_aliases: &HashSet<String>,
|
||||
original_sql: &str,
|
||||
) -> Result<(), String> {
|
||||
let mut available_aliases = parent_aliases.clone();
|
||||
|
||||
// 1. Collect all declared table aliases in the FROM clause and JOINs
|
||||
for table_with_joins in &select.from {
|
||||
Self::collect_aliases_from_table_factor(&table_with_joins.relation, &mut available_aliases);
|
||||
for join in &table_with_joins.joins {
|
||||
Self::collect_aliases_from_table_factor(&join.relation, &mut available_aliases);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Validate all SELECT projection fields
|
||||
for projection in &select.projection {
|
||||
if let SelectItem::UnnamedExpr(expr) | SelectItem::ExprWithAlias { expr, .. } = projection {
|
||||
Self::validate_expr(expr, &available_aliases, original_sql)?;
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Validate ON conditions in joins
|
||||
for table_with_joins in &select.from {
|
||||
for join in &table_with_joins.joins {
|
||||
if let sqlparser::ast::JoinOperator::Inner(sqlparser::ast::JoinConstraint::On(expr))
|
||||
| sqlparser::ast::JoinOperator::LeftOuter(sqlparser::ast::JoinConstraint::On(expr))
|
||||
| sqlparser::ast::JoinOperator::RightOuter(sqlparser::ast::JoinConstraint::On(expr))
|
||||
| sqlparser::ast::JoinOperator::FullOuter(sqlparser::ast::JoinConstraint::On(expr))
|
||||
| sqlparser::ast::JoinOperator::Join(sqlparser::ast::JoinConstraint::On(expr)) =
|
||||
&join.join_operator
|
||||
{
|
||||
Self::validate_expr(expr, &available_aliases, original_sql)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Validate WHERE conditions
|
||||
if let Some(selection) = &select.selection {
|
||||
Self::validate_expr(selection, &available_aliases, original_sql)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn collect_aliases_from_table_factor(tf: &TableFactor, aliases: &mut HashSet<String>) {
|
||||
match tf {
|
||||
TableFactor::Table { name, alias, .. } => {
|
||||
if let Some(table_alias) = alias {
|
||||
aliases.insert(table_alias.name.value.clone());
|
||||
} else if let Some(last) = name.0.last() {
|
||||
match last {
|
||||
sqlparser::ast::ObjectNamePart::Identifier(i) => {
|
||||
aliases.insert(i.value.clone());
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
TableFactor::Derived {
|
||||
subquery,
|
||||
alias: Some(table_alias),
|
||||
..
|
||||
} => {
|
||||
aliases.insert(table_alias.name.value.clone());
|
||||
// A derived table is technically a nested scope which is opaque outside, but for pure semantic checks
|
||||
// its internal contents should be validated purely within its own scope (not leaking external aliases in, usually)
|
||||
// but Postgres allows lateral correlation. We will validate its interior with an empty scope.
|
||||
let _ = Self::validate_query(subquery, &HashSet::new(), "");
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_expr(
|
||||
expr: &Expr,
|
||||
available_aliases: &HashSet<String>,
|
||||
sql: &str,
|
||||
) -> Result<(), String> {
|
||||
match expr {
|
||||
Expr::CompoundIdentifier(idents) => {
|
||||
if idents.len() == 2 {
|
||||
let alias = &idents[0].value;
|
||||
if !available_aliases.is_empty() && !available_aliases.contains(alias) {
|
||||
return Err(format!(
|
||||
"Semantic Error: Orchestrated query referenced table alias '{}' but it was not declared in the query's FROM/JOIN clauses.\nAvailable aliases: {:?}\nSQL: {}",
|
||||
alias, available_aliases, sql
|
||||
));
|
||||
}
|
||||
} else if idents.len() > 2 {
|
||||
let alias = &idents[1].value; // In form schema.table.column, 'table' is idents[1]
|
||||
if !available_aliases.is_empty() && !available_aliases.contains(alias) {
|
||||
return Err(format!(
|
||||
"Semantic Error: Orchestrated query referenced table '{}' but it was not mapped.\nAvailable aliases: {:?}\nSQL: {}",
|
||||
alias, available_aliases, sql
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
Expr::Subquery(subquery) => Self::validate_query(subquery, available_aliases, sql)?,
|
||||
Expr::Exists { subquery, .. } => Self::validate_query(subquery, available_aliases, sql)?,
|
||||
Expr::InSubquery {
|
||||
expr: e, subquery, ..
|
||||
} => {
|
||||
Self::validate_expr(e, available_aliases, sql)?;
|
||||
Self::validate_query(subquery, available_aliases, sql)?;
|
||||
}
|
||||
Expr::BinaryOp { left, right, .. } => {
|
||||
Self::validate_expr(left, available_aliases, sql)?;
|
||||
Self::validate_expr(right, available_aliases, sql)?;
|
||||
}
|
||||
Expr::IsFalse(e)
|
||||
| Expr::IsNotFalse(e)
|
||||
| Expr::IsTrue(e)
|
||||
| Expr::IsNotTrue(e)
|
||||
| Expr::IsNull(e)
|
||||
| Expr::IsNotNull(e)
|
||||
| Expr::InList { expr: e, .. }
|
||||
| Expr::Nested(e)
|
||||
| Expr::UnaryOp { expr: e, .. }
|
||||
| Expr::Cast { expr: e, .. }
|
||||
| Expr::Like { expr: e, .. }
|
||||
| Expr::ILike { expr: e, .. }
|
||||
| Expr::AnyOp { left: e, .. }
|
||||
| Expr::AllOp { left: e, .. } => {
|
||||
Self::validate_expr(e, available_aliases, sql)?;
|
||||
}
|
||||
Expr::Function(func) => {
|
||||
if let sqlparser::ast::FunctionArguments::List(args) = &func.args {
|
||||
if let Some(sqlparser::ast::FunctionArg::Unnamed(sqlparser::ast::FunctionArgExpr::Expr(
|
||||
e,
|
||||
))) = args.args.get(0)
|
||||
{
|
||||
Self::validate_expr(e, available_aliases, sql)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
7
src/tests/types/mod.rs
Normal file
7
src/tests/types/mod.rs
Normal file
@ -0,0 +1,7 @@
|
||||
pub mod case;
|
||||
pub mod expect;
|
||||
pub mod suite;
|
||||
|
||||
pub use case::Case;
|
||||
pub use expect::Expect;
|
||||
pub use suite::Suite;
|
||||
10
src/tests/types/suite.rs
Normal file
10
src/tests/types/suite.rs
Normal file
@ -0,0 +1,10 @@
|
||||
use super::case::Case;
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Suite {
|
||||
#[allow(dead_code)]
|
||||
pub description: String,
|
||||
pub database: serde_json::Value,
|
||||
pub tests: Vec<Case>,
|
||||
}
|
||||
@ -4,7 +4,6 @@ pub mod context;
|
||||
pub mod error;
|
||||
pub mod result;
|
||||
pub mod rules;
|
||||
pub mod util;
|
||||
|
||||
pub use context::ValidationContext;
|
||||
pub use error::ValidationError;
|
||||
@ -46,11 +45,7 @@ impl Validator {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn validate(
|
||||
&self,
|
||||
schema_id: &str,
|
||||
instance: &Value,
|
||||
) -> Result<ValidationResult, ValidationError> {
|
||||
pub fn validate(&self, schema_id: &str, instance: &Value) -> crate::drop::Drop {
|
||||
if let Some(schema) = self.db.schemas.get(schema_id) {
|
||||
let ctx = ValidationContext::new(
|
||||
&self.db,
|
||||
@ -61,13 +56,50 @@ impl Validator {
|
||||
false,
|
||||
false,
|
||||
);
|
||||
ctx.validate_scoped()
|
||||
match ctx.validate_scoped() {
|
||||
Ok(result) => {
|
||||
if result.is_valid() {
|
||||
crate::drop::Drop::success()
|
||||
} else {
|
||||
let errors: Vec<crate::drop::Error> = result
|
||||
.errors
|
||||
.into_iter()
|
||||
.map(|e| crate::drop::Error {
|
||||
code: e.code,
|
||||
message: e.message,
|
||||
details: crate::drop::ErrorDetails {
|
||||
path: e.path,
|
||||
cause: None,
|
||||
context: None,
|
||||
schema: None,
|
||||
},
|
||||
})
|
||||
.collect();
|
||||
crate::drop::Drop::with_errors(errors)
|
||||
}
|
||||
}
|
||||
Err(e) => crate::drop::Drop::with_errors(vec![crate::drop::Error {
|
||||
code: e.code,
|
||||
message: e.message,
|
||||
details: crate::drop::ErrorDetails {
|
||||
path: e.path,
|
||||
cause: None,
|
||||
context: None,
|
||||
schema: None,
|
||||
},
|
||||
}]),
|
||||
}
|
||||
} else {
|
||||
Err(ValidationError {
|
||||
crate::drop::Drop::with_errors(vec![crate::drop::Error {
|
||||
code: "SCHEMA_NOT_FOUND".to_string(),
|
||||
message: format!("Schema {} not found", schema_id),
|
||||
path: "".to_string(),
|
||||
})
|
||||
details: crate::drop::ErrorDetails {
|
||||
path: "/".to_string(),
|
||||
cause: None,
|
||||
context: None,
|
||||
schema: None,
|
||||
},
|
||||
}])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,91 +0,0 @@
|
||||
use serde::Deserialize;
|
||||
use std::fs;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct TestSuite {
|
||||
#[allow(dead_code)]
|
||||
description: String,
|
||||
database: serde_json::Value,
|
||||
tests: Vec<TestCase>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct TestCase {
|
||||
description: String,
|
||||
data: serde_json::Value,
|
||||
valid: bool,
|
||||
// Support explicit schema ID target for test case
|
||||
schema_id: String,
|
||||
}
|
||||
|
||||
// use crate::validator::registry::REGISTRY; // No longer used directly for tests!
|
||||
use crate::validator::Validator;
|
||||
use serde_json::Value;
|
||||
|
||||
pub fn deserialize_some<'de, D>(deserializer: D) -> Result<Option<Value>, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let v = Value::deserialize(deserializer)?;
|
||||
Ok(Some(v))
|
||||
}
|
||||
|
||||
pub fn run_test_file_at_index(path: &str, index: usize) -> Result<(), String> {
|
||||
let content =
|
||||
fs::read_to_string(path).unwrap_or_else(|_| panic!("Failed to read file: {}", path));
|
||||
let suite: Vec<TestSuite> = serde_json::from_str(&content)
|
||||
.unwrap_or_else(|e| panic!("Failed to parse JSON in {}: {}", path, e));
|
||||
|
||||
if index >= suite.len() {
|
||||
panic!("Index {} out of bounds for file {}", index, path);
|
||||
}
|
||||
|
||||
let group = &suite[index];
|
||||
let mut failures = Vec::<String>::new();
|
||||
|
||||
let db_json = group.database.clone();
|
||||
let db = crate::database::Database::new(&db_json);
|
||||
let validator = Validator::new(std::sync::Arc::new(db));
|
||||
|
||||
// 4. Run Tests
|
||||
for test in group.tests.iter() {
|
||||
let schema_id = &test.schema_id;
|
||||
|
||||
if !validator.db.schemas.contains_key(schema_id) {
|
||||
failures.push(format!(
|
||||
"[{}] Missing Schema: Cannot find schema ID '{}'",
|
||||
group.description, schema_id
|
||||
));
|
||||
continue;
|
||||
}
|
||||
|
||||
let result = validator.validate(schema_id, &test.data);
|
||||
|
||||
let (got_valid, _errors) = match &result {
|
||||
Ok(res) => (res.is_valid(), &res.errors),
|
||||
Err(_e) => {
|
||||
// If we encounter an execution error (e.g. Schema Not Found),
|
||||
// we treat it as a test failure.
|
||||
(false, &vec![])
|
||||
}
|
||||
};
|
||||
|
||||
if got_valid != test.valid {
|
||||
let error_msg = match &result {
|
||||
Ok(res) => format!("{:?}", res.errors),
|
||||
Err(e) => format!("Execution Error: {:?}", e),
|
||||
};
|
||||
|
||||
failures.push(format!(
|
||||
"[{}] Test '{}' failed. Expected: {}, Got: {}. Errors: {}",
|
||||
group.description, test.description, test.valid, got_valid, error_msg
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if !failures.is_empty() {
|
||||
return Err(failures.join("\n"));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
54
t10.json
Normal file
54
t10.json
Normal file
@ -0,0 +1,54 @@
|
||||
[
|
||||
[
|
||||
"(SELECT jsonb_build_object(",
|
||||
" 'id', organization_1.id,",
|
||||
" 'type', CASE",
|
||||
" WHEN organization_1.type = 'person' THEN",
|
||||
" ((SELECT jsonb_build_object(",
|
||||
" 'age', person_3.age,",
|
||||
" 'archived', entity_5.archived,",
|
||||
" 'created_at', entity_5.created_at,",
|
||||
" 'first_name', person_3.first_name,",
|
||||
" 'id', entity_5.id,",
|
||||
" 'last_name', person_3.last_name,",
|
||||
" 'name', entity_5.name,",
|
||||
" 'type', entity_5.type",
|
||||
" )",
|
||||
" FROM agreego.person person_3",
|
||||
" JOIN agreego.organization organization_4 ON organization_4.id = person_3.id",
|
||||
" JOIN agreego.entity entity_5 ON entity_5.id = organization_4.id",
|
||||
" WHERE",
|
||||
" NOT entity_5.archived))",
|
||||
" WHEN organization_1.type = 'bot' THEN",
|
||||
" ((SELECT jsonb_build_object(",
|
||||
" 'archived', entity_8.archived,",
|
||||
" 'created_at', entity_8.created_at,",
|
||||
" 'id', entity_8.id,",
|
||||
" 'name', entity_8.name,",
|
||||
" 'token', bot_6.token,",
|
||||
" 'type', entity_8.type",
|
||||
" )",
|
||||
" FROM agreego.bot bot_6",
|
||||
" JOIN agreego.organization organization_7 ON organization_7.id = bot_6.id",
|
||||
" JOIN agreego.entity entity_8 ON entity_8.id = organization_7.id",
|
||||
" WHERE",
|
||||
" NOT entity_8.archived))",
|
||||
" WHEN organization_1.type = 'organization' THEN",
|
||||
" ((SELECT jsonb_build_object(",
|
||||
" 'archived', entity_10.archived,",
|
||||
" 'created_at', entity_10.created_at,",
|
||||
" 'id', entity_10.id,",
|
||||
" 'name', entity_10.name,",
|
||||
" 'type', entity_10.type",
|
||||
" )",
|
||||
" FROM agreego.organization organization_9",
|
||||
" JOIN agreego.entity entity_10 ON entity_10.id = organization_9.id",
|
||||
" WHERE",
|
||||
" NOT entity_10.archived))",
|
||||
" ELSE NULL END",
|
||||
")",
|
||||
"FROM agreego.organization organization_1",
|
||||
"JOIN agreego.entity entity_2 ON entity_2.id = organization_1.id",
|
||||
"WHERE NOT entity_2.archived)"
|
||||
]
|
||||
]
|
||||
164
t4.json
Normal file
164
t4.json
Normal file
@ -0,0 +1,164 @@
|
||||
[
|
||||
[
|
||||
"(SELECT jsonb_build_object(",
|
||||
" 'addresses',",
|
||||
" (SELECT COALESCE(jsonb_agg(jsonb_build_object(",
|
||||
" 'archived', entity_6.archived,",
|
||||
" 'created_at', entity_6.created_at,",
|
||||
" 'id', entity_6.id,",
|
||||
" 'is_primary', contact_4.is_primary,",
|
||||
" 'name', entity_6.name,",
|
||||
" 'target',",
|
||||
" (SELECT jsonb_build_object(",
|
||||
" 'archived', entity_8.archived,",
|
||||
" 'city', address_7.city,",
|
||||
" 'created_at', entity_8.created_at,",
|
||||
" 'id', entity_8.id,",
|
||||
" 'name', entity_8.name,",
|
||||
" 'type', entity_8.type",
|
||||
" )",
|
||||
" FROM agreego.address address_7",
|
||||
" JOIN agreego.entity entity_8 ON entity_8.id = address_7.id",
|
||||
" WHERE",
|
||||
" NOT entity_8.archived",
|
||||
" AND relationship_5.target_id = address_7.id),",
|
||||
" 'type', entity_6.type",
|
||||
" )), '[]'::jsonb)",
|
||||
" FROM agreego.contact contact_4",
|
||||
" JOIN agreego.relationship relationship_5 ON relationship_5.id = contact_4.id",
|
||||
" JOIN agreego.entity entity_6 ON entity_6.id = relationship_5.id",
|
||||
" WHERE",
|
||||
" NOT entity_6.archived",
|
||||
" AND contact_4.parent_id = entity_3.id),",
|
||||
" 'age', person_1.age,",
|
||||
" 'archived', entity_3.archived,",
|
||||
" 'contacts',",
|
||||
" (SELECT COALESCE(jsonb_agg(jsonb_build_object(",
|
||||
" 'archived', entity_11.archived,",
|
||||
" 'created_at', entity_11.created_at,",
|
||||
" 'id', entity_11.id,",
|
||||
" 'is_primary', contact_9.is_primary,",
|
||||
" 'name', entity_11.name,",
|
||||
" 'target', CASE",
|
||||
" WHEN entity_11.target_type = 'address' THEN",
|
||||
" ((SELECT jsonb_build_object(",
|
||||
" 'archived', entity_17.archived,",
|
||||
" 'city', address_16.city,",
|
||||
" 'created_at', entity_17.created_at,",
|
||||
" 'id', entity_17.id,",
|
||||
" 'name', entity_17.name,",
|
||||
" 'type', entity_17.type",
|
||||
" )",
|
||||
" FROM agreego.address address_16",
|
||||
" JOIN agreego.entity entity_17 ON entity_17.id = address_16.id",
|
||||
" WHERE",
|
||||
" NOT entity_17.archived",
|
||||
" AND relationship_10.target_id = address_16.id))",
|
||||
" WHEN entity_11.target_type = 'email_address' THEN",
|
||||
" ((SELECT jsonb_build_object(",
|
||||
" 'address', email_address_14.address,",
|
||||
" 'archived', entity_15.archived,",
|
||||
" 'created_at', entity_15.created_at,",
|
||||
" 'id', entity_15.id,",
|
||||
" 'name', entity_15.name,",
|
||||
" 'type', entity_15.type",
|
||||
" )",
|
||||
" FROM agreego.email_address email_address_14",
|
||||
" JOIN agreego.entity entity_15 ON entity_15.id = email_address_14.id",
|
||||
" WHERE",
|
||||
" NOT entity_15.archived",
|
||||
" AND relationship_10.target_id = email_address_14.id))",
|
||||
" WHEN entity_11.target_type = 'phone_number' THEN",
|
||||
" ((SELECT jsonb_build_object(",
|
||||
" 'archived', entity_13.archived,",
|
||||
" 'created_at', entity_13.created_at,",
|
||||
" 'id', entity_13.id,",
|
||||
" 'name', entity_13.name,",
|
||||
" 'number', phone_number_12.number,",
|
||||
" 'type', entity_13.type",
|
||||
" )",
|
||||
" FROM agreego.phone_number phone_number_12",
|
||||
" JOIN agreego.entity entity_13 ON entity_13.id = phone_number_12.id",
|
||||
" WHERE",
|
||||
" NOT entity_13.archived",
|
||||
" AND relationship_10.target_id = phone_number_12.id))",
|
||||
" ELSE NULL END,",
|
||||
" 'type', entity_11.type",
|
||||
" )), '[]'::jsonb)",
|
||||
" FROM agreego.contact contact_9",
|
||||
" JOIN agreego.relationship relationship_10 ON relationship_10.id = contact_9.id",
|
||||
" JOIN agreego.entity entity_11 ON entity_11.id = relationship_10.id",
|
||||
" WHERE",
|
||||
" NOT entity_11.archived",
|
||||
" AND contact_9.parent_id = entity_3.id),",
|
||||
" 'created_at', entity_3.created_at,",
|
||||
" 'email_addresses',",
|
||||
" (SELECT COALESCE(jsonb_agg(jsonb_build_object(",
|
||||
" 'archived', entity_20.archived,",
|
||||
" 'created_at', entity_20.created_at,",
|
||||
" 'id', entity_20.id,",
|
||||
" 'is_primary', contact_18.is_primary,",
|
||||
" 'name', entity_20.name,",
|
||||
" 'target',",
|
||||
" (SELECT jsonb_build_object(",
|
||||
" 'address', email_address_21.address,",
|
||||
" 'archived', entity_22.archived,",
|
||||
" 'created_at', entity_22.created_at,",
|
||||
" 'id', entity_22.id,",
|
||||
" 'name', entity_22.name,",
|
||||
" 'type', entity_22.type",
|
||||
" )",
|
||||
" FROM agreego.email_address email_address_21",
|
||||
" JOIN agreego.entity entity_22 ON entity_22.id = email_address_21.id",
|
||||
" WHERE",
|
||||
" NOT entity_22.archived",
|
||||
" AND relationship_19.target_id = email_address_21.id),",
|
||||
" 'type', entity_20.type",
|
||||
" )), '[]'::jsonb)",
|
||||
" FROM agreego.contact contact_18",
|
||||
" JOIN agreego.relationship relationship_19 ON relationship_19.id = contact_18.id",
|
||||
" JOIN agreego.entity entity_20 ON entity_20.id = relationship_19.id",
|
||||
" WHERE",
|
||||
" NOT entity_20.archived",
|
||||
" AND contact_18.parent_id = entity_3.id),",
|
||||
" 'first_name', person_1.first_name,",
|
||||
" 'id', entity_3.id,",
|
||||
" 'last_name', person_1.last_name,",
|
||||
" 'name', entity_3.name,",
|
||||
" 'phone_numbers',",
|
||||
" (SELECT COALESCE(jsonb_agg(jsonb_build_object(",
|
||||
" 'archived', entity_25.archived,",
|
||||
" 'created_at', entity_25.created_at,",
|
||||
" 'id', entity_25.id,",
|
||||
" 'is_primary', contact_23.is_primary,",
|
||||
" 'name', entity_25.name,",
|
||||
" 'target',",
|
||||
" (SELECT jsonb_build_object(",
|
||||
" 'archived', entity_27.archived,",
|
||||
" 'created_at', entity_27.created_at,",
|
||||
" 'id', entity_27.id,",
|
||||
" 'name', entity_27.name,",
|
||||
" 'number', phone_number_26.number,",
|
||||
" 'type', entity_27.type",
|
||||
" )",
|
||||
" FROM agreego.phone_number phone_number_26",
|
||||
" JOIN agreego.entity entity_27 ON entity_27.id = phone_number_26.id",
|
||||
" WHERE",
|
||||
" NOT entity_27.archived",
|
||||
" AND relationship_24.target_id = phone_number_26.id),",
|
||||
" 'type', entity_25.type",
|
||||
" )), '[]'::jsonb)",
|
||||
" FROM agreego.contact contact_23",
|
||||
" JOIN agreego.relationship relationship_24 ON relationship_24.id = contact_23.id",
|
||||
" JOIN agreego.entity entity_25 ON entity_25.id = relationship_24.id",
|
||||
" WHERE",
|
||||
" NOT entity_25.archived",
|
||||
" AND contact_23.parent_id = entity_3.id),",
|
||||
" 'type', entity_3.type",
|
||||
")",
|
||||
"FROM agreego.person person_1",
|
||||
"JOIN agreego.organization organization_2 ON organization_2.id = person_1.id",
|
||||
"JOIN agreego.entity entity_3 ON entity_3.id = organization_2.id",
|
||||
"WHERE NOT entity_3.archived)"
|
||||
]
|
||||
]
|
||||
62
test_err.log
62
test_err.log
@ -1,62 +0,0 @@
|
||||
Compiling jspg v0.1.0 (/Users/awgneo/Repositories/thoughtpatterns/cellular/jspg)
|
||||
Finished `test` profile [unoptimized + debuginfo] target(s) in 26.14s
|
||||
Running unittests src/lib.rs (target/debug/deps/jspg-99ace086c3537f5a)
|
||||
|
||||
running 1 test
|
||||
[32m[1m Using[0m[39m [37m[1mPgConfig("pg18")[0m[39m and `pg_config` from [36m/opt/homebrew/opt/postgresql@18/bin/pg_config[39m
|
||||
[32m[1m Building[0m[39m extension with features [36mpg_test pg18[39m
|
||||
[32m[1m Running[0m[39m command [36m"/opt/homebrew/bin/cargo" "build" "--lib" "--features" "pg_test pg18" "--no-default-features" "--message-format=json-render-diagnostics"[39m
|
||||
Compiling jspg v0.1.0 (/Users/awgneo/Repositories/thoughtpatterns/cellular/jspg)
|
||||
Finished `dev` profile [unoptimized + debuginfo] target(s) in 7.10s
|
||||
[32m[1m Installing[0m[39m extension
|
||||
[32m[1m Copying[0m[39m control file to [36m/opt/homebrew/share/postgresql@18/extension/jspg.control[39m
|
||||
[32m[1m Copying[0m[39m shared library to [36m/opt/homebrew/lib/postgresql@18/jspg.dylib[39m
|
||||
[32m[1m Discovered[0m[39m [36m[1m351[0m[39m SQL entities: [36m[1m1[0m[39m schemas ([36m[1m1[0m[39m unique), [36m[1m350[0m[39m functions, [36m[1m0[0m[39m types, [36m[1m0[0m[39m enums, [36m[1m0[0m[39m sqls, [36m[1m0[0m[39m ords, [36m[1m0[0m[39m hashes, [36m[1m0[0m[39m aggregates, [36m[1m0[0m[39m triggers
|
||||
[32m[1m Rebuilding[0m[39m [36mpgrx_embed[39m, in debug mode, for SQL generation with features [36mpg_test pg18[39m
|
||||
Compiling jspg v0.1.0 (/Users/awgneo/Repositories/thoughtpatterns/cellular/jspg)
|
||||
Finished `dev` profile [unoptimized + debuginfo] target(s) in 10.63s
|
||||
[32m[1m Writing[0m[39m SQL entities to /opt/homebrew/share/postgresql@18/extension/jspg--0.1.0.sql
|
||||
[32m[1m Finished[0m[39m installing jspg
|
||||
[36m[2026-03-01 22:54:19.068 EST] [82952] [69a509eb.14408]: LOG: starting PostgreSQL 18.1 (Homebrew) on aarch64-apple-darwin25.2.0, compiled by Apple clang version 17.0.0 (clang-1700.6.3.2), 64-bit[39m
|
||||
[36m[2026-03-01 22:54:19.070 EST] [82952] [69a509eb.14408]: LOG: listening on IPv6 address "::1", port 32218[39m
|
||||
[36m[2026-03-01 22:54:19.070 EST] [82952] [69a509eb.14408]: LOG: listening on IPv4 address "127.0.0.1", port 32218[39m
|
||||
[36m[2026-03-01 22:54:19.071 EST] [82952] [69a509eb.14408]: LOG: listening on Unix socket "/Users/awgneo/Repositories/thoughtpatterns/cellular/jspg/target/test-pgdata/.s.PGSQL.32218"[39m
|
||||
[36m[2026-03-01 22:54:19.077 EST] [82958] [69a509eb.1440e]: LOG: database system was shut down at 2026-03-01 22:49:02 EST[39m
|
||||
[32m[1m Creating[0m[39m database [36m[1mpgrx_tests[0m[39m
|
||||
|
||||
thread 'tests::pg_test_typed_refs_0' (29092254) panicked at /Users/awgneo/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/pgrx-tests-0.16.1/src/framework.rs:166:9:
|
||||
|
||||
|
||||
Postgres Messages:
|
||||
[37m[2m[2026-03-01 22:54:19.068 EST] [82952] [69a509eb.14408]: LOG: starting PostgreSQL 18.1 (Homebrew) on aarch64-apple-darwin25.2.0, compiled by Apple clang version 17.0.0 (clang-1700.6.3.2), 64-bit
|
||||
[2026-03-01 22:54:19.070 EST] [82952] [69a509eb.14408]: LOG: listening on IPv6 address "::1", port 32218
|
||||
[2026-03-01 22:54:19.070 EST] [82952] [69a509eb.14408]: LOG: listening on IPv4 address "127.0.0.1", port 32218
|
||||
[2026-03-01 22:54:19.071 EST] [82952] [69a509eb.14408]: LOG: listening on Unix socket "/Users/awgneo/Repositories/thoughtpatterns/cellular/jspg/target/test-pgdata/.s.PGSQL.32218"
|
||||
[2026-03-01 22:54:19.081 EST] [82952] [69a509eb.14408]: LOG: database system is ready to accept connections
|
||||
[0m[39m
|
||||
|
||||
Test Function Messages:
|
||||
[36m[2026-03-01 22:54:20.058 EST] [82982] [69a509ec.14426]: LOG: statement: START TRANSACTION
|
||||
[2026-03-01 22:54:20.058 EST] [82982] [69a509ec.14426]: LOG: statement: SELECT "tests"."test_typed_refs_0"();
|
||||
[2026-03-01 22:54:20.062 EST] [82982] [69a509ec.14426]: ERROR: called `Result::unwrap()` on an `Err` value: "[Entity inheritance and native type discrimination] Test 'Valid person against organization schema (implicit type allowance)' failed. Expected: true, Got: false. Errors: [ValidationError { code: \"CONST_VIOLATED\", message: \"Value does not match const\", path: \"/type\" }, ValidationError { code: \"STRICT_PROPERTY_VIOLATION\", message: \"Unexpected property 'first_name'\", path: \"/first_name\" }, ValidationError { code: \"STRICT_PROPERTY_VIOLATION\", message: \"Unexpected property 'first_name'\", path: \"/first_name\" }, ValidationError { code: \"STRICT_PROPERTY_VIOLATION\", message: \"Unexpected property 'first_name'\", path: \"/first_name\" }]\n[Entity inheritance and native type discrimination] Test 'Valid organization against organization schema' failed. Expected: true, Got: false. Errors: [ValidationError { code: \"CONST_VIOLATED\", message: \"Value does not match const\", path: \"/type\" }]\n[Entity inheritance and native type discrimination] Test 'Invalid entity against organization schema (ancestor not allowed)' failed. Expected: false, Got: true. Errors: []"
|
||||
[2026-03-01 22:54:20.062 EST] [82982] [69a509ec.14426]: STATEMENT: SELECT "tests"."test_typed_refs_0"();
|
||||
[2026-03-01 22:54:20.062 EST] [82982] [69a509ec.14426]: LOG: statement: ROLLBACK
|
||||
[39m
|
||||
|
||||
Client Error:
|
||||
[31m[1mcalled `Result::unwrap()` on an `Err` value: "[Entity inheritance and native type discrimination] Test 'Valid person against organization schema (implicit type allowance)' failed. Expected: true, Got: false. Errors: [ValidationError { code: \"CONST_VIOLATED\", message: \"Value does not match const\", path: \"/type\" }, ValidationError { code: \"STRICT_PROPERTY_VIOLATION\", message: \"Unexpected property 'first_name'\", path: \"/first_name\" }, ValidationError { code: \"STRICT_PROPERTY_VIOLATION\", message: \"Unexpected property 'first_name'\", path: \"/first_name\" }, ValidationError { code: \"STRICT_PROPERTY_VIOLATION\", message: \"Unexpected property 'first_name'\", path: \"/first_name\" }]\n[Entity inheritance and native type discrimination] Test 'Valid organization against organization schema' failed. Expected: true, Got: false. Errors: [ValidationError { code: \"CONST_VIOLATED\", message: \"Value does not match const\", path: \"/type\" }]\n[Entity inheritance and native type discrimination] Test 'Invalid entity against organization schema (ancestor not allowed)' failed. Expected: false, Got: true. Errors: []"[0m[39m
|
||||
postgres location: [37m[2mfixtures.rs[0m[39m
|
||||
rust location: [33m<unknown>[39m
|
||||
|
||||
|
||||
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
|
||||
test tests::pg_test_typed_refs_0 ... FAILED
|
||||
|
||||
failures:
|
||||
|
||||
failures:
|
||||
tests::pg_test_typed_refs_0
|
||||
|
||||
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 343 filtered out; finished in 21.82s
|
||||
|
||||
error: test failed, to rerun pass `--lib`
|
||||
1669
tests/fixtures.rs
1669
tests/fixtures.rs
File diff suppressed because it is too large
Load Diff
152
tests/fixtures/additionalProperties.json
vendored
152
tests/fixtures/additionalProperties.json
vendored
@ -1,152 +0,0 @@
|
||||
[
|
||||
{
|
||||
"description": "additionalProperties validates properties not matched by properties",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"$id": "schema1",
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "string"
|
||||
},
|
||||
"bar": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"additionalProperties": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "defined properties are valid",
|
||||
"data": {
|
||||
"foo": "value",
|
||||
"bar": 123
|
||||
},
|
||||
"valid": true,
|
||||
"schema_id": "schema1"
|
||||
},
|
||||
{
|
||||
"description": "additional property matching schema is valid",
|
||||
"data": {
|
||||
"foo": "value",
|
||||
"is_active": true,
|
||||
"hidden": false
|
||||
},
|
||||
"valid": true,
|
||||
"schema_id": "schema1"
|
||||
},
|
||||
{
|
||||
"description": "additional property not matching schema is invalid",
|
||||
"data": {
|
||||
"foo": "value",
|
||||
"is_active": 1
|
||||
},
|
||||
"valid": false,
|
||||
"schema_id": "schema1"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "extensible: true with additionalProperties still validates structure",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"extensible": true,
|
||||
"additionalProperties": {
|
||||
"type": "integer"
|
||||
},
|
||||
"$id": "additionalProperties_1_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "additional property matching schema is valid",
|
||||
"data": {
|
||||
"foo": "hello",
|
||||
"count": 5,
|
||||
"age": 42
|
||||
},
|
||||
"valid": true,
|
||||
"schema_id": "additionalProperties_1_0"
|
||||
},
|
||||
{
|
||||
"description": "additional property not matching schema is invalid despite extensible: true",
|
||||
"data": {
|
||||
"foo": "hello",
|
||||
"count": "five"
|
||||
},
|
||||
"valid": false,
|
||||
"schema_id": "additionalProperties_1_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "complex additionalProperties with object and array items",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"$id": "schema3",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "valid array of strings",
|
||||
"data": {
|
||||
"type": "my_type",
|
||||
"group_a": [
|
||||
"field1",
|
||||
"field2"
|
||||
],
|
||||
"group_b": [
|
||||
"field3"
|
||||
]
|
||||
},
|
||||
"valid": true,
|
||||
"schema_id": "schema3"
|
||||
},
|
||||
{
|
||||
"description": "invalid array of integers",
|
||||
"data": {
|
||||
"type": "my_type",
|
||||
"group_a": [
|
||||
1,
|
||||
2
|
||||
]
|
||||
},
|
||||
"valid": false,
|
||||
"schema_id": "schema3"
|
||||
},
|
||||
{
|
||||
"description": "invalid non-array type",
|
||||
"data": {
|
||||
"type": "my_type",
|
||||
"group_a": "field1"
|
||||
},
|
||||
"valid": false,
|
||||
"schema_id": "schema3"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
590
tests/fixtures/allOf.json
vendored
590
tests/fixtures/allOf.json
vendored
@ -1,590 +0,0 @@
|
||||
[
|
||||
{
|
||||
"description": "allOf",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
"properties": {
|
||||
"bar": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"bar"
|
||||
]
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"foo"
|
||||
]
|
||||
}
|
||||
],
|
||||
"$id": "allOf_0_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "allOf",
|
||||
"data": {
|
||||
"foo": "baz",
|
||||
"bar": 2
|
||||
},
|
||||
"valid": true,
|
||||
"schema_id": "allOf_0_0"
|
||||
},
|
||||
{
|
||||
"description": "mismatch second",
|
||||
"data": {
|
||||
"foo": "baz"
|
||||
},
|
||||
"valid": false,
|
||||
"schema_id": "allOf_0_0"
|
||||
},
|
||||
{
|
||||
"description": "mismatch first",
|
||||
"data": {
|
||||
"bar": 2
|
||||
},
|
||||
"valid": false,
|
||||
"schema_id": "allOf_0_0"
|
||||
},
|
||||
{
|
||||
"description": "wrong type",
|
||||
"data": {
|
||||
"foo": "baz",
|
||||
"bar": "quux"
|
||||
},
|
||||
"valid": false,
|
||||
"schema_id": "allOf_0_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "allOf with base schema",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"properties": {
|
||||
"bar": {
|
||||
"type": "integer"
|
||||
},
|
||||
"baz": {},
|
||||
"foo": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"bar"
|
||||
],
|
||||
"allOf": [
|
||||
{
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"foo"
|
||||
]
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"baz": {
|
||||
"type": "null"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"baz"
|
||||
]
|
||||
}
|
||||
],
|
||||
"$id": "allOf_1_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "valid",
|
||||
"data": {
|
||||
"foo": "quux",
|
||||
"bar": 2,
|
||||
"baz": null
|
||||
},
|
||||
"valid": true,
|
||||
"schema_id": "allOf_1_0"
|
||||
},
|
||||
{
|
||||
"description": "mismatch base schema",
|
||||
"data": {
|
||||
"foo": "quux",
|
||||
"baz": null
|
||||
},
|
||||
"valid": false,
|
||||
"schema_id": "allOf_1_0"
|
||||
},
|
||||
{
|
||||
"description": "mismatch first allOf",
|
||||
"data": {
|
||||
"bar": 2,
|
||||
"baz": null
|
||||
},
|
||||
"valid": false,
|
||||
"schema_id": "allOf_1_0"
|
||||
},
|
||||
{
|
||||
"description": "mismatch second allOf",
|
||||
"data": {
|
||||
"foo": "quux",
|
||||
"bar": 2
|
||||
},
|
||||
"valid": false,
|
||||
"schema_id": "allOf_1_0"
|
||||
},
|
||||
{
|
||||
"description": "mismatch both",
|
||||
"data": {
|
||||
"bar": 2
|
||||
},
|
||||
"valid": false,
|
||||
"schema_id": "allOf_1_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "allOf simple types",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
"maximum": 30
|
||||
},
|
||||
{
|
||||
"minimum": 20
|
||||
}
|
||||
],
|
||||
"$id": "allOf_2_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "valid",
|
||||
"data": 25,
|
||||
"valid": true,
|
||||
"schema_id": "allOf_2_0"
|
||||
},
|
||||
{
|
||||
"description": "mismatch one",
|
||||
"data": 35,
|
||||
"valid": false,
|
||||
"schema_id": "allOf_2_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "allOf with boolean schemas, all true",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"allOf": [
|
||||
true,
|
||||
true
|
||||
],
|
||||
"$id": "allOf_3_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "any value is valid",
|
||||
"data": "foo",
|
||||
"valid": true,
|
||||
"schema_id": "allOf_3_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "allOf with boolean schemas, some false",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"allOf": [
|
||||
true,
|
||||
false
|
||||
],
|
||||
"$id": "allOf_4_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "any value is invalid",
|
||||
"data": "foo",
|
||||
"valid": false,
|
||||
"schema_id": "allOf_4_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "allOf with boolean schemas, all false",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"allOf": [
|
||||
false,
|
||||
false
|
||||
],
|
||||
"$id": "allOf_5_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "any value is invalid",
|
||||
"data": "foo",
|
||||
"valid": false,
|
||||
"schema_id": "allOf_5_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "allOf with one empty schema",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"allOf": [
|
||||
{}
|
||||
],
|
||||
"$id": "allOf_6_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "any data is valid",
|
||||
"data": 1,
|
||||
"valid": true,
|
||||
"schema_id": "allOf_6_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "allOf with two empty schemas",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"allOf": [
|
||||
{},
|
||||
{}
|
||||
],
|
||||
"$id": "allOf_7_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "any data is valid",
|
||||
"data": 1,
|
||||
"valid": true,
|
||||
"schema_id": "allOf_7_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "allOf with the first empty schema",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"allOf": [
|
||||
{},
|
||||
{
|
||||
"type": "number"
|
||||
}
|
||||
],
|
||||
"$id": "allOf_8_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "number is valid",
|
||||
"data": 1,
|
||||
"valid": true,
|
||||
"schema_id": "allOf_8_0"
|
||||
},
|
||||
{
|
||||
"description": "string is invalid",
|
||||
"data": "foo",
|
||||
"valid": false,
|
||||
"schema_id": "allOf_8_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "allOf with the last empty schema",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
"type": "number"
|
||||
},
|
||||
{}
|
||||
],
|
||||
"$id": "allOf_9_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "number is valid",
|
||||
"data": 1,
|
||||
"valid": true,
|
||||
"schema_id": "allOf_9_0"
|
||||
},
|
||||
{
|
||||
"description": "string is invalid",
|
||||
"data": "foo",
|
||||
"valid": false,
|
||||
"schema_id": "allOf_9_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "nested allOf, to check validation semantics",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"$id": "allOf_10_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "null is valid",
|
||||
"data": null,
|
||||
"valid": true,
|
||||
"schema_id": "allOf_10_0"
|
||||
},
|
||||
{
|
||||
"description": "anything non-null is invalid",
|
||||
"data": 123,
|
||||
"valid": false,
|
||||
"schema_id": "allOf_10_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "extensible: true allows extra properties in allOf",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
"properties": {
|
||||
"bar": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"bar"
|
||||
]
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"foo"
|
||||
]
|
||||
}
|
||||
],
|
||||
"extensible": true,
|
||||
"$id": "allOf_12_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "extra property is valid",
|
||||
"data": {
|
||||
"foo": "baz",
|
||||
"bar": 2,
|
||||
"qux": 3
|
||||
},
|
||||
"valid": true,
|
||||
"schema_id": "allOf_12_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "strict by default with allOf properties",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
"properties": {
|
||||
"foo": {
|
||||
"const": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"bar": {
|
||||
"const": 2
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"$id": "allOf_13_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "validates merged properties",
|
||||
"data": {
|
||||
"foo": 1,
|
||||
"bar": 2
|
||||
},
|
||||
"valid": true,
|
||||
"schema_id": "allOf_13_0"
|
||||
},
|
||||
{
|
||||
"description": "fails on extra property z explicitly",
|
||||
"data": {
|
||||
"foo": 1,
|
||||
"bar": 2,
|
||||
"z": 3
|
||||
},
|
||||
"valid": false,
|
||||
"schema_id": "allOf_13_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "allOf with nested extensible: true (partial looseness)",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
"properties": {
|
||||
"foo": {
|
||||
"const": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"extensible": true,
|
||||
"properties": {
|
||||
"bar": {
|
||||
"const": 2
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"$id": "allOf_14_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "extensible subschema doesn't make root extensible if root is strict",
|
||||
"data": {
|
||||
"foo": 1,
|
||||
"bar": 2,
|
||||
"z": 3
|
||||
},
|
||||
"valid": true,
|
||||
"schema_id": "allOf_14_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "strictness: allOf composition with strict refs",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "partA"
|
||||
},
|
||||
{
|
||||
"$ref": "partB"
|
||||
}
|
||||
],
|
||||
"$id": "allOf_15_0"
|
||||
},
|
||||
{
|
||||
"$id": "partA",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"$id": "partB",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "merged instance is valid",
|
||||
"data": {
|
||||
"id": "1",
|
||||
"name": "Me"
|
||||
},
|
||||
"valid": true,
|
||||
"schema_id": "allOf_15_0"
|
||||
},
|
||||
{
|
||||
"description": "extra property is invalid (root is strict)",
|
||||
"data": {
|
||||
"id": "1",
|
||||
"name": "Me",
|
||||
"extra": 1
|
||||
},
|
||||
"valid": false,
|
||||
"schema_id": "allOf_15_0"
|
||||
},
|
||||
{
|
||||
"description": "partA mismatch is invalid",
|
||||
"data": {
|
||||
"id": 1,
|
||||
"name": "Me"
|
||||
},
|
||||
"valid": false,
|
||||
"schema_id": "allOf_15_0"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
143
tests/fixtures/booleanSchema.json
vendored
143
tests/fixtures/booleanSchema.json
vendored
@ -1,143 +0,0 @@
|
||||
[
|
||||
{
|
||||
"description": "boolean schema 'true'",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"$id": "booleanSchema_0_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "number is valid",
|
||||
"data": 1,
|
||||
"valid": true,
|
||||
"schema_id": "booleanSchema_0_0"
|
||||
},
|
||||
{
|
||||
"description": "string is valid",
|
||||
"data": "foo",
|
||||
"valid": true,
|
||||
"schema_id": "booleanSchema_0_0"
|
||||
},
|
||||
{
|
||||
"description": "boolean true is valid",
|
||||
"data": true,
|
||||
"valid": true,
|
||||
"schema_id": "booleanSchema_0_0"
|
||||
},
|
||||
{
|
||||
"description": "boolean false is valid",
|
||||
"data": false,
|
||||
"valid": true,
|
||||
"schema_id": "booleanSchema_0_0"
|
||||
},
|
||||
{
|
||||
"description": "null is valid",
|
||||
"data": null,
|
||||
"valid": true,
|
||||
"schema_id": "booleanSchema_0_0"
|
||||
},
|
||||
{
|
||||
"description": "object is valid",
|
||||
"data": {
|
||||
"foo": "bar"
|
||||
},
|
||||
"valid": true,
|
||||
"schema_id": "booleanSchema_0_0"
|
||||
},
|
||||
{
|
||||
"description": "empty object is valid",
|
||||
"data": {},
|
||||
"valid": true,
|
||||
"schema_id": "booleanSchema_0_0"
|
||||
},
|
||||
{
|
||||
"description": "array is valid",
|
||||
"data": [
|
||||
"foo"
|
||||
],
|
||||
"valid": true,
|
||||
"schema_id": "booleanSchema_0_0"
|
||||
},
|
||||
{
|
||||
"description": "empty array is valid",
|
||||
"data": [],
|
||||
"valid": true,
|
||||
"schema_id": "booleanSchema_0_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "boolean schema 'false'",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"not": {},
|
||||
"$id": "booleanSchema_1_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "number is invalid",
|
||||
"data": 1,
|
||||
"valid": false,
|
||||
"schema_id": "booleanSchema_1_0"
|
||||
},
|
||||
{
|
||||
"description": "string is invalid",
|
||||
"data": "foo",
|
||||
"valid": false,
|
||||
"schema_id": "booleanSchema_1_0"
|
||||
},
|
||||
{
|
||||
"description": "boolean true is invalid",
|
||||
"data": true,
|
||||
"valid": false,
|
||||
"schema_id": "booleanSchema_1_0"
|
||||
},
|
||||
{
|
||||
"description": "boolean false is invalid",
|
||||
"data": false,
|
||||
"valid": false,
|
||||
"schema_id": "booleanSchema_1_0"
|
||||
},
|
||||
{
|
||||
"description": "null is invalid",
|
||||
"data": null,
|
||||
"valid": false,
|
||||
"schema_id": "booleanSchema_1_0"
|
||||
},
|
||||
{
|
||||
"description": "object is invalid",
|
||||
"data": {
|
||||
"foo": "bar"
|
||||
},
|
||||
"valid": false,
|
||||
"schema_id": "booleanSchema_1_0"
|
||||
},
|
||||
{
|
||||
"description": "empty object is invalid",
|
||||
"data": {},
|
||||
"valid": false,
|
||||
"schema_id": "booleanSchema_1_0"
|
||||
},
|
||||
{
|
||||
"description": "array is invalid",
|
||||
"data": [
|
||||
"foo"
|
||||
],
|
||||
"valid": false,
|
||||
"schema_id": "booleanSchema_1_0"
|
||||
},
|
||||
{
|
||||
"description": "empty array is invalid",
|
||||
"data": [],
|
||||
"valid": false,
|
||||
"schema_id": "booleanSchema_1_0"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
650
tests/fixtures/const.json
vendored
650
tests/fixtures/const.json
vendored
@ -1,650 +0,0 @@
|
||||
[
|
||||
{
|
||||
"description": "const validation",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"const": 2,
|
||||
"$id": "const_0_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "same value is valid",
|
||||
"data": 2,
|
||||
"valid": true,
|
||||
"schema_id": "const_0_0"
|
||||
},
|
||||
{
|
||||
"description": "another value is invalid",
|
||||
"data": 5,
|
||||
"valid": false,
|
||||
"schema_id": "const_0_0"
|
||||
},
|
||||
{
|
||||
"description": "another type is invalid",
|
||||
"data": "a",
|
||||
"valid": false,
|
||||
"schema_id": "const_0_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "const with object",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"const": {
|
||||
"foo": "bar",
|
||||
"baz": "bax"
|
||||
},
|
||||
"properties": {
|
||||
"foo": {},
|
||||
"baz": {}
|
||||
},
|
||||
"$id": "const_1_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "same object is valid",
|
||||
"data": {
|
||||
"foo": "bar",
|
||||
"baz": "bax"
|
||||
},
|
||||
"valid": true,
|
||||
"schema_id": "const_1_0"
|
||||
},
|
||||
{
|
||||
"description": "same object with different property order is valid",
|
||||
"data": {
|
||||
"baz": "bax",
|
||||
"foo": "bar"
|
||||
},
|
||||
"valid": true,
|
||||
"schema_id": "const_1_0"
|
||||
},
|
||||
{
|
||||
"description": "another object is invalid",
|
||||
"data": {
|
||||
"foo": "bar"
|
||||
},
|
||||
"valid": false,
|
||||
"schema_id": "const_1_0"
|
||||
},
|
||||
{
|
||||
"description": "another type is invalid",
|
||||
"data": [
|
||||
1,
|
||||
2
|
||||
],
|
||||
"valid": false,
|
||||
"schema_id": "const_1_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "const with array",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"const": [
|
||||
{
|
||||
"foo": "bar"
|
||||
}
|
||||
],
|
||||
"$id": "const_2_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "same array is valid",
|
||||
"data": [
|
||||
{
|
||||
"foo": "bar"
|
||||
}
|
||||
],
|
||||
"valid": true,
|
||||
"schema_id": "const_2_0"
|
||||
},
|
||||
{
|
||||
"description": "another array item is invalid",
|
||||
"data": [
|
||||
2
|
||||
],
|
||||
"valid": false,
|
||||
"schema_id": "const_2_0"
|
||||
},
|
||||
{
|
||||
"description": "array with additional items is invalid",
|
||||
"data": [
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"valid": false,
|
||||
"schema_id": "const_2_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "const with null",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"const": null,
|
||||
"$id": "const_3_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "null is valid",
|
||||
"data": null,
|
||||
"valid": true,
|
||||
"schema_id": "const_3_0"
|
||||
},
|
||||
{
|
||||
"description": "not null is invalid",
|
||||
"data": 0,
|
||||
"valid": false,
|
||||
"schema_id": "const_3_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "const with false does not match 0",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"const": false,
|
||||
"$id": "const_4_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "false is valid",
|
||||
"data": false,
|
||||
"valid": true,
|
||||
"schema_id": "const_4_0"
|
||||
},
|
||||
{
|
||||
"description": "integer zero is invalid",
|
||||
"data": 0,
|
||||
"valid": false,
|
||||
"schema_id": "const_4_0"
|
||||
},
|
||||
{
|
||||
"description": "float zero is invalid",
|
||||
"data": 0,
|
||||
"valid": false,
|
||||
"schema_id": "const_4_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "const with true does not match 1",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"const": true,
|
||||
"$id": "const_5_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "true is valid",
|
||||
"data": true,
|
||||
"valid": true,
|
||||
"schema_id": "const_5_0"
|
||||
},
|
||||
{
|
||||
"description": "integer one is invalid",
|
||||
"data": 1,
|
||||
"valid": false,
|
||||
"schema_id": "const_5_0"
|
||||
},
|
||||
{
|
||||
"description": "float one is invalid",
|
||||
"data": 1,
|
||||
"valid": false,
|
||||
"schema_id": "const_5_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "const with [false] does not match [0]",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"const": [
|
||||
false
|
||||
],
|
||||
"$id": "const_6_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "[false] is valid",
|
||||
"data": [
|
||||
false
|
||||
],
|
||||
"valid": true,
|
||||
"schema_id": "const_6_0"
|
||||
},
|
||||
{
|
||||
"description": "[0] is invalid",
|
||||
"data": [
|
||||
0
|
||||
],
|
||||
"valid": false,
|
||||
"schema_id": "const_6_0"
|
||||
},
|
||||
{
|
||||
"description": "[0.0] is invalid",
|
||||
"data": [
|
||||
0
|
||||
],
|
||||
"valid": false,
|
||||
"schema_id": "const_6_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "const with [true] does not match [1]",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"const": [
|
||||
true
|
||||
],
|
||||
"$id": "const_7_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "[true] is valid",
|
||||
"data": [
|
||||
true
|
||||
],
|
||||
"valid": true,
|
||||
"schema_id": "const_7_0"
|
||||
},
|
||||
{
|
||||
"description": "[1] is invalid",
|
||||
"data": [
|
||||
1
|
||||
],
|
||||
"valid": false,
|
||||
"schema_id": "const_7_0"
|
||||
},
|
||||
{
|
||||
"description": "[1.0] is invalid",
|
||||
"data": [
|
||||
1
|
||||
],
|
||||
"valid": false,
|
||||
"schema_id": "const_7_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "const with {\"a\": false} does not match {\"a\": 0}",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"const": {
|
||||
"a": false
|
||||
},
|
||||
"$id": "const_8_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "{\"a\": false} is valid",
|
||||
"data": {
|
||||
"a": false
|
||||
},
|
||||
"valid": true,
|
||||
"schema_id": "const_8_0"
|
||||
},
|
||||
{
|
||||
"description": "{\"a\": 0} is invalid",
|
||||
"data": {
|
||||
"a": 0
|
||||
},
|
||||
"valid": false,
|
||||
"schema_id": "const_8_0"
|
||||
},
|
||||
{
|
||||
"description": "{\"a\": 0.0} is invalid",
|
||||
"data": {
|
||||
"a": 0
|
||||
},
|
||||
"valid": false,
|
||||
"schema_id": "const_8_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "const with {\"a\": true} does not match {\"a\": 1}",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"const": {
|
||||
"a": true
|
||||
},
|
||||
"$id": "const_9_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "{\"a\": true} is valid",
|
||||
"data": {
|
||||
"a": true
|
||||
},
|
||||
"valid": true,
|
||||
"schema_id": "const_9_0"
|
||||
},
|
||||
{
|
||||
"description": "{\"a\": 1} is invalid",
|
||||
"data": {
|
||||
"a": 1
|
||||
},
|
||||
"valid": false,
|
||||
"schema_id": "const_9_0"
|
||||
},
|
||||
{
|
||||
"description": "{\"a\": 1.0} is invalid",
|
||||
"data": {
|
||||
"a": 1
|
||||
},
|
||||
"valid": false,
|
||||
"schema_id": "const_9_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "const with 0 does not match other zero-like types",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"const": 0,
|
||||
"$id": "const_10_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "false is invalid",
|
||||
"data": false,
|
||||
"valid": false,
|
||||
"schema_id": "const_10_0"
|
||||
},
|
||||
{
|
||||
"description": "integer zero is valid",
|
||||
"data": 0,
|
||||
"valid": true,
|
||||
"schema_id": "const_10_0"
|
||||
},
|
||||
{
|
||||
"description": "float zero is valid",
|
||||
"data": 0,
|
||||
"valid": true,
|
||||
"schema_id": "const_10_0"
|
||||
},
|
||||
{
|
||||
"description": "empty object is invalid",
|
||||
"data": {},
|
||||
"valid": false,
|
||||
"schema_id": "const_10_0"
|
||||
},
|
||||
{
|
||||
"description": "empty array is invalid",
|
||||
"data": [],
|
||||
"valid": false,
|
||||
"schema_id": "const_10_0"
|
||||
},
|
||||
{
|
||||
"description": "empty string is invalid",
|
||||
"data": "",
|
||||
"valid": false,
|
||||
"schema_id": "const_10_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "const with 1 does not match true",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"const": 1,
|
||||
"$id": "const_11_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "true is invalid",
|
||||
"data": true,
|
||||
"valid": false,
|
||||
"schema_id": "const_11_0"
|
||||
},
|
||||
{
|
||||
"description": "integer one is valid",
|
||||
"data": 1,
|
||||
"valid": true,
|
||||
"schema_id": "const_11_0"
|
||||
},
|
||||
{
|
||||
"description": "float one is valid",
|
||||
"data": 1,
|
||||
"valid": true,
|
||||
"schema_id": "const_11_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "const with -2.0 matches integer and float types",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"const": -2,
|
||||
"$id": "const_12_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "integer -2 is valid",
|
||||
"data": -2,
|
||||
"valid": true,
|
||||
"schema_id": "const_12_0"
|
||||
},
|
||||
{
|
||||
"description": "integer 2 is invalid",
|
||||
"data": 2,
|
||||
"valid": false,
|
||||
"schema_id": "const_12_0"
|
||||
},
|
||||
{
|
||||
"description": "float -2.0 is valid",
|
||||
"data": -2,
|
||||
"valid": true,
|
||||
"schema_id": "const_12_0"
|
||||
},
|
||||
{
|
||||
"description": "float 2.0 is invalid",
|
||||
"data": 2,
|
||||
"valid": false,
|
||||
"schema_id": "const_12_0"
|
||||
},
|
||||
{
|
||||
"description": "float -2.00001 is invalid",
|
||||
"data": -2.00001,
|
||||
"valid": false,
|
||||
"schema_id": "const_12_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "float and integers are equal up to 64-bit representation limits",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"const": 9007199254740992,
|
||||
"$id": "const_13_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "integer is valid",
|
||||
"data": 9007199254740992,
|
||||
"valid": true,
|
||||
"schema_id": "const_13_0"
|
||||
},
|
||||
{
|
||||
"description": "integer minus one is invalid",
|
||||
"data": 9007199254740991,
|
||||
"valid": false,
|
||||
"schema_id": "const_13_0"
|
||||
},
|
||||
{
|
||||
"description": "float is valid",
|
||||
"data": 9007199254740992,
|
||||
"valid": true,
|
||||
"schema_id": "const_13_0"
|
||||
},
|
||||
{
|
||||
"description": "float minus one is invalid",
|
||||
"data": 9007199254740991,
|
||||
"valid": false,
|
||||
"schema_id": "const_13_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "nul characters in strings",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"const": "hello\u0000there",
|
||||
"$id": "const_14_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "match string with nul",
|
||||
"data": "hello\u0000there",
|
||||
"valid": true,
|
||||
"schema_id": "const_14_0"
|
||||
},
|
||||
{
|
||||
"description": "do not match string lacking nul",
|
||||
"data": "hellothere",
|
||||
"valid": false,
|
||||
"schema_id": "const_14_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "characters with the same visual representation but different codepoint",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"const": "μ",
|
||||
"$comment": "U+03BC",
|
||||
"$id": "const_15_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "character uses the same codepoint",
|
||||
"data": "μ",
|
||||
"comment": "U+03BC",
|
||||
"valid": true,
|
||||
"schema_id": "const_15_0"
|
||||
},
|
||||
{
|
||||
"description": "character looks the same but uses a different codepoint",
|
||||
"data": "µ",
|
||||
"comment": "U+00B5",
|
||||
"valid": false,
|
||||
"schema_id": "const_15_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "characters with the same visual representation, but different number of codepoints",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"const": "ä",
|
||||
"$comment": "U+00E4",
|
||||
"$id": "const_16_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "character uses the same codepoint",
|
||||
"data": "ä",
|
||||
"comment": "U+00E4",
|
||||
"valid": true,
|
||||
"schema_id": "const_16_0"
|
||||
},
|
||||
{
|
||||
"description": "character looks the same but uses combining marks",
|
||||
"data": "ä",
|
||||
"comment": "a, U+0308",
|
||||
"valid": false,
|
||||
"schema_id": "const_16_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "extensible: true allows extra properties in const object match",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"const": {
|
||||
"a": 1
|
||||
},
|
||||
"extensible": true,
|
||||
"$id": "const_17_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "extra property ignored during strict check, but const check still applies (mismatch)",
|
||||
"data": {
|
||||
"a": 1,
|
||||
"b": 2
|
||||
},
|
||||
"valid": false,
|
||||
"schema_id": "const_17_0"
|
||||
},
|
||||
{
|
||||
"description": "extra property match in const (this is effectively impossible if data has extra props not in const, it implicitly fails const check unless we assume const check ignored extra props? No, const check is strict. So this test is just to show strictness passes.)",
|
||||
"data": {
|
||||
"a": 1
|
||||
},
|
||||
"valid": true,
|
||||
"schema_id": "const_17_0"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
346
tests/fixtures/contains.json
vendored
346
tests/fixtures/contains.json
vendored
@ -1,346 +0,0 @@
|
||||
[
|
||||
{
|
||||
"description": "contains keyword validation",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"contains": {
|
||||
"minimum": 5
|
||||
},
|
||||
"items": true,
|
||||
"$id": "contains_0_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "array with item matching schema (5) is valid (items: true)",
|
||||
"data": [
|
||||
3,
|
||||
4,
|
||||
5
|
||||
],
|
||||
"valid": true,
|
||||
"schema_id": "contains_0_0"
|
||||
},
|
||||
{
|
||||
"description": "array with item matching schema (6) is valid (items: true)",
|
||||
"data": [
|
||||
3,
|
||||
4,
|
||||
6
|
||||
],
|
||||
"valid": true,
|
||||
"schema_id": "contains_0_0"
|
||||
},
|
||||
{
|
||||
"description": "array with two items matching schema (5, 6) is valid (items: true)",
|
||||
"data": [
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6
|
||||
],
|
||||
"valid": true,
|
||||
"schema_id": "contains_0_0"
|
||||
},
|
||||
{
|
||||
"description": "array without items matching schema is invalid",
|
||||
"data": [
|
||||
2,
|
||||
3,
|
||||
4
|
||||
],
|
||||
"valid": false,
|
||||
"schema_id": "contains_0_0"
|
||||
},
|
||||
{
|
||||
"description": "empty array is invalid",
|
||||
"data": [],
|
||||
"valid": false,
|
||||
"schema_id": "contains_0_0"
|
||||
},
|
||||
{
|
||||
"description": "not array is valid",
|
||||
"data": {},
|
||||
"valid": true,
|
||||
"schema_id": "contains_0_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "contains keyword with const keyword",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"contains": {
|
||||
"const": 5
|
||||
},
|
||||
"items": true,
|
||||
"$id": "contains_1_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "array with item 5 is valid (items: true)",
|
||||
"data": [
|
||||
3,
|
||||
4,
|
||||
5
|
||||
],
|
||||
"valid": true,
|
||||
"schema_id": "contains_1_0"
|
||||
},
|
||||
{
|
||||
"description": "array with two items 5 is valid (items: true)",
|
||||
"data": [
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
5
|
||||
],
|
||||
"valid": true,
|
||||
"schema_id": "contains_1_0"
|
||||
},
|
||||
{
|
||||
"description": "array without item 5 is invalid",
|
||||
"data": [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4
|
||||
],
|
||||
"valid": false,
|
||||
"schema_id": "contains_1_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "contains keyword with boolean schema true",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"contains": true,
|
||||
"$id": "contains_2_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "any non-empty array is valid",
|
||||
"data": [
|
||||
"foo"
|
||||
],
|
||||
"valid": true,
|
||||
"schema_id": "contains_2_0"
|
||||
},
|
||||
{
|
||||
"description": "empty array is invalid",
|
||||
"data": [],
|
||||
"valid": false,
|
||||
"schema_id": "contains_2_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "contains keyword with boolean schema false",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"contains": false,
|
||||
"$id": "contains_3_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "any non-empty array is invalid",
|
||||
"data": [
|
||||
"foo"
|
||||
],
|
||||
"valid": false,
|
||||
"schema_id": "contains_3_0"
|
||||
},
|
||||
{
|
||||
"description": "empty array is invalid",
|
||||
"data": [],
|
||||
"valid": false,
|
||||
"schema_id": "contains_3_0"
|
||||
},
|
||||
{
|
||||
"description": "non-arrays are valid",
|
||||
"data": "contains does not apply to strings",
|
||||
"valid": true,
|
||||
"schema_id": "contains_3_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "items + contains",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"items": {
|
||||
"multipleOf": 2
|
||||
},
|
||||
"contains": {
|
||||
"multipleOf": 3
|
||||
},
|
||||
"$id": "contains_4_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "matches items, does not match contains",
|
||||
"data": [
|
||||
2,
|
||||
4,
|
||||
8
|
||||
],
|
||||
"valid": false,
|
||||
"schema_id": "contains_4_0"
|
||||
},
|
||||
{
|
||||
"description": "does not match items, matches contains",
|
||||
"data": [
|
||||
3,
|
||||
6,
|
||||
9
|
||||
],
|
||||
"valid": false,
|
||||
"schema_id": "contains_4_0"
|
||||
},
|
||||
{
|
||||
"description": "matches both items and contains",
|
||||
"data": [
|
||||
6,
|
||||
12
|
||||
],
|
||||
"valid": true,
|
||||
"schema_id": "contains_4_0"
|
||||
},
|
||||
{
|
||||
"description": "matches neither items nor contains",
|
||||
"data": [
|
||||
1,
|
||||
5
|
||||
],
|
||||
"valid": false,
|
||||
"schema_id": "contains_4_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "contains with false if subschema",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"contains": {
|
||||
"if": false,
|
||||
"else": true
|
||||
},
|
||||
"$id": "contains_5_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "any non-empty array is valid",
|
||||
"data": [
|
||||
"foo"
|
||||
],
|
||||
"valid": true,
|
||||
"schema_id": "contains_5_0"
|
||||
},
|
||||
{
|
||||
"description": "empty array is invalid",
|
||||
"data": [],
|
||||
"valid": false,
|
||||
"schema_id": "contains_5_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "contains with null instance elements",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"contains": {
|
||||
"type": "null"
|
||||
},
|
||||
"$id": "contains_6_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "allows null items",
|
||||
"data": [
|
||||
null
|
||||
],
|
||||
"valid": true,
|
||||
"schema_id": "contains_6_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "extensible: true allows non-matching items in contains",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"contains": {
|
||||
"const": 1
|
||||
},
|
||||
"extensible": true,
|
||||
"$id": "contains_7_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "extra items acceptable",
|
||||
"data": [
|
||||
1,
|
||||
2
|
||||
],
|
||||
"valid": true,
|
||||
"schema_id": "contains_7_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "strict by default: non-matching items in contains are invalid",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"contains": {
|
||||
"const": 1
|
||||
},
|
||||
"$id": "contains_8_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "extra items cause failure",
|
||||
"data": [
|
||||
1,
|
||||
2
|
||||
],
|
||||
"valid": false,
|
||||
"schema_id": "contains_8_0"
|
||||
},
|
||||
{
|
||||
"description": "only matching items is valid",
|
||||
"data": [
|
||||
1,
|
||||
1
|
||||
],
|
||||
"valid": true,
|
||||
"schema_id": "contains_8_0"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
178
tests/fixtures/content.json
vendored
178
tests/fixtures/content.json
vendored
@ -1,178 +0,0 @@
|
||||
[
|
||||
{
|
||||
"description": "validation of string-encoded content based on media type",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"contentMediaType": "application/json",
|
||||
"$id": "content_0_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "a valid JSON document",
|
||||
"data": "{\"foo\": \"bar\"}",
|
||||
"valid": true,
|
||||
"schema_id": "content_0_0"
|
||||
},
|
||||
{
|
||||
"description": "an invalid JSON document; validates true",
|
||||
"data": "{:}",
|
||||
"valid": true,
|
||||
"schema_id": "content_0_0"
|
||||
},
|
||||
{
|
||||
"description": "ignores non-strings",
|
||||
"data": 100,
|
||||
"valid": true,
|
||||
"schema_id": "content_0_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "validation of binary string-encoding",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"contentEncoding": "base64",
|
||||
"$id": "content_1_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "a valid base64 string",
|
||||
"data": "eyJmb28iOiAiYmFyIn0K",
|
||||
"valid": true,
|
||||
"schema_id": "content_1_0"
|
||||
},
|
||||
{
|
||||
"description": "an invalid base64 string (% is not a valid character); validates true",
|
||||
"data": "eyJmb28iOi%iYmFyIn0K",
|
||||
"valid": true,
|
||||
"schema_id": "content_1_0"
|
||||
},
|
||||
{
|
||||
"description": "ignores non-strings",
|
||||
"data": 100,
|
||||
"valid": true,
|
||||
"schema_id": "content_1_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "validation of binary-encoded media type documents",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"contentMediaType": "application/json",
|
||||
"contentEncoding": "base64",
|
||||
"$id": "content_2_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "a valid base64-encoded JSON document",
|
||||
"data": "eyJmb28iOiAiYmFyIn0K",
|
||||
"valid": true,
|
||||
"schema_id": "content_2_0"
|
||||
},
|
||||
{
|
||||
"description": "a validly-encoded invalid JSON document; validates true",
|
||||
"data": "ezp9Cg==",
|
||||
"valid": true,
|
||||
"schema_id": "content_2_0"
|
||||
},
|
||||
{
|
||||
"description": "an invalid base64 string that is valid JSON; validates true",
|
||||
"data": "{}",
|
||||
"valid": true,
|
||||
"schema_id": "content_2_0"
|
||||
},
|
||||
{
|
||||
"description": "ignores non-strings",
|
||||
"data": 100,
|
||||
"valid": true,
|
||||
"schema_id": "content_2_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "validation of binary-encoded media type documents with schema",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"contentMediaType": "application/json",
|
||||
"contentEncoding": "base64",
|
||||
"contentSchema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"foo"
|
||||
],
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "string"
|
||||
},
|
||||
"boo": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"$id": "content_3_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "a valid base64-encoded JSON document",
|
||||
"data": "eyJmb28iOiAiYmFyIn0K",
|
||||
"valid": true,
|
||||
"schema_id": "content_3_0"
|
||||
},
|
||||
{
|
||||
"description": "another valid base64-encoded JSON document",
|
||||
"data": "eyJib28iOiAyMCwgImZvbyI6ICJiYXoifQ==",
|
||||
"valid": true,
|
||||
"schema_id": "content_3_0"
|
||||
},
|
||||
{
|
||||
"description": "an invalid base64-encoded JSON document; validates true",
|
||||
"data": "eyJib28iOiAyMH0=",
|
||||
"valid": true,
|
||||
"schema_id": "content_3_0"
|
||||
},
|
||||
{
|
||||
"description": "an empty object as a base64-encoded JSON document; validates true",
|
||||
"data": "e30=",
|
||||
"valid": true,
|
||||
"schema_id": "content_3_0"
|
||||
},
|
||||
{
|
||||
"description": "an empty array as a base64-encoded JSON document",
|
||||
"data": "W10=",
|
||||
"valid": true,
|
||||
"schema_id": "content_3_0"
|
||||
},
|
||||
{
|
||||
"description": "a validly-encoded invalid JSON document; validates true",
|
||||
"data": "ezp9Cg==",
|
||||
"valid": true,
|
||||
"schema_id": "content_3_0"
|
||||
},
|
||||
{
|
||||
"description": "an invalid base64 string that is valid JSON; validates true",
|
||||
"data": "{}",
|
||||
"valid": true,
|
||||
"schema_id": "content_3_0"
|
||||
},
|
||||
{
|
||||
"description": "ignores non-strings",
|
||||
"data": 100,
|
||||
"valid": true,
|
||||
"schema_id": "content_3_0"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
619
tests/fixtures/dependencies.json
vendored
619
tests/fixtures/dependencies.json
vendored
@ -1,619 +0,0 @@
|
||||
[
|
||||
{
|
||||
"description": "single dependency (required)",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "schema1",
|
||||
"dependencies": {
|
||||
"bar": [
|
||||
"foo"
|
||||
]
|
||||
},
|
||||
"extensible": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "neither",
|
||||
"data": {},
|
||||
"valid": true,
|
||||
"schema_id": "schema1"
|
||||
},
|
||||
{
|
||||
"description": "nondependant",
|
||||
"data": {
|
||||
"foo": 1
|
||||
},
|
||||
"valid": true,
|
||||
"schema_id": "schema1"
|
||||
},
|
||||
{
|
||||
"description": "with dependency",
|
||||
"data": {
|
||||
"foo": 1,
|
||||
"bar": 2
|
||||
},
|
||||
"valid": true,
|
||||
"schema_id": "schema1"
|
||||
},
|
||||
{
|
||||
"description": "missing dependency",
|
||||
"data": {
|
||||
"bar": 2
|
||||
},
|
||||
"valid": false,
|
||||
"schema_id": "schema1"
|
||||
},
|
||||
{
|
||||
"description": "ignores arrays",
|
||||
"data": [
|
||||
"bar"
|
||||
],
|
||||
"valid": true,
|
||||
"schema_id": "schema1"
|
||||
},
|
||||
{
|
||||
"description": "ignores strings",
|
||||
"data": "foobar",
|
||||
"valid": true,
|
||||
"schema_id": "schema1"
|
||||
},
|
||||
{
|
||||
"description": "ignores other non-objects",
|
||||
"data": 12,
|
||||
"valid": true,
|
||||
"schema_id": "schema1"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "empty dependents",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "schema2",
|
||||
"dependencies": {
|
||||
"bar": []
|
||||
},
|
||||
"extensible": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "empty object",
|
||||
"data": {},
|
||||
"valid": true,
|
||||
"schema_id": "schema2"
|
||||
},
|
||||
{
|
||||
"description": "object with one property",
|
||||
"data": {
|
||||
"bar": 2
|
||||
},
|
||||
"valid": true,
|
||||
"schema_id": "schema2"
|
||||
},
|
||||
{
|
||||
"description": "non-object is valid",
|
||||
"data": 1,
|
||||
"valid": true,
|
||||
"schema_id": "schema2"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "multiple dependents required",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "schema3",
|
||||
"dependencies": {
|
||||
"quux": [
|
||||
"foo",
|
||||
"bar"
|
||||
]
|
||||
},
|
||||
"extensible": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "neither",
|
||||
"data": {},
|
||||
"valid": true,
|
||||
"schema_id": "schema3"
|
||||
},
|
||||
{
|
||||
"description": "nondependants",
|
||||
"data": {
|
||||
"foo": 1,
|
||||
"bar": 2
|
||||
},
|
||||
"valid": true,
|
||||
"schema_id": "schema3"
|
||||
},
|
||||
{
|
||||
"description": "with dependencies",
|
||||
"data": {
|
||||
"foo": 1,
|
||||
"bar": 2,
|
||||
"quux": 3
|
||||
},
|
||||
"valid": true,
|
||||
"schema_id": "schema3"
|
||||
},
|
||||
{
|
||||
"description": "missing dependency",
|
||||
"data": {
|
||||
"foo": 1,
|
||||
"quux": 2
|
||||
},
|
||||
"valid": false,
|
||||
"schema_id": "schema3"
|
||||
},
|
||||
{
|
||||
"description": "missing other dependency",
|
||||
"data": {
|
||||
"bar": 1,
|
||||
"quux": 2
|
||||
},
|
||||
"valid": false,
|
||||
"schema_id": "schema3"
|
||||
},
|
||||
{
|
||||
"description": "missing both dependencies",
|
||||
"data": {
|
||||
"quux": 1
|
||||
},
|
||||
"valid": false,
|
||||
"schema_id": "schema3"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "dependencies with escaped characters",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "schema4",
|
||||
"dependencies": {
|
||||
"foo\nbar": [
|
||||
"foo\rbar"
|
||||
],
|
||||
"foo\"bar": [
|
||||
"foo'bar"
|
||||
]
|
||||
},
|
||||
"extensible": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "CRLF",
|
||||
"data": {
|
||||
"foo\nbar": 1,
|
||||
"foo\rbar": 2
|
||||
},
|
||||
"valid": true,
|
||||
"schema_id": "schema4"
|
||||
},
|
||||
{
|
||||
"description": "quoted quotes",
|
||||
"data": {
|
||||
"foo'bar": 1,
|
||||
"foo\"bar": 2
|
||||
},
|
||||
"valid": true,
|
||||
"schema_id": "schema4"
|
||||
},
|
||||
{
|
||||
"description": "CRLF missing dependent",
|
||||
"data": {
|
||||
"foo\nbar": 1,
|
||||
"foo": 2
|
||||
},
|
||||
"valid": false,
|
||||
"schema_id": "schema4"
|
||||
},
|
||||
{
|
||||
"description": "quoted quotes missing dependent",
|
||||
"data": {
|
||||
"foo\"bar": 2
|
||||
},
|
||||
"valid": false,
|
||||
"schema_id": "schema4"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "extensible: true allows extra properties in dependentRequired",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "schema5",
|
||||
"dependencies": {
|
||||
"bar": [
|
||||
"foo"
|
||||
]
|
||||
},
|
||||
"extensible": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "extra property is valid",
|
||||
"data": {
|
||||
"foo": 1,
|
||||
"bar": 2,
|
||||
"baz": 3
|
||||
},
|
||||
"valid": true,
|
||||
"schema_id": "schema5"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "single dependency (schemas, STRICT)",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "schema_schema1",
|
||||
"properties": {
|
||||
"foo": true,
|
||||
"bar": true
|
||||
},
|
||||
"dependencies": {
|
||||
"bar": {
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "integer"
|
||||
},
|
||||
"bar": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "valid",
|
||||
"data": {
|
||||
"foo": 1,
|
||||
"bar": 2
|
||||
},
|
||||
"valid": true,
|
||||
"schema_id": "schema_schema1"
|
||||
},
|
||||
{
|
||||
"description": "no dependency",
|
||||
"data": {
|
||||
"foo": "quux"
|
||||
},
|
||||
"valid": true,
|
||||
"schema_id": "schema_schema1"
|
||||
},
|
||||
{
|
||||
"description": "wrong type",
|
||||
"data": {
|
||||
"foo": "quux",
|
||||
"bar": 2
|
||||
},
|
||||
"valid": false,
|
||||
"schema_id": "schema_schema1"
|
||||
},
|
||||
{
|
||||
"description": "wrong type other",
|
||||
"data": {
|
||||
"foo": 2,
|
||||
"bar": "quux"
|
||||
},
|
||||
"valid": false,
|
||||
"schema_id": "schema_schema1"
|
||||
},
|
||||
{
|
||||
"description": "wrong type both",
|
||||
"data": {
|
||||
"foo": "quux",
|
||||
"bar": "quux"
|
||||
},
|
||||
"valid": false,
|
||||
"schema_id": "schema_schema1"
|
||||
},
|
||||
{
|
||||
"description": "ignores arrays (invalid in strict mode)",
|
||||
"data": [
|
||||
"bar"
|
||||
],
|
||||
"valid": false,
|
||||
"expect_errors": [
|
||||
{
|
||||
"code": "STRICT_ITEM_VIOLATION"
|
||||
}
|
||||
],
|
||||
"schema_id": "schema_schema1"
|
||||
},
|
||||
{
|
||||
"description": "ignores strings",
|
||||
"data": "foobar",
|
||||
"valid": true,
|
||||
"schema_id": "schema_schema1"
|
||||
},
|
||||
{
|
||||
"description": "ignores other non-objects",
|
||||
"data": 12,
|
||||
"valid": true,
|
||||
"schema_id": "schema_schema1"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "single dependency (schemas, EXTENSIBLE)",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "schema_schema2",
|
||||
"properties": {
|
||||
"foo": true,
|
||||
"bar": true
|
||||
},
|
||||
"dependencies": {
|
||||
"bar": {
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "integer"
|
||||
},
|
||||
"bar": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"extensible": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "ignores arrays (valid in extensible mode)",
|
||||
"data": [
|
||||
"bar"
|
||||
],
|
||||
"valid": true,
|
||||
"schema_id": "schema_schema2"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "boolean subschemas",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "schema_schema3",
|
||||
"properties": {
|
||||
"foo": true,
|
||||
"bar": true
|
||||
},
|
||||
"dependencies": {
|
||||
"foo": true,
|
||||
"bar": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "object with property having schema true is valid",
|
||||
"data": {
|
||||
"foo": 1
|
||||
},
|
||||
"valid": true,
|
||||
"schema_id": "schema_schema3"
|
||||
},
|
||||
{
|
||||
"description": "object with property having schema false is invalid",
|
||||
"data": {
|
||||
"bar": 2
|
||||
},
|
||||
"valid": false,
|
||||
"schema_id": "schema_schema3"
|
||||
},
|
||||
{
|
||||
"description": "object with both properties is invalid",
|
||||
"data": {
|
||||
"foo": 1,
|
||||
"bar": 2
|
||||
},
|
||||
"valid": false,
|
||||
"schema_id": "schema_schema3"
|
||||
},
|
||||
{
|
||||
"description": "empty object is valid",
|
||||
"data": {},
|
||||
"valid": true,
|
||||
"schema_id": "schema_schema3"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "dependencies with escaped characters",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "schema_schema4",
|
||||
"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": [
|
||||
{
|
||||
"description": "quoted tab",
|
||||
"data": {
|
||||
"foo\tbar": 1,
|
||||
"a": 2,
|
||||
"b": 3,
|
||||
"c": 4
|
||||
},
|
||||
"valid": true,
|
||||
"schema_id": "schema_schema4"
|
||||
},
|
||||
{
|
||||
"description": "quoted quote",
|
||||
"data": {
|
||||
"foo'bar": {
|
||||
"foo\"bar": 1
|
||||
}
|
||||
},
|
||||
"valid": false,
|
||||
"schema_id": "schema_schema4"
|
||||
},
|
||||
{
|
||||
"description": "quoted tab invalid under dependent schema",
|
||||
"data": {
|
||||
"foo\tbar": 1,
|
||||
"a": 2
|
||||
},
|
||||
"valid": false,
|
||||
"schema_id": "schema_schema4"
|
||||
},
|
||||
{
|
||||
"description": "quoted quote invalid under dependent schema",
|
||||
"data": {
|
||||
"foo'bar": 1
|
||||
},
|
||||
"valid": false,
|
||||
"schema_id": "schema_schema4"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "dependent subschema incompatible with root (STRICT)",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "schema_schema5",
|
||||
"properties": {
|
||||
"foo": {},
|
||||
"baz": true
|
||||
},
|
||||
"dependencies": {
|
||||
"foo": {
|
||||
"properties": {
|
||||
"bar": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "matches root",
|
||||
"data": {
|
||||
"foo": 1
|
||||
},
|
||||
"valid": false,
|
||||
"schema_id": "schema_schema5"
|
||||
},
|
||||
{
|
||||
"description": "matches dependency (invalid in strict mode - bar not allowed if foo missing)",
|
||||
"data": {
|
||||
"bar": 1
|
||||
},
|
||||
"valid": false,
|
||||
"expect_errors": [
|
||||
{
|
||||
"code": "STRICT_PROPERTY_VIOLATION"
|
||||
}
|
||||
],
|
||||
"schema_id": "schema_schema5"
|
||||
},
|
||||
{
|
||||
"description": "matches both",
|
||||
"data": {
|
||||
"foo": 1,
|
||||
"bar": 2
|
||||
},
|
||||
"valid": false,
|
||||
"schema_id": "schema_schema5"
|
||||
},
|
||||
{
|
||||
"description": "no dependency",
|
||||
"data": {
|
||||
"baz": 1
|
||||
},
|
||||
"valid": true,
|
||||
"schema_id": "schema_schema5"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "dependent subschema incompatible with root (EXTENSIBLE)",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "schema_schema6",
|
||||
"properties": {
|
||||
"foo": {},
|
||||
"baz": true
|
||||
},
|
||||
"dependencies": {
|
||||
"foo": {
|
||||
"properties": {
|
||||
"bar": {}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"extensible": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "matches dependency (valid in extensible mode)",
|
||||
"data": {
|
||||
"bar": 1
|
||||
},
|
||||
"valid": true,
|
||||
"schema_id": "schema_schema6"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
133
tests/fixtures/emptyString.json
vendored
133
tests/fixtures/emptyString.json
vendored
@ -1,133 +0,0 @@
|
||||
[
|
||||
{
|
||||
"description": "empty string is valid for all types (except const)",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"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": ""
|
||||
}
|
||||
},
|
||||
"$id": "emptyString_0_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "empty string valid for object",
|
||||
"data": {
|
||||
"obj": ""
|
||||
},
|
||||
"valid": true,
|
||||
"schema_id": "emptyString_0_0"
|
||||
},
|
||||
{
|
||||
"description": "empty string valid for array",
|
||||
"data": {
|
||||
"arr": ""
|
||||
},
|
||||
"valid": true,
|
||||
"schema_id": "emptyString_0_0"
|
||||
},
|
||||
{
|
||||
"description": "empty string valid for string",
|
||||
"data": {
|
||||
"str": ""
|
||||
},
|
||||
"valid": true,
|
||||
"schema_id": "emptyString_0_0"
|
||||
},
|
||||
{
|
||||
"description": "empty string valid for integer",
|
||||
"data": {
|
||||
"int": ""
|
||||
},
|
||||
"valid": true,
|
||||
"schema_id": "emptyString_0_0"
|
||||
},
|
||||
{
|
||||
"description": "empty string valid for number",
|
||||
"data": {
|
||||
"num": ""
|
||||
},
|
||||
"valid": true,
|
||||
"schema_id": "emptyString_0_0"
|
||||
},
|
||||
{
|
||||
"description": "empty string valid for boolean",
|
||||
"data": {
|
||||
"bool": ""
|
||||
},
|
||||
"valid": true,
|
||||
"schema_id": "emptyString_0_0"
|
||||
},
|
||||
{
|
||||
"description": "empty string valid for null",
|
||||
"data": {
|
||||
"nul": ""
|
||||
},
|
||||
"valid": true,
|
||||
"schema_id": "emptyString_0_0"
|
||||
},
|
||||
{
|
||||
"description": "empty string valid for format",
|
||||
"data": {
|
||||
"fmt": ""
|
||||
},
|
||||
"valid": true,
|
||||
"schema_id": "emptyString_0_0"
|
||||
},
|
||||
{
|
||||
"description": "empty string INVALID for const (unless const is empty string)",
|
||||
"data": {
|
||||
"con": ""
|
||||
},
|
||||
"valid": false,
|
||||
"expect_errors": [
|
||||
{
|
||||
"code": "CONST_VIOLATED",
|
||||
"path": "/con"
|
||||
}
|
||||
],
|
||||
"schema_id": "emptyString_0_0"
|
||||
},
|
||||
{
|
||||
"description": "empty string VALID for const if const IS empty string",
|
||||
"data": {
|
||||
"con_empty": ""
|
||||
},
|
||||
"valid": true,
|
||||
"schema_id": "emptyString_0_0"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
595
tests/fixtures/enum.json
vendored
595
tests/fixtures/enum.json
vendored
@ -1,595 +0,0 @@
|
||||
[
|
||||
{
|
||||
"description": "simple enum validation",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"enum": [
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"$id": "enum_0_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "one of the enum is valid",
|
||||
"data": 1,
|
||||
"valid": true,
|
||||
"schema_id": "enum_0_0"
|
||||
},
|
||||
{
|
||||
"description": "something else is invalid",
|
||||
"data": 4,
|
||||
"valid": false,
|
||||
"schema_id": "enum_0_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "heterogeneous enum validation",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"enum": [
|
||||
6,
|
||||
"foo",
|
||||
[],
|
||||
true,
|
||||
{
|
||||
"foo": 12
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"foo": {}
|
||||
},
|
||||
"$id": "enum_1_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "one of the enum is valid",
|
||||
"data": [],
|
||||
"valid": true,
|
||||
"schema_id": "enum_1_0"
|
||||
},
|
||||
{
|
||||
"description": "something else is invalid",
|
||||
"data": null,
|
||||
"valid": false,
|
||||
"schema_id": "enum_1_0"
|
||||
},
|
||||
{
|
||||
"description": "objects are deep compared",
|
||||
"data": {
|
||||
"foo": false
|
||||
},
|
||||
"valid": false,
|
||||
"schema_id": "enum_1_0"
|
||||
},
|
||||
{
|
||||
"description": "valid object matches",
|
||||
"data": {
|
||||
"foo": 12
|
||||
},
|
||||
"valid": true,
|
||||
"schema_id": "enum_1_0"
|
||||
},
|
||||
{
|
||||
"description": "extra properties in object is invalid",
|
||||
"data": {
|
||||
"foo": 12,
|
||||
"boo": 42
|
||||
},
|
||||
"valid": false,
|
||||
"schema_id": "enum_1_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "heterogeneous enum-with-null validation",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"enum": [
|
||||
6,
|
||||
null
|
||||
],
|
||||
"$id": "enum_2_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "null is valid",
|
||||
"data": null,
|
||||
"valid": true,
|
||||
"schema_id": "enum_2_0"
|
||||
},
|
||||
{
|
||||
"description": "number is valid",
|
||||
"data": 6,
|
||||
"valid": true,
|
||||
"schema_id": "enum_2_0"
|
||||
},
|
||||
{
|
||||
"description": "something else is invalid",
|
||||
"data": "test",
|
||||
"valid": false,
|
||||
"schema_id": "enum_2_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "enums in properties",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"foo": {
|
||||
"enum": [
|
||||
"foo"
|
||||
]
|
||||
},
|
||||
"bar": {
|
||||
"enum": [
|
||||
"bar"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"bar"
|
||||
],
|
||||
"$id": "enum_3_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "both properties are valid",
|
||||
"data": {
|
||||
"foo": "foo",
|
||||
"bar": "bar"
|
||||
},
|
||||
"valid": true,
|
||||
"schema_id": "enum_3_0"
|
||||
},
|
||||
{
|
||||
"description": "wrong foo value",
|
||||
"data": {
|
||||
"foo": "foot",
|
||||
"bar": "bar"
|
||||
},
|
||||
"valid": false,
|
||||
"schema_id": "enum_3_0"
|
||||
},
|
||||
{
|
||||
"description": "wrong bar value",
|
||||
"data": {
|
||||
"foo": "foo",
|
||||
"bar": "bart"
|
||||
},
|
||||
"valid": false,
|
||||
"schema_id": "enum_3_0"
|
||||
},
|
||||
{
|
||||
"description": "missing optional property is valid",
|
||||
"data": {
|
||||
"bar": "bar"
|
||||
},
|
||||
"valid": true,
|
||||
"schema_id": "enum_3_0"
|
||||
},
|
||||
{
|
||||
"description": "missing required property is invalid",
|
||||
"data": {
|
||||
"foo": "foo"
|
||||
},
|
||||
"valid": false,
|
||||
"schema_id": "enum_3_0"
|
||||
},
|
||||
{
|
||||
"description": "missing all properties is invalid",
|
||||
"data": {},
|
||||
"valid": false,
|
||||
"schema_id": "enum_3_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "enum with escaped characters",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"enum": [
|
||||
"foo\nbar",
|
||||
"foo\rbar"
|
||||
],
|
||||
"$id": "enum_4_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "member 1 is valid",
|
||||
"data": "foo\nbar",
|
||||
"valid": true,
|
||||
"schema_id": "enum_4_0"
|
||||
},
|
||||
{
|
||||
"description": "member 2 is valid",
|
||||
"data": "foo\rbar",
|
||||
"valid": true,
|
||||
"schema_id": "enum_4_0"
|
||||
},
|
||||
{
|
||||
"description": "another string is invalid",
|
||||
"data": "abc",
|
||||
"valid": false,
|
||||
"schema_id": "enum_4_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "enum with false does not match 0",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"enum": [
|
||||
false
|
||||
],
|
||||
"$id": "enum_5_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "false is valid",
|
||||
"data": false,
|
||||
"valid": true,
|
||||
"schema_id": "enum_5_0"
|
||||
},
|
||||
{
|
||||
"description": "integer zero is invalid",
|
||||
"data": 0,
|
||||
"valid": false,
|
||||
"schema_id": "enum_5_0"
|
||||
},
|
||||
{
|
||||
"description": "float zero is invalid",
|
||||
"data": 0,
|
||||
"valid": false,
|
||||
"schema_id": "enum_5_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "enum with [false] does not match [0]",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"enum": [
|
||||
[
|
||||
false
|
||||
]
|
||||
],
|
||||
"$id": "enum_6_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "[false] is valid",
|
||||
"data": [
|
||||
false
|
||||
],
|
||||
"valid": true,
|
||||
"schema_id": "enum_6_0"
|
||||
},
|
||||
{
|
||||
"description": "[0] is invalid",
|
||||
"data": [
|
||||
0
|
||||
],
|
||||
"valid": false,
|
||||
"schema_id": "enum_6_0"
|
||||
},
|
||||
{
|
||||
"description": "[0.0] is invalid",
|
||||
"data": [
|
||||
0
|
||||
],
|
||||
"valid": false,
|
||||
"schema_id": "enum_6_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "enum with true does not match 1",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"enum": [
|
||||
true
|
||||
],
|
||||
"$id": "enum_7_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "true is valid",
|
||||
"data": true,
|
||||
"valid": true,
|
||||
"schema_id": "enum_7_0"
|
||||
},
|
||||
{
|
||||
"description": "integer one is invalid",
|
||||
"data": 1,
|
||||
"valid": false,
|
||||
"schema_id": "enum_7_0"
|
||||
},
|
||||
{
|
||||
"description": "float one is invalid",
|
||||
"data": 1,
|
||||
"valid": false,
|
||||
"schema_id": "enum_7_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "enum with [true] does not match [1]",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"enum": [
|
||||
[
|
||||
true
|
||||
]
|
||||
],
|
||||
"$id": "enum_8_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "[true] is valid",
|
||||
"data": [
|
||||
true
|
||||
],
|
||||
"valid": true,
|
||||
"schema_id": "enum_8_0"
|
||||
},
|
||||
{
|
||||
"description": "[1] is invalid",
|
||||
"data": [
|
||||
1
|
||||
],
|
||||
"valid": false,
|
||||
"schema_id": "enum_8_0"
|
||||
},
|
||||
{
|
||||
"description": "[1.0] is invalid",
|
||||
"data": [
|
||||
1
|
||||
],
|
||||
"valid": false,
|
||||
"schema_id": "enum_8_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "enum with 0 does not match false",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"enum": [
|
||||
0
|
||||
],
|
||||
"$id": "enum_9_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "false is invalid",
|
||||
"data": false,
|
||||
"valid": false,
|
||||
"schema_id": "enum_9_0"
|
||||
},
|
||||
{
|
||||
"description": "integer zero is valid",
|
||||
"data": 0,
|
||||
"valid": true,
|
||||
"schema_id": "enum_9_0"
|
||||
},
|
||||
{
|
||||
"description": "float zero is valid",
|
||||
"data": 0,
|
||||
"valid": true,
|
||||
"schema_id": "enum_9_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "enum with [0] does not match [false]",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"enum": [
|
||||
[
|
||||
0
|
||||
]
|
||||
],
|
||||
"$id": "enum_10_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "[false] is invalid",
|
||||
"data": [
|
||||
false
|
||||
],
|
||||
"valid": false,
|
||||
"schema_id": "enum_10_0"
|
||||
},
|
||||
{
|
||||
"description": "[0] is valid",
|
||||
"data": [
|
||||
0
|
||||
],
|
||||
"valid": true,
|
||||
"schema_id": "enum_10_0"
|
||||
},
|
||||
{
|
||||
"description": "[0.0] is valid",
|
||||
"data": [
|
||||
0
|
||||
],
|
||||
"valid": true,
|
||||
"schema_id": "enum_10_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "enum with 1 does not match true",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"enum": [
|
||||
1
|
||||
],
|
||||
"$id": "enum_11_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "true is invalid",
|
||||
"data": true,
|
||||
"valid": false,
|
||||
"schema_id": "enum_11_0"
|
||||
},
|
||||
{
|
||||
"description": "integer one is valid",
|
||||
"data": 1,
|
||||
"valid": true,
|
||||
"schema_id": "enum_11_0"
|
||||
},
|
||||
{
|
||||
"description": "float one is valid",
|
||||
"data": 1,
|
||||
"valid": true,
|
||||
"schema_id": "enum_11_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "enum with [1] does not match [true]",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"enum": [
|
||||
[
|
||||
1
|
||||
]
|
||||
],
|
||||
"$id": "enum_12_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "[true] is invalid",
|
||||
"data": [
|
||||
true
|
||||
],
|
||||
"valid": false,
|
||||
"schema_id": "enum_12_0"
|
||||
},
|
||||
{
|
||||
"description": "[1] is valid",
|
||||
"data": [
|
||||
1
|
||||
],
|
||||
"valid": true,
|
||||
"schema_id": "enum_12_0"
|
||||
},
|
||||
{
|
||||
"description": "[1.0] is valid",
|
||||
"data": [
|
||||
1
|
||||
],
|
||||
"valid": true,
|
||||
"schema_id": "enum_12_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "nul characters in strings",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"enum": [
|
||||
"hello\u0000there"
|
||||
],
|
||||
"$id": "enum_13_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "match string with nul",
|
||||
"data": "hello\u0000there",
|
||||
"valid": true,
|
||||
"schema_id": "enum_13_0"
|
||||
},
|
||||
{
|
||||
"description": "do not match string lacking nul",
|
||||
"data": "hellothere",
|
||||
"valid": false,
|
||||
"schema_id": "enum_13_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "extensible: true allows extra properties in enum object match",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"enum": [
|
||||
{
|
||||
"foo": 1
|
||||
}
|
||||
],
|
||||
"extensible": true,
|
||||
"$id": "enum_14_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "extra property ignored during strict check, but enum check still applies (mismatch here)",
|
||||
"data": {
|
||||
"foo": 1,
|
||||
"bar": 2
|
||||
},
|
||||
"valid": false,
|
||||
"schema_id": "enum_14_0"
|
||||
},
|
||||
{
|
||||
"description": "extra property ignored during strict check, enum match succeeds",
|
||||
"data": {
|
||||
"foo": 1
|
||||
},
|
||||
"valid": true,
|
||||
"schema_id": "enum_14_0"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
39
tests/fixtures/exclusiveMaximum.json
vendored
39
tests/fixtures/exclusiveMaximum.json
vendored
@ -1,39 +0,0 @@
|
||||
[
|
||||
{
|
||||
"description": "exclusiveMaximum validation",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"exclusiveMaximum": 3,
|
||||
"$id": "exclusiveMaximum_0_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "below the exclusiveMaximum is valid",
|
||||
"data": 2.2,
|
||||
"valid": true,
|
||||
"schema_id": "exclusiveMaximum_0_0"
|
||||
},
|
||||
{
|
||||
"description": "boundary point is invalid",
|
||||
"data": 3,
|
||||
"valid": false,
|
||||
"schema_id": "exclusiveMaximum_0_0"
|
||||
},
|
||||
{
|
||||
"description": "above the exclusiveMaximum is invalid",
|
||||
"data": 3.5,
|
||||
"valid": false,
|
||||
"schema_id": "exclusiveMaximum_0_0"
|
||||
},
|
||||
{
|
||||
"description": "ignores non-numbers",
|
||||
"data": "x",
|
||||
"valid": true,
|
||||
"schema_id": "exclusiveMaximum_0_0"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
39
tests/fixtures/exclusiveMinimum.json
vendored
39
tests/fixtures/exclusiveMinimum.json
vendored
@ -1,39 +0,0 @@
|
||||
[
|
||||
{
|
||||
"description": "exclusiveMinimum validation",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"exclusiveMinimum": 1.1,
|
||||
"$id": "exclusiveMinimum_0_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "above the exclusiveMinimum is valid",
|
||||
"data": 1.2,
|
||||
"valid": true,
|
||||
"schema_id": "exclusiveMinimum_0_0"
|
||||
},
|
||||
{
|
||||
"description": "boundary point is invalid",
|
||||
"data": 1.1,
|
||||
"valid": false,
|
||||
"schema_id": "exclusiveMinimum_0_0"
|
||||
},
|
||||
{
|
||||
"description": "below the exclusiveMinimum is invalid",
|
||||
"data": 0.6,
|
||||
"valid": false,
|
||||
"schema_id": "exclusiveMinimum_0_0"
|
||||
},
|
||||
{
|
||||
"description": "ignores non-numbers",
|
||||
"data": "x",
|
||||
"valid": true,
|
||||
"schema_id": "exclusiveMinimum_0_0"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
199
tests/fixtures/families.json
vendored
199
tests/fixtures/families.json
vendored
@ -1,199 +0,0 @@
|
||||
[
|
||||
{
|
||||
"description": "Entity families via pure $ref graph",
|
||||
"database": {
|
||||
"types": [
|
||||
{
|
||||
"name": "entity",
|
||||
"variations": [
|
||||
"entity",
|
||||
"organization",
|
||||
"person"
|
||||
],
|
||||
"schemas": [
|
||||
{
|
||||
"$id": "entity",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"$id": "light.entity",
|
||||
"$ref": "entity"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "organization",
|
||||
"variations": [
|
||||
"organization",
|
||||
"person"
|
||||
],
|
||||
"schemas": [
|
||||
{
|
||||
"$id": "organization",
|
||||
"$ref": "entity",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "person",
|
||||
"variations": [
|
||||
"person"
|
||||
],
|
||||
"schemas": [
|
||||
{
|
||||
"$id": "person",
|
||||
"$ref": "organization",
|
||||
"properties": {
|
||||
"first_name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"$id": "light.person",
|
||||
"$ref": "light.entity"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"puncs": [
|
||||
{
|
||||
"name": "get_entities",
|
||||
"schemas": [
|
||||
{
|
||||
"$id": "get_entities.response",
|
||||
"$family": "entity"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "get_light_entities",
|
||||
"schemas": [
|
||||
{
|
||||
"$id": "get_light_entities.response",
|
||||
"$family": "light.entity"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "Family matches base entity",
|
||||
"schema_id": "get_entities.response",
|
||||
"data": {
|
||||
"id": "1",
|
||||
"type": "entity"
|
||||
},
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"description": "Family matches descendant person",
|
||||
"schema_id": "get_entities.response",
|
||||
"data": {
|
||||
"id": "2",
|
||||
"type": "person",
|
||||
"name": "ACME",
|
||||
"first_name": "John"
|
||||
},
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"description": "Graph family matches light.entity",
|
||||
"schema_id": "get_light_entities.response",
|
||||
"data": {
|
||||
"id": "3",
|
||||
"type": "entity"
|
||||
},
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"description": "Graph family matches light.person (because it $refs light.entity)",
|
||||
"schema_id": "get_light_entities.response",
|
||||
"data": {
|
||||
"id": "4",
|
||||
"type": "person"
|
||||
},
|
||||
"valid": true
|
||||
},
|
||||
{
|
||||
"description": "Graph family excludes organization (missing light. schema that $refs light.entity)",
|
||||
"schema_id": "get_light_entities.response",
|
||||
"data": {
|
||||
"id": "5",
|
||||
"type": "organization",
|
||||
"name": "ACME"
|
||||
},
|
||||
"valid": false,
|
||||
"expect_errors": [
|
||||
{
|
||||
"code": "FAMILY_MISMATCH",
|
||||
"path": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Ad-hoc non-entity families (using normal json-schema object structures)",
|
||||
"database": {
|
||||
"puncs": [
|
||||
{
|
||||
"name": "get_widgets",
|
||||
"schemas": [
|
||||
{
|
||||
"$id": "widget",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"widget_type": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"$id": "special_widget",
|
||||
"$ref": "widget",
|
||||
"properties": {
|
||||
"special_feature": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"$id": "get_widgets.response",
|
||||
"$family": "widget"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "Ad-hoc family matches strictly by shape (no magic variations for base schemas)",
|
||||
"schema_id": "get_widgets.response",
|
||||
"data": {
|
||||
"id": "1",
|
||||
"widget_type": "special",
|
||||
"special_feature": "yes"
|
||||
},
|
||||
"valid": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
3770
tests/fixtures/format.json
vendored
3770
tests/fixtures/format.json
vendored
File diff suppressed because it is too large
Load Diff
495
tests/fixtures/if-then-else.json
vendored
495
tests/fixtures/if-then-else.json
vendored
@ -1,495 +0,0 @@
|
||||
[
|
||||
{
|
||||
"description": "ignore if without then or else",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"if": {
|
||||
"const": 0
|
||||
},
|
||||
"$id": "if-then-else_0_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "valid when valid against lone if",
|
||||
"data": 0,
|
||||
"valid": true,
|
||||
"schema_id": "if-then-else_0_0"
|
||||
},
|
||||
{
|
||||
"description": "valid when invalid against lone if",
|
||||
"data": "hello",
|
||||
"valid": true,
|
||||
"schema_id": "if-then-else_0_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "ignore then without if",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"then": {
|
||||
"const": 0
|
||||
},
|
||||
"$id": "if-then-else_1_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "valid when valid against lone then",
|
||||
"data": 0,
|
||||
"valid": true,
|
||||
"schema_id": "if-then-else_1_0"
|
||||
},
|
||||
{
|
||||
"description": "valid when invalid against lone then",
|
||||
"data": "hello",
|
||||
"valid": true,
|
||||
"schema_id": "if-then-else_1_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "ignore else without if",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"else": {
|
||||
"const": 0
|
||||
},
|
||||
"$id": "if-then-else_2_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "valid when valid against lone else",
|
||||
"data": 0,
|
||||
"valid": true,
|
||||
"schema_id": "if-then-else_2_0"
|
||||
},
|
||||
{
|
||||
"description": "valid when invalid against lone else",
|
||||
"data": "hello",
|
||||
"valid": true,
|
||||
"schema_id": "if-then-else_2_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "if and then without else",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"if": {
|
||||
"exclusiveMaximum": 0
|
||||
},
|
||||
"then": {
|
||||
"minimum": -10
|
||||
},
|
||||
"$id": "if-then-else_3_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "valid through then",
|
||||
"data": -1,
|
||||
"valid": true,
|
||||
"schema_id": "if-then-else_3_0"
|
||||
},
|
||||
{
|
||||
"description": "invalid through then",
|
||||
"data": -100,
|
||||
"valid": false,
|
||||
"schema_id": "if-then-else_3_0"
|
||||
},
|
||||
{
|
||||
"description": "valid when if test fails",
|
||||
"data": 3,
|
||||
"valid": true,
|
||||
"schema_id": "if-then-else_3_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "if and else without then",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"if": {
|
||||
"exclusiveMaximum": 0
|
||||
},
|
||||
"else": {
|
||||
"multipleOf": 2
|
||||
},
|
||||
"$id": "if-then-else_4_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "valid when if test passes",
|
||||
"data": -1,
|
||||
"valid": true,
|
||||
"schema_id": "if-then-else_4_0"
|
||||
},
|
||||
{
|
||||
"description": "valid through else",
|
||||
"data": 4,
|
||||
"valid": true,
|
||||
"schema_id": "if-then-else_4_0"
|
||||
},
|
||||
{
|
||||
"description": "invalid through else",
|
||||
"data": 3,
|
||||
"valid": false,
|
||||
"schema_id": "if-then-else_4_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "validate against correct branch, then vs else",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"if": {
|
||||
"exclusiveMaximum": 0
|
||||
},
|
||||
"then": {
|
||||
"minimum": -10
|
||||
},
|
||||
"else": {
|
||||
"multipleOf": 2
|
||||
},
|
||||
"$id": "if-then-else_5_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "valid through then",
|
||||
"data": -1,
|
||||
"valid": true,
|
||||
"schema_id": "if-then-else_5_0"
|
||||
},
|
||||
{
|
||||
"description": "invalid through then",
|
||||
"data": -100,
|
||||
"valid": false,
|
||||
"schema_id": "if-then-else_5_0"
|
||||
},
|
||||
{
|
||||
"description": "valid through else",
|
||||
"data": 4,
|
||||
"valid": true,
|
||||
"schema_id": "if-then-else_5_0"
|
||||
},
|
||||
{
|
||||
"description": "invalid through else",
|
||||
"data": 3,
|
||||
"valid": false,
|
||||
"schema_id": "if-then-else_5_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "non-interference across combined schemas",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
"if": {
|
||||
"exclusiveMaximum": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"then": {
|
||||
"minimum": -10
|
||||
}
|
||||
},
|
||||
{
|
||||
"else": {
|
||||
"multipleOf": 2
|
||||
}
|
||||
}
|
||||
],
|
||||
"$id": "if-then-else_6_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "valid, but would have been invalid through then",
|
||||
"data": -100,
|
||||
"valid": true,
|
||||
"schema_id": "if-then-else_6_0"
|
||||
},
|
||||
{
|
||||
"description": "valid, but would have been invalid through else",
|
||||
"data": 3,
|
||||
"valid": true,
|
||||
"schema_id": "if-then-else_6_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "if with boolean schema true",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"if": true,
|
||||
"then": {
|
||||
"const": "then"
|
||||
},
|
||||
"else": {
|
||||
"const": "else"
|
||||
},
|
||||
"$id": "if-then-else_7_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "boolean schema true in if always chooses the then path (valid)",
|
||||
"data": "then",
|
||||
"valid": true,
|
||||
"schema_id": "if-then-else_7_0"
|
||||
},
|
||||
{
|
||||
"description": "boolean schema true in if always chooses the then path (invalid)",
|
||||
"data": "else",
|
||||
"valid": false,
|
||||
"schema_id": "if-then-else_7_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "if with boolean schema false",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"if": false,
|
||||
"then": {
|
||||
"const": "then"
|
||||
},
|
||||
"else": {
|
||||
"const": "else"
|
||||
},
|
||||
"$id": "if-then-else_8_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "boolean schema false in if always chooses the else path (invalid)",
|
||||
"data": "then",
|
||||
"valid": false,
|
||||
"schema_id": "if-then-else_8_0"
|
||||
},
|
||||
{
|
||||
"description": "boolean schema false in if always chooses the else path (valid)",
|
||||
"data": "else",
|
||||
"valid": true,
|
||||
"schema_id": "if-then-else_8_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "if appears at the end when serialized (keyword processing sequence)",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"then": {
|
||||
"const": "yes"
|
||||
},
|
||||
"else": {
|
||||
"const": "other"
|
||||
},
|
||||
"if": {
|
||||
"maxLength": 4
|
||||
},
|
||||
"$id": "if-then-else_9_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "yes redirects to then and passes",
|
||||
"data": "yes",
|
||||
"valid": true,
|
||||
"schema_id": "if-then-else_9_0"
|
||||
},
|
||||
{
|
||||
"description": "other redirects to else and passes",
|
||||
"data": "other",
|
||||
"valid": true,
|
||||
"schema_id": "if-then-else_9_0"
|
||||
},
|
||||
{
|
||||
"description": "no redirects to then and fails",
|
||||
"data": "no",
|
||||
"valid": false,
|
||||
"schema_id": "if-then-else_9_0"
|
||||
},
|
||||
{
|
||||
"description": "invalid redirects to else and fails",
|
||||
"data": "invalid",
|
||||
"valid": false,
|
||||
"schema_id": "if-then-else_9_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "then: false fails when condition matches",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"if": {
|
||||
"const": 1
|
||||
},
|
||||
"then": false,
|
||||
"$id": "if-then-else_10_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "matches if → then=false → invalid",
|
||||
"data": 1,
|
||||
"valid": false,
|
||||
"schema_id": "if-then-else_10_0"
|
||||
},
|
||||
{
|
||||
"description": "does not match if → then ignored → valid",
|
||||
"data": 2,
|
||||
"valid": true,
|
||||
"schema_id": "if-then-else_10_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "else: false fails when condition does not match",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"if": {
|
||||
"const": 1
|
||||
},
|
||||
"else": false,
|
||||
"$id": "if-then-else_11_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "matches if → else ignored → valid",
|
||||
"data": 1,
|
||||
"valid": true,
|
||||
"schema_id": "if-then-else_11_0"
|
||||
},
|
||||
{
|
||||
"description": "does not match if → else executes → invalid",
|
||||
"data": 2,
|
||||
"valid": false,
|
||||
"schema_id": "if-then-else_11_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "extensible: true allows extra properties in if-then-else",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"if": {
|
||||
"properties": {
|
||||
"foo": {
|
||||
"const": 1
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"foo"
|
||||
]
|
||||
},
|
||||
"then": {
|
||||
"properties": {
|
||||
"bar": {
|
||||
"const": 2
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"bar"
|
||||
]
|
||||
},
|
||||
"extensible": true,
|
||||
"$id": "if-then-else_12_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "extra property is valid (matches if and then)",
|
||||
"data": {
|
||||
"foo": 1,
|
||||
"bar": 2,
|
||||
"extra": "prop"
|
||||
},
|
||||
"valid": true,
|
||||
"schema_id": "if-then-else_12_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "strict by default with if-then properties",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"if": {
|
||||
"properties": {
|
||||
"foo": {
|
||||
"const": 1
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"foo"
|
||||
]
|
||||
},
|
||||
"then": {
|
||||
"properties": {
|
||||
"bar": {
|
||||
"const": 2
|
||||
}
|
||||
}
|
||||
},
|
||||
"$id": "if-then-else_13_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "valid match (foo + bar)",
|
||||
"data": {
|
||||
"foo": 1,
|
||||
"bar": 2
|
||||
},
|
||||
"valid": true,
|
||||
"schema_id": "if-then-else_13_0"
|
||||
},
|
||||
{
|
||||
"description": "fails on extra property z explicitly",
|
||||
"data": {
|
||||
"foo": 1,
|
||||
"bar": 2,
|
||||
"z": 3
|
||||
},
|
||||
"valid": false,
|
||||
"schema_id": "if-then-else_13_0"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
843
tests/fixtures/items.json
vendored
843
tests/fixtures/items.json
vendored
@ -1,843 +0,0 @@
|
||||
[
|
||||
{
|
||||
"description": "a schema given for items",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"items": {
|
||||
"type": "integer"
|
||||
},
|
||||
"$id": "items_0_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "valid items",
|
||||
"data": [
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"valid": true,
|
||||
"schema_id": "items_0_0"
|
||||
},
|
||||
{
|
||||
"description": "wrong type of items",
|
||||
"data": [
|
||||
1,
|
||||
"x"
|
||||
],
|
||||
"valid": false,
|
||||
"schema_id": "items_0_0"
|
||||
},
|
||||
{
|
||||
"description": "non-arrays are invalid",
|
||||
"data": {
|
||||
"foo": "bar"
|
||||
},
|
||||
"valid": false,
|
||||
"schema_id": "items_0_0"
|
||||
},
|
||||
{
|
||||
"description": "JavaScript pseudo-arrays are invalid",
|
||||
"data": {
|
||||
"0": "invalid",
|
||||
"length": 1
|
||||
},
|
||||
"valid": false,
|
||||
"schema_id": "items_0_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "items with boolean schema (true)",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"items": true,
|
||||
"$id": "items_1_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "any array is valid",
|
||||
"data": [
|
||||
1,
|
||||
"foo",
|
||||
true
|
||||
],
|
||||
"valid": true,
|
||||
"schema_id": "items_1_0"
|
||||
},
|
||||
{
|
||||
"description": "empty array is valid",
|
||||
"data": [],
|
||||
"valid": true,
|
||||
"schema_id": "items_1_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "items with boolean schema (false)",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"items": false,
|
||||
"$id": "items_2_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "any non-empty array is invalid",
|
||||
"data": [
|
||||
1,
|
||||
"foo",
|
||||
true
|
||||
],
|
||||
"valid": false,
|
||||
"schema_id": "items_2_0"
|
||||
},
|
||||
{
|
||||
"description": "empty array is valid",
|
||||
"data": [],
|
||||
"valid": true,
|
||||
"schema_id": "items_2_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "items and subitems",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"type": "array",
|
||||
"items": false,
|
||||
"prefixItems": [
|
||||
{
|
||||
"$ref": "item"
|
||||
},
|
||||
{
|
||||
"$ref": "item"
|
||||
},
|
||||
{
|
||||
"$ref": "item"
|
||||
}
|
||||
],
|
||||
"$id": "items_3_0"
|
||||
},
|
||||
{
|
||||
"$id": "item",
|
||||
"type": "array",
|
||||
"items": false,
|
||||
"prefixItems": [
|
||||
{
|
||||
"$ref": "sub-item"
|
||||
},
|
||||
{
|
||||
"$ref": "sub-item"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"$id": "sub-item",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"foo"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "valid items",
|
||||
"data": [
|
||||
[
|
||||
{
|
||||
"foo": null
|
||||
},
|
||||
{
|
||||
"foo": null
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"foo": null
|
||||
},
|
||||
{
|
||||
"foo": null
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"foo": null
|
||||
},
|
||||
{
|
||||
"foo": null
|
||||
}
|
||||
]
|
||||
],
|
||||
"valid": false,
|
||||
"schema_id": "items_3_0"
|
||||
},
|
||||
{
|
||||
"description": "too many items",
|
||||
"data": [
|
||||
[
|
||||
{
|
||||
"foo": null
|
||||
},
|
||||
{
|
||||
"foo": null
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"foo": null
|
||||
},
|
||||
{
|
||||
"foo": null
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"foo": null
|
||||
},
|
||||
{
|
||||
"foo": null
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"foo": null
|
||||
},
|
||||
{
|
||||
"foo": null
|
||||
}
|
||||
]
|
||||
],
|
||||
"valid": false,
|
||||
"schema_id": "items_3_0"
|
||||
},
|
||||
{
|
||||
"description": "too many sub-items",
|
||||
"data": [
|
||||
[
|
||||
{
|
||||
"foo": null
|
||||
},
|
||||
{
|
||||
"foo": null
|
||||
},
|
||||
{
|
||||
"foo": null
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"foo": null
|
||||
},
|
||||
{
|
||||
"foo": null
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"foo": null
|
||||
},
|
||||
{
|
||||
"foo": null
|
||||
}
|
||||
]
|
||||
],
|
||||
"valid": false,
|
||||
"schema_id": "items_3_0"
|
||||
},
|
||||
{
|
||||
"description": "wrong item",
|
||||
"data": [
|
||||
{
|
||||
"foo": null
|
||||
},
|
||||
[
|
||||
{
|
||||
"foo": null
|
||||
},
|
||||
{
|
||||
"foo": null
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"foo": null
|
||||
},
|
||||
{
|
||||
"foo": null
|
||||
}
|
||||
]
|
||||
],
|
||||
"valid": false,
|
||||
"schema_id": "items_3_0"
|
||||
},
|
||||
{
|
||||
"description": "wrong sub-item",
|
||||
"data": [
|
||||
[
|
||||
{},
|
||||
{
|
||||
"foo": null
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"foo": null
|
||||
},
|
||||
{
|
||||
"foo": null
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"foo": null
|
||||
},
|
||||
{
|
||||
"foo": null
|
||||
}
|
||||
]
|
||||
],
|
||||
"valid": false,
|
||||
"schema_id": "items_3_0"
|
||||
},
|
||||
{
|
||||
"description": "fewer items is invalid",
|
||||
"data": [
|
||||
[
|
||||
{
|
||||
"foo": null
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"foo": null
|
||||
}
|
||||
]
|
||||
],
|
||||
"valid": false,
|
||||
"schema_id": "items_3_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "nested items",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"$id": "items_4_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "valid nested array",
|
||||
"data": [
|
||||
[
|
||||
[
|
||||
[
|
||||
1
|
||||
]
|
||||
],
|
||||
[
|
||||
[
|
||||
2
|
||||
],
|
||||
[
|
||||
3
|
||||
]
|
||||
]
|
||||
],
|
||||
[
|
||||
[
|
||||
[
|
||||
4
|
||||
],
|
||||
[
|
||||
5
|
||||
],
|
||||
[
|
||||
6
|
||||
]
|
||||
]
|
||||
]
|
||||
],
|
||||
"valid": true,
|
||||
"schema_id": "items_4_0"
|
||||
},
|
||||
{
|
||||
"description": "nested array with invalid type",
|
||||
"data": [
|
||||
[
|
||||
[
|
||||
[
|
||||
"1"
|
||||
]
|
||||
],
|
||||
[
|
||||
[
|
||||
2
|
||||
],
|
||||
[
|
||||
3
|
||||
]
|
||||
]
|
||||
],
|
||||
[
|
||||
[
|
||||
[
|
||||
4
|
||||
],
|
||||
[
|
||||
5
|
||||
],
|
||||
[
|
||||
6
|
||||
]
|
||||
]
|
||||
]
|
||||
],
|
||||
"valid": false,
|
||||
"schema_id": "items_4_0"
|
||||
},
|
||||
{
|
||||
"description": "not deep enough",
|
||||
"data": [
|
||||
[
|
||||
[
|
||||
1
|
||||
],
|
||||
[
|
||||
2
|
||||
],
|
||||
[
|
||||
3
|
||||
]
|
||||
],
|
||||
[
|
||||
[
|
||||
4
|
||||
],
|
||||
[
|
||||
5
|
||||
],
|
||||
[
|
||||
6
|
||||
]
|
||||
]
|
||||
],
|
||||
"valid": false,
|
||||
"schema_id": "items_4_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "prefixItems with no additional items allowed",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"prefixItems": [
|
||||
{},
|
||||
{},
|
||||
{}
|
||||
],
|
||||
"items": false,
|
||||
"$id": "items_5_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "empty array",
|
||||
"data": [],
|
||||
"valid": true,
|
||||
"schema_id": "items_5_0"
|
||||
},
|
||||
{
|
||||
"description": "fewer number of items present (1)",
|
||||
"data": [
|
||||
1
|
||||
],
|
||||
"valid": true,
|
||||
"schema_id": "items_5_0"
|
||||
},
|
||||
{
|
||||
"description": "fewer number of items present (2)",
|
||||
"data": [
|
||||
1,
|
||||
2
|
||||
],
|
||||
"valid": true,
|
||||
"schema_id": "items_5_0"
|
||||
},
|
||||
{
|
||||
"description": "equal number of items present",
|
||||
"data": [
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"valid": true,
|
||||
"schema_id": "items_5_0"
|
||||
},
|
||||
{
|
||||
"description": "additional items are not permitted",
|
||||
"data": [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4
|
||||
],
|
||||
"valid": false,
|
||||
"schema_id": "items_5_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "items does not look in applicators, valid case",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
"prefixItems": [
|
||||
{
|
||||
"minimum": 3
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"items": {
|
||||
"minimum": 5
|
||||
},
|
||||
"$id": "items_6_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "prefixItems in allOf does not constrain items, invalid case",
|
||||
"data": [
|
||||
3,
|
||||
5
|
||||
],
|
||||
"valid": false,
|
||||
"schema_id": "items_6_0"
|
||||
},
|
||||
{
|
||||
"description": "prefixItems in allOf does not constrain items, valid case",
|
||||
"data": [
|
||||
5,
|
||||
5
|
||||
],
|
||||
"valid": true,
|
||||
"schema_id": "items_6_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "prefixItems validation adjusts the starting index for items",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"prefixItems": [
|
||||
{
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"items": {
|
||||
"type": "integer"
|
||||
},
|
||||
"$id": "items_7_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "valid items",
|
||||
"data": [
|
||||
"x",
|
||||
2,
|
||||
3
|
||||
],
|
||||
"valid": true,
|
||||
"schema_id": "items_7_0"
|
||||
},
|
||||
{
|
||||
"description": "wrong type of second item",
|
||||
"data": [
|
||||
"x",
|
||||
"y"
|
||||
],
|
||||
"valid": false,
|
||||
"schema_id": "items_7_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "items with heterogeneous array",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"prefixItems": [
|
||||
{}
|
||||
],
|
||||
"items": false,
|
||||
"$id": "items_8_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "heterogeneous invalid instance",
|
||||
"data": [
|
||||
"foo",
|
||||
"bar",
|
||||
37
|
||||
],
|
||||
"valid": false,
|
||||
"schema_id": "items_8_0"
|
||||
},
|
||||
{
|
||||
"description": "valid instance",
|
||||
"data": [
|
||||
null
|
||||
],
|
||||
"valid": true,
|
||||
"schema_id": "items_8_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "items with null instance elements",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"items": {
|
||||
"type": "null"
|
||||
},
|
||||
"$id": "items_9_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "allows null elements",
|
||||
"data": [
|
||||
null
|
||||
],
|
||||
"valid": true,
|
||||
"schema_id": "items_9_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "extensible: true allows extra items (when items is false)",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"items": false,
|
||||
"extensible": true,
|
||||
"$id": "items_10_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "extra item is valid",
|
||||
"data": [
|
||||
1
|
||||
],
|
||||
"valid": false,
|
||||
"schema_id": "items_10_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "extensible: true allows extra properties for items",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"items": {
|
||||
"minimum": 5
|
||||
},
|
||||
"extensible": true,
|
||||
"$id": "items_11_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "valid item is valid",
|
||||
"data": [
|
||||
5,
|
||||
6
|
||||
],
|
||||
"valid": true,
|
||||
"schema_id": "items_11_0"
|
||||
},
|
||||
{
|
||||
"description": "invalid item (less than min) is invalid even with extensible: true",
|
||||
"data": [
|
||||
4
|
||||
],
|
||||
"valid": false,
|
||||
"schema_id": "items_11_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "array: simple extensible array",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"type": "array",
|
||||
"extensible": true,
|
||||
"$id": "items_12_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "empty array is valid",
|
||||
"data": [],
|
||||
"valid": true,
|
||||
"schema_id": "items_12_0"
|
||||
},
|
||||
{
|
||||
"description": "array with items is valid (extensible)",
|
||||
"data": [
|
||||
1,
|
||||
"foo"
|
||||
],
|
||||
"valid": true,
|
||||
"schema_id": "items_12_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "array: strict array",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"type": "array",
|
||||
"extensible": false,
|
||||
"$id": "items_13_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "empty array is valid",
|
||||
"data": [],
|
||||
"valid": true,
|
||||
"schema_id": "items_13_0"
|
||||
},
|
||||
{
|
||||
"description": "array with items is invalid (strict)",
|
||||
"data": [
|
||||
1
|
||||
],
|
||||
"valid": false,
|
||||
"schema_id": "items_13_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "array: items extensible",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"extensible": true
|
||||
},
|
||||
"$id": "items_14_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "empty array is valid",
|
||||
"data": [],
|
||||
"valid": true,
|
||||
"schema_id": "items_14_0"
|
||||
},
|
||||
{
|
||||
"description": "array with items is valid (items explicitly allowed to be anything extensible)",
|
||||
"data": [
|
||||
1,
|
||||
"foo",
|
||||
{}
|
||||
],
|
||||
"valid": true,
|
||||
"schema_id": "items_14_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "array: items strict",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"extensible": false
|
||||
},
|
||||
"$id": "items_15_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "empty array is valid (empty objects)",
|
||||
"data": [
|
||||
{}
|
||||
],
|
||||
"valid": true,
|
||||
"schema_id": "items_15_0"
|
||||
},
|
||||
{
|
||||
"description": "array with strict object items is valid",
|
||||
"data": [
|
||||
{}
|
||||
],
|
||||
"valid": true,
|
||||
"schema_id": "items_15_0"
|
||||
},
|
||||
{
|
||||
"description": "array with invalid strict object items (extra property)",
|
||||
"data": [
|
||||
{
|
||||
"extra": 1
|
||||
}
|
||||
],
|
||||
"valid": false,
|
||||
"schema_id": "items_15_0"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
196
tests/fixtures/maxContains.json
vendored
196
tests/fixtures/maxContains.json
vendored
@ -1,196 +0,0 @@
|
||||
[
|
||||
{
|
||||
"description": "maxContains without contains is ignored",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"maxContains": 1,
|
||||
"extensible": true,
|
||||
"$id": "maxContains_0_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "one item valid against lone maxContains",
|
||||
"data": [
|
||||
1
|
||||
],
|
||||
"valid": true,
|
||||
"schema_id": "maxContains_0_0"
|
||||
},
|
||||
{
|
||||
"description": "two items still valid against lone maxContains",
|
||||
"data": [
|
||||
1,
|
||||
2
|
||||
],
|
||||
"valid": true,
|
||||
"schema_id": "maxContains_0_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "maxContains with contains",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"contains": {
|
||||
"const": 1
|
||||
},
|
||||
"maxContains": 1,
|
||||
"extensible": true,
|
||||
"$id": "maxContains_1_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "empty data",
|
||||
"data": [],
|
||||
"valid": false,
|
||||
"schema_id": "maxContains_1_0"
|
||||
},
|
||||
{
|
||||
"description": "all elements match, valid maxContains",
|
||||
"data": [
|
||||
1
|
||||
],
|
||||
"valid": true,
|
||||
"schema_id": "maxContains_1_0"
|
||||
},
|
||||
{
|
||||
"description": "all elements match, invalid maxContains",
|
||||
"data": [
|
||||
1,
|
||||
1
|
||||
],
|
||||
"valid": false,
|
||||
"schema_id": "maxContains_1_0"
|
||||
},
|
||||
{
|
||||
"description": "some elements match, valid maxContains",
|
||||
"data": [
|
||||
1,
|
||||
2
|
||||
],
|
||||
"valid": true,
|
||||
"schema_id": "maxContains_1_0"
|
||||
},
|
||||
{
|
||||
"description": "some elements match, invalid maxContains",
|
||||
"data": [
|
||||
1,
|
||||
2,
|
||||
1
|
||||
],
|
||||
"valid": false,
|
||||
"schema_id": "maxContains_1_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "maxContains with contains, value with a decimal",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"contains": {
|
||||
"const": 1
|
||||
},
|
||||
"maxContains": 1,
|
||||
"extensible": true,
|
||||
"$id": "maxContains_2_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "one element matches, valid maxContains",
|
||||
"data": [
|
||||
1
|
||||
],
|
||||
"valid": true,
|
||||
"schema_id": "maxContains_2_0"
|
||||
},
|
||||
{
|
||||
"description": "too many elements match, invalid maxContains",
|
||||
"data": [
|
||||
1,
|
||||
1
|
||||
],
|
||||
"valid": false,
|
||||
"schema_id": "maxContains_2_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "minContains < maxContains",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"contains": {
|
||||
"const": 1
|
||||
},
|
||||
"minContains": 1,
|
||||
"maxContains": 3,
|
||||
"extensible": true,
|
||||
"$id": "maxContains_3_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "actual < minContains < maxContains",
|
||||
"data": [],
|
||||
"valid": false,
|
||||
"schema_id": "maxContains_3_0"
|
||||
},
|
||||
{
|
||||
"description": "minContains < actual < maxContains",
|
||||
"data": [
|
||||
1,
|
||||
1
|
||||
],
|
||||
"valid": true,
|
||||
"schema_id": "maxContains_3_0"
|
||||
},
|
||||
{
|
||||
"description": "minContains < maxContains < actual",
|
||||
"data": [
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1
|
||||
],
|
||||
"valid": false,
|
||||
"schema_id": "maxContains_3_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "extensible: true allows non-matching items in maxContains",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"contains": {
|
||||
"const": 1
|
||||
},
|
||||
"maxContains": 1,
|
||||
"extensible": true,
|
||||
"$id": "maxContains_4_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "extra items disregarded for maxContains",
|
||||
"data": [
|
||||
1,
|
||||
2
|
||||
],
|
||||
"valid": true,
|
||||
"schema_id": "maxContains_4_0"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
105
tests/fixtures/maxItems.json
vendored
105
tests/fixtures/maxItems.json
vendored
@ -1,105 +0,0 @@
|
||||
[
|
||||
{
|
||||
"description": "maxItems validation",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"maxItems": 2,
|
||||
"extensible": true,
|
||||
"$id": "maxItems_0_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "shorter is valid",
|
||||
"data": [
|
||||
1
|
||||
],
|
||||
"valid": true,
|
||||
"schema_id": "maxItems_0_0"
|
||||
},
|
||||
{
|
||||
"description": "exact length is valid",
|
||||
"data": [
|
||||
1,
|
||||
2
|
||||
],
|
||||
"valid": true,
|
||||
"schema_id": "maxItems_0_0"
|
||||
},
|
||||
{
|
||||
"description": "too long is invalid",
|
||||
"data": [
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"valid": false,
|
||||
"schema_id": "maxItems_0_0"
|
||||
},
|
||||
{
|
||||
"description": "ignores non-arrays",
|
||||
"data": "foobar",
|
||||
"valid": true,
|
||||
"schema_id": "maxItems_0_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "maxItems validation with a decimal",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"maxItems": 2,
|
||||
"extensible": true,
|
||||
"$id": "maxItems_1_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "shorter is valid",
|
||||
"data": [
|
||||
1
|
||||
],
|
||||
"valid": true,
|
||||
"schema_id": "maxItems_1_0"
|
||||
},
|
||||
{
|
||||
"description": "too long is invalid",
|
||||
"data": [
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"valid": false,
|
||||
"schema_id": "maxItems_1_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "extensible: true allows extra items in maxItems (but counted)",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"maxItems": 2,
|
||||
"extensible": true,
|
||||
"$id": "maxItems_2_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "extra item counted towards maxItems",
|
||||
"data": [
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"valid": false,
|
||||
"schema_id": "maxItems_2_0"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
70
tests/fixtures/maxLength.json
vendored
70
tests/fixtures/maxLength.json
vendored
@ -1,70 +0,0 @@
|
||||
[
|
||||
{
|
||||
"description": "maxLength validation",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"maxLength": 2,
|
||||
"$id": "maxLength_0_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "shorter is valid",
|
||||
"data": "f",
|
||||
"valid": true,
|
||||
"schema_id": "maxLength_0_0"
|
||||
},
|
||||
{
|
||||
"description": "exact length is valid",
|
||||
"data": "fo",
|
||||
"valid": true,
|
||||
"schema_id": "maxLength_0_0"
|
||||
},
|
||||
{
|
||||
"description": "too long is invalid",
|
||||
"data": "foo",
|
||||
"valid": false,
|
||||
"schema_id": "maxLength_0_0"
|
||||
},
|
||||
{
|
||||
"description": "ignores non-strings",
|
||||
"data": 100,
|
||||
"valid": true,
|
||||
"schema_id": "maxLength_0_0"
|
||||
},
|
||||
{
|
||||
"description": "two graphemes is long enough",
|
||||
"data": "💩💩",
|
||||
"valid": true,
|
||||
"schema_id": "maxLength_0_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "maxLength validation with a decimal",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"maxLength": 2,
|
||||
"$id": "maxLength_1_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "shorter is valid",
|
||||
"data": "f",
|
||||
"valid": true,
|
||||
"schema_id": "maxLength_1_0"
|
||||
},
|
||||
{
|
||||
"description": "too long is invalid",
|
||||
"data": "foo",
|
||||
"valid": false,
|
||||
"schema_id": "maxLength_1_0"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
157
tests/fixtures/maxProperties.json
vendored
157
tests/fixtures/maxProperties.json
vendored
@ -1,157 +0,0 @@
|
||||
[
|
||||
{
|
||||
"description": "maxProperties validation",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"maxProperties": 2,
|
||||
"extensible": true,
|
||||
"$id": "maxProperties_0_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "shorter is valid",
|
||||
"data": {
|
||||
"foo": 1
|
||||
},
|
||||
"valid": true,
|
||||
"schema_id": "maxProperties_0_0"
|
||||
},
|
||||
{
|
||||
"description": "exact length is valid",
|
||||
"data": {
|
||||
"foo": 1,
|
||||
"bar": 2
|
||||
},
|
||||
"valid": true,
|
||||
"schema_id": "maxProperties_0_0"
|
||||
},
|
||||
{
|
||||
"description": "too long is invalid",
|
||||
"data": {
|
||||
"foo": 1,
|
||||
"bar": 2,
|
||||
"baz": 3
|
||||
},
|
||||
"valid": false,
|
||||
"schema_id": "maxProperties_0_0"
|
||||
},
|
||||
{
|
||||
"description": "ignores arrays",
|
||||
"data": [
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"valid": true,
|
||||
"schema_id": "maxProperties_0_0"
|
||||
},
|
||||
{
|
||||
"description": "ignores strings",
|
||||
"data": "foobar",
|
||||
"valid": true,
|
||||
"schema_id": "maxProperties_0_0"
|
||||
},
|
||||
{
|
||||
"description": "ignores other non-objects",
|
||||
"data": 12,
|
||||
"valid": true,
|
||||
"schema_id": "maxProperties_0_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "maxProperties validation with a decimal",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"maxProperties": 2,
|
||||
"extensible": true,
|
||||
"$id": "maxProperties_1_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "shorter is valid",
|
||||
"data": {
|
||||
"foo": 1
|
||||
},
|
||||
"valid": true,
|
||||
"schema_id": "maxProperties_1_0"
|
||||
},
|
||||
{
|
||||
"description": "too long is invalid",
|
||||
"data": {
|
||||
"foo": 1,
|
||||
"bar": 2,
|
||||
"baz": 3
|
||||
},
|
||||
"valid": false,
|
||||
"schema_id": "maxProperties_1_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "maxProperties = 0 means the object is empty",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"maxProperties": 0,
|
||||
"extensible": true,
|
||||
"$id": "maxProperties_2_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "no properties is valid",
|
||||
"data": {},
|
||||
"valid": true,
|
||||
"schema_id": "maxProperties_2_0"
|
||||
},
|
||||
{
|
||||
"description": "one property is invalid",
|
||||
"data": {
|
||||
"foo": 1
|
||||
},
|
||||
"valid": false,
|
||||
"schema_id": "maxProperties_2_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "extensible: true allows extra properties in maxProperties (though maxProperties still counts them!)",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"maxProperties": 2,
|
||||
"extensible": true,
|
||||
"$id": "maxProperties_3_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "extra property is counted towards maxProperties",
|
||||
"data": {
|
||||
"foo": 1,
|
||||
"bar": 2,
|
||||
"baz": 3
|
||||
},
|
||||
"valid": false,
|
||||
"schema_id": "maxProperties_3_0"
|
||||
},
|
||||
{
|
||||
"description": "extra property is valid if below maxProperties",
|
||||
"data": {
|
||||
"foo": 1
|
||||
},
|
||||
"valid": true,
|
||||
"schema_id": "maxProperties_3_0"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
76
tests/fixtures/maximum.json
vendored
76
tests/fixtures/maximum.json
vendored
@ -1,76 +0,0 @@
|
||||
[
|
||||
{
|
||||
"description": "maximum validation",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"maximum": 3,
|
||||
"$id": "maximum_0_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "below the maximum is valid",
|
||||
"data": 2.6,
|
||||
"valid": true,
|
||||
"schema_id": "maximum_0_0"
|
||||
},
|
||||
{
|
||||
"description": "boundary point is valid",
|
||||
"data": 3,
|
||||
"valid": true,
|
||||
"schema_id": "maximum_0_0"
|
||||
},
|
||||
{
|
||||
"description": "above the maximum is invalid",
|
||||
"data": 3.5,
|
||||
"valid": false,
|
||||
"schema_id": "maximum_0_0"
|
||||
},
|
||||
{
|
||||
"description": "ignores non-numbers",
|
||||
"data": "x",
|
||||
"valid": true,
|
||||
"schema_id": "maximum_0_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "maximum validation with unsigned integer",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"maximum": 300,
|
||||
"$id": "maximum_1_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "below the maximum is invalid",
|
||||
"data": 299.97,
|
||||
"valid": true,
|
||||
"schema_id": "maximum_1_0"
|
||||
},
|
||||
{
|
||||
"description": "boundary point integer is valid",
|
||||
"data": 300,
|
||||
"valid": true,
|
||||
"schema_id": "maximum_1_0"
|
||||
},
|
||||
{
|
||||
"description": "boundary point float is valid",
|
||||
"data": 300,
|
||||
"valid": true,
|
||||
"schema_id": "maximum_1_0"
|
||||
},
|
||||
{
|
||||
"description": "above the maximum is invalid",
|
||||
"data": 300.5,
|
||||
"valid": false,
|
||||
"schema_id": "maximum_1_0"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
247
tests/fixtures/merge.json
vendored
247
tests/fixtures/merge.json
vendored
@ -1,247 +0,0 @@
|
||||
[
|
||||
{
|
||||
"description": "merging: properties accumulate",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"$id": "base_0",
|
||||
"properties": {
|
||||
"base_prop": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"$ref": "base_0",
|
||||
"properties": {
|
||||
"child_prop": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"$id": "merge_0_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "valid with both properties",
|
||||
"data": {
|
||||
"base_prop": "a",
|
||||
"child_prop": "b"
|
||||
},
|
||||
"valid": true,
|
||||
"schema_id": "merge_0_0"
|
||||
},
|
||||
{
|
||||
"description": "invalid when base property has wrong type",
|
||||
"data": {
|
||||
"base_prop": 1,
|
||||
"child_prop": "b"
|
||||
},
|
||||
"valid": false,
|
||||
"expect_errors": [
|
||||
{
|
||||
"code": "TYPE_MISMATCH",
|
||||
"path": "/base_prop"
|
||||
}
|
||||
],
|
||||
"schema_id": "merge_0_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "merging: required fields accumulate",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"$id": "base_1",
|
||||
"properties": {
|
||||
"a": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"a"
|
||||
]
|
||||
},
|
||||
{
|
||||
"$ref": "base_1",
|
||||
"properties": {
|
||||
"b": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"b"
|
||||
],
|
||||
"$id": "merge_1_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "valid when both present",
|
||||
"data": {
|
||||
"a": "ok",
|
||||
"b": "ok"
|
||||
},
|
||||
"valid": true,
|
||||
"schema_id": "merge_1_0"
|
||||
},
|
||||
{
|
||||
"description": "invalid when base required missing",
|
||||
"data": {
|
||||
"b": "ok"
|
||||
},
|
||||
"valid": false,
|
||||
"expect_errors": [
|
||||
{
|
||||
"code": "REQUIRED_FIELD_MISSING",
|
||||
"path": "/a"
|
||||
}
|
||||
],
|
||||
"schema_id": "merge_1_0"
|
||||
},
|
||||
{
|
||||
"description": "invalid when child required missing",
|
||||
"data": {
|
||||
"a": "ok"
|
||||
},
|
||||
"valid": false,
|
||||
"expect_errors": [
|
||||
{
|
||||
"code": "REQUIRED_FIELD_MISSING",
|
||||
"path": "/b"
|
||||
}
|
||||
],
|
||||
"schema_id": "merge_1_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "merging: dependencies accumulate",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"$id": "base_2",
|
||||
"properties": {
|
||||
"trigger": {
|
||||
"type": "string"
|
||||
},
|
||||
"base_dep": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"trigger": [
|
||||
"base_dep"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"$ref": "base_2",
|
||||
"properties": {
|
||||
"child_dep": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"trigger": [
|
||||
"child_dep"
|
||||
]
|
||||
},
|
||||
"$id": "merge_2_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "valid with all deps",
|
||||
"data": {
|
||||
"trigger": "go",
|
||||
"base_dep": "ok",
|
||||
"child_dep": "ok"
|
||||
},
|
||||
"valid": true,
|
||||
"schema_id": "merge_2_0"
|
||||
},
|
||||
{
|
||||
"description": "invalid missing base dep",
|
||||
"data": {
|
||||
"trigger": "go",
|
||||
"child_dep": "ok"
|
||||
},
|
||||
"valid": false,
|
||||
"expect_errors": [
|
||||
{
|
||||
"code": "DEPENDENCY_FAILED",
|
||||
"path": "/base_dep"
|
||||
}
|
||||
],
|
||||
"schema_id": "merge_2_0"
|
||||
},
|
||||
{
|
||||
"description": "invalid missing child dep",
|
||||
"data": {
|
||||
"trigger": "go",
|
||||
"base_dep": "ok"
|
||||
},
|
||||
"valid": false,
|
||||
"expect_errors": [
|
||||
{
|
||||
"code": "DEPENDENCY_FAILED",
|
||||
"path": "/child_dep"
|
||||
}
|
||||
],
|
||||
"schema_id": "merge_2_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "merging: form and display do NOT merge",
|
||||
"database": {
|
||||
"schemas": [
|
||||
{
|
||||
"$id": "base_3",
|
||||
"properties": {
|
||||
"a": {
|
||||
"type": "string"
|
||||
},
|
||||
"b": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"form": [
|
||||
"a",
|
||||
"b"
|
||||
]
|
||||
},
|
||||
{
|
||||
"$ref": "base_3",
|
||||
"properties": {
|
||||
"c": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"form": [
|
||||
"c"
|
||||
],
|
||||
"$id": "merge_3_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "child schema validation",
|
||||
"data": {
|
||||
"a": "ok",
|
||||
"b": "ok",
|
||||
"c": "ok"
|
||||
},
|
||||
"valid": true,
|
||||
"comment": "Verifies validator handles the unmerged metadata correctly (ignores it or handles replacement)",
|
||||
"schema_id": "merge_3_0"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user