use std::{collections::HashMap, fmt::Debug};
use ::http::{uri::Authority, Uri};
use nutype::nutype;
use serde::{Deserialize, Serialize};
use serde_with::skip_serializing_none;
mod http;
#[cfg(feature = "reqwest")]
mod reqwest;
#[derive(Debug)]
pub struct Request {
pub host: Authority,
pub resource: Uri,
pub link_relation_types: Vec<LinkRelationType>,
}
#[skip_serializing_none]
#[derive(Serialize, Deserialize)]
pub struct Response {
pub subject: String,
pub aliases: Option<Vec<String>>,
pub properties: Option<HashMap<String, String>>,
pub links: Vec<Link>,
}
impl Debug for Response {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut debug = f.debug_struct("Response");
let mut debug = debug.field("subject", &self.subject);
if let Some(aliases) = &self.aliases {
debug = debug.field("aliases", &aliases);
}
if let Some(properties) = &self.properties {
debug = debug.field("properties", &properties);
}
debug.field("links", &self.links).finish()
}
}
#[derive(Serialize, Deserialize)]
pub struct Link {
pub rel: LinkRelationType,
pub r#type: Option<String>,
pub href: Option<String>,
pub titles: Option<Vec<Title>>,
pub properties: Option<HashMap<String, Option<String>>>,
}
impl Link {
pub fn new(rel: LinkRelationType) -> Self {
Self {
rel,
r#type: None,
href: None,
titles: None,
properties: None,
}
}
}
impl Debug for Link {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut debug = f.debug_struct("Link");
let mut debug = debug.field("rel", &self.rel);
if let Some(r#type) = &self.r#type {
debug = debug.field("type", &r#type);
}
if let Some(href) = &self.href {
debug = debug.field("href", &href);
}
if let Some(titles) = &self.titles {
debug = debug.field("titles", &titles);
}
if let Some(properties) = &self.properties {
debug = debug.field("properties", &properties);
}
debug.finish()
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Title {
language: String,
value: String,
}
#[nutype(derive(
Debug,
Display,
Clone,
From,
Into,
FromStr,
Display,
Serialize,
Deserialize,
AsRef,
Deref,
PartialEq,
Eq,
))]
pub struct LinkRelationType(String);
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error(transparent)]
Http(#[from] ::http::Error),
#[cfg(feature = "reqwest")]
#[error(transparent)]
Reqwest(#[from] ::reqwest::Error),
#[error("json error: {0}")]
Json(#[from] serde_json::Error),
}
#[cfg(test)]
mod tests {
use ::http::Uri;
use super::*;
#[test]
fn example_3_1() {
let resource = "acct:carol@example.com".parse().unwrap();
let rel = LinkRelationType::from("http://openid.net/specs/connect/1.0/issuer");
let host = "example.com".parse().unwrap();
let query = Request {
host,
resource,
link_relation_types: vec![rel],
};
let uri = Uri::try_from(&query).unwrap();
assert_eq!(
uri.to_string(),
"https://example.com/.well-known/webfinger?resource=acct:carol@example.com&rel=http://openid.net/specs/connect/1.0/issuer",
);
}
#[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,
link_relation_types: vec![],
};
let uri = Uri::try_from(&query).unwrap();
assert_eq!(
uri.to_string(),
"https://blog.example.com/.well-known/webfinger?resource=http://blog.example.com/article/id/314",
);
}
}