Merge pull request #6 from Plume-org/no-null

No null
pull/9/head
Baptiste Gelez 5 years ago committed by GitHub
commit 82e6d7e3f0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,8 +1,9 @@
//! A crate to help you fetch and serve WebFinger resources.
//!
//!
//! Use [`resolve`] to fetch remote resources, and [`Resolver`] to serve your own resources.
#[macro_use] extern crate serde_derive;
#[macro_use]
extern crate serde_derive;
use reqwest::{header::ACCEPT, Client};
@ -13,7 +14,7 @@ mod tests;
#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct Webfinger {
/// The subject of this WebFinger result.
///
///
/// It is an `acct:` URI
pub subject: String,
@ -21,7 +22,7 @@ pub struct Webfinger {
pub aliases: Vec<String>,
/// Links to places where you may find more information about this resource.
pub links: Vec<Link>
pub links: Vec<Link>,
}
/// Structure to represent a WebFinger link
@ -31,17 +32,19 @@ pub struct Link {
pub rel: String,
/// The actual URL of the link
#[serde(skip_serializing_if = "Option::is_none")]
pub href: Option<String>,
/// The Link may also contain an URL template, instead of an actual URL
#[serde(skip_serializing_if = "Option::is_none")]
pub template: Option<String>,
/// The mime-type of this link.
///
///
/// If you fetch this URL, you may want to use this value for the Accept header of your HTTP
/// request.
#[serde(rename="type")]
pub mime_type: Option<String>
#[serde(rename = "type", skip_serializing_if = "Option::is_none")]
pub mime_type: Option<String>,
}
/// An error that occured while fetching a WebFinger resource.
@ -54,10 +57,9 @@ pub enum WebfingerError {
ParseError,
/// The received JSON couldn't be parsed into a valid [`Webfinger`] struct.
JsonError
JsonError,
}
/// A prefix for a resource, either `acct:`, `group:` or some custom type.
#[derive(Debug, PartialEq)]
pub enum Prefix {
@ -97,23 +99,32 @@ impl Into<String> for Prefix {
/// - `acct`: the identifier of the resource, for instance: `someone@example.org`
/// - `with_https`: indicates wether the URL should be on HTTPS or HTTP
///
pub fn url_for(prefix: Prefix, acct: impl Into<String>, with_https: bool) -> Result<String, WebfingerError> {
pub fn url_for(
prefix: Prefix,
acct: impl Into<String>,
with_https: bool,
) -> Result<String, WebfingerError> {
let acct = acct.into();
let scheme = if with_https {
"https"
} else {
"http"
};
let scheme = if with_https { "https" } else { "http" };
let prefix: String = prefix.into();
acct.split("@")
.nth(1)
.ok_or(WebfingerError::ParseError)
.map(|instance| format!("{}://{}/.well-known/webfinger?resource={}:{}", scheme, instance, prefix, acct))
.map(|instance| {
format!(
"{}://{}/.well-known/webfinger?resource={}:{}",
scheme, instance, prefix, acct
)
})
}
/// Fetches a WebFinger resource, identified by the `acct` parameter, a Webfinger URI.
pub fn resolve_with_prefix(prefix: Prefix, acct: impl Into<String>, with_https: bool) -> Result<Webfinger, WebfingerError> {
pub fn resolve_with_prefix(
prefix: Prefix,
acct: impl Into<String>,
with_https: bool,
) -> Result<Webfinger, WebfingerError> {
let url = url_for(prefix, acct, with_https)?;
Client::new()
.get(&url[..])
@ -131,12 +142,14 @@ pub fn resolve(acct: impl Into<String>, with_https: bool) -> Result<Webfinger, W
let mut parsed = acct.splitn(2, ':');
let first = parsed.next().ok_or(WebfingerError::ParseError)?;
if first.contains('@') { // This : was a port number, not a prefix
if first.contains('@') {
// This : was a port number, not a prefix
resolve_with_prefix(Prefix::Acct, acct, with_https)
} else {
if let Some(other) = parsed.next() {
resolve_with_prefix(Prefix::from(first), other, with_https)
} else { // fallback to acct:
} else {
// fallback to acct:
resolve_with_prefix(Prefix::Acct, first, with_https)
}
}
@ -152,11 +165,11 @@ pub enum ResolverError {
WrongDomain,
/// The requested resource was not found.
NotFound
NotFound,
}
/// A trait to easily generate a WebFinger endpoint for any resource repository.
///
///
/// The `R` type is your resource repository (a database for instance) that will be passed to the
/// [`find`](Resolver::find) and [`endpoint`](Resolver::endpoint) functions.
pub trait Resolver<R> {
@ -164,10 +177,10 @@ pub trait Resolver<R> {
fn instance_domain<'a>() -> &'a str;
/// Tries to find a resource, `acct`, in the repository `resource_repo`.
///
///
/// `acct` is not a complete `acct:` URI, it only contains the identifier of the requested resource
/// (e.g. `test` for `acct:test@example.org`)
///
///
/// If the resource couldn't be found, you may probably want to return a [`ResolverError::NotFound`].
fn find(prefix: Prefix, acct: String, resource_repo: R) -> Result<Webfinger, ResolverError>;

@ -1,11 +1,13 @@
use serde_json;
use super::*;
use serde_json;
#[test]
fn test_url_for() {
assert_eq!(
url_for(Prefix::Acct, "test@example.org", true),
Ok(String::from("https://example.org/.well-known/webfinger?resource=acct:test@example.org"))
Ok(String::from(
"https://example.org/.well-known/webfinger?resource=acct:test@example.org"
))
);
assert_eq!(
url_for(Prefix::Acct, "test", true),
@ -13,22 +15,29 @@ fn test_url_for() {
);
assert_eq!(
url_for(Prefix::Acct, "test@example.org", false),
Ok(String::from("http://example.org/.well-known/webfinger?resource=acct:test@example.org"))
Ok(String::from(
"http://example.org/.well-known/webfinger?resource=acct:test@example.org"
))
);
assert_eq!(
url_for(Prefix::Group, "test@example.org", true),
Ok(String::from("https://example.org/.well-known/webfinger?resource=group:test@example.org"))
Ok(String::from(
"https://example.org/.well-known/webfinger?resource=group:test@example.org"
))
);
assert_eq!(
url_for(Prefix::Custom("hey".into()), "test@example.org", true),
Ok(String::from("https://example.org/.well-known/webfinger?resource=hey:test@example.org"))
Ok(String::from(
"https://example.org/.well-known/webfinger?resource=hey:test@example.org"
))
);
}
#[test]
fn test_resolve() {
let m = mockito::mock("GET", mockito::Matcher::Any)
.with_body(r#"
.with_body(
r#"
{
"subject": "acct:test@example.org",
"aliases": [
@ -51,7 +60,8 @@ fn test_resolve() {
}
]
}
"#)
"#,
)
.create();
let url = format!("test@{}", mockito::server_url()).replace("http://", "");
@ -91,26 +101,29 @@ fn test_webfinger_parsing() {
let webfinger: Webfinger = serde_json::from_str(valid).unwrap();
assert_eq!(String::from("acct:test@example.org"), webfinger.subject);
assert_eq!(vec!["https://example.org/@test/"], webfinger.aliases);
assert_eq!(vec![
Link {
rel: "http://webfinger.net/rel/profile-page".to_string(),
mime_type: None,
href: Some("https://example.org/@test/".to_string()),
template: None
},
Link {
rel: "http://schemas.google.com/g/2010#updates-from".to_string(),
mime_type: Some("application/atom+xml".to_string()),
href: Some("https://example.org/@test/feed.atom".to_string()),
template: None
},
Link {
rel: "self".to_string(),
mime_type: Some("application/activity+json".to_string()),
href: Some("https://example.org/@test/".to_string()),
template: None
}
], webfinger.links);
assert_eq!(
vec![
Link {
rel: "http://webfinger.net/rel/profile-page".to_string(),
mime_type: None,
href: Some("https://example.org/@test/".to_string()),
template: None
},
Link {
rel: "http://schemas.google.com/g/2010#updates-from".to_string(),
mime_type: Some("application/atom+xml".to_string()),
href: Some("https://example.org/@test/feed.atom".to_string()),
template: None
},
Link {
rel: "self".to_string(),
mime_type: Some("application/activity+json".to_string()),
href: Some("https://example.org/@test/".to_string()),
template: None
}
],
webfinger.links
);
}
pub struct MyResolver;
@ -121,19 +134,21 @@ impl Resolver<&'static str> for MyResolver {
"instance.tld"
}
fn find(prefix: Prefix, acct: String, resource_repo: &'static str) -> Result<Webfinger, ResolverError> {
fn find(
prefix: Prefix,
acct: String,
resource_repo: &'static str,
) -> Result<Webfinger, ResolverError> {
if acct == resource_repo.to_string() && prefix == Prefix::Acct {
Ok(Webfinger {
subject: acct.clone(),
aliases: vec![acct.clone()],
links: vec![
Link {
rel: "http://webfinger.net/rel/profile-page".to_string(),
mime_type: None,
href: Some(format!("https://instance.tld/@{}/", acct)),
template: None
}
]
links: vec![Link {
rel: "http://webfinger.net/rel/profile-page".to_string(),
mime_type: None,
href: Some(format!("https://instance.tld/@{}/", acct)),
template: None,
}],
})
} else {
Err(ResolverError::NotFound)
@ -144,10 +159,28 @@ impl Resolver<&'static str> for MyResolver {
#[test]
fn test_my_resolver() {
assert!(MyResolver::endpoint("acct:admin@instance.tld", "admin").is_ok());
assert_eq!(MyResolver::endpoint("acct:test@instance.tld", "admin"), Err(ResolverError::NotFound));
assert_eq!(MyResolver::endpoint("acct:admin@oops.ie", "admin"), Err(ResolverError::WrongDomain));
assert_eq!(MyResolver::endpoint("admin@instance.tld", "admin"), Err(ResolverError::InvalidResource));
assert_eq!(MyResolver::endpoint("admin", "admin"), Err(ResolverError::InvalidResource));
assert_eq!(MyResolver::endpoint("acct:admin", "admin"), Err(ResolverError::InvalidResource));
assert_eq!(MyResolver::endpoint("group:admin@instance.tld", "admin"), Err(ResolverError::NotFound));
assert_eq!(
MyResolver::endpoint("acct:test@instance.tld", "admin"),
Err(ResolverError::NotFound)
);
assert_eq!(
MyResolver::endpoint("acct:admin@oops.ie", "admin"),
Err(ResolverError::WrongDomain)
);
assert_eq!(
MyResolver::endpoint("admin@instance.tld", "admin"),
Err(ResolverError::InvalidResource)
);
assert_eq!(
MyResolver::endpoint("admin", "admin"),
Err(ResolverError::InvalidResource)
);
assert_eq!(
MyResolver::endpoint("acct:admin", "admin"),
Err(ResolverError::InvalidResource)
);
assert_eq!(
MyResolver::endpoint("group:admin@instance.tld", "admin"),
Err(ResolverError::NotFound)
);
}

Loading…
Cancel
Save