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}