1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
use http::Uri;
use tracing::trace;
use crate::error::Error;
use crate::{WebFingerRequest, WebFingerResponse};
struct EmptyBody;
impl From<EmptyBody> for reqwest::Body {
fn from(_: EmptyBody) -> reqwest::Body {
reqwest::Body::default()
}
}
impl TryFrom<&WebFingerRequest> for http::Request<EmptyBody> {
type Error = http::Error;
fn try_from(query: &WebFingerRequest) -> Result<http::Request<EmptyBody>, http::Error> {
let uri = Uri::try_from(query)?;
http::Request::builder()
.method("GET")
.uri(uri)
.body(EmptyBody)
}
}
impl TryFrom<&WebFingerRequest> for reqwest::Request {
type Error = crate::Error;
fn try_from(query: &WebFingerRequest) -> Result<reqwest::Request, crate::Error> {
let request = http::Request::try_from(query)?;
let request = reqwest::Request::try_from(request)?;
Ok(request)
}
}
impl WebFingerRequest {
/// Executes the WebFinger request with a fresh [`reqwest::Client`].
///
/// This is the shortest path from a [`WebFingerRequest`] to a parsed [`WebFingerResponse`].
/// The method:
///
/// 1. Converts the WebFinger query into a `GET` [`reqwest::Request`].
/// 1. Creates a new default [`reqwest::Client`].
/// 1. Sends the request with that client.
/// 1. Rejects non-success HTTP statuses with [`reqwest::Response::error_for_status`].
/// 1. Deserializes the response body as JSON into [`WebFingerResponse`].
///
/// Use this when the default Reqwest client configuration is sufficient. If you need shared
/// connection pooling, custom headers, middleware, proxies, timeouts, or TLS settings, prefer
/// [`Self::execute_reqwest_with_client`] instead.
///
/// Errors are returned as [`crate::Error`]:
///
/// - Request-construction failures surface as [`crate::Error::Http`] or
/// [`crate::Error::InvalidUri`].
/// - Reqwest transport failures, non-success HTTP statuses, and JSON decoding failures surface
/// as [`crate::Error::Reqwest`].
///
/// # Examples
///
/// ```rust,no_run
/// use webfinger_rs::WebFingerRequest;
///
/// # async fn run() -> Result<(), Box<dyn std::error::Error>> {
/// let request = WebFingerRequest::builder("acct:carol@example.com")?
/// .host("example.com")
/// .rel("http://webfinger.net/rel/profile-page")
/// .build();
///
/// let response = request.execute_reqwest().await?;
/// println!("{response:#?}");
/// # Ok(())
/// # }
/// ```
#[tracing::instrument]
pub async fn execute_reqwest(&self) -> Result<WebFingerResponse, Error> {
let client = reqwest::Client::new();
self.execute_reqwest_with_client(&client).await
}
/// Executes the WebFinger request with a caller-provided [`reqwest::Client`].
///
/// This follows the same conversion, status handling, and JSON decoding path as
/// [`Self::execute_reqwest`], but reuses the client you provide instead of constructing a new
/// default one for each call.
///
/// Use this when your application already owns a configured client, for example to:
///
/// - reuse connection pools across multiple requests;
/// - set default headers, user agents, or auth;
/// - configure timeouts, proxies, redirects, or TLS behavior; or
/// - integrate with Reqwest middleware or client-wide instrumentation.
///
/// Non-success HTTP statuses and JSON decoding failures still surface as
/// [`crate::Error::Reqwest`], because they originate from Reqwest's response handling.
///
/// # Examples
///
/// ```rust,no_run
/// use std::time::Duration;
///
/// use reqwest::Client;
/// use webfinger_rs::WebFingerRequest;
///
/// # async fn run() -> Result<(), Box<dyn std::error::Error>> {
/// let client = Client::builder()
/// .timeout(Duration::from_secs(10))
/// .user_agent("webfinger-rs docs example")
/// .build()?;
///
/// let request = WebFingerRequest::builder("acct:carol@example.com")?
/// .host("example.com")
/// .build();
///
/// let response = request.execute_reqwest_with_client(&client).await?;
/// println!("{response:#?}");
/// # Ok(())
/// # }
/// ```
#[tracing::instrument]
pub async fn execute_reqwest_with_client(
&self,
client: &reqwest::Client,
) -> Result<WebFingerResponse, Error> {
let request = self.try_into()?;
trace!("request: {:?}", request);
let response = client.execute(request).await?;
trace!("response: {:?}", response);
async_convert::TryFrom::try_from(response).await
}
/// Converts this WebFinger query into a [`reqwest::Request`] without executing it.
///
/// This is useful when you want to inspect or modify the outgoing request before sending it,
/// or when another part of your application is responsible for execution.
///
/// The resulting request is an HTTPS `GET` to the WebFinger well-known endpoint with the
/// current `resource`, `host`, and `rel` values encoded into the URL.
///
/// This only performs request construction. It does not send anything over the network.
///
/// # Examples
///
/// ```rust
/// use webfinger_rs::WebFingerRequest;
///
/// let request = WebFingerRequest::builder("acct:carol@example.com")?
/// .host("example.com")
/// .rel("http://webfinger.net/rel/profile-page")
/// .build();
///
/// let reqwest_request = request.try_into_reqwest()?;
/// assert_eq!(reqwest_request.method(), reqwest::Method::GET);
/// assert_eq!(
/// reqwest_request.url().as_str(),
/// "https://example.com/.well-known/webfinger?resource=acct:carol@example.com&rel=http://webfinger.net/rel/profile-page"
/// );
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
pub fn try_into_reqwest(&self) -> Result<reqwest::Request, Error> {
self.try_into()
}
}
impl WebFingerResponse {
/// Converts a completed [`reqwest::Response`] into a [`WebFingerResponse`].
///
/// This is useful when you execute the HTTP request yourself, but still want this crate's
/// WebFinger response parsing behavior.
///
/// The conversion:
///
/// 1. Rejects non-success HTTP statuses with [`reqwest::Response::error_for_status`].
/// 1. Deserializes the response body as JSON into [`WebFingerResponse`].
///
/// Both status failures and JSON decoding failures surface as [`crate::Error::Reqwest`].
///
/// # Examples
///
/// ```rust,no_run
/// use reqwest::Client;
/// use webfinger_rs::{WebFingerRequest, WebFingerResponse};
///
/// # async fn run() -> Result<(), Box<dyn std::error::Error>> {
/// let client = Client::new();
/// let request = WebFingerRequest::builder("acct:carol@example.com")?
/// .host("example.com")
/// .build()
/// .try_into_reqwest()?;
///
/// let response = client.execute(request).await?;
/// let webfinger = WebFingerResponse::try_from_reqwest(response).await?;
/// println!("{webfinger:#?}");
/// # Ok(())
/// # }
/// ```
pub async fn try_from_reqwest(response: reqwest::Response) -> Result<WebFingerResponse, Error> {
async_convert::TryFrom::try_from(response).await
}
}
#[async_convert::async_trait]
impl async_convert::TryFrom<reqwest::Response> for WebFingerResponse {
type Error = crate::Error;
async fn try_from(response: reqwest::Response) -> Result<WebFingerResponse, crate::Error> {
let response = response.error_for_status()?;
let response = response.json().await?;
Ok(response)
}
}