1
0
Fork 0
forked from alemi/upub
upub/apb/src/node.rs
alemi a3cf7a17e8
feat(apb): allow upcasting from object
basically now object.as_actor() or object.as_collection() returns an
option which is full with a self ref only if the type is correct, so
that we can assume flexible json behavior from anything implementing our
apb traits, and we don't need to rely anymore on knowing the underlying
type
also little import refactor
2024-04-29 20:01:39 +02:00

183 lines
4.3 KiB
Rust

/// ActivityPub object node, representing either nothing, something, a link to something or
/// multiple things
pub enum Node<T : super::Base> {
Array(std::collections::VecDeque<T>), // TODO would be cool to make it Box<[T]> so that Node is just a ptr
Object(Box<T>),
Link(Box<dyn crate::Link + Sync + Send>), // TODO feature flag to toggle these maybe?
Empty,
}
impl<T : super::Base> From<Option<T>> for Node<T> {
fn from(value: Option<T>) -> Self {
match value {
Some(x) => Node::Object(Box::new(x)),
None => Node::Empty,
}
}
}
impl<T : super::Base + Clone> Iterator for Node<T> {
type Item = T;
fn next(&mut self) -> Option<Self::Item> {
let x = match self {
Self::Empty => return None,
Self::Link(_) => return None,
Self::Array(arr) => return arr.pop_front(), // TODO weird that we iter in reverse
Self::Object(x) => *x.clone(), // TODO needed because next() on object can't get value without owning
};
*self = Self::Empty;
Some(x)
}
}
impl<T : super::Base> Node<T> {
/// return reference to embedded object (or last if many are present)
pub fn get(&self) -> Option<&T> {
match self {
Node::Empty | Node::Link(_) => None,
Node::Object(x) => Some(x),
Node::Array(v) => v.front(),
}
}
/// consume node and extract embedded object (or last if many are present)
pub fn extract(self) -> Option<T> {
match self {
Node::Empty | Node::Link(_) => None,
Node::Object(x) => Some(*x),
Node::Array(mut v) => v.pop_front(),
}
}
/// true only if Node is empty
pub fn is_nothing(&self) -> bool {
matches!(self, Node::Empty)
}
/// true only if Node is link
pub fn is_link(&self) -> bool {
matches!(self, Node::Link(_))
}
/// true only if Node contains one embedded object
pub fn is_object(&self) -> bool {
matches!(self, Node::Object(_))
}
/// true only if Node contains many embedded objects
pub fn is_array(&self) -> bool {
matches!(self, Node::Array(_))
}
/// true only if Node is empty
pub fn is_empty(&self) -> bool {
self.len() == 0
}
/// returns number of contained items (links count as items for len)
pub fn len(&self) -> usize {
match self {
Node::Empty => 0,
Node::Link(_) => 1,
Node::Object(_) => 1,
Node::Array(v) => v.len(),
}
}
/// returns id of object: url for link, id for object, None if empty or array
pub fn id(&self) -> Option<String> {
match self {
Node::Empty | Node::Array(_) => None,
Node::Link(uri) => Some(uri.href().to_string()),
Node::Object(obj) => obj.id().map(|x| x.to_string()),
}
}
}
#[cfg(feature = "unstructured")]
impl Node<serde_json::Value> {
pub fn link(uri: String) -> Self {
Node::Link(Box::new(uri))
}
pub fn links(uris: Vec<String>) -> Self {
Node::Array(
uris
.into_iter()
.map(serde_json::Value::String)
.collect()
)
}
pub fn maybe_link(uri: Option<String>) -> Self {
match uri {
Some(uri) => Node::Link(Box::new(uri)),
None => Node::Empty,
}
}
pub fn object(x: serde_json::Value) -> Self {
Node::Object(Box::new(x))
}
pub fn maybe_object(x: Option<serde_json::Value>) -> Self {
match x {
Some(x) => Node::Object(Box::new(x)),
None => Node::Empty,
}
}
pub fn array(values: Vec<serde_json::Value>) -> Self {
Node::Array(values.into())
}
#[cfg(feature = "fetch")]
pub async fn fetch(&mut self) -> reqwest::Result<&mut Self> {
if let Node::Link(link) = self {
*self = reqwest::Client::new()
.get(link.href())
.header("Accept", "application/json")
.send()
.await?
.json::<serde_json::Value>()
.await?
.into();
}
Ok(self)
}
}
#[cfg(feature = "unstructured")]
impl From<Option<&str>> for Node<serde_json::Value> {
fn from(value: Option<&str>) -> Self {
match value {
Some(x) => Node::Link(Box::new(x.to_string())),
None => Node::Empty,
}
}
}
#[cfg(feature = "unstructured")]
impl From<&str> for Node<serde_json::Value> {
fn from(value: &str) -> Self {
Node::Link(Box::new(value.to_string()))
}
}
#[cfg(feature = "unstructured")]
impl From<serde_json::Value> for Node<serde_json::Value> {
fn from(value: serde_json::Value) -> Self {
match value {
serde_json::Value::String(uri) => Node::Link(Box::new(uri)),
serde_json::Value::Array(arr) => Node::Array(arr.into()),
serde_json::Value::Object(_) => match value.get("href") {
None => Node::Object(Box::new(value)),
Some(_) => Node::Link(Box::new(value)),
},
_ => Node::Empty,
}
}
}