indy_utils/
qualifiable.rs

1use once_cell::sync::Lazy;
2
3use regex::Regex;
4
5use super::{invalid, Validatable, ValidationError};
6
7pub(crate) static REGEX: Lazy<Regex> =
8    Lazy::new(|| Regex::new("^([a-z0-9]+):([a-z0-9]+):(.*)$").unwrap());
9
10/// Combine a prefix, method, and value into a qualified identifier
11pub fn combine(prefix: &str, method: Option<&str>, entity: &str) -> String {
12    match method {
13        Some(method) => format!("{}:{}:{}", prefix, method, entity),
14        _ => entity.to_owned(),
15    }
16}
17
18/// Split a qualifiable identifier into its method and value components
19pub fn split<'a>(prefix: &str, val: &'a str) -> (Option<&'a str>, &'a str) {
20    match REGEX.captures(&val) {
21        None => (None, val),
22        Some(caps) => {
23            if caps.get(1).map(|m| m.as_str()) == Some(prefix) {
24                (
25                    Some(caps.get(2).unwrap().as_str()),
26                    caps.get(3).unwrap().as_str(),
27                )
28            } else {
29                (None, val)
30            }
31        }
32    }
33}
34
35/// Check if an identifier is qualified by a prefix and method
36pub fn is_fully_qualified(entity: &str) -> bool {
37    REGEX.captures(entity).is_some()
38}
39
40/// An identifier which can be qualified with a prefix and method
41pub trait Qualifiable: From<String> + std::ops::Deref<Target = str> + Validatable {
42    fn prefix() -> &'static str;
43
44    fn combine(method: Option<&str>, entity: &str) -> Self {
45        Self::from(combine(Self::prefix(), method, entity))
46    }
47
48    fn split<'a>(&'a self) -> (Option<&'a str>, &'a str) {
49        split(Self::prefix(), self.deref())
50    }
51
52    fn get_method<'a>(&'a self) -> Option<&'a str> {
53        let (method, _rest) = self.split();
54        method
55    }
56
57    fn default_method(&self, method: Option<&str>) -> Self {
58        let (prev_method, rest) = self.split();
59        match prev_method {
60            Some(_) => Self::from(self.to_string()),
61            None => Self::combine(method, rest),
62        }
63    }
64
65    fn replace_method(&self, method: Option<&str>) -> Self {
66        let (_method, rest) = self.split();
67        Self::combine(method, rest)
68    }
69
70    fn remove_method(&self, method: &str) -> Self {
71        let (prev_method, rest) = self.split();
72        if prev_method == Some(method) {
73            Self::combine(None, rest)
74        } else {
75            Self::from(self.to_string())
76        }
77    }
78
79    fn from_str(entity: &str) -> Result<Self, ValidationError> {
80        let result = Self::from(entity.to_owned());
81        result.validate()?;
82        Ok(result)
83    }
84
85    fn is_fully_qualified(&self) -> bool {
86        self.get_method().is_some()
87    }
88
89    fn to_qualified(&self, method: &str) -> Result<Self, ValidationError> {
90        match self.split() {
91            (None, rest) => Ok(Self::combine(Some(method), rest)),
92            (Some(prev_method), rest) if prev_method == method => {
93                Ok(Self::combine(Some(method), rest))
94            }
95            _ => Err(invalid!(
96                "Identifier is already qualified with another method",
97            )),
98        }
99    }
100
101    fn to_unqualified(&self) -> Self {
102        let (_, rest) = self.split();
103        Self::from(rest.to_owned())
104    }
105}
106
107/// Derive a new `Qualifiable` string type
108#[macro_export]
109macro_rules! qualifiable_type {
110    ($newtype:ident, $doc:expr) => {
111        $crate::serde_derive_impl! {
112            #[doc=$doc]
113            #[derive(Debug, Clone, PartialEq, Eq, Hash)]
114            pub struct $newtype(pub String);
115        }
116
117        impl From<String> for $newtype {
118            fn from(val: String) -> Self {
119                Self(val)
120            }
121        }
122
123        impl std::ops::Deref for $newtype {
124            type Target = str;
125            fn deref(&self) -> &str {
126                &self.0
127            }
128        }
129
130        impl std::fmt::Display for $newtype {
131            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
132                f.write_str(self.0.as_str())
133            }
134        }
135    };
136    ($newtype:ident) => {
137        qualifiable_type!($newtype, "");
138    };
139}