Initial commit
This commit is contained in:
commit
19471c125f
|
@ -0,0 +1,3 @@
|
|||
/target
|
||||
.idea
|
||||
.vscode
|
|
@ -0,0 +1,156 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "form_urlencoded"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8"
|
||||
dependencies = [
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6"
|
||||
dependencies = [
|
||||
"unicode-bidi",
|
||||
"unicode-normalization",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440"
|
||||
|
||||
[[package]]
|
||||
name = "magnetar"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.51"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.152"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.152"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.93"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cad406b69c91885b5107daf2c29572f6c8cdb3c66826821e286c533490c0bc76"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.107"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec"
|
||||
version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
|
||||
dependencies = [
|
||||
"tinyvec_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec_macros"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-bidi"
|
||||
version = "0.3.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d54675592c1dbefd78cbd98db9bacd89886e1ca50692a0692baefffdeb92dd58"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-normalization"
|
||||
version = "0.1.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921"
|
||||
dependencies = [
|
||||
"tinyvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643"
|
||||
dependencies = [
|
||||
"form_urlencoded",
|
||||
"idna",
|
||||
"percent-encoding",
|
||||
]
|
|
@ -0,0 +1,10 @@
|
|||
[package]
|
||||
name = "magnetar"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
url = "2.3"
|
|
@ -0,0 +1,6 @@
|
|||
#[forbid(unsafe_code)]
|
||||
mod web_model;
|
||||
|
||||
fn main() {
|
||||
println!("Hello, world!");
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
struct JsonLD {}
|
|
@ -0,0 +1,109 @@
|
|||
use serde::Serialize;
|
||||
|
||||
pub mod jsonld;
|
||||
pub mod webfinger;
|
||||
|
||||
trait ContentType: Serialize {
|
||||
fn mime_type(&self) -> &'static str;
|
||||
}
|
||||
|
||||
macro_rules! content_type {
|
||||
($visib:vis $typ:ident, $expression:expr) => {
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Debug, Default)]
|
||||
$visib struct $typ;
|
||||
|
||||
impl AsRef<str> for $typ {
|
||||
fn as_ref(&self) -> &'static str {
|
||||
$expression
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for $typ {
|
||||
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
serializer.serialize_str($expression)
|
||||
}
|
||||
}
|
||||
|
||||
impl ContentType for $typ {
|
||||
fn mime_type(&self) -> &'static str {
|
||||
$expression
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for $typ {
|
||||
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
||||
let content_type = String::deserialize(deserializer)?;
|
||||
|
||||
if matches!(content_type.as_ref(), $expression) {
|
||||
Ok(Self)
|
||||
} else {
|
||||
Err(Error::custom(format!(
|
||||
"Invalid content type: {content_type}"
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub mod content_type {
|
||||
use crate::web_model::ContentType;
|
||||
use serde::de::Error;
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
|
||||
content_type!(pub ContentActivityPlusJson, "application/activity+json");
|
||||
content_type!(pub ContentHtml, "text/html");
|
||||
}
|
||||
|
||||
macro_rules! link_rel {
|
||||
($visib:vis $typ:ident, $expression:expr) => {
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Debug, Default)]
|
||||
$visib struct $typ;
|
||||
|
||||
impl AsRef<str> for $typ {
|
||||
fn as_ref(&self) -> &'static str {
|
||||
$expression
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for $typ {
|
||||
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
serializer.serialize_str($expression)
|
||||
}
|
||||
}
|
||||
|
||||
impl Rel for $typ {
|
||||
fn rel(&self) -> &'static str {
|
||||
$expression
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for $typ {
|
||||
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
||||
let rel = String::deserialize(deserializer)?;
|
||||
|
||||
if matches!(rel.as_ref(), $expression) {
|
||||
Ok(Self)
|
||||
} else {
|
||||
Err(Error::custom(format!(
|
||||
"Invalid rel: {rel}"
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
trait Rel: Serialize {
|
||||
fn rel(&self) -> &'static str;
|
||||
}
|
||||
|
||||
pub mod rel {
|
||||
use crate::web_model::Rel;
|
||||
use serde::de::Error;
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
|
||||
link_rel!(pub RelWebFingerProfilePage, "http://webfinger.net/rel/profile-page");
|
||||
link_rel!(pub RelSelf, "self");
|
||||
link_rel!(pub RelOStatusSubscribe, "http://ostatus.org/schema/1.0/subscribe");
|
||||
}
|
|
@ -0,0 +1,153 @@
|
|||
use crate::web_model::content_type::{ContentActivityPlusJson, ContentHtml};
|
||||
use crate::web_model::rel::{RelOStatusSubscribe, RelSelf, RelWebFingerProfilePage};
|
||||
use serde::de::Error;
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
|
||||
struct WebFinger {
|
||||
subject: WebFingerSubject,
|
||||
aliases: Vec<WebFingerSubject>,
|
||||
links: Vec<WebFingerRel>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
enum WebFingerSubject {
|
||||
Acct(Acct),
|
||||
Url(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
#[allow(clippy::enum_variant_names)]
|
||||
enum WebFingerRel {
|
||||
RelWebFingerProfilePage {
|
||||
rel: RelWebFingerProfilePage,
|
||||
#[serde(rename = "type")]
|
||||
content_type: ContentHtml,
|
||||
href: String,
|
||||
},
|
||||
RelSelf {
|
||||
rel: RelSelf,
|
||||
#[serde(rename = "type")]
|
||||
content_type: ContentActivityPlusJson,
|
||||
href: String,
|
||||
},
|
||||
RelOStatusSubscribe {
|
||||
rel: RelOStatusSubscribe,
|
||||
template: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct Acct(String);
|
||||
|
||||
impl AsRef<str> for Acct {
|
||||
fn as_ref(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for Acct {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_str(&format!("acct:{}", self.0))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for Acct {
|
||||
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
||||
let acct = String::deserialize(deserializer)?;
|
||||
|
||||
if let Some(rem) = acct.strip_prefix("acct:") {
|
||||
Ok(Acct(rem.to_owned()))
|
||||
} else {
|
||||
Err(Error::custom(
|
||||
"Missing acct protocol for account!".to_owned(),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::web_model::content_type::{ContentActivityPlusJson, ContentHtml};
|
||||
use crate::web_model::rel::{RelOStatusSubscribe, RelSelf, RelWebFingerProfilePage};
|
||||
use crate::web_model::webfinger::WebFingerSubject::Url;
|
||||
use crate::web_model::webfinger::{Acct, WebFinger, WebFingerRel, WebFingerSubject};
|
||||
use serde_json::json;
|
||||
|
||||
#[test]
|
||||
fn should_remove_acct_prefix() {
|
||||
let json = json!("acct:natty@tech.lgbt");
|
||||
|
||||
let acct: Acct = serde_json::from_value(json).unwrap();
|
||||
|
||||
assert_eq!(acct, Acct("natty@tech.lgbt".to_owned()))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_add_acct_prefix() {
|
||||
let acct = Acct("natty@tech.lgbt".to_owned());
|
||||
let json = serde_json::to_value(acct).unwrap();
|
||||
|
||||
assert_eq!(json, json!("acct:natty@tech.lgbt"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_parse_webfinger() {
|
||||
let json = json!({
|
||||
"subject": "acct:natty@tech.lgbt",
|
||||
"aliases": [
|
||||
"https://tech.lgbt/@natty",
|
||||
"https://tech.lgbt/users/natty"
|
||||
],
|
||||
"links": [
|
||||
{
|
||||
"rel": "http://webfinger.net/rel/profile-page",
|
||||
"type": "text/html",
|
||||
"href": "https://tech.lgbt/@natty"
|
||||
},
|
||||
{
|
||||
"rel": "self",
|
||||
"type": "application/activity+json",
|
||||
"href": "https://tech.lgbt/users/natty"
|
||||
},
|
||||
{
|
||||
"rel": "http://ostatus.org/schema/1.0/subscribe",
|
||||
"template": "https://tech.lgbt/authorize_interaction?uri={uri}"
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
let webfinger: WebFinger = serde_json::from_value(json).unwrap();
|
||||
|
||||
let real = WebFinger {
|
||||
subject: WebFingerSubject::Acct(Acct("natty@tech.lgbt".to_owned())),
|
||||
aliases: vec![
|
||||
Url("https://tech.lgbt/@natty".to_owned()),
|
||||
Url("https://tech.lgbt/users/natty".to_owned()),
|
||||
],
|
||||
links: vec![
|
||||
WebFingerRel::RelWebFingerProfilePage {
|
||||
rel: RelWebFingerProfilePage,
|
||||
content_type: ContentHtml,
|
||||
href: "https://tech.lgbt/@natty".to_owned(),
|
||||
},
|
||||
WebFingerRel::RelSelf {
|
||||
rel: RelSelf,
|
||||
content_type: ContentActivityPlusJson,
|
||||
href: "https://tech.lgbt/users/natty".to_owned(),
|
||||
},
|
||||
WebFingerRel::RelOStatusSubscribe {
|
||||
rel: RelOStatusSubscribe,
|
||||
template: "https://tech.lgbt/authorize_interaction?uri={uri}".to_owned(),
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
assert_eq!(webfinger, real)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue