1use self::errors::*;
9use crate::DmntkError;
10use uriparse::{RelativeReference, URIReference, URI};
11
12#[derive(Debug, Clone)]
14pub struct HRef {
15 namespace: Option<String>,
17 id: String,
19}
20
21impl HRef {
22 pub fn namespace(&self) -> Option<&String> {
24 self.namespace.as_ref()
25 }
26
27 pub fn id(&self) -> &str {
29 &self.id
30 }
31}
32
33impl TryFrom<&str> for HRef {
34 type Error = DmntkError;
35 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 #[derive(ToErrorMessage)]
66 struct HRefError(String);
67
68 pub fn err_invalid_reference(s: &str) -> DmntkError {
70 HRefError(format!("invalid reference: '{s}'")).into()
71 }
72
73 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}