Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 704770051c | |||
| 88c77deede | |||
| 0184c244d9 | |||
| e40de2eb12 |
88
src/helpers.rs
Normal file
88
src/helpers.rs
Normal file
@ -0,0 +1,88 @@
|
||||
use serde_json::Value;
|
||||
use pgrx::JsonB;
|
||||
|
||||
// Simple test helpers for cleaner test code
|
||||
pub fn assert_success(result: &JsonB) {
|
||||
let json = &result.0;
|
||||
if !json.get("response").is_some() || json.get("errors").is_some() {
|
||||
let pretty = serde_json::to_string_pretty(json).unwrap_or_else(|_| format!("{:?}", json));
|
||||
panic!("Expected success but got:\n{}", pretty);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn assert_failure(result: &JsonB) {
|
||||
let json = &result.0;
|
||||
if json.get("response").is_some() || !json.get("errors").is_some() {
|
||||
let pretty = serde_json::to_string_pretty(json).unwrap_or_else(|_| format!("{:?}", json));
|
||||
panic!("Expected failure but got:\n{}", pretty);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn assert_error_count(result: &JsonB, expected_count: usize) {
|
||||
assert_failure(result);
|
||||
let errors = get_errors(result);
|
||||
if errors.len() != expected_count {
|
||||
let pretty = serde_json::to_string_pretty(&result.0).unwrap_or_else(|_| format!("{:?}", result.0));
|
||||
panic!("Expected {} errors, got {}:\n{}", expected_count, errors.len(), pretty);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_errors(result: &JsonB) -> &Vec<Value> {
|
||||
result.0["errors"].as_array().expect("errors should be an array")
|
||||
}
|
||||
|
||||
pub fn has_error_with_code(result: &JsonB, code: &str) -> bool {
|
||||
get_errors(result).iter().any(|e| e["code"] == code)
|
||||
}
|
||||
|
||||
|
||||
pub fn has_error_with_code_and_path(result: &JsonB, code: &str, path: &str) -> bool {
|
||||
get_errors(result).iter().any(|e| e["code"] == code && e["details"]["path"] == path)
|
||||
}
|
||||
|
||||
pub fn assert_has_error(result: &JsonB, code: &str, path: &str) {
|
||||
if !has_error_with_code_and_path(result, code, path) {
|
||||
let pretty = serde_json::to_string_pretty(&result.0).unwrap_or_else(|_| format!("{:?}", result.0));
|
||||
panic!("Expected error with code='{}' and path='{}' but not found:\n{}", code, path, pretty);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn find_error_with_code<'a>(result: &'a JsonB, code: &str) -> &'a Value {
|
||||
get_errors(result).iter().find(|e| e["code"] == code)
|
||||
.unwrap_or_else(|| panic!("No error found with code '{}'", code))
|
||||
}
|
||||
|
||||
|
||||
pub fn find_error_with_code_and_path<'a>(result: &'a JsonB, code: &str, path: &str) -> &'a Value {
|
||||
get_errors(result).iter().find(|e| e["code"] == code && e["details"]["path"] == path)
|
||||
.unwrap_or_else(|| panic!("No error found with code '{}' and path '{}'", code, path))
|
||||
}
|
||||
|
||||
pub fn assert_error_detail(error: &Value, detail_key: &str, expected_value: &str) {
|
||||
let actual = error["details"][detail_key].as_str()
|
||||
.unwrap_or_else(|| panic!("Error detail '{}' is not a string", detail_key));
|
||||
assert_eq!(actual, expected_value, "Error detail '{}' mismatch", detail_key);
|
||||
}
|
||||
|
||||
|
||||
// Additional convenience helpers for common patterns
|
||||
|
||||
pub fn assert_error_message_contains(error: &Value, substring: &str) {
|
||||
let message = error["message"].as_str().expect("error should have message");
|
||||
assert!(message.contains(substring), "Expected message to contain '{}', got '{}'", substring, message);
|
||||
}
|
||||
|
||||
pub fn assert_error_cause_json(error: &Value, expected_cause: &Value) {
|
||||
let cause = &error["details"]["cause"];
|
||||
assert!(cause.is_object(), "cause should be JSON object");
|
||||
assert_eq!(cause, expected_cause, "cause mismatch");
|
||||
}
|
||||
|
||||
pub fn assert_error_context(error: &Value, expected_context: &Value) {
|
||||
assert_eq!(&error["details"]["context"], expected_context, "context mismatch");
|
||||
}
|
||||
|
||||
|
||||
pub fn jsonb(val: Value) -> JsonB {
|
||||
JsonB(val)
|
||||
}
|
||||
180
src/lib.rs
180
src/lib.rs
@ -9,6 +9,14 @@ use std::borrow::Cow;
|
||||
use std::collections::hash_map::Entry;
|
||||
use std::{collections::HashMap, sync::RwLock};
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
enum SchemaType {
|
||||
Enum,
|
||||
Type,
|
||||
PublicPunc,
|
||||
PrivatePunc,
|
||||
}
|
||||
|
||||
struct BoonCache {
|
||||
schemas: Schemas,
|
||||
id_to_index: HashMap<String, SchemaIndex>,
|
||||
@ -31,8 +39,9 @@ lazy_static! {
|
||||
}
|
||||
|
||||
#[pg_extern(strict)]
|
||||
fn cache_json_schemas(types: JsonB, puncs: JsonB) -> JsonB {
|
||||
fn cache_json_schemas(enums: JsonB, types: JsonB, puncs: JsonB) -> JsonB {
|
||||
let mut cache = SCHEMA_CACHE.write().unwrap();
|
||||
let enums_value: Value = enums.0;
|
||||
let types_value: Value = types.0;
|
||||
let puncs_value: Value = puncs.0;
|
||||
|
||||
@ -51,8 +60,42 @@ fn cache_json_schemas(types: JsonB, puncs: JsonB) -> JsonB {
|
||||
// Track all schema IDs for compilation
|
||||
let mut all_schema_ids = Vec::new();
|
||||
|
||||
// Phase 1: Add all type schemas as resources (these are referenced by puncs)
|
||||
// Types are never strict - they're reusable building blocks
|
||||
// Phase 1: Add all enum schemas as resources (priority 1 - these are referenced by types and puncs)
|
||||
// Enums are never strict - they're reusable building blocks
|
||||
if let Some(enums_array) = enums_value.as_array() {
|
||||
for enum_row in enums_array {
|
||||
if let Some(enum_obj) = enum_row.as_object() {
|
||||
if let (Some(enum_name), Some(schemas_raw)) = (
|
||||
enum_obj.get("name").and_then(|v| v.as_str()),
|
||||
enum_obj.get("schemas")
|
||||
) {
|
||||
// Parse the schemas JSONB field
|
||||
if let Some(schemas_array) = schemas_raw.as_array() {
|
||||
for schema_def in schemas_array {
|
||||
if let Some(schema_id) = schema_def.get("$id").and_then(|v| v.as_str()) {
|
||||
if let Err(e) = add_schema_resource(&mut compiler, schema_id, schema_def.clone(), SchemaType::Enum, &mut errors) {
|
||||
errors.push(json!({
|
||||
"code": "ENUM_SCHEMA_RESOURCE_FAILED",
|
||||
"message": format!("Failed to add schema resource '{}' for enum '{}'", schema_id, enum_name),
|
||||
"details": {
|
||||
"enum_name": enum_name,
|
||||
"schema_id": schema_id,
|
||||
"cause": format!("{}", e)
|
||||
}
|
||||
}));
|
||||
} else {
|
||||
all_schema_ids.push(schema_id.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 2: Add all type schemas as resources (priority 2 - these are referenced by puncs)
|
||||
// Types are always strict - they should not allow extra properties
|
||||
if let Some(types_array) = types_value.as_array() {
|
||||
for type_row in types_array {
|
||||
if let Some(type_obj) = type_row.as_object() {
|
||||
@ -64,7 +107,7 @@ fn cache_json_schemas(types: JsonB, puncs: JsonB) -> JsonB {
|
||||
if let Some(schemas_array) = schemas_raw.as_array() {
|
||||
for schema_def in schemas_array {
|
||||
if let Some(schema_id) = schema_def.get("$id").and_then(|v| v.as_str()) {
|
||||
if let Err(e) = add_schema_resource(&mut compiler, schema_id, schema_def.clone(), false, &mut errors) {
|
||||
if let Err(e) = add_schema_resource(&mut compiler, schema_id, schema_def.clone(), SchemaType::Type, &mut errors) {
|
||||
errors.push(json!({
|
||||
"code": "TYPE_SCHEMA_RESOURCE_FAILED",
|
||||
"message": format!("Failed to add schema resource '{}' for type '{}'", schema_id, type_name),
|
||||
@ -85,26 +128,36 @@ fn cache_json_schemas(types: JsonB, puncs: JsonB) -> JsonB {
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 2: Add all punc schemas as resources (these may reference type schemas)
|
||||
// Phase 3: Add all punc schemas as resources (these may reference enum and type schemas)
|
||||
// Each punc gets strict validation based on its public field
|
||||
if let Some(puncs_array) = puncs_value.as_array() {
|
||||
for punc_row in puncs_array {
|
||||
if let Some(punc_obj) = punc_row.as_object() {
|
||||
if let Some(punc_name) = punc_obj.get("name").and_then(|v| v.as_str()) {
|
||||
// Get the strict setting for this specific punc (public = strict)
|
||||
let punc_strict = punc_obj.get("public")
|
||||
// Determine schema type based on public status
|
||||
let is_public = punc_obj.get("public")
|
||||
.and_then(|v| v.as_bool())
|
||||
.unwrap_or(false);
|
||||
let punc_schema_type = if is_public { SchemaType::PublicPunc } else { SchemaType::PrivatePunc };
|
||||
|
||||
// Add punc local schemas as resources (from schemas field) - use $id directly (universal)
|
||||
// Add punc schemas from the 'schemas' array
|
||||
if let Some(schemas_raw) = punc_obj.get("schemas") {
|
||||
if let Some(schemas_array) = schemas_raw.as_array() {
|
||||
for schema_def in schemas_array {
|
||||
if let Some(schema_id) = schema_def.get("$id").and_then(|v| v.as_str()) {
|
||||
if let Err(e) = add_schema_resource(&mut compiler, schema_id, schema_def.clone(), punc_strict, &mut errors) {
|
||||
let request_schema_id = format!("{}.request", punc_name);
|
||||
let response_schema_id = format!("{}.response", punc_name);
|
||||
|
||||
let schema_type_for_def = if schema_id == request_schema_id || schema_id == response_schema_id {
|
||||
punc_schema_type
|
||||
} else {
|
||||
SchemaType::Type // For local/nested schemas
|
||||
};
|
||||
|
||||
if let Err(e) = add_schema_resource(&mut compiler, schema_id, schema_def.clone(), schema_type_for_def, &mut errors) {
|
||||
errors.push(json!({
|
||||
"code": "PUNC_LOCAL_SCHEMA_RESOURCE_FAILED",
|
||||
"message": format!("Failed to add local schema resource '{}' for punc '{}'", schema_id, punc_name),
|
||||
"code": "PUNC_SCHEMA_RESOURCE_FAILED",
|
||||
"message": format!("Failed to add schema resource '{}' for punc '{}'", schema_id, punc_name),
|
||||
"details": {
|
||||
"punc_name": punc_name,
|
||||
"schema_id": schema_id,
|
||||
@ -118,59 +171,26 @@ fn cache_json_schemas(types: JsonB, puncs: JsonB) -> JsonB {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add request schema as resource if present - use {punc_name}.request
|
||||
if let Some(request_schema) = punc_obj.get("request") {
|
||||
if !request_schema.is_null() {
|
||||
let request_schema_id = format!("{}.request", punc_name);
|
||||
if let Err(e) = add_schema_resource(&mut compiler, &request_schema_id, request_schema.clone(), punc_strict, &mut errors) {
|
||||
errors.push(json!({
|
||||
"code": "PUNC_REQUEST_SCHEMA_RESOURCE_FAILED",
|
||||
"message": format!("Failed to add request schema resource for punc '{}'", punc_name),
|
||||
"details": {
|
||||
"punc_name": punc_name,
|
||||
"schema_id": request_schema_id,
|
||||
"cause": format!("{}", e)
|
||||
}
|
||||
}));
|
||||
} else {
|
||||
all_schema_ids.push(request_schema_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add response schema as resource if present - use {punc_name}.response
|
||||
if let Some(response_schema) = punc_obj.get("response") {
|
||||
if !response_schema.is_null() {
|
||||
let response_schema_id = format!("{}.response", punc_name);
|
||||
if let Err(e) = add_schema_resource(&mut compiler, &response_schema_id, response_schema.clone(), punc_strict, &mut errors) {
|
||||
errors.push(json!({
|
||||
"code": "PUNC_RESPONSE_SCHEMA_RESOURCE_FAILED",
|
||||
"message": format!("Failed to add response schema resource for punc '{}'", punc_name),
|
||||
"details": {
|
||||
"punc_name": punc_name,
|
||||
"schema_id": response_schema_id,
|
||||
"cause": format!("{}", e)
|
||||
}
|
||||
}));
|
||||
} else {
|
||||
all_schema_ids.push(response_schema_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 3: Compile all schemas now that all resources are added
|
||||
// Phase 4: Compile all schemas now that all resources are added
|
||||
if !errors.is_empty() {
|
||||
// If we had errors adding resources, don't attempt compilation
|
||||
return JsonB(json!({ "errors": errors }));
|
||||
}
|
||||
|
||||
if let Err(_) = compile_all_schemas(&mut compiler, &mut cache, &all_schema_ids, &mut errors) {
|
||||
// compile_all_schemas already adds errors to the errors vector
|
||||
// Add a high-level wrapper error when schema compilation fails
|
||||
errors.push(json!({
|
||||
"code": "COMPILE_ALL_SCHEMAS_FAILED",
|
||||
"message": "Failed to compile JSON schemas during cache operation",
|
||||
"details": {
|
||||
"cause": "Schema compilation failed - see detailed errors above"
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
if errors.is_empty() {
|
||||
@ -185,12 +205,17 @@ fn add_schema_resource(
|
||||
compiler: &mut Compiler,
|
||||
schema_id: &str,
|
||||
mut schema_value: Value,
|
||||
strict: bool,
|
||||
schema_type: SchemaType,
|
||||
errors: &mut Vec<Value>
|
||||
) -> Result<(), String> {
|
||||
// Apply strict validation to all objects in the schema if requested
|
||||
if strict {
|
||||
apply_strict_validation(&mut schema_value);
|
||||
// Apply strict validation based on schema type
|
||||
match schema_type {
|
||||
SchemaType::Enum | SchemaType::PrivatePunc => {
|
||||
// Enums and private puncs don't need strict validation
|
||||
},
|
||||
SchemaType::Type | SchemaType::PublicPunc => {
|
||||
apply_strict_validation(&mut schema_value, schema_type);
|
||||
}
|
||||
}
|
||||
|
||||
// Use schema_id directly - simple IDs like "entity", "user", "punc.request"
|
||||
@ -256,22 +281,27 @@ fn compile_all_schemas(
|
||||
//
|
||||
// This recursively adds unevaluatedProperties: false to object-type schemas,
|
||||
// but SKIPS schemas inside if/then/else to avoid breaking conditional validation.
|
||||
fn apply_strict_validation(schema: &mut Value) {
|
||||
apply_strict_validation_recursive(schema, false);
|
||||
// For type schemas, it skips the top level to allow inheritance.
|
||||
fn apply_strict_validation(schema: &mut Value, schema_type: SchemaType) {
|
||||
apply_strict_validation_recursive(schema, false, schema_type, true);
|
||||
}
|
||||
|
||||
fn apply_strict_validation_recursive(schema: &mut Value, inside_conditional: bool) {
|
||||
fn apply_strict_validation_recursive(schema: &mut Value, inside_conditional: bool, schema_type: SchemaType, is_top_level: bool) {
|
||||
match schema {
|
||||
Value::Object(map) => {
|
||||
// Skip adding strict validation if we're inside a conditional
|
||||
if !inside_conditional {
|
||||
// Add strict validation to object schemas only at top level
|
||||
if let Some(Value::String(t)) = map.get("type") {
|
||||
if t == "object" && !map.contains_key("unevaluatedProperties") && !map.contains_key("additionalProperties") {
|
||||
// At top level, use unevaluatedProperties: false
|
||||
// This considers all evaluated properties from all schemas
|
||||
map.insert("unevaluatedProperties".to_string(), Value::Bool(false));
|
||||
}
|
||||
// OR if we're at the top level of a type schema (types should be extensible)
|
||||
let skip_strict = inside_conditional || (matches!(schema_type, SchemaType::Type) && is_top_level);
|
||||
|
||||
if !skip_strict {
|
||||
// Apply unevaluatedProperties: false to schemas that have $ref OR type: "object"
|
||||
let has_ref = map.contains_key("$ref");
|
||||
let has_object_type = map.get("type").and_then(|v| v.as_str()) == Some("object");
|
||||
|
||||
if (has_ref || has_object_type) && !map.contains_key("unevaluatedProperties") && !map.contains_key("additionalProperties") {
|
||||
// Use unevaluatedProperties: false to prevent extra properties
|
||||
// This considers all evaluated properties from all schemas including refs
|
||||
map.insert("unevaluatedProperties".to_string(), Value::Bool(false));
|
||||
}
|
||||
}
|
||||
|
||||
@ -279,13 +309,13 @@ fn apply_strict_validation_recursive(schema: &mut Value, inside_conditional: boo
|
||||
for (key, value) in map.iter_mut() {
|
||||
// Mark when we're inside conditional branches
|
||||
let in_conditional = inside_conditional || matches!(key.as_str(), "if" | "then" | "else");
|
||||
apply_strict_validation_recursive(value, in_conditional);
|
||||
apply_strict_validation_recursive(value, in_conditional, schema_type, false)
|
||||
}
|
||||
}
|
||||
Value::Array(arr) => {
|
||||
// Recurse into array items
|
||||
for item in arr.iter_mut() {
|
||||
apply_strict_validation_recursive(item, inside_conditional);
|
||||
apply_strict_validation_recursive(item, inside_conditional, schema_type, false);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
@ -957,6 +987,16 @@ pub mod pg_test {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "pg_test"))]
|
||||
mod helpers {
|
||||
include!("helpers.rs");
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "pg_test"))]
|
||||
mod schemas {
|
||||
include!("schemas.rs");
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "pg_test"))]
|
||||
#[pg_schema]
|
||||
mod tests {
|
||||
|
||||
763
src/schemas.rs
Normal file
763
src/schemas.rs
Normal file
@ -0,0 +1,763 @@
|
||||
use crate::*;
|
||||
use serde_json::{json, Value};
|
||||
use pgrx::JsonB;
|
||||
|
||||
// Helper to convert Value to JsonB
|
||||
fn jsonb(val: Value) -> JsonB {
|
||||
JsonB(val)
|
||||
}
|
||||
|
||||
pub fn simple_schemas() -> JsonB {
|
||||
let enums = json!([]);
|
||||
let types = json!([]);
|
||||
let puncs = json!([{
|
||||
"name": "simple",
|
||||
"public": false,
|
||||
"schemas": [{
|
||||
"$id": "simple.request",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": { "type": "string" },
|
||||
"age": { "type": "integer", "minimum": 0 }
|
||||
},
|
||||
"required": ["name", "age"]
|
||||
}]
|
||||
}]);
|
||||
|
||||
cache_json_schemas(jsonb(enums), jsonb(types), jsonb(puncs))
|
||||
}
|
||||
|
||||
pub fn invalid_schemas() -> JsonB {
|
||||
let enums = json!([]);
|
||||
let types = json!([]);
|
||||
let puncs = json!([{
|
||||
"name": "invalid_punc",
|
||||
"public": false,
|
||||
"schemas": [{
|
||||
"$id": "invalid_punc.request",
|
||||
"type": ["invalid_type_value"]
|
||||
}]
|
||||
}]);
|
||||
|
||||
cache_json_schemas(jsonb(enums), jsonb(types), jsonb(puncs))
|
||||
}
|
||||
|
||||
pub fn errors_schemas() -> JsonB {
|
||||
let enums = json!([]);
|
||||
let types = json!([]);
|
||||
let puncs = json!([{
|
||||
"name": "detailed_errors_test",
|
||||
"public": false,
|
||||
"schemas": [{
|
||||
"$id": "detailed_errors_test.request",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"address": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"street": { "type": "string" },
|
||||
"city": { "type": "string", "maxLength": 10 }
|
||||
},
|
||||
"required": ["street", "city"]
|
||||
}
|
||||
},
|
||||
"required": ["address"]
|
||||
}]
|
||||
}]);
|
||||
|
||||
cache_json_schemas(jsonb(enums), jsonb(types), jsonb(puncs))
|
||||
}
|
||||
|
||||
pub fn oneof_schemas() -> JsonB {
|
||||
let enums = json!([]);
|
||||
let types = json!([]);
|
||||
let puncs = json!([{
|
||||
"name": "oneof_test",
|
||||
"public": false,
|
||||
"schemas": [{
|
||||
"$id": "oneof_test.request",
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"string_prop": { "type": "string", "maxLength": 5 }
|
||||
},
|
||||
"required": ["string_prop"]
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"number_prop": { "type": "number", "minimum": 10 }
|
||||
},
|
||||
"required": ["number_prop"]
|
||||
}
|
||||
]
|
||||
}]
|
||||
}]);
|
||||
|
||||
cache_json_schemas(jsonb(enums), jsonb(types), jsonb(puncs))
|
||||
}
|
||||
|
||||
pub fn root_types_schemas() -> JsonB {
|
||||
let enums = json!([]);
|
||||
let types = json!([]);
|
||||
let puncs = json!([
|
||||
{
|
||||
"name": "object_test",
|
||||
"public": false,
|
||||
"schemas": [{
|
||||
"$id": "object_test.request",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": { "type": "string" },
|
||||
"age": { "type": "integer", "minimum": 0 }
|
||||
},
|
||||
"required": ["name", "age"]
|
||||
}]
|
||||
},
|
||||
{
|
||||
"name": "array_test",
|
||||
"public": false,
|
||||
"schemas": [{
|
||||
"$id": "array_test.request",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": { "type": "string", "format": "uuid" }
|
||||
}
|
||||
}
|
||||
}]
|
||||
}
|
||||
]);
|
||||
|
||||
cache_json_schemas(jsonb(enums), jsonb(types), jsonb(puncs))
|
||||
}
|
||||
|
||||
pub fn strict_schemas() -> JsonB {
|
||||
let enums = json!([]);
|
||||
let types = json!([]);
|
||||
let puncs = json!([
|
||||
{
|
||||
"name": "basic_strict_test",
|
||||
"public": true,
|
||||
"schemas": [{
|
||||
"$id": "basic_strict_test.request",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": { "type": "string" }
|
||||
}
|
||||
}]
|
||||
},
|
||||
{
|
||||
"name": "non_strict_test",
|
||||
"public": false,
|
||||
"schemas": [{
|
||||
"$id": "non_strict_test.request",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": { "type": "string" }
|
||||
}
|
||||
}]
|
||||
},
|
||||
{
|
||||
"name": "nested_strict_test",
|
||||
"public": true,
|
||||
"schemas": [{
|
||||
"$id": "nested_strict_test.request",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"user": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": { "type": "string" }
|
||||
}
|
||||
},
|
||||
"items": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": { "type": "string" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}]
|
||||
},
|
||||
{
|
||||
"name": "already_unevaluated_test",
|
||||
"public": true,
|
||||
"schemas": [{
|
||||
"$id": "already_unevaluated_test.request",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": { "type": "string" }
|
||||
},
|
||||
"unevaluatedProperties": true
|
||||
}]
|
||||
},
|
||||
{
|
||||
"name": "already_additional_test",
|
||||
"public": true,
|
||||
"schemas": [{
|
||||
"$id": "already_additional_test.request",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": { "type": "string" }
|
||||
},
|
||||
"additionalProperties": false
|
||||
}]
|
||||
},
|
||||
{
|
||||
"name": "conditional_strict_test",
|
||||
"public": true,
|
||||
"schemas": [{
|
||||
"$id": "conditional_strict_test.request",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"creating": { "type": "boolean" }
|
||||
},
|
||||
"if": {
|
||||
"properties": {
|
||||
"creating": { "const": true }
|
||||
}
|
||||
},
|
||||
"then": {
|
||||
"properties": {
|
||||
"name": { "type": "string" }
|
||||
},
|
||||
"required": ["name"]
|
||||
}
|
||||
}]
|
||||
}
|
||||
]);
|
||||
|
||||
cache_json_schemas(jsonb(enums), jsonb(types), jsonb(puncs))
|
||||
}
|
||||
|
||||
pub fn required_schemas() -> JsonB {
|
||||
let enums = json!([]);
|
||||
let types = json!([]);
|
||||
let puncs = json!([{
|
||||
"name": "basic_validation_test",
|
||||
"public": false,
|
||||
"schemas": [{
|
||||
"$id": "basic_validation_test.request",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": { "type": "string" },
|
||||
"age": { "type": "integer", "minimum": 0 }
|
||||
},
|
||||
"required": ["name", "age"]
|
||||
}]
|
||||
}]);
|
||||
|
||||
cache_json_schemas(jsonb(enums), jsonb(types), jsonb(puncs))
|
||||
}
|
||||
|
||||
pub fn dependencies_schemas() -> JsonB {
|
||||
let enums = json!([]);
|
||||
let types = json!([]);
|
||||
let puncs = json!([{
|
||||
"name": "dependency_split_test",
|
||||
"public": false,
|
||||
"schemas": [{
|
||||
"$id": "dependency_split_test.request",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"creating": { "type": "boolean" },
|
||||
"name": { "type": "string" },
|
||||
"kind": { "type": "string" },
|
||||
"description": { "type": "string" }
|
||||
},
|
||||
"dependencies": {
|
||||
"creating": ["name", "kind"]
|
||||
}
|
||||
}]
|
||||
}]);
|
||||
|
||||
cache_json_schemas(jsonb(enums), jsonb(types), jsonb(puncs))
|
||||
}
|
||||
|
||||
pub fn nested_req_deps_schemas() -> JsonB {
|
||||
let enums = json!([]);
|
||||
let types = json!([]);
|
||||
let puncs = json!([{
|
||||
"name": "nested_dep_test",
|
||||
"public": false,
|
||||
"schemas": [{
|
||||
"$id": "nested_dep_test.request",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"items": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": { "type": "string" },
|
||||
"creating": { "type": "boolean" },
|
||||
"name": { "type": "string" },
|
||||
"kind": { "type": "string" }
|
||||
},
|
||||
"required": ["id"],
|
||||
"dependencies": {
|
||||
"creating": ["name", "kind"]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["items"]
|
||||
}]
|
||||
}]);
|
||||
|
||||
cache_json_schemas(jsonb(enums), jsonb(types), jsonb(puncs))
|
||||
}
|
||||
|
||||
pub fn additional_properties_schemas() -> JsonB {
|
||||
let enums = json!([]);
|
||||
let types = json!([]);
|
||||
let puncs = json!([
|
||||
{
|
||||
"name": "additional_props_test",
|
||||
"public": false,
|
||||
"schemas": [{
|
||||
"$id": "additional_props_test.request",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": { "type": "string" },
|
||||
"age": { "type": "number" }
|
||||
},
|
||||
"additionalProperties": false
|
||||
}]
|
||||
},
|
||||
{
|
||||
"name": "nested_additional_props_test",
|
||||
"public": false,
|
||||
"schemas": [{
|
||||
"$id": "nested_additional_props_test.request",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"user": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": { "type": "string" }
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
}]
|
||||
}
|
||||
]);
|
||||
|
||||
cache_json_schemas(jsonb(enums), jsonb(types), jsonb(puncs))
|
||||
}
|
||||
|
||||
pub fn unevaluated_properties_schemas() -> JsonB {
|
||||
let enums = json!([]);
|
||||
let types = json!([]);
|
||||
let puncs = json!([
|
||||
{
|
||||
"name": "simple_unevaluated_test",
|
||||
"public": false,
|
||||
"schemas": [{
|
||||
"$id": "simple_unevaluated_test.request",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": { "type": "string" },
|
||||
"age": { "type": "number" }
|
||||
},
|
||||
"patternProperties": {
|
||||
"^attr_": { "type": "string" }
|
||||
},
|
||||
"unevaluatedProperties": false
|
||||
}]
|
||||
},
|
||||
{
|
||||
"name": "conditional_unevaluated_test",
|
||||
"public": false,
|
||||
"schemas": [{
|
||||
"$id": "conditional_unevaluated_test.request",
|
||||
"type": "object",
|
||||
"allOf": [
|
||||
{
|
||||
"properties": {
|
||||
"firstName": { "type": "string" }
|
||||
}
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"lastName": { "type": "string" }
|
||||
}
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"age": { "type": "number" }
|
||||
},
|
||||
"unevaluatedProperties": false
|
||||
}]
|
||||
}
|
||||
]);
|
||||
|
||||
cache_json_schemas(jsonb(enums), jsonb(types), jsonb(puncs))
|
||||
}
|
||||
|
||||
pub fn format_schemas() -> JsonB {
|
||||
let enums = json!([]);
|
||||
let types = json!([]);
|
||||
let puncs = json!([{
|
||||
"name": "format_test",
|
||||
"public": false,
|
||||
"schemas": [{
|
||||
"$id": "format_test.request",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"uuid": { "type": "string", "format": "uuid" },
|
||||
"date_time": { "type": "string", "format": "date-time" },
|
||||
"email": { "type": "string", "format": "email" }
|
||||
}
|
||||
}]
|
||||
}]);
|
||||
|
||||
cache_json_schemas(jsonb(enums), jsonb(types), jsonb(puncs))
|
||||
}
|
||||
|
||||
pub fn property_merging_schemas() -> JsonB {
|
||||
let enums = json!([]);
|
||||
let types = json!([
|
||||
{
|
||||
"name": "entity",
|
||||
"schemas": [{
|
||||
"$id": "entity",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": { "type": "string" },
|
||||
"name": { "type": "string" }
|
||||
},
|
||||
"required": ["id"]
|
||||
}]
|
||||
},
|
||||
{
|
||||
"name": "user",
|
||||
"schemas": [{
|
||||
"$id": "user",
|
||||
"$ref": "entity",
|
||||
"properties": {
|
||||
"password": { "type": "string", "minLength": 8 }
|
||||
},
|
||||
"required": ["password"]
|
||||
}]
|
||||
},
|
||||
{
|
||||
"name": "person",
|
||||
"schemas": [{
|
||||
"$id": "person",
|
||||
"$ref": "user",
|
||||
"properties": {
|
||||
"first_name": { "type": "string", "minLength": 1 },
|
||||
"last_name": { "type": "string", "minLength": 1 }
|
||||
},
|
||||
"required": ["first_name", "last_name"]
|
||||
}]
|
||||
}
|
||||
]);
|
||||
|
||||
let puncs = json!([]);
|
||||
|
||||
cache_json_schemas(jsonb(enums), jsonb(types), jsonb(puncs))
|
||||
}
|
||||
|
||||
pub fn required_merging_schemas() -> JsonB {
|
||||
let enums = json!([]);
|
||||
|
||||
let types = json!([
|
||||
{
|
||||
"name": "entity",
|
||||
"schemas": [{
|
||||
"$id": "entity",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": { "type": "string", "format": "uuid" },
|
||||
"type": { "type": "string" },
|
||||
"created_by": { "type": "string", "format": "uuid" }
|
||||
},
|
||||
"required": ["id", "type", "created_by"]
|
||||
}]
|
||||
},
|
||||
{
|
||||
"name": "user",
|
||||
"schemas": [{
|
||||
"$id": "user",
|
||||
"$ref": "entity",
|
||||
"properties": {
|
||||
"password": { "type": "string", "minLength": 8 }
|
||||
},
|
||||
"if": {
|
||||
"properties": { "type": { "const": "user" } }
|
||||
},
|
||||
"then": {
|
||||
"required": ["password"]
|
||||
}
|
||||
}]
|
||||
},
|
||||
{
|
||||
"name": "person",
|
||||
"schemas": [{
|
||||
"$id": "person",
|
||||
"$ref": "user",
|
||||
"properties": {
|
||||
"first_name": { "type": "string", "minLength": 1 },
|
||||
"last_name": { "type": "string", "minLength": 1 }
|
||||
},
|
||||
"if": {
|
||||
"properties": { "type": { "const": "person" } }
|
||||
},
|
||||
"then": {
|
||||
"required": ["first_name", "last_name"]
|
||||
}
|
||||
}]
|
||||
}
|
||||
]);
|
||||
|
||||
let puncs = json!([]);
|
||||
|
||||
cache_json_schemas(jsonb(enums), jsonb(types), jsonb(puncs))
|
||||
}
|
||||
|
||||
pub fn dependencies_merging_schemas() -> JsonB {
|
||||
let enums = json!([]);
|
||||
|
||||
let types = json!([
|
||||
{
|
||||
"name": "entity",
|
||||
"schemas": [{
|
||||
"$id": "entity",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": { "type": "string", "format": "uuid" },
|
||||
"type": { "type": "string" },
|
||||
"created_by": { "type": "string", "format": "uuid" },
|
||||
"creating": { "type": "boolean" },
|
||||
"name": { "type": "string" }
|
||||
},
|
||||
"required": ["id", "type", "created_by"],
|
||||
"dependencies": {
|
||||
"creating": ["name"]
|
||||
}
|
||||
}]
|
||||
},
|
||||
{
|
||||
"name": "user",
|
||||
"schemas": [{
|
||||
"$id": "user",
|
||||
"$ref": "entity",
|
||||
"properties": {
|
||||
"password": { "type": "string", "minLength": 8 }
|
||||
},
|
||||
"dependencies": {
|
||||
"creating": ["name"]
|
||||
}
|
||||
}]
|
||||
},
|
||||
{
|
||||
"name": "person",
|
||||
"schemas": [{
|
||||
"$id": "person",
|
||||
"$ref": "user",
|
||||
"properties": {
|
||||
"first_name": { "type": "string", "minLength": 1 },
|
||||
"last_name": { "type": "string", "minLength": 1 }
|
||||
},
|
||||
"dependencies": {
|
||||
"creating": ["first_name", "last_name"]
|
||||
}
|
||||
}]
|
||||
}
|
||||
]);
|
||||
|
||||
let puncs = json!([]);
|
||||
|
||||
cache_json_schemas(jsonb(enums), jsonb(types), jsonb(puncs))
|
||||
}
|
||||
|
||||
pub fn punc_with_refs_schemas() -> JsonB {
|
||||
let enums = json!([]);
|
||||
|
||||
let types = json!([
|
||||
{
|
||||
"name": "entity",
|
||||
"schemas": [{
|
||||
"$id": "entity",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": { "type": "string" },
|
||||
"name": { "type": "string" }
|
||||
},
|
||||
"required": ["id"]
|
||||
}]
|
||||
},
|
||||
{
|
||||
"name": "person",
|
||||
"schemas": [{
|
||||
"$id": "person",
|
||||
"$ref": "entity",
|
||||
"properties": {
|
||||
"first_name": { "type": "string", "minLength": 1 },
|
||||
"last_name": { "type": "string", "minLength": 1 },
|
||||
"address": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"street": { "type": "string" },
|
||||
"city": { "type": "string" }
|
||||
},
|
||||
"required": ["street", "city"]
|
||||
}
|
||||
}
|
||||
}]
|
||||
}
|
||||
]);
|
||||
|
||||
let puncs = json!([
|
||||
{
|
||||
"name": "public_ref_test",
|
||||
"public": true,
|
||||
"schemas": [{
|
||||
"$id": "public_ref_test.request",
|
||||
"$ref": "person"
|
||||
}]
|
||||
},
|
||||
{
|
||||
"name": "private_ref_test",
|
||||
"public": false,
|
||||
"schemas": [{
|
||||
"$id": "private_ref_test.request",
|
||||
"$ref": "person"
|
||||
}]
|
||||
}
|
||||
]);
|
||||
|
||||
cache_json_schemas(jsonb(enums), jsonb(types), jsonb(puncs))
|
||||
}
|
||||
|
||||
pub fn enum_schemas() -> JsonB {
|
||||
let enums = json!([
|
||||
{
|
||||
"name": "task_priority",
|
||||
"values": ["low", "medium", "high", "urgent"],
|
||||
"schemas": [{
|
||||
"$id": "task_priority",
|
||||
"type": "string",
|
||||
"enum": ["low", "medium", "high", "urgent"]
|
||||
}]
|
||||
}
|
||||
]);
|
||||
|
||||
let types = json!([]);
|
||||
|
||||
let puncs = json!([{
|
||||
"name": "enum_ref_test",
|
||||
"public": false,
|
||||
"schemas": [{
|
||||
"$id": "enum_ref_test.request",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"priority": { "$ref": "task_priority" }
|
||||
},
|
||||
"required": ["priority"]
|
||||
}]
|
||||
}]);
|
||||
|
||||
cache_json_schemas(jsonb(enums), jsonb(types), jsonb(puncs))
|
||||
}
|
||||
|
||||
pub fn punc_local_refs_schemas() -> JsonB {
|
||||
let enums = json!([]);
|
||||
|
||||
let types = json!([
|
||||
{
|
||||
"name": "global_thing",
|
||||
"schemas": [{
|
||||
"$id": "global_thing",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": { "type": "string", "format": "uuid" }
|
||||
},
|
||||
"required": ["id"]
|
||||
}]
|
||||
}
|
||||
]);
|
||||
|
||||
let puncs = json!([
|
||||
{
|
||||
"name": "punc_with_local_ref_test",
|
||||
"public": false,
|
||||
"schemas": [
|
||||
{
|
||||
"$id": "local_address",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"street": { "type": "string" },
|
||||
"city": { "type": "string" }
|
||||
},
|
||||
"required": ["street", "city"]
|
||||
},
|
||||
{
|
||||
"$id": "punc_with_local_ref_test.request",
|
||||
"$ref": "local_address"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "punc_with_local_ref_to_global_test",
|
||||
"public": false,
|
||||
"schemas": [
|
||||
{
|
||||
"$id": "local_user_with_thing",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"user_name": { "type": "string" },
|
||||
"thing": { "$ref": "global_thing" }
|
||||
},
|
||||
"required": ["user_name", "thing"]
|
||||
},
|
||||
{
|
||||
"$id": "punc_with_local_ref_to_global_test.request",
|
||||
"$ref": "local_user_with_thing"
|
||||
}
|
||||
]
|
||||
}
|
||||
]);
|
||||
|
||||
cache_json_schemas(jsonb(enums), jsonb(types), jsonb(puncs))
|
||||
}
|
||||
|
||||
pub fn title_override_schemas() -> JsonB {
|
||||
let enums = json!([]);
|
||||
|
||||
let types = json!([
|
||||
{
|
||||
"name": "base_with_title",
|
||||
"schemas": [{
|
||||
"$id": "base_with_title",
|
||||
"type": "object",
|
||||
"title": "Base Title",
|
||||
"properties": {
|
||||
"name": { "type": "string" }
|
||||
},
|
||||
"required": ["name"]
|
||||
}]
|
||||
},
|
||||
{
|
||||
"name": "override_with_title",
|
||||
"schemas": [{
|
||||
"$id": "override_with_title",
|
||||
"$ref": "base_with_title",
|
||||
"title": "Override Title"
|
||||
}]
|
||||
}
|
||||
]);
|
||||
|
||||
let puncs = json!([]);
|
||||
|
||||
cache_json_schemas(jsonb(enums), jsonb(types), jsonb(puncs))
|
||||
}
|
||||
1693
src/tests.rs
1693
src/tests.rs
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user