///|
pub(all) enum SimpleType {
ArrayType
BooleanType
IntegerType
NullType
NumberType
ObjectType
StringType
} derive(Eq, Debug)
///|
pub fn SimpleType::to_string(self : SimpleType) -> String {
match self {
ArrayType => "array"
BooleanType => "boolean"
IntegerType => "integer"
NullType => "null"
NumberType => "number"
ObjectType => "object"
StringType => "string"
}
}
///|
pub struct TypeConstraint {
types : Array[SimpleType]
union : Bool
} derive(Debug)
///|
pub fn TypeConstraint::one(type_ : SimpleType) -> TypeConstraint {
{ types: [type_], union: false, }
}
///|
pub fn TypeConstraint::many(
types : Array[SimpleType],
) -> Result[TypeConstraint, @json.Diagnostic] {
if types.is_empty() {
return Err(
@json.Diagnostic::new(@json.InvalidSchema, "A type set must be nonempty"),
)
}
let seen : Set[String] = Set([])
for type_ in types {
let name = type_.to_string()
if seen.contains(name) {
return Err(
@json.Diagnostic::new(
@json.InvalidSchema,
"A type set must contain unique types",
),
)
}
seen.add(name)
}
Ok({ types: types.copy(), union: true, })
}
///|
pub fn TypeConstraint::types(self : TypeConstraint) -> Array[SimpleType] {
self.types.copy()
}
///|
pub fn TypeConstraint::is_union(self : TypeConstraint) -> Bool {
self.union
}
///|
pub fn TypeConstraint::to_json(self : TypeConstraint) -> @json.Value {
if self.union {
@json.Value::array(self.types.map(t => @json.Value::string(t.to_string())))
} else {
@json.Value::string(self.types[0].to_string())
}
}
///|
pub struct NonNegativeInteger {
number : @json.Number
} derive(Eq, Debug)
///|
pub fn NonNegativeInteger::from_number(
number : @json.Number,
) -> Result[NonNegativeInteger, @json.Diagnostic] {
if number.is_integer() &&
number.numeric_compare(@json.Number::parse("0").unwrap()) >= 0 {
Ok({ number, })
} else {
Err(
@json.Diagnostic::new(
@json.InvalidSchema,
"Expected a nonnegative mathematical integer",
),
)
}
}
///|
pub fn NonNegativeInteger::parse(
token : String,
) -> Result[NonNegativeInteger, @json.Diagnostic] {
match @json.Number::parse(token) {
Ok(number) => NonNegativeInteger::from_number(number)
Err(d) => Err(d)
}
}
///|
pub fn NonNegativeInteger::number(self : NonNegativeInteger) -> @json.Number {
self.number
}
///|
pub struct PositiveNumber {
number : @json.Number
} derive(Eq, Debug)
///|
pub fn PositiveNumber::from_number(
number : @json.Number,
) -> Result[PositiveNumber, @json.Diagnostic] {
if number.numeric_compare(@json.Number::parse("0").unwrap()) > 0 {
Ok({ number, })
} else {
Err(
@json.Diagnostic::new(@json.InvalidSchema, "Expected a positive number"),
)
}
}
///|
pub fn PositiveNumber::parse(
token : String,
) -> Result[PositiveNumber, @json.Diagnostic] {
match @json.Number::parse(token) {
Ok(number) => PositiveNumber::from_number(number)
Err(d) => Err(d)
}
}
///|
pub fn PositiveNumber::number(self : PositiveNumber) -> @json.Number {
self.number
}
///|
pub(all) enum Items {
SingleItems(Schema)
TupleItems(Array[Schema])
} derive(Debug)
///|
pub(all) enum Dependency {
SchemaDependency(Schema)
PropertyDependencies(Array[String])
} derive(Debug)
///|
pub(all) enum Keyword {
Id(@reference.UriReference)
Dialect(@reference.UriReference)
Ref(@reference.Reference)
Comment(String)
Title(String)
Description(String)
Default(@json.Value)
ReadOnly(Bool)
WriteOnly(Bool)
Examples(Array[@json.Value])
MultipleOf(PositiveNumber)
Maximum(@json.Number)
ExclusiveMaximum(@json.Number)
Minimum(@json.Number)
ExclusiveMinimum(@json.Number)
MaxLength(NonNegativeInteger)
MinLength(NonNegativeInteger)
Pattern(String)
AdditionalItems(Schema)
Items(Items)
MaxItems(NonNegativeInteger)
MinItems(NonNegativeInteger)
UniqueItems(Bool)
Contains(Schema)
MaxProperties(NonNegativeInteger)
MinProperties(NonNegativeInteger)
Required(Array[String])
AdditionalProperties(Schema)
Definitions(Array[(String, Schema)])
Properties(Array[(String, Schema)])
PatternProperties(Array[(String, Schema)])
Dependencies(Array[(String, Dependency)])
PropertyNames(Schema)
Const(@json.Value)
Enum(Array[@json.Value])
Type(TypeConstraint)
Format(String)
ContentMediaType(String)
ContentEncoding(String)
If(Schema)
Then(Schema)
Else(Schema)
AllOf(Array[Schema])
AnyOf(Array[Schema])
OneOf(Array[Schema])
Not(Schema)
} derive(Debug)
///|
pub fn Keyword::name(self : Keyword) -> String {
match self {
Id(_) => "$id"
Dialect(_) => "$schema"
Ref(_) => "$ref"
Comment(_) => "$comment"
Title(_) => "title"
Description(_) => "description"
Default(_) => "default"
ReadOnly(_) => "readOnly"
WriteOnly(_) => "writeOnly"
Examples(_) => "examples"
MultipleOf(_) => "multipleOf"
Maximum(_) => "maximum"
ExclusiveMaximum(_) => "exclusiveMaximum"
Minimum(_) => "minimum"
ExclusiveMinimum(_) => "exclusiveMinimum"
MaxLength(_) => "maxLength"
MinLength(_) => "minLength"
Pattern(_) => "pattern"
AdditionalItems(_) => "additionalItems"
Items(_) => "items"
MaxItems(_) => "maxItems"
MinItems(_) => "minItems"
UniqueItems(_) => "uniqueItems"
Contains(_) => "contains"
MaxProperties(_) => "maxProperties"
MinProperties(_) => "minProperties"
Required(_) => "required"
AdditionalProperties(_) => "additionalProperties"
Definitions(_) => "definitions"
Properties(_) => "properties"
PatternProperties(_) => "patternProperties"
Dependencies(_) => "dependencies"
PropertyNames(_) => "propertyNames"
Const(_) => "const"
Enum(_) => "enum"
Type(_) => "type"
Format(_) => "format"
ContentMediaType(_) => "contentMediaType"
ContentEncoding(_) => "contentEncoding"
If(_) => "if"
Then(_) => "then"
Else(_) => "else"
AllOf(_) => "allOf"
AnyOf(_) => "anyOf"
OneOf(_) => "oneOf"
Not(_) => "not"
}
}
///|
let all_names : Array[String] = [
"$id", "$schema", "$ref", "$comment", "title", "description", "default", "readOnly",
"writeOnly", "examples", "multipleOf", "maximum", "exclusiveMaximum", "minimum",
"exclusiveMinimum", "maxLength", "minLength", "pattern", "additionalItems", "items",
"maxItems", "minItems", "uniqueItems", "contains", "maxProperties", "minProperties",
"required", "additionalProperties", "definitions", "properties", "patternProperties",
"dependencies", "propertyNames", "const", "enum", "type", "format", "contentMediaType",
"contentEncoding", "if", "then", "else", "allOf", "anyOf", "oneOf", "not",
]
///|
pub fn keyword_names() -> Array[String] {
all_names.copy()
}
///|
fn schema_map_json(
entries : Array[(String, Schema)],
) -> Result[@json.Value, @json.Diagnostic] {
@json.Value::object(entries.map(entry => (entry.0, entry.1.to_json())))
}
///|
pub fn Keyword::to_json(
self : Keyword,
) -> Result[@json.Value, @json.Diagnostic] {
Ok(
match self {
Id(ref_) | Dialect(ref_) => @json.Value::string(ref_.text())
Ref(ref_) => @json.Value::string(ref_.text())
Comment(text)
| Title(text)
| Description(text)
| Pattern(text)
| Format(text)
| ContentMediaType(text)
| ContentEncoding(text) => @json.Value::string(text)
Default(value) | Const(value) => value
ReadOnly(b) | WriteOnly(b) | UniqueItems(b) => @json.Value::boolean(b)
Examples(values) | Enum(values) => @json.Value::array(values)
MultipleOf(n) => @json.Value::number(n.number)
Maximum(n) | ExclusiveMaximum(n) | Minimum(n) | ExclusiveMinimum(n) =>
@json.Value::number(n)
MaxLength(n)
| MinLength(n)
| MaxItems(n)
| MinItems(n)
| MaxProperties(n)
| MinProperties(n) => @json.Value::number(n.number)
AdditionalItems(schema)
| Contains(schema)
| AdditionalProperties(schema)
| PropertyNames(schema)
| If(schema)
| Then(schema)
| Else(schema)
| Not(schema) => schema.to_json()
Items(SingleItems(schema)) => schema.to_json()
Items(TupleItems(schemas))
| AllOf(schemas)
| AnyOf(schemas)
| OneOf(schemas) =>
@json.Value::array(schemas.map(schema => schema.to_json()))
Required(names) =>
@json.Value::array(names.map(name => @json.Value::string(name)))
Definitions(entries) | Properties(entries) | PatternProperties(entries) =>
return schema_map_json(entries)
Dependencies(entries) =>
return @json.Value::object(
entries.map(entry => {
let value = match entry.1 {
SchemaDependency(schema) => schema.to_json()
PropertyDependencies(names) =>
@json.Value::array(names.map(name => @json.Value::string(name)))
}
(entry.0, value)
}),
)
Type(types) => types.to_json()
},
)
}
///|
pub struct SchemaObject {
value : @json.Value
} derive(Debug)
///|
pub struct ReferenceObject {
reference : @reference.Reference
siblings : @json.Value
} derive(Debug)
///|
pub fn ReferenceObject::reference(
self : ReferenceObject,
) -> @reference.Reference {
self.reference
}
///|
pub fn ReferenceObject::siblings(self : ReferenceObject) -> @json.Value {
self.siblings
}
///|
pub(all) enum SchemaKind {
BooleanSchema(Bool)
ObjectSchema(SchemaObject)
ReferenceSchema(ReferenceObject)
UnsupportedDialectSchema(@reference.UriReference, @json.Value)
} derive(Debug)
///|
fn draft07(text : String) -> Bool {
@domain.draft07_dialect(text)
}
///|
pub fn Schema::kind(self : Schema) -> SchemaKind {
match self.value.kind() {
@json.Boolean(b) => BooleanSchema(b)
@json.Object(fields) => {
if self.value.get("$schema") is Some(dialect) &&
!draft07(dialect.text().unwrap()) {
return UnsupportedDialectSchema(
@reference.UriReference::parse(
dialect.text().unwrap(),
limits=@json.Limits::unlimited(),
).unwrap(),
self.value,
)
}
if self.value.get("$ref") is Some(ref_) {
return ReferenceSchema({
reference: @reference.Reference::parse(
ref_.text().unwrap(),
limits=@json.Limits::unlimited(),
).unwrap(),
siblings: @json.Value::object(
fields.filter(entry => entry.0 != "$ref"),
).unwrap(),
})
}
ObjectSchema({ value: self.value, })
}
_ => abort("Schema constructor invariant")
}
}
///|
pub fn SchemaObject::keywords(self : SchemaObject) -> Array[Keyword] {
guard self.value.kind() is @json.Object(fields) else {
abort("Schema object invariant")
}
fields
.filter(entry => all_names.contains(entry.0))
.map(entry => {
decode_keyword(entry.0, entry.1, limits=@json.Limits::unlimited()).unwrap()
})
}
///|
pub fn SchemaObject::keyword(self : SchemaObject, name : String) -> Keyword? {
if !all_names.contains(name) {
return None
}
self.value
.get(name)
.map(value => {
decode_keyword(name, value, limits=@json.Limits::unlimited()).unwrap()
})
}
///|
pub fn SchemaObject::additional_fields(
self : SchemaObject,
) -> Array[(String, @json.Value)] {
guard self.value.kind() is @json.Object(fields) else {
abort("Schema object invariant")
}
fields.filter(entry => {
!all_names.contains(entry.0) && !entry.0.has_prefix("x-")
})
}
///|
pub fn SchemaObject::extensions(self : SchemaObject) -> @domain.Extensions {
guard self.value.kind() is @json.Object(fields) else {
abort("Schema object invariant")
}
@domain.Extensions::from_json(
@json.Value::object(fields.filter(entry => entry.0.has_prefix("x-"))).unwrap(),
).unwrap()
}
///|
pub fn Schema::from_keywords(
keywords : Array[Keyword],
additional_fields~ : Array[(String, @json.Value)] = [],
extensions~ : @domain.Extensions = @domain.Extensions::empty(),
limits~ : @json.Limits = @json.Limits::new(),
) -> Result[Schema, @json.Diagnostic] {
let fields = []
for keyword in keywords {
match keyword.to_json() {
Ok(value) => fields.push((keyword.name(), value))
Err(d) => {
let path = [keyword.name()]
for part in d.path() {
path.push(part)
}
return Err(@json.Diagnostic::new(d.code(), d.message(), path~))
}
}
}
for (name, value) in additional_fields {
if all_names.contains(name) || name.has_prefix("x-") {
return Err(
@json.Diagnostic::new(
@json.InvalidSchema,
"Known keywords and extensions require their typed inputs",
path=[name],
),
)
}
fields.push((name, value))
}
for (name, value) in extensions.entries() {
fields.push((name.to_string(), value))
}
match @json.Value::object(fields) {
Ok(value) => Schema::from_json(value, limits~)
Err(d) => Err(d)
}
}
///|
pub extend SimpleType with Eq::{equal, not_equal}
///|
pub extend SimpleType with @debug.Debug::{to_repr}
///|
pub extend TypeConstraint with @debug.Debug::{to_repr}
///|
pub extend NonNegativeInteger with Eq::{equal, not_equal}
///|
pub extend NonNegativeInteger with @debug.Debug::{to_repr}
///|
pub extend PositiveNumber with Eq::{equal, not_equal}
///|
pub extend PositiveNumber with @debug.Debug::{to_repr}
///|
pub extend Items with @debug.Debug::{to_repr}
///|
pub extend Dependency with @debug.Debug::{to_repr}
///|
pub extend Keyword with @debug.Debug::{to_repr}
///|
pub extend SchemaObject with @debug.Debug::{to_repr}
///|
pub extend ReferenceObject with @debug.Debug::{to_repr}
///|
pub extend SchemaKind with @debug.Debug::{to_repr}