Initial commit

This commit is contained in:
Natty 2023-02-14 01:59:15 +01:00
commit 19471c125f
Signed by: natty
GPG Key ID: BF6CB659ADEE60EC
7 changed files with 438 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
/target
.idea
.vscode

156
Cargo.lock generated Normal file
View File

@ -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",
]

10
Cargo.toml Normal file
View File

@ -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"

6
src/main.rs Normal file
View File

@ -0,0 +1,6 @@
#[forbid(unsafe_code)]
mod web_model;
fn main() {
println!("Hello, world!");
}

View File

@ -0,0 +1 @@
struct JsonLD {}

109
src/web_model/mod.rs Normal file
View File

@ -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");
}

View File

@ -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)
}
}