1
Fork 0

Checkpoint non-atomic commit

This commit is contained in:
Steffo 2024-11-19 03:31:07 +01:00
parent aec5a01f2c
commit 8b01cc14c0
Signed by: steffo
GPG key ID: 5ADA3868646C3FC0
15 changed files with 124 additions and 65 deletions

View file

@ -4,12 +4,13 @@
<exclude-output /> <exclude-output />
<content url="file://$MODULE_DIR$"> <content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/acrate-inbox/src" isTestSource="false" /> <sourceFolder url="file://$MODULE_DIR$/acrate-inbox/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/acrate-nodeinfo/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/acrate-nodeinfo/tests" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/acrate-webfinger/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/acrate_database/src" isTestSource="false" /> <sourceFolder url="file://$MODULE_DIR$/acrate_database/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/acrate_nodeinfo/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/acrate_nodeinfo/tests" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/acrate_rd/src" isTestSource="false" /> <sourceFolder url="file://$MODULE_DIR$/acrate_rd/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/acrate_rd/tests" isTestSource="true" /> <sourceFolder url="file://$MODULE_DIR$/acrate_rd/tests" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/acrate_mime/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/acrate_rdserver/src" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/target" /> <excludeFolder url="file://$MODULE_DIR$/target" />
</content> </content>
<orderEntry type="inheritedJdk" /> <orderEntry type="inheritedJdk" />

View file

@ -1,3 +1,3 @@
[workspace] [workspace]
resolver = "2" resolver = "2"
members = ["acrate_database", "acrate_rd", "acrate-nodeinfo", "acrate-webfinger"] members = ["acrate_database", "acrate_rd", "acrate_nodeinfo", "acrate_rdserver", "acrate_mime"]

13
acrate_mime/Cargo.toml Normal file
View file

@ -0,0 +1,13 @@
[package]
name = "acrate_mime"
version = "0.1.0"
authors = ["Stefano Pigozzi <me@steffo.eu>"]
edition = "2021"
description = "Rust typing and utilities for MIME / media types"
repository = "https://forge.steffo.eu/unimore/tirocinio-canali-steffo-acrate"
license = "EUPL-1.2"
keywords = ["mime", "mimetype", "media", "media-type", "mime-type"]
categories = ["web-programming"]
[dependencies]
mime = "0.3.17"

14
acrate_mime/src/lib.rs Normal file
View file

@ -0,0 +1,14 @@
pub fn add(left: u64, right: u64) -> u64 {
left + right
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let result = add(2, 2);
assert_eq!(result, 4);
}
}

View file

@ -1,11 +1,18 @@
[package] [package]
name = "acrate-nodeinfo" name = "acrate_nodeinfo"
version = "0.1.0" version = "0.3.0"
authors = ["Stefano Pigozzi <me@steffo.eu>"]
edition = "2021" edition = "2021"
description = "Rust typing and utilities for the NodeInfo format"
repository = "https://forge.steffo.eu/unimore/tirocinio-canali-steffo-acrate"
license = "EUPL-1.2"
keywords = ["nodeinfo", "fediverse"]
categories = ["web-programming"]
[dependencies] [dependencies]
acrate-hostmeta = { path = "../acrate-hostmeta" } acrate_rd = { path = "../acrate_rd" }
log = "0.4.22" log = "0.4.22"
mime = "0.3.17"
reqwest = { version = "0.12.9", features = ["json", "stream"] } reqwest = { version = "0.12.9", features = ["json", "stream"] }
serde = { version = "1.0.214", features = ["derive"] } serde = { version = "1.0.214", features = ["derive"] }
serde_json = "1.0.132" serde_json = "1.0.132"

View file

@ -1,4 +1,4 @@
//! Serde-based NodeInfo fetcher and loose parser. //! Rust typing and utilities for the NodeInfo format.
//! //!
//! > NodeInfo is an effort to create a standardized way of exposing metadata about a server running one of the distributed social networks. //! > NodeInfo is an effort to create a standardized way of exposing metadata about a server running one of the distributed social networks.
//! //!
@ -6,7 +6,9 @@
//! //!
//! - <https://github.com/jhass/nodeinfo/blob/main/PROTOCOL.md> //! - <https://github.com/jhass/nodeinfo/blob/main/PROTOCOL.md>
//! - <https://codeberg.org/fediverse/fep/src/branch/main/fep/f1d5/fep-f1d5.md> //! - <https://codeberg.org/fediverse/fep/src/branch/main/fep/f1d5/fep-f1d5.md>
//!
use std::str::FromStr;
use serde::Deserialize; use serde::Deserialize;
use thiserror::Error; use thiserror::Error;
@ -15,6 +17,7 @@ use thiserror::Error;
/// # Specification /// # Specification
/// ///
/// - <https://github.com/jhass/nodeinfo/blob/main/PROTOCOL.md> /// - <https://github.com/jhass/nodeinfo/blob/main/PROTOCOL.md>
///
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum NodeInfo { pub enum NodeInfo {
V1(NodeInfo1), V1(NodeInfo1),
@ -206,7 +209,7 @@ impl NodeInfo {
base.set_path(Self::WELLKNOWN_NODEINFO_PATH); base.set_path(Self::WELLKNOWN_NODEINFO_PATH);
log::trace!("Discovering NodeInfo document locations..."); log::trace!("Discovering NodeInfo document locations...");
let discovery = acrate_hostmeta::any::ResourceDescriptor::get(client, base) let discovery = acrate_rd::any::ResourceDescriptor::get(client, base)
.await .await
.map_err(Get)? .map_err(Get)?
.jrd(); .jrd();
@ -315,7 +318,7 @@ impl NodeInfo {
pub enum NodeInfoGetWellknownError { pub enum NodeInfoGetWellknownError {
/// The discovery of possible locations for NodeInfo documents failed. /// The discovery of possible locations for NodeInfo documents failed.
#[error("the discovery of possible locations for NodeInfo documents failed")] #[error("the discovery of possible locations for NodeInfo documents failed")]
Get(acrate_hostmeta::any::GetError), Get(acrate_rd::any::GetError),
/// No compatible NodeInfo documents were detected at the given URL. /// No compatible NodeInfo documents were detected at the given URL.
#[error("no compatible NodeInfo documents were detected at the given URL")] #[error("no compatible NodeInfo documents were detected at the given URL")]
Unsupported, Unsupported,
@ -346,21 +349,27 @@ impl NodeInfo1 {
let response = client.execute(request) let response = client.execute(request)
.await .await
.map_err(Request)?; .map_err(Request)?;
log::trace!("Checking `Content-Type` of the response..."); log::trace!("Checking `Content-Type` of the response...");
let content_type = response let content_type = response
.headers() .headers()
.get(reqwest::header::CONTENT_TYPE) .get(reqwest::header::CONTENT_TYPE)
.ok_or(ContentTypeMissing)?; .ok_or(ContentTypeMissing)?;
log::trace!("Extracting MIME type from the `Content-Type` header..."); log::trace!("Extracting media type from the `Content-Type` header...");
let mime_type = extract_mime_from_content_type(content_type) let mime_type = content_type.to_str()
.ok_or(ContentTypeInvalid)?; .map_err(ContentTypeUnprintable)?;
log::trace!("Ensuring MIME type is acceptable for NodeInfo documents..."); log::trace!("Parsing media type: {mime_type:?}");
if mime_type != "application/json" { let mime_type = mime::Mime::from_str(mime_type)
log::error!("MIME type `{mime_type}` is not acceptable for NodeInfo documents."); .map_err(ContentTypeInvalid)?;
return Err(ContentTypeInvalid)
log::trace!("Checking if media type is supported: {mime_type:?}");
let mime_is_json = mime_type == mime::APPLICATION_JSON;
log::trace!("Is media type application/json? {mime_is_json:?}");
if !mime_is_json {
log::error!("MIME type `{mime_type}` is not acceptable for JSON parsing.");
return Err(ContentTypeUnsupported);
} }
log::trace!("Attempting to parse response as JSON..."); log::trace!("Attempting to parse response as JSON...");
@ -418,21 +427,27 @@ impl NodeInfo2 {
let response = client.execute(request) let response = client.execute(request)
.await .await
.map_err(Request)?; .map_err(Request)?;
log::trace!("Checking `Content-Type` of the response..."); log::trace!("Checking `Content-Type` of the response...");
let content_type = response let content_type = response
.headers() .headers()
.get(reqwest::header::CONTENT_TYPE) .get(reqwest::header::CONTENT_TYPE)
.ok_or(ContentTypeMissing)?; .ok_or(ContentTypeMissing)?;
log::trace!("Extracting MIME type from the `Content-Type` header..."); log::trace!("Extracting media type from the `Content-Type` header...");
let mime_type = extract_mime_from_content_type(content_type) let mime_type = content_type.to_str()
.ok_or(ContentTypeInvalid)?; .map_err(ContentTypeUnprintable)?;
log::trace!("Ensuring MIME type is acceptable for NodeInfo documents..."); log::trace!("Parsing media type: {mime_type:?}");
if mime_type != "application/json" { let mime_type = mime::Mime::from_str(mime_type)
log::error!("MIME type `{mime_type}` is not acceptable for NodeInfo documents."); .map_err(ContentTypeInvalid)?;
return Err(ContentTypeInvalid)
log::trace!("Checking if media type is supported...");
let mime_is_json = mime_type == mime::APPLICATION_JSON;
log::trace!("Is media type application/json? {mime_is_json:?}");
if !mime_is_json {
log::error!("MIME type `{mime_type}` is not acceptable for JSON parsing.");
return Err(ContentTypeUnsupported);
} }
log::trace!("Attempting to parse response as JSON..."); log::trace!("Attempting to parse response as JSON...");
@ -455,14 +470,22 @@ pub enum NodeInfoGetError {
/// The HTTP request failed. /// The HTTP request failed.
#[error("the HTTP request failed")] #[error("the HTTP request failed")]
Request(reqwest::Error), Request(reqwest::Error),
/// The `Content-Type` header of the response is missing. /// The `Content-Type` header of the response is missing.
#[error("the Content-Type header of the response is missing")] #[error("the Content-Type header of the response is missing")]
ContentTypeMissing, ContentTypeMissing,
/// The `Content-Type` header of the response is invalid. /// The `Content-Type` header of the response can't be converted to a [`str`].
#[error("the Content-Type header of the response is invalid")] #[error("the Content-Type header of the response cannot be converted to a &str")]
ContentTypeInvalid, ContentTypeUnprintable(reqwest::header::ToStrError),
/// The `Content-Type` header of the response is not a valid [`mime::Mime`] type.
#[error("the Content-Type header of the response is not a valid media type")]
ContentTypeInvalid(mime::FromStrError),
/// The `Content-Type` header of the response is not a supported [`mime::Mime`] type.
#[error("the Content-Type header of the response is not a supported media type")]
ContentTypeUnsupported,
/// The document failed to be parsed as JSON by [`reqwest`]. /// The document failed to be parsed as JSON by [`reqwest`].
#[error("the document failed to be parsed as JSON")] #[error("the document failed to be parsed as JSON")]
@ -472,12 +495,3 @@ pub enum NodeInfoGetError {
#[error("the returned NodeInfo version would not match the version of the called method")] #[error("the returned NodeInfo version would not match the version of the called method")]
Version, Version,
} }
/// Extract the MIME type from the value of the `Content-Type` header.
fn extract_mime_from_content_type(value: &reqwest::header::HeaderValue) -> Option<String> {
let value = value.to_str().ok()?;
match value.split_once("; ") {
None => Some(value.to_string()),
Some((mime, _)) => Some(mime.to_string()),
}
}

View file

@ -6,7 +6,7 @@ edition = "2021"
description = "Rust typing and utilities for the JSON and XML resource descriptior formats" description = "Rust typing and utilities for the JSON and XML resource descriptior formats"
repository = "https://forge.steffo.eu/unimore/tirocinio-canali-steffo-acrate" repository = "https://forge.steffo.eu/unimore/tirocinio-canali-steffo-acrate"
license = "EUPL-1.2" license = "EUPL-1.2"
keywords = ["jrd", "xrd", "hostmeta", "webfinger", "resource-descriptor"] keywords = ["jrd", "xrd", "host-meta", "webfinger", "resource-descriptor"]
categories = ["web-programming"] categories = ["web-programming"]
[dependencies] [dependencies]

View file

@ -181,7 +181,7 @@ impl ResourceDescriptorJRD {
let mime_type = mime::Mime::from_str(mime_type) let mime_type = mime::Mime::from_str(mime_type)
.map_err(ContentTypeInvalid)?; .map_err(ContentTypeInvalid)?;
log::trace!("Checking if media type is supported..."); log::trace!("Checking if media type is supported: {mime_type:?}");
let mime_is_json = mime_type == mime::APPLICATION_JSON; let mime_is_json = mime_type == mime::APPLICATION_JSON;
log::trace!("Is media type application/json? {mime_is_json:?}"); log::trace!("Is media type application/json? {mime_is_json:?}");
let mime_is_jrd = let mime_is_jrd =

View file

@ -217,9 +217,9 @@ impl ResourceDescriptorXRD {
let mime_type = mime::Mime::from_str(mime_type) let mime_type = mime::Mime::from_str(mime_type)
.map_err(ContentTypeInvalid)?; .map_err(ContentTypeInvalid)?;
log::trace!("Checking if media type is supported..."); log::trace!("Checking if media type is supported: {mime_type:?}");
let mime_is_xrd = let mime_is_xrd =
mime_type.type_() == mime::APPLICATION mime_type.type_().as_str() == mime::APPLICATION
&& mime_type.subtype() == "xrd" && mime_type.subtype() == "xrd"
&& mime_type.suffix() == Some(mime::XML); && mime_type.suffix() == Some(mime::XML);
log::trace!("Is media type application/xrd+xml? {mime_is_xrd:?}"); log::trace!("Is media type application/xrd+xml? {mime_is_xrd:?}");

View file

@ -1,11 +1,17 @@
[package] [package]
name = "acrate-webfinger" name = "acrate_rdserver"
version = "0.1.0" version = "0.3.0"
authors = ["Stefano Pigozzi <me@steffo.eu>"]
edition = "2021" edition = "2021"
description = "Resource descriptor web server for the acrate project"
repository = "https://forge.steffo.eu/unimore/tirocinio-canali-steffo-acrate"
license = "EUPL-1.2"
keywords = ["jrd", "xrd", "hostmeta", "webfinger", "resource-descriptor"]
categories = ["web-programming"]
[dependencies] [dependencies]
acrate_database = { path = "../acrate_database" } acrate_database = { path = "../acrate_database" }
acrate-hostmeta = { path = "../acrate-hostmeta" } acrate_rd = { path = "../acrate_rd" }
anyhow = "1.0.93" anyhow = "1.0.93"
axum = { version = "0.7.7", features = ["macros"] } axum = { version = "0.7.7", features = ["macros"] }
axum-extra = { version = "0.9.4", features = ["query"] } axum-extra = { version = "0.9.4", features = ["query"] }
@ -17,3 +23,7 @@ quick-xml = { version = "0.37.0", features = ["serialize"] }
serde = { version = "1.0.215", features = ["derive"] } serde = { version = "1.0.215", features = ["derive"] }
serde_json = "1.0.132" serde_json = "1.0.132"
tokio = { version = "1.41.1", features = ["macros", "net", "rt-multi-thread"] } tokio = { version = "1.41.1", features = ["macros", "net", "rt-multi-thread"] }
mime = "0.3.17"
[lints.clippy]
tabs-in-doc-comments = "allow"

View file

@ -6,8 +6,8 @@ use serde::Deserialize;
use acrate_database::diesel::GroupedBy; use acrate_database::diesel::GroupedBy;
use acrate_database::diesel_async::{AsyncConnection, AsyncPgConnection}; use acrate_database::diesel_async::{AsyncConnection, AsyncPgConnection};
use acrate_database::meta::{MetaAlias, MetaLink, MetaLinkProperty, MetaLinkTitle, MetaProperty, MetaSubject}; use acrate_database::meta::{MetaAlias, MetaLink, MetaLinkProperty, MetaLinkTitle, MetaProperty, MetaSubject};
use acrate_hostmeta::jrd::ResourceDescriptorLinkJRD; use acrate_rd::jrd::ResourceDescriptorLinkJRD;
use acrate_hostmeta::xrd::{ResourceDescriptorLinkXRD, ResourceDescriptorPropertyXRD, ResourceDescriptorTitleXRD}; use acrate_rd::xrd::{ResourceDescriptorLinkXRD, ResourceDescriptorPropertyXRD, ResourceDescriptorTitleXRD};
use crate::config; use crate::config;
#[derive(Debug, Clone, Deserialize)] #[derive(Debug, Clone, Deserialize)]
@ -47,7 +47,7 @@ pub async fn webfinger_handler(
.await .await
.map_err(|_| StatusCode::BAD_GATEWAY)?; .map_err(|_| StatusCode::BAD_GATEWAY)?;
let subjects = MetaSubject::query_matching(&mut conn, WEBFINGER_DOC, &resource) let subjects = MetaSubject::aquery_matching(&mut conn, WEBFINGER_DOC, &resource)
.await .await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
@ -76,24 +76,24 @@ pub async fn webfinger_handler(
let subject = subject.subject.clone(); let subject = subject.subject.clone();
let aliases = MetaAlias::query_matching(&mut conn, WEBFINGER_DOC, &resource) let aliases = MetaAlias::aquery_matching(&mut conn, WEBFINGER_DOC, &resource)
.await .await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
let properties = MetaProperty::query_matching(&mut conn, WEBFINGER_DOC, &resource) let properties = MetaProperty::aquery_matching(&mut conn, WEBFINGER_DOC, &resource)
.await .await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
let links = MetaLink::query_matching(&mut conn, WEBFINGER_DOC, &resource) let links = MetaLink::aquery_matching(&mut conn, WEBFINGER_DOC, &resource)
.await .await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
let link_properties = MetaLinkProperty::query_by_link(&mut conn, &links) let link_properties = MetaLinkProperty::aquery_by_link(&mut conn, &links)
.await .await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)? .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?
.grouped_by(&links); .grouped_by(&links);
let link_titles = MetaLinkTitle::query_by_link(&mut conn, &links) let link_titles = MetaLinkTitle::aquery_by_link(&mut conn, &links)
.await .await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)? .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?
.grouped_by(&links); .grouped_by(&links);
@ -143,7 +143,7 @@ pub async fn webfinger_handler(
.into_iter() .into_iter()
.map(|(link, properties, titles)| ResourceDescriptorLinkJRD { .map(|(link, properties, titles)| ResourceDescriptorLinkJRD {
rel: link.rel, rel: link.rel,
r#type: link.type_, r#type: link.type_.map(|m| m.0),
href: link.href, href: link.href,
template: link.template, template: link.template,
properties: properties properties: properties
@ -157,7 +157,7 @@ pub async fn webfinger_handler(
}) })
.collect::<Vec<ResourceDescriptorLinkJRD>>(); .collect::<Vec<ResourceDescriptorLinkJRD>>();
let rd = acrate_hostmeta::jrd::ResourceDescriptorJRD { let rd = acrate_rd::jrd::ResourceDescriptorJRD {
subject, subject,
aliases, aliases,
properties, properties,
@ -192,7 +192,7 @@ pub async fn webfinger_handler(
.into_iter() .into_iter()
.map(|(link, properties, titles)| ResourceDescriptorLinkXRD { .map(|(link, properties, titles)| ResourceDescriptorLinkXRD {
rel: link.rel, rel: link.rel,
r#type: link.type_, r#type: link.type_.map(|m| m.0),
href: link.href, href: link.href,
template: link.template, template: link.template,
properties: properties properties: properties
@ -212,7 +212,7 @@ pub async fn webfinger_handler(
}) })
.collect::<Vec<ResourceDescriptorLinkXRD>>(); .collect::<Vec<ResourceDescriptorLinkXRD>>();
let rd = acrate_hostmeta::xrd::ResourceDescriptorXRD { let rd = acrate_rd::xrd::ResourceDescriptorXRD {
subject, subject,
aliases, aliases,
properties, properties,
@ -241,12 +241,12 @@ pub async fn webfinger_handler(
}) })
.collect(); .collect();
let links: Vec<(String, Option<String>, Option<String>, Option<String>, Vec<(String, Option<String>)>, Vec<(String, String)>)> = links_full let links: Vec<(String, Option<mime::Mime>, Option<String>, Option<String>, Vec<(String, Option<String>)>, Vec<(String, String)>)> = links_full
.into_iter() .into_iter()
.map(|(link, properties, titles)| { .map(|(link, properties, titles)| {
( (
link.rel, link.rel,
link.type_, link.type_.map(|m| m.0),
link.href, link.href,
link.template, link.template,
properties.into_iter() properties.into_iter()