diff --git a/apb/src/node.rs b/apb/src/node.rs index 58e8a451..56cbcc2d 100644 --- a/apb/src/node.rs +++ b/apb/src/node.rs @@ -102,7 +102,7 @@ impl Node { pub fn id(&self) -> crate::Field<&str> { match self { Node::Empty => Err(crate::FieldErr("id")), - Node::Link(uri) => Ok(uri.href()), + Node::Link(uri) => uri.href(), Node::Object(obj) => obj.id(), Node::Array(arr) => arr.front().map(|x| x.id()).ok_or(crate::FieldErr("id"))?, } @@ -111,7 +111,7 @@ impl Node { pub fn all_ids(&self) -> Vec { match self { Node::Empty => vec![], - Node::Link(uri) => vec![uri.href().to_string()], + Node::Link(uri) => uri.href().map(|x| vec![x.to_string()]).unwrap_or_default(), Node::Object(x) => x.id().map_or(vec![], |x| vec![x.to_string()]), Node::Array(x) => x.iter().filter_map(|x| Some(x.id().ok()?.to_string())).collect() } @@ -236,7 +236,7 @@ impl From> for serde_json::Value { fn from(value: Node) -> Self { match value { Node::Empty => serde_json::Value::Null, - Node::Link(l) => serde_json::Value::String(l.href().to_string()), // TODO there could be more + Node::Link(l) => serde_json::Value::String(l.href().unwrap_or_default().to_string()), // TODO there could be more Node::Object(o) => *o, Node::Array(arr) => serde_json::Value::Array(arr.into_iter().map(|x| x.into()).collect()), diff --git a/apb/src/types/link.rs b/apb/src/types/link.rs index 0506ef9c..e21f0af4 100644 --- a/apb/src/types/link.rs +++ b/apb/src/types/link.rs @@ -19,7 +19,7 @@ crate::strenum! { pub trait Link : crate::Base { fn link_type(&self) -> Field { Err(FieldErr("type")) } - fn href(&self) -> &str; + fn href(&self) -> Field<&str>; fn rel(&self) -> Field<&str> { Err(FieldErr("rel")) } fn media_type(&self) -> Field<&str> { Err(FieldErr("mediaType")) } // also in obj fn name(&self) -> Field<&str> { Err(FieldErr("name")) } // also in obj @@ -31,7 +31,7 @@ pub trait Link : crate::Base { pub trait LinkMut : crate::BaseMut { fn set_link_type(self, val: Option) -> Self; - fn set_href(self, href: &str) -> Self; + fn set_href(self, href: Option<&str>) -> Self; fn set_rel(self, val: Option<&str>) -> Self; fn set_media_type(self, val: Option<&str>) -> Self; // also in obj fn set_name(self, val: Option<&str>) -> Self; // also in obj @@ -42,19 +42,19 @@ pub trait LinkMut : crate::BaseMut { } impl Link for String { - fn href(&self) -> &str { - self + fn href(&self) -> Field<&str> { + Ok(self) } } #[cfg(feature = "unstructured")] impl Link for serde_json::Value { // TODO this can fail, but it should never do! - fn href(&self) -> &str { + fn href(&self) -> Field<&str> { if self.is_string() { - self.as_str().unwrap_or("") + self.as_str().ok_or(FieldErr("href")) } else { - self.get("href").map(|x| x.as_str().unwrap_or("")).unwrap_or("") + self.get("href").and_then(|x| x.as_str()).ok_or(FieldErr("href")) } } @@ -70,15 +70,18 @@ impl Link for serde_json::Value { #[cfg(feature = "unstructured")] impl LinkMut for serde_json::Value { - fn set_href(mut self, href: &str) -> Self { + fn set_href(mut self, href: Option<&str>) -> Self { match &mut self { serde_json::Value::Object(map) => { - map.insert( - "href".to_string(), - serde_json::Value::String(href.to_string()) - ); + match href { + Some(href) => map.insert( + "href".to_string(), + serde_json::Value::String(href.to_string()) + ), + None => map.remove("href"), + }; }, - x => *x = serde_json::Value::String(href.to_string()), + x => *x = serde_json::Value::String(href.unwrap_or_default().to_string()), } self } diff --git a/upub/core/src/model/mod.rs b/upub/core/src/model/mod.rs index b049a1af..9320ee5e 100644 --- a/upub/core/src/model/mod.rs +++ b/upub/core/src/model/mod.rs @@ -31,7 +31,7 @@ impl From> for Audience { Audience( match value { apb::Node::Empty => vec![], - apb::Node::Link(l) => vec![l.href().to_string()], + apb::Node::Link(l) => l.href().map(|x| vec![x.to_string()]).unwrap_or_default(), apb::Node::Object(o) => if let Ok(id) = o.id() { vec![id.to_string()] } else { vec![] }, apb::Node::Array(arr) => arr.into_iter().filter_map(|l| l.id().str()).collect(), } diff --git a/upub/core/src/selector/rich.rs b/upub/core/src/selector/rich.rs index d3d62d91..e8fa5b6a 100644 --- a/upub/core/src/selector/rich.rs +++ b/upub/core/src/selector/rich.rs @@ -12,7 +12,7 @@ impl RichMention { use apb::LinkMut; apb::new() .set_link_type(Some(apb::LinkType::Mention)) - .set_href(&self.id) + .set_href(Some(&self.id)) .set_name(Some(&self.fqn)) } } diff --git a/upub/core/src/traits/fetch.rs b/upub/core/src/traits/fetch.rs index 43f8da6e..815e8d68 100644 --- a/upub/core/src/traits/fetch.rs +++ b/upub/core/src/traits/fetch.rs @@ -427,11 +427,13 @@ pub trait Fetchable : Sync + Send { impl Fetchable for apb::Node { async fn fetch(&mut self, ctx: &crate::Context) -> Result<&mut Self, PullError> { if let apb::Node::Link(uri) = self { - *self = crate::Context::request(Method::GET, uri.href(), None, ctx.base(), ctx.pkey(), ctx.domain()) - .await? - .json::() - .await? - .into(); + if let Ok(href) = uri.href() { + *self = crate::Context::request(Method::GET, href, None, ctx.base(), ctx.pkey(), ctx.domain()) + .await? + .json::() + .await? + .into(); + } } Ok(self) diff --git a/upub/core/src/traits/normalize.rs b/upub/core/src/traits/normalize.rs index 6456709d..ba0a7056 100644 --- a/upub/core/src/traits/normalize.rs +++ b/upub/core/src/traits/normalize.rs @@ -77,7 +77,7 @@ impl Normalizer for crate::Context { }, Node::Link(l) => crate::model::attachment::ActiveModel { internal: sea_orm::ActiveValue::NotSet, - url: Set(l.href().to_string()), + url: Set(l.href().unwrap_or_default().to_string()), object: Set(object_model.internal), document_type: Set(apb::DocumentType::Page), name: Set(l.name().str()), @@ -96,20 +96,22 @@ impl Normalizer for crate::Context { Node::Empty | Node::Object(_) | Node::Array(_) => {}, Node::Link(l) => match l.link_type() { Ok(apb::LinkType::Mention) => { - if let Some(internal) = crate::model::actor::Entity::ap_to_internal(l.href(), tx).await? { - let model = crate::model::mention::ActiveModel { - internal: NotSet, - object: Set(object_model.internal), - actor: Set(internal), - }; - crate::model::mention::Entity::insert(model) - .exec(tx) - .await?; + if let Ok(href) = l.href() { + if let Some(internal) = crate::model::actor::Entity::ap_to_internal(href, tx).await? { + let model = crate::model::mention::ActiveModel { + internal: NotSet, + object: Set(object_model.internal), + actor: Set(internal), + }; + crate::model::mention::Entity::insert(model) + .exec(tx) + .await?; + } } }, Ok(apb::LinkType::Hashtag) => { let hashtag = l.name() - .unwrap_or_else(|_| l.href().split('/').last().unwrap_or_default()) + .unwrap_or_else(|_| l.href().unwrap_or_default().split('/').last().unwrap_or_default()) // TODO maybe just fail? .replace('#', ""); let model = crate::model::hashtag::ActiveModel { internal: NotSet, diff --git a/web/src/objects/item.rs b/web/src/objects/item.rs index 1238d48c..4d5290d1 100644 --- a/web/src/objects/item.rs +++ b/web/src/objects/item.rs @@ -72,7 +72,7 @@ pub fn Object( }) }, Ok(apb::LinkType::Mention) => { - let uid = apb::Link::href(link.as_ref()); + let uid = apb::Link::href(link.as_ref()).unwrap_or_default(); let mention = apb::Link::name(link.as_ref()).unwrap_or_default().replacen('@', "", 1); let (username, domain) = if let Some((username, server)) = mention.split_once('@') { (username.to_string(), server.to_string())