webfinger_rs/types/
response.rs

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