fix(apb): actually link href is not guaranteed

This commit is contained in:
əlemi 2024-07-06 04:25:06 +02:00
parent c3e319d5a9
commit 2b4fb3bd62
Signed by: alemi
GPG key ID: A4895B84D311642C
7 changed files with 42 additions and 35 deletions

View file

@ -102,7 +102,7 @@ impl<T : super::Base> Node<T> {
pub fn id(&self) -> crate::Field<&str> { pub fn id(&self) -> crate::Field<&str> {
match self { match self {
Node::Empty => Err(crate::FieldErr("id")), Node::Empty => Err(crate::FieldErr("id")),
Node::Link(uri) => Ok(uri.href()), Node::Link(uri) => uri.href(),
Node::Object(obj) => obj.id(), Node::Object(obj) => obj.id(),
Node::Array(arr) => arr.front().map(|x| x.id()).ok_or(crate::FieldErr("id"))?, Node::Array(arr) => arr.front().map(|x| x.id()).ok_or(crate::FieldErr("id"))?,
} }
@ -111,7 +111,7 @@ impl<T : super::Base> Node<T> {
pub fn all_ids(&self) -> Vec<String> { pub fn all_ids(&self) -> Vec<String> {
match self { match self {
Node::Empty => vec![], 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::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() Node::Array(x) => x.iter().filter_map(|x| Some(x.id().ok()?.to_string())).collect()
} }
@ -236,7 +236,7 @@ impl From<Node<serde_json::Value>> for serde_json::Value {
fn from(value: Node<serde_json::Value>) -> Self { fn from(value: Node<serde_json::Value>) -> Self {
match value { match value {
Node::Empty => serde_json::Value::Null, 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::Object(o) => *o,
Node::Array(arr) => Node::Array(arr) =>
serde_json::Value::Array(arr.into_iter().map(|x| x.into()).collect()), serde_json::Value::Array(arr.into_iter().map(|x| x.into()).collect()),

View file

@ -19,7 +19,7 @@ crate::strenum! {
pub trait Link : crate::Base { pub trait Link : crate::Base {
fn link_type(&self) -> Field<LinkType> { Err(FieldErr("type")) } fn link_type(&self) -> Field<LinkType> { Err(FieldErr("type")) }
fn href(&self) -> &str; fn href(&self) -> Field<&str>;
fn rel(&self) -> Field<&str> { Err(FieldErr("rel")) } fn rel(&self) -> Field<&str> { Err(FieldErr("rel")) }
fn media_type(&self) -> Field<&str> { Err(FieldErr("mediaType")) } // also in obj fn media_type(&self) -> Field<&str> { Err(FieldErr("mediaType")) } // also in obj
fn name(&self) -> Field<&str> { Err(FieldErr("name")) } // 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 { pub trait LinkMut : crate::BaseMut {
fn set_link_type(self, val: Option<LinkType>) -> Self; fn set_link_type(self, val: Option<LinkType>) -> 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_rel(self, val: Option<&str>) -> Self;
fn set_media_type(self, val: Option<&str>) -> Self; // also in obj fn set_media_type(self, val: Option<&str>) -> Self; // also in obj
fn set_name(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 { impl Link for String {
fn href(&self) -> &str { fn href(&self) -> Field<&str> {
self Ok(self)
} }
} }
#[cfg(feature = "unstructured")] #[cfg(feature = "unstructured")]
impl Link for serde_json::Value { impl Link for serde_json::Value {
// TODO this can fail, but it should never do! // TODO this can fail, but it should never do!
fn href(&self) -> &str { fn href(&self) -> Field<&str> {
if self.is_string() { if self.is_string() {
self.as_str().unwrap_or("") self.as_str().ok_or(FieldErr("href"))
} else { } 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")] #[cfg(feature = "unstructured")]
impl LinkMut for serde_json::Value { 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 { match &mut self {
serde_json::Value::Object(map) => { serde_json::Value::Object(map) => {
map.insert( match href {
"href".to_string(), Some(href) => map.insert(
serde_json::Value::String(href.to_string()) "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 self
} }

View file

@ -31,7 +31,7 @@ impl<T: apb::Base> From<apb::Node<T>> for Audience {
Audience( Audience(
match value { match value {
apb::Node::Empty => vec![], 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::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(), apb::Node::Array(arr) => arr.into_iter().filter_map(|l| l.id().str()).collect(),
} }

View file

@ -12,7 +12,7 @@ impl RichMention {
use apb::LinkMut; use apb::LinkMut;
apb::new() apb::new()
.set_link_type(Some(apb::LinkType::Mention)) .set_link_type(Some(apb::LinkType::Mention))
.set_href(&self.id) .set_href(Some(&self.id))
.set_name(Some(&self.fqn)) .set_name(Some(&self.fqn))
} }
} }

View file

@ -427,11 +427,13 @@ pub trait Fetchable : Sync + Send {
impl Fetchable for apb::Node<serde_json::Value> { impl Fetchable for apb::Node<serde_json::Value> {
async fn fetch(&mut self, ctx: &crate::Context) -> Result<&mut Self, PullError> { async fn fetch(&mut self, ctx: &crate::Context) -> Result<&mut Self, PullError> {
if let apb::Node::Link(uri) = self { if let apb::Node::Link(uri) = self {
*self = crate::Context::request(Method::GET, uri.href(), None, ctx.base(), ctx.pkey(), ctx.domain()) if let Ok(href) = uri.href() {
.await? *self = crate::Context::request(Method::GET, href, None, ctx.base(), ctx.pkey(), ctx.domain())
.json::<serde_json::Value>() .await?
.await? .json::<serde_json::Value>()
.into(); .await?
.into();
}
} }
Ok(self) Ok(self)

View file

@ -77,7 +77,7 @@ impl Normalizer for crate::Context {
}, },
Node::Link(l) => crate::model::attachment::ActiveModel { Node::Link(l) => crate::model::attachment::ActiveModel {
internal: sea_orm::ActiveValue::NotSet, 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), object: Set(object_model.internal),
document_type: Set(apb::DocumentType::Page), document_type: Set(apb::DocumentType::Page),
name: Set(l.name().str()), name: Set(l.name().str()),
@ -96,20 +96,22 @@ impl Normalizer for crate::Context {
Node::Empty | Node::Object(_) | Node::Array(_) => {}, Node::Empty | Node::Object(_) | Node::Array(_) => {},
Node::Link(l) => match l.link_type() { Node::Link(l) => match l.link_type() {
Ok(apb::LinkType::Mention) => { Ok(apb::LinkType::Mention) => {
if let Some(internal) = crate::model::actor::Entity::ap_to_internal(l.href(), tx).await? { if let Ok(href) = l.href() {
let model = crate::model::mention::ActiveModel { if let Some(internal) = crate::model::actor::Entity::ap_to_internal(href, tx).await? {
internal: NotSet, let model = crate::model::mention::ActiveModel {
object: Set(object_model.internal), internal: NotSet,
actor: Set(internal), object: Set(object_model.internal),
}; actor: Set(internal),
crate::model::mention::Entity::insert(model) };
.exec(tx) crate::model::mention::Entity::insert(model)
.await?; .exec(tx)
.await?;
}
} }
}, },
Ok(apb::LinkType::Hashtag) => { Ok(apb::LinkType::Hashtag) => {
let hashtag = l.name() 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('#', ""); .replace('#', "");
let model = crate::model::hashtag::ActiveModel { let model = crate::model::hashtag::ActiveModel {
internal: NotSet, internal: NotSet,

View file

@ -72,7 +72,7 @@ pub fn Object(
}) })
}, },
Ok(apb::LinkType::Mention) => { 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 mention = apb::Link::name(link.as_ref()).unwrap_or_default().replacen('@', "", 1);
let (username, domain) = if let Some((username, server)) = mention.split_once('@') { let (username, domain) = if let Some((username, server)) = mention.split_once('@') {
(username.to_string(), server.to_string()) (username.to_string(), server.to_string())