1
Fork 0

Compare commits

...

5 commits

14 changed files with 1314 additions and 740 deletions

View file

@ -3,6 +3,8 @@
<option name="myName" value="Project Default" />
<inspection_tool class="DuplicatedCode" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="FunctionName" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="RsUnreachableCode" enabled="true" level="WEAK WARNING" enabled_by_default="true" editorAttributes="NOT_USED_ELEMENT_ATTRIBUTES" />
<inspection_tool class="RsUnreachablePatterns" enabled="true" level="WEAK WARNING" enabled_by_default="true" editorAttributes="NOT_USED_ELEMENT_ATTRIBUTES" />
<inspection_tool class="SillyAssignmentJS" enabled="true" level="WEAK WARNING" enabled_by_default="true" editorAttributes="INFO_ATTRIBUTES" />
<inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
<option name="processCode" value="true" />

View file

@ -19,7 +19,8 @@ serde_json = "1.0.132"
static-iref = "3.0.0"
thiserror = "2.0.3"
mediatype = { version = "0.19.18", features = ["serde"] }
language-tags = { version = "0.3.2", features = ["serde"] }
chrono = { version = "0.4.39", features = ["serde"] }
speedate = "0.15.0"
[dev-dependencies]
tokio = { version = "1.41.1", features = ["macros", "rt-multi-thread"] }

View file

@ -1,292 +0,0 @@
use anyhow::{anyhow, Error};
use static_iref::iri;
use anyhow::Result as AResult;
use iref::Iri;
use json_ld::Direction;
use json_ld::object::Any;
use json_ld::syntax::LangTagBuf;
use mediatype::MediaType;
use crate::activitystreams::{StreamsEntity, StreamsLink};
pub type LangTriple = (String, Option<LangTagBuf>, Option<Direction>);
pub trait StreamsJsonLD<Entity> {
fn jsonld_any_value_string(&self, id: &Iri) -> Option<AResult<String>>;
fn jsonld_iter_value_string(&self, id: &Iri) -> impl Iterator<Item = AResult<String>>;
fn jsonld_iter_value_langstring(&self, id: &Iri) -> impl Iterator<Item = AResult<LangTriple>>;
fn jsonld_any_value_mediatype(&self, id: &Iri) -> Option<AResult<MediaType>>;
fn jsonld_any_node_string(&self, id: &Iri) -> Option<AResult<String>>;
fn jsonld_any_value_langtag(&self, id: &Iri) -> Option<AResult<LangTagBuf>>;
fn jsonld_any_value_u32(&self, id: &Iri) -> Option<AResult<u32>>;
fn jsonld_any_value_u64(&self, id: &Iri) -> Option<AResult<u64>>;
fn jsonld_iter_node_entity(&self, id: &Iri) -> impl Iterator<Item = AResult<Entity>>;
}
impl StreamsJsonLD<json_ld::Node> for json_ld::Node {
fn jsonld_any_value_string(&self, id: &Iri) -> Option<AResult<String>> {
let property = match self.properties.get_any(&id) {
None => return None,
Some(property) => property,
};
let value = match property.as_value() {
None => return Some(Err(anyhow!("Couldn't process property as JSON-LD value"))),
Some(value) => value,
};
let r#str = match value.as_str() {
None => return Some(Err(anyhow!("Couldn't process property as JSON-LD string"))),
Some(r#str) => r#str
};
let string = r#str.to_string();
Some(Ok(string))
}
fn jsonld_iter_value_string(&self, id: &Iri) -> impl Iterator<Item = AResult<String>> {
let properties = self.properties.get(&id);
let values = properties.map(|v| v
.as_value()
.ok_or(anyhow!("Couldn't process property as JSON-LD value"))
);
let strs = values.flat_map(|v| v
.map(|v| v
.as_str()
.ok_or(anyhow!("Couldn't process property as JSON-LD string"))
)
);
let strings = strs.map(|v| v
.map(|v| v
.to_string()
)
);
strings
}
fn jsonld_iter_value_langstring(&self, id: &Iri) -> impl Iterator<Item = AResult<LangTriple>> {
let properties = self.properties.get(&id);
let values = properties.map(|v| v
.as_value()
.ok_or(anyhow!("Couldn't process property as JSON-LD value"))
);
let values = values.flat_map(|v| v
.map(|v| {
let string = v
.as_str()
.ok_or(anyhow!("Expected property to be a langString, but no string was obtained"))?
.to_string();
let lang = v
.language()
.map(|l| l
.as_well_formed()
.ok_or(anyhow!("Expected property to have a valid language tag, but got an invalid one instead"))
);
let lang = match lang {
None => None,
Some(Ok(lang)) => Some(lang.to_owned()),
Some(Err(err)) => return Err(err),
};
let direction = v.direction();
Ok((string, lang, direction))
})
);
values
}
fn jsonld_any_value_mediatype(&self, id: &Iri) -> Option<AResult<MediaType>> {
let property = match self.properties.get_any(&id) {
None => return None,
Some(property) => property,
};
let value = match property.as_value() {
None => return Some(Err(anyhow!("Couldn't process property as JSON-LD value"))),
Some(value) => value,
};
let r#str = match value.as_str() {
None => return Some(Err(anyhow!("Couldn't process property as JSON-LD string"))),
Some(r#str) => r#str
};
let mediatype = match MediaType::parse(r#str) {
Err(e) => return Some(Err(Error::from(e).context("Couldn't parse property as MIME media type"))),
Ok(mediatype) => mediatype,
};
Some(Ok(mediatype))
}
fn jsonld_any_node_string(&self, id: &Iri) -> Option<AResult<String>> {
let property = match self.properties.get_any(&id) {
None => return None,
Some(property) => property,
};
let node = match property.as_node() {
None => return Some(Err(anyhow!("Couldn't process property as JSON-LD node"))),
Some(value) => value,
};
let id = match node.id() {
None => return Some(Err(anyhow!("Couldn't process property's JSON-LD node @id"))),
Some(id) => id
};
let string = id.to_string();
Some(Ok(string))
}
fn jsonld_any_value_langtag(&self, id: &Iri) -> Option<AResult<LangTagBuf>> {
let property = match self.properties.get_any(&id) {
None => return None,
Some(property) => property,
};
let value = match property.as_value() {
None => return Some(Err(anyhow!("Couldn't process property as JSON-LD value"))),
Some(value) => value,
};
let r#str = match value.as_str() {
None => return Some(Err(anyhow!("Couldn't process property as JSON-LD string"))),
Some(r#str) => r#str
};
let string = r#str.to_string();
let langtag = match LangTagBuf::new(string) {
Err(e) => return Some(Err(anyhow!("Couldn't process property as a BCP47 language tag: {e:#?}"))),
Ok(langtag) => langtag,
};
Some(Ok(langtag))
}
fn jsonld_any_value_u32(&self, id: &Iri) -> Option<AResult<u32>> {
let property = match self.properties.get_any(&id) {
None => return None,
Some(property) => property,
};
let value = match property.as_value() {
None => return Some(Err(anyhow!("Couldn't process property as JSON-LD value"))),
Some(value) => value,
};
let number = match value.as_number() {
None => return Some(Err(anyhow!("Couldn't process property as JSON-LD number"))),
Some(number) => number
};
let r#u32 = match number.as_u32() {
None => return Some(Err(anyhow!("Couldn't losslessly convert JSON-LD number to u64"))),
Some(r#u32) => r#u32,
};
Some(Ok(r#u32))
}
fn jsonld_any_value_u64(&self, id: &Iri) -> Option<AResult<u64>> {
let property = match self.properties.get_any(&id) {
None => return None,
Some(property) => property,
};
let value = match property.as_value() {
None => return Some(Err(anyhow!("Couldn't process property as JSON-LD value"))),
Some(value) => value,
};
let number = match value.as_number() {
None => return Some(Err(anyhow!("Couldn't process property as JSON-LD number"))),
Some(number) => number
};
let r#u64 = match number.as_u64() {
None => return Some(Err(anyhow!("Couldn't losslessly convert JSON-LD number to u64"))),
Some(r#u64) => r#u64,
};
Some(Ok(r#u64))
}
fn jsonld_iter_node_entity(&self, id: &Iri) -> impl Iterator<Item = AResult<json_ld::Node>> {
let properties = self.properties.get(&id);
let nodes = properties.map(|v| v
.as_node()
.ok_or(anyhow!("Couldn't process property as JSON-LD node"))
.map(|v| v
.clone()
)
);
nodes
}
}
impl StreamsEntity<json_ld::Node> for json_ld::Node {
fn activitystreams_previews(&self) -> impl Iterator<Item = AResult<json_ld::Node>> {
self.jsonld_iter_node_entity(
iri!("https://www.w3.org/ns/activitystreams#preview")
)
}
}
impl StreamsLink<json_ld::Node> for json_ld::Node {
fn activitystreams_href(&self) -> Option<AResult<String>> {
self.jsonld_any_node_string(
iri!("https://www.w3.org/ns/activitystreams#href")
)
}
fn activitystreams_rels_lenient(&self) -> impl Iterator<Item = AResult<String>> {
self.jsonld_iter_value_string(
iri!("https://www.w3.org/ns/activitystreams#rel")
)
}
fn activitystreams_mediatype(&self) -> Option<AResult<MediaType>> {
self.jsonld_any_value_mediatype(
iri!("https://www.w3.org/ns/activitystreams#mediaType")
)
}
fn activitystreams_names(&self) -> impl Iterator<Item = AResult<LangTriple>> {
self.jsonld_iter_value_langstring(
iri!("https://www.w3.org/ns/activitystreams#name")
)
}
fn activitystreams_hreflang(&self) -> Option<AResult<LangTagBuf>> {
self.jsonld_any_value_langtag(
iri!("https://www.w3.org/ns/activitystreams#hreflang")
)
}
fn activitystreams_height(&self) -> Option<AResult<u64>> {
self.jsonld_any_value_u64(
iri!("https://www.w3.org/ns/activitystreams#height")
)
}
fn activitystreams_width(&self) -> Option<AResult<u64>> {
self.jsonld_any_value_u64(
iri!("https://www.w3.org/ns/activitystreams#width")
)
}
}

View file

@ -1,54 +0,0 @@
//! Struct definitions for ActivityStreams Core and Extended Types.
//!
//! # Functional
//!
//! > Properties marked as being "Functional" can have only one value.
//! >
//! > Items not marked as "Functional" can have multiple values.
//!
//! # Specification
//!
//! - <https://www.w3.org/TR/activitystreams-vocabulary/>
//!
use anyhow::Result as AResult;
use json_ld::Direction;
use json_ld::syntax::LangTagBuf;
use mediatype::MediaType;
pub mod jsonld;
/// Something that is either a [`StreamsObject`] or a [`StreamsLink`].
pub trait StreamsEntity<Preview> {
fn activitystreams_previews(&self) -> impl Iterator<Item = AResult<Preview>>;
}
/// Something that can be considered a `https://www.w3.org/ns/activitystreams#Object`.
pub trait StreamsObject<Preview> where
Self: StreamsEntity<Preview>,
{
}
/// Something that can be considered a `https://www.w3.org/ns/activitystreams#Link`.
pub trait StreamsLink<Preview> where
Self: StreamsEntity<Preview>,
Preview: StreamsEntity<Preview>,
{
fn activitystreams_href(&self) -> Option<AResult<String>>;
// FIXME: This accepts any kind of string, and does not filter to HTML link relations
fn activitystreams_rels_lenient(&self) -> impl Iterator<Item = AResult<String>>;
fn activitystreams_mediatype(&self) -> Option<AResult<MediaType>>;
fn activitystreams_names(&self) -> impl Iterator<Item = AResult<(String, Option<LangTagBuf>, Option<Direction>)>>;
fn activitystreams_hreflang(&self) -> Option<AResult<LangTagBuf>>;
// FIXME: This doesn't accept numbers greater than u64
fn activitystreams_height(&self) -> Option<AResult<u64>>;
// FIXME: This doesn't accept numbers greater than u64
fn activitystreams_width(&self) -> Option<AResult<u64>>;
}

View file

@ -1,6 +1,2 @@
pub mod activitystreams;
pub mod mastodon;
pub mod miajetzt;
pub mod litepub;
pub mod lemmy;
pub mod linkeddata;
pub mod vocabulary;

View file

@ -0,0 +1,535 @@
use anyhow::{anyhow, Context, Error};
use chrono::{DateTime, FixedOffset, Local, TimeDelta};
use iref::IriBuf;
use json_ld::{Id, Indexed, Node, Object, ValidId};
use json_ld::Value as CoreValue;
use json_ld::syntax::Value as SynValue;
use json_ld::syntax::String as SynString;
use json_ld::syntax::NumberBuf as SynNumber;
use json_ld::object::{Any};
use json_ld::object::node::Multiset;
use json_ld::syntax::LangTagBuf;
use mediatype::MediaType;
use super::{LangDir, LangString, LinkedData, ResultGetMany, ResultGetOne, ResultSetMany, ResultSetOne};
impl LinkedData for Node {
fn ld_has_type(&self, id: &Id) -> bool {
self.has_type(id)
}
fn ld_get_one_string(&self, id: &Id) -> ResultGetOne<String> {
let property = self.properties.get_any(id)?;
let value = match property.as_value() {
None => return Some(Err(anyhow!("Couldn't process property as JSON-LD value"))),
Some(value) => value,
};
let r#str = match value.as_str() {
None => return Some(Err(anyhow!("Couldn't process property as JSON-LD string"))),
Some(r#str) => r#str
};
let string = r#str.to_string();
Some(Ok(string))
}
fn ld_get_id_string(&self, id: &Id) -> ResultGetOne<String> {
let property = self.properties.get_any(id)?;
let node = match property.as_node() {
None => return Some(Err(anyhow!("Couldn't process property as JSON-LD node"))),
Some(value) => value,
};
let id = match node.id() {
None => return Some(Err(anyhow!("Couldn't process property's JSON-LD node @id"))),
Some(id) => id
};
let string = id.to_string();
Some(Ok(string))
}
fn ld_get_one_mediatype(&self, id: &Id) -> ResultGetOne<MediaType> {
let property = self.properties.get_any(id)?;
let value = match property.as_value() {
None => return Some(Err(anyhow!("Couldn't process property as JSON-LD value"))),
Some(value) => value,
};
let r#str = match value.as_str() {
None => return Some(Err(anyhow!("Couldn't process property as JSON-LD string"))),
Some(r#str) => r#str
};
let mediatype = match MediaType::parse(r#str) {
Err(e) => return Some(Err(Error::from(e).context("Couldn't parse property as MIME media type"))),
Ok(mediatype) => mediatype,
};
Some(Ok(mediatype))
}
fn ld_get_one_langtag(&self, id: &Id) -> ResultGetOne<LangTagBuf> {
let property = self.properties.get_any(id)?;
let value = match property.as_value() {
None => return Some(Err(anyhow!("Couldn't process property as JSON-LD value"))),
Some(value) => value,
};
let r#str = match value.as_str() {
None => return Some(Err(anyhow!("Couldn't process property as JSON-LD string"))),
Some(r#str) => r#str
};
let string = r#str.to_string();
let langtag = match LangTagBuf::new(string) {
Err(e) => return Some(Err(anyhow!("Couldn't process property as a BCP47 language tag: {e:#?}"))),
Ok(langtag) => langtag,
};
Some(Ok(langtag))
}
fn ld_get_one_u32(&self, id: &Id) -> ResultGetOne<u32> {
let property = self.properties.get_any(id)?;
let value = match property.as_value() {
None => return Some(Err(anyhow!("Couldn't process property as JSON-LD value"))),
Some(value) => value,
};
let number = match value.as_number() {
None => return Some(Err(anyhow!("Couldn't process property as JSON-LD number"))),
Some(number) => number
};
let r#u32 = match number.as_u32() {
None => return Some(Err(anyhow!("Couldn't losslessly convert JSON-LD number to u64"))),
Some(r#u32) => r#u32,
};
Some(Ok(r#u32))
}
fn ld_get_one_u64(&self, id: &Id) -> ResultGetOne<u64> {
let property = self.properties.get_any(id)?;
let value = match property.as_value() {
None => return Some(Err(anyhow!("Couldn't process property as JSON-LD value"))),
Some(value) => value,
};
let number = match value.as_number() {
None => return Some(Err(anyhow!("Couldn't process property as JSON-LD number"))),
Some(number) => number
};
let r#u64 = match number.as_u64() {
None => return Some(Err(anyhow!("Couldn't losslessly convert JSON-LD number to u64"))),
Some(r#u64) => r#u64,
};
Some(Ok(r#u64))
}
fn ld_get_one_timedelta(&self, id: &Id) -> ResultGetOne<TimeDelta> {
let property = self.properties.get_any(id)?;
let value = match property.as_value() {
None => return Some(Err(anyhow!("Couldn't process property as JSON-LD value"))),
Some(value) => value,
};
let r#str = match value.as_str() {
None => return Some(Err(anyhow!("Couldn't process property as JSON-LD string"))),
Some(r#str) => r#str
};
let duration_sd = match speedate::Duration::parse_str(r#str) {
Err(e) => return Some(Err(anyhow!("Couldn't process property as JSON-LD duration: {e:?}"))),
Ok(duration_sd) => duration_sd,
};
let duration_s = duration_sd.signed_total_seconds();
let duration_us = duration_sd.microsecond;
let duration_chrono = match TimeDelta::new(duration_s, duration_us) {
None => return Some(Err(anyhow!("Couldn't convert speedate duration to chrono duration because chrono considers the duration to be out-of-bounds"))),
Some(duration_chrono) => duration_chrono
};
Some(Ok(duration_chrono))
}
fn ld_get_one_datetime_local(&self, id: &Id) -> ResultGetOne<DateTime<Local>> {
let property = self.properties.get_any(id)?;
let value = match property.as_value() {
None => return Some(Err(anyhow!("Couldn't process property as JSON-LD value"))),
Some(value) => value,
};
let r#str = match value.as_str() {
None => return Some(Err(anyhow!("Couldn't process property as JSON-LD string"))),
Some(r#str) => r#str
};
let datetime_fixed = match DateTime::<FixedOffset>::parse_from_rfc3339(r#str) {
Err(e) => return Some(Err(anyhow!("Couldn't process property as JSON-LD dateTime: {e:?}"))),
Ok(datetime_fixed) => datetime_fixed
};
let datetime_local: DateTime<Local> = datetime_fixed.into();
Some(Ok(datetime_local))
}
fn ld_get_many_strings(&self, id: &Id) -> ResultGetMany<String> {
let properties = self.properties.get(id);
let values = properties.map(|v| v
.as_value()
.ok_or(anyhow!("Couldn't process property as JSON-LD value"))
);
let strs = values.flat_map(|v| v
.map(|v| v
.as_str()
.ok_or(anyhow!("Couldn't process property as JSON-LD string"))
)
);
let strings = strs.map(|v| v
.map(|v| v
.to_string()
)
);
strings.collect()
}
fn ld_get_many_langstrings(&self, id: &Id) -> ResultGetMany<LangString> {
let properties = self.properties.get(id);
let values = properties.map(|v| v
.as_value()
.ok_or(anyhow!("Couldn't process property as JSON-LD value"))
);
let values = values.flat_map(|v| v
.map(|v| {
let string = v
.as_str()
.ok_or(anyhow!("Expected property to be a langString, but no string was obtained"))?
.to_string();
let language = v
.language()
.map(|l| l
.as_well_formed()
.ok_or(anyhow!("Expected property to have a valid language tag, but got an invalid one instead"))
);
let language = match language {
None => None,
Some(Ok(lang)) => Some(lang.to_owned()),
Some(Err(err)) => return Err(err),
};
let direction = v.direction();
let langdir = match (language, direction) {
(None, None) => return Err(anyhow!("Expected property to have either a valid language tag, or a valid direction")),
(Some(language), None) => LangDir::Language(language),
(None, Some(direction)) => LangDir::Direction(direction),
(Some(language), Some(direction)) => LangDir::Both(language, direction),
};
Ok((string, langdir))
})
);
values.collect()
}
fn ld_get_many_self(&self, id: &Id) -> ResultGetMany<&Self> {
let properties = self.properties.get(id);
let nodes = properties.map(|v| v
.as_node()
.ok_or(anyhow!("Couldn't process property as JSON-LD node"))
)
.collect();
nodes
}
fn ld_get_many_self_mut(&mut self, id: &Id) -> ResultGetMany<&mut Self> {
let properties = self.properties_mut();
// TODO: Replace with a get_mut or similar when available
let nodes = properties
.iter_mut()
.filter(|(cid, _)| cid == &id)
.flat_map(|(_, prop)| prop
.iter_mut()
.map(|obj| obj
.as_node_mut()
.ok_or(anyhow!("Couldn't process property as JSON-LD node"))
)
)
.collect();
nodes
}
fn ld_set_one_string(&mut self, id: Id, value: String) -> ResultSetOne {
let string = SynString::from(value);
let json = SynValue::String(string);
let value = CoreValue::Json(json);
let object: Object = Object::Value(value);
let indexed_object: Indexed<Object> = Indexed::from(object);
let prop_objects: Multiset<Indexed<Object>> = Multiset::singleton(indexed_object);
self.properties.set(id, prop_objects);
Ok(())
}
fn ld_set_id_string(&mut self, id: Id, value: String) -> ResultSetOne {
let value_iri = IriBuf::new(value)
.context("Couldn't convert string into an IRI")?;
let valid_value_id = ValidId::Iri(value_iri);
let value_id = Id::Valid(valid_value_id);
let mut node = Node::new();
node.id = Some(value_id);
let boxed_node = Box::new(node);
let object: Object = Object::Node(boxed_node);
let indexed_object: Indexed<Object> = Indexed::from(object);
let prop_objects: Multiset<Indexed<Object>> = Multiset::singleton(indexed_object);
self.properties.set(id, prop_objects);
Ok(())
}
fn ld_set_one_mediatype(&mut self, id: Id, value: MediaType) -> ResultSetOne {
let stringified = value.to_string();
let string = SynString::from(stringified);
let json = SynValue::String(string);
let value = CoreValue::Json(json);
let object: Object = Object::Value(value);
let indexed_object: Indexed<Object> = Indexed::from(object);
let prop_objects: Multiset<Indexed<Object>> = Multiset::singleton(indexed_object);
self.properties.set(id, prop_objects);
Ok(())
}
fn ld_set_one_langtag(&mut self, id: Id, value: LangTagBuf) -> ResultSetOne {
let stringified = value.as_str();
let string = SynString::from(stringified);
let json = SynValue::String(string);
let value = CoreValue::Json(json);
let object: Object = Object::Value(value);
let indexed_object: Indexed<Object> = Indexed::from(object);
let prop_objects: Multiset<Indexed<Object>> = Multiset::singleton(indexed_object);
self.properties.set(id, prop_objects);
Ok(())
}
fn ld_set_one_u32(&mut self, id: Id, value: u32) -> ResultSetOne {
let number = SynNumber::from(value);
let json = SynValue::Number(number);
let value = CoreValue::Json(json);
let object: Object = Object::Value(value);
let indexed_object: Indexed<Object> = Indexed::from(object);
let prop_objects: Multiset<Indexed<Object>> = Multiset::singleton(indexed_object);
self.properties.set(id, prop_objects);
Ok(())
}
fn ld_set_one_u64(&mut self, id: Id, value: u64) -> ResultSetOne {
let number = SynNumber::from(value);
let json = SynValue::Number(number);
let value = CoreValue::Json(json);
let object: Object = Object::Value(value);
let indexed_object: Indexed<Object> = Indexed::from(object);
let prop_objects: Multiset<Indexed<Object>> = Multiset::singleton(indexed_object);
self.properties.set(id, prop_objects);
Ok(())
}
fn ld_set_one_timedelta(&mut self, id: Id, value: TimeDelta) -> ResultSetOne {
let total_seconds = value.num_seconds();
if total_seconds.is_negative() {
return Err(anyhow!("Negative durations aren't supported by JSON-LD"))
}
let stringified = format!("PT{total_seconds}S");
let string = SynString::from(stringified);
let json = SynValue::String(string);
let value = CoreValue::Json(json);
let object: Object = Object::Value(value);
let indexed_object: Indexed<Object> = Indexed::from(object);
let prop_objects: Multiset<Indexed<Object>> = Multiset::singleton(indexed_object);
self.properties.set(id, prop_objects);
Ok(())
}
fn ld_set_one_datetime_local(&mut self, id: Id, value: DateTime<Local>) -> ResultSetOne {
let stringified = value.to_rfc3339();
let string = SynString::from(stringified);
let json = SynValue::String(string);
let value = CoreValue::Json(json);
let object: Object = Object::Value(value);
let indexed_object: Indexed<Object> = Indexed::from(object);
let prop_objects: Multiset<Indexed<Object>> = Multiset::singleton(indexed_object);
self.properties.set(id, prop_objects);
Ok(())
}
fn ld_set_many_strings(&mut self, id: Id, values: Vec<String>) -> ResultSetMany {
let indexed_objects = values
.into_iter()
.map(|value| {
let string = SynString::from(value);
let json = SynValue::String(string);
let value = CoreValue::Json(json);
let object: Object = Object::Value(value);
let indexed_object: Indexed<Object> = Indexed::from(object);
indexed_object
});
let prop_objects: Multiset<Indexed<Object>> = Multiset::from_iter(indexed_objects);
self.properties.set(id, prop_objects);
Ok(())
}
fn ld_set_many_langstrings(&mut self, id: Id, values: Vec<LangString>) -> ResultSetMany {
let indexed_objects = values
.into_iter()
.map(|(string, langdir)| {
let string = SynString::from(string);
let (language, direction) = match langdir {
LangDir::Language(language) => (Some(language), None),
LangDir::Direction(direction) => (None, Some(direction)),
LangDir::Both(language, direction) => (Some(language), Some(direction))
};
let language = language.map(|l| l.into());
let langstring = json_ld::LangString::new(string, language, direction)
.unwrap(); // We've enforced the language and direction constraints via enum.
let value = CoreValue::LangString(langstring);
let object: Object = Object::Value(value);
let indexed_object: Indexed<Object> = Indexed::from(object);
indexed_object
});
let prop_objects: Multiset<Indexed<Object>> = Multiset::from_iter(indexed_objects);
self.properties.set(id, prop_objects);
Ok(())
}
fn ld_set_many_self(&mut self, id: Id, values: Vec<Self>) -> ResultSetMany {
let indexed_objects = values
.into_iter()
.map(|value| {
let boxed_node = Box::new(value);
let object: Object = Object::Node(boxed_node);
let indexed_object: Indexed<Object> = Indexed::from(object);
indexed_object
});
let prop_objects: Multiset<Indexed<Object>> = Multiset::from_iter(indexed_objects);
self.properties.set(id, prop_objects);
Ok(())
}
}

View file

@ -0,0 +1,62 @@
use chrono::{DateTime, Local, TimeDelta};
use json_ld::syntax::LangTagBuf;
use mediatype::MediaType;
use anyhow::Result as AResult;
use json_ld::{Direction, Id};
mod jsonld;
#[macro_export]
macro_rules! iri_id {
($iri:literal) => {
json_ld::Id::Valid(
json_ld::ValidId::Iri(
iri!($iri).to_owned()
)
)
};
}
pub enum LangDir {
Language(LangTagBuf),
Direction(Direction),
Both(LangTagBuf, Direction),
}
pub type LangString = (String, LangDir);
pub type ResultGetOne<T> = Option<AResult<T>>;
pub type ResultGetMany<T> = Vec<AResult<T>>;
pub type ResultSetOne = AResult<()>;
pub type ResultSetMany = AResult<()>;
/// Something that has IRI-indexed properties.
pub trait LinkedData: Sized
{
fn ld_has_type(&self, id: &Id) -> bool;
fn ld_get_one_string(&self, id: &Id) -> ResultGetOne<String>;
fn ld_get_id_string(&self, id: &Id) -> ResultGetOne<String>;
fn ld_get_one_mediatype(&self, id: &Id) -> ResultGetOne<MediaType>;
fn ld_get_one_langtag(&self, id: &Id) -> ResultGetOne<LangTagBuf>;
fn ld_get_one_u32(&self, id: &Id) -> ResultGetOne<u32>;
fn ld_get_one_u64(&self, id: &Id) -> ResultGetOne<u64>;
fn ld_get_one_timedelta(&self, id: &Id) -> ResultGetOne<TimeDelta>;
fn ld_get_one_datetime_local(&self, id: &Id) -> ResultGetOne<DateTime<Local>>;
fn ld_get_many_strings(&self, id: &Id) -> ResultGetMany<String>;
fn ld_get_many_langstrings(&self, id: &Id) -> ResultGetMany<LangString>;
fn ld_get_many_self(&self, id: &Id) -> ResultGetMany<&Self>;
fn ld_get_many_self_mut(&mut self, id: &Id) -> ResultGetMany<&mut Self>;
fn ld_set_one_string(&mut self, id: Id, value: String) -> ResultSetOne;
fn ld_set_id_string(&mut self, id: Id, value: String) -> ResultSetOne;
fn ld_set_one_mediatype(&mut self, id: Id, value: MediaType) -> ResultSetOne;
fn ld_set_one_langtag(&mut self, id: Id, value: LangTagBuf) -> ResultSetOne;
fn ld_set_one_u32(&mut self, id: Id, value: u32) -> ResultSetOne;
fn ld_set_one_u64(&mut self, id: Id, value: u64) -> ResultSetOne;
fn ld_set_one_timedelta(&mut self, id: Id, value: TimeDelta) -> ResultSetOne;
fn ld_set_one_datetime_local(&mut self, id: Id, value: DateTime<Local>) -> ResultSetOne;
fn ld_set_many_strings(&mut self, id: Id, values: Vec<String>) -> ResultSetMany;
fn ld_set_many_langstrings(&mut self, id: Id, values: Vec<LangString>) -> ResultSetMany;
fn ld_set_many_self(&mut self, id: Id, values: Vec<Self>) -> ResultSetMany;
}

View file

@ -0,0 +1,34 @@
//! Struct definitions for ActivityStreams Core and Extended Types.
//!
//! # Functional
//!
//! > Properties marked as being "Functional" can have only one value.
//! >
//! > Items not marked as "Functional" can have multiple values.
//!
//! # Specification
//!
//! - <https://www.w3.org/TR/activitystreams-vocabulary/>
//!
use static_iref::iri;
use crate::{iri_id, vocab};
use chrono::TimeDelta;
vocab!(
"https://www.w3.org/ns/activitystreams#Object",
ref Object,
mut ObjectMut,
one
[
{
"https://www.w3.org/ns/activitystreams#duration",
duration: TimeDelta,
get ld_get_one_timedelta,
put ld_set_one_timedelta,
}
],
many
[
],
);

View file

@ -0,0 +1,204 @@
pub mod activitystreams;
pub mod mastodon;
pub mod miajetzt;
pub mod litepub;
pub mod lemmy;
#[macro_export]
macro_rules! vocab {
(
$vocab_iri:literal,
$( #[ $vocab_ref_meta:meta ] )*
ref $vocab_ref:ident,
$( #[ $vocab_mut_meta:meta ] )*
mut $vocab_mut:ident,
$(
one [
$(
{
$one_iri:literal,
$one:ident: $one_type:ty,
$( #[ $one_get_meta:meta ] )*
get $one_get:ident,
$( #[ $one_put_meta:meta ] )*
put $one_put:ident,
}
),*
],
)?
$(
many [
$(
{
$many_iri:literal,
$many:ident: $many_type:ty,
$( #[ $many_get_meta:meta ] )*
get $many_get:ident,
$( #[ $many_patch_meta:meta ] )*
patch $many_patch:ident,
$( #[ $many_put_meta:meta ] )*
put $many_put:ident,
}
),*
],
)?
) => {
$( #[ $vocab_ref_meta ] )*
#[repr(transparent)]
pub struct $vocab_ref<'b, B>
where B: $crate::linkeddata::LinkedData
{
backend: &'b B
}
$( #[ $vocab_mut_meta ] )*
#[repr(transparent)]
pub struct $vocab_mut<'b, B>
where B: $crate::linkeddata::LinkedData
{
backend: &'b mut B
}
impl<'b, B> TryFrom<&'b B> for $vocab_ref<'b, B>
where B: $crate::linkeddata::LinkedData
{
type Error = anyhow::Error;
fn try_from(value: &'b B) -> Result<Self, Self::Error> {
match value.ld_has_type( &iri_id!($vocab_iri) ) {
false => Err(anyhow::anyhow!("Required JSON-LD type is missing: {}", $vocab_iri)),
true => Ok(Self { backend: value }),
}
}
}
impl<'b, B> TryFrom<&'b mut B> for $vocab_mut<'b, B>
where B: $crate::linkeddata::LinkedData
{
type Error = anyhow::Error;
fn try_from(value: &'b mut B) -> Result<Self, Self::Error> {
match value.ld_has_type( &iri_id!($vocab_iri) ) {
false => Err(anyhow::anyhow!("Required JSON-LD type is missing: {}", $vocab_iri)),
true => Ok(Self { backend: value }),
}
}
}
impl<'b, B> From<$vocab_mut<'b, B>> for $vocab_ref<'b, B>
where B: $crate::linkeddata::LinkedData
{
fn from(value: $vocab_mut<'b, B>) -> Self {
Self {
backend: value.backend
}
}
}
impl<'b, B> From<&'b B> for $vocab_ref<'b, B>
where B: $crate::linkeddata::LinkedData
{
fn from(value: &'b B) -> Self {
Self {
backend: value
}
}
}
impl<'b, B> From<&'b mut B> for $vocab_mut<'b, B>
where B: $crate::linkeddata::LinkedData
{
fn from(value: &'b mut B) -> Self {
Self {
backend: value
}
}
}
impl<'b, B> From<$vocab_ref<'b, B>> for &'b B
where B: $crate::linkeddata::LinkedData
{
fn from(value: $vocab_ref<'b, B>) -> Self {
value.backend
}
}
impl<'b, B> From<$vocab_mut<'b, B>> for &'b mut B
where B: $crate::linkeddata::LinkedData
{
fn from(value: $vocab_mut<'b, B>) -> Self {
value.backend
}
}
$(
impl<'b, B> $vocab_ref<'b, B>
where B: $crate::linkeddata::LinkedData
{
$(
$( #[ $one_get_meta ] )*
fn $one(&'b self) -> $crate::linkeddata::ResultGetOne<$one_type> {
self.backend.$one_get(
&iri_id!( $one_iri )
)
}
),*
}
impl<'b, B> $vocab_mut<'b, B>
where B: $crate::linkeddata::LinkedData
{
$(
$( #[ $one_put_meta ] )*
fn $one(&'b self, value: $one_type) -> $crate::linkeddata::ResultSetOne {
self.backend.$one_put(
iri_id!( $one_iri ),
value
)
}
),*
}
)?
$(
impl<'b, B> $vocab_ref<'b, B>
where B: $crate::linkeddata::LinkedData
{
$(
$( #[ $many_get_meta ] )*
fn $many(&'b self) -> $crate::linkeddata::ResultGetMany<$many_type> {
self.backend.$many_get(
&iri_id!( $many_iri )
)
}
),*
}
impl<'b, B> $vocab_mut<'b, B>
where B: $crate::linkeddata::LinkedData
{
$(
$( #[ $many_put_meta ] )*
fn $many(&'b self, value: Vec<$many_type>) -> $crate::linkeddata::ResultSetMany {
self.backend.$many_put(
iri_id!( $many_iri ),
value
)
}
),*
}
)?
};
}

View file

@ -1,408 +1,494 @@
use json_ld::syntax::LangTagBuf;
use acrate_astreams::activitystreams::jsonld::LangTriple;
macro_rules! test_example {
($modname:ident, $filename:literal) => {
mod $modname {
use json_ld::syntax::Parse;
use json_ld::Expand;
macro_rules! fixture_example {
($filename:literal) => {
use json_ld::syntax::Parse;
use json_ld::Expand;
pub const DATA: &'static str = include_str!($filename);
pub fn get_parsed_document() -> json_ld::RemoteDocument {
let (value, _codemap) = json_ld::syntax::Value::parse_str(DATA).expect("Failed to parse example");
pub const DATA: &'static str = include_str!($filename);
let document = json_ld::RemoteDocument::new(
None,
None,
value,
);
pub fn parse() -> json_ld::syntax::Value {
let (value, _codemap) = json_ld::syntax::Value::parse_str(DATA)
.expect("Failed to parse example");
value
}
document
}
pub async fn get_expanded_document() -> json_ld::ExpandedDocument {
let mut loader = json_ld::ReqwestLoader::new();
let doc = get_parsed_document();
let expanded = doc.expand(&mut loader)
.await
.expect("Failed to expand JSON-LD document");
pub fn document() -> json_ld::RemoteDocument {
let document = json_ld::RemoteDocument::new(
None,
None,
parse(),
);
document
}
pub async fn expand() -> json_ld::ExpandedDocument {
let mut loader = json_ld::ReqwestLoader::new();
let doc = document();
let expanded = doc.expand(&mut loader)
.await
.expect("Failed to expand JSON-LD document");
expanded
}
#[tokio::test]
async fn test_expand() {
let doc = expand().await;
println!("{doc:#?}");
}
expanded
}
#[test]
fn parse() {
let doc = get_parsed_document();
println!("{doc:#?}");
}
#[tokio::test]
async fn expand() {
let doc = get_expanded_document().await;
println!("{doc:#?}");
}
}
}
test_example!(e001, "./activitystreams/examples/1.json");
test_example!(e002, "./activitystreams/examples/2.json");
test_example!(e003, "./activitystreams/examples/3.json");
test_example!(e004, "./activitystreams/examples/4.json");
test_example!(e005, "./activitystreams/examples/5.json");
test_example!(e006, "./activitystreams/examples/6.json");
test_example!(e007, "./activitystreams/examples/7.json");
test_example!(e008, "./activitystreams/examples/8.json");
test_example!(e009, "./activitystreams/examples/9.json");