///|
/// A trait representing a JSON Schema (Draft 2020-12) node.
/// Sealed to this package to prevent external invalid schema nodes.
priv trait SchemaNode: ToJson + @debug.Debug {
fn validate(
Self,
instance : Json,
path~ : @json.JsonPath,
depth~ : Int,
ctx~ : ValidationContext,
) -> Unit
}
///|
/// A first-class JSON Schema value wrapping any JSON Schema construct.
pub struct Schema {
priv node : &SchemaNode
priv defs : Map[String, Schema]
}
///|
pub impl Eq for Schema with fn equal(self : Schema, other : Schema) -> Bool {
self.to_json() == other.to_json()
}
///|
pub impl Show for Schema with fn output(self : Schema, logger : &Logger) -> Unit {
logger.write_string(self.to_json().stringify())
}
///|
pub impl @debug.Debug for Schema with fn to_repr(self : Schema) -> @debug.Repr {
@debug.Repr::string(self.to_json().stringify())
}
///|
pub impl ToJson for Schema with fn to_json(self : Schema) -> Json {
ToJson::to_json(self.node)
}
///|
pub fn Schema::to_json(self : Schema) -> Json {
ToJson::to_json(self.node)
}
///|
/// Parse a JSON value as a JSON Schema document (Draft 2020-12).
pub fn Schema::from_json(
value : Json,
max_depth? : Int = 128,
max_nodes? : Int = 10000,
) -> Schema raise SchemaError {
if max_depth <= 0 || max_nodes <= 0 {
raise InvalidSchema("schema compilation limits must be positive")
}
validate_document_compilation(value, max_depth~, max_nodes~)
let defs : Map[String, Schema] = Map([])
collect_definitions(value, defs~, pointer="#", depth=0, max_depth~)
let parsed = parse_schema_json(value)
{ node: parsed.node, defs }
}
///|
/// Validate a JSON instance against this schema.
/// Raises `ValidationIssues` if any validation constraints are violated.
pub fn Schema::validate(
self : Schema,
instance : Json,
max_depth? : Int = 128,
) -> Unit raise ValidationIssues {
let ctx : ValidationContext = { defs: self.defs, max_depth, issues: [] }
self.node.validate(instance, path=root_json_path(), depth=0, ctx~)
if !ctx.issues.is_empty() {
raise ValidationIssues(ctx.issues)
}
}
///|
fn[T : SchemaNode] Schema::from_node(
node : T,
defs? : Map[String, Schema] = {},
) -> Schema {
{ node, defs }
}
///|
/// A boolean JSON schema that always passes validation (`true`).
pub fn Schema::always() -> Schema {
Schema::from_node(BooleanSchema::always())
}
///|
/// A boolean JSON schema that always fails validation (`false`).
pub fn Schema::never() -> Schema {
Schema::from_node(BooleanSchema::never())
}
///|
/// A `null` type schema (`type: "null"`).
pub fn Schema::null(description? : String) -> Schema {
Schema::from_node(NullSchema(description?))
}
///|
/// A `boolean` primitive type schema (`type: "boolean"`).
pub fn Schema::boolean(description? : String) -> Schema {
Schema::from_node(BoolTypeSchema(description?))
}
///|
/// A `string` type schema.
pub fn Schema::string(
min_length? : Int,
max_length? : Int,
description? : String,
) -> Schema raise SchemaError {
Schema::from_node(StringSchema(min_length?, max_length?, description?))
}
///|
/// An `integer` type schema.
pub fn Schema::integer(
minimum? : Int,
maximum? : Int,
description? : String,
) -> Schema raise SchemaError {
Schema::from_node(IntegerSchema(minimum?, maximum?, description?))
}
///|
/// A `number` type schema.
pub fn Schema::number(
minimum? : Double,
maximum? : Double,
exclusive_minimum? : Double,
exclusive_maximum? : Double,
multiple_of? : Double,
description? : String,
) -> Schema raise SchemaError {
Schema::from_node(
NumberSchema(
minimum?,
maximum?,
exclusive_minimum?,
exclusive_maximum?,
multiple_of?,
description?,
),
)
}
///|
/// An `array` type schema.
pub fn Schema::array(
items? : Schema,
min_items? : Int,
max_items? : Int,
unique_items? : Bool = false,
description? : String,
) -> Schema raise SchemaError {
Schema::from_node(
ArraySchema(items?, min_items?, max_items?, unique_items~, description?),
)
}
///|
/// An `object` type schema.
pub fn Schema::object(
properties? : Map[String, Schema] = {},
required? : Array[String] = [],
additional_properties? : Bool = true,
min_properties? : Int,
max_properties? : Int,
description? : String,
) -> Schema raise SchemaError {
Schema::from_node(
ObjectSchema(
properties~,
required~,
additional_properties~,
min_properties?,
max_properties?,
description?,
),
)
}
///|
/// A `const` assertion schema.
pub fn[T : ToJson] Schema::constant(value : T, description? : String) -> Schema {
Schema::from_node(ConstSchema::from_value(value, description?))
}
///|
/// An `enum` assertion schema.
pub fn[T : ToJson] Schema::enumeration(
values : Array[T],
description? : String,
) -> Schema raise SchemaError {
Schema::from_node(EnumSchema::from_values(values, description?))
}
///|
/// An `allOf` combinator schema.
pub fn Schema::all_of(values : Array[Schema], description? : String) -> Schema {
Schema::from_node(AllOfSchema(values, description?))
}
///|
/// An `anyOf` combinator schema.
pub fn Schema::any_of(
values : Array[Schema],
description? : String,
) -> Schema raise SchemaError {
Schema::from_node(AnyOfSchema(values, description?))
}
///|
/// A `oneOf` combinator schema.
pub fn Schema::one_of(
values : Array[Schema],
description? : String,
) -> Schema raise SchemaError {
Schema::from_node(OneOfSchema(values, description?))
}
///|
/// A `not` combinator schema.
pub fn Schema::not(value : Schema, description? : String) -> Schema {
Schema::from_node(NotSchema(value, description?))
}
///|
/// A `$ref` reference schema.
pub fn Schema::ref_(
reference : String,
description? : String,
) -> Schema raise SchemaError {
Schema::from_node(RefSchema(reference, description?))
}
///|
/// An unconstrained escape hatch schema for raw JSON.
pub fn Schema::raw(value : Json) -> Schema raise SchemaError {
Schema::from_node(RawSchema(value))
}