pub struct WebFingerResponse {
pub subject: JrdUri,
pub aliases: Option<Vec<JrdUri>>,
pub properties: Option<BTreeMap<JrdUri, Option<String>>>,
pub links: Vec<Link>,
}Expand description
A WebFinger response.
This is the JSON Resource Descriptor (JRD) returned by a WebFinger server. The Rust fields map directly to the top-level members from RFC 7033:
subjectis required and usesJrdUribecause the RFC defines it as the URI of the resource described by the JRD.aliasesis an optional list of URI strings, also represented asJrdUri.propertiesis an optional object with URI property identifiers and string-or-null values.linksis the JRD link array. Missinglinksdeserializes as an empty vector.
The response serializes to the RFC JSON shape. It uses typed wrappers for URI-valued and relation-valued fields while keeping builder methods string-friendly for application code.
See RFC 7033 section 4.4.
§Examples
Constructing a response with builders keeps common server code concise:
use webfinger_rs::{Link, WebFingerResponse};
let avatar = Link::builder("http://webfinger.net/rel/avatar")
.href("https://example.com/avatar.png")
.build();
let profile = Link::builder("http://webfinger.net/rel/profile-page")
.href("https://example.com/profile/carol")
.build();
let response = WebFingerResponse::builder("acct:carol@example.com")
.alias("https://example.com/profile/carol")
.property("https://example.com/ns/role", "developer")
.link(avatar)
.link(profile)
.build();JSON null property values are represented with null_property on the builder:
use webfinger_rs::{Link, WebFingerResponse};
let response = WebFingerResponse::builder("acct:carol@example.com")
.property("https://example.com/ns/role", "developer")
.null_property("https://example.com/ns/previous-role")
.link(
Link::builder("author")
.href("https://example.com/people/carol")
.null_property("https://example.com/ns/legacy-page"),
)
.build();
let json = serde_json::to_value(response)?;
assert_eq!(
json["properties"]["https://example.com/ns/previous-role"],
serde_json::Value::Null,
);Response can be used as a response in Axum handlers as it implements
axum::response::IntoResponse.
use axum::response::IntoResponse;
use webfinger_rs::{Link, WebFingerRequest, WebFingerResponse};
async fn handler(request: WebFingerRequest) -> WebFingerResponse {
// ... handle the request ...
WebFingerResponse::builder("acct:carol@example.com")
.alias("https://example.com/profile/carol")
.property("https://example.com/ns/role", "developer")
.link(
Link::builder("http://webfinger.net/rel/avatar")
.href("https://example.com/avatar.png"),
)
.build()
}Fields§
§subject: JrdUriThe subject of the response.
This is the URI of the resource that the JRD describes. RFC 7033 makes it required when a response is returned, so the Rust field is not optional.
JrdUri is used instead of String so relative references are rejected during
deserialization and builder construction.
aliases: Option<Vec<JrdUri>>The aliases of the response.
Aliases are additional URI strings for the same subject. The field is optional because the
JSON member may be absent. Each value is a JrdUri for the same reason as
Response::subject.
properties: Option<BTreeMap<JrdUri, Option<String>>>The properties of the response.
JRD properties are a JSON object whose names are URI strings. Values may be strings or JSON
null, so the Rust value type is Option<String>. None serializes as a property value of
null; it does not omit the property from the map.
A BTreeMap is used for deterministic ordering and to support the standard ordering and
hashing traits on Response.
links: Vec<Link>The links of the response.
This is the JRD links array. A missing JSON member deserializes to an empty vector so code
can iterate links without handling a separate absent state.
See RFC 7033 section 4.4.4 and Link.
Implementations§
Source§impl WebFingerResponse
impl WebFingerResponse
Sourcepub async fn try_from_reqwest(
response: Response,
) -> Result<WebFingerResponse, Error>
Available on crate feature reqwest only.
pub async fn try_from_reqwest( response: Response, ) -> Result<WebFingerResponse, Error>
reqwest only.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:
- Rejects non-success HTTP statuses with
reqwest::Response::error_for_status. - Deserializes the response body as JSON into
WebFingerResponse.
Both status failures and JSON decoding failures surface as crate::Error::Reqwest.
§Examples
use reqwest::Client;
use webfinger_rs::{WebFingerRequest, WebFingerResponse};
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:#?}");Examples found in repository?
9async fn main() -> Result<(), Box<dyn std::error::Error>> {
10 let _ = rustls::crypto::aws_lc_rs::default_provider().install_default();
11
12 let request = WebFingerRequest::builder(SUBJECT)?
13 .host(HOST)
14 .rel(PROFILE_PAGE_REL)
15 .rel(AVATAR_REL)
16 .build();
17
18 let reqwest_request = request.try_into_reqwest()?;
19 eprintln!("GET {}", reqwest_request.url());
20
21 let client = reqwest::Client::builder()
22 .danger_accept_invalid_certs(true)
23 .https_only(true)
24 .build()?;
25 let response = client.execute(reqwest_request).await?;
26 let response = WebFingerResponse::try_from_reqwest(response).await?;
27
28 println!("Subject: {}", response.subject);
29 for rel in [PROFILE_PAGE_REL, AVATAR_REL] {
30 if let Some(href) = response
31 .links
32 .iter()
33 .find(|link| link.rel.as_ref() == rel)
34 .and_then(|link| link.href.as_ref().map(|href| href.as_ref()))
35 {
36 println!("{rel}: {href}");
37 }
38 }
39 println!("{}", serde_json::to_string_pretty(&response)?);
40 Ok(())
41}Source§impl Response
impl Response
Sourcepub fn new<S: AsRef<str>>(subject: S) -> Self
pub fn new<S: AsRef<str>>(subject: S) -> Self
Creates a response with the given subject and no optional JRD members.
This constructor is intended for application-controlled subject strings. It validates the
subject as a JrdUri and panics if the string is not an absolute URI. Use
Response::try_builder when the subject comes from external input.
Sourcepub fn builder<S: AsRef<str>>(subject: S) -> Builder
pub fn builder<S: AsRef<str>>(subject: S) -> Builder
Creates a Builder with the given subject.
The builder accepts strings at the API boundary and stores typed JRD values internally. This keeps straightforward server responses concise while still producing the RFC-shaped JSON object.
§Examples
use webfinger_rs::{Link, WebFingerResponse};
let avatar =
Link::builder("http://webfinger.net/rel/avatar").href("https://example.com/avatar.png");
let response = WebFingerResponse::builder("acct:carol@example.com")
.alias("https://example.com/profile/carol")
.property("https://example.com/ns/role", "developer")
.link(avatar)
.build();Examples found in repository?
68async fn webfinger(request: WebFingerRequest) -> actix_web::Result<WebFingerResponse> {
69 info!("fetching webfinger resource: {:?}", request);
70 let subject = request.resource.to_string();
71 if subject != SUBJECT {
72 let message = format!("{subject} does not exist");
73 return Err(actix_web::error::ErrorNotFound(message))?;
74 }
75 let mut links = Vec::new();
76
77 let profile_rel = Rel::new(PROFILE_PAGE_REL);
78 if request.rels.is_empty() || request.rels.contains(&profile_rel) {
79 links.push(
80 Link::builder(profile_rel)
81 .href(PROFILE_HREF)
82 .title("en", "Carol's profile")
83 .build(),
84 );
85 }
86
87 let avatar_rel = Rel::new(AVATAR_REL);
88 if request.rels.is_empty() || request.rels.contains(&avatar_rel) {
89 links.push(
90 Link::builder(avatar_rel)
91 .href(AVATAR_HREF)
92 .r#type("image/png")
93 .build(),
94 );
95 }
96
97 let response = WebFingerResponse::builder(subject)
98 .alias(PROFILE_HREF)
99 .property(ROLE_PROPERTY, "maintainer")
100 .links(links)
101 .build();
102 Ok(response)
103}More examples
71async fn webfinger(request: WebFingerRequest) -> axum::response::Result<WebFingerResponse> {
72 info!("fetching webfinger resource: {:?}", request);
73 let subject = request.resource.to_string();
74 if subject != SUBJECT {
75 let message = format!("{subject} does not exist");
76 return Err((StatusCode::NOT_FOUND, message).into());
77 }
78 let mut links = Vec::new();
79
80 let profile_rel = Rel::new(PROFILE_PAGE_REL);
81 if request.rels.is_empty() || request.rels.contains(&profile_rel) {
82 links.push(
83 Link::builder(profile_rel)
84 .href(PROFILE_HREF)
85 .title("en", "Carol's profile")
86 .build(),
87 );
88 }
89
90 let avatar_rel = Rel::new(AVATAR_REL);
91 if request.rels.is_empty() || request.rels.contains(&avatar_rel) {
92 links.push(
93 Link::builder(avatar_rel)
94 .href(AVATAR_HREF)
95 .r#type("image/png")
96 .build(),
97 );
98 }
99
100 let response = WebFingerResponse::builder(subject)
101 .alias(PROFILE_HREF)
102 .property(ROLE_PROPERTY, "maintainer")
103 .links(links)
104 .build();
105 Ok(response)
106}Sourcepub fn try_builder<S: AsRef<str>>(subject: S) -> Result<Builder, Error>
pub fn try_builder<S: AsRef<str>>(subject: S) -> Result<Builder, Error>
Tries to create a new Builder with the given subject.
Use this when the subject string has not already been validated by application logic. The
returned builder has the same methods as Response::builder.
§Examples
use webfinger_rs::WebFingerResponse;
assert!(WebFingerResponse::try_builder("acct:carol@example.com").is_ok());
assert!(WebFingerResponse::try_builder("/users/carol").is_err());Trait Implementations§
Source§impl<'de> Deserialize<'de> for Response
impl<'de> Deserialize<'de> for Response
Source§fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
impl Eq for Response
Source§impl IntoResponse for WebFingerResponse
Available on crate feature axum only.
impl IntoResponse for WebFingerResponse
axum only.Source§fn into_response(self) -> AxumResponse
fn into_response(self) -> AxumResponse
Converts a WebFingerResponse into an Axum response.
This serializes the body as JSON, sets the Content-Type header to
application/jrd+json, and allows cross-origin browser requests with
Access-Control-Allow-Origin: * as recommended by RFC 7033 section 5.
Handlers can therefore return WebFingerResponse directly without manually wrapping it in
axum::Json or setting the response header themselves.
Mount the route at crate::WELL_KNOWN_PATH so the handler matches the standard WebFinger
endpoint path.
See also the crate::axum module docs and the Axum example.
§Example
use axum::{Router, routing::get};
use http::StatusCode;
use webfinger_rs::{Link, Rel, WELL_KNOWN_PATH, WebFingerRequest, WebFingerResponse};
async fn webfinger(request: WebFingerRequest) -> axum::response::Result<WebFingerResponse> {
let subject = request.resource.to_string();
if subject != "acct:carol@example.com" {
return Err((StatusCode::NOT_FOUND, "not found").into());
}
let rel = Rel::new("http://webfinger.net/rel/profile-page");
let response = if request.rels.is_empty() || request.rels.contains(&rel) {
let link = Link::builder(rel).href("https://example.com/users/carol");
WebFingerResponse::builder(subject).link(link).build()
} else {
WebFingerResponse::builder(subject).build()
};
Ok(response)
}
let app = Router::<()>::new().route(WELL_KNOWN_PATH, get(webfinger));Source§impl Ord for Response
impl Ord for Response
1.21.0 (const: unstable) · Source§fn max(self, other: Self) -> Selfwhere
Self: Sized,
fn max(self, other: Self) -> Selfwhere
Self: Sized,
Source§impl PartialOrd for Response
impl PartialOrd for Response
Source§impl Responder for WebFingerResponse
Available on crate feature actix only.
impl Responder for WebFingerResponse
actix only.Source§type Body = <Json<Response> as Responder>::Body
type Body = <Json<Response> as Responder>::Body
Converts a WebFingerResponse into an Actix response.
This serializes the body as JSON and sets the Content-Type header to
application/jrd+json, which is the JRD media type used by WebFinger.
It also sets Access-Control-Allow-Origin: * as recommended by RFC 7033 section 5.
Handlers can therefore return WebFingerResponse directly without manually wrapping it in
actix_web::web::Json or setting the response header themselves.
See also the crate::actix module docs and the Actix example.
§Example
use actix_web::{get, App};
use webfinger_rs::{Link, Rel, WebFingerRequest, WebFingerResponse};
#[get("/.well-known/webfinger")]
async fn webfinger(request: WebFingerRequest) -> actix_web::Result<WebFingerResponse> {
let subject = request.resource.to_string();
let rel = Rel::new("http://webfinger.net/rel/profile-page");
let response = if request.rels.is_empty() || request.rels.contains(&rel) {
let link = Link::builder(rel).href("https://example.com/users/carol");
WebFingerResponse::builder(subject).link(link).build()
} else {
WebFingerResponse::builder(subject).build()
};
Ok(response)
}
let app = App::new().service(webfinger);Source§fn respond_to(self, request: &HttpRequest) -> HttpResponse<Self::Body>
fn respond_to(self, request: &HttpRequest) -> HttpResponse<Self::Body>
HttpResponse.impl StructuralPartialEq for Response
Source§impl TryFrom<Response> for WebFingerResponse
Available on crate feature reqwest only.
impl TryFrom<Response> for WebFingerResponse
reqwest only.Auto Trait Implementations§
impl Freeze for Response
impl RefUnwindSafe for Response
impl Send for Response
impl Sync for Response
impl Unpin for Response
impl UnsafeUnpin for Response
impl UnwindSafe for Response
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<Q, K> Comparable<K> for Q
impl<Q, K> Comparable<K> for Q
impl<T> DeserializeOwned for Twhere
T: for<'de> Deserialize<'de>,
Source§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
Source§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
Source§fn equivalent(&self, key: &K) -> bool
fn equivalent(&self, key: &K) -> bool
key and return true if they are equal.Source§impl<T, S> Handler<IntoResponseHandler, S> for T
impl<T, S> Handler<IntoResponseHandler, S> for T
Source§fn call(
self,
_req: Request<Body>,
_state: S,
) -> <T as Handler<IntoResponseHandler, S>>::Future
fn call( self, _req: Request<Body>, _state: S, ) -> <T as Handler<IntoResponseHandler, S>>::Future
Source§fn layer<L>(self, layer: L) -> Layered<L, Self, T, S>where
L: Layer<HandlerService<Self, T, S>> + Clone,
<L as Layer<HandlerService<Self, T, S>>>::Service: Service<Request<Body>>,
fn layer<L>(self, layer: L) -> Layered<L, Self, T, S>where
L: Layer<HandlerService<Self, T, S>> + Clone,
<L as Layer<HandlerService<Self, T, S>>>::Service: Service<Request<Body>>,
tower::Layer to the handler. Read moreSource§fn with_state(self, state: S) -> HandlerService<Self, T, S>
fn with_state(self, state: S) -> HandlerService<Self, T, S>
Service by providing the stateSource§impl<H, T> HandlerWithoutStateExt<T> for H
impl<H, T> HandlerWithoutStateExt<T> for H
Source§fn into_service(self) -> HandlerService<H, T, ()>
fn into_service(self) -> HandlerService<H, T, ()>
Service and no state.Source§fn into_make_service(self) -> IntoMakeService<HandlerService<H, T, ()>>
fn into_make_service(self) -> IntoMakeService<HandlerService<H, T, ()>>
MakeService and no state. Read moreSource§fn into_make_service_with_connect_info<C>(
self,
) -> IntoMakeServiceWithConnectInfo<HandlerService<H, T, ()>, C>
fn into_make_service_with_connect_info<C>( self, ) -> IntoMakeServiceWithConnectInfo<HandlerService<H, T, ()>, C>
tokio only.MakeService which stores information
about the incoming connection and has no state. Read more