aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md2
-rw-r--r--src/lib.rs146
-rw-r--r--tests/ref-resolution.rs145
3 files changed, 292 insertions, 1 deletions
diff --git a/README.md b/README.md
index 02fcf1d..70d98ff 100644
--- a/README.md
+++ b/README.md
@@ -74,8 +74,8 @@ assert_eq!(kdl_schema::SCHEMA_SCHEMA.document.info.title[0].text, "KDL Schema");
## conditions blocking version 1.0.0
+- documentation at all
- good API for parsing from a file
-- good API for resolving refs (as long as the ref is a simple global query by ID because using anything other than that as a ref is a weird lifehack and not idiomatic)
- types actually match the schema (currently I'm omitting several things because the schema schema doesn't use them)
- ergonomic builder API to define a schema in Rust in a non-ugly way
- can generate KDL from schema object in Rust
diff --git a/src/lib.rs b/src/lib.rs
index c724687..6b61dea 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -11,6 +11,12 @@ pub trait BuildFromRef {
fn ref_to(query: impl Into<String>) -> 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#""]"#))
+}
+
#[derive(Debug, PartialEq, Eq, Default)]
#[cfg_attr(feature = "parse-knuffel", derive(Decode))]
pub struct Schema {
@@ -18,6 +24,48 @@ pub struct Schema {
pub document: Document,
}
+impl Schema {
+ /// Panics if ref is not of the form `[id="foo"]`.
+ 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()
+ .filter_map(|node| node.find_node_by_id(id))
+ .next()
+ }
+
+ /// Panics if ref is not of the form `[id="foo"]`.
+ 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()
+ .filter_map(|node| node.find_prop_by_id(id))
+ .next()
+ }
+
+ /// Panics if ref is not of the form `[id="foo"]`.
+ 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()
+ .filter_map(|node| node.find_value_by_id(id))
+ .next()
+ }
+
+ /// Panics if ref is not of the form `[id="foo"]`.
+ 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()
+ .filter_map(|node| node.find_children_by_id(id))
+ .next()
+ }
+}
+
#[cfg(feature = "parse-knuffel")]
impl Schema {
pub fn parse(
@@ -131,6 +179,50 @@ pub struct Node {
pub children: Vec<Children>,
}
+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()
+ .filter_map(|children| children.find_node_by_id(id))
+ .next()
+ }
+ }
+
+ fn find_prop_by_id(&self, id: &str) -> Option<&Prop> {
+ self.props
+ .iter()
+ .filter_map(|prop| prop.find_prop_by_id(id))
+ .chain(
+ self.children
+ .iter()
+ .filter_map(|children| children.find_prop_by_id(id)),
+ )
+ .next()
+ }
+
+ fn find_value_by_id(&self, id: &str) -> Option<&Value> {
+ self.values
+ .iter()
+ .filter_map(|value| value.find_value_by_id(id))
+ .chain(
+ self.children
+ .iter()
+ .filter_map(|children| children.find_value_by_id(id)),
+ )
+ .next()
+ }
+
+ fn find_children_by_id(&self, id: &str) -> Option<&Children> {
+ self.children
+ .iter()
+ .filter_map(|children| children.find_children_by_id(id))
+ .next()
+ }
+}
+
impl BuildFromRef for Node {
fn ref_to(query: impl Into<String>) -> Self {
Self {
@@ -157,6 +249,16 @@ pub struct Prop {
pub validations: Vec<Validation>,
}
+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<String>) -> Self {
Self {
@@ -183,6 +285,16 @@ pub struct Value {
pub validations: Vec<Validation>,
}
+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<String>) -> Self {
Self {
@@ -205,6 +317,40 @@ pub struct Children {
pub nodes: Vec<Node>,
}
+impl Children {
+ fn find_node_by_id(&self, id: &str) -> Option<&Node> {
+ self.nodes
+ .iter()
+ .filter_map(|node| node.find_node_by_id(id))
+ .next()
+ }
+
+ fn find_prop_by_id(&self, id: &str) -> Option<&Prop> {
+ self.nodes
+ .iter()
+ .filter_map(|node| node.find_prop_by_id(id))
+ .next()
+ }
+
+ fn find_value_by_id(&self, id: &str) -> Option<&Value> {
+ self.nodes
+ .iter()
+ .filter_map(|node| node.find_value_by_id(id))
+ .next()
+ }
+
+ fn find_children_by_id(&self, id: &str) -> Option<&Children> {
+ if self.id.as_deref() == Some(id) {
+ Some(self)
+ } else {
+ self.nodes
+ .iter()
+ .filter_map(|node| node.find_children_by_id(id))
+ .next()
+ }
+ }
+}
+
impl BuildFromRef for Children {
fn ref_to(query: impl Into<String>) -> Self {
Self {
diff --git a/tests/ref-resolution.rs b/tests/ref-resolution.rs
new file mode 100644
index 0000000..e6bb648
--- /dev/null
+++ b/tests/ref-resolution.rs
@@ -0,0 +1,145 @@
+use kdl_schema::{Node, SCHEMA_SCHEMA};
+
+#[test]
+fn node_ref_proper() {
+ assert_eq!(
+ SCHEMA_SCHEMA
+ .resolve_node_ref(r#"[id="tag-node"]"#)
+ .unwrap()
+ .name
+ .as_deref(),
+ Some("tag")
+ );
+}
+
+#[test]
+fn node_ref_missing() {
+ assert_eq!(
+ SCHEMA_SCHEMA.resolve_node_ref(r#"[id="not-in-there-lol"]"#),
+ None
+ );
+}
+
+#[test]
+#[should_panic]
+fn node_ref_malformed() {
+ let _ = SCHEMA_SCHEMA.resolve_node_ref(r#"[description="hi"]"#);
+}
+
+#[test]
+fn prop_ref_proper() {
+ assert_eq!(
+ SCHEMA_SCHEMA
+ .resolve_prop_ref(r#"[id="info-time"]"#)
+ .unwrap()
+ .key
+ .as_deref(),
+ Some("time")
+ );
+}
+
+#[test]
+fn prop_ref_missing() {
+ assert_eq!(
+ SCHEMA_SCHEMA.resolve_prop_ref(r#"[id="not-in-there-lol"]"#),
+ None
+ );
+}
+
+#[test]
+#[should_panic]
+fn prop_ref_malformed() {
+ let _ = SCHEMA_SCHEMA.resolve_prop_ref(r#"[description="hi"]"#);
+}
+
+#[test]
+fn value_ref_proper() {
+ assert_eq!(
+ SCHEMA_SCHEMA
+ .resolve_value_ref(r#"[id="info-person-name"]"#)
+ .unwrap()
+ .description
+ .as_deref(),
+ Some("Person name")
+ );
+}
+
+#[test]
+fn value_ref_missing() {
+ assert_eq!(
+ SCHEMA_SCHEMA.resolve_value_ref(r#"[id="not-in-there-lol"]"#),
+ None
+ );
+}
+
+#[test]
+#[should_panic]
+fn value_ref_malformed() {
+ let _ = SCHEMA_SCHEMA.resolve_value_ref(r#"[description="hi"]"#);
+}
+
+#[test]
+fn children_ref_proper() {
+ assert_eq!(
+ SCHEMA_SCHEMA
+ .resolve_children_ref(r#"[id="validations"]"#)
+ .unwrap()
+ .description
+ .as_deref(),
+ Some("General value validations.")
+ );
+}
+
+#[test]
+fn children_ref_missing() {
+ assert_eq!(
+ SCHEMA_SCHEMA.resolve_children_ref(r#"[id="not-in-there-lol"]"#),
+ None
+ );
+}
+
+#[test]
+#[should_panic]
+fn children_ref_malformed() {
+ let _ = SCHEMA_SCHEMA.resolve_children_ref(r#"[description="hi"]"#);
+}
+
+#[test]
+fn all_schema_schema_refs_resolve() {
+ fn all_refs_resolve(node: &Node) {
+ if let Some(r#ref) = &node.ref_ {
+ SCHEMA_SCHEMA
+ .resolve_node_ref(r#ref)
+ .unwrap_or_else(|| panic!("node ref {} not found", r#ref));
+ } else {
+ for prop in &node.props {
+ if let Some(r#ref) = &prop.ref_ {
+ SCHEMA_SCHEMA
+ .resolve_prop_ref(r#ref)
+ .unwrap_or_else(|| panic!("prop ref {} not found", r#ref));
+ }
+ }
+ for value in &node.values {
+ if let Some(r#ref) = &value.ref_ {
+ SCHEMA_SCHEMA
+ .resolve_value_ref(r#ref)
+ .unwrap_or_else(|| panic!("value ref {} not found", r#ref));
+ }
+ }
+ for children in &node.children {
+ if let Some(r#ref) = &children.ref_ {
+ SCHEMA_SCHEMA
+ .resolve_children_ref(r#ref)
+ .unwrap_or_else(|| panic!("children ref {} not found", r#ref));
+ } else {
+ for node in &children.nodes {
+ all_refs_resolve(node);
+ }
+ }
+ }
+ }
+ }
+ for node in &SCHEMA_SCHEMA.document.nodes {
+ all_refs_resolve(node);
+ }
+}