webfinger_rs/types/
rel.rs1use std::borrow::Borrow;
2use std::fmt;
3use std::str::FromStr;
4
5use serde::de::{self, Visitor};
6use serde::{Deserialize, Deserializer, Serialize, Serializer};
7
8use crate::Error;
9use crate::types::jrd_uri::is_absolute_uri;
10
11#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
54pub struct Rel(String);
55
56impl Rel {
57 pub fn new<S: AsRef<str>>(rel: S) -> Self {
67 Self::try_new(rel).expect("invalid WebFinger link relation type")
68 }
69
70 pub fn try_new<S: AsRef<str>>(rel: S) -> Result<Self, Error> {
76 let rel = rel.as_ref();
77 if is_absolute_uri(rel) || is_registered_relation_type(rel) {
78 Ok(Self(rel.to_string()))
79 } else {
80 Err(Error::InvalidRel(rel.to_string()))
81 }
82 }
83}
84
85impl fmt::Display for Rel {
86 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
87 f.write_str(&self.0)
88 }
89}
90
91impl FromStr for Rel {
92 type Err = Error;
93
94 fn from_str(rel: &str) -> Result<Self, Self::Err> {
95 Self::try_new(rel)
96 }
97}
98
99impl TryFrom<&str> for Rel {
100 type Error = Error;
101
102 fn try_from(rel: &str) -> Result<Self, Self::Error> {
103 Self::try_new(rel)
104 }
105}
106
107impl TryFrom<String> for Rel {
108 type Error = Error;
109
110 fn try_from(rel: String) -> Result<Self, Self::Error> {
111 Self::try_new(rel)
112 }
113}
114
115impl From<Rel> for String {
116 fn from(rel: Rel) -> Self {
117 rel.0
118 }
119}
120
121impl AsRef<str> for Rel {
122 fn as_ref(&self) -> &str {
123 &self.0
124 }
125}
126
127impl Borrow<str> for Rel {
128 fn borrow(&self) -> &str {
129 &self.0
130 }
131}
132
133impl Serialize for Rel {
134 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
135 where
136 S: Serializer,
137 {
138 serializer.serialize_str(&self.0)
139 }
140}
141
142impl<'de> Deserialize<'de> for Rel {
143 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
144 where
145 D: Deserializer<'de>,
146 {
147 deserializer.deserialize_str(RelVisitor)
148 }
149}
150
151struct RelVisitor;
152
153impl Visitor<'_> for RelVisitor {
154 type Value = Rel;
155
156 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
157 formatter.write_str("a URI relation type or registered relation type")
158 }
159
160 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
161 where
162 E: de::Error,
163 {
164 Rel::try_new(value).map_err(E::custom)
165 }
166}
167
168fn is_registered_relation_type(value: &str) -> bool {
169 let mut chars = value.chars();
170 let Some(first) = chars.next() else {
171 return false;
172 };
173 first.is_ascii_lowercase()
174 && chars.all(|ch| ch.is_ascii_lowercase() || ch.is_ascii_digit() || matches!(ch, '.' | '-'))
175}
176
177#[cfg(test)]
178mod tests {
179 use std::collections::BTreeSet;
180 use std::fmt::{Debug, Display};
181 use std::hash::Hash;
182
183 use serde::{Deserialize, Serialize};
184
185 use super::*;
186
187 fn assert_common_traits<T>()
188 where
189 T: Clone
190 + Debug
191 + Display
192 + Eq
193 + Ord
194 + Hash
195 + Send
196 + Sync
197 + Serialize
198 + for<'de> Deserialize<'de>,
199 {
200 }
201
202 #[test]
203 fn implements_applicable_common_traits() {
204 assert_common_traits::<Rel>();
205 }
206
207 #[test]
208 fn accepts_uri_relation_types() {
209 let rel = Rel::try_new("http://webfinger.net/rel/profile-page").unwrap();
210
211 assert_eq!(rel.as_ref(), "http://webfinger.net/rel/profile-page");
212 }
213
214 #[test]
215 fn accepts_registered_relation_types() {
216 let rel = Rel::try_new("author").unwrap();
217
218 assert_eq!(rel.as_ref(), "author");
219 }
220
221 #[test]
222 fn try_from_parses_valid_relation_types() {
223 let rel = Rel::try_from("author").unwrap();
224
225 assert_eq!(rel.as_ref(), "author");
226 }
227
228 #[test]
229 fn converts_back_into_owned_string() {
230 let rel = Rel::new("author");
231
232 assert_eq!(String::from(rel), "author");
233 }
234
235 #[test]
236 fn supports_borrowed_string_set_lookup() {
237 let mut values = BTreeSet::new();
238 values.insert(Rel::new("author"));
239
240 assert!(values.contains("author"));
241 }
242
243 #[test]
244 fn orders_by_relation_string() {
245 let first = Rel::new("author");
246 let second = Rel::new("http://webfinger.net/rel/profile-page");
247
248 assert!(first < second);
249 }
250
251 #[test]
252 fn rejects_empty_relation_types() {
253 let error = Rel::try_new("").expect_err("empty relation type");
254
255 assert!(error.to_string().contains("invalid relation type"));
256 }
257
258 #[test]
259 fn rejects_multiple_relation_types_in_one_value() {
260 let error = Rel::try_new("author avatar").expect_err("multiple relation types");
261
262 assert!(error.to_string().contains("invalid relation type"));
263 }
264
265 #[test]
266 fn rejects_relative_uri_relation_types() {
267 let error = Rel::try_new("/rel/profile-page").expect_err("relative URI relation type");
268
269 assert!(error.to_string().contains("invalid relation type"));
270 }
271
272 #[test]
273 fn rejects_uri_relation_types_with_malformed_percent_escapes() {
274 let error = Rel::try_new("http://example.com/a%GG").expect_err("malformed percent escape");
275
276 assert!(error.to_string().contains("invalid relation type"));
277 }
278
279 #[test]
280 fn deserialization_rejects_invalid_relation_types() {
281 let error = serde_json::from_str::<Rel>(r#""""#).expect_err("empty relation type");
282
283 assert!(error.to_string().contains("invalid relation type"));
284 }
285}