#![doc = include_str!("../README.md")] #![warn( elided_lifetimes_in_paths, explicit_outlives_requirements, missing_debug_implementations, missing_docs, noop_method_call, single_use_lifetimes, trivial_casts, trivial_numeric_casts, unreachable_pub, unsafe_code, unused_crate_dependencies, unused_qualifications )] #![warn(clippy::pedantic, clippy::cargo)] #[cfg(feature = "parse-knuffel")] use knuffel::{Decode, DecodeScalar}; mod schema_schema; pub use schema_schema::SCHEMA_SCHEMA; pub(crate) trait BuildFromRef { fn ref_to(query: impl Into) -> Self; } fn get_id_from_ref(r#ref: &str) -> Option<&str> { r#ref .strip_prefix(r#"[id=""#) .and_then(|r#ref| r#ref.strip_suffix(r#""]"#)) } /// the schema itself #[derive(Debug, PartialEq, Eq, Default)] #[cfg_attr(feature = "parse-knuffel", derive(Decode))] pub struct Schema { /// the document this schema defines /// /// this is redundant but it matches the schema document structure #[cfg_attr(feature = "parse-knuffel", knuffel(child))] pub document: Document, } impl Schema { /// find the node matching the given ref /// /// # Panics /// /// Panics if ref is not of the form `[id="foo"]`. #[must_use] pub fn resolve_node_ref(&self, r#ref: &str) -> Option<&Node> { let id = get_id_from_ref(r#ref).expect("invalid ref"); self.document .nodes .iter() .find_map(|node| node.find_node_by_id(id)) } /// find the prop matching the given ref /// /// # Panics /// /// Panics if ref is not of the form `[id="foo"]`. #[must_use] pub fn resolve_prop_ref(&self, r#ref: &str) -> Option<&Prop> { let id = get_id_from_ref(r#ref).expect("invalid ref"); self.document .nodes .iter() .find_map(|node| node.find_prop_by_id(id)) } /// find the value matching the given ref /// /// # Panics /// /// Panics if ref is not of the form `[id="foo"]`. #[must_use] pub fn resolve_value_ref(&self, r#ref: &str) -> Option<&Value> { let id = get_id_from_ref(r#ref).expect("invalid ref"); self.document .nodes .iter() .find_map(|node| node.find_value_by_id(id)) } /// find the children matching the given ref /// /// # Panics /// /// Panics if ref is not of the form `[id="foo"]`. #[must_use] pub fn resolve_children_ref(&self, r#ref: &str) -> Option<&Children> { let id = get_id_from_ref(r#ref).expect("invalid ref"); self.document .nodes .iter() .find_map(|node| node.find_children_by_id(id)) } } #[cfg(feature = "parse-knuffel")] impl Schema { /// parse a KDL schema definition /// /// # Errors /// /// returns an error if knuffel can't parse the document as a Schema pub fn parse(schema_kdl: &str) -> Result { knuffel::parse("", schema_kdl) } } /// the schema document #[derive(Debug, PartialEq, Eq, Default)] #[cfg_attr(feature = "parse-knuffel", derive(Decode))] pub struct Document { /// schema metadata #[cfg_attr(feature = "parse-knuffel", knuffel(child))] pub info: Info, /// top-level node definitions #[cfg_attr(feature = "parse-knuffel", knuffel(children(name = "node")))] pub nodes: Vec, } /// schema metadata #[derive(Debug, PartialEq, Eq, Default)] #[cfg_attr(feature = "parse-knuffel", derive(Decode))] pub struct Info { /// schema titles #[cfg_attr(feature = "parse-knuffel", knuffel(children(name = "title")))] pub title: Vec, /// schema descriptions #[cfg_attr(feature = "parse-knuffel", knuffel(children(name = "description")))] pub description: Vec, /// schema authors #[cfg_attr(feature = "parse-knuffel", knuffel(children(name = "author")))] pub authors: Vec, /// schema contributors #[cfg_attr(feature = "parse-knuffel", knuffel(children(name = "contributor")))] pub contributors: Vec, /// schema links #[cfg_attr(feature = "parse-knuffel", knuffel(children(name = "link")))] pub links: Vec, /// schema licenses #[cfg_attr(feature = "parse-knuffel", knuffel(children(name = "license")))] pub licenses: Vec, /// schema publication date #[cfg_attr(feature = "parse-knuffel", knuffel(child))] pub published: Option, /// schema modification date #[cfg_attr(feature = "parse-knuffel", knuffel(child))] pub modified: Option, } /// a text value with an optional language tag #[derive(Debug, PartialEq, Eq)] #[cfg_attr(feature = "parse-knuffel", derive(Decode))] pub struct TextValue { /// text itself #[cfg_attr(feature = "parse-knuffel", knuffel(argument))] pub text: String, /// BCP 47 language tag #[cfg_attr(feature = "parse-knuffel", knuffel(property))] pub lang: Option, } /// information about a schema author/contributor #[derive(Debug, PartialEq, Eq)] #[cfg_attr(feature = "parse-knuffel", derive(Decode))] pub struct Person { /// name #[cfg_attr(feature = "parse-knuffel", knuffel(argument))] pub name: String, /// [ORCID](https://orcid.org) #[cfg_attr(feature = "parse-knuffel", knuffel(property))] pub orcid: Option, /// relevant links #[cfg_attr(feature = "parse-knuffel", knuffel(children(name = "link")))] pub links: Vec, } /// link related to specification metadata, with optional relationship and language tag #[derive(Debug, PartialEq, Eq)] #[cfg_attr(feature = "parse-knuffel", derive(Decode))] pub struct Link { /// URI/IRI of link target #[cfg_attr(feature = "parse-knuffel", knuffel(argument))] pub iri: String, /// relationship of link to schema (`self`, `documentation`) #[cfg_attr(feature = "parse-knuffel", knuffel(property))] pub rel: Option, /// BCP 47 language tag #[cfg_attr(feature = "parse-knuffel", knuffel(property))] pub lang: Option, } /// schema license information #[derive(Debug, PartialEq, Eq)] #[cfg_attr(feature = "parse-knuffel", derive(Decode))] pub struct License { /// license name #[cfg_attr(feature = "parse-knuffel", knuffel(argument))] pub name: String, /// license [SPDX identifier](https://spdx.org/licenses/) #[cfg_attr(feature = "parse-knuffel", knuffel(property))] pub spdx: Option, /// links for license information #[cfg_attr(feature = "parse-knuffel", knuffel(children(name = "link")))] pub link: Vec, } /// date with optional time #[derive(Debug, PartialEq, Eq)] #[cfg_attr(feature = "parse-knuffel", derive(Decode))] pub struct Date { /// date #[cfg_attr(feature = "parse-knuffel", knuffel(argument))] pub date: String, /// time #[cfg_attr(feature = "parse-knuffel", knuffel(property))] pub time: Option, } /// schema for a node #[derive(Debug, PartialEq, Eq, Default)] #[cfg_attr(feature = "parse-knuffel", derive(Decode))] pub struct Node { /// name of the node (applies to all nodes at this level if `None`) #[cfg_attr(feature = "parse-knuffel", knuffel(argument))] pub name: Option, /// id of the node (can be used for refs) #[cfg_attr(feature = "parse-knuffel", knuffel(property))] pub id: Option, /// human-readable description of the node's purpose #[cfg_attr(feature = "parse-knuffel", knuffel(property))] pub description: Option, /// KDL query from which to load node information instead of specifying it inline (allows for recursion) #[cfg_attr(feature = "parse-knuffel", knuffel(property))] pub r#ref: Option, /// minimum number of occurrences of this node #[cfg_attr(feature = "parse-knuffel", knuffel(child, unwrap(argument)))] pub min: Option, /// maximum number of occurrences of this node #[cfg_attr(feature = "parse-knuffel", knuffel(child, unwrap(argument)))] pub max: Option, /// properties allowed on this node #[cfg_attr(feature = "parse-knuffel", knuffel(children(name = "prop")))] pub props: Vec, /// values allowed on this node #[cfg_attr(feature = "parse-knuffel", knuffel(children(name = "value")))] pub values: Vec, /// children allowed on this node #[cfg_attr(feature = "parse-knuffel", knuffel(children(name = "children")))] pub children: Vec, } impl Node { fn find_node_by_id(&self, id: &str) -> Option<&Node> { if self.id.as_deref() == Some(id) { Some(self) } else { self.children .iter() .find_map(|children| children.find_node_by_id(id)) } } fn find_prop_by_id(&self, id: &str) -> Option<&Prop> { self.props .iter() .find_map(|prop| prop.find_prop_by_id(id)) .or_else(|| { self.children .iter() .find_map(|children| children.find_prop_by_id(id)) }) } fn find_value_by_id(&self, id: &str) -> Option<&Value> { self.values .iter() .find_map(|value| value.find_value_by_id(id)) .or_else(|| { self.children .iter() .find_map(|children| children.find_value_by_id(id)) }) } fn find_children_by_id(&self, id: &str) -> Option<&Children> { self.children .iter() .find_map(|children| children.find_children_by_id(id)) } } impl BuildFromRef for Node { fn ref_to(query: impl Into) -> Self { Self { r#ref: Some(query.into()), ..Self::default() } } } /// schema for a property #[derive(Debug, PartialEq, Eq, Default)] #[cfg_attr(feature = "parse-knuffel", derive(Decode))] pub struct Prop { /// property key (applies to all properties in this node if `None`) #[cfg_attr(feature = "parse-knuffel", knuffel(argument))] pub key: Option, /// id of the property (can be used for refs) #[cfg_attr(feature = "parse-knuffel", knuffel(property))] pub id: Option, /// human-readable description of the property #[cfg_attr(feature = "parse-knuffel", knuffel(property))] pub description: Option, /// KDL query from which to load property information instead of specifying it inline (allows for recursion) #[cfg_attr(feature = "parse-knuffel", knuffel(property))] pub r#ref: Option, /// whether or not this property is required #[cfg_attr(feature = "parse-knuffel", knuffel(child))] pub required: bool, /// validations to apply to the property value #[cfg_attr(feature = "parse-knuffel", knuffel(children))] pub validations: Vec, } impl Prop { fn find_prop_by_id(&self, id: &str) -> Option<&Prop> { if self.id.as_deref() == Some(id) { Some(self) } else { None } } } impl BuildFromRef for Prop { fn ref_to(query: impl Into) -> Self { Self { r#ref: Some(query.into()), ..Self::default() } } } /// schema for a value #[derive(Debug, PartialEq, Eq, Default)] #[cfg_attr(feature = "parse-knuffel", derive(Decode))] pub struct Value { /// id of the value (can be used for refs) #[cfg_attr(feature = "parse-knuffel", knuffel(property))] pub id: Option, /// human readable description of the value #[cfg_attr(feature = "parse-knuffel", knuffel(property))] pub description: Option, /// KDL query from which to load value information instead of specifying it inline (allows for recursion) #[cfg_attr(feature = "parse-knuffel", knuffel(property))] pub r#ref: Option, /// minimum number of occurrences of this value #[cfg_attr(feature = "parse-knuffel", knuffel(child, unwrap(argument)))] pub min: Option, /// maximum number of occurrences of this value #[cfg_attr(feature = "parse-knuffel", knuffel(child, unwrap(argument)))] pub max: Option, /// validations to apply to this value #[cfg_attr(feature = "parse-knuffel", knuffel(children))] pub validations: Vec, } impl Value { fn find_value_by_id(&self, id: &str) -> Option<&Value> { if self.id.as_deref() == Some(id) { Some(self) } else { None } } } impl BuildFromRef for Value { fn ref_to(query: impl Into) -> Self { Self { r#ref: Some(query.into()), ..Self::default() } } } /// schema for a node's children #[derive(Debug, PartialEq, Eq, Default)] #[cfg_attr(feature = "parse-knuffel", derive(Decode))] pub struct Children { /// id for these children (can be used for refs) #[cfg_attr(feature = "parse-knuffel", knuffel(property))] pub id: Option, /// human readable description of these children #[cfg_attr(feature = "parse-knuffel", knuffel(property))] pub description: Option, /// KDL query from which to load children information instead of specifying it inline (allows for recursion) #[cfg_attr(feature = "parse-knuffel", knuffel(property))] pub r#ref: Option, /// nodes which can appear as children #[cfg_attr(feature = "parse-knuffel", knuffel(children(name = "node")))] pub nodes: Vec, } impl Children { fn find_node_by_id(&self, id: &str) -> Option<&Node> { self.nodes.iter().find_map(|node| node.find_node_by_id(id)) } fn find_prop_by_id(&self, id: &str) -> Option<&Prop> { self.nodes.iter().find_map(|node| node.find_prop_by_id(id)) } fn find_value_by_id(&self, id: &str) -> Option<&Value> { self.nodes.iter().find_map(|node| node.find_value_by_id(id)) } fn find_children_by_id(&self, id: &str) -> Option<&Children> { if self.id.as_deref() == Some(id) { Some(self) } else { self.nodes .iter() .find_map(|node| node.find_children_by_id(id)) } } } impl BuildFromRef for Children { fn ref_to(query: impl Into) -> Self { Self { r#ref: Some(query.into()), ..Self::default() } } } /// a validation to apply to some value or property value #[derive(Debug, PartialEq, Eq)] #[cfg_attr(feature = "parse-knuffel", derive(Decode))] pub enum Validation { /// ensure the value is of the given type Type(#[cfg_attr(feature = "parse-knuffel", knuffel(argument))] String), /// ensure the value is one of the given options Enum(#[cfg_attr(feature = "parse-knuffel", knuffel(arguments))] Vec), /// ensure the value matches the given regular expression Pattern(#[cfg_attr(feature = "parse-knuffel", knuffel(argument))] String), /// ensure the value is of the given format Format(#[cfg_attr(feature = "parse-knuffel", knuffel(arguments))] Vec), } /// a format to ensure a value has #[derive(Clone, Debug, PartialEq, Eq)] #[cfg_attr(feature = "parse-knuffel", derive(DecodeScalar))] pub enum Format { /// iso 8601 datetime string DateTime, /// iso 8601 date string Date, /// iso 8601 time string Time, /// iso 8601 duration string Duration, /// ieee 754-2008 decimal string Decimal, /// iso 4217 currency code string Currency, /// iso 3166-1 alpha-2 country code string Country2, /// iso 3166-1 alpha-3 country code string Country3, /// iso 3166-2 country subdivision code string CountrySubdivision, /// rfc 5302 email address string Email, /// rfc 6531 internationalized email address string IdnEmail, /// rfc 1132 internet hostname string Hostname, /// rfc 5890 internationalized internet hostname string IdnHostname, /// rfc 2673 ipv4 address string Ipv4, /// rfc 2373 ipv6 address string Ipv6, /// rfc 3986 uri string Url, /// rfc 3986 uri reference string UrlReference, /// rfc 3987 iri string Irl, /// rfc 3987 iri reference string IrlReference, /// rfc 6750 uri template string UrlTemplate, /// rfc 4122 uuid string Uuid, /// regular expression string Regex, /// base64 encoded string Base64, /// KDL query string KdlQuery, }