fix: generic JsonVec that accepts null

not the cleanest solution but should be generic and transparent
This commit is contained in:
əlemi 2024-07-15 20:20:43 +02:00
parent 88b87c0b20
commit fafe5307c5
Signed by: alemi
GPG key ID: A4895B84D311642C
9 changed files with 119 additions and 61 deletions

View file

@ -1,4 +1,4 @@
use upub::model::{addressing, config, credential, activity, object, actor, Audience}; use upub::{ext::JsonVec, model::{activity, actor, addressing, config, credential, object}};
use openssl::rsa::Rsa; use openssl::rsa::Rsa;
use sea_orm::{ActiveValue::NotSet, IntoActiveModel}; use sea_orm::{ActiveValue::NotSet, IntoActiveModel};
@ -21,8 +21,8 @@ pub async fn faker(ctx: upub::Context, count: i64) -> Result<(), sea_orm::DbErr>
followers: None, followers: None,
followers_count: 0, followers_count: 0,
statuses_count: count as i32, statuses_count: count as i32,
fields: vec![], fields: JsonVec::default(),
also_known_as: vec![], also_known_as: JsonVec::default(),
moved_to: None, moved_to: None,
icon: Some("https://cdn.alemi.dev/social/circle-square.png".to_string()), icon: Some("https://cdn.alemi.dev/social/circle-square.png".to_string()),
image: Some("https://cdn.alemi.dev/social/someriver-xs.jpg".to_string()), image: Some("https://cdn.alemi.dev/social/someriver-xs.jpg".to_string()),
@ -90,10 +90,10 @@ pub async fn faker(ctx: upub::Context, count: i64) -> Result<(), sea_orm::DbErr>
likes: Set(0), likes: Set(0),
announces: Set(0), announces: Set(0),
audience: Set(None), audience: Set(None),
to: Set(Audience(vec![apb::target::PUBLIC.to_string()])), to: Set(JsonVec(vec![apb::target::PUBLIC.to_string()])),
bto: Set(Audience::default()), bto: Set(JsonVec::default()),
cc: Set(Audience(vec![])), cc: Set(JsonVec(vec![])),
bcc: Set(Audience::default()), bcc: Set(JsonVec::default()),
url: Set(None), url: Set(None),
sensitive: Set(false), sensitive: Set(false),
}).exec(db).await?; }).exec(db).await?;
@ -106,10 +106,10 @@ pub async fn faker(ctx: upub::Context, count: i64) -> Result<(), sea_orm::DbErr>
object: Set(Some(format!("{domain}/objects/{oid}"))), object: Set(Some(format!("{domain}/objects/{oid}"))),
target: Set(None), target: Set(None),
published: Set(chrono::Utc::now() - std::time::Duration::from_secs(60*i as u64)), published: Set(chrono::Utc::now() - std::time::Duration::from_secs(60*i as u64)),
to: Set(Audience(vec![apb::target::PUBLIC.to_string()])), to: Set(JsonVec(vec![apb::target::PUBLIC.to_string()])),
bto: Set(Audience::default()), bto: Set(JsonVec::default()),
cc: Set(Audience(vec![])), cc: Set(JsonVec(vec![])),
bcc: Set(Audience::default()), bcc: Set(JsonVec::default()),
}).exec(db).await?; }).exec(db).await?;
} }

View file

@ -48,3 +48,66 @@ impl<T, E: std::error::Error> LoggableError for Result<T, E> {
} }
} }
} }
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct JsonVec<T>(pub Vec<T>);
impl<T> From<Vec<T>> for JsonVec<T> {
fn from(value: Vec<T>) -> Self {
JsonVec(value)
}
}
impl<T> Default for JsonVec<T> {
fn default() -> Self {
JsonVec(Vec::new())
}
}
impl<T: serde::de::DeserializeOwned> sea_orm::TryGetableFromJson for JsonVec<T> {}
impl<T: serde::ser::Serialize> std::convert::From<JsonVec<T>> for sea_orm::Value {
fn from(source: JsonVec<T>) -> Self {
sea_orm::Value::Json(serde_json::to_value(&source).ok().map(std::boxed::Box::new))
}
}
impl<T: serde::de::DeserializeOwned + TypeName> sea_orm::sea_query::ValueType for JsonVec<T> {
fn try_from(v: sea_orm::Value) -> Result<Self, sea_orm::sea_query::ValueTypeErr> {
match v {
sea_orm::Value::Json(Some(json)) => Ok(
serde_json::from_value(*json).map_err(|_| sea_orm::sea_query::ValueTypeErr)?,
),
sea_orm::Value::Json(None) => Ok(JsonVec::default()),
_ => Err(sea_orm::sea_query::ValueTypeErr),
}
}
fn type_name() -> String {
format!("JsonVec_{}", T::type_name())
}
fn array_type() -> sea_orm::sea_query::ArrayType {
sea_orm::sea_query::ArrayType::Json
}
fn column_type() -> sea_orm::sea_query::ColumnType {
sea_orm::sea_query::ColumnType::Json
}
}
impl<T> sea_orm::sea_query::Nullable for JsonVec<T> {
fn null() -> sea_orm::Value {
sea_orm::Value::Json(None)
}
}
pub trait TypeName {
fn type_name() -> String;
}
impl TypeName for String {
fn type_name() -> String {
"String".to_string()
}
}

View file

@ -1,7 +1,7 @@
use openssl::rsa::Rsa; use openssl::rsa::Rsa;
use sea_orm::{ActiveValue::{NotSet, Set}, DatabaseConnection, EntityTrait}; use sea_orm::{ActiveValue::{NotSet, Set}, DatabaseConnection, EntityTrait};
use crate::model; use crate::{ext::JsonVec, model};
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
pub enum InitError { pub enum InitError {
@ -34,9 +34,9 @@ pub async fn application(
domain: Set(domain.clone()), domain: Set(domain.clone()),
preferred_username: Set(domain.clone()), preferred_username: Set(domain.clone()),
actor_type: Set(apb::ActorType::Application), actor_type: Set(apb::ActorType::Application),
also_known_as: Set(vec![]), also_known_as: Set(JsonVec::default()),
moved_to: Set(None), moved_to: Set(None),
fields: Set(vec![]), // TODO we could put some useful things here actually fields: Set(JsonVec::default()), // TODO we could put some useful things here actually
private_key: Set(Some(privk)), private_key: Set(Some(privk)),
public_key: Set(pubk), public_key: Set(pubk),
following: Set(None), following: Set(None),

View file

@ -1,7 +1,7 @@
use apb::{ActivityMut, ActivityType, BaseMut, ObjectMut}; use apb::{ActivityMut, ActivityType, BaseMut, ObjectMut};
use sea_orm::{entity::prelude::*, QuerySelect, SelectColumns}; use sea_orm::{entity::prelude::*, QuerySelect, SelectColumns};
use crate::model::Audience; use crate::ext::JsonVec;
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
#[sea_orm(table_name = "activities")] #[sea_orm(table_name = "activities")]
@ -14,10 +14,10 @@ pub struct Model {
pub actor: String, pub actor: String,
pub object: Option<String>, pub object: Option<String>,
pub target: Option<String>, pub target: Option<String>,
pub to: Audience, pub to: JsonVec<String>,
pub bto: Audience, pub bto: JsonVec<String>,
pub cc: Audience, pub cc: JsonVec<String>,
pub bcc: Audience, pub bcc: JsonVec<String>,
pub published: ChronoDateTimeUtc, pub published: ChronoDateTimeUtc,
} }

View file

@ -2,7 +2,9 @@ use sea_orm::{entity::prelude::*, QuerySelect, SelectColumns};
use apb::{field::OptionalString, ActorMut, ActorType, BaseMut, DocumentMut, EndpointsMut, ObjectMut, PublicKeyMut}; use apb::{field::OptionalString, ActorMut, ActorType, BaseMut, DocumentMut, EndpointsMut, ObjectMut, PublicKeyMut};
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize, sea_orm::FromJsonQueryResult)] use crate::ext::{JsonVec, TypeName};
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct Field { pub struct Field {
pub name: String, pub name: String,
pub content: String, pub content: String,
@ -12,6 +14,12 @@ pub struct Field {
pub field_type: String, pub field_type: String,
} }
impl TypeName for Field {
fn type_name() -> String {
"Field".to_string()
}
}
impl<T: apb::Object> From<T> for Field { impl<T: apb::Object> From<T> for Field {
fn from(value: T) -> Self { fn from(value: T) -> Self {
Field { Field {
@ -37,7 +45,7 @@ pub struct Model {
pub image: Option<String>, pub image: Option<String>,
pub icon: Option<String>, pub icon: Option<String>,
pub preferred_username: String, pub preferred_username: String,
pub fields: Vec<Field>, pub fields: JsonVec<Field>,
pub inbox: Option<String>, pub inbox: Option<String>,
pub shared_inbox: Option<String>, pub shared_inbox: Option<String>,
pub outbox: Option<String>, pub outbox: Option<String>,
@ -50,7 +58,7 @@ pub struct Model {
pub private_key: Option<String>, pub private_key: Option<String>,
pub published: ChronoDateTimeUtc, pub published: ChronoDateTimeUtc,
pub updated: ChronoDateTimeUtc, pub updated: ChronoDateTimeUtc,
pub also_known_as: Vec<String>, pub also_known_as: JsonVec<String>,
pub moved_to: Option<String>, pub moved_to: Option<String>,
} }
@ -208,7 +216,7 @@ impl Model {
.set_url(apb::Node::link(i.clone())) .set_url(apb::Node::link(i.clone()))
))) )))
.set_attachment(apb::Node::array( .set_attachment(apb::Node::array(
self.fields self.fields.0
.into_iter() .into_iter()
.filter_map(|x| serde_json::to_value(x).ok()) .filter_map(|x| serde_json::to_value(x).ok())
.collect() .collect()
@ -233,7 +241,7 @@ impl Model {
apb::new() apb::new()
.set_shared_inbox(self.shared_inbox.as_deref()) .set_shared_inbox(self.shared_inbox.as_deref())
)) ))
.set_also_known_as(apb::Node::links(self.also_known_as)) .set_also_known_as(apb::Node::links(self.also_known_as.0))
.set_moved_to(apb::Node::maybe_link(self.moved_to)) .set_moved_to(apb::Node::maybe_link(self.moved_to))
.set_discoverable(Some(true)) .set_discoverable(Some(true))
} }

View file

@ -19,23 +19,3 @@ pub mod dislike;
pub mod hashtag; pub mod hashtag;
pub mod mention; pub mod mention;
pub mod attachment; pub mod attachment;
#[derive(Clone, Debug, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize, sea_orm::FromJsonQueryResult)]
pub struct Audience(pub Vec<String>);
impl<T: apb::Base> From<apb::Node<T>> for Audience {
fn from(value: apb::Node<T>) -> Self {
use apb::field::OptionalString;
Audience(
match value {
apb::Node::Empty => vec![],
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(),
}
)
}
}

View file

@ -1,7 +1,7 @@
use apb::{BaseMut, CollectionMut, DocumentMut, ObjectMut, ObjectType}; use apb::{BaseMut, CollectionMut, DocumentMut, ObjectMut, ObjectType};
use sea_orm::{entity::prelude::*, QuerySelect, SelectColumns}; use sea_orm::{entity::prelude::*, QuerySelect, SelectColumns};
use super::Audience; use crate::ext::JsonVec;
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
#[sea_orm(table_name = "objects")] #[sea_orm(table_name = "objects")]
@ -24,10 +24,10 @@ pub struct Model {
pub announces: i32, pub announces: i32,
pub replies: i32, pub replies: i32,
pub context: Option<String>, pub context: Option<String>,
pub to: Audience, pub to: JsonVec<String>,
pub bto: Audience, pub bto: JsonVec<String>,
pub cc: Audience, pub cc: JsonVec<String>,
pub bcc: Audience, pub bcc: JsonVec<String>,
pub published: ChronoDateTimeUtc, pub published: ChronoDateTimeUtc,
pub updated: ChronoDateTimeUtc, pub updated: ChronoDateTimeUtc,

View file

@ -1,5 +1,7 @@
use sea_orm::{ActiveValue::{NotSet, Set}, DbErr, EntityTrait}; use sea_orm::{ActiveValue::{NotSet, Set}, DbErr, EntityTrait};
use crate::ext::JsonVec;
#[async_trait::async_trait] #[async_trait::async_trait]
pub trait Administrable { pub trait Administrable {
async fn register_user( async fn register_user(
@ -35,13 +37,13 @@ impl Administrable for crate::Context {
domain: Set(domain), domain: Set(domain),
summary: Set(summary), summary: Set(summary),
preferred_username: Set(username.clone()), preferred_username: Set(username.clone()),
fields: Set(vec![]), fields: Set(JsonVec::default()),
following: Set(None), following: Set(None),
following_count: Set(0), following_count: Set(0),
followers: Set(None), followers: Set(None),
followers_count: Set(0), followers_count: Set(0),
statuses_count: Set(0), statuses_count: Set(0),
also_known_as: Set(vec![]), also_known_as: Set(JsonVec::default()),
moved_to: Set(None), moved_to: Set(None),
icon: Set(avatar_url), icon: Set(avatar_url),
image: Set(banner_url), image: Set(banner_url),

View file

@ -203,10 +203,10 @@ impl AP {
object: activity.object().id().str(), object: activity.object().id().str(),
target: activity.target().id().str(), target: activity.target().id().str(),
published: activity.published().unwrap_or(chrono::Utc::now()), published: activity.published().unwrap_or(chrono::Utc::now()),
to: activity.to().into(), to: activity.to().all_ids().into(),
bto: activity.bto().into(), bto: activity.bto().all_ids().into(),
cc: activity.cc().into(), cc: activity.cc().all_ids().into(),
bcc: activity.bcc().into(), bcc: activity.bcc().all_ids().into(),
}) })
} }
@ -287,10 +287,10 @@ impl AP {
announces: object.shares().get() announces: object.shares().get()
.map_or(0, |x| x.total_items().unwrap_or(0)) as i32, .map_or(0, |x| x.total_items().unwrap_or(0)) as i32,
audience: object.audience().id().str(), audience: object.audience().id().str(),
to: object.to().into(), to: object.to().all_ids().into(),
bto: object.bto().into(), bto: object.bto().all_ids().into(),
cc: object.cc().into(), cc: object.cc().all_ids().into(),
bcc: object.bcc().into(), bcc: object.bcc().all_ids().into(),
sensitive: object.sensitive().unwrap_or(false), sensitive: object.sensitive().unwrap_or(false),
}) })
@ -333,13 +333,12 @@ impl AP {
summary: actor.summary().str(), summary: actor.summary().str(),
icon: actor.icon().get().and_then(|x| x.url().id().str()), icon: actor.icon().get().and_then(|x| x.url().id().str()),
image: actor.image().get().and_then(|x| x.url().id().str()), image: actor.image().get().and_then(|x| x.url().id().str()),
fields: actor.attachment().flat().into_iter().filter_map(|x| Some(crate::model::actor::Field::from(x.extract()?))).collect(),
inbox: actor.inbox().id().str(), inbox: actor.inbox().id().str(),
outbox: actor.outbox().id().str(), outbox: actor.outbox().id().str(),
shared_inbox: actor.endpoints().get().and_then(|x| x.shared_inbox().str()), shared_inbox: actor.endpoints().get().and_then(|x| x.shared_inbox().str()),
followers: actor.followers().id().str(), followers: actor.followers().id().str(),
following: actor.following().id().str(), following: actor.following().id().str(),
also_known_as: actor.also_known_as().flat().into_iter().filter_map(|x| x.id().str()).collect(), also_known_as: actor.also_known_as().flat().into_iter().filter_map(|x| x.id().str()).collect::<Vec<String>>().into(),
moved_to: actor.moved_to().id().str(), moved_to: actor.moved_to().id().str(),
published: actor.published().unwrap_or(chrono::Utc::now()), published: actor.published().unwrap_or(chrono::Utc::now()),
updated: chrono::Utc::now(), updated: chrono::Utc::now(),
@ -348,6 +347,12 @@ impl AP {
statuses_count: actor.statuses_count().unwrap_or(0) as i32, statuses_count: actor.statuses_count().unwrap_or(0) as i32,
public_key: actor.public_key().get().ok_or(apb::FieldErr("publicKey"))?.public_key_pem().to_string(), public_key: actor.public_key().get().ok_or(apb::FieldErr("publicKey"))?.public_key_pem().to_string(),
private_key: None, // there's no way to transport privkey over AP json, must come from DB private_key: None, // there's no way to transport privkey over AP json, must come from DB
fields: actor.attachment()
.flat()
.into_iter()
.filter_map(|x| Some(crate::model::actor::Field::from(x.extract()?)))
.collect::<Vec<crate::model::actor::Field>>()
.into(),
}) })
} }