From 26a20cf4f3a71ccdd7c6abb06cffd4c12d08395e Mon Sep 17 00:00:00 2001 From: Melody Horn Date: Sat, 26 Mar 2022 19:57:57 -0600 Subject: parse schemas with knuffel --- tests/kdl-schema.kdl | 373 ++++++++++++++++++++++++++++++++++++++++++++ tests/schema-via-knuffel.rs | 63 ++++++++ 2 files changed, 436 insertions(+) create mode 100644 tests/kdl-schema.kdl create mode 100644 tests/schema-via-knuffel.rs (limited to 'tests') diff --git a/tests/kdl-schema.kdl b/tests/kdl-schema.kdl new file mode 100644 index 0000000..4315199 --- /dev/null +++ b/tests/kdl-schema.kdl @@ -0,0 +1,373 @@ +document { + info { + 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/zkat/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" + } + node "document" { + min 1 + max 1 + children id="node-children" { + node "node-names" id="node-names-node" description="Validations to apply specifically to arbitrary node names" { + children ref=r#"[id="validations"]"# + } + node "other-nodes-allowed" id="other-nodes-allowed-node" description="Whether to allow child nodes other than the ones explicitly listed. Defaults to 'false'." { + max 1 + value { + min 1 + max 1 + type "boolean" + } + } + node "tag-names" description="Validations to apply specifically to arbitrary type tag names" { + children ref=r#"[id="validations"]"# + } + node "other-tags-allowed" description="Whether to allow child node tags other than the ones explicitly listed. Defaults to 'false'." { + max 1 + value { + min 1 + max 1 + type "boolean" + } + } + node "info" description="A child node that describes the schema itself." { + children { + node "title" description="The title of the schema or the format it describes" { + value description="The title text" { + type "string" + min 1 + max 1 + } + prop "lang" id="info-lang" description="The language of the text" { + type "string" + } + } + node "description" description="A description of the schema or the format it describes" { + value description="The description text" { + type "string" + min 1 + max 1 + } + prop ref=r#"[id="info-lang"]"# + } + node "author" description="Author of the schema" { + value id="info-person-name" description="Person name" { + type "string" + min 1 + max 1 + } + prop "orcid" id="info-orcid" description="The ORCID of the person" { + type "string" + pattern r"\d{4}-\d{4}-\d{4}-\d{4}" + } + children { + node ref=r#"[id="info-link"]"# + } + } + node "contributor" description="Contributor to the schema" { + value ref=r#"[id="info-person-name"]"# + prop ref=r#"[id="info-orcid"]"# + } + node "link" id="info-link" description="Links to itself, and to sources describing it" { + value description="A URL that the link points to" { + type "string" + format "url" "irl" + min 1 + max 1 + } + prop "rel" description="The relation between the current entity and the URL" { + type "string" + enum "self" "documentation" + } + prop ref=r#"[id="info-lang"]"# + } + node "license" description="The license(s) that the schema is licensed under" { + value description="Name of the used license" { + type "string" + min 1 + max 1 + } + prop "spdx" description="An SPDX license identifier" { + type "string" + } + children { + node ref=r#"[id="info-link"]"# + } + } + node "published" description="When the schema was published" { + value description="Publication date" { + type "string" + format "date" + min 1 + max 1 + } + prop "time" id="info-time" description="A time to accompany the date" { + type "string" + format "time" + } + } + node "modified" description="When the schema was last modified" { + value description="Modification date" { + type "string" + format "date" + min 1 + max 1 + } + prop ref=r#"[id="info-time"]"# + } + node "version" description="The version number of this version of the schema" { + value description="Semver version number" { + type "string" + pattern r"^(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-]+)*))?$" + min 1 + max 1 + } + } + } + } + node "tag" id="tag-node" description="A tag belonging to a child node of `document` or another node." { + value description="The name of the tag. If a tag name is not supplied, the node rules apply to _all_ nodes belonging to the parent." { + type "string" + max 1 + } + prop "description" description="A description of this node's purpose." { + type "string" + } + prop "id" description="A globally-unique ID for this node." { + type "string" + } + prop "ref" description="A globally unique reference to another node." { + type "string" + format "kdl-query" + } + children { + node ref=r#"[id="node-names-node"]"# + node ref=r#"[id="other-nodes-allowed-node"]"# + node ref=r#"[id="node-node"]"# + } + } + node "node" id="node-node" description="A child node belonging either to `document` or to another `node`. Nodes may be anonymous." { + value description="The name of the node. If a node name is not supplied, the node rules apply to _all_ nodes belonging to the parent." { + type "string" + max 1 + } + prop "description" description="A description of this node's purpose." { + type "string" + } + prop "id" description="A globally-unique ID for this node." { + type "string" + } + prop "ref" description="A globally unique reference to another node." { + type "string" + format "kdl-query" + } + children { + node "prop-names" description="Validations to apply specifically to arbitrary property names" { + children ref=r#"[id="validations"]"# + } + node "other-props-allowed" description="Whether to allow properties other than the ones explicitly listed. Defaults to 'false'." { + max 1 + value { + min 1 + max 1 + type "boolean" + } + } + node "min" description="minimum number of instances of this node in its parent's children." { + max 1 + value { + min 1 + max 1 + type "number" + } + } + node "max" description="maximum number of instances of this node in its parent's children." { + max 1 + value { + min 1 + max 1 + type "number" + } + } + node ref=r#"[id="value-tag-node"]"# + node "prop" id="prop-node" description="A node property key/value pair." { + value description="The property key." { + type "string" + } + prop "id" description="A globally-unique ID of this property." { + type "string" + } + prop "ref" description="A globally unique reference to another property node." { + type "string" + format "kdl-query" + } + prop "description" description="A description of this property's purpose." { + type "string" + } + children description="Property-specific validations." { + node "required" description="Whether this property is required if its parent is present." { + max 1 + value { + min 1 + max 1 + type "boolean" + } + } + } + children id="validations" description="General value validations." { + node "tag" id="value-tag-node" description="The tags associated with this value" { + max 1 + children ref=r#"[id="validations"]"# + } + node "type" description="The type for this prop's value." { + max 1 + value { + min 1 + type "string" + } + } + node "enum" description="An enumeration of possible values" { + max 1 + value description="Enumeration choices" { + min 1 + } + } + node "pattern" description="PCRE (Regex) pattern or patterns to test prop values against." { + value { + min 1 + type "string" + } + } + node "min-length" description="Minimum length of prop value, if it's a string." { + max 1 + value { + min 1 + type "number" + } + } + node "max-length" description="Maximum length of prop value, if it's a string." { + max 1 + value { + min 1 + type "number" + } + } + node "format" description="Intended data format." { + max 1 + value { + min 1 + type "string" + // https://json-schema.org/understanding-json-schema/reference/string.html#format + 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" "kdl-query" "i8" "i16" "i32" "i64" "u8" "u16" "u32" "u64" "isize" "usize" "f32" "f64" "decimal64" "decimal128" + } + } + node "%" description="Only used for numeric values. Constrains them to be multiples of the given number(s)" { + max 1 + value { + min 1 + type "number" + } + } + node ">" description="Only used for numeric values. Constrains them to be greater than the given number(s)" { + max 1 + value { + min 1 + max 1 + type "number" + } + } + node ">=" description="Only used for numeric values. Constrains them to be greater than or equal to the given number(s)" { + max 1 + value { + min 1 + max 1 + type "number" + } + } + node "<" description="Only used for numeric values. Constrains them to be less than the given number(s)" { + max 1 + value { + min 1 + max 1 + type "number" + } + } + node "<=" description="Only used for numeric values. Constrains them to be less than or equal to the given number(s)" { + max 1 + value { + min 1 + max 1 + type "number" + } + } + } + } + node "value" id="value-node" description="one or more direct node values" { + prop "id" description="A globally-unique ID of this value." { + type "string" + } + prop "ref" description="A globally unique reference to another value node." { + type "string" + format "kdl-query" + } + prop "description" description="A description of this property's purpose." { + type "string" + } + children ref=r#"[id="validations"]"# + children description="Node value-specific validations" { + node "min" description="minimum number of values for this node." { + max 1 + value { + min 1 + max 1 + type "number" + } + } + node "max" description="maximum number of values for this node." { + max 1 + value { + min 1 + max 1 + type "number" + } + } + } + } + node "children" id="children-node" { + prop "id" description="A globally-unique ID of this children node." { + type "string" + } + prop "ref" description="A globally unique reference to another children node." { + type "string" + format "kdl-query" + } + prop "description" description="A description of this these children's purpose." { + type "string" + } + children ref=r#"[id="node-children"]"# + } + } + } + node "definitions" description="Definitions to reference in parts of the top-level nodes" { + children { + node ref=r#"[id="node-node"]"# + node ref=r#"[id="value-node"]"# + node ref=r#"[id="prop-node"]"# + node ref=r#"[id="children-node"]"# + node ref=r#"[id="tag-node"]"# + } + } + } + } +} diff --git a/tests/schema-via-knuffel.rs b/tests/schema-via-knuffel.rs new file mode 100644 index 0000000..b0d1a1c --- /dev/null +++ b/tests/schema-via-knuffel.rs @@ -0,0 +1,63 @@ +#![cfg(feature = "knuffel")] + +use std::fmt::Debug; + +use kdl_schema::*; + +trait DeepAssertEq +where + Self: PartialEq + Debug, +{ + fn deep_assert_eq(left: &Self, right: &Self) { + assert_eq!(left, right); + } +} + +impl DeepAssertEq for Schema { + fn deep_assert_eq(left: &Self, right: &Self) { + DeepAssertEq::deep_assert_eq(&left.document, &right.document) + } +} + +impl DeepAssertEq for Document { + fn deep_assert_eq(left: &Self, right: &Self) { + DeepAssertEq::deep_assert_eq(&left.info, &right.info); + DeepAssertEq::deep_assert_eq(&left.nodes, &right.nodes); + } +} + +impl DeepAssertEq for Vec { + fn deep_assert_eq(left: &Self, right: &Self) { + for (left, right) in left.iter().zip(right.iter()) { + DeepAssertEq::deep_assert_eq(left, right); + } + assert_eq!(left, right); + } +} + +impl DeepAssertEq for Info {} + +impl DeepAssertEq for Node { + fn deep_assert_eq(left: &Self, right: &Self) { + DeepAssertEq::deep_assert_eq(&left.props, &right.props); + DeepAssertEq::deep_assert_eq(&left.values, &right.values); + DeepAssertEq::deep_assert_eq(&left.children, &right.children); + assert_eq!(left, right); + } +} + +impl DeepAssertEq for node::Prop {} +impl DeepAssertEq for node::Value {} +impl DeepAssertEq for node::Children { + fn deep_assert_eq(left: &Self, right: &Self) { + DeepAssertEq::deep_assert_eq(&left.nodes, &right.nodes); + assert_eq!(left, right); + } +} + +#[test] +fn schema_loads() -> miette::Result<()> { + let schema: Schema = knuffel::parse("kdl-schema.kdl", include_str!("kdl-schema.kdl"))?; + DeepAssertEq::deep_assert_eq(&schema, &*SCHEMA_SCHEMA); + Ok(()) +} -- cgit v1.2.3