1use thiserror::Error;
2
3#[derive(Clone, Debug, Eq, PartialEq)]
5pub struct Ref(String);
6
7impl Ref {
8 pub fn new(s: String) -> Result<Self, ParseRefError> {
16 if Self::is_valid_ref(&s) {
17 Ok(Ref(s))
18 } else {
19 Err(ParseRefError::from_string(s))
20 }
21 }
22 pub fn from_encoded_json_string(
33 json_string: &str,
34 ) -> Result<Self, ParseRefError> {
35 if let Some(raw_id) = json_string.split(' ').next() {
36 Self::new(raw_id.replacen("r:", "@", 1))
37 } else {
38 Err(ParseRefError::from_str(json_string))
39 }
40 }
41
42 pub fn to_encoded_json_string(&self) -> String {
46 self.0.replacen("@", "r:", 1)
47 }
48
49 pub fn into_string(self) -> String {
51 self.0
52 }
53
54 pub fn to_axon_code(&self) -> &str {
56 self.as_ref()
57 }
58
59 pub(crate) fn is_valid_ref(s: &str) -> bool {
61 if s.is_empty() {
62 false
63 } else {
64 let chars = s.chars().enumerate();
65
66 let mut is_valid_ref = true;
67 let mut last_index_seen = 0;
68
69 for (index, c) in chars {
70 if index == 0 {
71 if c != '@' {
72 is_valid_ref = false;
73 break;
74 }
75 } else {
76 last_index_seen = index;
77 if !(Self::is_valid_ref_char(c)) {
78 is_valid_ref = false;
79 break;
80 }
81 };
82 }
83
84 if last_index_seen == 0 {
85 false
86 } else {
87 is_valid_ref
88 }
89 }
90 }
91
92 fn is_valid_ref_char(c: char) -> bool {
93 c.is_alphanumeric() || Self::is_valid_symbol_char(c)
94 }
95
96 fn is_valid_symbol_char(c: char) -> bool {
97 c == '_' || c == ':' || c == '-' || c == '.' || c == '~'
98 }
99}
100
101impl std::str::FromStr for Ref {
102 type Err = ParseRefError;
103
104 fn from_str(s: &str) -> Result<Self, Self::Err> {
105 if Self::is_valid_ref(s) {
106 Ok(Ref(s.to_owned()))
107 } else {
108 let unparsable_ref = s.to_owned();
109 Err(ParseRefError { unparsable_ref })
110 }
111 }
112}
113
114impl std::fmt::Display for Ref {
115 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
116 write!(f, "{}", self.to_axon_code())
117 }
118}
119
120impl std::convert::AsRef<str> for Ref {
121 fn as_ref(&self) -> &str {
122 &self.0
123 }
124}
125
126#[derive(Clone, Debug, Eq, Error, PartialEq)]
128#[error("Could not parse a Ref from the string {unparsable_ref}")]
129pub struct ParseRefError {
130 unparsable_ref: String,
131}
132
133impl ParseRefError {
134 pub(crate) fn from_str(s: &str) -> Self {
135 let unparsable_ref = s.to_owned();
136 ParseRefError { unparsable_ref }
137 }
138
139 pub(crate) fn from_string(s: String) -> Self {
140 ParseRefError { unparsable_ref: s }
141 }
142}
143
144#[cfg(test)]
145mod test {
146 use super::Ref;
147 #[test]
148 fn parse_ref() {
149 assert_eq!(Ref::is_valid_ref("@p:some_proj:r:1e85e02f-0459cf96"), true);
150 assert_eq!(Ref::is_valid_ref("@H.NAE_05.NAE~2d05~2fFC~2d2~2eFD~2d21-VAV~2d10~2d17~2eVAV~2d10~2d17-ZNT~2dSP~2eTrend1"), true);
151 assert_eq!(Ref::is_valid_ref("@"), false);
152 assert_eq!(Ref::is_valid_ref(""), false);
153 assert_eq!(Ref::is_valid_ref("@o/o"), false);
154 assert_eq!(Ref::is_valid_ref("@o,o"), false);
155 assert_eq!(Ref::is_valid_ref("@o|o"), false);
156 }
157}