diff --git a/SCHEMA-SPEC.md b/SCHEMA-SPEC.md index 907c1af..d453edd 100644 --- a/SCHEMA-SPEC.md +++ b/SCHEMA-SPEC.md @@ -6,7 +6,7 @@ constrain the allowed semantics of a KDL document. This can be used for many purposes: documentation for users, automated verification, or even automated generation of bindings! -This document describes KDL Schema version `1.0.0`. It was released on September 11, 2021. +This document describes KDL Schema version `2.0.0`. It is unreleased. ## The Formal Schema @@ -39,6 +39,14 @@ None. * `tag-names` (optional): [Validations](#validation-nodes) to apply to the _names_ of tags of child nodes. * `other-tags-allowed` (optional): Whether to allow node tags other than the ones explicitly listed here. Defaults to `#false`. +#### Example + +```kdl +document { + +} +``` + ### `info` node The `info` node describes the schema itself. diff --git a/schema/cargo.kdl b/schema/cargo.kdl new file mode 100644 index 0000000..b72817e --- /dev/null +++ b/schema/cargo.kdl @@ -0,0 +1,168 @@ +@kdl:schema "https://github.com/kdl-org/kdl/blob/main/schema/kdl-schema.kdl" + +metadata { + // TODO: update this link when we're ready to release something. + link "https://github.com/kdl-org/kdl/blob/main/schema/cargo.kdl" rel=self + title "Cargo Schema" lang=en + description "KDL-based translation of the Cargo.toml schema." lang=en + author "Kat Marchán" { + link "https://github.com/zkat" rel=self + } + link "https://github.com/kdl-org/kdl" rel=documentation + link "https://doc.rust-lang.org/cargo/reference/manifest.html" rel=documentation + license "Creative Commons Attribution-ShareAlike 4.0 International License" spdx=CC-BY-SA-4.0 { + link "https://creativecommons.org/licenses/by-sa/4.0/" lang=en + } +} + +children { + node package title="Describes a package" { + children { + node name title="The name of the package" { + required + arg { + type string + pattern #"^[a-zA-Z0-0\-_]+$"# + } + } + node version title="The version of the package." { + arg { + type string + // From https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string + pattern #"^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$"# + } + } + node authors title="The authors of the package." { + repeatable + args { + distinct + type string + } + children { + node - { + repeatable + arg title="Name" { + type string + } + prop email title="Email address" { + type string + format email + } + prop about title="Brief note about author (role, etc)" { + type string + } + } + } + } + node edition title="The Rust edition." { + arg { + type string + enum "2015" "2018" "2021" "2024" + } + } + node rust-version title="The minimal supported Rust version." { + arg { + type string + } + } + node description title="A description of the package." { + arg { + type string + } + } + node documentation title="URL of the package documentation." { + arg { + type string + format url + } + } + node readme title="Path to the package’s README file." { + arg { + type string #boolean + } + } + node homepage title="URL of the package homepage." { + arg { + type string + format url + } + } + node repository title="URL of the package source repository." { + arg { + type string + format url + } + } + node license title="The package license." { + arg { + type string + } + } + node license-file title="Path to the text of the license." { + arg { + type string + } + } + node keywords title="Keywords for the package." { + args { + type string + // No pattern because keyword restrictions are only on + // crates.io + } + } + node categories title="Categories of the package." { + args { + type string + // No pattern because category restrictions are only on + // crates.io + } + } + node workspace title="Path to the workspace for the package." { + arg { + type string + } + } + node build title="Path to the package build script." { + arg { + type string boolean + } + } + node links title="Name of the native library the package links with." { + arg { + type string + } + } + node exclude title="Files to exclude when publishing." { + args { + type string + } + } + node include title="Files to include when publishing." { + args { + type string + } + } + node publish title="Can be used to prevent publishing the package." { + // TODO: This is a good example of where we might need smarter + // comstraints ("either a single boolean, or 1+ strings") + args { + type string boolean + } + ] + node metadata title="Extra settings for external tools." { + repeat; args; props; children + } + node default-run title="The default binary to run by cargo run." { + arg { + type string + } + } + node no-autolib title="Disables library auto discovery." + node no-autobins title="Disables binary auto discovery." + node no-autoexamples title="Disables example auto discovery." + node no-autotests title="Disables test auto discovery." + node no-autobenches title="Disables bench auto discovery." + node resolver title="Sets the dependency resolver to use." + } + } +} diff --git a/schema/kdl-schema.kdl b/schema/kdl-schema.kdl new file mode 100644 index 0000000..5df6152 --- /dev/null +++ b/schema/kdl-schema.kdl @@ -0,0 +1,425 @@ +// TODO: +// * examples +// * dependentRequired +// * dependentSchema +// * if-then-else +// * composition (anyOf, allOf, oneOf, not, etc: https://json-schema.org/understanding-json-schema/reference/combining) +// * followed-by (I think this might be useful: declaring relationships between children) +// * requires (a more general-purpose version of followed-by that uses kpath+children?) + +@kdl:schema "https://github.com/kdl-org/kdl/blob/main/examples/kdl-schema.kdl" + +metadata { + // TODO: update this link when we're ready to release something. + link "https://github.com/kdl-org/kdl/blob/main/examples/kdl-schema.kdl" rel=self + title "KDL Schema" lang=en + description "KDL Schema KDL schema in KDL" lang=en + author "Kat Marchán" { + link "https://github.com/zkat" rel=self + } + contributor "Lars Willighagen" { + link "https://github.com/larsgw" rel=self + } + link "https://github.com/kdl-org/kdl" rel=documentation + license "Creative Commons Attribution-ShareAlike 4.0 International License" spdx=CC-BY-SA-4.0 { + link "https://creativecommons.org/licenses/by-sa/4.0/" lang=en + } + published "2021-08-31" + modified "2021-09-01" +} +children { + node metadata title="Schema metadata" description="Contains metadata about the schema itself." { + children { + node title title="Schema title" description="The title of the schema or the format it describes" { + arg description="The title text" { + type string + } + prop lang id=metadata-lang title="Title language" description="The language of the text" { + type string + } + } + node description description="A description of the schema or the format it describes" { + arg description="The description text" { + type string + } + prop ref=metadata-lang + } + node author description="Author of the schema" { + arg id=metadata-person-name description="Person name" { + type string + } + prop orcid id=metadata-orcid description="The ORCID of the person" { + type string + pattern #"\d{4}-\d{4}-\d{4}-\d{4}"# + } + children { + node ref=metadata-link + } + } + node contributor description="Contributor to the schema" { + arg ref=metadata-person-name + prop ref=metadata-orcid + children { + node ref=metadata-link + } + } + node link id=metadata-link description="Links to itself, and to sources describing it" { + arg description="A URL that the link points to" { + type string + format url irl + } + prop rel description="The relation between the current entity and the URL" { + type string + enum self documentation + } + prop ref=metadata-lang + } + node license description="The license(s) that the schema is licensed under" { + arg description="Name of the used license" { + type string + } + prop spdx description="An SPDX license identifier" { + type string + // TODO: validation? + } + children { + node ref=metadata-link + } + } + node published description="When the schema was published" { + arg description="Publication date" { + type string + format date + } + prop time id=metadata-time description="A time to accompany the date" { + type string + format time + } + } + node modified description="When the schema was last modified" { + arg description="Modification date" { + type string + format date + } + prop ref=metadata-time + } + node version description="The version number of this version of the schema" { + arg description="Semver version number" { + type string + pattern #"^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$"# + } + } + } + } + node definitions title="Inert validation definitions" description="An optional set of definitions that may be referenced elsewhere in the schema. They will be inert (that is, not directly apply to the document) unless referenced by another node." + node children id=children-node title="Node children" description="Validations and definitions used for all nodes in this scope. Children are only allowed on nodes (or the toplevel document) if at least one `children` node is present in their definitions." { + children { + node names ref=string-validations description="Validations to apply to all node names in this scope." { + repeatable + } + node allow-others title="Allow other children" description=""" + If present, allows child nodes in this scope other than the ones \ + explicitly listed. + + Note: If this is used, you will need to add `node @kdl:schema` \ + to your schema definition if you want your data format to be \ + able to reference KDL schemas directly. + """ + node node title="A KDL node" description="A KDL node belonging either to the top-level document or to another `node`'s children." { + repeatable + arg description="The name of the node. If a node name is not supplied, the node rules apply to _all_ nodes belonging to the parent, including those with names present in the schema." { + type string + optional + } + prop title title="Short descriptor for node." { + type string + } + prop description description="A longer description of this item's purpose and behavior." { + type string + } + prop id description="A globally-unique ID for this node." { + type string + } + prop ref description="A reference to a previously-defined unique ID." { + type string + } + prop refpath description="A reference to a previously-defined unique ID, using a KPath query instead of a plain ID." { + type string + format kpath + } + children { + node ref description="A reference to a previously-defined unique ID." { + repeatable + args { + min 1 + type string + } + } + node refpath description="A reference to a previously-defined unique ID, using a KPath query instead of a plain ID." { + repeatable + args { + min 1 + type string + format kpath + } + } + node title title="Short description" title="A short descriptor for node." { + arg { + type string + } + } + node description title="Long description" description="A longer description of this item's purpose and behavior." { + arg { + type string + } + } + node repeatable // TODO + node deprecated title="Mark node as deprecated" description="When present, this node will be considered a deprecated part of the API. You may optionally supply a message, and/or a reference to a node that should be used instead." { + prop message { + type string + } + prop by title="Deprecated by this node `id`" { + type string + } + prop by-kpath title="Depreceated by this node KPath" { + type string + format kpath + } + } + node annotation ref=string-validations description="Validations to apply specifically to arbitrary node type annotation names" { + repeatable + } + node prop ref=value-validations title="Node property" description="A node property key/value pair. Properties declared with `prop` are always optional, unless marked as `required` or included in `props:required`" { + repeatable + arg description="The property key." { + type string + } + children description="Property-specific validations." { + node required description="Whether this property is required in the node." + } + } + node props ref=value-validations description="Validations to apply to all properties of this node." { + children { + node names ref=string-validations description="Validations to apply to all property names." + node min description="minimum number of properties that match this validation." { + arg { + gte 0 + type number + } + } + node max description="maximum number of properties that match this validation." { + arg { + gte 0 + type number + } + } + node required title="List of required props" description=""" + List of property names that must be present on \ + the node. Individual `prop` nodes may specify \ + additional required properties beyond those \ + specified in this list. Properties listed here \ + which already have a `prop` node marked as \ + `required` are allowed, but are redundant. + """ { + args { + min 1 + type string + } + } + node allow-others title="Allow other properties" description="If present, allows other properties that don't match this validator." + } + } + node arg ref=value-validations title="Single node argument" description=""" + Specifies validations for a single node argument. + + Each nth instance of this node will specify validations \ + for the corresponding nth instance of the arg. Every \ + specified `arg` is required, in the given order, unless \ + marked as `optional`. + """ { + repeatable + children { + node optional title="Argument is not required" description=""" + Whether this argument is optional. Specified \ + `arg`s are required by default. + + Note: `optional` only applies to *presence*: \ + an existing argument in an optional `arg` \ + \"slot\" that fails validation will fail \ + normally, even though it is optional. As such, \ + `optional` is only really useful if it is on the \ + last `arg`, or is only followed by optional \ + `arg`s. + """ + } + } + // TODO: add a feature that will let us specify that `args` + // MUST be after any existing `arg` nodes in the current + // scope. i.e. you can't do `node x { args; arg }` + node args ref=value-validation description="Specifies validations for all arguments. Can be used in conjunction with `arg`." { + children description="Additional validations when args can be variable-length" { + // TODO: opportunity for mutual requirements here + node min title="Minimum argument count" description="Minimum number of arguments that must be present in a node. Must be less than or equal to `max`, if the latter is present." { + arg { + gte 0 + type number + } + } + node max title="Maximum argument count" description="Maximum number of arguments that may be present in a node. Must be greater than or equal to `max`, if the latter is present." { + arg { + gte 0 + type number + } + } + node distinct title="All arguments must be distinct" description="If present, all of this node's arguments need to be distinct values." + node allow-others title="Allow other arguments" description="If present, allows other arguments that don't match this validator." + } + } + prop deprecated title="Mark node as deprecated" description="When present, this node will be considered a deprecated part of the API. You may optionally supply a message, and/or a reference to a node that should be used instead." { + prop message { + type string + } + prop by title="Deprecated by this node `id`" { + type string + } + prop by-kpath title="Depreceated by this node KPath" { + type string + format kpath + } + } + node children ref=children-node + } + } + } + } +} +definitions { + node id=string-validations ref=shared-validations description="String-related validations" { + children { + node pattern description="EcmaScript-compatible Regex pattern or patterns to test string values against." { + args { + min 1 + type string + } + } + node min-length description="Minimum length of value, if it's a string." { + max 1 + arg { + gte 0 + type integer + } + } + node max-length description="Maximum length of value, if it's a string." { + max 1 + arg { + gte 0 + type integer + } + } + node format title="Intended data format." { + args { + min 1 + type string + // https://json-schema.org/understanding-json-schema/reference/string.html#format + // TODO: Make sure this is up to date with the types listed in the spec. + enum date-time date time duration decimal currency country-2 country-3 \ + country-subdivision email idn-email hostname idn-hostname ipv4 ipv6 url \ + url-reference irl irl-reference url-template regex uuid kpath i8 i16 \ + i32 i64 i128 u8 u16 u32 u64 u128 isize usize f32 f64 decimal64 decimal128 + } + } + node media-type title="MIME type" description="MIME type of string value. May be applied to 'deserialized' data if value format is base64/base85 or some other stringly binary encoding." { + args { + min 1 + type string + } + } + } + } + node id=number-validations ref=shared-validations descriptions="Number-specific validations" { + children { + node div description="Only used for numeric values. Constrains them to be multiples of the given number(s)" { + args { + min 1 + type number + } + } + node gt description="Only used for numeric values. Constrains them to be greater than the given number" { + max 1 + arg { + type number + } + } + node gte description="Only used for numeric values. Constrains them to be greater than or equal to the given number" { + max 1 + arg { + type number + } + } + node lt description="Only used for numeric values. Constrains them to be less than the given number" { + max 1 + arg { + type number + } + } + node lte description="Only used for numeric values. Constrains them to be less than or equal to the given number" { + max 1 + arg { + type number + } + } + } + } + node id=shared-validations description="Validations shared across all types." { + children { + node type description="The type for this value. Multiple arguments signify a sum type." { + args { + min 1 + type string + enum string boolean number integer #null + distinct + } + } + node const description="Exact value that this value must match. Equivalent to a single-value `enum` validation." { + max 1 + arg description="Constant value." + } + node enum description="An enumeration of possible values" { + args description="Enumeration choices" { + min 1 + } + children description="Enumeration choices" { + node - description="Enumeration choice" { + prop description description="Documentation for this enumerated item." + arg description="Enum value" + } + } + } + } + } + node id=value-validations description="General value validations." { + ref string-validations number-validations + children { + node annotations ref=string-validations description="Validations for the type annotations that can be applied to this value." + node default title="Default value" { + arg + } + } + } + node id=node-validations description="Validations that can be applied to nodes themselves." { + children { + node min description="minimum number of instances of this node in its parent's children." { + max 1 + arg { + type number + } + } + node max description="maximum number of instances of this node in its parent's children." { + max 1 + arg { + type number + } + } + } + } +}