webfinger_rs/types/response.rs
1use std::collections::HashMap;
2use std::fmt::{self, Debug};
3
4use serde::{Deserialize, Serialize};
5use serde_with::skip_serializing_none;
6
7use crate::Rel;
8
9/// A WebFinger response.
10///
11/// This represents the response portion of a WebFinger query that is returned by a WebFinger
12/// server.
13///
14/// See [RFC 7033 Section 4.4](https://www.rfc-editor.org/rfc/rfc7033.html#section-4.4) for more
15/// information.
16///
17/// # Examples
18///
19/// ```rust
20/// use webfinger_rs::{Link, WebFingerResponse};
21///
22/// let avatar = Link::builder("http://webfinger.net/rel/avatar")
23/// .href("https://example.com/avatar.png")
24/// .build();
25/// let profile = Link::builder("http://webfinger.net/rel/profile-page")
26/// .href("https://example.com/profile/carol")
27/// .build();
28/// let response = WebFingerResponse::builder("acct:carol@example.com")
29/// .alias("https://example.com/profile/carol")
30/// .property("https://example.com/ns/role", "developer")
31/// .link(avatar)
32/// .link(profile)
33/// .build();
34/// ```
35///
36/// `Response` can be used as a response in Axum handlers as it implements
37/// [`axum::response::IntoResponse`].
38///
39/// ```rust
40/// use axum::response::IntoResponse;
41/// use webfinger_rs::{Link, WebFingerRequest, WebFingerResponse};
42///
43/// async fn handler(request: WebFingerRequest) -> WebFingerResponse {
44/// // ... handle the request ...
45/// WebFingerResponse::builder("acct:carol@example.com")
46/// .alias("https://example.com/profile/carol")
47/// .property("https://example.com/ns/role", "developer")
48/// .link(
49/// Link::builder("http://webfinger.net/rel/avatar")
50/// .href("https://example.com/avatar.png"),
51/// )
52/// .build()
53/// }
54/// ```
55#[skip_serializing_none]
56#[derive(Serialize, Deserialize)]
57pub struct Response {
58 /// The subject of the response.
59 ///
60 /// This is the URI of the resource that the response is about.
61 ///
62 /// Defined in [RFC 7033 Section
63 /// 4.4.1](https://www.rfc-editor.org/rfc/rfc7033.html#section-4.4.1)
64 pub subject: String,
65
66 /// The aliases of the response.
67 ///
68 /// Defined in [RFC 7033 Section
69 /// 4.4.2](https://www.rfc-editor.org/rfc/rfc7033.html#section-4.4.2)
70 pub aliases: Option<Vec<String>>,
71
72 /// The properties of the response.
73 ///
74 /// Defined in [RFC 7033 Section
75 /// 4.4.3](https://www.rfc-editor.org/rfc/rfc7033.html#section-4.4.3)
76 pub properties: Option<HashMap<String, String>>,
77
78 /// The links of the response.
79 ///
80 /// Defined in [RFC 7033 Section
81 /// 4.4.4](https://www.rfc-editor.org/rfc/rfc7033.html#section-4.4.4)
82 pub links: Vec<Link>,
83}
84
85impl Response {
86 /// Create a new response with the given subject.
87 pub fn new<S: Into<String>>(subject: S) -> Self {
88 Self {
89 subject: subject.into(),
90 aliases: None,
91 properties: None,
92 links: Vec::new(),
93 }
94 }
95
96 /// Create a new [`WebFingerBuilder`] with the given subject.
97 ///
98 /// # Examples
99 ///
100 /// ```rust
101 /// use webfinger_rs::{Link, WebFingerResponse};
102 ///
103 /// let avatar =
104 /// Link::builder("http://webfinger.net/rel/avatar").href("https://example.com/avatar.png");
105 /// let response = WebFingerResponse::builder("acct:carol@example.com")
106 /// .alias("https://example.com/profile/carol")
107 /// .property("https://example.com/ns/role", "developer")
108 /// .link(avatar)
109 /// .build();
110 /// ```
111 pub fn builder<S: Into<String>>(subject: S) -> Builder {
112 Builder::new(subject.into())
113 }
114}
115
116impl fmt::Display for Response {
117 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
118 write!(f, "{}", serde_json::to_string_pretty(self).unwrap())
119 }
120}
121
122/// A builder for a WebFinger response.
123///
124/// This is used to construct a [`Response`] with the desired fields.
125pub struct Builder {
126 response: Response,
127}
128
129impl Builder {
130 /// Create a new response builder with the given subject.
131 pub fn new<S: Into<String>>(subject: S) -> Self {
132 Self {
133 response: Response::new(subject.into()),
134 }
135 }
136
137 /// Add an alias to the response.
138 ///
139 /// Defined in [RFC 7033 Section
140 /// 4.4.2](https://www.rfc-editor.org/rfc/rfc7033.html#section-4.4.2)
141 pub fn alias<S: Into<String>>(mut self, alias: S) -> Self {
142 self.response
143 .aliases
144 .get_or_insert_with(Vec::new)
145 .push(alias.into());
146 self
147 }
148
149 /// Add a property to the response.
150 ///
151 /// Defined in [RFC 7033 Section
152 /// 4.4.3](https://www.rfc-editor.org/rfc/rfc7033.html#section-4.4.3)
153 pub fn property<K: Into<String>, V: Into<String>>(mut self, key: K, value: V) -> Self {
154 self.response
155 .properties
156 .get_or_insert_with(HashMap::new)
157 .insert(key.into(), value.into());
158 self
159 }
160
161 /// Add a link to the response.
162 ///
163 /// If the link is constructed with a builder, it is not necessary to call the `build` method on
164 /// the link as the builder implements `From<LinkBuilder> for Link`.
165 ///
166 /// Defined in [RFC 7033 Section
167 /// 4.4.4](https://www.rfc-editor.org/rfc/rfc7033.html#section-4.4.4)
168 pub fn link<L: Into<Link>>(mut self, link: L) -> Self {
169 self.response.links.push(link.into());
170 self
171 }
172
173 /// Set the links of the response.
174 ///
175 /// Defined in [RFC 7033 Section
176 /// 4.4.4](https://www.rfc-editor.org/rfc/rfc7033.html#section-4.4.4)
177 pub fn links(mut self, links: Vec<Link>) -> Self {
178 self.response.links = links;
179 self
180 }
181
182 /// Build the response.
183 pub fn build(self) -> Response {
184 self.response
185 }
186}
187
188/// Custom debug implementation to avoid printing `None` fields
189impl Debug for Response {
190 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
191 let mut debug = f.debug_struct("Response");
192 let mut debug = debug.field("subject", &self.subject);
193 if let Some(aliases) = &self.aliases {
194 debug = debug.field("aliases", &aliases);
195 }
196 if let Some(properties) = &self.properties {
197 debug = debug.field("properties", &properties);
198 }
199 debug.field("links", &self.links).finish()
200 }
201}
202
203/// A link in the WebFinger response.
204///
205/// Defined in [RFC 7033 Section 4.4](https://www.rfc-editor.org/rfc/rfc7033.html#section-4.4.4)
206#[skip_serializing_none]
207#[derive(Serialize, Deserialize)]
208pub struct Link {
209 /// The relation type of the link.
210 ///
211 /// Defined in [RFC 7033 Section
212 /// 4.4.4.1](https://www.rfc-editor.org/rfc/rfc7033.html#section-4.4.4.1)
213 pub rel: Rel,
214
215 /// The media type of the link.
216 ///
217 /// Defined in [RFC 7033 Section
218 /// 4.4.4.2](https://www.rfc-editor.org/rfc/rfc7033.html#section-4.4.4.2)
219 pub r#type: Option<String>,
220
221 /// The target URI of the link.
222 ///
223 /// Defined in [RFC 7033 Section
224 /// 4.4.4.3](https://www.rfc-editor.org/rfc/rfc7033.html#section-4.4.4.3)
225 pub href: Option<String>,
226
227 /// The titles of the link.
228 ///
229 /// Defined in [RFC 7033 Section
230 /// 4.4.4.4](https://www.rfc-editor.org/rfc/rfc7033.html#section-4.4.4.4)
231 pub titles: Option<Vec<Title>>,
232
233 /// The properties of the link.
234 ///
235 /// Defined in [RFC 7033 Section
236 /// 4.4.4.5](https://www.rfc-editor.org/rfc/rfc7033.html#section-4.4.4.5)
237 pub properties: Option<HashMap<String, Option<String>>>,
238}
239
240impl Link {
241 /// Create a new link with the given relation type.
242 pub fn new(rel: Rel) -> Self {
243 Self {
244 rel,
245 r#type: None,
246 href: None,
247 titles: None,
248 properties: None,
249 }
250 }
251
252 /// Create a new [`LinkBuilder`] with the given relation type.
253 pub fn builder<R: Into<Rel>>(rel: R) -> LinkBuilder {
254 LinkBuilder::new(rel)
255 }
256}
257
258/// A builder for a WebFinger link.
259///
260/// This is used to construct a [`Link`] with the desired fields.
261pub struct LinkBuilder {
262 link: Link,
263}
264
265impl LinkBuilder {
266 /// Create a new link builder with the given relation type.
267 pub fn new<R: Into<Rel>>(rel: R) -> Self {
268 Self {
269 link: Link::new(rel.into()),
270 }
271 }
272
273 /// Set the media type of the link.
274 ///
275 /// Defined in [RFC 7033 Section
276 /// 4.4.4.2](https://www.rfc-editor.org/rfc/rfc7033.html#section-4.4.4.2)
277 pub fn r#type<S: Into<String>>(mut self, r#type: S) -> Self {
278 self.link.r#type = Some(r#type.into());
279 self
280 }
281
282 /// Set the target URI of the link.
283 ///
284 /// Defined in [RFC 7033 Section
285 /// 4.4.4.3](https://www.rfc-editor.org/rfc/rfc7033.html#section-4.4.4.3)
286 pub fn href<S: Into<String>>(mut self, href: S) -> Self {
287 self.link.href = Some(href.into());
288 self
289 }
290
291 /// Add a single title for the the link.
292 ///
293 /// Defined in [RFC 7033 Section
294 /// 4.4.4.4](https://www.rfc-editor.org/rfc/rfc7033.html#section-4.4.4.4)
295 pub fn title<L: Into<String>, V: Into<String>>(mut self, language: L, value: V) -> Self {
296 let title = Title::new(language, value);
297 self.link.titles.get_or_insert_with(Vec::new).push(title);
298 self
299 }
300
301 /// Set the titles of the link.
302 ///
303 /// Defined in [RFC 7033 Section
304 /// 4.4.4.4](https://www.rfc-editor.org/rfc/rfc7033.html#section-4.4.4.4)
305 pub fn titles(mut self, titles: Vec<Title>) -> Self {
306 self.link.titles = Some(titles);
307 self
308 }
309
310 /// Add a single property to the link.
311 ///
312 /// Defined in [RFC 7033 Section
313 /// 4.4.4.5](https://www.rfc-editor.org/rfc/rfc7033.html#section-4.4.4.5)
314 pub fn property<K: Into<String>, V: Into<Option<String>>>(mut self, key: K, value: V) -> Self {
315 self.link
316 .properties
317 .get_or_insert_with(HashMap::new)
318 .insert(key.into(), value.into());
319 self
320 }
321
322 /// Set the properties of the link.
323 ///
324 /// Defined in [RFC 7033 Section
325 /// 4.4.4.5](https://www.rfc-editor.org/rfc/rfc7033.html#section-4.4.4.5)
326 pub fn properties(mut self, properties: HashMap<String, Option<String>>) -> Self {
327 self.link.properties = Some(properties);
328 self
329 }
330
331 /// Build the link.
332 ///
333 /// This can be omitted if the link is being converted to a `Link` directly from the builder as
334 /// `LinkBuilder` also implements `From<LinkBuilder> for Link`.
335 pub fn build(self) -> Link {
336 self.link
337 }
338}
339
340impl From<LinkBuilder> for Link {
341 fn from(builder: LinkBuilder) -> Self {
342 builder.build()
343 }
344}
345
346/// Custom debug implementation to avoid printing `None` fields
347impl Debug for Link {
348 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
349 let mut debug = f.debug_struct("Link");
350 let mut debug = debug.field("rel", &self.rel);
351 if let Some(r#type) = &self.r#type {
352 debug = debug.field("type", &r#type);
353 }
354 if let Some(href) = &self.href {
355 debug = debug.field("href", &href);
356 }
357 if let Some(titles) = &self.titles {
358 debug = debug.field("titles", &titles);
359 }
360 if let Some(properties) = &self.properties {
361 debug = debug.field("properties", &properties);
362 }
363 debug.finish()
364 }
365}
366
367/// A title in the WebFinger response.
368///
369/// Defined in [RFC 7033 Section 4.4.4.4](https://www.rfc-editor.org/rfc/rfc7033.html#section-4.4.4.4)
370///
371/// # Examples
372///
373/// ```rust
374/// use webfinger_rs::Title;
375///
376/// let title = Title::new("en-us", "Carol's Profile");
377/// ```
378#[derive(Debug, Serialize, Deserialize)]
379pub struct Title {
380 /// The language of the title.
381 ///
382 /// This can be any valid language tag as defined in [RFC
383 /// 5646](https://www.rfc-editor.org/rfc/rfc5646.html) or the string `und` to indicate an
384 /// undefined language.
385 pub language: String,
386 /// The value of the title.
387 pub value: String,
388}
389
390impl Title {
391 /// Create a new title with the given language and value.
392 pub fn new<L: Into<String>, V: Into<String>>(language: L, value: V) -> Self {
393 Self {
394 language: language.into(),
395 value: value.into(),
396 }
397 }
398}