// Copyright 2026 Leo Cheng
// SPDX-License-Identifier: Apache-2.0
///|
/// Which version of JSON Schema a document is written against.
///
/// The six released versions that are in use. `draft-03` is not here: it
/// predates the `$` keywords and nothing has been written against it since 2010.
/// The next release arrives as another value of this enum and a keyword table,
/// not as a second validator.
pub(all) enum Draft {
/// draft-04 (2013). What OpenAPI 3.0 profiles, and what a great deal of Java
/// and Python tooling still emits.
Draft4
/// draft-06 (2017). `$id` replaces `id`, `exclusiveMinimum` becomes a number.
Draft6
/// draft-07 (2018). The most widely deployed version.
Draft7
/// 2019-09. Vocabularies, `$defs`, `$recursiveRef`, and the annotation-aware
/// `unevaluatedProperties`.
Draft2019
/// 2020-12. `$dynamicRef`, `prefixItems`, and what OpenAPI 3.1 is.
Draft2020
/// v1 (2026). `format` asserts by default and `propertyDependencies` joins the
/// applicators; the rest of the language is 2020-12's.
V1
} derive(Eq, Debug)
///|
pub extend Draft with Eq::{equal, not_equal}
///|
pub extend Draft with Debug::{to_repr}
///|
/// A version prints as the name the specification gives it, which is what a
/// message about a document should say.
pub impl Show for Draft with fn output(self, logger) {
logger.write_string(
match self {
Draft4 => "draft-04"
Draft6 => "draft-06"
Draft7 => "draft-07"
Draft2019 => "2019-09"
Draft2020 => "2020-12"
V1 => "v1"
},
)
}
///|
pub extend Draft with Show::{output, to_string}
///|
/// The version assumed when a document does not name one.
///
/// 2020-12: the current release, and the one OpenAPI 3.1 is defined against. A
/// document that says `$schema` is read at the version it says, whatever this is
/// set to — the document is more specific than the setting.
pub let draft : Draft = Draft2020
///|
/// The `$schema` URI that names this version.
pub fn Draft::uri(self : Draft) -> String {
match self {
Draft4 => "http://json-schema.org/draft-04/schema#"
Draft6 => "http://json-schema.org/draft-06/schema#"
Draft7 => "http://json-schema.org/draft-07/schema#"
Draft2019 => "https://json-schema.org/draft/2019-09/schema"
Draft2020 => "https://json-schema.org/draft/2020-12/schema"
V1 => "https://json-schema.org/v1"
}
}
///|
/// The version a `$schema` URI names.
///
/// It answers `None` for a URI it does not know rather than falling back to a
/// version of its own choosing: a document written against something else is not
/// a document to guess at. Both the `http` and `https` spellings are accepted,
/// and a trailing `#` is optional, because both are found in the wild.
pub fn Draft::of(uri : StringView) -> Draft? {
let trimmed = if uri.has_suffix("#") {
uri[0:uri.length() - 1]
} else {
uri[:]
}
let bare = if trimmed.has_prefix("https://") {
trimmed[8:]
} else if trimmed.has_prefix("http://") {
trimmed[7:]
} else {
trimmed
}
match bare.to_owned() {
"json-schema.org/draft-04/schema" => Some(Draft4)
"json-schema.org/draft-06/schema" => Some(Draft6)
"json-schema.org/draft-07/schema" => Some(Draft7)
"json-schema.org/draft/2019-09/schema" => Some(Draft2019)
"json-schema.org/draft/2020-12/schema" => Some(Draft2020)
// The release IRI is `…/v1/`, and the version alone names the latest
// release within it, which is what a document written against either means.
"json-schema.org/v1" | "json-schema.org/v1/2026" => Some(V1)
_ => None
}
}
///|
/// How much a validation says when it fails.
///
/// The four formats the specification itself defines (2020-12 §12.4), named as
/// it names them.
pub(all) enum Output {
/// Whether it validated, and nothing else. The cheapest, and what a guard on
/// a request wants.
Flag
/// A flat list of the errors, each with the location in the instance and in
/// the schema.
Basic
/// The errors as a tree, with the schema's own structure preserved.
Detailed
/// Every annotation as well as every error, which is what a tool that explains
/// a schema to a person needs.
Verbose
} derive(Eq, Debug)
///|
pub extend Output with Eq::{equal, not_equal}
///|
pub extend Output with Debug::{to_repr}
///|
/// An output format prints as the specification names it (2020-12 §12.4).
pub impl Show for Output with fn output(self, logger) {
logger.write_string(
match self {
Flag => "flag"
Basic => "basic"
Detailed => "detailed"
Verbose => "verbose"
},
)
}
///|
pub extend Output with Show::{output, to_string}
///|
/// Whether this version has vocabularies: 2019-09 and everything after it.
///
/// One question asked five times in the validator — does a `$ref` replace its
/// siblings, does `unevaluated*` apply, is `dependencies` split — and asked here
/// once so that a release added later cannot be forgotten in one of the five.
fn Draft::vocabularies(self : Draft) -> Bool {
match self {
Draft4 | Draft6 | Draft7 => false
Draft2019 | Draft2020 | V1 => true
}
}
///|
/// Whether this version asserts `format` without being asked.
///
/// Before v1 the keyword is an annotation unless the schema's own dialect
/// declares the assertion vocabulary; v1 made assertion the requirement, so a v1
/// document asserts it unless its reader says otherwise.
fn Draft::asserts_format(self : Draft) -> Bool {
match self {
Draft4 | Draft6 | Draft7 | Draft2019 | Draft2020 => false
V1 => true
}
}
///|
/// Whether this version spells the tuple form of `items` as `prefixItems`, which
/// 2020-12 introduced and v1 kept.
fn Draft::prefix_items(self : Draft) -> Bool {
match self {
Draft4 | Draft6 | Draft7 | Draft2019 => false
Draft2020 | V1 => true
}
}
///|
/// The output format a validation produces unless asked for another.
///
/// `Flag`, because the common case is a guard that wants a yes or a no and
/// collecting errors nobody reads is work nobody asked for. A caller that wants
/// to tell someone what went wrong asks for `Basic`.
pub let output : Output = Flag