Simplified data transformation

This commit is contained in:
Natty 2023-09-23 18:05:13 +02:00
parent e045d2aae4
commit 85ee56e21b
Signed by: natty
GPG Key ID: BF6CB659ADEE60EC
15 changed files with 210 additions and 796 deletions

2
Cargo.lock generated
View File

@ -1405,6 +1405,7 @@ dependencies = [
"tower-http", "tower-http",
"tracing", "tracing",
"tracing-subscriber", "tracing-subscriber",
"unicode-segmentation",
] ]
[[package]] [[package]]
@ -1490,6 +1491,7 @@ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
"ts-rs", "ts-rs",
"unicode-segmentation",
] ]
[[package]] [[package]]

View File

@ -55,6 +55,7 @@ tower-http = "0.4"
tracing = "0.1" tracing = "0.1"
tracing-subscriber = "0.3" tracing-subscriber = "0.3"
ts-rs = "6" ts-rs = "6"
unicode-segmentation = "1.10"
url = "2.3" url = "2.3"
walkdir = "2.3" walkdir = "2.3"
wasm-bindgen = "0.2" wasm-bindgen = "0.2"
@ -95,5 +96,7 @@ serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true } serde_json = { workspace = true }
toml = { workspace = true } toml = { workspace = true }
unicode-segmentation = { workspace = true }
[profile.release] [profile.release]
lto = true lto = true

View File

@ -13,3 +13,5 @@ serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true } serde_json = { workspace = true }
ts-rs = { workspace = true, features = ["chrono", "chrono-impl"] } ts-rs = { workspace = true, features = ["chrono", "chrono-impl"] }
unicode-segmentation = { workspace = true }

View File

@ -22,7 +22,7 @@ fn default_timeline_limit<const MIN: u64, const MAX: u64>() -> U64Range<MIN, MAX
#[derive(Serialize, Deserialize, TS)] #[derive(Serialize, Deserialize, TS)]
#[ts(export)] #[ts(export)]
#[serde(transparent)] #[repr(transparent)]
pub struct GetTimelineRes(pub Vec<PackNoteFull>); pub struct GetTimelineRes(pub Vec<PackNoteFull>);
#[derive(Endpoint)] #[derive(Endpoint)]

View File

@ -9,7 +9,7 @@ use magnetar_sdk_macros::pack;
use crate::types::Id; use crate::types::Id;
#[derive(Clone, Debug, Deserialize, Serialize, TS)] #[derive(Clone, Debug, Default, Deserialize, Serialize, TS)]
#[ts(export)] #[ts(export)]
pub struct ImageMeta { pub struct ImageMeta {
pub width: Option<u64>, pub width: Option<u64>,
@ -49,9 +49,9 @@ pub struct DriveFileBase {
pub name: String, pub name: String,
pub created_at: DateTime<Utc>, pub created_at: DateTime<Utc>,
pub size: u64, pub size: u64,
pub sha256: String, pub hash: Option<String>,
pub mime_type: String, pub mime_type: String,
pub image_meta: ImageMeta, pub media_metadata: ImageMeta,
pub url: Option<String>, pub url: Option<String>,
pub thumbnail_url: Option<String>, pub thumbnail_url: Option<String>,
pub sensitive: bool, pub sensitive: bool,

View File

@ -23,6 +23,12 @@ pub struct Id {
pub id: String, pub id: String,
} }
impl From<&str> for Id {
fn from(id: &str) -> Self {
Self { id: id.to_string() }
}
}
#[derive(Copy, Clone, Debug, Deserialize, Serialize, TS)] #[derive(Copy, Clone, Debug, Deserialize, Serialize, TS)]
#[ts(export)] #[ts(export)]
pub enum NotificationType { pub enum NotificationType {

View File

@ -1,5 +1,5 @@
use crate::types::emoji::EmojiContext; use crate::types::emoji::EmojiContext;
use crate::types::user::{PackUserBase, UserBase}; use crate::types::user::PackUserBase;
use crate::types::Id; use crate::types::Id;
use crate::{Packed, Required}; use crate::{Packed, Required};
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
@ -56,7 +56,7 @@ pub struct NoteBase {
pub url: Option<String>, pub url: Option<String>,
pub text: String, pub text: String,
pub visibility: NoteVisibility, pub visibility: NoteVisibility,
pub user: Box<UserBase>, pub user: Box<PackUserBase>,
pub parent_note_id: Option<String>, pub parent_note_id: Option<String>,
pub renoted_note_id: Option<String>, pub renoted_note_id: Option<String>,
pub reply_count: u64, pub reply_count: u64,
@ -65,6 +65,8 @@ pub struct NoteBase {
pub reactions: Vec<PackReactionBase>, pub reactions: Vec<PackReactionBase>,
pub emojis: EmojiContext, pub emojis: EmojiContext,
pub local_only: bool, pub local_only: bool,
pub has_poll: bool,
pub file_ids: Vec<String>,
} }
pack!(PackNoteBase, Required<Id> as id & Required<NoteBase> as note); pack!(PackNoteBase, Required<Id> as id & Required<NoteBase> as note);
@ -73,7 +75,6 @@ pack!(PackNoteBase, Required<Id> as id & Required<NoteBase> as note);
#[ts(export)] #[ts(export)]
pub struct NoteAttachmentExt { pub struct NoteAttachmentExt {
pub attachments: Vec<PackDriveFileBase>, pub attachments: Vec<PackDriveFileBase>,
pub poll: Option<PollBase>,
} }
pack!( pack!(
@ -93,10 +94,28 @@ pack!(
Required<Id> as id & Required<NoteBase> as note & Required<NoteAttachmentExt> as attachment & Required<NoteDetailExt> as detail Required<Id> as id & Required<NoteBase> as note & Required<NoteAttachmentExt> as attachment & Required<NoteDetailExt> as detail
); );
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
pub enum Reaction {
Unicode(String),
Shortcode(String),
}
impl Reaction {
pub fn guess_from(s: &str, default_emote: &str) -> Option<Self> {
let code = s.trim();
match code.chars().next() {
Some(':') => Some(Reaction::Shortcode(code.to_string())),
Some(_) => Some(Reaction::Unicode(code.to_string())),
None => Self::guess_from(default_emote, "👍"),
}
}
}
#[derive(Clone, Debug, Deserialize, Serialize, TS)] #[derive(Clone, Debug, Deserialize, Serialize, TS)]
pub struct ReactionBase { pub struct ReactionBase {
pub created_at: DateTime<Utc>, pub created_at: DateTime<Utc>,
pub user_id: String, pub user_id: String,
pub reaction: Reaction,
} }
pack!(PackReactionBase, Required<Id> as id & Required<ReactionBase> as reaction); pack!(PackReactionBase, Required<Id> as id & Required<ReactionBase> as reaction);

View File

@ -5,10 +5,9 @@ use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use ts_rs::TS; use ts_rs::TS;
use crate::types::note::PackNoteFull;
use magnetar_sdk_macros::pack; use magnetar_sdk_macros::pack;
use super::note::PackNoteWithAttachments;
#[derive(Clone, Debug, Default, Deserialize, Serialize, TS)] #[derive(Clone, Debug, Default, Deserialize, Serialize, TS)]
#[ts(export)] #[ts(export)]
pub enum AvatarDecoration { pub enum AvatarDecoration {
@ -85,7 +84,7 @@ pub struct UserProfileExt {
#[derive(Clone, Debug, Deserialize, Serialize, TS)] #[derive(Clone, Debug, Deserialize, Serialize, TS)]
#[ts(export)] #[ts(export)]
pub struct UserProfilePinsEx { pub struct UserProfilePinsEx {
pub pinned_notes: Vec<PackNoteWithAttachments>, pub pinned_notes: Vec<PackNoteFull>,
// pub pinned_page: Option<Page>, // pub pinned_page: Option<Page>,
} }

View File

@ -1,27 +1,26 @@
use magnetar_calckey_model::ck; use magnetar_calckey_model::ck;
use magnetar_sdk::types::drive::DriveFileBase; use magnetar_sdk::types::drive::{DriveFileBase, ImageMeta};
use serde::Deserialize;
use crate::model::{PackBase, PackExtract, PackType, PackingContext}; use crate::model::{PackType, PackingContext};
impl PackType for PackExtract<DriveFileBase> { impl PackType<&ck::drive_file::Model> for DriveFileBase {
type Input = PackBase<ck::drive_file::Model>; fn extract(_context: &PackingContext, file: &ck::drive_file::Model) -> Self {
let media_metadata = ImageMeta::deserialize(file.properties.clone()).unwrap_or_default();
fn extract(context: &PackingContext<Self>, data: Self::Input) -> Self { DriveFileBase {
let PackBase(file) = data; name: file.name.clone(),
PackExtract(DriveFileBase {
name: file.name,
created_at: file.created_at.into(), created_at: file.created_at.into(),
size: file.size as u64, size: file.size as u64,
sha256: todo!(), hash: None, // TODO: blake3
mime_type: file.r#type, mime_type: file.r#type.clone(),
image_meta: todo!(), media_metadata,
url: Some(file.url), url: Some(file.url.clone()),
thumbnail_url: file.thumbnail_url, thumbnail_url: file.thumbnail_url.clone(),
sensitive: file.is_sensitive, sensitive: file.is_sensitive,
comment: file.comment, comment: file.comment.clone(),
folder_id: file.folder_id, folder_id: file.folder_id.clone(),
user_id: file.user_id, user_id: file.user_id.clone(),
}) }
} }
} }

View File

@ -1,19 +1,15 @@
use crate::model::{PackBase, PackExtract, PackType, PackingContext}; use crate::model::{PackType, PackingContext};
use magnetar_calckey_model::ck; use magnetar_calckey_model::ck;
use magnetar_sdk::types::emoji::EmojiBase; use magnetar_sdk::types::emoji::EmojiBase;
impl PackType for PackExtract<EmojiBase> { impl PackType<&ck::emoji::Model> for EmojiBase {
type Input = PackBase<ck::emoji::Model>; fn extract(_context: &PackingContext, emoji: &ck::emoji::Model) -> Self {
EmojiBase {
fn extract(_context: &PackingContext<Self>, data: Self::Input) -> Self { shortcode: emoji.name.clone(),
let PackBase(model) = data; category: emoji.category.clone(),
url: emoji.public_url.clone(),
PackExtract(EmojiBase { width: emoji.width,
shortcode: model.name.clone(), height: emoji.height,
category: model.category.clone(), }
url: model.public_url.clone(),
width: model.width,
height: model.height,
})
} }
} }

View File

@ -1,57 +1,54 @@
use magnetar_calckey_model::ck; use magnetar_calckey_model::ck;
use magnetar_sdk::types::note::Reaction;
use magnetar_sdk::types::user::PackUserBase;
use magnetar_sdk::types::{ use magnetar_sdk::types::{
drive::PackDriveFileBase, drive::PackDriveFileBase,
emoji::EmojiContext, emoji::EmojiContext,
note::{NoteAttachmentExt, NoteBase, NoteVisibility, PackReactionBase, ReactionBase}, note::{NoteAttachmentExt, NoteBase, NoteVisibility, PackReactionBase, ReactionBase},
user::UserBase, user::UserBase,
}; };
use magnetar_sdk::{Packed, Required};
use crate::model::{ use crate::model::{PackType, PackingContext};
LinkAuto, PackAggregate, PackBase, PackChild, PackExtract, PackMulti, PackPacked, PackType,
PackingContext,
};
impl PackType for PackExtract<ReactionBase> { impl PackType<&ck::note_reaction::Model> for ReactionBase {
type Input = PackBase<ck::note_reaction::Model>; fn extract(context: &PackingContext, reaction: &ck::note_reaction::Model) -> Self {
ReactionBase {
fn extract(_context: &PackingContext<Self>, data: Self::Input) -> Self {
let PackBase(reaction) = data;
PackExtract(ReactionBase {
created_at: reaction.created_at.into(), created_at: reaction.created_at.into(),
user_id: reaction.user_id, user_id: reaction.user_id.clone(),
}) reaction: Reaction::guess_from(
&reaction.reaction,
&context.instance_info.default_reaction,
)
.unwrap_or_else(|| /* Shouldn't happen */ Reaction::Unicode("👍".to_string())),
}
} }
} }
impl PackType for PackExtract<NoteBase> { pub struct NoteBaseSource<'a> {
type Input = PackMulti<( pub note: &'a ck::note::Model,
PackBase<ck::note::Model>, pub reactions: &'a Vec<PackReactionBase>,
PackAggregate<PackChild<PackPacked<PackReactionBase>, LinkAuto>>, pub emojis: &'a EmojiContext,
PackChild<PackExtract<EmojiContext>, LinkAuto>, pub user: &'a UserBase,
PackChild<PackExtract<UserBase>, LinkAuto>, }
)>;
fn extract(_context: &PackingContext<Self>, data: Self::Input) -> Self {
let PackMulti((
PackBase(note),
PackAggregate(reactions, ..),
PackChild(PackExtract(emojis), ..),
PackChild(PackExtract(user), ..),
)) = data;
let reactions = reactions
.into_iter()
.map(|PackChild(PackPacked(t, ..), ..)| t)
.collect::<Vec<_>>();
impl PackType<NoteBaseSource<'_>> for NoteBase {
fn extract(
_context: &PackingContext,
NoteBaseSource {
note,
reactions,
emojis,
user,
}: NoteBaseSource<'_>,
) -> Self {
use ck::sea_orm_active_enums::NoteVisibilityEnum as NVE; use ck::sea_orm_active_enums::NoteVisibilityEnum as NVE;
PackExtract(NoteBase { NoteBase {
created_at: note.created_at.into(), created_at: note.created_at.into(),
cw: note.cw, cw: note.cw.clone(),
uri: note.uri, uri: note.uri.clone(),
url: note.url, url: note.url.clone(),
text: note.text.unwrap_or_default(), text: note.text.clone().unwrap_or_default(),
visibility: match note.visibility { visibility: match note.visibility {
NVE::Followers => NoteVisibility::Followers, NVE::Followers => NoteVisibility::Followers,
NVE::Hidden => NoteVisibility::Direct, NVE::Hidden => NoteVisibility::Direct,
@ -59,29 +56,28 @@ impl PackType for PackExtract<NoteBase> {
NVE::Home => NoteVisibility::Home, NVE::Home => NoteVisibility::Home,
NVE::Specified => NoteVisibility::Direct, NVE::Specified => NoteVisibility::Direct,
}, },
user: Box::new(user), user: Box::new(PackUserBase::pack_from((
parent_note_id: note.reply_id, Required(note.user_id.as_str().into()),
renoted_note_id: note.renote_id, Required(user.clone()),
))),
parent_note_id: note.reply_id.clone(),
renoted_note_id: note.renote_id.clone(),
reply_count: note.replies_count as u64, reply_count: note.replies_count as u64,
renote_count: note.renote_count as u64, renote_count: note.renote_count as u64,
hashtags: note.tags, hashtags: note.tags.clone(),
reactions, reactions: reactions.clone(),
emojis, emojis: emojis.clone(),
local_only: note.local_only, local_only: note.local_only,
}) has_poll: note.has_poll,
file_ids: note.file_ids.clone(),
}
} }
} }
impl PackType for PackExtract<NoteAttachmentExt> { impl PackType<&Vec<PackDriveFileBase>> for NoteAttachmentExt {
type Input = PackMulti<( fn extract(_context: &PackingContext, data: &Vec<PackDriveFileBase>) -> Self {
PackBase<ck::note::Model>, NoteAttachmentExt {
PackAggregate<PackChild<PackPacked<PackDriveFileBase>, LinkAuto>>, attachments: data.clone(),
)>; }
fn extract(_context: &PackingContext<Self>, data: Self::Input) -> Self {
PackExtract(NoteAttachmentExt {
attachments: todo!(),
poll: todo!(),
})
} }
} }

View File

@ -1,60 +1,29 @@
use magnetar_calckey_model::ck; use magnetar_calckey_model::ck;
use magnetar_calckey_model::ck::sea_orm_active_enums::UserProfileFfvisibilityEnum; use magnetar_calckey_model::ck::sea_orm_active_enums::UserProfileFfvisibilityEnum;
use magnetar_sdk::types::emoji::{EmojiContext, PackEmojiBase}; use magnetar_sdk::types::emoji::{EmojiContext, PackEmojiBase};
use magnetar_sdk::types::note::PackNoteWithAttachments; use magnetar_sdk::types::note::PackNoteFull;
use magnetar_sdk::types::user::{ use magnetar_sdk::types::user::{
AvatarDecoration, PackSecurityKeyBase, SecurityKeyBase, SpeechTransform, UserBase, AvatarDecoration, PackSecurityKeyBase, SecurityKeyBase, SpeechTransform, UserBase,
UserDetailExt, UserProfileExt, UserProfilePinsEx, UserRelationExt, UserSecretsExt, UserDetailExt, UserProfileExt, UserProfilePinsEx, UserRelationExt, UserSecretsExt,
}; };
use crate::model::{ use crate::model::{PackType, PackingContext};
LinkAuto, LinkIdent, PackAggregate, PackBase, PackChild, PackExtract, PackMaybe, PackMulti,
PackPacked, PackType, PackingContext,
};
impl PackType for PackExtract<EmojiContext> { impl PackType<&[PackEmojiBase]> for EmojiContext {
type Input = PackMulti<( fn extract(_context: &PackingContext, data: &[PackEmojiBase]) -> Self {
PackBase<ck::user::Model>, EmojiContext(data.to_owned())
PackChild<PackAggregate<PackPacked<PackEmojiBase>>, LinkAuto>,
)>;
fn extract(_context: &PackingContext<Self>, data: Self::Input) -> Self {
let PackMulti((PackBase(_), PackChild(PackAggregate(emojis), ..))) = data;
PackExtract(EmojiContext(
emojis
.into_iter()
.map(|PackPacked(t, ..)| t)
.collect::<Vec<_>>(),
))
} }
} }
#[derive(Copy, Clone, Debug, Default)] type UserBaseSource<'a> = (
pub struct UserAvatarLink; &'a ck::user::Model,
impl From<UserAvatarLink> for LinkIdent { &'a Option<ck::drive_file::Model>,
fn from(_: UserAvatarLink) -> Self { &'a EmojiContext,
LinkIdent::Named("avatar".to_string()) );
}
}
impl PackType for PackExtract<UserBase> { impl PackType<UserBaseSource<'_>> for UserBase {
type Input = PackMulti<( fn extract(_context: &PackingContext, (user, avatar, emoji_context): UserBaseSource) -> Self {
PackBase<ck::user::Model>, UserBase {
PackChild<PackMaybe<PackBase<ck::drive_file::Model>>, UserAvatarLink>,
PackChild<PackExtract<EmojiContext>, LinkAuto>,
)>;
fn extract(_context: &PackingContext<Self>, data: Self::Input) -> Self {
let PackMulti((
PackBase(ref user),
PackChild(PackMaybe(ref avatar), _),
PackChild(PackExtract(ref emoji_context), _),
)) = data;
let avatar = avatar.as_ref().map(|PackBase(v)| v);
PackExtract(UserBase {
acct: user acct: user
.host .host
.as_ref() .as_ref()
@ -69,8 +38,8 @@ impl PackType for PackExtract<UserBase> {
SpeechTransform::None SpeechTransform::None
}, },
created_at: user.created_at.into(), created_at: user.created_at.into(),
avatar_url: avatar.map(|v| v.url.clone()), avatar_url: avatar.as_ref().map(|v| v.url.clone()),
avatar_blurhash: avatar.and_then(|v| v.blurhash.clone()), avatar_blurhash: avatar.as_ref().and_then(|v| v.blurhash.clone()),
avatar_color: None, avatar_color: None,
avatar_decoration: if user.is_cat { avatar_decoration: if user.is_cat {
AvatarDecoration::CatEars AvatarDecoration::CatEars
@ -81,35 +50,25 @@ impl PackType for PackExtract<UserBase> {
is_moderator: user.is_moderator, is_moderator: user.is_moderator,
is_bot: user.is_bot, is_bot: user.is_bot,
emojis: emoji_context.clone(), emojis: emoji_context.clone(),
}) }
} }
} }
impl PackType for PackExtract<UserProfileExt> { type UserProfileExtSource<'a> = (
type Input = PackMulti<( &'a ck::user::Model,
PackBase<ck::user::Model>, &'a ck::user_profile::Model,
PackChild<PackBase<ck::user_profile::Model>, LinkAuto>, Option<&'a UserRelationExt>,
PackMaybe<PackExtract<UserRelationExt>>, );
)>;
fn extract(context: &PackingContext<Self>, data: Self::Input) -> Self {
let PackMulti((
PackBase(ref user),
PackChild(PackBase(ref profile), ..),
PackMaybe(ref relation),
)) = data;
let relation = relation.as_ref().map(|PackExtract(v)| v);
impl PackType<UserProfileExtSource<'_>> for UserProfileExt {
fn extract(context: &PackingContext, (user, profile, relation): UserProfileExtSource) -> Self {
let follow_visibility = match profile.ff_visibility { let follow_visibility = match profile.ff_visibility {
UserProfileFfvisibilityEnum::Public => true, UserProfileFfvisibilityEnum::Public => true,
UserProfileFfvisibilityEnum::Followers => { UserProfileFfvisibilityEnum::Followers => relation.is_some_and(|r| r.follows_you),
relation.as_ref().is_some_and(|r| r.follows_you)
}
UserProfileFfvisibilityEnum::Private => false, UserProfileFfvisibilityEnum::Private => false,
} || context.is_self(user); } || context.is_self(user);
PackExtract(UserProfileExt { UserProfileExt {
is_locked: user.is_locked, is_locked: user.is_locked,
is_silenced: user.is_silenced, is_silenced: user.is_silenced,
is_suspended: user.is_suspended, is_suspended: user.is_suspended,
@ -130,131 +89,96 @@ impl PackType for PackExtract<UserProfileExt> {
banner_color: None, banner_color: None,
banner_blurhash: None, banner_blurhash: None,
has_public_reactions: profile.public_reactions, has_public_reactions: profile.public_reactions,
}) }
} }
} }
impl PackType for PackExtract<UserDetailExt> { impl PackType<&ck::user::Model> for UserDetailExt {
type Input = PackBase<ck::user::Model>; fn extract(_context: &PackingContext, user: &ck::user::Model) -> Self {
UserDetailExt {
fn extract(_context: &PackingContext<Self>, data: Self::Input) -> Self { last_fetched_at: user.last_fetched_at.map(Into::into),
let PackBase(model) = data; uri: user.uri.clone(),
updated_at: user.updated_at.map(Into::into),
PackExtract(UserDetailExt { }
last_fetched_at: model.last_fetched_at.map(Into::into),
uri: model.uri.clone(),
updated_at: model.updated_at.map(Into::into),
})
} }
} }
impl PackType for PackExtract<UserRelationExt> { type UserRelationExtSource<'a> = (
type Input = PackMulti<( Option<&'a ck::following::Model>,
PackChild<PackBase<ck::following::Model>, LinkAuto>, Option<&'a ck::following::Model>,
PackChild<PackBase<ck::blocking::Model>, LinkAuto>, Option<&'a ck::blocking::Model>,
PackChild<PackBase<ck::muting::Model>, LinkAuto>, Option<&'a ck::blocking::Model>,
PackChild<PackBase<ck::renote_muting::Model>, LinkAuto>, Option<&'a ck::muting::Model>,
)>; Option<&'a ck::renote_muting::Model>,
);
fn extract(context: &PackingContext<Self>, data: Self::Input) -> Self {
let PackMulti((
PackChild(PackBase(ref follow), ..),
PackChild(PackBase(ref block), ..),
PackChild(PackBase(ref mute), ..),
PackChild(PackBase(ref renote_mute), ..),
)) = data;
impl PackType<UserRelationExtSource<'_>> for UserRelationExt {
fn extract(
context: &PackingContext,
(follow_out, follow_in, block_out, block_in, mute, renote_mute): UserRelationExtSource,
) -> Self {
let self_user = context.self_user(); let self_user = context.self_user();
PackExtract(UserRelationExt { UserRelationExt {
follows_you: self_user.is_some_and(|self_user| { follows_you: self_user.is_some_and(|self_user| {
self_user.id == follow.followee_id && self_user.id != follow.follower_id follow_in.is_some_and(|follow_in| {
self_user.id == follow_in.followee_id && self_user.id != follow_in.follower_id
})
}), }),
you_follow: self_user.is_some_and(|self_user| { you_follow: self_user.is_some_and(|self_user| {
self_user.id == follow.follower_id && self_user.id != follow.followee_id follow_out.is_some_and(|follow_out| {
self_user.id == follow_out.follower_id && self_user.id != follow_out.followee_id
})
}), }),
blocks_you: self_user.is_some_and(|self_user| { blocks_you: self_user.is_some_and(|self_user| {
self_user.id == block.blockee_id && self_user.id != block.blocker_id block_in.is_some_and(|block_in| {
self_user.id == block_in.blockee_id && self_user.id != block_in.blocker_id
})
}), }),
you_block: self_user.is_some_and(|self_user| { you_block: self_user.is_some_and(|self_user| {
self_user.id == block.blocker_id && self_user.id != block.blockee_id block_out.is_some_and(|block_out| {
self_user.id == block_out.blocker_id && self_user.id != block_out.blockee_id
})
}), }),
mute: self_user.is_some_and(|self_user| { mute: self_user.is_some_and(|self_user| {
mute.is_some_and(|mute| {
self_user.id == mute.muter_id && self_user.id != mute.mutee_id self_user.id == mute.muter_id && self_user.id != mute.mutee_id
})
}), }),
mute_renotes: self_user.is_some_and(|self_user| { mute_renotes: self_user.is_some_and(|self_user| {
renote_mute.is_some_and(|renote_mute| {
self_user.id == renote_mute.muter_id && self_user.id != renote_mute.mutee_id self_user.id == renote_mute.muter_id && self_user.id != renote_mute.mutee_id
})
}), }),
}) }
} }
} }
impl PackType for PackExtract<UserProfilePinsEx> { impl PackType<&[PackNoteFull]> for UserProfilePinsEx {
type Input = PackMulti<( fn extract(_context: &PackingContext, pinned_notes: &[PackNoteFull]) -> Self {
PackBase<ck::user::Model>, UserProfilePinsEx {
PackChild< pinned_notes: pinned_notes.to_owned(),
PackAggregate< }
PackMulti<(
PackBase<ck::user_note_pining::Model>,
PackChild<PackPacked<PackNoteWithAttachments>, LinkAuto>,
)>,
>,
LinkAuto,
>,
)>;
fn extract(_context: &PackingContext<Self>, data: Self::Input) -> Self {
let PackMulti((PackBase(_), PackChild(PackAggregate(pins), ..))) = data;
PackExtract(UserProfilePinsEx {
pinned_notes: pins
.into_iter()
.map(|PackMulti((_, PackChild(PackPacked(note, ..), ..)))| note)
.collect(),
})
} }
} }
impl PackType for PackExtract<SecurityKeyBase> { impl PackType<ck::user_security_key::Model> for SecurityKeyBase {
type Input = PackBase<ck::user_security_key::Model>; fn extract(_context: &PackingContext, key: ck::user_security_key::Model) -> Self {
SecurityKeyBase {
fn extract(_context: &PackingContext<Self>, data: Self::Input) -> Self { name: key.name.clone(),
let PackBase(model) = data; last_used_at: Some(key.last_used.into()),
}
PackExtract(SecurityKeyBase {
name: model.name.clone(),
last_used_at: Some(model.last_used.into()),
})
} }
} }
impl PackType for PackExtract<UserSecretsExt> { type UserSecretsExtSource<'a> = (&'a ck::user_profile::Model, &'a [PackSecurityKeyBase]);
type Input = PackMulti<(
PackBase<ck::user::Model>,
PackChild<
PackMulti<(
PackBase<ck::user_profile::Model>,
PackChild<PackAggregate<PackPacked<PackSecurityKeyBase>>, LinkAuto>,
)>,
LinkAuto,
>,
)>;
fn extract(_context: &PackingContext<Self>, data: Self::Input) -> Self { impl PackType<UserSecretsExtSource<'_>> for UserSecretsExt {
let PackMulti(( fn extract(_context: &PackingContext, (profile, keys): UserSecretsExtSource) -> Self {
_, UserSecretsExt {
PackChild(PackMulti((PackBase(ref profile), PackChild(PackAggregate(keys), ..))), ..),
)) = data;
let keys = keys
.into_iter()
.map(|PackPacked(t, ..)| t)
.collect::<Vec<_>>();
PackExtract(UserSecretsExt {
email: profile.email.clone(), email: profile.email.clone(),
email_verified: profile.email_verified, email_verified: profile.email_verified,
security_keys: keys.clone(), security_keys: keys.to_owned(),
}) }
} }
} }

View File

@ -1,163 +0,0 @@
use crate::model::LinkIdent;
use std::any::TypeId;
use std::fmt::Debug;
use std::sync::Arc;
type PMT = PackMapType;
#[derive(Clone, Debug)]
pub struct Multi(Vec<Arc<PMT>>);
impl Multi {
pub fn get_base(&self) -> Option<&PMT> {
for pmt in &self.0 {
match pmt.as_ref() {
PackMapType::FromType(CommandPackFromType { base: true, .. }) => return Some(pmt),
PackMapType::Maybe(CommandPackMaybe { inner }) => {
if let PackMapType::FromType(..) = inner.as_ref() {
return Some(pmt);
}
}
PackMapType::Multi(CommandPackMulti { inner }) => {
if let Some(pmt) = inner.get_base() {
return Some(pmt);
}
}
_ => {}
}
}
None
}
}
impl From<Vec<PMT>> for Multi {
fn from(v: Vec<PMT>) -> Self {
Multi(v.into_iter().map(Arc::new).collect())
}
}
#[derive(Clone, Debug)]
pub enum PackMapType {
FromType(CommandPackFromType),
Maybe(CommandPackMaybe),
Child(CommandPackChild),
Aggregate(CommandPackAggregate),
Multi(CommandPackMulti),
}
trait PackCommand {}
#[derive(Clone, Debug)]
pub struct CommandPackFromType {
pub name: String,
pub id: TypeId,
pub base: bool,
}
impl PackCommand for CommandPackFromType {}
#[derive(Clone, Debug)]
pub struct CommandPackMaybe {
pub inner: Arc<PackMapType>,
}
impl PackCommand for CommandPackMaybe {}
#[derive(Clone, Debug)]
pub struct CommandPackChild {
pub inner: Arc<PackMapType>,
pub link: LinkIdent,
}
impl PackCommand for CommandPackChild {}
#[derive(Clone, Debug)]
pub struct CommandPackAggregate {
pub inner: Arc<PackMapType>,
}
impl PackCommand for CommandPackAggregate {}
#[derive(Clone, Debug)]
pub struct CommandPackMulti {
pub inner: Multi,
}
impl PackCommand for CommandPackMulti {}
impl PackMapType {
pub fn from_type<T: Sized + 'static>(base: bool) -> Self {
PackMapType::FromType(CommandPackFromType {
name: std::any::type_name::<T>().to_string(),
id: TypeId::of::<T>(),
base,
})
}
pub fn get_base_type(&self) -> TypeId {
let id = match self {
PackMapType::FromType(CommandPackFromType { base: true, .. }) => Some(self),
PackMapType::Multi(CommandPackMulti { inner }) => inner.get_base(),
_ => None,
};
let PackMapType::FromType(CommandPackFromType { id, .. }) = id.expect("no base type") else {
panic!("base type is not FromType")
};
*id
}
pub fn as_edges(
&self,
base_parent: &Option<(usize, String)>,
parent: &Option<(usize, String)>,
level: usize,
) -> Vec<(Option<String>, String)> {
match self {
PackMapType::FromType(CommandPackFromType {
name, base: true, ..
}) => {
vec![(base_parent.clone().map(|(_, s)| s), name.clone())]
}
PackMapType::FromType(CommandPackFromType {
name, base: false, ..
}) => {
vec![(parent.clone().map(|(_, s)| s), name.clone())]
}
PackMapType::Maybe(CommandPackMaybe { inner }) => {
inner.as_edges(base_parent, parent, level)
}
PackMapType::Aggregate(CommandPackAggregate { inner }) => {
inner.as_edges(base_parent, parent, level)
}
PackMapType::Child(CommandPackChild { inner, .. }) => {
inner.as_edges(parent, &None, level + 1)
}
PackMapType::Multi(CommandPackMulti { inner }) => {
let new_parent = if let Some(PackMapType::FromType(CommandPackFromType {
name,
base: true,
..
})) = inner.get_base()
{
if parent.clone().is_some_and(|(l, _)| l == level) {
parent.clone()
} else {
Some((level, name.clone()))
}
} else {
parent.clone()
};
let mut edges = Vec::new();
for t in &inner.0 {
edges.extend(t.as_edges(base_parent, &new_parent, level));
}
edges
}
}
}
}

View File

@ -1,356 +1,21 @@
use crate::model::map_type::{
CommandPackAggregate, CommandPackChild, CommandPackMaybe, CommandPackMulti, PackMapType,
};
use magnetar_calckey_model::ck; use magnetar_calckey_model::ck;
use magnetar_sdk::types::Id;
use magnetar_sdk::{Packed, Required};
use std::marker::PhantomData;
use std::sync::Arc; use std::sync::Arc;
use self::data::id::BaseId;
pub mod data; pub mod data;
pub mod db_pack;
pub mod map_type;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
struct InternalContext { pub struct PackingContext {
instance_info: Arc<ck::meta::Model>,
self_user: Option<Arc<ck::user::Model>>, self_user: Option<Arc<ck::user::Model>>,
} }
#[derive(Clone, Debug)] pub trait PackType<I>: 'static {
pub struct PackingContext<T: ?Sized> { fn extract(context: &PackingContext, data: I) -> Self;
context: Arc<InternalContext>,
data: Arc<PackMapType>,
_type: PhantomData<T>,
} }
impl<T: ?Sized> PackingContext<T> { impl PackingContext {
fn cloned<S>(&self) -> PackingContext<S> {
PackingContext {
context: self.context.clone(),
data: self.data.clone(),
_type: PhantomData,
}
}
}
pub trait ReportType: 'static {
type Input: 'static;
fn report_type() -> PackMapType;
}
pub trait PackType: 'static {
type Input: 'static;
fn extract(context: &PackingContext<Self>, data: Self::Input) -> Self;
fn is_base_type() -> bool {
false
}
}
#[derive(Clone, Debug)]
pub struct PackExtract<T>(T);
impl<Out> ReportType for PackExtract<Out>
where
Self: PackType,
<Self as PackType>::Input: ReportType + PackType,
{
type Input = <Self as PackType>::Input;
fn report_type() -> PackMapType {
<Self as PackType>::Input::report_type()
}
}
#[derive(Clone, Debug)]
pub enum LinkIdent {
Auto,
Named(String),
}
#[derive(Copy, Clone, Debug, Default)]
pub struct LinkAuto;
impl From<LinkAuto> for LinkIdent {
fn from(_: LinkAuto) -> Self {
LinkIdent::Auto
}
}
// Raw input type children
#[derive(Clone, Debug)]
pub struct PackBase<T: 'static>(T);
impl<In: 'static> ReportType for PackBase<In> {
type Input = In;
fn report_type() -> PackMapType {
PackMapType::from_type::<In>(true)
}
}
impl<In: 'static> PackType for PackBase<In> {
type Input = In;
fn extract(_context: &PackingContext<Self>, data: <Self as PackType>::Input) -> Self {
PackBase(data)
}
fn is_base_type() -> bool {
true
}
}
// Raw input type children, non-base
#[derive(Clone, Debug)]
pub struct PackExt<T: 'static>(T);
impl<In: 'static> ReportType for PackExt<In> {
type Input = In;
fn report_type() -> PackMapType {
PackMapType::from_type::<In>(false)
}
}
impl<In: 'static> PackType for PackExt<In> {
type Input = In;
fn extract(_context: &PackingContext<Self>, data: <Self as PackType>::Input) -> Self {
PackExt(data)
}
}
// Tree children
#[derive(Clone, Debug)]
pub struct PackChild<T: ReportType + PackType, L: Into<LinkIdent> + Default + 'static>(
T,
PhantomData<fn() -> L>,
);
impl<In: ReportType + PackType, L: Into<LinkIdent> + Default + 'static> ReportType
for PackChild<In, L>
{
type Input = In;
fn report_type() -> PackMapType {
PackMapType::Child(CommandPackChild {
inner: Arc::new(In::report_type()),
link: L::into(L::default()),
})
}
}
impl<In: ReportType + PackType, L: Into<LinkIdent> + Default + 'static> PackType
for PackChild<In, L>
{
type Input = In;
fn extract(_context: &PackingContext<Self>, data: <Self as PackType>::Input) -> Self {
PackChild(data, PhantomData)
}
}
// Aggregate multiple entries 1:N
#[derive(Clone, Debug)]
pub struct PackAggregate<T: ReportType + PackType>(Vec<T>);
impl<In: ReportType + PackType> ReportType for PackAggregate<In> {
type Input = In;
fn report_type() -> PackMapType {
PackMapType::Aggregate(CommandPackAggregate {
inner: Arc::new(In::report_type()),
})
}
}
impl<In: ReportType + PackType> PackType for PackAggregate<In> {
type Input = Vec<In>;
fn extract(_context: &PackingContext<Self>, data: <Self as PackType>::Input) -> Self {
PackAggregate(data)
}
}
// An item that may or may not be present
#[derive(Clone, Debug)]
pub struct PackMaybe<T: ReportType + PackType>(Option<T>);
impl<In: ReportType + PackType> ReportType for PackMaybe<In> {
type Input = In;
fn report_type() -> PackMapType {
In::report_type()
}
}
impl<In: ReportType + PackType> PackType for PackMaybe<In> {
type Input = Option<In>;
fn extract(_context: &PackingContext<Self>, data: <Self as PackType>::Input) -> Self {
PackMaybe(data)
}
}
// Multiple adjacent values
#[derive(Clone, Debug)]
pub struct PackMulti<T>(T);
macro_rules! impl_pack_multi {
($($a:ident as $b:ident),*) => {
impl<$($a,)*> ReportType for PackMulti<($($a,)*)>
where $($a: ReportType + PackType,)* {
type Input = ($(<$a as ReportType>::Input,)*);
fn report_type() -> PackMapType {
PackMapType::Multi(CommandPackMulti {
inner: vec![$(
$a::report_type(),
)*].into(),
})
}
}
impl<$($a,)*> PackType for PackMulti<($($a,)*)>
where $($a: ReportType + PackType,)* {
type Input = ($(<$a as PackType>::Input,)*);
fn extract(context: &PackingContext<Self>, ($($b,)*): <Self as PackType>::Input) -> Self {
PackMulti(($(
<$a as PackType>::extract(&context.cloned(), $b),
)*))
}
}
};
}
impl_pack_multi!();
impl_pack_multi!(A as a);
impl_pack_multi!(A as a, B as b);
impl_pack_multi!(A as a, B as b, C as c);
impl_pack_multi!(A as a, B as b, C as c, D as d);
impl_pack_multi!(A as a, B as b, C as c, D as d, E as e);
impl_pack_multi!(A as a, B as b, C as c, D as d, E as e, F as f);
impl_pack_multi!(A as a, B as b, C as c, D as d, E as e, F as f, G as g);
impl_pack_multi!(A as a, B as b, C as c, D as d, E as e, F as f, G as g, H as h);
// API packed types
pub struct PackPacked<T: Packed, In = <T as Packed>::Input>(T, PhantomData<In>);
macro_rules! impl_pack_raw_tuple {
($($a:ident),*) => {
impl<$($a,)* P: Packed> ReportType for PackPacked<P, ($($a,)*)>
where $(PackWrapper<$a>: ReportType + PackType),* {
type Input = ($($a,)*);
fn report_type() -> PackMapType {
PackMapType::Multi(CommandPackMulti {
inner: vec![
$(
PackWrapper::<$a>::report_type()
),*
].into()
})
}
}
impl<$($a,)* P: Packed<Input = ($($a,)*)>> PackType for PackPacked<P, ($($a,)*)>
where $(PackWrapper<$a>: ReportType + PackType),* {
type Input = ($($a,)*);
fn extract(_context: &PackingContext<Self>, data: <Self as PackType>::Input) -> Self {
PackPacked(P::pack_from(data), PhantomData)
}
}
};
}
impl_pack_raw_tuple!(A);
impl_pack_raw_tuple!(A, B);
impl_pack_raw_tuple!(A, B, C);
impl_pack_raw_tuple!(A, B, C, D);
impl_pack_raw_tuple!(A, B, C, D, E);
impl_pack_raw_tuple!(A, B, C, D, E, F);
impl_pack_raw_tuple!(A, B, C, D, E, F, G);
impl_pack_raw_tuple!(A, B, C, D, E, F, G, H);
// Helper wrapper for Multi components
#[derive(Clone, Debug)]
pub struct PackWrapper<T>(T);
impl<In: 'static> ReportType for PackWrapper<Required<In>>
where
PackExtract<In>: ReportType + PackType,
{
type Input = <PackExtract<In> as ReportType>::Input;
fn report_type() -> PackMapType {
PackExtract::<In>::report_type()
}
}
impl<In: 'static> PackType for PackWrapper<Required<In>>
where
PackExtract<In>: ReportType + PackType,
{
type Input = <PackExtract<In> as PackType>::Input;
fn extract(context: &PackingContext<Self>, data: Self::Input) -> Self {
PackWrapper(Required(
PackExtract::<In>::extract(&context.cloned(), data).0,
))
}
}
impl<In: 'static> ReportType for PackWrapper<Option<In>>
where
PackExtract<In>: ReportType + PackType,
{
type Input = In;
fn report_type() -> PackMapType {
PackMapType::Maybe(CommandPackMaybe {
inner: Arc::new(PackExtract::<In>::report_type()),
})
}
}
impl<In: 'static> PackType for PackWrapper<Option<In>>
where
PackExtract<In>: ReportType + PackType,
{
type Input = Option<In>;
fn extract(context: &PackingContext<Self>, data: Self::Input) -> Self {
PackWrapper(
PackMaybe::<PackExtract<In>>::extract(&context.cloned(), data.map(PackExtract))
.0
.map(|PackExtract(x)| x),
)
}
}
// Concrete extractable types
impl PackType for PackExtract<Id> {
type Input = PackExt<Box<dyn BaseId>>;
fn extract(_context: &PackingContext<Self>, PackExt(data): <Self as PackType>::Input) -> Self {
PackExtract(Id {
id: data.get_id().to_owned(),
})
}
}
// Packing context that gets passed in
impl<T> PackingContext<T> {
fn self_user(&self) -> Option<&ck::user::Model> { fn self_user(&self) -> Option<&ck::user::Model> {
self.context.self_user.as_deref() self.self_user.as_deref()
} }
fn is_self(&self, user: &ck::user::Model) -> bool { fn is_self(&self, user: &ck::user::Model) -> bool {
@ -358,37 +23,3 @@ impl<T> PackingContext<T> {
.is_some_and(|self_user| self_user.id == user.id) .is_some_and(|self_user| self_user.id == user.id)
} }
} }
pub struct Packer<T> {
context: PackingContext<T>,
}
pub fn pack_from_db<T: PackType + ReportType>(
self_user: Option<Arc<ck::user::Model>>,
) -> Packer<T> {
let types = T::report_type();
let context = PackingContext {
context: Arc::new(InternalContext { self_user }),
data: Arc::new(types),
_type: PhantomData,
};
Packer { context }
}
#[cfg(test)]
mod tests {
use super::*;
use magnetar_sdk::types::user::PackUserSelfMaybeAll;
#[test]
fn test_pack_type() {
let types = <PackPacked<PackUserSelfMaybeAll> as ReportType>::report_type();
println!("{:?}", types);
let edges = types.as_edges(&None, &None, 0);
for (from, to) in edges {
println!("{:?} --> {:?}", from, to);
}
}
}