dmntk_common/
href.rs

1//! # URI reference
2//!
3//! This [HRef] struct utilizes an **href** attribute whose value must be a valid URI reference
4//! [RFC 3986](https://datatracker.ietf.org/doc/html/rfc3986), where the path components
5//! may be absolute or relative, the reference has no query component,
6//! and the fragment consists of the value of the **id** of the referenced DMN element.
7
8use self::errors::*;
9use crate::DmntkError;
10use uriparse::{RelativeReference, URIReference, URI};
11
12/// URI reference used for utilizing `href` attribute.
13#[derive(Debug, Clone)]
14pub struct HRef {
15  /// Namespace built from URI's path components.
16  namespace: Option<String>,
17  /// DMN element's identifier built from URI's fragment.
18  id: String,
19}
20
21impl HRef {
22  /// Returns the optional namespace.
23  pub fn namespace(&self) -> Option<&String> {
24    self.namespace.as_ref()
25  }
26
27  /// Returns the identifier.
28  pub fn id(&self) -> &str {
29    &self.id
30  }
31}
32
33impl TryFrom<&str> for HRef {
34  type Error = DmntkError;
35  /// Converts [HRef] from string.
36  fn try_from(value: &str) -> Result<Self, Self::Error> {
37    if let Ok(reference) = RelativeReference::try_from(value) {
38      if reference.has_query() {
39        return Err(err_invalid_reference(value));
40      }
41      let id = reference.fragment().ok_or_else(|| err_invalid_reference_no_fragment(value))?.to_string();
42      let path = reference.path().to_string().trim().trim_end_matches('/').to_string();
43      let namespace = if path.is_empty() { None } else { Some(path) };
44      return Ok(Self { namespace, id });
45    }
46    if let Ok(uri_reference) = URIReference::try_from(value) {
47      if let Ok(uri) = URI::try_from(uri_reference) {
48        if uri.has_query() {
49          return Err(err_invalid_reference(value));
50        }
51        let id = uri.fragment().ok_or_else(|| err_invalid_reference_no_fragment(value))?.to_string();
52        let path = uri.into_base_uri().to_string().trim().trim_end_matches('/').to_string();
53        let namespace = if path.is_empty() { None } else { Some(path) };
54        return Ok(Self { namespace, id });
55      }
56    }
57    Err(err_invalid_reference(value))
58  }
59}
60
61mod errors {
62  use crate::{DmntkError, ToErrorMessage};
63
64  /// Errors reported by [HRef](crate::href::HRef).
65  #[derive(ToErrorMessage)]
66  struct HRefError(String);
67
68  /// Creates an error indicating an invalid reference.
69  pub fn err_invalid_reference(s: &str) -> DmntkError {
70    HRefError(format!("invalid reference: '{s}'")).into()
71  }
72
73  /// Creates an error indicating the missing fragment.
74  pub fn err_invalid_reference_no_fragment(s: &str) -> DmntkError {
75    HRefError(format!("no fragment in reference: '{s}'")).into()
76  }
77}
78
79#[cfg(test)]
80mod tests {
81  use super::*;
82
83  #[test]
84  fn test_relative_references() {
85    assert_eq!(r#"Err(DmntkError("<HRefError> no fragment in reference: ''"))"#, format!("{:?}", HRef::try_from("")));
86    assert_eq!(
87      r#"Err(DmntkError("<HRefError> no fragment in reference: 'documents'"))"#,
88      format!("{:?}", HRef::try_from("documents"))
89    );
90    assert_eq!(
91      r#"Ok(HRef { namespace: Some("documents"), id: "_b51ac78b-fd76-42fc-a12d-aad7150c9278" })"#,
92      format!("{:?}", HRef::try_from("documents#_b51ac78b-fd76-42fc-a12d-aad7150c9278"))
93    );
94    assert_eq!(
95      r#"Ok(HRef { namespace: None, id: "_b51ac78b-fd76-42fc-a12d-aad7150c9278" })"#,
96      format!("{:?}", HRef::try_from("#_b51ac78b-fd76-42fc-a12d-aad7150c9278"))
97    );
98    assert_eq!(
99      r#"Err(DmntkError("<HRefError> invalid reference: 'documents?name=Introduction#_b51ac78b-fd76-42fc-a12d-aad7150c9278'"))"#,
100      format!("{:?}", HRef::try_from("documents?name=Introduction#_b51ac78b-fd76-42fc-a12d-aad7150c9278"))
101    );
102  }
103
104  #[test]
105  fn test_absolute_references() {
106    assert_eq!(r#"Err(DmntkError("<HRefError> no fragment in reference: ''"))"#, format!("{:?}", HRef::try_from("")));
107    assert_eq!(
108      r#"Err(DmntkError("<HRefError> invalid reference: '                                               '"))"#,
109      format!("{:?}", HRef::try_from("                                               "))
110    );
111    assert_eq!(
112      r#"Ok(HRef { namespace: Some("https://dmntk.io/documents"), id: "_b51ac78b-fd76-42fc-a12d-aad7150c9278" })"#,
113      format!("{:?}", HRef::try_from("https://dmntk.io/documents#_b51ac78b-fd76-42fc-a12d-aad7150c9278"))
114    );
115    assert_eq!(
116      r#"Err(DmntkError("<HRefError> no fragment in reference: 'https://dmntk.io/documents'"))"#,
117      format!("{:?}", HRef::try_from("https://dmntk.io/documents"))
118    );
119    assert_eq!(
120      r#"Err(DmntkError("<HRefError> invalid reference: 'https::\\/dmntk.io/documents#id'"))"#,
121      format!("{:?}", HRef::try_from("https::\\/dmntk.io/documents#id"))
122    );
123  }
124}