validator refactor progress

This commit is contained in:
2026-03-03 00:13:37 -05:00
parent e14f53e7d9
commit 3898c43742
81 changed files with 6331 additions and 7934 deletions

1
Cargo.lock generated
View File

@ -817,6 +817,7 @@ dependencies = [
"chrono", "chrono",
"fluent-uri", "fluent-uri",
"idna", "idna",
"indexmap",
"json-pointer", "json-pointer",
"lazy_static", "lazy_static",
"once_cell", "once_cell",

View File

@ -19,6 +19,7 @@ percent-encoding = "2.3.2"
uuid = { version = "1.20.0", features = ["v4", "serde"] } uuid = { version = "1.20.0", features = ["v4", "serde"] }
chrono = { version = "0.4.43", features = ["serde"] } chrono = { version = "0.4.43", features = ["serde"] }
json-pointer = "0.3.4" json-pointer = "0.3.4"
indexmap = { version = "2.13.0", features = ["serde"] }
[dev-dependencies] [dev-dependencies]
pgrx-tests = "0.16.1" pgrx-tests = "0.16.1"
@ -51,4 +52,4 @@ lto = "fat"
codegen-units = 1 codegen-units = 1
[package.metadata.jspg] [package.metadata.jspg]
target_draft = "draft2020-12" target_draft = "draft2020-12"

View File

@ -69,19 +69,26 @@ Returns a debug dump of the currently cached schemas (for development/debugging)
JSPG implements specific extensions to the Draft 2020-12 standard to support the Punc architecture's object-oriented needs. JSPG implements specific extensions to the Draft 2020-12 standard to support the Punc architecture's object-oriented needs.
### 1. Implicit Keyword Shadowing ### 1. The Unified Semantic Graph & Native Inheritance
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. JSPG goes beyond Draft 2020-12 to natively understand Object-Oriented inheritance and polymorphism. During the `cache_json_schemas()` phase, JSPG builds a single Directed Acyclic Graph (DAG) using **only** the `$ref` keyword. Every schema that uses `$ref` establishes a parent-to-child relationship.
* **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. Furthermore, `jspg` knows which schemas belong directly to database tables (Entities) versus which are ad-hoc API shapes.
* *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`. * **Native `type` Discrimination**: For any schema that traces its ancestry back to the base `entity`, JSPG securely and implicitly manages the `type` property. You do **not** need to explicitly override `"type": {"const": "person"}` in entity subclasses. If a schema `$ref`s `organization`, JSPG automatically allows the incoming `type` to be anything in the `organization` family tree (e.g., `person`, `bot`), but rigidly truncates/masks the data structure to the requested `organization` shape.
* *Granularity*: Shadowing is per-keyword. If `Entity` defined `type: { const: "entity", minLength: 5 }`, `Person` would shadow `const` but still inherit `minLength: 5`. * **Ad-Hoc Objects**: If an ad-hoc schema `$ref`s a base object but does not trace back to `entity`, standard JSON Schema rules apply (no magical `type` tracking).
* **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. > [!NOTE]
> **`$ref` never creates a Union.** When you use `$ref`, you are asking for a single, concrete struct/shape. The schema's strict fields will be rigidly enforced, but the `type` property is permitted to match any valid descendant via the native discrimination.
### 2. Virtual Family References (`$family`) ### 2. Shape Polymorphism & Virtual Unions (`$family`)
To support polymorphic fields (e.g., a field that accepts any "User" type), JSPG generates virtual schemas representing type hierarchies. To support polymorphic API contracts and deeply nested UI Unions without manually writing massive `oneOf` blocks, JSPG provides the `$family` macro. While `$ref` guarantees a single shape, `$family` asks the code generators for a true Polymorphic Union class.
* **Mechanism**: When caching types, if a type defines a `hierarchy` (e.g., `["entity", "organization", "person"]`), JSPG generates a virtual `oneOf` family containing refs to all valid descendants. These can be pointed to exclusively by using `{"$family": "organization"}`. Because `$family` is a macro-pointer that swaps in the virtual union, it **must** be used exclusively in its schema object; you cannot define other properties alongside it. When `{"$family": "organization.light"}` is encountered, JSPG:
1. Locates the base `organization` node in the Semantic Graph.
2. Recursively walks down to find all descendants via `$ref`.
3. **Strictly Filters** the descendants using the exact dot-notation suffix requested. It will only include descendants whose `$id` matches the shape modifier (e.g., `person.light`, `user.light`). If `bot` has no `.light` shape defined, it is securely omitted from the union.
4. Generates a virtual `oneOf` array containing those precise `$ref`s.
This cleanly separates **Database Ancestry** (managed entirely and implicitly by `$ref` for single shapes) from **Shape Variations** (managed explicitly by `$family` to build `oneOf` unions).
### 3. Strict by Default & Extensibility ### 3. 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. JSPG enforces a "Secure by Default" philosophy. All schemas are treated as if `unevaluatedProperties: false` (and `unevaluatedItems: false`) is set, unless explicitly overridden.

12
src/database/enum.rs Normal file
View File

@ -0,0 +1,12 @@
use crate::database::schema::Schema;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[serde(default)]
pub struct Enum {
pub name: String,
pub module: String,
pub source: String,
pub values: Vec<String>,
pub schemas: Vec<Schema>,
}

220
src/database/mod.rs Normal file
View File

@ -0,0 +1,220 @@
pub mod r#enum;
pub mod formats;
pub mod page;
pub mod punc;
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;
use std::collections::HashMap;
pub struct Database {
pub enums: HashMap<String, Enum>,
pub types: HashMap<String, Type>,
pub puncs: HashMap<String, Punc>,
pub schemas: HashMap<String, Schema>,
pub descendants: HashMap<String, Vec<String>>,
}
impl Database {
pub fn new(val: &serde_json::Value) -> Self {
let mut db = Self {
enums: HashMap::new(),
types: HashMap::new(),
puncs: HashMap::new(),
schemas: HashMap::new(),
descendants: HashMap::new(),
};
if let Some(arr) = val.get("enums").and_then(|v| v.as_array()) {
for item in arr {
if let Ok(def) = serde_json::from_value::<Enum>(item.clone()) {
db.enums.insert(def.name.clone(), def);
}
}
}
if let Some(arr) = val.get("types").and_then(|v| v.as_array()) {
for item in arr {
if let Ok(def) = serde_json::from_value::<Type>(item.clone()) {
db.types.insert(def.name.clone(), def);
}
}
}
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()) {
db.puncs.insert(def.name.clone(), def);
}
}
}
if let Some(arr) = val.get("schemas").and_then(|v| v.as_array()) {
for (i, item) in arr.iter().enumerate() {
if let Ok(mut schema) = serde_json::from_value::<Schema>(item.clone()) {
let id = schema
.obj
.id
.clone()
.unwrap_or_else(|| format!("schema_{}", i));
schema.obj.id = Some(id.clone());
db.schemas.insert(id, schema);
}
}
}
let _ = db.compile();
db
}
/// Organizes the graph of the database, compiling regex, format functions, and pointing schema references.
fn compile(&mut self) -> Result<(), String> {
self.collect_schemas();
// 1. Compile regex and formats sequentially
for schema in self.schemas.values_mut() {
schema.compile();
}
// 2. Compute the Unified Semantic Graph (descendants)
self.collect_descendents();
// 3. For any schema representing a Postgres table, cache its allowed subclasses
self.compile_allowed_types();
// 4. Finally, securely link all string $refs into memory pointers (Arc)
self.compile_pointers();
Ok(())
}
fn collect_schemas(&mut self) {
let mut to_insert = Vec::new();
for (_, type_def) in &self.types {
for schema in &type_def.schemas {
if let Some(id) = &schema.obj.id {
to_insert.push((id.clone(), schema.clone()));
}
}
}
for (_, punc_def) in &self.puncs {
for schema in &punc_def.schemas {
if let Some(id) = &schema.obj.id {
to_insert.push((id.clone(), schema.clone()));
}
}
}
for (_, enum_def) in &self.enums {
for schema in &enum_def.schemas {
if let Some(id) = &schema.obj.id {
to_insert.push((id.clone(), schema.clone()));
}
}
}
for (id, schema) in to_insert {
self.schemas.insert(id, schema);
}
}
fn collect_descendents(&mut self) {
let mut direct_children: HashMap<String, Vec<String>> = HashMap::new();
// First pass: Find all schemas that have a $ref to another schema
let schema_ids: Vec<String> = self.schemas.keys().cloned().collect();
for id in schema_ids {
if let Some(ref_str) = self.schemas.get(&id).and_then(|s| s.obj.ref_string.clone()) {
if self.schemas.contains_key(&ref_str) {
direct_children.entry(ref_str).or_default().push(id.clone());
}
}
}
// Now compute descendants for all schemas
let mut descendants_map: HashMap<String, Vec<String>> = HashMap::new();
for key in self.schemas.keys() {
let mut descendants = Vec::new();
let mut queue = Vec::new();
if let Some(children) = direct_children.get(key) {
queue.extend(children.iter().cloned());
}
let mut visited = std::collections::HashSet::new();
while let Some(child) = queue.pop() {
if visited.insert(child.clone()) {
descendants.push(child.clone());
if let Some(grandchildren) = direct_children.get(&child) {
queue.extend(grandchildren.iter().cloned());
}
}
}
descendants_map.insert(key.clone(), descendants);
}
self.descendants = descendants_map;
}
fn compile_allowed_types(&mut self) {
// 1. Identify which types act as bases (table-backed schemas)
let mut entity_bases = HashMap::new();
for type_def in self.types.values() {
for type_schema in &type_def.schemas {
if let Some(id) = &type_schema.obj.id {
entity_bases.insert(id.clone(), type_def.name.clone());
}
}
}
// 2. Compute compiled_allowed_types for all descendants of entity bases
let mut allowed_types_map: HashMap<String, std::collections::HashSet<String>> = HashMap::new();
for base_id in entity_bases.keys() {
allowed_types_map.insert(
base_id.clone(),
self
.descendants
.get(base_id)
.unwrap_or(&vec![])
.iter()
.cloned()
.collect(),
);
if let Some(descendants) = self.descendants.get(base_id) {
let set: std::collections::HashSet<String> = descendants.iter().cloned().collect();
for desc_id in descendants {
allowed_types_map.insert(desc_id.clone(), set.clone());
}
}
}
// 3. Inject types into the schemas
let schema_ids: Vec<String> = self.schemas.keys().cloned().collect();
for id in schema_ids {
if let Some(set) = allowed_types_map.get(&id) {
if let Some(schema) = self.schemas.get_mut(&id) {
schema.obj.compiled_allowed_types = Some(set.clone());
}
}
}
}
fn compile_pointers(&mut self) {
let schema_ids: Vec<String> = self.schemas.keys().cloned().collect();
for id in schema_ids {
let mut compiled_ref = None;
if let Some(schema) = self.schemas.get(&id) {
if let Some(ref_str) = &schema.obj.ref_string {
if let Some(target) = self.schemas.get(ref_str) {
compiled_ref = Some(std::sync::Arc::new(target.clone()));
}
}
}
if let Some(schema) = self.schemas.get_mut(&id) {
schema.obj.compiled_ref = compiled_ref;
}
}
}
}

35
src/database/page.rs Normal file
View File

@ -0,0 +1,35 @@
use indexmap::IndexMap;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[serde(default)]
pub struct Page {
#[serde(skip_serializing_if = "Option::is_none")]
pub path: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub sidebar: Option<Sidebar>,
#[serde(skip_serializing_if = "Option::is_none")]
pub actions: Option<IndexMap<String, Action>>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[serde(default)]
pub struct Sidebar {
#[serde(skip_serializing_if = "Option::is_none")]
pub category: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub priority: Option<i32>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[serde(default)]
pub struct Action {
#[serde(skip_serializing_if = "Option::is_none")]
pub punc: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub navigate: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub present: Option<String>,
}

20
src/database/punc.rs Normal file
View File

@ -0,0 +1,20 @@
use crate::database::page::Page;
use crate::database::schema::Schema;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[serde(default)]
pub struct Punc {
pub id: String,
pub r#type: String,
pub name: String,
pub module: String,
pub source: String,
pub description: Option<String>,
pub public: bool,
pub form: bool,
pub get: Option<String>,
pub page: Option<Page>,
#[serde(default)]
pub schemas: Vec<Schema>,
}

15
src/database/relation.rs Normal file
View File

@ -0,0 +1,15 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[serde(default)]
pub struct Relation {
pub id: String,
pub constraint_name: String,
pub source_type: String,
#[serde(default)]
pub source_columns: Vec<String>,
pub destination_type: String,
#[serde(default)]
pub destination_columns: Vec<String>,
pub prefix: Option<String>,
}

View File

@ -12,12 +12,6 @@ pub struct SchemaObject {
pub id: Option<String>, pub id: Option<String>,
#[serde(rename = "$ref")] #[serde(rename = "$ref")]
pub ref_string: Option<String>, pub ref_string: Option<String>,
#[serde(rename = "$anchor")]
pub anchor: Option<String>,
#[serde(rename = "$dynamicAnchor")]
pub dynamic_anchor: Option<String>,
#[serde(rename = "$dynamicRef")]
pub dynamic_ref: Option<String>,
/* /*
Note: The `Ref` field in the Go struct is a pointer populated by the linker. 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), In Rust, we might handle this differently (e.g., separate lookup or Rc/Arc),
@ -43,12 +37,6 @@ pub struct SchemaObject {
// dependencies can be schema dependencies or property dependencies // dependencies can be schema dependencies or property dependencies
pub dependencies: Option<BTreeMap<String, Dependency>>, pub dependencies: Option<BTreeMap<String, Dependency>>,
// Definitions (for $ref resolution)
#[serde(rename = "$defs")]
pub defs: Option<BTreeMap<String, Arc<Schema>>>,
#[serde(rename = "definitions")]
pub definitions: Option<BTreeMap<String, Arc<Schema>>>,
// Array Keywords // Array Keywords
#[serde(rename = "items")] #[serde(rename = "items")]
pub items: Option<Arc<Schema>>, pub items: Option<Arc<Schema>>,
@ -83,10 +71,6 @@ pub struct SchemaObject {
pub max_properties: Option<f64>, pub max_properties: Option<f64>,
#[serde(rename = "propertyNames")] #[serde(rename = "propertyNames")]
pub property_names: Option<Arc<Schema>>, pub property_names: Option<Arc<Schema>>,
#[serde(rename = "dependentRequired")]
pub dependent_required: Option<BTreeMap<String, Vec<String>>>,
#[serde(rename = "dependentSchemas")]
pub dependent_schemas: Option<BTreeMap<String, Arc<Schema>>>,
// Numeric Validation // Numeric Validation
pub format: Option<String>, pub format: Option<String>,
@ -138,15 +122,42 @@ pub struct SchemaObject {
// Compiled Fields (Hidden from JSON/Serde) // Compiled Fields (Hidden from JSON/Serde)
#[serde(skip)] #[serde(skip)]
pub compiled_format: Option<crate::validator::compiler::CompiledFormat>, pub compiled_ref: Option<Arc<Schema>>,
#[serde(skip)] #[serde(skip)]
pub compiled_pattern: Option<crate::validator::compiler::CompiledRegex>, pub compiled_allowed_types: Option<std::collections::HashSet<String>>,
#[serde(skip)] #[serde(skip)]
pub compiled_pattern_properties: Option<Vec<(crate::validator::compiler::CompiledRegex, Arc<Schema>)>>, pub compiled_format: Option<CompiledFormat>,
#[serde(skip)] #[serde(skip)]
pub compiled_registry: Option<Arc<crate::validator::registry::Registry>>, pub compiled_pattern: Option<CompiledRegex>,
#[serde(skip)]
pub compiled_pattern_properties: Option<Vec<(CompiledRegex, Arc<Schema>)>>,
} }
pub enum ResolvedRef<'a> {
Local(&'a Schema),
Global(&'a Schema, &'a Schema),
}
/// Represents a compiled format validator
#[derive(Clone)]
pub enum CompiledFormat {
Func(fn(&serde_json::Value) -> Result<(), Box<dyn std::error::Error + Send + Sync>>),
Regex(regex::Regex),
}
impl std::fmt::Debug for CompiledFormat {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
CompiledFormat::Func(_) => write!(f, "CompiledFormat::Func(...)"),
CompiledFormat::Regex(r) => write!(f, "CompiledFormat::Regex({:?})", r),
}
}
}
/// A wrapper for compiled regex patterns
#[derive(Debug, Clone)]
pub struct CompiledRegex(pub regex::Regex);
#[derive(Debug, Clone, Serialize)] #[derive(Debug, Clone, Serialize)]
pub struct Schema { pub struct Schema {
#[serde(flatten)] #[serde(flatten)]
@ -176,6 +187,129 @@ impl std::ops::DerefMut for Schema {
} }
} }
impl Schema {
pub fn resolve_ref(&self, _ref_string: &str) -> Option<&Arc<Schema>> {
// This is vestigial for now. References are global pointers. We will remove this shortly.
None
}
pub fn compile(&mut self) {
if let Some(format_str) = &self.obj.format {
if let Some(fmt) = crate::database::formats::FORMATS.get(format_str.as_str()) {
self.obj.compiled_format = Some(crate::database::schema::CompiledFormat::Func(fmt.func));
}
}
if let Some(pattern_str) = &self.obj.pattern {
if let Ok(re) = regex::Regex::new(pattern_str) {
self.obj.compiled_pattern = Some(crate::database::schema::CompiledRegex(re));
}
}
if let Some(pattern_props) = &self.obj.pattern_properties {
let mut compiled = Vec::new();
for (k, v) in pattern_props {
if let Ok(re) = regex::Regex::new(k) {
compiled.push((crate::database::schema::CompiledRegex(re), v.clone()));
}
}
if !compiled.is_empty() {
self.obj.compiled_pattern_properties = Some(compiled);
}
}
// Crawl children recursively to compile their internals
if let Some(props) = &mut self.obj.properties {
for (_, v) in props {
// Safe deep mutation workaround without unsafe Arc unwrap
let mut inner = (**v).clone();
inner.compile();
*v = Arc::new(inner);
}
}
if let Some(arr) = &mut self.obj.prefix_items {
for v in arr.iter_mut() {
let mut inner = (**v).clone();
inner.compile();
*v = Arc::new(inner);
}
}
if let Some(arr) = &mut self.obj.all_of {
for v in arr.iter_mut() {
let mut inner = (**v).clone();
inner.compile();
*v = Arc::new(inner);
}
}
if let Some(arr) = &mut self.obj.any_of {
for v in arr.iter_mut() {
let mut inner = (**v).clone();
inner.compile();
*v = Arc::new(inner);
}
}
if let Some(arr) = &mut self.obj.one_of {
for v in arr.iter_mut() {
let mut inner = (**v).clone();
inner.compile();
*v = Arc::new(inner);
}
}
if let Some(v) = &mut self.obj.additional_properties {
let mut inner = (**v).clone();
inner.compile();
*v = Arc::new(inner);
}
if let Some(v) = &mut self.obj.items {
let mut inner = (**v).clone();
inner.compile();
*v = Arc::new(inner);
}
if let Some(v) = &mut self.obj.contains {
let mut inner = (**v).clone();
inner.compile();
*v = Arc::new(inner);
}
if let Some(v) = &mut self.obj.property_names {
let mut inner = (**v).clone();
inner.compile();
*v = Arc::new(inner);
}
if let Some(v) = &mut self.obj.not {
let mut inner = (**v).clone();
inner.compile();
*v = Arc::new(inner);
}
if let Some(v) = &mut self.obj.if_ {
let mut inner = (**v).clone();
inner.compile();
*v = Arc::new(inner);
}
if let Some(v) = &mut self.obj.then_ {
let mut inner = (**v).clone();
inner.compile();
*v = Arc::new(inner);
}
if let Some(v) = &mut self.obj.else_ {
let mut inner = (**v).clone();
inner.compile();
*v = Arc::new(inner);
}
}
}
impl<'de> Deserialize<'de> for Schema { impl<'de> Deserialize<'de> for Schema {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where where

35
src/database/type.rs Normal file
View File

@ -0,0 +1,35 @@
use crate::database::schema::Schema;
use serde::{Deserialize, Serialize};
use serde_json::Value;
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[serde(default)]
pub struct Type {
pub id: String,
pub r#type: String,
pub name: String,
pub module: String,
pub source: String,
#[serde(default)]
pub historical: bool,
#[serde(default)]
pub sensitive: bool,
#[serde(default)]
pub ownable: bool,
pub longevity: Option<i32>,
#[serde(default)]
pub hierarchy: Vec<String>,
pub relationship: Option<bool>,
#[serde(default)]
pub fields: Vec<String>,
pub grouped_fields: Option<Value>,
#[serde(default)]
pub lookup_fields: Vec<String>,
#[serde(default)]
pub null_fields: Vec<String>,
#[serde(default)]
pub default_fields: Vec<String>,
pub field_types: Option<Value>,
#[serde(default)]
pub schemas: Vec<Schema>,
}

29
src/jspg.rs Normal file
View File

@ -0,0 +1,29 @@
use crate::database::Database;
use crate::merger::Merger;
use crate::queryer::Queryer;
use crate::validator::Validator;
use std::sync::Arc;
pub struct Jspg {
pub database: Arc<Database>,
pub validator: Validator,
pub queryer: Queryer,
pub merger: Merger,
}
impl Jspg {
pub fn new(database_val: &serde_json::Value) -> Self {
let database_instance = Database::new(database_val);
let database = Arc::new(database_instance);
let validator = Validator::new(std::sync::Arc::new(database.schemas.clone()));
let queryer = Queryer::new();
let merger = Merger::new();
Self {
database,
validator,
queryer,
merger,
}
}
}

View File

@ -2,7 +2,11 @@ use pgrx::*;
pg_module_magic!(); pg_module_magic!();
pub mod database;
pub mod drop; pub mod drop;
pub mod jspg;
pub mod merger;
pub mod queryer;
pub mod validator; pub mod validator;
use serde_json::json; use serde_json::json;
@ -12,100 +16,38 @@ lazy_static::lazy_static! {
// Global Atomic Swap Container: // Global Atomic Swap Container:
// - RwLock: To protect the SWAP of the Option. // - RwLock: To protect the SWAP of the Option.
// - Option: Because it starts empty. // - Option: Because it starts empty.
// - Arc: Because multiple running threads might hold the OLD validator while we swap. // - Arc: Because multiple running threads might hold the OLD engine while we swap.
// - Validator: It immutably owns the Registry. // - Jspg: The root semantic engine encapsulating the database metadata, validator, queryer, and merger.
static ref GLOBAL_VALIDATOR: RwLock<Option<Arc<validator::Validator>>> = RwLock::new(None); static ref GLOBAL_JSPG: RwLock<Option<Arc<jspg::Jspg>>> = RwLock::new(None);
} }
#[pg_extern(strict)] #[pg_extern(strict)]
pub fn cache_json_schemas(enums: JsonB, types: JsonB, puncs: JsonB) -> JsonB { pub fn jspg_cache_database(database: JsonB) -> JsonB {
// 1 & 2. Build Registry, Families, and Wrap in Validator all in one shot let new_jspg = crate::jspg::Jspg::new(&database.0);
let new_validator = crate::validator::Validator::from_punc_definition( let new_arc = Arc::new(new_jspg);
Some(&enums.0),
Some(&types.0),
Some(&puncs.0),
);
let new_arc = Arc::new(new_validator);
// 3. ATOMIC SWAP // 3. ATOMIC SWAP
{ {
let mut lock = GLOBAL_VALIDATOR.write().unwrap(); let mut lock = GLOBAL_JSPG.write().unwrap();
*lock = Some(new_arc); *lock = Some(new_arc);
} }
let drop = crate::drop::Drop::success(); let drop = crate::drop::Drop::success();
JsonB(serde_json::to_value(drop).unwrap()) JsonB(serde_json::to_value(drop).unwrap())
} }
// `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 mask_json_schema(schema_id: &str, instance: JsonB) -> JsonB {
// 1. Acquire Snapshot
let validator_arc = {
let lock = GLOBAL_VALIDATOR.read().unwrap();
lock.clone()
};
// 2. Validate (Lock-Free)
if let Some(validator) = validator_arc {
// We need a mutable copy of the value to mask it
let mut mutable_instance = instance.0.clone();
match validator.mask(schema_id, &mut mutable_instance) {
Ok(result) => {
// If valid, return the MASKED instance
if result.is_valid() {
let drop = crate::drop::Drop::success_with_val(mutable_instance);
JsonB(serde_json::to_value(drop).unwrap())
} else {
// If invalid, return errors (Schema Validation Errors)
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) => {
// Schema Not Found or other fatal error
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: "JSON Schemas have not been cached yet. Run cache_json_schemas()".to_string(),
details: crate::drop::ErrorDetails {
path: "".to_string(),
},
};
let drop = crate::drop::Drop::with_errors(vec![error]);
JsonB(serde_json::to_value(drop).unwrap())
}
}
#[pg_extern(strict, parallel_safe)] #[pg_extern(strict, parallel_safe)]
pub fn validate_json_schema(schema_id: &str, instance: JsonB) -> JsonB { pub fn validate_json_schema(schema_id: &str, instance: JsonB) -> JsonB {
// 1. Acquire Snapshot // 1. Acquire Snapshot
let validator_arc = { let jspg_arc = {
let lock = GLOBAL_VALIDATOR.read().unwrap(); let lock = GLOBAL_JSPG.read().unwrap();
lock.clone() lock.clone()
}; };
// 2. Validate (Lock-Free) // 2. Validate (Lock-Free)
if let Some(validator) = validator_arc { if let Some(engine) = jspg_arc {
match validator.validate(schema_id, &instance.0) { match engine.validator.validate(schema_id, &instance.0) {
Ok(result) => { Ok(result) => {
if result.is_valid() { if result.is_valid() {
let drop = crate::drop::Drop::success(); let drop = crate::drop::Drop::success();
@ -137,7 +79,7 @@ pub fn validate_json_schema(schema_id: &str, instance: JsonB) -> JsonB {
} else { } else {
let error = crate::drop::Error { let error = crate::drop::Error {
code: "VALIDATOR_NOT_INITIALIZED".to_string(), code: "VALIDATOR_NOT_INITIALIZED".to_string(),
message: "JSON Schemas have not been cached yet. Run cache_json_schemas()".to_string(), message: "The JSPG database has not been cached yet. Run jspg_cache_database()".to_string(),
details: crate::drop::ErrorDetails { details: crate::drop::ErrorDetails {
path: "".to_string(), path: "".to_string(),
}, },
@ -149,8 +91,11 @@ pub fn validate_json_schema(schema_id: &str, instance: JsonB) -> JsonB {
#[pg_extern(strict, parallel_safe)] #[pg_extern(strict, parallel_safe)]
pub fn json_schema_cached(schema_id: &str) -> bool { pub fn json_schema_cached(schema_id: &str) -> bool {
if let Some(validator) = GLOBAL_VALIDATOR.read().unwrap().as_ref() { if let Some(engine) = GLOBAL_JSPG.read().unwrap().as_ref() {
match validator.validate(schema_id, &serde_json::Value::Null) { match engine
.validator
.validate(schema_id, &serde_json::Value::Null)
{
Err(e) if e.code == "SCHEMA_NOT_FOUND" => false, Err(e) if e.code == "SCHEMA_NOT_FOUND" => false,
_ => true, _ => true,
} }
@ -161,7 +106,7 @@ pub fn json_schema_cached(schema_id: &str) -> bool {
#[pg_extern(strict)] #[pg_extern(strict)]
pub fn clear_json_schemas() -> JsonB { pub fn clear_json_schemas() -> JsonB {
let mut lock = GLOBAL_VALIDATOR.write().unwrap(); let mut lock = GLOBAL_JSPG.write().unwrap();
*lock = None; *lock = None;
let drop = crate::drop::Drop::success(); let drop = crate::drop::Drop::success();
JsonB(serde_json::to_value(drop).unwrap()) JsonB(serde_json::to_value(drop).unwrap())
@ -169,8 +114,8 @@ pub fn clear_json_schemas() -> JsonB {
#[pg_extern(strict, parallel_safe)] #[pg_extern(strict, parallel_safe)]
pub fn show_json_schemas() -> JsonB { pub fn show_json_schemas() -> JsonB {
if let Some(validator) = GLOBAL_VALIDATOR.read().unwrap().as_ref() { if let Some(engine) = GLOBAL_JSPG.read().unwrap().as_ref() {
let mut keys = validator.get_schema_ids(); let mut keys = engine.validator.get_schema_ids();
keys.sort(); keys.sort();
let drop = crate::drop::Drop::success_with_val(json!(keys)); let drop = crate::drop::Drop::success_with_val(json!(keys));
JsonB(serde_json::to_value(drop).unwrap()) JsonB(serde_json::to_value(drop).unwrap())

9
src/merger/mod.rs Normal file
View File

@ -0,0 +1,9 @@
pub struct Merger {
// To be implemented
}
impl Merger {
pub fn new() -> Self {
Self {}
}
}

9
src/queryer/mod.rs Normal file
View File

@ -0,0 +1,9 @@
pub struct Queryer {
// To be implemented
}
impl Queryer {
pub fn new() -> Self {
Self {}
}
}

View File

@ -1,28 +1,4 @@
#[pg_test]
fn test_anchor_0() {
let path = format!("{}/tests/fixtures/anchor.json", env!("CARGO_MANIFEST_DIR"));
crate::validator::util::run_test_file_at_index(&path, 0).unwrap();
}
#[pg_test]
fn test_anchor_1() {
let path = format!("{}/tests/fixtures/anchor.json", env!("CARGO_MANIFEST_DIR"));
crate::validator::util::run_test_file_at_index(&path, 1).unwrap();
}
#[pg_test]
fn test_anchor_2() {
let path = format!("{}/tests/fixtures/anchor.json", env!("CARGO_MANIFEST_DIR"));
crate::validator::util::run_test_file_at_index(&path, 2).unwrap();
}
#[pg_test]
fn test_anchor_3() {
let path = format!("{}/tests/fixtures/anchor.json", env!("CARGO_MANIFEST_DIR"));
crate::validator::util::run_test_file_at_index(&path, 3).unwrap();
}
#[pg_test] #[pg_test]
fn test_content_0() { fn test_content_0() {
let path = format!("{}/tests/fixtures/content.json", env!("CARGO_MANIFEST_DIR")); let path = format!("{}/tests/fixtures/content.json", env!("CARGO_MANIFEST_DIR"));
@ -107,54 +83,6 @@ fn test_min_items_2() {
crate::validator::util::run_test_file_at_index(&path, 2).unwrap(); crate::validator::util::run_test_file_at_index(&path, 2).unwrap();
} }
#[pg_test]
fn test_puncs_0() {
let path = format!("{}/tests/fixtures/puncs.json", env!("CARGO_MANIFEST_DIR"));
crate::validator::util::run_test_file_at_index(&path, 0).unwrap();
}
#[pg_test]
fn test_puncs_1() {
let path = format!("{}/tests/fixtures/puncs.json", env!("CARGO_MANIFEST_DIR"));
crate::validator::util::run_test_file_at_index(&path, 1).unwrap();
}
#[pg_test]
fn test_puncs_2() {
let path = format!("{}/tests/fixtures/puncs.json", env!("CARGO_MANIFEST_DIR"));
crate::validator::util::run_test_file_at_index(&path, 2).unwrap();
}
#[pg_test]
fn test_puncs_3() {
let path = format!("{}/tests/fixtures/puncs.json", env!("CARGO_MANIFEST_DIR"));
crate::validator::util::run_test_file_at_index(&path, 3).unwrap();
}
#[pg_test]
fn test_puncs_4() {
let path = format!("{}/tests/fixtures/puncs.json", env!("CARGO_MANIFEST_DIR"));
crate::validator::util::run_test_file_at_index(&path, 4).unwrap();
}
#[pg_test]
fn test_puncs_5() {
let path = format!("{}/tests/fixtures/puncs.json", env!("CARGO_MANIFEST_DIR"));
crate::validator::util::run_test_file_at_index(&path, 5).unwrap();
}
#[pg_test]
fn test_puncs_6() {
let path = format!("{}/tests/fixtures/puncs.json", env!("CARGO_MANIFEST_DIR"));
crate::validator::util::run_test_file_at_index(&path, 6).unwrap();
}
#[pg_test]
fn test_puncs_7() {
let path = format!("{}/tests/fixtures/puncs.json", env!("CARGO_MANIFEST_DIR"));
crate::validator::util::run_test_file_at_index(&path, 7).unwrap();
}
#[pg_test] #[pg_test]
fn test_additional_properties_0() { fn test_additional_properties_0() {
let path = format!("{}/tests/fixtures/additionalProperties.json", env!("CARGO_MANIFEST_DIR")); let path = format!("{}/tests/fixtures/additionalProperties.json", env!("CARGO_MANIFEST_DIR"));
@ -347,6 +275,18 @@ fn test_any_of_9() {
crate::validator::util::run_test_file_at_index(&path, 9).unwrap(); crate::validator::util::run_test_file_at_index(&path, 9).unwrap();
} }
#[pg_test]
fn test_families_0() {
let path = format!("{}/tests/fixtures/families.json", env!("CARGO_MANIFEST_DIR"));
crate::validator::util::run_test_file_at_index(&path, 0).unwrap();
}
#[pg_test]
fn test_families_1() {
let path = format!("{}/tests/fixtures/families.json", env!("CARGO_MANIFEST_DIR"));
crate::validator::util::run_test_file_at_index(&path, 1).unwrap();
}
#[pg_test] #[pg_test]
fn test_property_names_0() { fn test_property_names_0() {
let path = format!("{}/tests/fixtures/propertyNames.json", env!("CARGO_MANIFEST_DIR")); let path = format!("{}/tests/fixtures/propertyNames.json", env!("CARGO_MANIFEST_DIR"));
@ -389,18 +329,6 @@ fn test_property_names_6() {
crate::validator::util::run_test_file_at_index(&path, 6).unwrap(); crate::validator::util::run_test_file_at_index(&path, 6).unwrap();
} }
#[pg_test]
fn test_boolean_schema_0() {
let path = format!("{}/tests/fixtures/boolean_schema.json", env!("CARGO_MANIFEST_DIR"));
crate::validator::util::run_test_file_at_index(&path, 0).unwrap();
}
#[pg_test]
fn test_boolean_schema_1() {
let path = format!("{}/tests/fixtures/boolean_schema.json", env!("CARGO_MANIFEST_DIR"));
crate::validator::util::run_test_file_at_index(&path, 1).unwrap();
}
#[pg_test] #[pg_test]
fn test_not_0() { fn test_not_0() {
let path = format!("{}/tests/fixtures/not.json", env!("CARGO_MANIFEST_DIR")); let path = format!("{}/tests/fixtures/not.json", env!("CARGO_MANIFEST_DIR"));
@ -569,6 +497,30 @@ fn test_items_15() {
crate::validator::util::run_test_file_at_index(&path, 15).unwrap(); crate::validator::util::run_test_file_at_index(&path, 15).unwrap();
} }
#[pg_test]
fn test_typed_refs_0() {
let path = format!("{}/tests/fixtures/typedRefs.json", env!("CARGO_MANIFEST_DIR"));
crate::validator::util::run_test_file_at_index(&path, 0).unwrap();
}
#[pg_test]
fn test_typed_refs_1() {
let path = format!("{}/tests/fixtures/typedRefs.json", env!("CARGO_MANIFEST_DIR"));
crate::validator::util::run_test_file_at_index(&path, 1).unwrap();
}
#[pg_test]
fn test_typed_refs_2() {
let path = format!("{}/tests/fixtures/typedRefs.json", env!("CARGO_MANIFEST_DIR"));
crate::validator::util::run_test_file_at_index(&path, 2).unwrap();
}
#[pg_test]
fn test_typed_refs_3() {
let path = format!("{}/tests/fixtures/typedRefs.json", env!("CARGO_MANIFEST_DIR"));
crate::validator::util::run_test_file_at_index(&path, 3).unwrap();
}
#[pg_test] #[pg_test]
fn test_enum_0() { fn test_enum_0() {
let path = format!("{}/tests/fixtures/enum.json", env!("CARGO_MANIFEST_DIR")); let path = format!("{}/tests/fixtures/enum.json", env!("CARGO_MANIFEST_DIR"));
@ -1013,6 +965,18 @@ fn test_one_of_12() {
crate::validator::util::run_test_file_at_index(&path, 12).unwrap(); crate::validator::util::run_test_file_at_index(&path, 12).unwrap();
} }
#[pg_test]
fn test_boolean_schema_0() {
let path = format!("{}/tests/fixtures/booleanSchema.json", env!("CARGO_MANIFEST_DIR"));
crate::validator::util::run_test_file_at_index(&path, 0).unwrap();
}
#[pg_test]
fn test_boolean_schema_1() {
let path = format!("{}/tests/fixtures/booleanSchema.json", env!("CARGO_MANIFEST_DIR"));
crate::validator::util::run_test_file_at_index(&path, 1).unwrap();
}
#[pg_test] #[pg_test]
fn test_if_then_else_0() { fn test_if_then_else_0() {
let path = format!("{}/tests/fixtures/if-then-else.json", env!("CARGO_MANIFEST_DIR")); let path = format!("{}/tests/fixtures/if-then-else.json", env!("CARGO_MANIFEST_DIR"));
@ -1960,129 +1924,3 @@ fn test_contains_8() {
let path = format!("{}/tests/fixtures/contains.json", env!("CARGO_MANIFEST_DIR")); let path = format!("{}/tests/fixtures/contains.json", env!("CARGO_MANIFEST_DIR"));
crate::validator::util::run_test_file_at_index(&path, 8).unwrap(); crate::validator::util::run_test_file_at_index(&path, 8).unwrap();
} }
#[pg_test]
fn test_dynamic_ref_0() {
let path = format!("{}/tests/fixtures/dynamicRef.json", env!("CARGO_MANIFEST_DIR"));
crate::validator::util::run_test_file_at_index(&path, 0).unwrap();
}
#[pg_test]
fn test_dynamic_ref_1() {
let path = format!("{}/tests/fixtures/dynamicRef.json", env!("CARGO_MANIFEST_DIR"));
crate::validator::util::run_test_file_at_index(&path, 1).unwrap();
}
#[pg_test]
fn test_dynamic_ref_2() {
let path = format!("{}/tests/fixtures/dynamicRef.json", env!("CARGO_MANIFEST_DIR"));
crate::validator::util::run_test_file_at_index(&path, 2).unwrap();
}
#[pg_test]
fn test_dynamic_ref_3() {
let path = format!("{}/tests/fixtures/dynamicRef.json", env!("CARGO_MANIFEST_DIR"));
crate::validator::util::run_test_file_at_index(&path, 3).unwrap();
}
#[pg_test]
fn test_dynamic_ref_4() {
let path = format!("{}/tests/fixtures/dynamicRef.json", env!("CARGO_MANIFEST_DIR"));
crate::validator::util::run_test_file_at_index(&path, 4).unwrap();
}
#[pg_test]
fn test_dynamic_ref_5() {
let path = format!("{}/tests/fixtures/dynamicRef.json", env!("CARGO_MANIFEST_DIR"));
crate::validator::util::run_test_file_at_index(&path, 5).unwrap();
}
#[pg_test]
fn test_dynamic_ref_6() {
let path = format!("{}/tests/fixtures/dynamicRef.json", env!("CARGO_MANIFEST_DIR"));
crate::validator::util::run_test_file_at_index(&path, 6).unwrap();
}
#[pg_test]
fn test_dynamic_ref_7() {
let path = format!("{}/tests/fixtures/dynamicRef.json", env!("CARGO_MANIFEST_DIR"));
crate::validator::util::run_test_file_at_index(&path, 7).unwrap();
}
#[pg_test]
fn test_dynamic_ref_8() {
let path = format!("{}/tests/fixtures/dynamicRef.json", env!("CARGO_MANIFEST_DIR"));
crate::validator::util::run_test_file_at_index(&path, 8).unwrap();
}
#[pg_test]
fn test_dynamic_ref_9() {
let path = format!("{}/tests/fixtures/dynamicRef.json", env!("CARGO_MANIFEST_DIR"));
crate::validator::util::run_test_file_at_index(&path, 9).unwrap();
}
#[pg_test]
fn test_dynamic_ref_10() {
let path = format!("{}/tests/fixtures/dynamicRef.json", env!("CARGO_MANIFEST_DIR"));
crate::validator::util::run_test_file_at_index(&path, 10).unwrap();
}
#[pg_test]
fn test_dynamic_ref_11() {
let path = format!("{}/tests/fixtures/dynamicRef.json", env!("CARGO_MANIFEST_DIR"));
crate::validator::util::run_test_file_at_index(&path, 11).unwrap();
}
#[pg_test]
fn test_dynamic_ref_12() {
let path = format!("{}/tests/fixtures/dynamicRef.json", env!("CARGO_MANIFEST_DIR"));
crate::validator::util::run_test_file_at_index(&path, 12).unwrap();
}
#[pg_test]
fn test_dynamic_ref_13() {
let path = format!("{}/tests/fixtures/dynamicRef.json", env!("CARGO_MANIFEST_DIR"));
crate::validator::util::run_test_file_at_index(&path, 13).unwrap();
}
#[pg_test]
fn test_dynamic_ref_14() {
let path = format!("{}/tests/fixtures/dynamicRef.json", env!("CARGO_MANIFEST_DIR"));
crate::validator::util::run_test_file_at_index(&path, 14).unwrap();
}
#[pg_test]
fn test_dynamic_ref_15() {
let path = format!("{}/tests/fixtures/dynamicRef.json", env!("CARGO_MANIFEST_DIR"));
crate::validator::util::run_test_file_at_index(&path, 15).unwrap();
}
#[pg_test]
fn test_dynamic_ref_16() {
let path = format!("{}/tests/fixtures/dynamicRef.json", env!("CARGO_MANIFEST_DIR"));
crate::validator::util::run_test_file_at_index(&path, 16).unwrap();
}
#[pg_test]
fn test_dynamic_ref_17() {
let path = format!("{}/tests/fixtures/dynamicRef.json", env!("CARGO_MANIFEST_DIR"));
crate::validator::util::run_test_file_at_index(&path, 17).unwrap();
}
#[pg_test]
fn test_dynamic_ref_18() {
let path = format!("{}/tests/fixtures/dynamicRef.json", env!("CARGO_MANIFEST_DIR"));
crate::validator::util::run_test_file_at_index(&path, 18).unwrap();
}
#[pg_test]
fn test_dynamic_ref_19() {
let path = format!("{}/tests/fixtures/dynamicRef.json", env!("CARGO_MANIFEST_DIR"));
crate::validator::util::run_test_file_at_index(&path, 19).unwrap();
}
#[pg_test]
fn test_dynamic_ref_20() {
let path = format!("{}/tests/fixtures/dynamicRef.json", env!("CARGO_MANIFEST_DIR"));
crate::validator::util::run_test_file_at_index(&path, 20).unwrap();
}

View File

@ -1,394 +0,0 @@
use crate::validator::schema::Schema;
use regex::Regex;
use serde_json::Value;
// use std::collections::HashMap;
use std::error::Error;
use std::sync::Arc;
/// Represents a compiled format validator
#[derive(Debug, Clone)]
pub enum CompiledFormat {
/// A simple function pointer validator
Func(fn(&Value) -> Result<(), Box<dyn Error + Send + Sync>>),
/// A regex-based validator
Regex(Regex),
}
/// A wrapper for compiled regex patterns
#[derive(Debug, Clone)]
pub struct CompiledRegex(pub Regex);
/// The Compiler is responsible for pre-calculating high-cost schema operations
pub struct Compiler;
impl Compiler {
/// Internal: Compiles formats and regexes in-place
fn compile_formats_and_regexes(schema: &mut Schema) {
// 1. Compile Format
if let Some(format_str) = &schema.format {
if let Some(fmt) = crate::validator::formats::FORMATS.get(format_str.as_str()) {
schema.compiled_format = Some(CompiledFormat::Func(fmt.func));
}
}
// 2. Compile Pattern (regex)
if let Some(pattern_str) = &schema.pattern {
if let Ok(re) = Regex::new(pattern_str) {
schema.compiled_pattern = Some(CompiledRegex(re));
}
}
// 2.5 Compile Pattern Properties
if let Some(pp) = &schema.pattern_properties {
let mut compiled_pp = Vec::new();
for (pattern, sub_schema) in pp {
if let Ok(re) = Regex::new(pattern) {
compiled_pp.push((CompiledRegex(re), sub_schema.clone()));
} else {
eprintln!(
"Invalid patternProperty regex in schema (compile time): {}",
pattern
);
}
}
if !compiled_pp.is_empty() {
schema.compiled_pattern_properties = Some(compiled_pp);
}
}
// 3. Recurse
Self::compile_recursive(schema);
}
fn normalize_dependencies(schema: &mut Schema) {
if let Some(deps) = schema.dependencies.take() {
for (key, dep) in deps {
match dep {
crate::validator::schema::Dependency::Props(props) => {
schema
.dependent_required
.get_or_insert_with(std::collections::BTreeMap::new)
.insert(key, props);
}
crate::validator::schema::Dependency::Schema(sub_schema) => {
schema
.dependent_schemas
.get_or_insert_with(std::collections::BTreeMap::new)
.insert(key, sub_schema);
}
}
}
}
}
fn compile_recursive(schema: &mut Schema) {
Self::normalize_dependencies(schema);
// Compile self
if let Some(format_str) = &schema.format {
if let Some(fmt) = crate::validator::formats::FORMATS.get(format_str.as_str()) {
schema.compiled_format = Some(CompiledFormat::Func(fmt.func));
}
}
if let Some(pattern_str) = &schema.pattern {
if let Ok(re) = Regex::new(pattern_str) {
schema.compiled_pattern = Some(CompiledRegex(re));
}
}
// Recurse
if let Some(defs) = &mut schema.definitions {
for s in defs.values_mut() {
Self::compile_recursive(Arc::make_mut(s));
}
}
if let Some(defs) = &mut schema.defs {
for s in defs.values_mut() {
Self::compile_recursive(Arc::make_mut(s));
}
}
if let Some(props) = &mut schema.properties {
for s in props.values_mut() {
Self::compile_recursive(Arc::make_mut(s));
}
}
if let Some(add_props) = &mut schema.additional_properties {
Self::compile_recursive(Arc::make_mut(add_props));
}
// ... Recurse logic ...
if let Some(items) = &mut schema.items {
Self::compile_recursive(Arc::make_mut(items));
}
if let Some(prefix_items) = &mut schema.prefix_items {
for s in prefix_items {
Self::compile_recursive(Arc::make_mut(s));
}
}
if let Some(not) = &mut schema.not {
Self::compile_recursive(Arc::make_mut(not));
}
if let Some(all_of) = &mut schema.all_of {
for s in all_of {
Self::compile_recursive(Arc::make_mut(s));
}
}
if let Some(any_of) = &mut schema.any_of {
for s in any_of {
Self::compile_recursive(Arc::make_mut(s));
}
}
if let Some(one_of) = &mut schema.one_of {
for s in one_of {
Self::compile_recursive(Arc::make_mut(s));
}
}
if let Some(s) = &mut schema.if_ {
Self::compile_recursive(Arc::make_mut(s));
}
if let Some(s) = &mut schema.then_ {
Self::compile_recursive(Arc::make_mut(s));
}
if let Some(s) = &mut schema.else_ {
Self::compile_recursive(Arc::make_mut(s));
}
if let Some(ds) = &mut schema.dependent_schemas {
for s in ds.values_mut() {
Self::compile_recursive(Arc::make_mut(s));
}
}
if let Some(pn) = &mut schema.property_names {
Self::compile_recursive(Arc::make_mut(pn));
}
}
/// Recursively traverses the schema tree to build the local registry index.
fn compile_index(
schema: &Arc<Schema>,
registry: &mut crate::validator::registry::Registry,
parent_base: Option<String>,
pointer: json_pointer::JsonPointer<String, Vec<String>>,
) {
// 1. Index using Parent Base (Path from Parent)
if let Some(base) = &parent_base {
// We use the pointer's string representation (e.g., "/definitions/foo")
// and append it to the base.
let fragment = pointer.to_string();
let ptr_uri = if fragment.is_empty() {
base.clone()
} else {
format!("{}#{}", base, fragment)
};
registry.insert(ptr_uri, schema.clone());
}
// 2. Determine Current Scope... (unchanged logic)
let mut current_base = parent_base.clone();
let mut child_pointer = pointer.clone();
if let Some(id) = &schema.obj.id {
let mut new_base = None;
if let Ok(_) = url::Url::parse(id) {
new_base = Some(id.clone());
} else if let Some(base) = &current_base {
if let Ok(base_url) = url::Url::parse(base) {
if let Ok(joined) = base_url.join(id) {
new_base = Some(joined.to_string());
}
}
} else {
new_base = Some(id.clone());
}
if let Some(base) = new_base {
// println!("DEBUG: Compiling index for path: {}", base); // Added println
registry.insert(base.clone(), schema.clone());
current_base = Some(base);
child_pointer = json_pointer::JsonPointer::new(vec![]); // Reset
}
}
// 3. Index by Anchor
if let Some(anchor) = &schema.obj.anchor {
if let Some(base) = &current_base {
let anchor_uri = format!("{}#{}", base, anchor);
registry.insert(anchor_uri, schema.clone());
}
}
// Index by Dynamic Anchor
if let Some(d_anchor) = &schema.obj.dynamic_anchor {
if let Some(base) = &current_base {
let anchor_uri = format!("{}#{}", base, d_anchor);
registry.insert(anchor_uri, schema.clone());
}
}
// 4. Recurse (unchanged logic structure, just passing registry)
if let Some(defs) = schema.defs.as_ref().or(schema.definitions.as_ref()) {
let segment = if schema.defs.is_some() {
"$defs"
} else {
"definitions"
};
for (key, sub_schema) in defs {
let mut sub = child_pointer.clone();
sub.push(segment.to_string());
let decoded_key = percent_encoding::percent_decode_str(key).decode_utf8_lossy();
sub.push(decoded_key.to_string());
Self::compile_index(sub_schema, registry, current_base.clone(), sub);
}
}
if let Some(props) = &schema.properties {
for (key, sub_schema) in props {
let mut sub = child_pointer.clone();
sub.push("properties".to_string());
sub.push(key.to_string());
Self::compile_index(sub_schema, registry, current_base.clone(), sub);
}
}
if let Some(items) = &schema.items {
let mut sub = child_pointer.clone();
sub.push("items".to_string());
Self::compile_index(items, registry, current_base.clone(), sub);
}
if let Some(prefix_items) = &schema.prefix_items {
for (i, sub_schema) in prefix_items.iter().enumerate() {
let mut sub = child_pointer.clone();
sub.push("prefixItems".to_string());
sub.push(i.to_string());
Self::compile_index(sub_schema, registry, current_base.clone(), sub);
}
}
if let Some(all_of) = &schema.all_of {
for (i, sub_schema) in all_of.iter().enumerate() {
let mut sub = child_pointer.clone();
sub.push("allOf".to_string());
sub.push(i.to_string());
Self::compile_index(sub_schema, registry, current_base.clone(), sub);
}
}
if let Some(any_of) = &schema.any_of {
for (i, sub_schema) in any_of.iter().enumerate() {
let mut sub = child_pointer.clone();
sub.push("anyOf".to_string());
sub.push(i.to_string());
Self::compile_index(sub_schema, registry, current_base.clone(), sub);
}
}
if let Some(one_of) = &schema.one_of {
for (i, sub_schema) in one_of.iter().enumerate() {
let mut sub = child_pointer.clone();
sub.push("oneOf".to_string());
sub.push(i.to_string());
Self::compile_index(sub_schema, registry, current_base.clone(), sub);
}
}
if let Some(not) = &schema.not {
let mut sub = child_pointer.clone();
sub.push("not".to_string());
Self::compile_index(not, registry, current_base.clone(), sub);
}
if let Some(if_) = &schema.if_ {
let mut sub = child_pointer.clone();
sub.push("if".to_string());
Self::compile_index(if_, registry, current_base.clone(), sub);
}
if let Some(then_) = &schema.then_ {
let mut sub = child_pointer.clone();
sub.push("then".to_string());
Self::compile_index(then_, registry, current_base.clone(), sub);
}
if let Some(else_) = &schema.else_ {
let mut sub = child_pointer.clone();
sub.push("else".to_string());
Self::compile_index(else_, registry, current_base.clone(), sub);
}
if let Some(deps) = &schema.dependent_schemas {
for (key, sub_schema) in deps {
let mut sub = child_pointer.clone();
sub.push("dependentSchemas".to_string());
sub.push(key.to_string());
Self::compile_index(sub_schema, registry, current_base.clone(), sub);
}
}
if let Some(pp) = &schema.pattern_properties {
for (key, sub_schema) in pp {
let mut sub = child_pointer.clone();
sub.push("patternProperties".to_string());
sub.push(key.to_string());
Self::compile_index(sub_schema, registry, current_base.clone(), sub);
}
}
if let Some(add_props) = &schema.additional_properties {
let mut sub = child_pointer.clone();
sub.push("additionalProperties".to_string());
Self::compile_index(add_props, registry, current_base.clone(), sub);
}
if let Some(contains) = &schema.contains {
let mut sub = child_pointer.clone();
sub.push("contains".to_string());
Self::compile_index(contains, registry, current_base.clone(), sub);
}
if let Some(property_names) = &schema.property_names {
let mut sub = child_pointer.clone();
sub.push("propertyNames".to_string());
Self::compile_index(property_names, registry, current_base.clone(), sub);
}
}
pub fn compile(mut root_schema: Schema, root_id: Option<String>) -> Arc<Schema> {
// 1. Compile in-place (formats/regexes/normalization)
Self::compile_formats_and_regexes(&mut root_schema);
// Apply root_id override if schema ID is missing
if let Some(rid) = &root_id {
if root_schema.obj.id.is_none() {
root_schema.obj.id = Some(rid.clone());
}
}
// 2. Build ID/Pointer Index
let mut registry = crate::validator::registry::Registry::new();
// We need a temporary Arc to satisfy compile_index recursion
// But we are modifying root_schema.
// This is tricky. compile_index takes &Arc<Schema>.
// We should build the index first, THEN attach it.
let root = Arc::new(root_schema);
// Default base_uri to ""
let base_uri = root_id
.clone()
.or_else(|| root.obj.id.clone())
.or(Some("".to_string()));
Self::compile_index(
&root,
&mut registry,
base_uri,
json_pointer::JsonPointer::new(vec![]),
);
// Also ensure root id is indexed if present
if let Some(rid) = root_id {
registry.insert(rid, root.clone());
}
// Now we need to attach this registry to the root schema.
// Since root is an Arc, we might need to recreate it if we can't mutate.
// Schema struct modifications require &mut.
let mut final_schema = Arc::try_unwrap(root).unwrap_or_else(|arc| (*arc).clone());
final_schema.obj.compiled_registry = Some(Arc::new(registry));
Arc::new(final_schema)
}
}

View File

@ -1,44 +1,60 @@
use crate::validator::schema::Schema; use crate::database::schema::Schema;
use crate::validator::Validator;
use crate::validator::error::ValidationError; use crate::validator::error::ValidationError;
use crate::validator::instance::ValidationInstance;
use crate::validator::result::ValidationResult; use crate::validator::result::ValidationResult;
use std::collections::HashSet;
pub struct ValidationContext<'a, I: ValidationInstance<'a>> { pub struct ValidationContext<'a> {
pub validator: &'a Validator, pub schemas: &'a std::collections::HashMap<String, Schema>,
pub root: &'a Schema, pub root: &'a Schema,
pub schema: &'a Schema, pub schema: &'a Schema,
pub instance: I, pub instance: &'a serde_json::Value,
pub path: String, pub path: String,
pub depth: usize, pub depth: usize,
pub scope: Vec<String>,
pub overrides: HashSet<String>,
pub extensible: bool, pub extensible: bool,
pub reporter: bool, pub reporter: bool,
} }
impl<'a, I: ValidationInstance<'a>> ValidationContext<'a, I> { impl<'a> ValidationContext<'a> {
pub fn resolve_ref(
&self,
ref_string: &str,
) -> Option<(crate::database::schema::ResolvedRef<'a>, String)> {
if let Some(local_schema_arc) = self.root.resolve_ref(ref_string) {
if ref_string.starts_with('#') {
return Some((
crate::database::schema::ResolvedRef::Local(local_schema_arc.as_ref()),
ref_string.to_string(),
));
}
}
// We will replace all of this with `self.schema.compiled_ref` heavily shortly.
// For now, doing a basic map lookup to pass compilation.
if let Some(s) = self.schemas.get(ref_string) {
return Some((
crate::database::schema::ResolvedRef::Global(s, s),
ref_string.to_string(),
));
}
None
}
pub fn new( pub fn new(
validator: &'a Validator, schemas: &'a std::collections::HashMap<String, Schema>,
root: &'a Schema, root: &'a Schema,
schema: &'a Schema, schema: &'a Schema,
instance: I, instance: &'a serde_json::Value,
scope: Vec<String>,
overrides: HashSet<String>,
extensible: bool, extensible: bool,
reporter: bool, reporter: bool,
) -> Self { ) -> Self {
let effective_extensible = schema.extensible.unwrap_or(extensible); let effective_extensible = schema.extensible.unwrap_or(extensible);
Self { Self {
validator, schemas,
root, root,
schema, schema,
instance, instance,
path: String::new(), path: String::new(),
depth: 0, depth: 0,
scope,
overrides,
extensible: effective_extensible, extensible: effective_extensible,
reporter, reporter,
} }
@ -47,72 +63,30 @@ impl<'a, I: ValidationInstance<'a>> ValidationContext<'a, I> {
pub fn derive( pub fn derive(
&self, &self,
schema: &'a Schema, schema: &'a Schema,
instance: I, instance: &'a serde_json::Value,
path: &str, path: &str,
scope: Vec<String>,
overrides: HashSet<String>,
extensible: bool, extensible: bool,
reporter: bool, reporter: bool,
) -> Self { ) -> Self {
let effective_extensible = schema.extensible.unwrap_or(extensible); let effective_extensible = schema.extensible.unwrap_or(extensible);
Self { Self {
validator: self.validator, schemas: self.schemas,
root: self.root, root: self.root,
schema, schema,
instance, instance,
path: path.to_string(), path: path.to_string(),
depth: self.depth + 1, depth: self.depth + 1,
scope,
overrides,
extensible: effective_extensible, extensible: effective_extensible,
reporter, reporter,
} }
} }
pub fn derive_for_schema(&self, schema: &'a Schema, reporter: bool) -> Self { pub fn derive_for_schema(&self, schema: &'a Schema, reporter: bool) -> Self {
self.derive( self.derive(schema, self.instance, &self.path, self.extensible, reporter)
schema,
self.instance,
&self.path,
self.scope.clone(),
HashSet::new(),
self.extensible,
reporter,
)
} }
pub fn validate(&self) -> Result<ValidationResult, ValidationError> { pub fn validate(&self) -> Result<ValidationResult, ValidationError> {
let mut effective_scope = self.scope.clone();
if let Some(id) = &self.schema.obj.id {
let current_base = self.scope.last().map(|s| s.as_str()).unwrap_or("");
let mut new_base = id.clone().to_string();
if !current_base.is_empty() {
if let Ok(base_url) = url::Url::parse(current_base) {
if let Ok(joined) = base_url.join(id) {
new_base = joined.to_string();
}
}
}
effective_scope.push(new_base);
let shadow = ValidationContext {
validator: self.validator,
root: self.root,
schema: self.schema,
instance: self.instance,
path: self.path.clone(),
depth: self.depth,
scope: effective_scope,
overrides: self.overrides.clone(),
extensible: self.extensible,
reporter: self.reporter,
};
return shadow.validate_scoped();
}
self.validate_scoped() self.validate_scoped()
} }
} }

View File

@ -1,101 +1,29 @@
pub mod compiler;
pub mod context; pub mod context;
pub mod error; pub mod error;
pub mod formats;
pub mod instance;
pub mod registry;
pub mod result; pub mod result;
pub mod rules; pub mod rules;
pub mod schema;
pub mod util; pub mod util;
pub use context::ValidationContext; pub use context::ValidationContext;
pub use error::ValidationError; pub use error::ValidationError;
pub use instance::{MutableInstance, ReadOnlyInstance};
pub use result::ValidationResult; pub use result::ValidationResult;
use crate::validator::registry::Registry;
use crate::validator::schema::Schema;
use serde_json::Value; use serde_json::Value;
use std::collections::HashSet; use std::collections::HashSet;
use std::sync::Arc;
pub enum ResolvedRef<'a> {
Local(&'a Schema),
Global(&'a Schema, &'a Schema),
}
pub struct Validator { pub struct Validator {
pub registry: Registry, pub schemas: std::sync::Arc<std::collections::HashMap<String, crate::database::schema::Schema>>,
pub families: std::collections::HashMap<String, Arc<Schema>>,
} }
impl Validator { impl Validator {
pub fn from_punc_definition( pub fn new(
enums: Option<&Value>, schemas: std::sync::Arc<std::collections::HashMap<String, crate::database::schema::Schema>>,
types: Option<&Value>,
puncs: Option<&Value>,
) -> Self { ) -> Self {
let mut registry = Registry::new(); Self { schemas }
let mut families = std::collections::HashMap::new();
let mut family_map: std::collections::HashMap<String, std::collections::HashSet<String>> =
std::collections::HashMap::new();
if let Some(Value::Array(arr)) = types {
for item in arr {
if let Some(name) = item.get("name").and_then(|v| v.as_str()) {
if let Some(hierarchy) = item.get("hierarchy").and_then(|v| v.as_array()) {
for ancestor in hierarchy {
if let Some(anc_str) = ancestor.as_str() {
family_map
.entry(anc_str.to_string())
.or_default()
.insert(name.to_string());
}
}
}
}
}
}
for (family_name, members) in family_map {
let object_refs: Vec<Value> = members
.iter()
.map(|s| serde_json::json!({ "$ref": s }))
.collect();
let schema_json = serde_json::json!({
"oneOf": object_refs
});
if let Ok(schema) = serde_json::from_value::<Schema>(schema_json) {
let compiled = crate::validator::compiler::Compiler::compile(schema, None);
families.insert(family_name, compiled);
}
}
let mut cache_items = |items_val: Option<&Value>| {
if let Some(Value::Array(arr)) = items_val {
for item in arr {
if let Some(Value::Array(schemas)) = item.get("schemas") {
for schema_val in schemas {
if let Ok(schema) = serde_json::from_value::<Schema>(schema_val.clone()) {
registry.add(schema);
}
}
}
}
}
};
cache_items(enums);
cache_items(types);
cache_items(puncs);
Self { registry, families }
} }
pub fn get_schema_ids(&self) -> Vec<String> { pub fn get_schema_ids(&self) -> Vec<String> {
self.registry.schemas.keys().cloned().collect() self.schemas.keys().cloned().collect()
} }
pub fn check_type(t: &str, val: &Value) -> bool { pub fn check_type(t: &str, val: &Value) -> bool {
@ -116,148 +44,14 @@ impl Validator {
} }
} }
pub fn resolve_ref<'a>(
&'a self,
root: &'a Schema,
ref_string: &str,
scope: &str,
) -> Option<(ResolvedRef<'a>, String)> {
if ref_string.starts_with('#') {
if let Some(indexrs) = &root.obj.compiled_registry {
if let Some(s) = indexrs.schemas.get(ref_string) {
return Some((ResolvedRef::Local(s.as_ref()), ref_string.to_string()));
}
}
}
if let Ok(base) = url::Url::parse(scope) {
if let Ok(joined) = base.join(ref_string) {
let joined_str = joined.to_string();
if let Some(indexrs) = &root.obj.compiled_registry {
if let Some(s) = indexrs.schemas.get(&joined_str) {
return Some((ResolvedRef::Local(s.as_ref() as &Schema), joined_str));
}
}
if let Ok(decoded) = percent_encoding::percent_decode_str(&joined_str).decode_utf8() {
let decoded_str = decoded.to_string();
if decoded_str != joined_str {
if let Some(indexrs) = &root.obj.compiled_registry {
if let Some(s) = indexrs.schemas.get(&decoded_str) {
return Some((ResolvedRef::Local(s.as_ref() as &Schema), decoded_str));
}
}
}
}
if let Some(s) = self.registry.schemas.get(&joined_str) {
return Some((ResolvedRef::Global(s.as_ref(), s.as_ref()), joined_str));
}
}
} else {
if ref_string.starts_with('#') {
let joined_str = format!("{}{}", scope, ref_string);
if let Some(indexrs) = &root.obj.compiled_registry {
if let Some(s) = indexrs.schemas.get(&joined_str) {
return Some((ResolvedRef::Local(s.as_ref() as &Schema), joined_str));
}
}
if let Ok(decoded) = percent_encoding::percent_decode_str(&joined_str).decode_utf8() {
let decoded_str = decoded.to_string();
if decoded_str != joined_str {
if let Some(indexrs) = &root.obj.compiled_registry {
if let Some(s) = indexrs.schemas.get(&decoded_str) {
return Some((ResolvedRef::Local(s.as_ref() as &Schema), decoded_str));
}
}
}
}
if let Some(s) = self.registry.schemas.get(&joined_str) {
return Some((ResolvedRef::Global(s.as_ref(), s.as_ref()), joined_str));
}
}
}
if let Ok(parsed) = url::Url::parse(ref_string) {
let absolute = parsed.to_string();
if let Some(indexrs) = &root.obj.compiled_registry {
if let Some(s) = indexrs.schemas.get(&absolute) {
return Some((ResolvedRef::Local(s.as_ref()), absolute));
}
}
let resource_base = if let Some((base, _)) = absolute.split_once('#') {
base
} else {
&absolute
};
if let Some(compiled) = self.registry.schemas.get(resource_base) {
if let Some(indexrs) = &compiled.obj.compiled_registry {
if let Some(s) = indexrs.schemas.get(&absolute) {
return Some((ResolvedRef::Global(compiled.as_ref(), s.as_ref()), absolute));
}
}
}
}
if let Some(compiled) = self.registry.schemas.get(ref_string) {
return Some((
ResolvedRef::Global(compiled.as_ref(), compiled.as_ref()),
ref_string.to_string(),
));
}
None
}
pub fn validate( pub fn validate(
&self, &self,
schema_id: &str, schema_id: &str,
instance: &Value, instance: &Value,
) -> Result<ValidationResult, ValidationError> { ) -> Result<ValidationResult, ValidationError> {
if let Some(schema) = self.registry.schemas.get(schema_id) { if let Some(schema) = self.schemas.get(schema_id) {
let ctx = ValidationContext::new( let ctx = ValidationContext::new(&self.schemas, schema, schema, instance, false, false);
self, ctx.validate_scoped()
schema,
schema,
ReadOnlyInstance(instance),
vec![],
HashSet::new(),
false,
false,
);
ctx.validate()
} else {
Err(ValidationError {
code: "SCHEMA_NOT_FOUND".to_string(),
message: format!("Schema {} not found", schema_id),
path: "".to_string(),
})
}
}
pub fn mask(
&self,
schema_id: &str,
instance: &mut Value,
) -> Result<ValidationResult, ValidationError> {
if let Some(schema) = self.registry.schemas.get(schema_id) {
let ctx = ValidationContext::new(
self,
schema,
schema,
MutableInstance::new(instance),
vec![],
HashSet::new(),
false,
false,
);
let res = ctx.validate()?;
Ok(res)
} else { } else {
Err(ValidationError { Err(ValidationError {
code: "SCHEMA_NOT_FOUND".to_string(), code: "SCHEMA_NOT_FOUND".to_string(),

View File

@ -1,50 +0,0 @@
use crate::validator::schema::Schema;
use lazy_static::lazy_static;
use std::collections::HashMap;
use std::sync::RwLock;
lazy_static! {
pub static ref REGISTRY: RwLock<Registry> = RwLock::new(Registry::new());
}
use std::sync::Arc;
#[derive(Debug, Clone, Default)]
pub struct Registry {
pub schemas: HashMap<String, Arc<Schema>>,
}
impl Registry {
pub fn new() -> Self {
Registry {
schemas: HashMap::new(),
}
}
pub fn add(&mut self, schema: crate::validator::schema::Schema) {
let id = schema
.obj
.id
.clone()
.expect("Schema must have an $id to be registered");
let compiled = crate::validator::compiler::Compiler::compile(schema, Some(id.clone()));
self.schemas.insert(id, compiled);
}
pub fn insert(&mut self, id: String, schema: Arc<Schema>) {
// We allow overwriting for now to support re-compilation in tests/dev
self.schemas.insert(id, schema);
}
pub fn get(&self, id: &str) -> Option<Arc<Schema>> {
self.schemas.get(id).cloned()
}
pub fn clear(&mut self) {
self.schemas.clear();
}
pub fn len(&self) -> usize {
self.schemas.len()
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,119 @@
use serde_json::Value;
use std::collections::HashSet;
use crate::validator::context::ValidationContext;
use crate::validator::error::ValidationError;
use crate::validator::result::ValidationResult;
impl<'a> ValidationContext<'a> {
pub(crate) fn validate_array(
&self,
result: &mut ValidationResult,
) -> Result<bool, ValidationError> {
let current = self.instance;
if let Some(arr) = current.as_array() {
if let Some(min) = self.schema.min_items {
if (arr.len() as f64) < min {
result.errors.push(ValidationError {
code: "MIN_ITEMS".to_string(),
message: "Too few items".to_string(),
path: self.path.to_string(),
});
}
}
if let Some(max) = self.schema.max_items {
if (arr.len() as f64) > max {
result.errors.push(ValidationError {
code: "MAX_ITEMS".to_string(),
message: "Too many items".to_string(),
path: self.path.to_string(),
});
}
}
if self.schema.unique_items.unwrap_or(false) {
let mut seen: Vec<&Value> = Vec::new();
for item in arr {
if seen.contains(&item) {
result.errors.push(ValidationError {
code: "UNIQUE_ITEMS_VIOLATED".to_string(),
message: "Array has duplicate items".to_string(),
path: self.path.to_string(),
});
break;
}
seen.push(item);
}
}
if let Some(ref contains_schema) = self.schema.contains {
let mut _match_count = 0;
for (i, child_instance) in arr.iter().enumerate() {
let derived = self.derive(
contains_schema,
child_instance,
&self.path,
self.extensible,
false,
);
let check = derived.validate()?;
if check.is_valid() {
_match_count += 1;
result.evaluated_indices.insert(i);
}
}
let min = self.schema.min_contains.unwrap_or(1.0) as usize;
if _match_count < min {
result.errors.push(ValidationError {
code: "CONTAINS_VIOLATED".to_string(),
message: format!("Contains matches {} < min {}", _match_count, min),
path: self.path.to_string(),
});
}
if let Some(max) = self.schema.max_contains {
if _match_count > max as usize {
result.errors.push(ValidationError {
code: "CONTAINS_VIOLATED".to_string(),
message: format!("Contains matches {} > max {}", _match_count, max),
path: self.path.to_string(),
});
}
}
}
let len = arr.len();
let mut validation_index = 0;
if let Some(ref prefix) = self.schema.prefix_items {
for (i, sub_schema) in prefix.iter().enumerate() {
if i < len {
let path = format!("{}/{}", self.path, i);
if let Some(child_instance) = arr.get(i) {
let derived = self.derive(sub_schema, child_instance, &path, self.extensible, false);
let item_res = derived.validate()?;
result.merge(item_res);
result.evaluated_indices.insert(i);
validation_index += 1;
}
}
}
}
if let Some(ref items_schema) = self.schema.items {
for i in validation_index..len {
let path = format!("{}/{}", self.path, i);
if let Some(child_instance) = arr.get(i) {
let derived = self.derive(items_schema, child_instance, &path, self.extensible, false);
let item_res = derived.validate()?;
result.merge(item_res);
result.evaluated_indices.insert(i);
}
}
}
}
Ok(true)
}
}

View File

@ -0,0 +1,83 @@
use crate::validator::context::ValidationContext;
use crate::validator::error::ValidationError;
use crate::validator::result::ValidationResult;
impl<'a> ValidationContext<'a> {
pub(crate) fn validate_combinators(
&self,
result: &mut ValidationResult,
) -> Result<bool, ValidationError> {
if let Some(ref all_of) = self.schema.all_of {
for sub in all_of {
let derived = self.derive_for_schema(sub, true);
let res = derived.validate()?;
result.merge(res);
}
}
if let Some(ref any_of) = self.schema.any_of {
let mut valid = false;
for sub in any_of {
let derived = self.derive_for_schema(sub, true);
let sub_res = derived.validate()?;
if sub_res.is_valid() {
valid = true;
result.merge(sub_res);
}
}
if !valid {
result.errors.push(ValidationError {
code: "ANY_OF_VIOLATED".to_string(),
message: "Matches none of anyOf schemas".to_string(),
path: self.path.to_string(),
});
}
}
if let Some(ref one_of) = self.schema.one_of {
let mut valid_count = 0;
let mut valid_res = ValidationResult::new();
for sub in one_of {
let derived = self.derive_for_schema(sub, true);
let sub_res = derived.validate()?;
if sub_res.is_valid() {
valid_count += 1;
valid_res = sub_res;
}
}
if valid_count == 1 {
result.merge(valid_res);
} else if valid_count == 0 {
result.errors.push(ValidationError {
code: "ONE_OF_VIOLATED".to_string(),
message: "Matches none of oneOf schemas".to_string(),
path: self.path.to_string(),
});
} else {
result.errors.push(ValidationError {
code: "ONE_OF_VIOLATED".to_string(),
message: format!("Matches {} of oneOf schemas (expected 1)", valid_count),
path: self.path.to_string(),
});
}
}
if let Some(ref not_schema) = self.schema.not {
let derived = self.derive_for_schema(not_schema, true);
let sub_res = derived.validate()?;
if sub_res.is_valid() {
result.errors.push(ValidationError {
code: "NOT_VIOLATED".to_string(),
message: "Matched 'not' schema".to_string(),
path: self.path.to_string(),
});
}
}
Ok(true)
}
}

View File

@ -0,0 +1,69 @@
use crate::validator::context::ValidationContext;
use crate::validator::error::ValidationError;
use crate::validator::result::ValidationResult;
impl<'a> ValidationContext<'a> {
pub(crate) fn validate_conditionals(
&self,
result: &mut ValidationResult,
) -> Result<bool, ValidationError> {
if let Some(ref if_schema) = self.schema.if_ {
let derived_if = self.derive_for_schema(if_schema, true);
let if_res = derived_if.validate()?;
result.evaluated_keys.extend(if_res.evaluated_keys.clone());
result
.evaluated_indices
.extend(if_res.evaluated_indices.clone());
if if_res.is_valid() {
if let Some(ref then_schema) = self.schema.then_ {
let derived_then = self.derive_for_schema(then_schema, true);
result.merge(derived_then.validate()?);
}
} else {
if let Some(ref else_schema) = self.schema.else_ {
let derived_else = self.derive_for_schema(else_schema, true);
result.merge(derived_else.validate()?);
}
}
}
Ok(true)
}
pub(crate) fn validate_strictness(
&self,
result: &mut ValidationResult,
) -> Result<bool, ValidationError> {
if self.extensible || self.reporter {
return Ok(true);
}
if let Some(obj) = self.instance.as_object() {
for key in obj.keys() {
if !result.evaluated_keys.contains(key) {
result.errors.push(ValidationError {
code: "STRICT_PROPERTY_VIOLATION".to_string(),
message: format!("Unexpected property '{}'", key),
path: format!("{}/{}", self.path, key),
});
}
}
}
if let Some(arr) = self.instance.as_array() {
for i in 0..arr.len() {
if !result.evaluated_indices.contains(&i) {
result.errors.push(ValidationError {
code: "STRICT_ITEM_VIOLATION".to_string(),
message: format!("Unexpected item at index {}", i),
path: format!("{}/{}", self.path, i),
});
}
}
}
Ok(true)
}
}

View File

@ -0,0 +1,84 @@
use crate::validator::Validator;
use crate::validator::context::ValidationContext;
use crate::validator::error::ValidationError;
use crate::validator::result::ValidationResult;
impl<'a> ValidationContext<'a> {
pub(crate) fn validate_core(
&self,
result: &mut ValidationResult,
) -> Result<bool, ValidationError> {
let current = self.instance;
if let Some(ref type_) = self.schema.type_ {
match type_ {
crate::database::schema::SchemaTypeOrArray::Single(t) => {
if !Validator::check_type(t, current) {
result.errors.push(ValidationError {
code: "INVALID_TYPE".to_string(),
message: format!("Expected type '{}'", t),
path: self.path.to_string(),
});
}
}
crate::database::schema::SchemaTypeOrArray::Multiple(types) => {
let mut valid = false;
for t in types {
if Validator::check_type(t, current) {
valid = true;
break;
}
}
if !valid {
result.errors.push(ValidationError {
code: "INVALID_TYPE".to_string(),
message: format!("Expected one of types {:?}", types),
path: self.path.to_string(),
});
}
}
}
}
if let Some(ref const_val) = self.schema.const_ {
if !crate::validator::util::equals(current, const_val) {
result.errors.push(ValidationError {
code: "CONST_VIOLATED".to_string(),
message: "Value does not match const".to_string(),
path: self.path.to_string(),
});
} else {
if let Some(obj) = current.as_object() {
result.evaluated_keys.extend(obj.keys().cloned());
} else if let Some(arr) = current.as_array() {
result.evaluated_indices.extend(0..arr.len());
}
}
}
if let Some(ref enum_vals) = self.schema.enum_ {
let mut found = false;
for val in enum_vals {
if crate::validator::util::equals(current, val) {
found = true;
break;
}
}
if !found {
result.errors.push(ValidationError {
code: "ENUM_MISMATCH".to_string(),
message: "Value is not in enum".to_string(),
path: self.path.to_string(),
});
} else {
if let Some(obj) = current.as_object() {
result.evaluated_keys.extend(obj.keys().cloned());
} else if let Some(arr) = current.as_array() {
result.evaluated_indices.extend(0..arr.len());
}
}
}
Ok(true)
}
}

View File

@ -0,0 +1,44 @@
use crate::validator::context::ValidationContext;
use crate::validator::error::ValidationError;
use crate::validator::result::ValidationResult;
impl<'a> ValidationContext<'a> {
pub(crate) fn validate_format(
&self,
result: &mut ValidationResult,
) -> Result<bool, ValidationError> {
let current = self.instance;
if let Some(ref compiled_fmt) = self.schema.compiled_format {
match compiled_fmt {
crate::database::schema::CompiledFormat::Func(f) => {
let should = if let Some(s) = current.as_str() {
!s.is_empty()
} else {
true
};
if should {
if let Err(e) = f(current) {
result.errors.push(ValidationError {
code: "FORMAT_MISMATCH".to_string(),
message: format!("Format error: {}", e),
path: self.path.to_string(),
});
}
}
}
crate::database::schema::CompiledFormat::Regex(re) => {
if let Some(s) = current.as_str() {
if !re.is_match(s) {
result.errors.push(ValidationError {
code: "FORMAT_MISMATCH".to_string(),
message: "Format regex mismatch".to_string(),
path: self.path.to_string(),
});
}
}
}
}
}
Ok(true)
}
}

View File

@ -0,0 +1,93 @@
use serde_json::Value;
use std::collections::HashSet;
use crate::validator::context::ValidationContext;
use crate::validator::error::ValidationError;
use crate::validator::result::ValidationResult;
pub mod array;
pub mod combinators;
pub mod conditionals;
pub mod core;
pub mod format;
pub mod numeric;
pub mod object;
pub mod polymorphism;
pub mod string;
impl<'a> ValidationContext<'a> {
pub(crate) fn validate_scoped(&self) -> Result<ValidationResult, ValidationError> {
let mut result = ValidationResult::new();
// Structural Limits
if !self.validate_depth(&mut result)? {
return Ok(result);
}
if !self.validate_always_fail(&mut result)? {
return Ok(result);
}
if !self.validate_family(&mut result)? {
return Ok(result);
}
if !self.validate_refs(&mut result)? {
return Ok(result);
}
// Core Type Constraints
self.validate_core(&mut result)?;
self.validate_numeric(&mut result)?;
self.validate_string(&mut result)?;
self.validate_format(&mut result)?;
// Complex Structures
self.validate_object(&mut result)?;
self.validate_array(&mut result)?;
// Multipliers & Conditionals
self.validate_combinators(&mut result)?;
self.validate_conditionals(&mut result)?;
// State Tracking
self.validate_extensible(&mut result)?;
self.validate_strictness(&mut result)?;
Ok(result)
}
fn validate_depth(&self, _result: &mut ValidationResult) -> Result<bool, ValidationError> {
if self.depth > 100 {
Err(ValidationError {
code: "RECURSION_LIMIT_EXCEEDED".to_string(),
message: "Recursion limit exceeded".to_string(),
path: self.path.to_string(),
})
} else {
Ok(true)
}
}
fn validate_always_fail(&self, result: &mut ValidationResult) -> Result<bool, ValidationError> {
if self.schema.always_fail {
result.errors.push(ValidationError {
code: "FALSE_SCHEMA".to_string(),
message: "Schema is false".to_string(),
path: self.path.to_string(),
});
// Short-circuit
Ok(false)
} else {
Ok(true)
}
}
fn validate_extensible(&self, result: &mut ValidationResult) -> Result<bool, ValidationError> {
if self.extensible {
if let Some(obj) = self.instance.as_object() {
result.evaluated_keys.extend(obj.keys().cloned());
} else if let Some(arr) = self.instance.as_array() {
result.evaluated_indices.extend(0..arr.len());
}
}
Ok(true)
}
}

View File

@ -0,0 +1,61 @@
use crate::validator::context::ValidationContext;
use crate::validator::error::ValidationError;
use crate::validator::result::ValidationResult;
impl<'a> ValidationContext<'a> {
pub(crate) fn validate_numeric(
&self,
result: &mut ValidationResult,
) -> Result<bool, ValidationError> {
let current = self.instance;
if let Some(num) = current.as_f64() {
if let Some(min) = self.schema.minimum {
if num < min {
result.errors.push(ValidationError {
code: "MINIMUM_VIOLATED".to_string(),
message: format!("Value {} < min {}", num, min),
path: self.path.to_string(),
});
}
}
if let Some(max) = self.schema.maximum {
if num > max {
result.errors.push(ValidationError {
code: "MAXIMUM_VIOLATED".to_string(),
message: format!("Value {} > max {}", num, max),
path: self.path.to_string(),
});
}
}
if let Some(ex_min) = self.schema.exclusive_minimum {
if num <= ex_min {
result.errors.push(ValidationError {
code: "EXCLUSIVE_MINIMUM_VIOLATED".to_string(),
message: format!("Value {} <= ex_min {}", num, ex_min),
path: self.path.to_string(),
});
}
}
if let Some(ex_max) = self.schema.exclusive_maximum {
if num >= ex_max {
result.errors.push(ValidationError {
code: "EXCLUSIVE_MAXIMUM_VIOLATED".to_string(),
message: format!("Value {} >= ex_max {}", num, ex_max),
path: self.path.to_string(),
});
}
}
if let Some(multiple_of) = self.schema.multiple_of {
let val: f64 = num / multiple_of;
if (val - val.round()).abs() > f64::EPSILON {
result.errors.push(ValidationError {
code: "MULTIPLE_OF_VIOLATED".to_string(),
message: format!("Value {} not multiple of {}", num, multiple_of),
path: self.path.to_string(),
});
}
}
}
Ok(true)
}
}

View File

@ -0,0 +1,183 @@
use serde_json::Value;
use std::collections::HashSet;
use crate::validator::context::ValidationContext;
use crate::validator::error::ValidationError;
use crate::validator::result::ValidationResult;
impl<'a> ValidationContext<'a> {
pub(crate) fn validate_object(
&self,
result: &mut ValidationResult,
) -> Result<bool, ValidationError> {
let current = self.instance;
if let Some(obj) = current.as_object() {
// Entity Bound Implicit Type Validation
if let Some(allowed_types) = &self.schema.obj.compiled_allowed_types {
if let Some(type_val) = obj.get("type") {
if let Some(type_str) = type_val.as_str() {
if allowed_types.contains(type_str) {
// Ensure it passes strict mode
result.evaluated_keys.insert("type".to_string());
} else {
result.errors.push(ValidationError {
code: "CONST_VIOLATED".to_string(), // Aligning with original const override errors
message: format!(
"Type '{}' is not a valid descendant for this entity bound schema",
type_str
),
path: format!("{}/type", self.path),
});
}
}
}
}
if let Some(min) = self.schema.min_properties {
if (obj.len() as f64) < min {
result.errors.push(ValidationError {
code: "MIN_PROPERTIES".to_string(),
message: "Too few properties".to_string(),
path: self.path.to_string(),
});
}
}
if let Some(max) = self.schema.max_properties {
if (obj.len() as f64) > max {
result.errors.push(ValidationError {
code: "MAX_PROPERTIES".to_string(),
message: "Too many properties".to_string(),
path: self.path.to_string(),
});
}
}
if let Some(ref req) = self.schema.required {
for field in req {
if !obj.contains_key(field) {
result.errors.push(ValidationError {
code: "REQUIRED_FIELD_MISSING".to_string(),
message: format!("Missing {}", field),
path: format!("{}/{}", self.path, field),
});
}
}
}
if let Some(props) = &self.schema.properties {
for (key, sub_schema) in props {
if let Some(child_instance) = obj.get(key) {
let new_path = format!("{}/{}", self.path, key);
let is_ref = sub_schema.ref_string.is_some() || sub_schema.obj.compiled_ref.is_some();
let next_extensible = if is_ref { false } else { self.extensible };
let derived = self.derive(
sub_schema,
child_instance,
&new_path,
next_extensible,
false,
);
let mut item_res = derived.validate()?;
// Entity Bound Implicit Type Interception
if key == "type" {
if let Some(allowed_types) = &self.schema.obj.compiled_allowed_types {
if let Some(instance_type) = child_instance.as_str() {
if allowed_types.contains(instance_type) {
item_res
.errors
.retain(|e| e.code != "CONST_VIOLATED" && e.code != "ENUM_VIOLATED");
}
}
}
}
result.merge(item_res);
result.evaluated_keys.insert(key.to_string());
}
}
}
if let Some(ref compiled_pp) = self.schema.compiled_pattern_properties {
for (compiled_re, sub_schema) in compiled_pp {
for (key, child_instance) in obj {
if compiled_re.0.is_match(key) {
let new_path = format!("{}/{}", self.path, key);
let is_ref = sub_schema.ref_string.is_some() || sub_schema.obj.compiled_ref.is_some();
let next_extensible = if is_ref { false } else { self.extensible };
let derived = self.derive(
sub_schema,
child_instance,
&new_path,
next_extensible,
false,
);
let item_res = derived.validate()?;
result.merge(item_res);
result.evaluated_keys.insert(key.to_string());
}
}
}
}
if let Some(ref additional_schema) = self.schema.additional_properties {
for (key, child_instance) in obj {
let mut locally_matched = false;
if let Some(props) = &self.schema.properties {
if props.contains_key(&key.to_string()) {
locally_matched = true;
}
}
if !locally_matched {
if let Some(ref compiled_pp) = self.schema.compiled_pattern_properties {
for (compiled_re, _) in compiled_pp {
if compiled_re.0.is_match(key) {
locally_matched = true;
break;
}
}
}
}
if !locally_matched {
let new_path = format!("{}/{}", self.path, key);
let is_ref = additional_schema.ref_string.is_some()
|| additional_schema.obj.compiled_ref.is_some();
let next_extensible = if is_ref { false } else { self.extensible };
let derived = self.derive(
additional_schema,
child_instance,
&new_path,
next_extensible,
false,
);
let item_res = derived.validate()?;
result.merge(item_res);
result.evaluated_keys.insert(key.to_string());
}
}
}
if let Some(ref property_names) = self.schema.property_names {
for key in obj.keys() {
let _new_path = format!("{}/propertyNames/{}", self.path, key);
let val_str = Value::String(key.to_string());
let ctx = ValidationContext::new(
self.schemas,
self.root,
property_names,
&val_str,
self.extensible,
self.reporter,
);
result.merge(ctx.validate()?);
}
}
}
Ok(true)
}
}

View File

@ -0,0 +1,64 @@
use crate::validator::context::ValidationContext;
use crate::validator::error::ValidationError;
use crate::validator::result::ValidationResult;
impl<'a> ValidationContext<'a> {
pub(crate) fn validate_family(
&self,
result: &mut ValidationResult,
) -> Result<bool, ValidationError> {
if self.schema.family.is_some() {
let conflicts = self.schema.type_.is_some()
|| self.schema.properties.is_some()
|| self.schema.required.is_some()
|| self.schema.additional_properties.is_some()
|| self.schema.items.is_some()
|| self.schema.ref_string.is_some()
|| self.schema.one_of.is_some()
|| self.schema.any_of.is_some()
|| self.schema.all_of.is_some()
|| self.schema.enum_.is_some()
|| self.schema.const_.is_some();
if conflicts {
result.errors.push(ValidationError {
code: "INVALID_SCHEMA".to_string(),
message: "$family must be used exclusively without other constraints".to_string(),
path: self.path.to_string(),
});
// Short-circuit: the schema formulation is broken
return Ok(false);
}
}
// Family specific runtime validation will go here later if needed
Ok(true)
}
pub(crate) fn validate_refs(
&self,
result: &mut ValidationResult,
) -> Result<bool, ValidationError> {
// 1. Core $ref logic fully transitioned to memory pointer resolutions.
if let Some(_ref_str) = &self.schema.ref_string {
if let Some(global_schema) = &self.schema.compiled_ref {
let mut shadow = self.derive(
global_schema,
self.instance,
&self.path,
self.extensible,
false,
);
shadow.root = global_schema;
result.merge(shadow.validate()?);
} else {
result.errors.push(ValidationError {
code: "REF_RESOLUTION_FAILED".to_string(),
message: format!("Reference pointer was not compiled inside Database graph"),
path: self.path.to_string(),
});
}
}
Ok(true)
}
}

View File

@ -0,0 +1,53 @@
use crate::validator::context::ValidationContext;
use crate::validator::error::ValidationError;
use crate::validator::result::ValidationResult;
use regex::Regex;
impl<'a> ValidationContext<'a> {
pub(crate) fn validate_string(
&self,
result: &mut ValidationResult,
) -> Result<bool, ValidationError> {
let current = self.instance;
if let Some(s) = current.as_str() {
if let Some(min) = self.schema.min_length {
if (s.chars().count() as f64) < min {
result.errors.push(ValidationError {
code: "MIN_LENGTH_VIOLATED".to_string(),
message: format!("Length < min {}", min),
path: self.path.to_string(),
});
}
}
if let Some(max) = self.schema.max_length {
if (s.chars().count() as f64) > max {
result.errors.push(ValidationError {
code: "MAX_LENGTH_VIOLATED".to_string(),
message: format!("Length > max {}", max),
path: self.path.to_string(),
});
}
}
if let Some(ref compiled_re) = self.schema.compiled_pattern {
if !compiled_re.0.is_match(s) {
result.errors.push(ValidationError {
code: "PATTERN_VIOLATED".to_string(),
message: format!("Pattern mismatch {:?}", self.schema.pattern),
path: self.path.to_string(),
});
}
} else if let Some(ref pattern) = self.schema.pattern {
if let Ok(re) = Regex::new(pattern) {
if !re.is_match(s) {
result.errors.push(ValidationError {
code: "PATTERN_VIOLATED".to_string(),
message: format!("Pattern mismatch {}", pattern),
path: self.path.to_string(),
});
}
}
}
}
Ok(true)
}
}

View File

@ -5,11 +5,7 @@ use std::fs;
struct TestSuite { struct TestSuite {
#[allow(dead_code)] #[allow(dead_code)]
description: String, description: String,
schema: Option<serde_json::Value>, database: serde_json::Value,
// Support JSPG-style test suites with explicit types/enums/puncs
types: Option<serde_json::Value>,
enums: Option<serde_json::Value>,
puncs: Option<serde_json::Value>,
tests: Vec<TestCase>, tests: Vec<TestCase>,
} }
@ -20,9 +16,6 @@ struct TestCase {
valid: bool, valid: bool,
// Support explicit schema ID target for test case // Support explicit schema ID target for test case
schema_id: Option<String>, schema_id: Option<String>,
// Expected output for masking tests
#[allow(dead_code)]
expected: Option<serde_json::Value>,
} }
// use crate::validator::registry::REGISTRY; // No longer used directly for tests! // use crate::validator::registry::REGISTRY; // No longer used directly for tests!
@ -50,64 +43,34 @@ pub fn run_test_file_at_index(path: &str, index: usize) -> Result<(), String> {
let group = &suite[index]; let group = &suite[index];
let mut failures = Vec::<String>::new(); let mut failures = Vec::<String>::new();
// Create Validator Instance and parse enums, types, and puncs automatically let db_json = group.database.clone();
let mut validator = Validator::from_punc_definition( let db = crate::database::Database::new(&db_json);
group.enums.as_ref(), let validator = Validator::new(std::sync::Arc::new(db.schemas));
group.types.as_ref(),
group.puncs.as_ref(),
);
// 3. Register root 'schemas' if present (generic test support)
// Some tests use a raw 'schema' or 'schemas' field at the group level
if let Some(schema_val) = &group.schema {
match serde_json::from_value::<crate::validator::schema::Schema>(schema_val.clone()) {
Ok(mut schema) => {
let id_clone = schema.obj.id.clone();
if id_clone.is_some() {
validator.registry.add(schema);
} else {
// Fallback ID if none provided in schema
let id = format!("test:{}:{}", path, index);
schema.obj.id = Some(id);
validator.registry.add(schema);
}
}
Err(e) => {
eprintln!(
"DEBUG: FAILED to deserialize group schema for index {}: {}",
index, e
);
}
}
}
// 4. Run Tests // 4. Run Tests
for (_test_index, test) in group.tests.iter().enumerate() { for (_test_index, test) in group.tests.iter().enumerate() {
let mut schema_id = test.schema_id.clone(); let mut schema_id = test.schema_id.clone();
// If no explicit schema_id, try to infer from the single schema in the group // If no explicit schema_id, infer from the database structure
if schema_id.is_none() { if schema_id.is_none() {
if let Some(s) = &group.schema { if let Some(schemas) = db_json.get("schemas").and_then(|v| v.as_array()) {
// If 'schema' is a single object, use its ID or "root" if let Some(first) = schemas.first() {
if let Some(obj) = s.as_object() { if let Some(id) = first.get("$id").and_then(|v| v.as_str()) {
if let Some(id_val) = obj.get("$id") { schema_id = Some(id.to_string());
schema_id = id_val.as_str().map(|s| s.to_string()); } else {
schema_id = Some("schema_0".to_string());
} }
} }
if schema_id.is_none() {
schema_id = Some(format!("test:{}:{}", path, index));
}
} }
}
// Default to the first punc if present (for puncs.json style) if schema_id.is_none() {
if schema_id.is_none() { if let Some(puncs) = db_json.get("puncs").and_then(|v| v.as_array()) {
if let Some(Value::Array(puncs)) = &group.puncs { if let Some(first_punc) = puncs.first() {
if let Some(first_punc) = puncs.first() { if let Some(schemas) = first_punc.get("schemas").and_then(|v| v.as_array()) {
if let Some(Value::Array(schemas)) = first_punc.get("schemas") { if let Some(first) = schemas.first() {
if let Some(first_schema) = schemas.first() { if let Some(id) = first.get("$id").and_then(|v| v.as_str()) {
if let Some(id) = first_schema.get("$id").and_then(|v| v.as_str()) { schema_id = Some(id.to_string());
schema_id = Some(id.to_string()); }
} }
} }
} }
@ -127,42 +90,16 @@ pub fn run_test_file_at_index(path: &str, index: usize) -> Result<(), String> {
} }
}; };
if let Some(expected) = &test.expected { if got_valid != test.valid {
// Masking Test let error_msg = match &result {
let mut data_for_mask = test.data.clone(); Ok(res) => format!("{:?}", res.errors),
match validator.mask(&sid, &mut data_for_mask) { Err(e) => format!("Execution Error: {:?}", e),
Ok(_) => { };
if !equals(&data_for_mask, expected) {
let msg = format!(
"Masking Test '{}' failed.\nExpected: {:?}\nGot: {:?}",
test.description, expected, data_for_mask
);
eprintln!("{}", msg);
failures.push(msg);
}
}
Err(e) => {
let msg = format!(
"Masking Test '{}' failed with execution error: {:?}",
test.description, e
);
eprintln!("{}", msg);
failures.push(msg);
}
}
} else {
// Standard Validation Test
if got_valid != test.valid {
let error_msg = match &result {
Ok(res) => format!("{:?}", res.errors),
Err(e) => format!("Execution Error: {:?}", e),
};
failures.push(format!( failures.push(format!(
"[{}] Test '{}' failed. Expected: {}, Got: {}. Errors: {}", "[{}] Test '{}' failed. Expected: {}, Got: {}. Errors: {}",
group.description, test.description, test.valid, got_valid, error_msg group.description, test.description, test.valid, got_valid, error_msg
)); ));
}
} }
} else { } else {
failures.push(format!( failures.push(format!(
@ -178,96 +115,6 @@ pub fn run_test_file_at_index(path: &str, index: usize) -> Result<(), String> {
Ok(()) Ok(())
} }
pub fn run_test_file(path: &str) -> 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));
let mut failures = Vec::<String>::new();
for (group_index, group) in suite.into_iter().enumerate() {
// Create Validator Instance and parse enums, types, and puncs automatically
let mut validator = Validator::from_punc_definition(
group.enums.as_ref(),
group.types.as_ref(),
group.puncs.as_ref(),
);
let unique_id = format!("test:{}:{}", path, group_index);
// Register main 'schema' if present (Standard style)
if let Some(ref schema_val) = group.schema {
let mut schema: crate::validator::schema::Schema =
serde_json::from_value(schema_val.clone()).expect("Failed to parse test schema");
// If schema has no ID, assign unique_id and use add() or manual insert?
// Compiler needs ID. Registry::add needs ID.
if schema.obj.id.is_none() {
schema.obj.id = Some(unique_id.clone());
}
validator.registry.add(schema);
}
for test in group.tests {
// Use explicit schema_id from test, or default to unique_id
let schema_id = test.schema_id.as_deref().unwrap_or(&unique_id).to_string();
let result = validator.validate(&schema_id, &test.data);
if test.valid {
match result {
Ok(res) => {
if !res.is_valid() {
let msg = format!(
"Test failed (expected valid): {}\nSchema: {:?}\nData: {:?}\nErrors: {:?}",
test.description,
group.schema, // We might need to find the actual schema used if schema_id is custom
test.data,
res.errors
);
eprintln!("{}", msg);
failures.push(msg);
}
}
Err(e) => {
let msg = format!(
"Test failed (expected valid) but got execution error: {}\nSchema: {:?}\nData: {:?}\nError: {:?}",
test.description, group.schema, test.data, e
);
eprintln!("{}", msg);
failures.push(msg);
}
}
} else {
match result {
Ok(res) => {
if res.is_valid() {
let msg = format!(
"Test failed (expected invalid): {}\nSchema: {:?}\nData: {:?}",
test.description, group.schema, test.data
);
eprintln!("{}", msg);
failures.push(msg);
}
}
Err(_) => {
// Expected invalid, got error (which implies invalid/failure), so this is PASS.
}
}
}
}
}
if !failures.is_empty() {
return Err(format!(
"{} tests failed in file {}:\n\n{}",
failures.len(),
path,
failures.join("\n\n")
));
}
Ok(())
}
pub fn is_integer(v: &Value) -> bool { pub fn is_integer(v: &Value) -> bool {
match v { match v {

62
test_err.log Normal file
View File

@ -0,0 +1,62 @@
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
 Using PgConfig("pg18") and `pg_config` from /opt/homebrew/opt/postgresql@18/bin/pg_config
 Building extension with features pg_test pg18
 Running command "/opt/homebrew/bin/cargo" "build" "--lib" "--features" "pg_test pg18" "--no-default-features" "--message-format=json-render-diagnostics"
Compiling jspg v0.1.0 (/Users/awgneo/Repositories/thoughtpatterns/cellular/jspg)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 7.10s
 Installing extension
 Copying control file to /opt/homebrew/share/postgresql@18/extension/jspg.control
 Copying shared library to /opt/homebrew/lib/postgresql@18/jspg.dylib
 Discovered 351 SQL entities: 1 schemas (1 unique), 350 functions, 0 types, 0 enums, 0 sqls, 0 ords, 0 hashes, 0 aggregates, 0 triggers
 Rebuilding pgrx_embed, in debug mode, for SQL generation with features pg_test pg18
Compiling jspg v0.1.0 (/Users/awgneo/Repositories/thoughtpatterns/cellular/jspg)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 10.63s
 Writing SQL entities to /opt/homebrew/share/postgresql@18/extension/jspg--0.1.0.sql
 Finished installing jspg
[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.077 EST] [82958] [69a509eb.1440e]: LOG: database system was shut down at 2026-03-01 22:49:02 EST
 Creating database pgrx_tests
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:
[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

Test Function Messages:
[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

Client 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: []"
postgres location: fixtures.rs
rust location: <unknown>
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`

View File

@ -1,29 +1,5 @@
use jspg::validator::util; use jspg::validator::util;
#[test]
fn test_anchor_0() {
let path = format!("{}/tests/fixtures/anchor.json", env!("CARGO_MANIFEST_DIR"));
util::run_test_file_at_index(&path, 0).unwrap();
}
#[test]
fn test_anchor_1() {
let path = format!("{}/tests/fixtures/anchor.json", env!("CARGO_MANIFEST_DIR"));
util::run_test_file_at_index(&path, 1).unwrap();
}
#[test]
fn test_anchor_2() {
let path = format!("{}/tests/fixtures/anchor.json", env!("CARGO_MANIFEST_DIR"));
util::run_test_file_at_index(&path, 2).unwrap();
}
#[test]
fn test_anchor_3() {
let path = format!("{}/tests/fixtures/anchor.json", env!("CARGO_MANIFEST_DIR"));
util::run_test_file_at_index(&path, 3).unwrap();
}
#[test] #[test]
fn test_content_0() { fn test_content_0() {
let path = format!("{}/tests/fixtures/content.json", env!("CARGO_MANIFEST_DIR")); let path = format!("{}/tests/fixtures/content.json", env!("CARGO_MANIFEST_DIR"));
@ -108,54 +84,6 @@ fn test_min_items_2() {
util::run_test_file_at_index(&path, 2).unwrap(); util::run_test_file_at_index(&path, 2).unwrap();
} }
#[test]
fn test_puncs_0() {
let path = format!("{}/tests/fixtures/puncs.json", env!("CARGO_MANIFEST_DIR"));
util::run_test_file_at_index(&path, 0).unwrap();
}
#[test]
fn test_puncs_1() {
let path = format!("{}/tests/fixtures/puncs.json", env!("CARGO_MANIFEST_DIR"));
util::run_test_file_at_index(&path, 1).unwrap();
}
#[test]
fn test_puncs_2() {
let path = format!("{}/tests/fixtures/puncs.json", env!("CARGO_MANIFEST_DIR"));
util::run_test_file_at_index(&path, 2).unwrap();
}
#[test]
fn test_puncs_3() {
let path = format!("{}/tests/fixtures/puncs.json", env!("CARGO_MANIFEST_DIR"));
util::run_test_file_at_index(&path, 3).unwrap();
}
#[test]
fn test_puncs_4() {
let path = format!("{}/tests/fixtures/puncs.json", env!("CARGO_MANIFEST_DIR"));
util::run_test_file_at_index(&path, 4).unwrap();
}
#[test]
fn test_puncs_5() {
let path = format!("{}/tests/fixtures/puncs.json", env!("CARGO_MANIFEST_DIR"));
util::run_test_file_at_index(&path, 5).unwrap();
}
#[test]
fn test_puncs_6() {
let path = format!("{}/tests/fixtures/puncs.json", env!("CARGO_MANIFEST_DIR"));
util::run_test_file_at_index(&path, 6).unwrap();
}
#[test]
fn test_puncs_7() {
let path = format!("{}/tests/fixtures/puncs.json", env!("CARGO_MANIFEST_DIR"));
util::run_test_file_at_index(&path, 7).unwrap();
}
#[test] #[test]
fn test_additional_properties_0() { fn test_additional_properties_0() {
let path = format!("{}/tests/fixtures/additionalProperties.json", env!("CARGO_MANIFEST_DIR")); let path = format!("{}/tests/fixtures/additionalProperties.json", env!("CARGO_MANIFEST_DIR"));
@ -348,6 +276,18 @@ fn test_any_of_9() {
util::run_test_file_at_index(&path, 9).unwrap(); util::run_test_file_at_index(&path, 9).unwrap();
} }
#[test]
fn test_families_0() {
let path = format!("{}/tests/fixtures/families.json", env!("CARGO_MANIFEST_DIR"));
util::run_test_file_at_index(&path, 0).unwrap();
}
#[test]
fn test_families_1() {
let path = format!("{}/tests/fixtures/families.json", env!("CARGO_MANIFEST_DIR"));
util::run_test_file_at_index(&path, 1).unwrap();
}
#[test] #[test]
fn test_property_names_0() { fn test_property_names_0() {
let path = format!("{}/tests/fixtures/propertyNames.json", env!("CARGO_MANIFEST_DIR")); let path = format!("{}/tests/fixtures/propertyNames.json", env!("CARGO_MANIFEST_DIR"));
@ -390,18 +330,6 @@ fn test_property_names_6() {
util::run_test_file_at_index(&path, 6).unwrap(); util::run_test_file_at_index(&path, 6).unwrap();
} }
#[test]
fn test_boolean_schema_0() {
let path = format!("{}/tests/fixtures/boolean_schema.json", env!("CARGO_MANIFEST_DIR"));
util::run_test_file_at_index(&path, 0).unwrap();
}
#[test]
fn test_boolean_schema_1() {
let path = format!("{}/tests/fixtures/boolean_schema.json", env!("CARGO_MANIFEST_DIR"));
util::run_test_file_at_index(&path, 1).unwrap();
}
#[test] #[test]
fn test_not_0() { fn test_not_0() {
let path = format!("{}/tests/fixtures/not.json", env!("CARGO_MANIFEST_DIR")); let path = format!("{}/tests/fixtures/not.json", env!("CARGO_MANIFEST_DIR"));
@ -570,6 +498,30 @@ fn test_items_15() {
util::run_test_file_at_index(&path, 15).unwrap(); util::run_test_file_at_index(&path, 15).unwrap();
} }
#[test]
fn test_typed_refs_0() {
let path = format!("{}/tests/fixtures/typedRefs.json", env!("CARGO_MANIFEST_DIR"));
util::run_test_file_at_index(&path, 0).unwrap();
}
#[test]
fn test_typed_refs_1() {
let path = format!("{}/tests/fixtures/typedRefs.json", env!("CARGO_MANIFEST_DIR"));
util::run_test_file_at_index(&path, 1).unwrap();
}
#[test]
fn test_typed_refs_2() {
let path = format!("{}/tests/fixtures/typedRefs.json", env!("CARGO_MANIFEST_DIR"));
util::run_test_file_at_index(&path, 2).unwrap();
}
#[test]
fn test_typed_refs_3() {
let path = format!("{}/tests/fixtures/typedRefs.json", env!("CARGO_MANIFEST_DIR"));
util::run_test_file_at_index(&path, 3).unwrap();
}
#[test] #[test]
fn test_enum_0() { fn test_enum_0() {
let path = format!("{}/tests/fixtures/enum.json", env!("CARGO_MANIFEST_DIR")); let path = format!("{}/tests/fixtures/enum.json", env!("CARGO_MANIFEST_DIR"));
@ -1014,6 +966,18 @@ fn test_one_of_12() {
util::run_test_file_at_index(&path, 12).unwrap(); util::run_test_file_at_index(&path, 12).unwrap();
} }
#[test]
fn test_boolean_schema_0() {
let path = format!("{}/tests/fixtures/booleanSchema.json", env!("CARGO_MANIFEST_DIR"));
util::run_test_file_at_index(&path, 0).unwrap();
}
#[test]
fn test_boolean_schema_1() {
let path = format!("{}/tests/fixtures/booleanSchema.json", env!("CARGO_MANIFEST_DIR"));
util::run_test_file_at_index(&path, 1).unwrap();
}
#[test] #[test]
fn test_if_then_else_0() { fn test_if_then_else_0() {
let path = format!("{}/tests/fixtures/if-then-else.json", env!("CARGO_MANIFEST_DIR")); let path = format!("{}/tests/fixtures/if-then-else.json", env!("CARGO_MANIFEST_DIR"));
@ -1961,129 +1925,3 @@ fn test_contains_8() {
let path = format!("{}/tests/fixtures/contains.json", env!("CARGO_MANIFEST_DIR")); let path = format!("{}/tests/fixtures/contains.json", env!("CARGO_MANIFEST_DIR"));
util::run_test_file_at_index(&path, 8).unwrap(); util::run_test_file_at_index(&path, 8).unwrap();
} }
#[test]
fn test_dynamic_ref_0() {
let path = format!("{}/tests/fixtures/dynamicRef.json", env!("CARGO_MANIFEST_DIR"));
util::run_test_file_at_index(&path, 0).unwrap();
}
#[test]
fn test_dynamic_ref_1() {
let path = format!("{}/tests/fixtures/dynamicRef.json", env!("CARGO_MANIFEST_DIR"));
util::run_test_file_at_index(&path, 1).unwrap();
}
#[test]
fn test_dynamic_ref_2() {
let path = format!("{}/tests/fixtures/dynamicRef.json", env!("CARGO_MANIFEST_DIR"));
util::run_test_file_at_index(&path, 2).unwrap();
}
#[test]
fn test_dynamic_ref_3() {
let path = format!("{}/tests/fixtures/dynamicRef.json", env!("CARGO_MANIFEST_DIR"));
util::run_test_file_at_index(&path, 3).unwrap();
}
#[test]
fn test_dynamic_ref_4() {
let path = format!("{}/tests/fixtures/dynamicRef.json", env!("CARGO_MANIFEST_DIR"));
util::run_test_file_at_index(&path, 4).unwrap();
}
#[test]
fn test_dynamic_ref_5() {
let path = format!("{}/tests/fixtures/dynamicRef.json", env!("CARGO_MANIFEST_DIR"));
util::run_test_file_at_index(&path, 5).unwrap();
}
#[test]
fn test_dynamic_ref_6() {
let path = format!("{}/tests/fixtures/dynamicRef.json", env!("CARGO_MANIFEST_DIR"));
util::run_test_file_at_index(&path, 6).unwrap();
}
#[test]
fn test_dynamic_ref_7() {
let path = format!("{}/tests/fixtures/dynamicRef.json", env!("CARGO_MANIFEST_DIR"));
util::run_test_file_at_index(&path, 7).unwrap();
}
#[test]
fn test_dynamic_ref_8() {
let path = format!("{}/tests/fixtures/dynamicRef.json", env!("CARGO_MANIFEST_DIR"));
util::run_test_file_at_index(&path, 8).unwrap();
}
#[test]
fn test_dynamic_ref_9() {
let path = format!("{}/tests/fixtures/dynamicRef.json", env!("CARGO_MANIFEST_DIR"));
util::run_test_file_at_index(&path, 9).unwrap();
}
#[test]
fn test_dynamic_ref_10() {
let path = format!("{}/tests/fixtures/dynamicRef.json", env!("CARGO_MANIFEST_DIR"));
util::run_test_file_at_index(&path, 10).unwrap();
}
#[test]
fn test_dynamic_ref_11() {
let path = format!("{}/tests/fixtures/dynamicRef.json", env!("CARGO_MANIFEST_DIR"));
util::run_test_file_at_index(&path, 11).unwrap();
}
#[test]
fn test_dynamic_ref_12() {
let path = format!("{}/tests/fixtures/dynamicRef.json", env!("CARGO_MANIFEST_DIR"));
util::run_test_file_at_index(&path, 12).unwrap();
}
#[test]
fn test_dynamic_ref_13() {
let path = format!("{}/tests/fixtures/dynamicRef.json", env!("CARGO_MANIFEST_DIR"));
util::run_test_file_at_index(&path, 13).unwrap();
}
#[test]
fn test_dynamic_ref_14() {
let path = format!("{}/tests/fixtures/dynamicRef.json", env!("CARGO_MANIFEST_DIR"));
util::run_test_file_at_index(&path, 14).unwrap();
}
#[test]
fn test_dynamic_ref_15() {
let path = format!("{}/tests/fixtures/dynamicRef.json", env!("CARGO_MANIFEST_DIR"));
util::run_test_file_at_index(&path, 15).unwrap();
}
#[test]
fn test_dynamic_ref_16() {
let path = format!("{}/tests/fixtures/dynamicRef.json", env!("CARGO_MANIFEST_DIR"));
util::run_test_file_at_index(&path, 16).unwrap();
}
#[test]
fn test_dynamic_ref_17() {
let path = format!("{}/tests/fixtures/dynamicRef.json", env!("CARGO_MANIFEST_DIR"));
util::run_test_file_at_index(&path, 17).unwrap();
}
#[test]
fn test_dynamic_ref_18() {
let path = format!("{}/tests/fixtures/dynamicRef.json", env!("CARGO_MANIFEST_DIR"));
util::run_test_file_at_index(&path, 18).unwrap();
}
#[test]
fn test_dynamic_ref_19() {
let path = format!("{}/tests/fixtures/dynamicRef.json", env!("CARGO_MANIFEST_DIR"));
util::run_test_file_at_index(&path, 19).unwrap();
}
#[test]
fn test_dynamic_ref_20() {
let path = format!("{}/tests/fixtures/dynamicRef.json", env!("CARGO_MANIFEST_DIR"));
util::run_test_file_at_index(&path, 20).unwrap();
}

View File

@ -1,19 +1,23 @@
[ [
{ {
"description": "additionalProperties validates properties not matched by properties", "description": "additionalProperties validates properties not matched by properties",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"properties": { {
"foo": { "$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "string" "properties": {
}, "foo": {
"bar": { "type": "string"
"type": "number" },
"bar": {
"type": "number"
}
},
"additionalProperties": {
"type": "boolean"
}
} }
}, ]
"additionalProperties": {
"type": "boolean"
}
}, },
"tests": [ "tests": [
{ {
@ -45,17 +49,21 @@
}, },
{ {
"description": "extensible: true with additionalProperties still validates structure", "description": "extensible: true with additionalProperties still validates structure",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"properties": { {
"foo": { "$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "string" "properties": {
"foo": {
"type": "string"
}
},
"extensible": true,
"additionalProperties": {
"type": "integer"
}
} }
}, ]
"extensible": true,
"additionalProperties": {
"type": "integer"
}
}, },
"tests": [ "tests": [
{ {
@ -79,19 +87,23 @@
}, },
{ {
"description": "complex additionalProperties with object and array items", "description": "complex additionalProperties with object and array items",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"properties": { {
"type": { "$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "string" "properties": {
"type": {
"type": "string"
}
},
"additionalProperties": {
"type": "array",
"items": {
"type": "string"
}
}
} }
}, ]
"additionalProperties": {
"type": "array",
"items": {
"type": "string"
}
}
}, },
"tests": [ "tests": [
{ {

View File

@ -1,27 +1,31 @@
[ [
{ {
"description": "allOf", "description": "allOf",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"allOf": [
{ {
"properties": { "$schema": "https://json-schema.org/draft/2020-12/schema",
"bar": { "allOf": [
"type": "integer" {
"properties": {
"bar": {
"type": "integer"
}
},
"required": [
"bar"
]
},
{
"properties": {
"foo": {
"type": "string"
}
},
"required": [
"foo"
]
} }
},
"required": [
"bar"
]
},
{
"properties": {
"foo": {
"type": "string"
}
},
"required": [
"foo"
] ]
} }
] ]
@ -61,39 +65,43 @@
}, },
{ {
"description": "allOf with base schema", "description": "allOf with base schema",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"properties": {
"bar": {
"type": "integer"
},
"baz": {},
"foo": {
"type": "string"
}
},
"required": [
"bar"
],
"allOf": [
{ {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": { "properties": {
"bar": {
"type": "integer"
},
"baz": {},
"foo": { "foo": {
"type": "string" "type": "string"
} }
}, },
"required": [ "required": [
"foo" "bar"
] ],
}, "allOf": [
{ {
"properties": { "properties": {
"baz": { "foo": {
"type": "null" "type": "string"
}
},
"required": [
"foo"
]
},
{
"properties": {
"baz": {
"type": "null"
}
},
"required": [
"baz"
]
} }
},
"required": [
"baz"
] ]
} }
] ]
@ -143,14 +151,18 @@
}, },
{ {
"description": "allOf simple types", "description": "allOf simple types",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"allOf": [
{ {
"maximum": 30 "$schema": "https://json-schema.org/draft/2020-12/schema",
}, "allOf": [
{ {
"minimum": 20 "maximum": 30
},
{
"minimum": 20
}
]
} }
] ]
}, },
@ -169,11 +181,15 @@
}, },
{ {
"description": "allOf with boolean schemas, all true", "description": "allOf with boolean schemas, all true",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"allOf": [ {
true, "$schema": "https://json-schema.org/draft/2020-12/schema",
true "allOf": [
true,
true
]
}
] ]
}, },
"tests": [ "tests": [
@ -186,11 +202,15 @@
}, },
{ {
"description": "allOf with boolean schemas, some false", "description": "allOf with boolean schemas, some false",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"allOf": [ {
true, "$schema": "https://json-schema.org/draft/2020-12/schema",
false "allOf": [
true,
false
]
}
] ]
}, },
"tests": [ "tests": [
@ -203,11 +223,15 @@
}, },
{ {
"description": "allOf with boolean schemas, all false", "description": "allOf with boolean schemas, all false",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"allOf": [ {
false, "$schema": "https://json-schema.org/draft/2020-12/schema",
false "allOf": [
false,
false
]
}
] ]
}, },
"tests": [ "tests": [
@ -220,10 +244,14 @@
}, },
{ {
"description": "allOf with one empty schema", "description": "allOf with one empty schema",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"allOf": [ {
{} "$schema": "https://json-schema.org/draft/2020-12/schema",
"allOf": [
{}
]
}
] ]
}, },
"tests": [ "tests": [
@ -236,11 +264,15 @@
}, },
{ {
"description": "allOf with two empty schemas", "description": "allOf with two empty schemas",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"allOf": [ {
{}, "$schema": "https://json-schema.org/draft/2020-12/schema",
{} "allOf": [
{},
{}
]
}
] ]
}, },
"tests": [ "tests": [
@ -253,12 +285,16 @@
}, },
{ {
"description": "allOf with the first empty schema", "description": "allOf with the first empty schema",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"allOf": [
{},
{ {
"type": "number" "$schema": "https://json-schema.org/draft/2020-12/schema",
"allOf": [
{},
{
"type": "number"
}
]
} }
] ]
}, },
@ -277,13 +313,17 @@
}, },
{ {
"description": "allOf with the last empty schema", "description": "allOf with the last empty schema",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"allOf": [
{ {
"type": "number" "$schema": "https://json-schema.org/draft/2020-12/schema",
}, "allOf": [
{} {
"type": "number"
},
{}
]
}
] ]
}, },
"tests": [ "tests": [
@ -301,13 +341,17 @@
}, },
{ {
"description": "nested allOf, to check validation semantics", "description": "nested allOf, to check validation semantics",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"allOf": [
{ {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"allOf": [ "allOf": [
{ {
"type": "null" "allOf": [
{
"type": "null"
}
]
} }
] ]
} }
@ -328,21 +372,25 @@
}, },
{ {
"description": "allOf combined with anyOf, oneOf", "description": "allOf combined with anyOf, oneOf",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"allOf": [
{ {
"multipleOf": 2 "$schema": "https://json-schema.org/draft/2020-12/schema",
} "allOf": [
], {
"anyOf": [ "multipleOf": 2
{ }
"multipleOf": 3 ],
} "anyOf": [
], {
"oneOf": [ "multipleOf": 3
{ }
"multipleOf": 5 ],
"oneOf": [
{
"multipleOf": 5
}
]
} }
] ]
}, },
@ -391,31 +439,35 @@
}, },
{ {
"description": "extensible: true allows extra properties in allOf", "description": "extensible: true allows extra properties in allOf",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"allOf": [
{ {
"properties": { "$schema": "https://json-schema.org/draft/2020-12/schema",
"bar": { "allOf": [
"type": "integer" {
"properties": {
"bar": {
"type": "integer"
}
},
"required": [
"bar"
]
},
{
"properties": {
"foo": {
"type": "string"
}
},
"required": [
"foo"
]
} }
}, ],
"required": [ "extensible": true
"bar"
]
},
{
"properties": {
"foo": {
"type": "string"
}
},
"required": [
"foo"
]
} }
], ]
"extensible": true
}, },
"tests": [ "tests": [
{ {
@ -431,22 +483,26 @@
}, },
{ {
"description": "strict by default with allOf properties", "description": "strict by default with allOf properties",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"allOf": [
{ {
"properties": { "$schema": "https://json-schema.org/draft/2020-12/schema",
"foo": { "allOf": [
"const": 1 {
"properties": {
"foo": {
"const": 1
}
}
},
{
"properties": {
"bar": {
"const": 2
}
}
} }
} ]
},
{
"properties": {
"bar": {
"const": 2
}
}
} }
] ]
}, },
@ -472,23 +528,27 @@
}, },
{ {
"description": "allOf with nested extensible: true (partial looseness)", "description": "allOf with nested extensible: true (partial looseness)",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"allOf": [
{ {
"properties": { "$schema": "https://json-schema.org/draft/2020-12/schema",
"foo": { "allOf": [
"const": 1 {
"properties": {
"foo": {
"const": 1
}
}
},
{
"extensible": true,
"properties": {
"bar": {
"const": 2
}
}
} }
} ]
},
{
"extensible": true,
"properties": {
"bar": {
"const": 2
}
}
} }
] ]
}, },
@ -506,31 +566,35 @@
}, },
{ {
"description": "strictness: allOf composition with strict refs", "description": "strictness: allOf composition with strict refs",
"schema": { "database": {
"allOf": [ "schemas": [
{ {
"$ref": "#/$defs/partA" "allOf": [
}, {
{ "$ref": "#/$defs/partA"
"$ref": "#/$defs/partB" },
} {
], "$ref": "#/$defs/partB"
"$defs": {
"partA": {
"properties": {
"id": {
"type": "string"
} }
} ],
}, "$defs": {
"partB": { "partA": {
"properties": { "properties": {
"name": { "id": {
"type": "string" "type": "string"
}
}
},
"partB": {
"properties": {
"name": {
"type": "string"
}
}
} }
} }
} }
} ]
}, },
"tests": [ "tests": [
{ {

View File

@ -1,120 +0,0 @@
[
{
"description": "Location-independent identifier",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$ref": "#foo",
"$defs": {
"A": {
"$anchor": "foo",
"type": "integer"
}
}
},
"tests": [
{
"data": 1,
"description": "match",
"valid": true
},
{
"data": "a",
"description": "mismatch",
"valid": false
}
]
},
{
"description": "Location-independent identifier with absolute URI",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$ref": "http://localhost:1234/draft2020-12/bar#foo",
"$defs": {
"A": {
"$id": "http://localhost:1234/draft2020-12/bar",
"$anchor": "foo",
"type": "integer"
}
}
},
"tests": [
{
"data": 1,
"description": "match",
"valid": true
},
{
"data": "a",
"description": "mismatch",
"valid": false
}
]
},
{
"description": "Location-independent identifier with base URI change in subschema",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "http://localhost:1234/draft2020-12/root",
"$ref": "http://localhost:1234/draft2020-12/nested.json#foo",
"$defs": {
"A": {
"$id": "nested.json",
"$defs": {
"B": {
"$anchor": "foo",
"type": "integer"
}
}
}
}
},
"tests": [
{
"data": 1,
"description": "match",
"valid": true
},
{
"data": "a",
"description": "mismatch",
"valid": false
}
]
},
{
"description": "same $anchor with different base uri",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "http://localhost:1234/draft2020-12/foobar",
"$defs": {
"A": {
"$id": "child1",
"allOf": [
{
"$id": "child2",
"$anchor": "my_anchor",
"type": "number"
},
{
"$anchor": "my_anchor",
"type": "string"
}
]
}
},
"$ref": "child1#my_anchor"
},
"tests": [
{
"description": "$ref resolves to /$defs/A/allOf/1",
"data": "a",
"valid": true
},
{
"description": "$ref does not resolve to /$defs/A/allOf/0",
"data": 1,
"valid": false
}
]
}
]

View File

@ -1,14 +1,18 @@
[ [
{ {
"description": "anyOf", "description": "anyOf",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"anyOf": [
{ {
"type": "integer" "$schema": "https://json-schema.org/draft/2020-12/schema",
}, "anyOf": [
{ {
"minimum": 2 "type": "integer"
},
{
"minimum": 2
}
]
} }
] ]
}, },
@ -37,15 +41,19 @@
}, },
{ {
"description": "anyOf with base schema", "description": "anyOf with base schema",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"type": "string",
"anyOf": [
{ {
"maxLength": 2 "$schema": "https://json-schema.org/draft/2020-12/schema",
}, "type": "string",
{ "anyOf": [
"minLength": 4 {
"maxLength": 2
},
{
"minLength": 4
}
]
} }
] ]
}, },
@ -69,11 +77,15 @@
}, },
{ {
"description": "anyOf with boolean schemas, all true", "description": "anyOf with boolean schemas, all true",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"anyOf": [ {
true, "$schema": "https://json-schema.org/draft/2020-12/schema",
true "anyOf": [
true,
true
]
}
] ]
}, },
"tests": [ "tests": [
@ -86,11 +98,15 @@
}, },
{ {
"description": "anyOf with boolean schemas, some true", "description": "anyOf with boolean schemas, some true",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"anyOf": [ {
true, "$schema": "https://json-schema.org/draft/2020-12/schema",
false "anyOf": [
true,
false
]
}
] ]
}, },
"tests": [ "tests": [
@ -103,11 +119,15 @@
}, },
{ {
"description": "anyOf with boolean schemas, all false", "description": "anyOf with boolean schemas, all false",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"anyOf": [ {
false, "$schema": "https://json-schema.org/draft/2020-12/schema",
false "anyOf": [
false,
false
]
}
] ]
}, },
"tests": [ "tests": [
@ -120,27 +140,31 @@
}, },
{ {
"description": "anyOf complex types", "description": "anyOf complex types",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"anyOf": [
{ {
"properties": { "$schema": "https://json-schema.org/draft/2020-12/schema",
"bar": { "anyOf": [
"type": "integer" {
"properties": {
"bar": {
"type": "integer"
}
},
"required": [
"bar"
]
},
{
"properties": {
"foo": {
"type": "string"
}
},
"required": [
"foo"
]
} }
},
"required": [
"bar"
]
},
{
"properties": {
"foo": {
"type": "string"
}
},
"required": [
"foo"
] ]
} }
] ]
@ -180,13 +204,17 @@
}, },
{ {
"description": "anyOf with one empty schema", "description": "anyOf with one empty schema",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"anyOf": [
{ {
"type": "number" "$schema": "https://json-schema.org/draft/2020-12/schema",
}, "anyOf": [
{} {
"type": "number"
},
{}
]
}
] ]
}, },
"tests": [ "tests": [
@ -204,13 +232,17 @@
}, },
{ {
"description": "nested anyOf, to check validation semantics", "description": "nested anyOf, to check validation semantics",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"anyOf": [
{ {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"anyOf": [ "anyOf": [
{ {
"type": "null" "anyOf": [
{
"type": "null"
}
]
} }
] ]
} }
@ -231,17 +263,21 @@
}, },
{ {
"description": "extensible: true allows extra properties in anyOf", "description": "extensible: true allows extra properties in anyOf",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"anyOf": [
{ {
"type": "integer" "$schema": "https://json-schema.org/draft/2020-12/schema",
}, "anyOf": [
{ {
"minimum": 2 "type": "integer"
},
{
"minimum": 2
}
],
"extensible": true
} }
], ]
"extensible": true
}, },
"tests": [ "tests": [
{ {
@ -255,22 +291,26 @@
}, },
{ {
"description": "strict by default with anyOf properties", "description": "strict by default with anyOf properties",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"anyOf": [
{ {
"properties": { "$schema": "https://json-schema.org/draft/2020-12/schema",
"foo": { "anyOf": [
"const": 1 {
"properties": {
"foo": {
"const": 1
}
}
},
{
"properties": {
"bar": {
"const": 2
}
}
} }
} ]
},
{
"properties": {
"bar": {
"const": 2
}
}
} }
] ]
}, },

View File

@ -1,7 +1,11 @@
[ [
{ {
"description": "boolean schema 'true'", "description": "boolean schema 'true'",
"schema": true, "database": {
"schemas": [
true
]
},
"tests": [ "tests": [
{ {
"description": "number is valid", "description": "number is valid",
@ -56,7 +60,11 @@
}, },
{ {
"description": "boolean schema 'false'", "description": "boolean schema 'false'",
"schema": false, "database": {
"schemas": [
false
]
},
"tests": [ "tests": [
{ {
"description": "number is invalid", "description": "number is invalid",

View File

@ -1,9 +1,13 @@
[ [
{ {
"description": "const validation", "description": "const validation",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"const": 2 {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"const": 2
}
]
}, },
"tests": [ "tests": [
{ {
@ -25,16 +29,20 @@
}, },
{ {
"description": "const with object", "description": "const with object",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"const": { {
"foo": "bar", "$schema": "https://json-schema.org/draft/2020-12/schema",
"baz": "bax" "const": {
}, "foo": "bar",
"properties": { "baz": "bax"
"foo": {}, },
"baz": {} "properties": {
} "foo": {},
"baz": {}
}
}
]
}, },
"tests": [ "tests": [
{ {
@ -72,11 +80,15 @@
}, },
{ {
"description": "const with array", "description": "const with array",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"const": [
{ {
"foo": "bar" "$schema": "https://json-schema.org/draft/2020-12/schema",
"const": [
{
"foo": "bar"
}
]
} }
] ]
}, },
@ -110,9 +122,13 @@
}, },
{ {
"description": "const with null", "description": "const with null",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"const": null {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"const": null
}
]
}, },
"tests": [ "tests": [
{ {
@ -129,9 +145,13 @@
}, },
{ {
"description": "const with false does not match 0", "description": "const with false does not match 0",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"const": false {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"const": false
}
]
}, },
"tests": [ "tests": [
{ {
@ -153,9 +173,13 @@
}, },
{ {
"description": "const with true does not match 1", "description": "const with true does not match 1",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"const": true {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"const": true
}
]
}, },
"tests": [ "tests": [
{ {
@ -177,10 +201,14 @@
}, },
{ {
"description": "const with [false] does not match [0]", "description": "const with [false] does not match [0]",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"const": [ {
false "$schema": "https://json-schema.org/draft/2020-12/schema",
"const": [
false
]
}
] ]
}, },
"tests": [ "tests": [
@ -209,10 +237,14 @@
}, },
{ {
"description": "const with [true] does not match [1]", "description": "const with [true] does not match [1]",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"const": [ {
true "$schema": "https://json-schema.org/draft/2020-12/schema",
"const": [
true
]
}
] ]
}, },
"tests": [ "tests": [
@ -241,11 +273,15 @@
}, },
{ {
"description": "const with {\"a\": false} does not match {\"a\": 0}", "description": "const with {\"a\": false} does not match {\"a\": 0}",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"const": { {
"a": false "$schema": "https://json-schema.org/draft/2020-12/schema",
} "const": {
"a": false
}
}
]
}, },
"tests": [ "tests": [
{ {
@ -273,11 +309,15 @@
}, },
{ {
"description": "const with {\"a\": true} does not match {\"a\": 1}", "description": "const with {\"a\": true} does not match {\"a\": 1}",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"const": { {
"a": true "$schema": "https://json-schema.org/draft/2020-12/schema",
} "const": {
"a": true
}
}
]
}, },
"tests": [ "tests": [
{ {
@ -305,9 +345,13 @@
}, },
{ {
"description": "const with 0 does not match other zero-like types", "description": "const with 0 does not match other zero-like types",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"const": 0 {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"const": 0
}
]
}, },
"tests": [ "tests": [
{ {
@ -344,9 +388,13 @@
}, },
{ {
"description": "const with 1 does not match true", "description": "const with 1 does not match true",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"const": 1 {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"const": 1
}
]
}, },
"tests": [ "tests": [
{ {
@ -368,9 +416,13 @@
}, },
{ {
"description": "const with -2.0 matches integer and float types", "description": "const with -2.0 matches integer and float types",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"const": -2.0 {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"const": -2.0
}
]
}, },
"tests": [ "tests": [
{ {
@ -402,9 +454,13 @@
}, },
{ {
"description": "float and integers are equal up to 64-bit representation limits", "description": "float and integers are equal up to 64-bit representation limits",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"const": 9007199254740992 {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"const": 9007199254740992
}
]
}, },
"tests": [ "tests": [
{ {
@ -431,9 +487,13 @@
}, },
{ {
"description": "nul characters in strings", "description": "nul characters in strings",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"const": "hello\u0000there" {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"const": "hello\u0000there"
}
]
}, },
"tests": [ "tests": [
{ {
@ -450,10 +510,14 @@
}, },
{ {
"description": "characters with the same visual representation but different codepoint", "description": "characters with the same visual representation but different codepoint",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"const": "μ", {
"$comment": "U+03BC" "$schema": "https://json-schema.org/draft/2020-12/schema",
"const": "μ",
"$comment": "U+03BC"
}
]
}, },
"tests": [ "tests": [
{ {
@ -472,10 +536,14 @@
}, },
{ {
"description": "characters with the same visual representation, but different number of codepoints", "description": "characters with the same visual representation, but different number of codepoints",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"const": "ä", {
"$comment": "U+00E4" "$schema": "https://json-schema.org/draft/2020-12/schema",
"const": "ä",
"$comment": "U+00E4"
}
]
}, },
"tests": [ "tests": [
{ {
@ -494,12 +562,16 @@
}, },
{ {
"description": "extensible: true allows extra properties in const object match", "description": "extensible: true allows extra properties in const object match",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"const": { {
"a": 1 "$schema": "https://json-schema.org/draft/2020-12/schema",
}, "const": {
"extensible": true "a": 1
},
"extensible": true
}
]
}, },
"tests": [ "tests": [
{ {

View File

@ -1,12 +1,16 @@
[ [
{ {
"description": "contains keyword validation", "description": "contains keyword validation",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"contains": { {
"minimum": 5 "$schema": "https://json-schema.org/draft/2020-12/schema",
}, "contains": {
"items": true "minimum": 5
},
"items": true
}
]
}, },
"tests": [ "tests": [
{ {
@ -60,12 +64,16 @@
}, },
{ {
"description": "contains keyword with const keyword", "description": "contains keyword with const keyword",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"contains": { {
"const": 5 "$schema": "https://json-schema.org/draft/2020-12/schema",
}, "contains": {
"items": true "const": 5
},
"items": true
}
]
}, },
"tests": [ "tests": [
{ {
@ -101,9 +109,13 @@
}, },
{ {
"description": "contains keyword with boolean schema true", "description": "contains keyword with boolean schema true",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"contains": true {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"contains": true
}
]
}, },
"tests": [ "tests": [
{ {
@ -122,9 +134,13 @@
}, },
{ {
"description": "contains keyword with boolean schema false", "description": "contains keyword with boolean schema false",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"contains": false {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"contains": false
}
]
}, },
"tests": [ "tests": [
{ {
@ -148,14 +164,18 @@
}, },
{ {
"description": "items + contains", "description": "items + contains",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"items": { {
"multipleOf": 2 "$schema": "https://json-schema.org/draft/2020-12/schema",
}, "items": {
"contains": { "multipleOf": 2
"multipleOf": 3 },
} "contains": {
"multipleOf": 3
}
}
]
}, },
"tests": [ "tests": [
{ {
@ -196,12 +216,16 @@
}, },
{ {
"description": "contains with false if subschema", "description": "contains with false if subschema",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"contains": { {
"if": false, "$schema": "https://json-schema.org/draft/2020-12/schema",
"else": true "contains": {
} "if": false,
"else": true
}
}
]
}, },
"tests": [ "tests": [
{ {
@ -220,11 +244,15 @@
}, },
{ {
"description": "contains with null instance elements", "description": "contains with null instance elements",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"contains": { {
"type": "null" "$schema": "https://json-schema.org/draft/2020-12/schema",
} "contains": {
"type": "null"
}
}
]
}, },
"tests": [ "tests": [
{ {
@ -238,12 +266,16 @@
}, },
{ {
"description": "extensible: true allows non-matching items in contains", "description": "extensible: true allows non-matching items in contains",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"contains": { {
"const": 1 "$schema": "https://json-schema.org/draft/2020-12/schema",
}, "contains": {
"extensible": true "const": 1
},
"extensible": true
}
]
}, },
"tests": [ "tests": [
{ {
@ -258,11 +290,15 @@
}, },
{ {
"description": "strict by default: non-matching items in contains are invalid", "description": "strict by default: non-matching items in contains are invalid",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"contains": { {
"const": 1 "$schema": "https://json-schema.org/draft/2020-12/schema",
} "contains": {
"const": 1
}
}
]
}, },
"tests": [ "tests": [
{ {

View File

@ -1,9 +1,13 @@
[ [
{ {
"description": "validation of string-encoded content based on media type", "description": "validation of string-encoded content based on media type",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"contentMediaType": "application/json" {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"contentMediaType": "application/json"
}
]
}, },
"tests": [ "tests": [
{ {
@ -25,9 +29,13 @@
}, },
{ {
"description": "validation of binary string-encoding", "description": "validation of binary string-encoding",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"contentEncoding": "base64" {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"contentEncoding": "base64"
}
]
}, },
"tests": [ "tests": [
{ {
@ -49,10 +57,14 @@
}, },
{ {
"description": "validation of binary-encoded media type documents", "description": "validation of binary-encoded media type documents",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"contentMediaType": "application/json", {
"contentEncoding": "base64" "$schema": "https://json-schema.org/draft/2020-12/schema",
"contentMediaType": "application/json",
"contentEncoding": "base64"
}
]
}, },
"tests": [ "tests": [
{ {
@ -79,24 +91,28 @@
}, },
{ {
"description": "validation of binary-encoded media type documents with schema", "description": "validation of binary-encoded media type documents with schema",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"contentMediaType": "application/json", {
"contentEncoding": "base64", "$schema": "https://json-schema.org/draft/2020-12/schema",
"contentSchema": { "contentMediaType": "application/json",
"type": "object", "contentEncoding": "base64",
"required": [ "contentSchema": {
"foo" "type": "object",
], "required": [
"properties": { "foo"
"foo": { ],
"type": "string" "properties": {
}, "foo": {
"boo": { "type": "string"
"type": "integer" },
"boo": {
"type": "integer"
}
}
} }
} }
} ]
}, },
"tests": [ "tests": [
{ {

576
tests/fixtures/dependencies.json vendored Normal file
View File

@ -0,0 +1,576 @@
[
{
"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
},
{
"description": "nondependant",
"data": {
"foo": 1
},
"valid": true
},
{
"description": "with dependency",
"data": {
"foo": 1,
"bar": 2
},
"valid": true
},
{
"description": "missing dependency",
"data": {
"bar": 2
},
"valid": false
},
{
"description": "ignores arrays",
"data": [
"bar"
],
"valid": true
},
{
"description": "ignores strings",
"data": "foobar",
"valid": true
},
{
"description": "ignores other non-objects",
"data": 12,
"valid": 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": {},
"valid": true
},
{
"description": "object with one property",
"data": {
"bar": 2
},
"valid": true
},
{
"description": "non-object is valid",
"data": 1,
"valid": 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": {},
"valid": true
},
{
"description": "nondependants",
"data": {
"foo": 1,
"bar": 2
},
"valid": true
},
{
"description": "with dependencies",
"data": {
"foo": 1,
"bar": 2,
"quux": 3
},
"valid": true
},
{
"description": "missing dependency",
"data": {
"foo": 1,
"quux": 2
},
"valid": false
},
{
"description": "missing other dependency",
"data": {
"bar": 1,
"quux": 2
},
"valid": false
},
{
"description": "missing both dependencies",
"data": {
"quux": 1
},
"valid": 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
},
"valid": true
},
{
"description": "quoted quotes",
"data": {
"foo'bar": 1,
"foo\"bar": 2
},
"valid": true
},
{
"description": "CRLF missing dependent",
"data": {
"foo\nbar": 1,
"foo": 2
},
"valid": false
},
{
"description": "quoted quotes missing dependent",
"data": {
"foo\"bar": 2
},
"valid": 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
},
"valid": 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
},
"valid": true
},
{
"description": "no dependency",
"data": {
"foo": "quux"
},
"valid": true
},
{
"description": "wrong type",
"data": {
"foo": "quux",
"bar": 2
},
"valid": false
},
{
"description": "wrong type other",
"data": {
"foo": 2,
"bar": "quux"
},
"valid": false
},
{
"description": "wrong type both",
"data": {
"foo": "quux",
"bar": "quux"
},
"valid": false
},
{
"description": "ignores arrays (invalid in strict mode)",
"data": [
"bar"
],
"valid": false,
"expect_errors": [
{
"code": "STRICT_ITEM_VIOLATION"
}
]
},
{
"description": "ignores strings",
"data": "foobar",
"valid": true
},
{
"description": "ignores other non-objects",
"data": 12,
"valid": 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"
],
"valid": 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
},
"valid": true
},
{
"description": "object with property having schema false is invalid",
"data": {
"bar": 2
},
"valid": false
},
{
"description": "object with both properties is invalid",
"data": {
"foo": 1,
"bar": 2
},
"valid": false
},
{
"description": "empty object is valid",
"data": {},
"valid": 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
},
"valid": true
},
{
"description": "quoted quote",
"data": {
"foo'bar": {
"foo\"bar": 1
}
},
"valid": false
},
{
"description": "quoted tab invalid under dependent schema",
"data": {
"foo\tbar": 1,
"a": 2
},
"valid": false
},
{
"description": "quoted quote invalid under dependent schema",
"data": {
"foo'bar": 1
},
"valid": 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
},
"valid": false
},
{
"description": "matches dependency (invalid in strict mode - bar not allowed if foo missing)",
"data": {
"bar": 1
},
"valid": false,
"expect_errors": [
{
"code": "STRICT_PROPERTY_VIOLATION"
}
]
},
{
"description": "matches both",
"data": {
"foo": 1,
"bar": 2
},
"valid": false
},
{
"description": "no dependency",
"data": {
"baz": 1
},
"valid": 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
},
"valid": true
}
]
}
]

View File

@ -1,220 +0,0 @@
[
{
"description": "single dependency",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"dependentRequired": {
"bar": [
"foo"
]
},
"extensible": true
},
"tests": [
{
"description": "neither",
"data": {},
"valid": true
},
{
"description": "nondependant",
"data": {
"foo": 1
},
"valid": true
},
{
"description": "with dependency",
"data": {
"foo": 1,
"bar": 2
},
"valid": true
},
{
"description": "missing dependency",
"data": {
"bar": 2
},
"valid": false
},
{
"description": "ignores arrays",
"data": [
"bar"
],
"valid": true
},
{
"description": "ignores strings",
"data": "foobar",
"valid": true
},
{
"description": "ignores other non-objects",
"data": 12,
"valid": true
}
]
},
{
"description": "empty dependents",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"dependentRequired": {
"bar": []
},
"extensible": true
},
"tests": [
{
"description": "empty object",
"data": {},
"valid": true
},
{
"description": "object with one property",
"data": {
"bar": 2
},
"valid": true
},
{
"description": "non-object is valid",
"data": 1,
"valid": true
}
]
},
{
"description": "multiple dependents required",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"dependentRequired": {
"quux": [
"foo",
"bar"
]
},
"extensible": true
},
"tests": [
{
"description": "neither",
"data": {},
"valid": true
},
{
"description": "nondependants",
"data": {
"foo": 1,
"bar": 2
},
"valid": true
},
{
"description": "with dependencies",
"data": {
"foo": 1,
"bar": 2,
"quux": 3
},
"valid": true
},
{
"description": "missing dependency",
"data": {
"foo": 1,
"quux": 2
},
"valid": false
},
{
"description": "missing other dependency",
"data": {
"bar": 1,
"quux": 2
},
"valid": false
},
{
"description": "missing both dependencies",
"data": {
"quux": 1
},
"valid": false
}
]
},
{
"description": "dependencies with escaped characters",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"dependentRequired": {
"foo\nbar": [
"foo\rbar"
],
"foo\"bar": [
"foo'bar"
]
},
"extensible": true
},
"tests": [
{
"description": "CRLF",
"data": {
"foo\nbar": 1,
"foo\rbar": 2
},
"valid": true
},
{
"description": "quoted quotes",
"data": {
"foo'bar": 1,
"foo\"bar": 2
},
"valid": true
},
{
"description": "CRLF missing dependent",
"data": {
"foo\nbar": 1,
"foo": 2
},
"valid": false
},
{
"description": "quoted quotes missing dependent",
"data": {
"foo\"bar": 2
},
"valid": false
}
]
},
{
"description": "extensible: true allows extra properties in dependentRequired",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"dependentRequired": {
"bar": [
"foo"
]
},
"extensible": true
},
"tests": [
{
"description": "extra property is valid",
"data": {
"foo": 1,
"bar": 2,
"baz": 3
},
"valid": true
}
]
}
]

View File

@ -1,303 +0,0 @@
[
{
"description": "single dependency (STRICT)",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"foo": true,
"bar": true
},
"dependentSchemas": {
"bar": {
"properties": {
"foo": {
"type": "integer"
},
"bar": {
"type": "integer"
}
}
}
}
},
"tests": [
{
"description": "valid",
"data": {
"foo": 1,
"bar": 2
},
"valid": true
},
{
"description": "no dependency",
"data": {
"foo": "quux"
},
"valid": true
},
{
"description": "wrong type",
"data": {
"foo": "quux",
"bar": 2
},
"valid": false
},
{
"description": "wrong type other",
"data": {
"foo": 2,
"bar": "quux"
},
"valid": false
},
{
"description": "wrong type both",
"data": {
"foo": "quux",
"bar": "quux"
},
"valid": false
},
{
"description": "ignores arrays (invalid in strict mode)",
"data": [
"bar"
],
"valid": false,
"expect_errors": [
{
"code": "STRICT_ITEM_VIOLATION"
}
]
},
{
"description": "ignores strings (invalid in strict mode - wait, strings are scalars, strict only checks obj/arr)",
"data": "foobar",
"valid": true
},
{
"description": "ignores other non-objects",
"data": 12,
"valid": true
}
]
},
{
"description": "single dependency (EXTENSIBLE)",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"foo": true,
"bar": true
},
"dependentSchemas": {
"bar": {
"properties": {
"foo": {
"type": "integer"
},
"bar": {
"type": "integer"
}
}
}
},
"extensible": true
},
"tests": [
{
"description": "ignores arrays (valid in extensible mode)",
"data": [
"bar"
],
"valid": true
}
]
},
{
"description": "boolean subschemas",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"foo": true,
"bar": true
},
"dependentSchemas": {
"foo": true,
"bar": false
}
},
"tests": [
{
"description": "object with property having schema true is valid",
"data": {
"foo": 1
},
"valid": true
},
{
"description": "object with property having schema false is invalid",
"data": {
"bar": 2
},
"valid": false
},
{
"description": "object with both properties is invalid",
"data": {
"foo": 1,
"bar": 2
},
"valid": false
},
{
"description": "empty object is valid",
"data": {},
"valid": true
}
]
},
{
"description": "dependencies with escaped characters",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"foo\tbar": true,
"foo'bar": true,
"a": true,
"b": true,
"c": true
},
"dependentSchemas": {
"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
},
{
"description": "quoted quote",
"data": {
"foo'bar": {
"foo\"bar": 1
}
},
"valid": false
},
{
"description": "quoted tab invalid under dependent schema",
"data": {
"foo\tbar": 1,
"a": 2
},
"valid": false
},
{
"description": "quoted quote invalid under dependent schema",
"data": {
"foo'bar": 1
},
"valid": false
}
]
},
{
"description": "dependent subschema incompatible with root (STRICT)",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"foo": {},
"baz": true
},
"dependentSchemas": {
"foo": {
"properties": {
"bar": {}
}
}
}
},
"tests": [
{
"description": "matches root",
"data": {
"foo": 1
},
"valid": false
},
{
"description": "matches dependency (invalid in strict mode - bar not allowed if foo missing)",
"data": {
"bar": 1
},
"valid": false,
"expect_errors": [
{
"code": "STRICT_PROPERTY_VIOLATION"
}
]
},
{
"description": "matches both",
"data": {
"foo": 1,
"bar": 2
},
"valid": false
},
{
"description": "no dependency",
"data": {
"baz": 1
},
"valid": true
}
]
},
{
"description": "dependent subschema incompatible with root (EXTENSIBLE)",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"foo": {},
"baz": true
},
"dependentSchemas": {
"foo": {
"properties": {
"bar": {}
},
"additionalProperties": false
}
},
"extensible": true
},
"tests": [
{
"description": "matches dependency (valid in extensible mode)",
"data": {
"bar": 1
},
"valid": true
}
]
}
]

File diff suppressed because it is too large Load Diff

View File

@ -1,41 +1,45 @@
[ [
{ {
"description": "empty string is valid for all types (except const)", "description": "empty string is valid for all types (except const)",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"properties": { {
"obj": { "$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object" "properties": {
}, "obj": {
"arr": { "type": "object"
"type": "array" },
}, "arr": {
"str": { "type": "array"
"type": "string" },
}, "str": {
"int": { "type": "string"
"type": "integer" },
}, "int": {
"num": { "type": "integer"
"type": "number" },
}, "num": {
"bool": { "type": "number"
"type": "boolean" },
}, "bool": {
"nul": { "type": "boolean"
"type": "null" },
}, "nul": {
"fmt": { "type": "null"
"type": "string", },
"format": "uuid" "fmt": {
}, "type": "string",
"con": { "format": "uuid"
"const": "value" },
}, "con": {
"con_empty": { "const": "value"
"const": "" },
"con_empty": {
"const": ""
}
}
} }
} ]
}, },
"tests": [ "tests": [
{ {

View File

@ -1,12 +1,16 @@
[ [
{ {
"description": "simple enum validation", "description": "simple enum validation",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"enum": [ {
1, "$schema": "https://json-schema.org/draft/2020-12/schema",
2, "enum": [
3 1,
2,
3
]
}
] ]
}, },
"tests": [ "tests": [
@ -24,20 +28,24 @@
}, },
{ {
"description": "heterogeneous enum validation", "description": "heterogeneous enum validation",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"enum": [
6,
"foo",
[],
true,
{ {
"foo": 12 "$schema": "https://json-schema.org/draft/2020-12/schema",
"enum": [
6,
"foo",
[],
true,
{
"foo": 12
}
],
"properties": {
"foo": {}
}
} }
], ]
"properties": {
"foo": {}
}
}, },
"tests": [ "tests": [
{ {
@ -76,11 +84,15 @@
}, },
{ {
"description": "heterogeneous enum-with-null validation", "description": "heterogeneous enum-with-null validation",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"enum": [ {
6, "$schema": "https://json-schema.org/draft/2020-12/schema",
null "enum": [
6,
null
]
}
] ]
}, },
"tests": [ "tests": [
@ -103,23 +115,27 @@
}, },
{ {
"description": "enums in properties", "description": "enums in properties",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"type": "object", {
"properties": { "$schema": "https://json-schema.org/draft/2020-12/schema",
"foo": { "type": "object",
"enum": [ "properties": {
"foo" "foo": {
] "enum": [
}, "foo"
"bar": { ]
"enum": [ },
"bar": {
"enum": [
"bar"
]
}
},
"required": [
"bar" "bar"
] ]
} }
},
"required": [
"bar"
] ]
}, },
"tests": [ "tests": [
@ -170,11 +186,15 @@
}, },
{ {
"description": "enum with escaped characters", "description": "enum with escaped characters",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"enum": [ {
"foo\nbar", "$schema": "https://json-schema.org/draft/2020-12/schema",
"foo\rbar" "enum": [
"foo\nbar",
"foo\rbar"
]
}
] ]
}, },
"tests": [ "tests": [
@ -197,10 +217,14 @@
}, },
{ {
"description": "enum with false does not match 0", "description": "enum with false does not match 0",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"enum": [ {
false "$schema": "https://json-schema.org/draft/2020-12/schema",
"enum": [
false
]
}
] ]
}, },
"tests": [ "tests": [
@ -223,12 +247,16 @@
}, },
{ {
"description": "enum with [false] does not match [0]", "description": "enum with [false] does not match [0]",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"enum": [ {
[ "$schema": "https://json-schema.org/draft/2020-12/schema",
false "enum": [
] [
false
]
]
}
] ]
}, },
"tests": [ "tests": [
@ -257,10 +285,14 @@
}, },
{ {
"description": "enum with true does not match 1", "description": "enum with true does not match 1",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"enum": [ {
true "$schema": "https://json-schema.org/draft/2020-12/schema",
"enum": [
true
]
}
] ]
}, },
"tests": [ "tests": [
@ -283,12 +315,16 @@
}, },
{ {
"description": "enum with [true] does not match [1]", "description": "enum with [true] does not match [1]",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"enum": [ {
[ "$schema": "https://json-schema.org/draft/2020-12/schema",
true "enum": [
] [
true
]
]
}
] ]
}, },
"tests": [ "tests": [
@ -317,10 +353,14 @@
}, },
{ {
"description": "enum with 0 does not match false", "description": "enum with 0 does not match false",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"enum": [ {
0 "$schema": "https://json-schema.org/draft/2020-12/schema",
"enum": [
0
]
}
] ]
}, },
"tests": [ "tests": [
@ -343,12 +383,16 @@
}, },
{ {
"description": "enum with [0] does not match [false]", "description": "enum with [0] does not match [false]",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"enum": [ {
[ "$schema": "https://json-schema.org/draft/2020-12/schema",
0 "enum": [
] [
0
]
]
}
] ]
}, },
"tests": [ "tests": [
@ -377,10 +421,14 @@
}, },
{ {
"description": "enum with 1 does not match true", "description": "enum with 1 does not match true",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"enum": [ {
1 "$schema": "https://json-schema.org/draft/2020-12/schema",
"enum": [
1
]
}
] ]
}, },
"tests": [ "tests": [
@ -403,12 +451,16 @@
}, },
{ {
"description": "enum with [1] does not match [true]", "description": "enum with [1] does not match [true]",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"enum": [ {
[ "$schema": "https://json-schema.org/draft/2020-12/schema",
1 "enum": [
] [
1
]
]
}
] ]
}, },
"tests": [ "tests": [
@ -437,10 +489,14 @@
}, },
{ {
"description": "nul characters in strings", "description": "nul characters in strings",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"enum": [ {
"hello\u0000there" "$schema": "https://json-schema.org/draft/2020-12/schema",
"enum": [
"hello\u0000there"
]
}
] ]
}, },
"tests": [ "tests": [
@ -458,14 +514,18 @@
}, },
{ {
"description": "extensible: true allows extra properties in enum object match", "description": "extensible: true allows extra properties in enum object match",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"enum": [
{ {
"foo": 1 "$schema": "https://json-schema.org/draft/2020-12/schema",
"enum": [
{
"foo": 1
}
],
"extensible": true
} }
], ]
"extensible": true
}, },
"tests": [ "tests": [
{ {

View File

@ -1,9 +1,13 @@
[ [
{ {
"description": "exclusiveMaximum validation", "description": "exclusiveMaximum validation",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"exclusiveMaximum": 3.0 {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"exclusiveMaximum": 3.0
}
]
}, },
"tests": [ "tests": [
{ {
@ -28,4 +32,4 @@
} }
] ]
} }
] ]

View File

@ -1,9 +1,13 @@
[ [
{ {
"description": "exclusiveMinimum validation", "description": "exclusiveMinimum validation",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"exclusiveMinimum": 1.1 {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"exclusiveMinimum": 1.1
}
]
}, },
"tests": [ "tests": [
{ {
@ -28,4 +32,4 @@
} }
] ]
} }
] ]

202
tests/fixtures/families.json vendored Normal file
View File

@ -0,0 +1,202 @@
[
{
"description": "Entity families with dot patterns",
"database": {
"types": [
{
"name": "entity",
"hierarchy": [
"entity"
],
"schemas": [
{
"$id": "entity",
"type": "object",
"properties": {
"id": {
"type": "string"
},
"type": {
"type": "string",
"const": "entity"
}
}
},
{
"$id": "entity.light",
"$ref": "entity"
}
]
},
{
"name": "organization",
"hierarchy": [
"entity",
"organization"
],
"schemas": [
{
"$id": "organization",
"$ref": "entity",
"properties": {
"name": {
"type": "string"
}
}
}
]
},
{
"name": "person",
"hierarchy": [
"entity",
"organization",
"person"
],
"schemas": [
{
"$id": "person",
"$ref": "organization",
"properties": {
"first_name": {
"type": "string"
}
}
},
{
"$id": "person.light",
"$ref": "person"
}
]
}
],
"puncs": [
{
"name": "get_entities",
"schemas": [
{
"$id": "get_entities.response",
"$family": "entity"
}
]
},
{
"name": "get_light_entities",
"schemas": [
{
"$id": "get_light_entities.response",
"$family": "entity.light"
}
]
}
]
},
"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": "Dot pattern family matches entity.light",
"schema_id": "get_light_entities.response",
"data": {
"id": "3",
"type": "entity"
},
"valid": true
},
{
"description": "Dot pattern family matches person.light",
"schema_id": "get_light_entities.response",
"data": {
"id": "4",
"type": "person",
"name": "ACME",
"first_name": "John"
},
"valid": true
},
{
"description": "Dot pattern family excludes organization (missing .light schema, constraint violation)",
"schema_id": "get_light_entities.response",
"data": {
"id": "5",
"type": "organization",
"name": "ACME"
},
"valid": false
}
]
},
{
"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 does not implicitly match descendants (no magical implicit hierarchy on normal ad-hoc schemas)",
"schema_id": "get_widgets.response",
"data": {
"id": "1",
"widget_type": "special",
"special_feature": "yes"
},
"valid": false,
"expect_errors": [
{
"code": "FAMILY_MISMATCH",
"path": ""
}
]
}
]
}
]

View File

@ -1,9 +1,13 @@
[ [
{ {
"description": "validation of date-time strings", "description": "validation of date-time strings",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"format": "date-time" {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"format": "date-time"
}
]
}, },
"tests": [ "tests": [
{ {
@ -140,9 +144,13 @@
}, },
{ {
"description": "validation of date strings", "description": "validation of date strings",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"format": "date" {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"format": "date"
}
]
}, },
"tests": [ "tests": [
{ {
@ -389,9 +397,13 @@
}, },
{ {
"description": "validation of duration strings", "description": "validation of duration strings",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"format": "duration" {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"format": "duration"
}
]
}, },
"tests": [ "tests": [
{ {
@ -528,9 +540,13 @@
}, },
{ {
"description": "\\a is not an ECMA 262 control escape", "description": "\\a is not an ECMA 262 control escape",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"format": "regex" {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"format": "regex"
}
]
}, },
"tests": [ "tests": [
{ {
@ -542,9 +558,13 @@
}, },
{ {
"description": "validation of e-mail addresses", "description": "validation of e-mail addresses",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"format": "email" {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"format": "email"
}
]
}, },
"tests": [ "tests": [
{ {
@ -671,9 +691,13 @@
}, },
{ {
"description": "validation of host names", "description": "validation of host names",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"format": "hostname" {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"format": "hostname"
}
]
}, },
"tests": [ "tests": [
{ {
@ -801,9 +825,13 @@
}, },
{ {
"description": "validation of A-label (punycode) host names", "description": "validation of A-label (punycode) host names",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"format": "hostname" {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"format": "hostname"
}
]
}, },
"tests": [ "tests": [
{ {
@ -1030,9 +1058,13 @@
}, },
{ {
"description": "validation of an internationalized e-mail addresses", "description": "validation of an internationalized e-mail addresses",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"format": "idn-email" {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"format": "idn-email"
}
]
}, },
"tests": [ "tests": [
{ {
@ -1089,9 +1121,13 @@
}, },
{ {
"description": "validation of internationalized host names", "description": "validation of internationalized host names",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"format": "idn-hostname" {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"format": "idn-hostname"
}
]
}, },
"tests": [ "tests": [
{ {
@ -1430,9 +1466,13 @@
"quote": "Whenever dots are used as label separators, the following characters MUST be recognized as dots: U+002E (full stop), U+3002 (ideographic full stop), U+FF0E (fullwidth full stop), U+FF61(halfwidth ideographic full stop)" "quote": "Whenever dots are used as label separators, the following characters MUST be recognized as dots: U+002E (full stop), U+3002 (ideographic full stop), U+FF0E (fullwidth full stop), U+FF61(halfwidth ideographic full stop)"
} }
], ],
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"format": "idn-hostname" {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"format": "idn-hostname"
}
]
}, },
"tests": [ "tests": [
{ {
@ -1539,9 +1579,13 @@
}, },
{ {
"description": "validation of IP addresses", "description": "validation of IP addresses",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"format": "ipv4" {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"format": "ipv4"
}
]
}, },
"tests": [ "tests": [
{ {
@ -1629,9 +1673,13 @@
}, },
{ {
"description": "validation of IPv6 addresses", "description": "validation of IPv6 addresses",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"format": "ipv6" {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"format": "ipv6"
}
]
}, },
"tests": [ "tests": [
{ {
@ -1838,9 +1886,13 @@
}, },
{ {
"description": "validation of IRI References", "description": "validation of IRI References",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"format": "iri-reference" {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"format": "iri-reference"
}
]
}, },
"tests": [ "tests": [
{ {
@ -1912,9 +1964,13 @@
}, },
{ {
"description": "validation of IRIs", "description": "validation of IRIs",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"format": "iri" {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"format": "iri"
}
]
}, },
"tests": [ "tests": [
{ {
@ -1996,9 +2052,13 @@
}, },
{ {
"description": "validation of JSON-pointers (JSON String Representation)", "description": "validation of JSON-pointers (JSON String Representation)",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"format": "json-pointer" {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"format": "json-pointer"
}
]
}, },
"tests": [ "tests": [
{ {
@ -2195,9 +2255,13 @@
}, },
{ {
"description": "validation of regular expressions", "description": "validation of regular expressions",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"format": "regex" {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"format": "regex"
}
]
}, },
"tests": [ "tests": [
{ {
@ -2244,9 +2308,13 @@
}, },
{ {
"description": "validation of Relative JSON Pointers (RJP)", "description": "validation of Relative JSON Pointers (RJP)",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"format": "relative-json-pointer" {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"format": "relative-json-pointer"
}
]
}, },
"tests": [ "tests": [
{ {
@ -2343,9 +2411,13 @@
}, },
{ {
"description": "validation of time strings", "description": "validation of time strings",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"format": "time" {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"format": "time"
}
]
}, },
"tests": [ "tests": [
{ {
@ -2582,9 +2654,13 @@
}, },
{ {
"description": "unknown format", "description": "unknown format",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"format": "unknown" {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"format": "unknown"
}
]
}, },
"tests": [ "tests": [
{ {
@ -2626,9 +2702,13 @@
}, },
{ {
"description": "validation of URI References", "description": "validation of URI References",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"format": "uri-reference" {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"format": "uri-reference"
}
]
}, },
"tests": [ "tests": [
{ {
@ -2710,9 +2790,13 @@
}, },
{ {
"description": "format: uri-template", "description": "format: uri-template",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"format": "uri-template" {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"format": "uri-template"
}
]
}, },
"tests": [ "tests": [
{ {
@ -2769,9 +2853,13 @@
}, },
{ {
"description": "validation of URIs", "description": "validation of URIs",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"format": "uri" {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"format": "uri"
}
]
}, },
"tests": [ "tests": [
{ {
@ -2958,9 +3046,13 @@
}, },
{ {
"description": "uuid format", "description": "uuid format",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"format": "uuid" {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"format": "uuid"
}
]
}, },
"tests": [ "tests": [
{ {
@ -3077,9 +3169,13 @@
}, },
{ {
"description": "period format", "description": "period format",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"format": "period" {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"format": "period"
}
]
}, },
"tests": [ "tests": [
{ {

View File

@ -1,11 +1,15 @@
[ [
{ {
"description": "ignore if without then or else", "description": "ignore if without then or else",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"if": { {
"const": 0 "$schema": "https://json-schema.org/draft/2020-12/schema",
} "if": {
"const": 0
}
}
]
}, },
"tests": [ "tests": [
{ {
@ -22,11 +26,15 @@
}, },
{ {
"description": "ignore then without if", "description": "ignore then without if",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"then": { {
"const": 0 "$schema": "https://json-schema.org/draft/2020-12/schema",
} "then": {
"const": 0
}
}
]
}, },
"tests": [ "tests": [
{ {
@ -43,11 +51,15 @@
}, },
{ {
"description": "ignore else without if", "description": "ignore else without if",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"else": { {
"const": 0 "$schema": "https://json-schema.org/draft/2020-12/schema",
} "else": {
"const": 0
}
}
]
}, },
"tests": [ "tests": [
{ {
@ -64,14 +76,18 @@
}, },
{ {
"description": "if and then without else", "description": "if and then without else",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"if": { {
"exclusiveMaximum": 0 "$schema": "https://json-schema.org/draft/2020-12/schema",
}, "if": {
"then": { "exclusiveMaximum": 0
"minimum": -10 },
} "then": {
"minimum": -10
}
}
]
}, },
"tests": [ "tests": [
{ {
@ -93,14 +109,18 @@
}, },
{ {
"description": "if and else without then", "description": "if and else without then",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"if": { {
"exclusiveMaximum": 0 "$schema": "https://json-schema.org/draft/2020-12/schema",
}, "if": {
"else": { "exclusiveMaximum": 0
"multipleOf": 2 },
} "else": {
"multipleOf": 2
}
}
]
}, },
"tests": [ "tests": [
{ {
@ -122,17 +142,21 @@
}, },
{ {
"description": "validate against correct branch, then vs else", "description": "validate against correct branch, then vs else",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"if": { {
"exclusiveMaximum": 0 "$schema": "https://json-schema.org/draft/2020-12/schema",
}, "if": {
"then": { "exclusiveMaximum": 0
"minimum": -10 },
}, "then": {
"else": { "minimum": -10
"multipleOf": 2 },
} "else": {
"multipleOf": 2
}
}
]
}, },
"tests": [ "tests": [
{ {
@ -159,23 +183,27 @@
}, },
{ {
"description": "non-interference across combined schemas", "description": "non-interference across combined schemas",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"allOf": [
{ {
"if": { "$schema": "https://json-schema.org/draft/2020-12/schema",
"exclusiveMaximum": 0 "allOf": [
} {
}, "if": {
{ "exclusiveMaximum": 0
"then": { }
"minimum": -10 },
} {
}, "then": {
{ "minimum": -10
"else": { }
"multipleOf": 2 },
} {
"else": {
"multipleOf": 2
}
}
]
} }
] ]
}, },
@ -194,15 +222,19 @@
}, },
{ {
"description": "if with boolean schema true", "description": "if with boolean schema true",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"if": true, {
"then": { "$schema": "https://json-schema.org/draft/2020-12/schema",
"const": "then" "if": true,
}, "then": {
"else": { "const": "then"
"const": "else" },
} "else": {
"const": "else"
}
}
]
}, },
"tests": [ "tests": [
{ {
@ -219,15 +251,19 @@
}, },
{ {
"description": "if with boolean schema false", "description": "if with boolean schema false",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"if": false, {
"then": { "$schema": "https://json-schema.org/draft/2020-12/schema",
"const": "then" "if": false,
}, "then": {
"else": { "const": "then"
"const": "else" },
} "else": {
"const": "else"
}
}
]
}, },
"tests": [ "tests": [
{ {
@ -244,17 +280,21 @@
}, },
{ {
"description": "if appears at the end when serialized (keyword processing sequence)", "description": "if appears at the end when serialized (keyword processing sequence)",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"then": { {
"const": "yes" "$schema": "https://json-schema.org/draft/2020-12/schema",
}, "then": {
"else": { "const": "yes"
"const": "other" },
}, "else": {
"if": { "const": "other"
"maxLength": 4 },
} "if": {
"maxLength": 4
}
}
]
}, },
"tests": [ "tests": [
{ {
@ -281,11 +321,15 @@
}, },
{ {
"description": "then: false fails when condition matches", "description": "then: false fails when condition matches",
"schema": { "database": {
"if": { "schemas": [
"const": 1 {
}, "if": {
"then": false "const": 1
},
"then": false
}
]
}, },
"tests": [ "tests": [
{ {
@ -302,11 +346,15 @@
}, },
{ {
"description": "else: false fails when condition does not match", "description": "else: false fails when condition does not match",
"schema": { "database": {
"if": { "schemas": [
"const": 1 {
}, "if": {
"else": false "const": 1
},
"else": false
}
]
}, },
"tests": [ "tests": [
{ {
@ -323,29 +371,33 @@
}, },
{ {
"description": "extensible: true allows extra properties in if-then-else", "description": "extensible: true allows extra properties in if-then-else",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"if": { {
"properties": { "$schema": "https://json-schema.org/draft/2020-12/schema",
"foo": { "if": {
"const": 1 "properties": {
} "foo": {
}, "const": 1
"required": [ }
"foo" },
] "required": [
}, "foo"
"then": { ]
"properties": { },
"bar": { "then": {
"const": 2 "properties": {
} "bar": {
}, "const": 2
"required": [ }
"bar" },
] "required": [
}, "bar"
"extensible": true ]
},
"extensible": true
}
]
}, },
"tests": [ "tests": [
{ {
@ -361,25 +413,29 @@
}, },
{ {
"description": "strict by default with if-then properties", "description": "strict by default with if-then properties",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"if": { {
"properties": { "$schema": "https://json-schema.org/draft/2020-12/schema",
"foo": { "if": {
"const": 1 "properties": {
} "foo": {
}, "const": 1
"required": [ }
"foo" },
] "required": [
}, "foo"
"then": { ]
"properties": { },
"bar": { "then": {
"const": 2 "properties": {
"bar": {
"const": 2
}
}
} }
} }
} ]
}, },
"tests": [ "tests": [
{ {

View File

@ -1,11 +1,15 @@
[ [
{ {
"description": "a schema given for items", "description": "a schema given for items",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"items": { {
"type": "integer" "$schema": "https://json-schema.org/draft/2020-12/schema",
} "items": {
"type": "integer"
}
}
]
}, },
"tests": [ "tests": [
{ {
@ -44,9 +48,13 @@
}, },
{ {
"description": "items with boolean schema (true)", "description": "items with boolean schema (true)",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"items": true {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"items": true
}
]
}, },
"tests": [ "tests": [
{ {
@ -67,9 +75,13 @@
}, },
{ {
"description": "items with boolean schema (false)", "description": "items with boolean schema (false)",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"items": false {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"items": false
}
]
}, },
"tests": [ "tests": [
{ {
@ -90,39 +102,43 @@
}, },
{ {
"description": "items and subitems", "description": "items and subitems",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"$defs": { {
"item": { "$schema": "https://json-schema.org/draft/2020-12/schema",
"$defs": {
"item": {
"type": "array",
"items": false,
"prefixItems": [
{
"$ref": "#/$defs/sub-item"
},
{
"$ref": "#/$defs/sub-item"
}
]
},
"sub-item": {
"type": "object",
"required": [
"foo"
]
}
},
"type": "array", "type": "array",
"items": false, "items": false,
"prefixItems": [ "prefixItems": [
{ {
"$ref": "#/$defs/sub-item" "$ref": "#/$defs/item"
}, },
{ {
"$ref": "#/$defs/sub-item" "$ref": "#/$defs/item"
},
{
"$ref": "#/$defs/item"
} }
] ]
},
"sub-item": {
"type": "object",
"required": [
"foo"
]
}
},
"type": "array",
"items": false,
"prefixItems": [
{
"$ref": "#/$defs/item"
},
{
"$ref": "#/$defs/item"
},
{
"$ref": "#/$defs/item"
} }
] ]
}, },
@ -301,21 +317,25 @@
}, },
{ {
"description": "nested items", "description": "nested items",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"type": "array", {
"items": { "$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "array",
"items": {
"type": "array", "type": "array",
"items": { "items": {
"type": "array", "type": "array",
"items": { "items": {
"type": "number" "type": "array",
"items": {
"type": "array",
"items": {
"type": "number"
}
}
} }
} }
} }
} ]
}, },
"tests": [ "tests": [
{ {
@ -418,14 +438,18 @@
}, },
{ {
"description": "prefixItems with no additional items allowed", "description": "prefixItems with no additional items allowed",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"prefixItems": [ {
{}, "$schema": "https://json-schema.org/draft/2020-12/schema",
{}, "prefixItems": [
{} {},
], {},
"items": false {}
],
"items": false
}
]
}, },
"tests": [ "tests": [
{ {
@ -471,20 +495,24 @@
}, },
{ {
"description": "items does not look in applicators, valid case", "description": "items does not look in applicators, valid case",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"allOf": [
{ {
"prefixItems": [ "$schema": "https://json-schema.org/draft/2020-12/schema",
"allOf": [
{ {
"minimum": 3 "prefixItems": [
{
"minimum": 3
}
]
} }
] ],
"items": {
"minimum": 5
}
} }
], ]
"items": {
"minimum": 5
}
}, },
"tests": [ "tests": [
{ {
@ -507,16 +535,20 @@
}, },
{ {
"description": "prefixItems validation adjusts the starting index for items", "description": "prefixItems validation adjusts the starting index for items",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"prefixItems": [
{ {
"type": "string" "$schema": "https://json-schema.org/draft/2020-12/schema",
"prefixItems": [
{
"type": "string"
}
],
"items": {
"type": "integer"
}
} }
], ]
"items": {
"type": "integer"
}
}, },
"tests": [ "tests": [
{ {
@ -540,12 +572,16 @@
}, },
{ {
"description": "items with heterogeneous array", "description": "items with heterogeneous array",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"prefixItems": [ {
{} "$schema": "https://json-schema.org/draft/2020-12/schema",
], "prefixItems": [
"items": false {}
],
"items": false
}
]
}, },
"tests": [ "tests": [
{ {
@ -568,11 +604,15 @@
}, },
{ {
"description": "items with null instance elements", "description": "items with null instance elements",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"items": { {
"type": "null" "$schema": "https://json-schema.org/draft/2020-12/schema",
} "items": {
"type": "null"
}
}
]
}, },
"tests": [ "tests": [
{ {
@ -586,10 +626,14 @@
}, },
{ {
"description": "extensible: true allows extra items (when items is false)", "description": "extensible: true allows extra items (when items is false)",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"items": false, {
"extensible": true "$schema": "https://json-schema.org/draft/2020-12/schema",
"items": false,
"extensible": true
}
]
}, },
"tests": [ "tests": [
{ {
@ -603,12 +647,16 @@
}, },
{ {
"description": "extensible: true allows extra properties for items", "description": "extensible: true allows extra properties for items",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"items": { {
"minimum": 5 "$schema": "https://json-schema.org/draft/2020-12/schema",
}, "items": {
"extensible": true "minimum": 5
},
"extensible": true
}
]
}, },
"tests": [ "tests": [
{ {
@ -630,10 +678,14 @@
}, },
{ {
"description": "array: simple extensible array", "description": "array: simple extensible array",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"type": "array", {
"extensible": true "$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "array",
"extensible": true
}
]
}, },
"tests": [ "tests": [
{ {
@ -653,10 +705,14 @@
}, },
{ {
"description": "array: strict array", "description": "array: strict array",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"type": "array", {
"extensible": false "$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "array",
"extensible": false
}
]
}, },
"tests": [ "tests": [
{ {
@ -675,12 +731,16 @@
}, },
{ {
"description": "array: items extensible", "description": "array: items extensible",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"type": "array", {
"items": { "$schema": "https://json-schema.org/draft/2020-12/schema",
"extensible": true "type": "array",
} "items": {
"extensible": true
}
}
]
}, },
"tests": [ "tests": [
{ {
@ -701,13 +761,17 @@
}, },
{ {
"description": "array: items strict", "description": "array: items strict",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"type": "array", {
"items": { "$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object", "type": "array",
"extensible": false "items": {
} "type": "object",
"extensible": false
}
}
]
}, },
"tests": [ "tests": [
{ {

View File

@ -1,21 +1,25 @@
[ [
{ {
"description": "Masking Properties", "description": "Masking Properties",
"schema": { "database": {
"$id": "mask_properties", "schemas": [
"type": "object", {
"properties": { "$id": "mask_properties",
"foo": { "type": "object",
"type": "string" "properties": {
}, "foo": {
"bar": { "type": "string"
"type": "integer" },
"bar": {
"type": "integer"
}
},
"required": [
"foo"
],
"extensible": false
} }
}, ]
"required": [
"foo"
],
"extensible": false
}, },
"tests": [ "tests": [
{ {
@ -58,21 +62,25 @@
}, },
{ {
"description": "Masking Nested Objects", "description": "Masking Nested Objects",
"schema": { "database": {
"$id": "mask_nested", "schemas": [
"type": "object", {
"properties": { "$id": "mask_nested",
"meta": {
"type": "object", "type": "object",
"properties": { "properties": {
"id": { "meta": {
"type": "integer" "type": "object",
"properties": {
"id": {
"type": "integer"
}
},
"extensible": false
} }
}, },
"extensible": false "extensible": false
} }
}, ]
"extensible": false
}, },
"tests": [ "tests": [
{ {
@ -95,18 +103,22 @@
}, },
{ {
"description": "Masking Arrays", "description": "Masking Arrays",
"schema": { "database": {
"$id": "mask_arrays", "schemas": [
"type": "object", {
"properties": { "$id": "mask_arrays",
"tags": { "type": "object",
"type": "array", "properties": {
"items": { "tags": {
"type": "string" "type": "array",
} "items": {
"type": "string"
}
}
},
"extensible": false
} }
}, ]
"extensible": false
}, },
"tests": [ "tests": [
{ {
@ -129,23 +141,27 @@
}, },
{ {
"description": "Masking Tuple Arrays (prefixItems)", "description": "Masking Tuple Arrays (prefixItems)",
"schema": { "database": {
"$id": "mask_tuple", "schemas": [
"type": "object", {
"properties": { "$id": "mask_tuple",
"coord": { "type": "object",
"type": "array", "properties": {
"prefixItems": [ "coord": {
{ "type": "array",
"type": "number" "prefixItems": [
}, {
{ "type": "number"
"type": "number" },
{
"type": "number"
}
]
} }
] },
"extensible": false
} }
}, ]
"extensible": false
}, },
"tests": [ "tests": [
{ {

View File

@ -1,10 +1,14 @@
[ [
{ {
"description": "maxContains without contains is ignored", "description": "maxContains without contains is ignored",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"maxContains": 1, {
"extensible": true "$schema": "https://json-schema.org/draft/2020-12/schema",
"maxContains": 1,
"extensible": true
}
]
}, },
"tests": [ "tests": [
{ {
@ -26,13 +30,17 @@
}, },
{ {
"description": "maxContains with contains", "description": "maxContains with contains",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"contains": { {
"const": 1 "$schema": "https://json-schema.org/draft/2020-12/schema",
}, "contains": {
"maxContains": 1, "const": 1
"extensible": true },
"maxContains": 1,
"extensible": true
}
]
}, },
"tests": [ "tests": [
{ {
@ -76,13 +84,17 @@
}, },
{ {
"description": "maxContains with contains, value with a decimal", "description": "maxContains with contains, value with a decimal",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"contains": { {
"const": 1 "$schema": "https://json-schema.org/draft/2020-12/schema",
}, "contains": {
"maxContains": 1.0, "const": 1
"extensible": true },
"maxContains": 1.0,
"extensible": true
}
]
}, },
"tests": [ "tests": [
{ {
@ -104,14 +116,18 @@
}, },
{ {
"description": "minContains < maxContains", "description": "minContains < maxContains",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"contains": { {
"const": 1 "$schema": "https://json-schema.org/draft/2020-12/schema",
}, "contains": {
"minContains": 1, "const": 1
"maxContains": 3, },
"extensible": true "minContains": 1,
"maxContains": 3,
"extensible": true
}
]
}, },
"tests": [ "tests": [
{ {
@ -141,13 +157,17 @@
}, },
{ {
"description": "extensible: true allows non-matching items in maxContains", "description": "extensible: true allows non-matching items in maxContains",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"contains": { {
"const": 1 "$schema": "https://json-schema.org/draft/2020-12/schema",
}, "contains": {
"maxContains": 1, "const": 1
"extensible": true },
"maxContains": 1,
"extensible": true
}
]
}, },
"tests": [ "tests": [
{ {

View File

@ -1,10 +1,14 @@
[ [
{ {
"description": "maxItems validation", "description": "maxItems validation",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"maxItems": 2, {
"extensible": true "$schema": "https://json-schema.org/draft/2020-12/schema",
"maxItems": 2,
"extensible": true
}
]
}, },
"tests": [ "tests": [
{ {
@ -40,10 +44,14 @@
}, },
{ {
"description": "maxItems validation with a decimal", "description": "maxItems validation with a decimal",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"maxItems": 2.0, {
"extensible": true "$schema": "https://json-schema.org/draft/2020-12/schema",
"maxItems": 2.0,
"extensible": true
}
]
}, },
"tests": [ "tests": [
{ {
@ -66,10 +74,14 @@
}, },
{ {
"description": "extensible: true allows extra items in maxItems (but counted)", "description": "extensible: true allows extra items in maxItems (but counted)",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"maxItems": 2, {
"extensible": true "$schema": "https://json-schema.org/draft/2020-12/schema",
"maxItems": 2,
"extensible": true
}
]
}, },
"tests": [ "tests": [
{ {

View File

@ -1,9 +1,13 @@
[ [
{ {
"description": "maxLength validation", "description": "maxLength validation",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"maxLength": 2 {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"maxLength": 2
}
]
}, },
"tests": [ "tests": [
{ {
@ -35,9 +39,13 @@
}, },
{ {
"description": "maxLength validation with a decimal", "description": "maxLength validation with a decimal",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"maxLength": 2.0 {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"maxLength": 2.0
}
]
}, },
"tests": [ "tests": [
{ {
@ -52,4 +60,4 @@
} }
] ]
} }
] ]

View File

@ -1,10 +1,14 @@
[ [
{ {
"description": "maxProperties validation", "description": "maxProperties validation",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"maxProperties": 2, {
"extensible": true "$schema": "https://json-schema.org/draft/2020-12/schema",
"maxProperties": 2,
"extensible": true
}
]
}, },
"tests": [ "tests": [
{ {
@ -54,10 +58,14 @@
}, },
{ {
"description": "maxProperties validation with a decimal", "description": "maxProperties validation with a decimal",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"maxProperties": 2.0, {
"extensible": true "$schema": "https://json-schema.org/draft/2020-12/schema",
"maxProperties": 2.0,
"extensible": true
}
]
}, },
"tests": [ "tests": [
{ {
@ -80,10 +88,14 @@
}, },
{ {
"description": "maxProperties = 0 means the object is empty", "description": "maxProperties = 0 means the object is empty",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"maxProperties": 0, {
"extensible": true "$schema": "https://json-schema.org/draft/2020-12/schema",
"maxProperties": 0,
"extensible": true
}
]
}, },
"tests": [ "tests": [
{ {
@ -102,10 +114,14 @@
}, },
{ {
"description": "extensible: true allows extra properties in maxProperties (though maxProperties still counts them!)", "description": "extensible: true allows extra properties in maxProperties (though maxProperties still counts them!)",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"maxProperties": 2, {
"extensible": true "$schema": "https://json-schema.org/draft/2020-12/schema",
"maxProperties": 2,
"extensible": true
}
]
}, },
"tests": [ "tests": [
{ {

View File

@ -1,9 +1,13 @@
[ [
{ {
"description": "maximum validation", "description": "maximum validation",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"maximum": 3.0 {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"maximum": 3.0
}
]
}, },
"tests": [ "tests": [
{ {
@ -30,11 +34,15 @@
}, },
{ {
"description": "maximum validation with unsigned integer", "description": "maximum validation with unsigned integer",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"maximum": 300 {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"maximum": 300
}
]
}, },
"tests": [ "tests": [
{ {
"description": "below the maximum is invalid", "description": "below the maximum is invalid",
"data": 299.97, "data": 299.97,
@ -57,4 +65,4 @@
} }
] ]
} }
] ]

View File

@ -1,23 +1,27 @@
[ [
{ {
"description": "merging: properties accumulate", "description": "merging: properties accumulate",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"$defs": { {
"base": { "$schema": "https://json-schema.org/draft/2020-12/schema",
"$defs": {
"base": {
"properties": {
"base_prop": {
"type": "string"
}
}
}
},
"$ref": "#/$defs/base",
"properties": { "properties": {
"base_prop": { "child_prop": {
"type": "string" "type": "string"
} }
} }
} }
}, ]
"$ref": "#/$defs/base",
"properties": {
"child_prop": {
"type": "string"
}
}
}, },
"tests": [ "tests": [
{ {
@ -46,28 +50,32 @@
}, },
{ {
"description": "merging: required fields accumulate", "description": "merging: required fields accumulate",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"$defs": { {
"base": { "$schema": "https://json-schema.org/draft/2020-12/schema",
"$defs": {
"base": {
"properties": {
"a": {
"type": "string"
}
},
"required": [
"a"
]
}
},
"$ref": "#/$defs/base",
"properties": { "properties": {
"a": { "b": {
"type": "string" "type": "string"
} }
}, },
"required": [ "required": [
"a" "b"
] ]
} }
},
"$ref": "#/$defs/base",
"properties": {
"b": {
"type": "string"
}
},
"required": [
"b"
] ]
}, },
"tests": [ "tests": [
@ -109,36 +117,40 @@
}, },
{ {
"description": "merging: dependencies accumulate", "description": "merging: dependencies accumulate",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"$defs": { {
"base": { "$schema": "https://json-schema.org/draft/2020-12/schema",
"$defs": {
"base": {
"properties": {
"trigger": {
"type": "string"
},
"base_dep": {
"type": "string"
}
},
"dependencies": {
"trigger": [
"base_dep"
]
}
}
},
"$ref": "#/$defs/base",
"properties": { "properties": {
"trigger": { "child_dep": {
"type": "string"
},
"base_dep": {
"type": "string" "type": "string"
} }
}, },
"dependencies": { "dependencies": {
"trigger": [ "trigger": [
"base_dep" "child_dep"
] ]
} }
} }
}, ]
"$ref": "#/$defs/base",
"properties": {
"child_dep": {
"type": "string"
}
},
"dependencies": {
"trigger": [
"child_dep"
]
}
}, },
"tests": [ "tests": [
{ {
@ -182,32 +194,36 @@
}, },
{ {
"description": "merging: form and display do NOT merge", "description": "merging: form and display do NOT merge",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"$defs": { {
"base": { "$schema": "https://json-schema.org/draft/2020-12/schema",
"$defs": {
"base": {
"properties": {
"a": {
"type": "string"
},
"b": {
"type": "string"
}
},
"form": [
"a",
"b"
]
}
},
"$ref": "#/$defs/base",
"properties": { "properties": {
"a": { "c": {
"type": "string"
},
"b": {
"type": "string" "type": "string"
} }
}, },
"form": [ "form": [
"a", "c"
"b"
] ]
} }
},
"$ref": "#/$defs/base",
"properties": {
"c": {
"type": "string"
}
},
"form": [
"c"
] ]
}, },
"tests": [ "tests": [

View File

@ -1,10 +1,14 @@
[ [
{ {
"description": "minContains without contains is ignored", "description": "minContains without contains is ignored",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"minContains": 1, {
"extensible": true "$schema": "https://json-schema.org/draft/2020-12/schema",
"minContains": 1,
"extensible": true
}
]
}, },
"tests": [ "tests": [
{ {
@ -23,13 +27,17 @@
}, },
{ {
"description": "minContains=1 with contains", "description": "minContains=1 with contains",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"contains": { {
"const": 1 "$schema": "https://json-schema.org/draft/2020-12/schema",
}, "contains": {
"minContains": 1, "const": 1
"extensible": true },
"minContains": 1,
"extensible": true
}
]
}, },
"tests": [ "tests": [
{ {
@ -71,13 +79,17 @@
}, },
{ {
"description": "minContains=2 with contains", "description": "minContains=2 with contains",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"contains": { {
"const": 1 "$schema": "https://json-schema.org/draft/2020-12/schema",
}, "contains": {
"minContains": 2, "const": 1
"extensible": true },
"minContains": 2,
"extensible": true
}
]
}, },
"tests": [ "tests": [
{ {
@ -130,13 +142,17 @@
}, },
{ {
"description": "minContains=2 with contains with a decimal value", "description": "minContains=2 with contains with a decimal value",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"contains": { {
"const": 1 "$schema": "https://json-schema.org/draft/2020-12/schema",
}, "contains": {
"minContains": 2.0, "const": 1
"extensible": true },
"minContains": 2.0,
"extensible": true
}
]
}, },
"tests": [ "tests": [
{ {
@ -158,14 +174,18 @@
}, },
{ {
"description": "maxContains = minContains", "description": "maxContains = minContains",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"contains": { {
"const": 1 "$schema": "https://json-schema.org/draft/2020-12/schema",
}, "contains": {
"maxContains": 2, "const": 1
"minContains": 2, },
"extensible": true "maxContains": 2,
"minContains": 2,
"extensible": true
}
]
}, },
"tests": [ "tests": [
{ {
@ -201,14 +221,18 @@
}, },
{ {
"description": "maxContains < minContains", "description": "maxContains < minContains",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"contains": { {
"const": 1 "$schema": "https://json-schema.org/draft/2020-12/schema",
}, "contains": {
"maxContains": 1, "const": 1
"minContains": 3, },
"extensible": true "maxContains": 1,
"minContains": 3,
"extensible": true
}
]
}, },
"tests": [ "tests": [
{ {
@ -244,13 +268,17 @@
}, },
{ {
"description": "minContains = 0", "description": "minContains = 0",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"contains": { {
"const": 1 "$schema": "https://json-schema.org/draft/2020-12/schema",
}, "contains": {
"minContains": 0, "const": 1
"extensible": true },
"minContains": 0,
"extensible": true
}
]
}, },
"tests": [ "tests": [
{ {
@ -269,14 +297,18 @@
}, },
{ {
"description": "minContains = 0 with maxContains", "description": "minContains = 0 with maxContains",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"contains": { {
"const": 1 "$schema": "https://json-schema.org/draft/2020-12/schema",
}, "contains": {
"minContains": 0, "const": 1
"maxContains": 1, },
"extensible": true "minContains": 0,
"maxContains": 1,
"extensible": true
}
]
}, },
"tests": [ "tests": [
{ {
@ -303,13 +335,17 @@
}, },
{ {
"description": "extensible: true allows non-matching items in minContains", "description": "extensible: true allows non-matching items in minContains",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"contains": { {
"const": 1 "$schema": "https://json-schema.org/draft/2020-12/schema",
}, "contains": {
"minContains": 1, "const": 1
"extensible": true },
"minContains": 1,
"extensible": true
}
]
}, },
"tests": [ "tests": [
{ {

View File

@ -1,10 +1,14 @@
[ [
{ {
"description": "minItems validation", "description": "minItems validation",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"minItems": 1, {
"extensible": true "$schema": "https://json-schema.org/draft/2020-12/schema",
"minItems": 1,
"extensible": true
}
]
}, },
"tests": [ "tests": [
{ {
@ -36,10 +40,14 @@
}, },
{ {
"description": "minItems validation with a decimal", "description": "minItems validation with a decimal",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"minItems": 1.0, {
"extensible": true "$schema": "https://json-schema.org/draft/2020-12/schema",
"minItems": 1.0,
"extensible": true
}
]
}, },
"tests": [ "tests": [
{ {
@ -59,10 +67,14 @@
}, },
{ {
"description": "extensible: true allows extra items in minItems", "description": "extensible: true allows extra items in minItems",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"minItems": 1, {
"extensible": true "$schema": "https://json-schema.org/draft/2020-12/schema",
"minItems": 1,
"extensible": true
}
]
}, },
"tests": [ "tests": [
{ {

View File

@ -1,9 +1,13 @@
[ [
{ {
"description": "minLength validation", "description": "minLength validation",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"minLength": 2 {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"minLength": 2
}
]
}, },
"tests": [ "tests": [
{ {
@ -35,9 +39,13 @@
}, },
{ {
"description": "minLength validation with a decimal", "description": "minLength validation with a decimal",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"minLength": 2.0 {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"minLength": 2.0
}
]
}, },
"tests": [ "tests": [
{ {
@ -52,4 +60,4 @@
} }
] ]
} }
] ]

View File

@ -1,10 +1,14 @@
[ [
{ {
"description": "minProperties validation", "description": "minProperties validation",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"minProperties": 1, {
"extensible": true "$schema": "https://json-schema.org/draft/2020-12/schema",
"minProperties": 1,
"extensible": true
}
]
}, },
"tests": [ "tests": [
{ {
@ -46,10 +50,14 @@
}, },
{ {
"description": "minProperties validation with a decimal", "description": "minProperties validation with a decimal",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"minProperties": 1.0, {
"extensible": true "$schema": "https://json-schema.org/draft/2020-12/schema",
"minProperties": 1.0,
"extensible": true
}
]
}, },
"tests": [ "tests": [
{ {
@ -69,10 +77,14 @@
}, },
{ {
"description": "extensible: true allows extra properties in minProperties", "description": "extensible: true allows extra properties in minProperties",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"minProperties": 1, {
"extensible": true "$schema": "https://json-schema.org/draft/2020-12/schema",
"minProperties": 1,
"extensible": true
}
]
}, },
"tests": [ "tests": [
{ {

View File

@ -1,9 +1,13 @@
[ [
{ {
"description": "minimum validation", "description": "minimum validation",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"minimum": 1.1 {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"minimum": 1.1
}
]
}, },
"tests": [ "tests": [
{ {
@ -30,9 +34,13 @@
}, },
{ {
"description": "minimum validation with signed integer", "description": "minimum validation with signed integer",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"minimum": -2 {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"minimum": -2
}
]
}, },
"tests": [ "tests": [
{ {
@ -72,4 +80,4 @@
} }
] ]
} }
] ]

View File

@ -1,9 +1,13 @@
[ [
{ {
"description": "by int", "description": "by int",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"multipleOf": 2 {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"multipleOf": 2
}
]
}, },
"tests": [ "tests": [
{ {
@ -25,9 +29,13 @@
}, },
{ {
"description": "by number", "description": "by number",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"multipleOf": 1.5 {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"multipleOf": 1.5
}
]
}, },
"tests": [ "tests": [
{ {
@ -49,9 +57,13 @@
}, },
{ {
"description": "by small number", "description": "by small number",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"multipleOf": 0.0001 {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"multipleOf": 0.0001
}
]
}, },
"tests": [ "tests": [
{ {
@ -68,10 +80,14 @@
}, },
{ {
"description": "small multiple of large integer", "description": "small multiple of large integer",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"type": "integer", {
"multipleOf": 1e-8 "$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "integer",
"multipleOf": 1e-8
}
]
}, },
"tests": [ "tests": [
{ {

View File

@ -1,11 +1,15 @@
[ [
{ {
"description": "not", "description": "not",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"not": { {
"type": "integer" "$schema": "https://json-schema.org/draft/2020-12/schema",
} "not": {
"type": "integer"
}
}
]
}, },
"tests": [ "tests": [
{ {
@ -22,14 +26,18 @@
}, },
{ {
"description": "not multiple types", "description": "not multiple types",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"not": { {
"type": [ "$schema": "https://json-schema.org/draft/2020-12/schema",
"integer", "not": {
"boolean" "type": [
] "integer",
} "boolean"
]
}
}
]
}, },
"tests": [ "tests": [
{ {
@ -51,17 +59,21 @@
}, },
{ {
"description": "not more complex schema", "description": "not more complex schema",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"not": { {
"type": "object", "$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": { "not": {
"foo": { "type": "object",
"type": "string" "properties": {
} "foo": {
"type": "string"
}
}
},
"extensible": true
} }
}, ]
"extensible": true
}, },
"tests": [ "tests": [
{ {
@ -87,13 +99,17 @@
}, },
{ {
"description": "forbidden property", "description": "forbidden property",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"properties": { {
"foo": { "$schema": "https://json-schema.org/draft/2020-12/schema",
"not": {} "properties": {
"foo": {
"not": {}
}
}
} }
} ]
}, },
"tests": [ "tests": [
{ {
@ -113,9 +129,13 @@
}, },
{ {
"description": "forbid everything with empty schema", "description": "forbid everything with empty schema",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"not": {} {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"not": {}
}
]
}, },
"tests": [ "tests": [
{ {
@ -171,9 +191,13 @@
}, },
{ {
"description": "forbid everything with boolean schema true", "description": "forbid everything with boolean schema true",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"not": true {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"not": true
}
]
}, },
"tests": [ "tests": [
{ {
@ -229,10 +253,14 @@
}, },
{ {
"description": "allow everything with boolean schema false", "description": "allow everything with boolean schema false",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"not": false, {
"extensible": true "$schema": "https://json-schema.org/draft/2020-12/schema",
"not": false,
"extensible": true
}
]
}, },
"tests": [ "tests": [
{ {
@ -288,11 +316,15 @@
}, },
{ {
"description": "double negation", "description": "double negation",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"not": { {
"not": {} "$schema": "https://json-schema.org/draft/2020-12/schema",
} "not": {
"not": {}
}
}
]
}, },
"tests": [ "tests": [
{ {
@ -304,12 +336,16 @@
}, },
{ {
"description": "extensible: true allows extra properties in not", "description": "extensible: true allows extra properties in not",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"not": { {
"type": "integer" "$schema": "https://json-schema.org/draft/2020-12/schema",
}, "not": {
"extensible": true "type": "integer"
},
"extensible": true
}
]
}, },
"tests": [ "tests": [
{ {
@ -323,11 +359,15 @@
}, },
{ {
"description": "extensible: false (default) forbids extra properties in not", "description": "extensible: false (default) forbids extra properties in not",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"not": { {
"type": "integer" "$schema": "https://json-schema.org/draft/2020-12/schema",
} "not": {
"type": "integer"
}
}
]
}, },
"tests": [ "tests": [
{ {
@ -341,17 +381,21 @@
}, },
{ {
"description": "property next to not (extensible: true)", "description": "property next to not (extensible: true)",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"properties": { {
"bar": { "$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "string" "properties": {
"bar": {
"type": "string"
}
},
"not": {
"type": "integer"
},
"extensible": true
} }
}, ]
"not": {
"type": "integer"
},
"extensible": true
}, },
"tests": [ "tests": [
{ {
@ -366,16 +410,20 @@
}, },
{ {
"description": "property next to not (extensible: false)", "description": "property next to not (extensible: false)",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"properties": { {
"bar": { "$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "string" "properties": {
"bar": {
"type": "string"
}
},
"not": {
"type": "integer"
}
} }
}, ]
"not": {
"type": "integer"
}
}, },
"tests": [ "tests": [
{ {

View File

@ -1,14 +1,18 @@
[ [
{ {
"description": "oneOf", "description": "oneOf",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"oneOf": [
{ {
"type": "integer" "$schema": "https://json-schema.org/draft/2020-12/schema",
}, "oneOf": [
{ {
"minimum": 2 "type": "integer"
},
{
"minimum": 2
}
]
} }
] ]
}, },
@ -37,15 +41,19 @@
}, },
{ {
"description": "oneOf with base schema", "description": "oneOf with base schema",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"type": "string",
"oneOf": [
{ {
"minLength": 2 "$schema": "https://json-schema.org/draft/2020-12/schema",
}, "type": "string",
{ "oneOf": [
"maxLength": 4 {
"minLength": 2
},
{
"maxLength": 4
}
]
} }
] ]
}, },
@ -69,12 +77,16 @@
}, },
{ {
"description": "oneOf with boolean schemas, all true", "description": "oneOf with boolean schemas, all true",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"oneOf": [ {
true, "$schema": "https://json-schema.org/draft/2020-12/schema",
true, "oneOf": [
true true,
true,
true
]
}
] ]
}, },
"tests": [ "tests": [
@ -87,12 +99,16 @@
}, },
{ {
"description": "oneOf with boolean schemas, one true", "description": "oneOf with boolean schemas, one true",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"oneOf": [ {
true, "$schema": "https://json-schema.org/draft/2020-12/schema",
false, "oneOf": [
false true,
false,
false
]
}
] ]
}, },
"tests": [ "tests": [
@ -105,12 +121,16 @@
}, },
{ {
"description": "oneOf with boolean schemas, more than one true", "description": "oneOf with boolean schemas, more than one true",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"oneOf": [ {
true, "$schema": "https://json-schema.org/draft/2020-12/schema",
true, "oneOf": [
false true,
true,
false
]
}
] ]
}, },
"tests": [ "tests": [
@ -123,12 +143,16 @@
}, },
{ {
"description": "oneOf with boolean schemas, all false", "description": "oneOf with boolean schemas, all false",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"oneOf": [ {
false, "$schema": "https://json-schema.org/draft/2020-12/schema",
false, "oneOf": [
false false,
false,
false
]
}
] ]
}, },
"tests": [ "tests": [
@ -141,27 +165,31 @@
}, },
{ {
"description": "oneOf complex types", "description": "oneOf complex types",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"oneOf": [
{ {
"properties": { "$schema": "https://json-schema.org/draft/2020-12/schema",
"bar": { "oneOf": [
"type": "integer" {
"properties": {
"bar": {
"type": "integer"
}
},
"required": [
"bar"
]
},
{
"properties": {
"foo": {
"type": "string"
}
},
"required": [
"foo"
]
} }
},
"required": [
"bar"
]
},
{
"properties": {
"foo": {
"type": "string"
}
},
"required": [
"foo"
] ]
} }
] ]
@ -201,13 +229,17 @@
}, },
{ {
"description": "oneOf with empty schema", "description": "oneOf with empty schema",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"oneOf": [
{ {
"type": "number" "$schema": "https://json-schema.org/draft/2020-12/schema",
}, "oneOf": [
{} {
"type": "number"
},
{}
]
}
] ]
}, },
"tests": [ "tests": [
@ -225,25 +257,29 @@
}, },
{ {
"description": "oneOf with required", "description": "oneOf with required",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"type": "object",
"properties": {
"foo": true,
"bar": true,
"baz": true
},
"oneOf": [
{ {
"required": [ "$schema": "https://json-schema.org/draft/2020-12/schema",
"foo", "type": "object",
"bar" "properties": {
] "foo": true,
}, "bar": true,
{ "baz": true
"required": [ },
"foo", "oneOf": [
"baz" {
"required": [
"foo",
"bar"
]
},
{
"required": [
"foo",
"baz"
]
}
] ]
} }
] ]
@ -294,21 +330,25 @@
}, },
{ {
"description": "oneOf with required (extensible)", "description": "oneOf with required (extensible)",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"type": "object",
"extensible": true,
"oneOf": [
{ {
"required": [ "$schema": "https://json-schema.org/draft/2020-12/schema",
"foo", "type": "object",
"bar" "extensible": true,
] "oneOf": [
}, {
{ "required": [
"required": [ "foo",
"foo", "bar"
"baz" ]
},
{
"required": [
"foo",
"baz"
]
}
] ]
} }
] ]
@ -359,24 +399,28 @@
}, },
{ {
"description": "oneOf with missing optional property", "description": "oneOf with missing optional property",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"oneOf": [
{ {
"properties": { "$schema": "https://json-schema.org/draft/2020-12/schema",
"bar": true, "oneOf": [
"baz": true {
}, "properties": {
"required": [ "bar": true,
"bar" "baz": true
] },
}, "required": [
{ "bar"
"properties": { ]
"foo": true },
}, {
"required": [ "properties": {
"foo" "foo": true
},
"required": [
"foo"
]
}
] ]
} }
] ]
@ -415,13 +459,17 @@
}, },
{ {
"description": "nested oneOf, to check validation semantics", "description": "nested oneOf, to check validation semantics",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"oneOf": [
{ {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"oneOf": [ "oneOf": [
{ {
"type": "null" "oneOf": [
{
"type": "null"
}
]
} }
] ]
} }
@ -442,31 +490,35 @@
}, },
{ {
"description": "extensible: true allows extra properties in oneOf", "description": "extensible: true allows extra properties in oneOf",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"oneOf": [
{ {
"properties": { "$schema": "https://json-schema.org/draft/2020-12/schema",
"bar": { "oneOf": [
"type": "integer" {
"properties": {
"bar": {
"type": "integer"
}
},
"required": [
"bar"
]
},
{
"properties": {
"foo": {
"type": "string"
}
},
"required": [
"foo"
]
} }
}, ],
"required": [ "extensible": true
"bar"
]
},
{
"properties": {
"foo": {
"type": "string"
}
},
"required": [
"foo"
]
} }
], ]
"extensible": true
}, },
"tests": [ "tests": [
{ {

View File

@ -1,9 +1,13 @@
[ [
{ {
"description": "pattern validation", "description": "pattern validation",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"pattern": "^a*$" {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"pattern": "^a*$"
}
]
}, },
"tests": [ "tests": [
{ {
@ -50,9 +54,13 @@
}, },
{ {
"description": "pattern is not anchored", "description": "pattern is not anchored",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"pattern": "a+" {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"pattern": "a+"
}
]
}, },
"tests": [ "tests": [
{ {
@ -62,4 +70,4 @@
} }
] ]
} }
] ]

View File

@ -1,14 +1,18 @@
[ [
{ {
"description": "patternProperties validates properties matching a regex", "description": "patternProperties validates properties matching a regex",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"patternProperties": { {
"f.*o": { "$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "integer" "patternProperties": {
"f.*o": {
"type": "integer"
}
},
"items": {}
} }
}, ]
"items": {}
}, },
"tests": [ "tests": [
{ {
@ -71,16 +75,20 @@
}, },
{ {
"description": "multiple simultaneous patternProperties are validated", "description": "multiple simultaneous patternProperties are validated",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"patternProperties": { {
"a*": { "$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "integer" "patternProperties": {
}, "a*": {
"aaa*": { "type": "integer"
"maximum": 20 },
"aaa*": {
"maximum": 20
}
}
} }
} ]
}, },
"tests": [ "tests": [
{ {
@ -131,17 +139,21 @@
}, },
{ {
"description": "regexes are not anchored by default and are case sensitive", "description": "regexes are not anchored by default and are case sensitive",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"patternProperties": { {
"[0-9]{2,}": { "$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "boolean" "patternProperties": {
}, "[0-9]{2,}": {
"X_": { "type": "boolean"
"type": "string" },
"X_": {
"type": "string"
}
},
"extensible": true
} }
}, ]
"extensible": true
}, },
"tests": [ "tests": [
{ {
@ -176,12 +188,16 @@
}, },
{ {
"description": "patternProperties with boolean schemas", "description": "patternProperties with boolean schemas",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"patternProperties": { {
"f.*": true, "$schema": "https://json-schema.org/draft/2020-12/schema",
"b.*": false "patternProperties": {
} "f.*": true,
"b.*": false
}
}
]
}, },
"tests": [ "tests": [
{ {
@ -222,13 +238,17 @@
}, },
{ {
"description": "patternProperties with null valued instance properties", "description": "patternProperties with null valued instance properties",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"patternProperties": { {
"^.*bar$": { "$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "null" "patternProperties": {
"^.*bar$": {
"type": "null"
}
}
} }
} ]
}, },
"tests": [ "tests": [
{ {
@ -242,14 +262,18 @@
}, },
{ {
"description": "extensible: true allows extra properties NOT matching pattern", "description": "extensible: true allows extra properties NOT matching pattern",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"patternProperties": { {
"f.*o": { "$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "integer" "patternProperties": {
"f.*o": {
"type": "integer"
}
},
"extensible": true
} }
}, ]
"extensible": true
}, },
"tests": [ "tests": [
{ {

View File

@ -1,14 +1,18 @@
[ [
{ {
"description": "a schema given for prefixItems", "description": "a schema given for prefixItems",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"prefixItems": [
{ {
"type": "integer" "$schema": "https://json-schema.org/draft/2020-12/schema",
}, "prefixItems": [
{ {
"type": "string" "type": "integer"
},
{
"type": "string"
}
]
} }
] ]
}, },
@ -63,11 +67,15 @@
}, },
{ {
"description": "prefixItems with boolean schemas", "description": "prefixItems with boolean schemas",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"prefixItems": [ {
true, "$schema": "https://json-schema.org/draft/2020-12/schema",
false "prefixItems": [
true,
false
]
}
] ]
}, },
"tests": [ "tests": [
@ -95,14 +103,18 @@
}, },
{ {
"description": "additional items are allowed by default", "description": "additional items are allowed by default",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"prefixItems": [
{ {
"type": "integer" "$schema": "https://json-schema.org/draft/2020-12/schema",
"prefixItems": [
{
"type": "integer"
}
],
"extensible": true
} }
], ]
"extensible": true
}, },
"tests": [ "tests": [
{ {
@ -118,11 +130,15 @@
}, },
{ {
"description": "prefixItems with null instance elements", "description": "prefixItems with null instance elements",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"prefixItems": [
{ {
"type": "null" "$schema": "https://json-schema.org/draft/2020-12/schema",
"prefixItems": [
{
"type": "null"
}
]
} }
] ]
}, },
@ -138,14 +154,18 @@
}, },
{ {
"description": "extensible: true allows extra items with prefixItems", "description": "extensible: true allows extra items with prefixItems",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"prefixItems": [
{ {
"type": "integer" "$schema": "https://json-schema.org/draft/2020-12/schema",
"prefixItems": [
{
"type": "integer"
}
],
"extensible": true
} }
], ]
"extensible": true
}, },
"tests": [ "tests": [
{ {

View File

@ -1,16 +1,20 @@
[ [
{ {
"description": "object properties validation", "description": "object properties validation",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"properties": { {
"foo": { "$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "integer" "properties": {
}, "foo": {
"bar": { "type": "integer"
"type": "string" },
"bar": {
"type": "string"
}
}
} }
} ]
}, },
"tests": [ "tests": [
{ {
@ -56,12 +60,16 @@
}, },
{ {
"description": "properties with boolean schema", "description": "properties with boolean schema",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"properties": { {
"foo": true, "$schema": "https://json-schema.org/draft/2020-12/schema",
"bar": false "properties": {
} "foo": true,
"bar": false
}
}
]
}, },
"tests": [ "tests": [
{ {
@ -95,28 +103,32 @@
}, },
{ {
"description": "properties with escaped characters", "description": "properties with escaped characters",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"properties": { {
"foo\nbar": { "$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "number" "properties": {
}, "foo\nbar": {
"foo\"bar": { "type": "number"
"type": "number" },
}, "foo\"bar": {
"foo\\bar": { "type": "number"
"type": "number" },
}, "foo\\bar": {
"foo\rbar": { "type": "number"
"type": "number" },
}, "foo\rbar": {
"foo\tbar": { "type": "number"
"type": "number" },
}, "foo\tbar": {
"foo\fbar": { "type": "number"
"type": "number" },
"foo\fbar": {
"type": "number"
}
}
} }
} ]
}, },
"tests": [ "tests": [
{ {
@ -147,13 +159,17 @@
}, },
{ {
"description": "properties with null valued instance properties", "description": "properties with null valued instance properties",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"properties": { {
"foo": { "$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "null" "properties": {
"foo": {
"type": "null"
}
}
} }
} ]
}, },
"tests": [ "tests": [
{ {
@ -168,23 +184,27 @@
{ {
"description": "properties whose names are Javascript object property names", "description": "properties whose names are Javascript object property names",
"comment": "Ensure JS implementations don't universally consider e.g. __proto__ to always be present in an object.", "comment": "Ensure JS implementations don't universally consider e.g. __proto__ to always be present in an object.",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"properties": { {
"__proto__": { "$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "number"
},
"toString": {
"properties": { "properties": {
"length": { "__proto__": {
"type": "string" "type": "number"
},
"toString": {
"properties": {
"length": {
"type": "string"
}
}
},
"constructor": {
"type": "number"
} }
} }
},
"constructor": {
"type": "number"
} }
} ]
}, },
"tests": [ "tests": [
{ {
@ -242,14 +262,18 @@
}, },
{ {
"description": "extensible: true allows extra properties", "description": "extensible: true allows extra properties",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"properties": { {
"foo": { "$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "integer" "properties": {
"foo": {
"type": "integer"
}
},
"extensible": true
} }
}, ]
"extensible": true
}, },
"tests": [ "tests": [
{ {
@ -264,13 +288,17 @@
}, },
{ {
"description": "strict by default: extra properties invalid", "description": "strict by default: extra properties invalid",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"properties": { {
"foo": { "$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "string" "properties": {
"foo": {
"type": "string"
}
}
} }
} ]
}, },
"tests": [ "tests": [
{ {
@ -285,17 +313,21 @@
}, },
{ {
"description": "inheritance: nested object inherits strictness from strict parent", "description": "inheritance: nested object inherits strictness from strict parent",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"properties": { {
"nested": { "$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": { "properties": {
"foo": { "nested": {
"type": "string" "properties": {
"foo": {
"type": "string"
}
}
} }
} }
} }
} ]
}, },
"tests": [ "tests": [
{ {
@ -312,18 +344,22 @@
}, },
{ {
"description": "override: nested object allows extra properties if extensible: true", "description": "override: nested object allows extra properties if extensible: true",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"properties": { {
"nested": { "$schema": "https://json-schema.org/draft/2020-12/schema",
"extensible": true,
"properties": { "properties": {
"foo": { "nested": {
"type": "string" "extensible": true,
"properties": {
"foo": {
"type": "string"
}
}
} }
} }
} }
} ]
}, },
"tests": [ "tests": [
{ {
@ -340,18 +376,22 @@
}, },
{ {
"description": "inheritance: nested object inherits looseness from loose parent", "description": "inheritance: nested object inherits looseness from loose parent",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"extensible": true, {
"properties": { "$schema": "https://json-schema.org/draft/2020-12/schema",
"nested": { "extensible": true,
"properties": { "properties": {
"foo": { "nested": {
"type": "string" "properties": {
"foo": {
"type": "string"
}
}
} }
} }
} }
} ]
}, },
"tests": [ "tests": [
{ {
@ -368,19 +408,23 @@
}, },
{ {
"description": "override: nested object enforces strictness if extensible: false", "description": "override: nested object enforces strictness if extensible: false",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"extensible": true, {
"properties": { "$schema": "https://json-schema.org/draft/2020-12/schema",
"nested": { "extensible": true,
"extensible": false,
"properties": { "properties": {
"foo": { "nested": {
"type": "string" "extensible": false,
"properties": {
"foo": {
"type": "string"
}
}
} }
} }
} }
} ]
}, },
"tests": [ "tests": [
{ {
@ -397,20 +441,24 @@
}, },
{ {
"description": "arrays: inline items inherit strictness from strict parent", "description": "arrays: inline items inherit strictness from strict parent",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"properties": { {
"list": { "$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "array", "properties": {
"items": { "list": {
"properties": { "type": "array",
"foo": { "items": {
"type": "string" "properties": {
"foo": {
"type": "string"
}
}
} }
} }
} }
} }
} ]
}, },
"tests": [ "tests": [
{ {
@ -429,21 +477,25 @@
}, },
{ {
"description": "arrays: inline items inherit looseness from loose parent", "description": "arrays: inline items inherit looseness from loose parent",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"extensible": true, {
"properties": { "$schema": "https://json-schema.org/draft/2020-12/schema",
"list": { "extensible": true,
"type": "array", "properties": {
"items": { "list": {
"properties": { "type": "array",
"foo": { "items": {
"type": "string" "properties": {
"foo": {
"type": "string"
}
}
} }
} }
} }
} }
} ]
}, },
"tests": [ "tests": [
{ {

View File

@ -1,12 +1,16 @@
[ [
{ {
"description": "propertyNames validation", "description": "propertyNames validation",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"propertyNames": { {
"maxLength": 3 "$schema": "https://json-schema.org/draft/2020-12/schema",
}, "propertyNames": {
"extensible": true "maxLength": 3
},
"extensible": true
}
]
}, },
"tests": [ "tests": [
{ {
@ -54,12 +58,16 @@
}, },
{ {
"description": "propertyNames validation with pattern", "description": "propertyNames validation with pattern",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"propertyNames": { {
"pattern": "^a+$" "$schema": "https://json-schema.org/draft/2020-12/schema",
}, "propertyNames": {
"extensible": true "pattern": "^a+$"
},
"extensible": true
}
]
}, },
"tests": [ "tests": [
{ {
@ -87,10 +95,14 @@
}, },
{ {
"description": "propertyNames with boolean schema true", "description": "propertyNames with boolean schema true",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"propertyNames": true, {
"extensible": true "$schema": "https://json-schema.org/draft/2020-12/schema",
"propertyNames": true,
"extensible": true
}
]
}, },
"tests": [ "tests": [
{ {
@ -109,10 +121,14 @@
}, },
{ {
"description": "propertyNames with boolean schema false", "description": "propertyNames with boolean schema false",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"propertyNames": false, {
"extensible": true "$schema": "https://json-schema.org/draft/2020-12/schema",
"propertyNames": false,
"extensible": true
}
]
}, },
"tests": [ "tests": [
{ {
@ -131,12 +147,16 @@
}, },
{ {
"description": "propertyNames with const", "description": "propertyNames with const",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"propertyNames": { {
"const": "foo" "$schema": "https://json-schema.org/draft/2020-12/schema",
}, "propertyNames": {
"extensible": true "const": "foo"
},
"extensible": true
}
]
}, },
"tests": [ "tests": [
{ {
@ -162,15 +182,19 @@
}, },
{ {
"description": "propertyNames with enum", "description": "propertyNames with enum",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"propertyNames": { {
"enum": [ "$schema": "https://json-schema.org/draft/2020-12/schema",
"foo", "propertyNames": {
"bar" "enum": [
] "foo",
}, "bar"
"extensible": true ]
},
"extensible": true
}
]
}, },
"tests": [ "tests": [
{ {
@ -204,12 +228,16 @@
}, },
{ {
"description": "extensible: true allows extra properties (checked by propertyNames)", "description": "extensible: true allows extra properties (checked by propertyNames)",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"propertyNames": { {
"maxLength": 3 "$schema": "https://json-schema.org/draft/2020-12/schema",
}, "propertyNames": {
"extensible": true "maxLength": 3
},
"extensible": true
}
]
}, },
"tests": [ "tests": [
{ {

File diff suppressed because it is too large Load Diff

1186
tests/fixtures/ref.json vendored

File diff suppressed because it is too large Load Diff

View File

@ -1,14 +1,18 @@
[ [
{ {
"description": "required validation", "description": "required validation",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"properties": { {
"foo": {}, "$schema": "https://json-schema.org/draft/2020-12/schema",
"bar": {} "properties": {
}, "foo": {},
"required": [ "bar": {}
"foo" },
"required": [
"foo"
]
}
] ]
}, },
"tests": [ "tests": [
@ -55,11 +59,15 @@
}, },
{ {
"description": "required default validation", "description": "required default validation",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"properties": { {
"foo": {} "$schema": "https://json-schema.org/draft/2020-12/schema",
} "properties": {
"foo": {}
}
}
]
}, },
"tests": [ "tests": [
{ {
@ -71,12 +79,16 @@
}, },
{ {
"description": "required with empty array", "description": "required with empty array",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"properties": { {
"foo": {} "$schema": "https://json-schema.org/draft/2020-12/schema",
}, "properties": {
"required": [] "foo": {}
},
"required": []
}
]
}, },
"tests": [ "tests": [
{ {
@ -88,17 +100,21 @@
}, },
{ {
"description": "required with escaped characters", "description": "required with escaped characters",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"required": [ {
"foo\nbar", "$schema": "https://json-schema.org/draft/2020-12/schema",
"foo\"bar", "required": [
"foo\\bar", "foo\nbar",
"foo\rbar", "foo\"bar",
"foo\tbar", "foo\\bar",
"foo\fbar" "foo\rbar",
], "foo\tbar",
"extensible": true "foo\fbar"
],
"extensible": true
}
]
}, },
"tests": [ "tests": [
{ {
@ -126,14 +142,18 @@
{ {
"description": "required properties whose names are Javascript object property names", "description": "required properties whose names are Javascript object property names",
"comment": "Ensure JS implementations don't universally consider e.g. __proto__ to always be present in an object.", "comment": "Ensure JS implementations don't universally consider e.g. __proto__ to always be present in an object.",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"required": [ {
"__proto__", "$schema": "https://json-schema.org/draft/2020-12/schema",
"toString", "required": [
"constructor" "__proto__",
], "toString",
"extensible": true "constructor"
],
"extensible": true
}
]
}, },
"tests": [ "tests": [
{ {
@ -191,12 +211,16 @@
}, },
{ {
"description": "extensible: true allows extra properties in required", "description": "extensible: true allows extra properties in required",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"required": [ {
"foo" "$schema": "https://json-schema.org/draft/2020-12/schema",
], "required": [
"extensible": true "foo"
],
"extensible": true
}
]
}, },
"tests": [ "tests": [
{ {

View File

@ -1,9 +1,13 @@
[ [
{ {
"description": "integer type matches integers", "description": "integer type matches integers",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"type": "integer" {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "integer"
}
]
}, },
"tests": [ "tests": [
{ {
@ -55,9 +59,13 @@
}, },
{ {
"description": "number type matches numbers", "description": "number type matches numbers",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"type": "number" {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "number"
}
]
}, },
"tests": [ "tests": [
{ {
@ -109,9 +117,13 @@
}, },
{ {
"description": "string type matches strings", "description": "string type matches strings",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"type": "string" {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "string"
}
]
}, },
"tests": [ "tests": [
{ {
@ -163,9 +175,13 @@
}, },
{ {
"description": "object type matches objects", "description": "object type matches objects",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"type": "object" {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object"
}
]
}, },
"tests": [ "tests": [
{ {
@ -207,9 +223,13 @@
}, },
{ {
"description": "array type matches arrays", "description": "array type matches arrays",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"type": "array" {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "array"
}
]
}, },
"tests": [ "tests": [
{ {
@ -251,9 +271,13 @@
}, },
{ {
"description": "boolean type matches booleans", "description": "boolean type matches booleans",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"type": "boolean" {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "boolean"
}
]
}, },
"tests": [ "tests": [
{ {
@ -310,9 +334,13 @@
}, },
{ {
"description": "null type matches only the null object", "description": "null type matches only the null object",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"type": "null" {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "null"
}
]
}, },
"tests": [ "tests": [
{ {
@ -369,11 +397,15 @@
}, },
{ {
"description": "multiple types can be specified in an array", "description": "multiple types can be specified in an array",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"type": [ {
"integer", "$schema": "https://json-schema.org/draft/2020-12/schema",
"string" "type": [
"integer",
"string"
]
}
] ]
}, },
"tests": [ "tests": [
@ -416,10 +448,14 @@
}, },
{ {
"description": "type as array with one item", "description": "type as array with one item",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"type": [ {
"string" "$schema": "https://json-schema.org/draft/2020-12/schema",
"type": [
"string"
]
}
] ]
}, },
"tests": [ "tests": [
@ -437,13 +473,17 @@
}, },
{ {
"description": "type: array or object", "description": "type: array or object",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"type": [ {
"array", "$schema": "https://json-schema.org/draft/2020-12/schema",
"object" "type": [
], "array",
"items": {} "object"
],
"items": {}
}
]
}, },
"tests": [ "tests": [
{ {
@ -479,14 +519,18 @@
}, },
{ {
"description": "type: array, object or null", "description": "type: array, object or null",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"type": [ {
"array", "$schema": "https://json-schema.org/draft/2020-12/schema",
"object", "type": [
"null" "array",
], "object",
"items": {} "null"
],
"items": {}
}
]
}, },
"tests": [ "tests": [
{ {
@ -522,10 +566,14 @@
}, },
{ {
"description": "extensible: true allows extra properties", "description": "extensible: true allows extra properties",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"type": "object", {
"extensible": true "$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"extensible": true
}
]
}, },
"tests": [ "tests": [
{ {

343
tests/fixtures/typedRefs.json vendored Normal file
View File

@ -0,0 +1,343 @@
[
{
"description": "Entities extending entities",
"database": {
"types": [
{
"name": "entity",
"hierarchy": [
"entity"
],
"schemas": [
{
"$id": "entity",
"type": "object",
"properties": {
"id": {
"type": "string"
},
"type": {
"type": "string",
"const": "entity"
}
}
}
]
},
{
"name": "organization",
"hierarchy": [
"entity",
"organization"
],
"schemas": [
{
"$id": "organization",
"$ref": "entity",
"properties": {
"name": {
"type": "string"
}
}
}
]
},
{
"name": "person",
"hierarchy": [
"entity",
"organization",
"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)",
"schema_id": "save_org.request",
"data": {
"id": "1",
"type": "person",
"name": "ACME"
},
"valid": true
},
{
"description": "Valid organization against organization schema",
"schema_id": "save_org.request",
"data": {
"id": "2",
"type": "organization",
"name": "ACME"
},
"valid": true
},
{
"description": "Invalid entity against organization schema (ancestor not allowed)",
"schema_id": "save_org.request",
"data": {
"id": "3",
"type": "entity"
},
"valid": false
},
{
"description": "Invalid generic type against organization schema",
"schema_id": "save_org.request",
"data": {
"id": "4",
"type": "generic_thing"
},
"valid": false
}
]
},
{
"description": "Ad-hocs extending entities (still entities with type magic)",
"database": {
"types": [
{
"name": "entity",
"hierarchy": [
"entity"
],
"schemas": [
{
"$id": "entity",
"type": "object",
"properties": {
"id": {
"type": "string"
},
"type": {
"type": "string",
"const": "entity"
}
}
}
]
},
{
"name": "person",
"hierarchy": [
"entity",
"person"
],
"schemas": [
{
"$id": "person",
"$ref": "entity",
"properties": {
"first_name": {
"type": "string"
}
}
}
]
}
],
"puncs": [
{
"name": "save_person_light",
"schemas": [
{
"$id": "person.light",
"$ref": "person",
"extensible": false,
"properties": {
"first_name": {
"type": "string"
}
}
},
{
"$id": "save_person_light.request",
"$ref": "person.light"
}
]
}
]
},
"tests": [
{
"description": "Valid person against person.light ad-hoc schema",
"schema_id": "save_person_light.request",
"data": {
"id": "1",
"type": "person",
"first_name": "John"
},
"valid": true
},
{
"description": "Invalid person against person.light (strictness violation)",
"schema_id": "save_person_light.request",
"data": {
"id": "1",
"type": "person",
"first_name": "John",
"extra": "bad"
},
"valid": false
},
{
"description": "Invalid entity against person.light ad-hoc schema (ancestor not allowed)",
"schema_id": "save_person_light.request",
"data": {
"id": "1",
"type": "entity",
"first_name": "John"
},
"valid": false
}
]
},
{
"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"
},
"valid": true
},
{
"description": "Invalid base address against us_address",
"schema_id": "save_address.request",
"data": {
"street": "123 Main",
"city": "Anytown"
},
"valid": 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"
},
"valid": 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"
},
"valid": false
}
]
}
]

View File

@ -1,10 +1,14 @@
[ [
{ {
"description": "uniqueItems validation", "description": "uniqueItems validation",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"uniqueItems": true, {
"extensible": true "$schema": "https://json-schema.org/draft/2020-12/schema",
"uniqueItems": true,
"extensible": true
}
]
}, },
"tests": [ "tests": [
{ {
@ -352,18 +356,22 @@
}, },
{ {
"description": "uniqueItems with an array of items", "description": "uniqueItems with an array of items",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"prefixItems": [
{ {
"type": "boolean" "$schema": "https://json-schema.org/draft/2020-12/schema",
}, "prefixItems": [
{ {
"type": "boolean" "type": "boolean"
},
{
"type": "boolean"
}
],
"uniqueItems": true,
"extensible": true
} }
], ]
"uniqueItems": true,
"extensible": true
}, },
"tests": [ "tests": [
{ {
@ -442,18 +450,22 @@
}, },
{ {
"description": "uniqueItems with an array of items and additionalItems=false", "description": "uniqueItems with an array of items and additionalItems=false",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"prefixItems": [
{ {
"type": "boolean" "$schema": "https://json-schema.org/draft/2020-12/schema",
}, "prefixItems": [
{ {
"type": "boolean" "type": "boolean"
},
{
"type": "boolean"
}
],
"uniqueItems": true,
"items": false
} }
], ]
"uniqueItems": true,
"items": false
}, },
"tests": [ "tests": [
{ {
@ -501,10 +513,14 @@
}, },
{ {
"description": "uniqueItems=false validation", "description": "uniqueItems=false validation",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"uniqueItems": false, {
"extensible": true "$schema": "https://json-schema.org/draft/2020-12/schema",
"uniqueItems": false,
"extensible": true
}
]
}, },
"tests": [ "tests": [
{ {
@ -683,18 +699,22 @@
}, },
{ {
"description": "uniqueItems=false with an array of items", "description": "uniqueItems=false with an array of items",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"prefixItems": [
{ {
"type": "boolean" "$schema": "https://json-schema.org/draft/2020-12/schema",
}, "prefixItems": [
{ {
"type": "boolean" "type": "boolean"
},
{
"type": "boolean"
}
],
"uniqueItems": false,
"extensible": true
} }
], ]
"uniqueItems": false,
"extensible": true
}, },
"tests": [ "tests": [
{ {
@ -773,18 +793,22 @@
}, },
{ {
"description": "uniqueItems=false with an array of items and additionalItems=false", "description": "uniqueItems=false with an array of items and additionalItems=false",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"prefixItems": [
{ {
"type": "boolean" "$schema": "https://json-schema.org/draft/2020-12/schema",
}, "prefixItems": [
{ {
"type": "boolean" "type": "boolean"
},
{
"type": "boolean"
}
],
"uniqueItems": false,
"items": false
} }
], ]
"uniqueItems": false,
"items": false
}, },
"tests": [ "tests": [
{ {
@ -832,10 +856,14 @@
}, },
{ {
"description": "extensible: true allows extra items in uniqueItems", "description": "extensible: true allows extra items in uniqueItems",
"schema": { "database": {
"$schema": "https://json-schema.org/draft/2020-12/schema", "schemas": [
"uniqueItems": true, {
"extensible": true "$schema": "https://json-schema.org/draft/2020-12/schema",
"uniqueItems": true,
"extensible": true
}
]
}, },
"tests": [ "tests": [
{ {

View File

@ -1,4 +1,4 @@
use jspg::*; use ::jspg::*;
use pgrx::JsonB; use pgrx::JsonB;
use serde_json::json; use serde_json::json;
@ -22,20 +22,22 @@ fn test_library_api() {
); );
// 2. Cache schemas // 2. Cache schemas
let puncs = json!([]); let db_json = json!({
let types = json!([{ "puncs": [],
"schemas": [{ "enums": [],
"$id": "test_schema", "types": [{
"type": "object", "schemas": [{
"properties": { "$id": "test_schema",
"name": { "type": "string" } "type": "object",
}, "properties": {
"required": ["name"] "name": { "type": "string" }
},
"required": ["name"]
}]
}] }]
}]); });
let enums = json!([]);
let cache_drop = cache_json_schemas(JsonB(enums), JsonB(types), JsonB(puncs)); let cache_drop = jspg_cache_database(JsonB(db_json));
assert_eq!( assert_eq!(
cache_drop.0, cache_drop.0,
json!({ json!({