// builder
///|
/// Schema builder like a zod schema
/// Example usage:
/// ```moon
/// let b = @jsonschema.Builder::new()
/// let schema = b.object(
/// properties={
/// name: b.string(),
/// age: b.number(),
/// },
/// required=["name", "age"],
/// )
/// let _result = schema.validate({ "name": "Alice", "age": 30.0 }).unwrap()
/// ```
pub struct Builder {} derive(Debug, Eq)
///|
pub fn Builder::new() -> Builder {
Builder::{ }
}
///|
/// { "type": "string" }
pub fn Builder::string(
_self : Builder,
minLength? : Int,
maxLength? : Int,
enum_? : Array[String],
) -> JsonSchema {
JsonSchema::String({ min_length: minLength, max_length: maxLength, enum_ })
}
///|
/// { "type": "number" }
pub fn Builder::number(
_self : Builder,
minimum? : Double,
maximum? : Double,
exclusive_minimum? : Double,
exclusive_maximum? : Double,
) -> JsonSchema {
JsonSchema::Number({ minimum, maximum, exclusive_minimum, exclusive_maximum },
// multipleOf,
)
}
///|
/// { "type": "integer" }
pub fn Builder::integer(
_self : Builder,
minimum? : Int,
maximum? : Int,
exclusive_minimum? : Int,
exclusive_maximum? : Int,
) -> JsonSchema {
JsonSchema::Integer({ minimum, maximum, exclusive_minimum, exclusive_maximum })
}
///|
/// { "type": "array" }
pub fn Builder::array(
_self : Builder,
items? : JsonSchema,
prefix_items? : Array[JsonSchema],
contains? : JsonSchema,
min_contains? : Int,
max_contains? : Int,
min_items? : Int,
max_items? : Int,
) -> JsonSchema {
JsonSchema::Array({
items,
prefix_items,
contains,
min_contains,
max_contains,
min_items,
max_items,
})
}
///|
/// { "type": "null" }
pub fn Builder::null(_self : Builder) -> JsonSchema {
Null(NullSchema::{ })
}
///|
/// { "type": "boolean" }
pub fn Builder::boolean(_self : Builder) -> JsonSchema {
Boolean(BooleanSchema::{ })
}
///|
/// { "type": "any" }
pub fn Builder::any(_self : Builder) -> JsonSchema {
Any(AnySchema::{ })
}
///|
/// { "type": "object" }
pub fn Builder::object(
_self : Builder,
properties? : Map[String, JsonSchema],
required? : Array[String],
additional_properties? : JsonSchema,
// patternProperties? : Map[String, JsonSchema],
// propertyNames? : JsonSchema,
// dependentSchemas? : Map[String, JsonSchema],
// dependentRequired? : Map[String, Array[String]],
// minProperties? : Int,
// maxProperties? : Int,
// over
required_all? : Bool = false,
) -> JsonSchema {
JsonSchema::Object(ObjectSchema::{
// description,
properties,
required: if required_all && properties is Some(props) {
props.keys().to_array() |> Some
} else {
required
},
additional_properties,
},
// pattern_properties: patternProperties,
// property_names: propertyNames,
// dependent_schemas: dependentSchemas,
// dependent_required: dependentRequired,
// min_properties: minProperties,
// max_properties: maxProperties,
)
}
///|
/// { "enum": [...] }
pub fn Builder::enum_(_self : Builder, values : Array[Json]) -> JsonSchema {
JsonSchema::Enum({ values, })
}
///|
/// { "const": value }
pub fn Builder::const_(_self : Builder, value : Json) -> JsonSchema {
JsonSchema::Const({ value, })
}
///|
/// { "oneOf": [...] }
pub fn Builder::one_of(
_self : Builder,
schemas : Array[JsonSchema],
) -> JsonSchema {
JsonSchema::OneOf({ schemas, parsed_enumerable: None, parsed_nullable: None })
}
///|
/// { "oneOf": [...] }
pub fn Builder::enumerable_one_of(
self : Builder,
tag : String,
schemas : Array[JsonSchema],
params~ : JsonSchema?,
) -> JsonSchema {
// TODO: validate schemas
if params is Some(params) {
JsonSchema::OneOf({
schemas: [
self.const_(tag.to_json()),
self.array(prefix_items=schemas),
params,
],
parsed_enumerable: None,
parsed_nullable: None,
})
} else {
JsonSchema::OneOf({
schemas: [self.const_(tag.to_json()), self.array(prefix_items=schemas)],
parsed_enumerable: None,
parsed_nullable: None,
})
}
}
///|
/// { "anyOf": [...] }
pub fn Builder::any_of(
_self : Builder,
schemas : Array[JsonSchema],
) -> JsonSchema {
JsonSchema::AnyOf({ schemas, })
}
///|
/// { "allOf": [...] }
pub fn Builder::all_of(
_self : Builder,
schemas : Array[JsonSchema],
) -> JsonSchema {
JsonSchema::AllOf({ schemas, })
}
///|
/// { "$ref": ... }
pub fn Builder::ref_from_string(
_self : Builder,
pointer : String,
) -> JsonSchema {
// TODO: validate pointer
let p = JsonPointer::from_string(pointer).unwrap()
JsonSchema::Ref({ pointer: p })
}
///|
pub fn Builder::ref_(_self : Builder, pointer : JsonPointer) -> JsonSchema {
JsonSchema::Ref({ pointer, })
}
///|
test "parse object" {
let input =
#|{
#| "type": "object",
#| "properties": {
#| "name": { "type": "string" },
#| "age": { "type": "number" }
#| },
#| "required": ["name", "age"]
#|}
let jsonschema : Json = @json.parse(input)
let schema : JsonSchema = @json.from_json(jsonschema)
let resolver = build_resolver({})
schema.validate(
{ "name": "Alice", "age": 30.0 },
resolver~,
json_path=JsonPointer::Root,
schema_path=JsonPointer::Root,
)
|> debug_inspect(content="[]")
schema.validate(
{ "name": 42, "age": 30.0 },
resolver~,
json_path=JsonPointer::Root,
schema_path=JsonPointer::Root,
)
|> debug_inspect(
content=(
#|[
#| {
#| input: Number(42),
#| json_path: Key(Root, key="name"),
#| schema_path: Key(Key(Root, key="properties"), key="name"),
#| message: "Value is not a string",
#| children: None,
#| },
#|]
),
)
()
}
///|
test "parse array" {
let input =
#|{
#| "type": "array",
#| "items": { "type": "string" }
#|}
let jsonschema : Json = @json.parse(input)
let schema : JsonSchema = @json.from_json(jsonschema)
let resolver = build_resolver({})
schema.validate(
["a", "b"],
resolver~,
json_path=JsonPointer::Root,
schema_path=JsonPointer::Root,
)
|> debug_inspect(content="[]")
schema.validate(
["a", 1],
resolver~,
json_path=JsonPointer::Root,
schema_path=JsonPointer::Root,
)
|> debug_inspect(
content=(
#|[
#| {
#| input: Number(1),
#| json_path: Index(Root, index=1),
#| schema_path: Key(Root, key="items"),
#| message: "Value is not a string",
#| children: None,
#| },
#|]
),
)
}
///|
test "parse nested" {
let input =
#|{
#| "type": "object",
#| "properties": {
#| "name": { "type": "string" },
#| "age": { "type": "integer" },
#| "friends": { "type": "array", "items": { "type": "string" } },
#| "nullValue": { "type": "null" },
#| "anyValue": { "type": "any" }
#| },
#| "required": ["name", "age", "friends", "nullValue", "anyValue"]
#|}
let jsonschema : Json = @json.parse(input)
let schema : JsonSchema = @json.from_json(jsonschema)
let resolver = build_resolver({})
schema.validate(
{
"name": "Alice",
"age": 30,
"friends": ["Bob"],
"nullValue": null,
"anyValue": "anything",
},
resolver~,
json_path=JsonPointer::Root,
schema_path=JsonPointer::Root,
)
|> debug_inspect(content="[]")
schema.validate(
{
"name": "Alice",
"age": 30,
"friends": [1],
"nullValue": null,
"anyValue": "anything",
},
resolver~,
json_path=JsonPointer::Root,
schema_path=JsonPointer::Root,
)
|> debug_inspect(
content=(
#|[
#| {
#| input: Number(1),
#| json_path: Index(Key(Root, key="friends"), index=0),
#| schema_path: Key(Key(Key(Root, key="properties"), key="friends"), key="items"),
#| message: "Value is not a string",
#| children: None,
#| },
#|]
),
)
}