use serde::{Deserialize, Serialize};
use serde_with::{DisplayFromStr, serde_as};
use crate::{Error, Rel, Resource};
#[serde_as]
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Request {
#[serde_as(as = "DisplayFromStr")]
pub resource: Resource,
pub host: String,
pub rels: Vec<Rel>,
}
impl Request {
pub fn new(resource: Resource) -> Self {
Self {
host: String::new(),
resource,
rels: Vec::new(),
}
}
pub fn builder<U>(uri: U) -> Result<Builder, Error>
where
Resource: TryFrom<U>,
<Resource as TryFrom<U>>::Error: Into<Error>,
{
Builder::new(uri)
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Builder {
request: Request,
}
impl Builder {
pub fn new<U>(uri: U) -> Result<Self, Error>
where
Resource: TryFrom<U>,
<Resource as TryFrom<U>>::Error: Into<Error>,
{
Resource::try_from(uri)
.map(|resource| Self {
request: Request::new(resource),
})
.map_err(Into::into)
}
pub fn host<S: Into<String>>(mut self, host: S) -> Self {
self.request.host = host.into();
self
}
pub fn rel<R: AsRef<str>>(mut self, rel: R) -> Self {
self.request.rels.push(Rel::new(rel));
self
}
pub fn build(self) -> Request {
self.request
}
}
#[cfg(test)]
mod tests {
use http::Uri;
use super::*;
#[test]
fn example_3_1() {
let resource = "acct:carol@example.com".parse().unwrap();
let rel = Rel::new("http://openid.net/specs/connect/1.0/issuer");
let host = "example.com".parse().unwrap();
let query = Request {
host,
resource,
rels: vec![rel],
};
let uri = Uri::try_from(&query).unwrap();
assert_eq!(
uri.to_string(),
"https://example.com/.well-known/webfinger?resource=acct%3Acarol%40example.com&rel=http%3A%2F%2Fopenid.net%2Fspecs%2Fconnect%2F1.0%2Fissuer",
);
}
#[test]
fn example_3_2() {
let resource = "http://blog.example.com/article/id/314".parse().unwrap();
let query = Request {
host: "blog.example.com".parse().unwrap(),
resource,
rels: vec![],
};
let uri = Uri::try_from(&query).unwrap();
assert_eq!(
uri.to_string(),
"https://blog.example.com/.well-known/webfinger?resource=http%3A%2F%2Fblog.example.com%2Farticle%2Fid%2F314",
);
}
#[test]
fn new_starts_with_resource_only() {
let resource = Resource::try_from("acct:carol@example.com").unwrap();
let request = Request::new(resource.clone());
assert_eq!(
request,
Request {
resource,
host: String::new(),
rels: Vec::new(),
}
);
}
#[test]
fn builder_sets_host_and_rels() {
let request = Request::builder("acct:carol@example.com")
.unwrap()
.host("example.com")
.rel("http://webfinger.net/rel/profile-page")
.rel("avatar")
.build();
assert_eq!(
request,
Request {
host: "example.com".to_string(),
resource: Resource::try_from("acct:carol@example.com").unwrap(),
rels: vec![
Rel::new("http://webfinger.net/rel/profile-page"),
Rel::new("avatar"),
],
}
);
}
#[test]
fn builder_rejects_relative_resource() {
let error = Request::builder("/users/carol").expect_err("relative resource");
assert!(matches!(error, Error::InvalidResource(_)));
}
}