astreams
: the cursed has arrived (and it does not work)
This commit is contained in:
parent
55fc252981
commit
d9ad8032f0
13 changed files with 839 additions and 663 deletions
2
.idea/inspectionProfiles/Project_Default.xml
generated
2
.idea/inspectionProfiles/Project_Default.xml
generated
|
@ -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" />
|
||||
|
|
|
@ -19,7 +19,6 @@ 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"
|
||||
|
||||
|
|
|
@ -1,535 +0,0 @@
|
|||
use anyhow::{anyhow, Error};
|
||||
use static_iref::iri;
|
||||
use anyhow::Result as AResult;
|
||||
use chrono::{DateTime, FixedOffset, Local, TimeDelta};
|
||||
use iref::Iri;
|
||||
use json_ld::{BlankIdBuf, Direction, Id, IndexedObject, IriBuf, Node, Object, ValidId};
|
||||
use json_ld::object::{Any};
|
||||
use json_ld::syntax::LangTagBuf;
|
||||
use mediatype::MediaType;
|
||||
use crate::activitystreams::{StreamsCollection, StreamsEntity, StreamsImage, StreamsLink, StreamsObject};
|
||||
|
||||
pub type LangTriple = (String, Option<LangTagBuf>, Option<Direction>);
|
||||
|
||||
pub trait ProcessableJsonLD<Entity, Link> {
|
||||
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_any_value_timedelta(&self, id: &Iri) -> Option<AResult<TimeDelta>>;
|
||||
fn jsonld_any_value_datetime_local(&self, id: &Iri) -> Option<AResult<DateTime<Local>>>;
|
||||
fn jsonld_iter_node_entity(&self, id: &Iri) -> impl Iterator<Item = AResult<Entity>>;
|
||||
fn jsonld_iter_node_url(&self, id: &Iri) -> impl Iterator<Item = AResult<Link>>;
|
||||
}
|
||||
|
||||
impl ProcessableJsonLD<Node, Node> for 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_any_value_timedelta(&self, id: &Iri) -> Option<AResult<TimeDelta>> {
|
||||
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 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 jsonld_any_value_datetime_local(&self, id: &Iri) -> Option<AResult<DateTime<Local>>> {
|
||||
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 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 jsonld_iter_node_entity(&self, id: &Iri) -> impl Iterator<Item = AResult<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
|
||||
}
|
||||
|
||||
fn jsonld_iter_node_url(&self, id: &Iri) -> impl Iterator<Item = AResult<Node>> {
|
||||
let properties = self.properties.get(&id);
|
||||
|
||||
let nodes = properties.map(|v| {
|
||||
if let Some(value) = v.as_value() {
|
||||
let mut node = Node::<IriBuf, BlankIdBuf>::new();
|
||||
|
||||
let r#type = Id::Valid(
|
||||
ValidId::Iri(
|
||||
IriBuf::new(
|
||||
String::from("https://www.w3.org/ns/activitystreams#Link")
|
||||
).unwrap()
|
||||
)
|
||||
);
|
||||
|
||||
let _ = node.types_mut_or_insert(vec![
|
||||
r#type
|
||||
]);
|
||||
|
||||
let properties = node.properties_mut();
|
||||
|
||||
let key = Id::Valid(
|
||||
ValidId::Iri(
|
||||
IriBuf::new(
|
||||
String::from("https://www.w3.org/ns/activitystreams#href")
|
||||
).unwrap()
|
||||
)
|
||||
);
|
||||
|
||||
properties.insert(
|
||||
key,
|
||||
IndexedObject::none(
|
||||
Object::Value(
|
||||
value.clone()
|
||||
)
|
||||
),
|
||||
);
|
||||
|
||||
Ok(node)
|
||||
}
|
||||
else if let Some(node) = v.as_node() {
|
||||
Ok(node.clone())
|
||||
}
|
||||
else {
|
||||
Err(anyhow!("Couldn't process property not as JSON-LD url nor as JSON-LD node"))
|
||||
}
|
||||
});
|
||||
|
||||
nodes
|
||||
}
|
||||
}
|
||||
|
||||
impl StreamsEntity<Node> for Node {
|
||||
fn activitystreams_names(&self) -> impl Iterator<Item = AResult<LangTriple>> {
|
||||
self.jsonld_iter_value_langstring(
|
||||
iri!("https://www.w3.org/ns/activitystreams#name")
|
||||
)
|
||||
}
|
||||
|
||||
fn activitystreams_previews(&self) -> impl Iterator<Item = AResult<Node>> {
|
||||
self.jsonld_iter_node_entity(
|
||||
iri!("https://www.w3.org/ns/activitystreams#preview")
|
||||
)
|
||||
}
|
||||
|
||||
fn activitystreams_mediatype(&self) -> Option<AResult<MediaType>> {
|
||||
self.jsonld_any_value_mediatype(
|
||||
iri!("https://www.w3.org/ns/activitystreams#mediaType")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl StreamsObject<Node, Node, Node, Node, Node, Node, Node, Node, Node, Node, Node, Node, Node> for Node {
|
||||
fn activitystreams_attachments(&self) -> impl Iterator<Item=AResult<Node>> {
|
||||
self.jsonld_iter_node_entity(
|
||||
iri!("https://www.w3.org/ns/activitystreams#attachment")
|
||||
)
|
||||
}
|
||||
|
||||
fn activitystreams_attributedto(&self) -> impl Iterator<Item=AResult<Node>> {
|
||||
self.jsonld_iter_node_entity(
|
||||
iri!("https://www.w3.org/ns/activitystreams#attributedTo")
|
||||
)
|
||||
}
|
||||
|
||||
fn activitystreams_audiences(&self) -> impl Iterator<Item=AResult<Node>> {
|
||||
self.jsonld_iter_node_entity(
|
||||
iri!("https://www.w3.org/ns/activitystreams#audience")
|
||||
)
|
||||
}
|
||||
|
||||
fn activitystreams_content(&self) -> impl Iterator<Item=AResult<LangTriple>> {
|
||||
self.jsonld_iter_value_langstring(
|
||||
iri!("https://www.w3.org/ns/activitystreams#content")
|
||||
)
|
||||
}
|
||||
|
||||
fn activitystreams_context(&self) -> impl Iterator<Item=AResult<Node>> {
|
||||
self.jsonld_iter_node_entity(
|
||||
iri!("https://www.w3.org/ns/activitystreams#context")
|
||||
)
|
||||
}
|
||||
|
||||
fn activitystreams_endtime(&self) -> Option<AResult<DateTime<Local>>> {
|
||||
self.jsonld_any_value_datetime_local(
|
||||
iri!("https://www.w3.org/ns/activitystreams#endTime")
|
||||
)
|
||||
}
|
||||
|
||||
fn activitystreams_published(&self) -> Option<AResult<DateTime<Local>>> {
|
||||
self.jsonld_any_value_datetime_local(
|
||||
iri!("https://www.w3.org/ns/activitystreams#published")
|
||||
)
|
||||
}
|
||||
|
||||
fn activitystreams_replies(&self) -> Option<AResult<Node>> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn activitystreams_generators(&self) -> impl Iterator<Item = AResult<Node>> {
|
||||
self.jsonld_iter_node_entity(
|
||||
iri!("https://www.w3.org/ns/activitystreams#generator")
|
||||
)
|
||||
}
|
||||
|
||||
fn activitystreams_icons(&self) -> impl Iterator<Item = AResult<Node>> {
|
||||
self.jsonld_iter_node_entity(
|
||||
iri!("https://www.w3.org/ns/activitystreams#icon")
|
||||
)
|
||||
}
|
||||
|
||||
fn activitystreams_images(&self) -> impl Iterator<Item = AResult<Node>> {
|
||||
self.jsonld_iter_node_entity(
|
||||
iri!("https://www.w3.org/ns/activitystreams#image")
|
||||
)
|
||||
}
|
||||
|
||||
fn activitystreams_starttime(&self) -> Option<AResult<DateTime<Local>>> {
|
||||
self.jsonld_any_value_datetime_local(
|
||||
iri!("https://www.w3.org/ns/activitystreams#startTime")
|
||||
)
|
||||
}
|
||||
|
||||
fn activitystreams_summary(&self) -> impl Iterator<Item=AResult<LangTriple>> {
|
||||
self.jsonld_iter_value_langstring(
|
||||
iri!("https://www.w3.org/ns/activitystreams#summary")
|
||||
)
|
||||
}
|
||||
|
||||
fn activitystreams_tags(&self) -> impl Iterator<Item=AResult<Node>> {
|
||||
self.jsonld_iter_node_entity(
|
||||
iri!("https://www.w3.org/ns/activitystreams#tag")
|
||||
)
|
||||
}
|
||||
|
||||
fn activitystreams_updated(&self) -> Option<AResult<DateTime<Local>>> {
|
||||
self.jsonld_any_value_datetime_local(
|
||||
iri!("https://www.w3.org/ns/activitystreams#updated")
|
||||
)
|
||||
}
|
||||
|
||||
fn activitystreams_urls(&self) -> impl Iterator<Item=AResult<Node>> {
|
||||
self.jsonld_iter_node_url(
|
||||
iri!("https://www.w3.org/ns/activitystreams#url")
|
||||
)
|
||||
}
|
||||
|
||||
fn activitystreams_to(&self) -> impl Iterator<Item=AResult<Node>> {
|
||||
self.jsonld_iter_node_entity(
|
||||
iri!("https://www.w3.org/ns/activitystreams#to")
|
||||
)
|
||||
}
|
||||
|
||||
fn activitystreams_bto(&self) -> impl Iterator<Item=AResult<Node>> {
|
||||
self.jsonld_iter_node_entity(
|
||||
iri!("https://www.w3.org/ns/activitystreams#bto")
|
||||
)
|
||||
}
|
||||
|
||||
fn activitystreams_cc(&self) -> impl Iterator<Item=AResult<Node>> {
|
||||
self.jsonld_iter_node_entity(
|
||||
iri!("https://www.w3.org/ns/activitystreams#cc")
|
||||
)
|
||||
}
|
||||
|
||||
fn activitystreams_bcc(&self) -> impl Iterator<Item=AResult<Node>> {
|
||||
self.jsonld_iter_node_entity(
|
||||
iri!("https://www.w3.org/ns/activitystreams#bcc")
|
||||
)
|
||||
}
|
||||
|
||||
fn activitystreams_duration(&self) -> Option<AResult<TimeDelta>> {
|
||||
self.jsonld_any_value_timedelta(
|
||||
iri!("https://www.w3.org/ns/activitystreams#duration")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl StreamsLink<Node> for 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_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")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl StreamsImage<Node, Node, Node, Node, Node, Node, Node, Node, Node, Node, Node, Node, Node> for Node {}
|
||||
|
||||
impl StreamsCollection<Node, Node, Node, Node, Node, Node, Node, Node, Node, Node, Node, Node, Node> for Node {}
|
|
@ -1,121 +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 chrono::{DateTime, Local, TimeDelta};
|
||||
use json_ld::Direction;
|
||||
use json_ld::syntax::LangTagBuf;
|
||||
use mediatype::MediaType;
|
||||
|
||||
pub mod jsonld;
|
||||
|
||||
// TODO: What to do with the LangTriple? Whose responsibility is it?
|
||||
|
||||
/// Something that is either a [`StreamsObject`] or a [`StreamsLink`].
|
||||
pub trait StreamsEntity<Preview> where
|
||||
Preview: StreamsEntity<Preview>,
|
||||
{
|
||||
fn activitystreams_names(&self) -> impl Iterator<Item = AResult<(String, Option<LangTagBuf>, Option<Direction>)>>;
|
||||
fn activitystreams_previews(&self) -> impl Iterator<Item = AResult<Preview>>;
|
||||
fn activitystreams_mediatype(&self) -> Option<AResult<MediaType>>;
|
||||
}
|
||||
|
||||
/// Something that can be considered a `https://www.w3.org/ns/activitystreams#Object`.
|
||||
pub trait StreamsObject<Preview, Attachment, Attribution, Audience, Context, Collection, Generator, Image, InReplyTo, Location, Tag, Link, Target> where
|
||||
Self: StreamsEntity<Preview>,
|
||||
Preview: StreamsEntity<Preview>,
|
||||
Attachment: StreamsEntity<Preview>,
|
||||
Audience: StreamsEntity<Preview>,
|
||||
Context: StreamsEntity<Preview>,
|
||||
Collection: StreamsCollection<Preview, Attachment, Attribution, Audience, Context, Collection, Generator, Image, InReplyTo, Location, Tag, Link, Target>,
|
||||
Generator: StreamsEntity<Preview>,
|
||||
Image: StreamsImage<Preview, Attachment, Attribution, Audience, Context, Collection, Generator, Image, InReplyTo, Location, Tag, Link, Target>, // TODO: IMAGE OR LINK!!
|
||||
InReplyTo: StreamsEntity<Preview>,
|
||||
Location: StreamsEntity<Preview>,
|
||||
Tag: StreamsEntity<Preview>,
|
||||
Link: StreamsLink<Preview>,
|
||||
Target: StreamsEntity<Preview>,
|
||||
{
|
||||
fn activitystreams_attachments(&self) -> impl Iterator<Item = AResult<Attachment>>;
|
||||
fn activitystreams_attributedto(&self) -> impl Iterator<Item = AResult<Attribution>>;
|
||||
fn activitystreams_audiences(&self) -> impl Iterator<Item = AResult<Audience>>;
|
||||
fn activitystreams_content(&self) -> impl Iterator<Item = AResult<(String, Option<LangTagBuf>, Option<Direction>)>>;
|
||||
fn activitystreams_context(&self) -> impl Iterator<Item = AResult<Context>>;
|
||||
fn activitystreams_endtime(&self) -> Option<AResult<DateTime<Local>>>;
|
||||
fn activitystreams_published(&self) -> Option<AResult<DateTime<Local>>>;
|
||||
fn activitystreams_replies(&self) -> Option<AResult<Collection>>;
|
||||
fn activitystreams_generators(&self) -> impl Iterator<Item = AResult<Generator>>;
|
||||
fn activitystreams_icons(&self) -> impl Iterator<Item = AResult<Image>>;
|
||||
fn activitystreams_images(&self) -> impl Iterator<Item = AResult<Image>>;
|
||||
fn activitystreams_starttime(&self) -> Option<AResult<DateTime<Local>>>;
|
||||
fn activitystreams_summary(&self) -> impl Iterator<Item = AResult<(String, Option<LangTagBuf>, Option<Direction>)>>;
|
||||
fn activitystreams_tags(&self) -> impl Iterator<Item = AResult<Audience>>;
|
||||
fn activitystreams_updated(&self) -> Option<AResult<DateTime<Local>>>;
|
||||
fn activitystreams_urls(&self) -> impl Iterator<Item = AResult<Link>>;
|
||||
fn activitystreams_to(&self) -> impl Iterator<Item = AResult<Target>>;
|
||||
fn activitystreams_bto(&self) -> impl Iterator<Item = AResult<Target>>;
|
||||
fn activitystreams_cc(&self) -> impl Iterator<Item = AResult<Target>>;
|
||||
fn activitystreams_bcc(&self) -> impl Iterator<Item = AResult<Target>>;
|
||||
fn activitystreams_duration(&self) -> Option<AResult<TimeDelta>>;
|
||||
}
|
||||
|
||||
/// 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_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>>;
|
||||
}
|
||||
|
||||
pub trait StreamsCollection<Preview, Attachment, Attribution, Audience, Context, Collection, Generator, Image, InReplyTo, Location, Tag, Link, Target> where
|
||||
Self: StreamsObject<Preview, Attachment, Attribution, Audience, Context, Collection, Generator, Image, InReplyTo, Location, Tag, Link, Target>,
|
||||
Preview: StreamsEntity<Preview>,
|
||||
Attachment: StreamsEntity<Preview>,
|
||||
Audience: StreamsEntity<Preview>,
|
||||
Context: StreamsEntity<Preview>,
|
||||
Collection: StreamsCollection<Preview, Attachment, Attribution, Audience, Context, Collection, Generator, Image, InReplyTo, Location, Tag, Link, Target>,
|
||||
Generator: StreamsEntity<Preview>,
|
||||
Image: StreamsImage<Preview, Attachment, Attribution, Audience, Context, Collection, Generator, Image, InReplyTo, Location, Tag, Link, Target>, // TODO: IMAGE OR LINK!!
|
||||
InReplyTo: StreamsEntity<Preview>,
|
||||
Location: StreamsEntity<Preview>,
|
||||
Tag: StreamsEntity<Preview>,
|
||||
Link: StreamsLink<Preview>,
|
||||
Target: StreamsEntity<Preview>,
|
||||
{}
|
||||
|
||||
pub trait StreamsImage<Preview, Attachment, Attribution, Audience, Context, Collection, Generator, Image, InReplyTo, Location, Tag, Link, Target> where
|
||||
Self: StreamsObject<Preview, Attachment, Attribution, Audience, Context, Collection, Generator, Image, InReplyTo, Location, Tag, Link, Target>,
|
||||
Preview: StreamsEntity<Preview>,
|
||||
Attachment: StreamsEntity<Preview>,
|
||||
Audience: StreamsEntity<Preview>,
|
||||
Context: StreamsEntity<Preview>,
|
||||
Collection: StreamsCollection<Preview, Attachment, Attribution, Audience, Context, Collection, Generator, Image, InReplyTo, Location, Tag, Link, Target>,
|
||||
Generator: StreamsEntity<Preview>,
|
||||
Image: StreamsImage<Preview, Attachment, Attribution, Audience, Context, Collection, Generator, Image, InReplyTo, Location, Tag, Link, Target>, // TODO: IMAGE OR LINK!!
|
||||
InReplyTo: StreamsEntity<Preview>,
|
||||
Location: StreamsEntity<Preview>,
|
||||
Tag: StreamsEntity<Preview>,
|
||||
Link: StreamsLink<Preview>,
|
||||
Target: StreamsEntity<Preview>,
|
||||
{}
|
|
@ -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;
|
||||
|
|
535
acrate_astreams/src/linkeddata/jsonld.rs
Normal file
535
acrate_astreams/src/linkeddata/jsonld.rs
Normal 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(())
|
||||
}
|
||||
}
|
62
acrate_astreams/src/linkeddata/mod.rs
Normal file
62
acrate_astreams/src/linkeddata/mod.rs
Normal 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;
|
||||
}
|
34
acrate_astreams/src/vocabulary/activitystreams.rs
Normal file
34
acrate_astreams/src/vocabulary/activitystreams.rs
Normal 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
|
||||
[
|
||||
],
|
||||
);
|