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}