diff --git a/magnetar_sdk/macros/src/lib.rs b/magnetar_sdk/macros/src/lib.rs index 34b07c1..4792975 100644 --- a/magnetar_sdk/macros/src/lib.rs +++ b/magnetar_sdk/macros/src/lib.rs @@ -1,8 +1,11 @@ use proc_macro::TokenStream; +use quote::ToTokens; use std::collections::HashSet; use syn::parse::Parse; use syn::punctuated::Punctuated; -use syn::{Expr, ExprLit, ExprPath, Ident, Lit, Meta, MetaNameValue, Token, Type}; +use syn::{ + Expr, ExprLit, ExprPath, Ident, Lit, Meta, MetaNameValue, PathArguments, Token, Type, TypePath, +}; struct Field { name: Ident, @@ -49,7 +52,9 @@ pub fn pack(item: TokenStream) -> TokenStream { let fields = &parsed.fields; let names = fields.iter().map(|f| &f.name); - let types = fields.iter().map(|f| &f.ty); + let types_decl = fields.iter().map(|f| &f.ty); + let types_struct = fields.iter().map(|f| &f.ty); + let types_deps = fields.iter().map(|f| &f.ty); let types_packed = fields.iter().map(|f| &f.ty); let names_packed = fields.iter().map(|f| &f.name); @@ -61,13 +66,19 @@ pub fn pack(item: TokenStream) -> TokenStream { (#(#types_packed,)*) }; + let name = struct_name.to_string(); + + let export_name = Ident::new( + &format!("export_bindings_{}", struct_name.to_string().to_lowercase()), + struct_name.span(), + ); + let expanded = quote::quote! { - #[derive(Clone, Debug, Deserialize, Serialize, TS)] - #[ts(export, export_to = #export_path)] + #[derive(Clone, Debug, Deserialize, Serialize)] pub struct #struct_name { #( #[serde(flatten)] - pub #names: #types + pub #names: #types_struct ),* } @@ -82,6 +93,40 @@ pub fn pack(item: TokenStream) -> TokenStream { } } } + + #[cfg(test)] + #[test] + fn #export_name() { + #struct_name::export().expect("could not export type"); + } + + impl TS for #struct_name { + const EXPORT_TO: Option<&'static str> = Some(#export_path); + + fn decl() -> String { + format!( + "type {} = {};", + Self::name(), + vec![#(<#types_decl as TS>::inline()),*].join(" & ") + ) + } + + fn name() -> String { + #name.to_string() + } + + fn dependencies() -> Vec { + vec![ + #( + <#types_deps as TS>::dependencies() + ),* + ].into_iter().flatten().collect::>() + } + + fn transparent() -> bool { + false + } + } }; TokenStream::from(expanded) @@ -109,6 +154,8 @@ pub fn derive_endpoint(item: TokenStream) -> TokenStream { let mut method = None; let mut request = None; let mut response = None; + let mut request_args = None; + let mut response_args = None; let mut found = HashSet::new(); @@ -145,39 +192,73 @@ pub fn derive_endpoint(item: TokenStream) -> TokenStream { match key.to_string().as_ref() { "name" => { - let Expr::Lit(ExprLit { lit: Lit::Str(new_name), .. }) = value else { - panic!("expected a string literal"); - }; + let Expr::Lit(ExprLit { + lit: Lit::Str(new_name), + .. + }) = value + else { + panic!("expected a string literal"); + }; name = new_name.value(); } "endpoint" => { - let Expr::Lit(ExprLit { lit: Lit::Str(endpoint_val), .. }) = value else { - panic!("expected a string literal"); - }; + let Expr::Lit(ExprLit { + lit: Lit::Str(endpoint_val), + .. + }) = value + else { + panic!("expected a string literal"); + }; endpoint = Some(endpoint_val.value()); } "method" => { let Expr::Path(ExprPath { path, .. }) = value else { - panic!("expected a an identifier"); - }; + panic!("expected a an identifier"); + }; method = Some(path); } "request" => { - let Expr::Path(ExprPath { path, .. }) = value else { - panic!("expected a an identifier"); - }; + let Expr::Lit(ExprLit { + lit: Lit::Str(str_lit), + .. + }) = value + else { + panic!("expected a an identifier"); + }; - request = Some(path); + let type_tok: TokenStream = str_lit.value().parse().unwrap(); + let req_typ = syn::parse::(type_tok).unwrap(); + + if let Some(seg_last) = req_typ.path.segments.last() { + if let PathArguments::AngleBracketed(args) = &seg_last.arguments { + let args_res = args.args.iter().cloned().collect::>(); + request_args = Some(args_res); + } + } + + request = Some(req_typ); } "response" => { - let Expr::Path(ExprPath { path, .. }) = value else { - panic!("expected an identifier") - }; + let Expr::Lit(ExprLit { + lit: Lit::Str(str_lit), + .. + }) = value + else { + panic!("expected a an identifier"); + }; - response = Some(path); + let type_tok: TokenStream = str_lit.value().parse().unwrap(); + let res_typ = syn::parse::(type_tok).unwrap(); + if let Some(seg_last) = res_typ.path.segments.last() { + if let PathArguments::AngleBracketed(args) = &seg_last.arguments { + let args_res = args.args.iter().cloned().collect::>(); + response_args = Some(args_res); + } + } + response = Some(res_typ); } _ => panic!("unknown attribute: {}", key), } @@ -197,6 +278,39 @@ pub fn derive_endpoint(item: TokenStream) -> TokenStream { struct_name.span(), ); + let call_name_req = if let Some(ref args) = request_args { + let args_str = args + .iter() + .map(|a| a.into_token_stream().to_string()) + .collect::>(); + + quote::quote! { + <#struct_name as Endpoint>::Request::name_with_type_args(vec![#( #args_str.to_string() ),*]) + } + } else { + quote::quote! { + <#struct_name as Endpoint>::Request::name() + } + }; + + let call_name_res = if let Some(ref args) = response_args { + let args_str = args + .iter() + .map(|a| a.into_token_stream().to_string()) + .collect::>(); + + quote::quote! { + <#struct_name as Endpoint>::Response::name_with_type_args(vec![#( #args_str.to_string() ),*]) + } + } else { + quote::quote! { + <#struct_name as Endpoint>::Response::name() + } + }; + + let req_args_flat = request_args.unwrap_or_default(); + let res_args_flat = response_args.unwrap_or_default(); + let expanded = quote::quote! { impl Default for #struct_name { fn default() -> Self { @@ -234,8 +348,8 @@ pub fn derive_endpoint(item: TokenStream) -> TokenStream { Self::name(), Self::ENDPOINT, Self::METHOD, - <#struct_name as Endpoint>::Request::name(), - <#struct_name as Endpoint>::Response::name() + #call_name_req, + #call_name_res ) } @@ -245,9 +359,15 @@ pub fn derive_endpoint(item: TokenStream) -> TokenStream { fn dependencies() -> Vec { vec![ - ts_rs::Dependency::from_ty::<<#struct_name as Endpoint>::Request>().unwrap(), - ts_rs::Dependency::from_ty::<<#struct_name as Endpoint>::Response>().unwrap(), - ] + ts_rs::Dependency::from_ty::<<#struct_name as Endpoint>::Request>(), + ts_rs::Dependency::from_ty::<<#struct_name as Endpoint>::Response>(), + #( + ts_rs::Dependency::from_ty::<#req_args_flat>(), + )* + #( + ts_rs::Dependency::from_ty::<#res_args_flat>(), + )* + ].into_iter().flatten().collect::>() } fn transparent() -> bool { diff --git a/magnetar_sdk/src/endpoints/note.rs b/magnetar_sdk/src/endpoints/note.rs index 9766294..a80671a 100644 --- a/magnetar_sdk/src/endpoints/note.rs +++ b/magnetar_sdk/src/endpoints/note.rs @@ -19,7 +19,7 @@ pub struct NoteByIdReq { #[endpoint( endpoint = "/notes/:id", method = Method::GET, - request = NoteByIdReq, - response = PackNoteMaybeFull + request = "NoteByIdReq", + response = "PackNoteMaybeFull" )] pub struct GetNoteById; diff --git a/magnetar_sdk/src/endpoints/timeline.rs b/magnetar_sdk/src/endpoints/timeline.rs index addeb36..680097f 100644 --- a/magnetar_sdk/src/endpoints/timeline.rs +++ b/magnetar_sdk/src/endpoints/timeline.rs @@ -12,7 +12,6 @@ use ts_rs::TS; pub struct GetTimelineReq { #[serde(default = "default_timeline_limit")] pub limit: U64Range<1, 100>, - #[serde(flatten)] pub filter: Option, } @@ -20,16 +19,11 @@ fn default_timeline_limit() -> U64Range); - #[derive(Endpoint)] #[endpoint( endpoint = "/timeline", method = Method::GET, - request = GetTimelineReq, - response = Vec::, + request = "GetTimelineReq", + response = "Vec", )] pub struct GetTimeline; diff --git a/magnetar_sdk/src/endpoints/user.rs b/magnetar_sdk/src/endpoints/user.rs index 540b3c6..5c89be0 100644 --- a/magnetar_sdk/src/endpoints/user.rs +++ b/magnetar_sdk/src/endpoints/user.rs @@ -24,8 +24,8 @@ pub struct UserSelfReq { #[endpoint( endpoint = "/users/@self", method = Method::GET, - request = UserSelfReq, - response = PackUserSelfMaybeAll + request = "UserSelfReq", + response = "PackUserSelfMaybeAll" )] pub struct GetUserSelf; @@ -49,8 +49,8 @@ pub struct UserByIdReq { #[endpoint( endpoint = "/users/:user_id", method = Method::GET, - request = UserByIdReq, - response = PackUserMaybeAll + request = "UserByIdReq", + response = "PackUserMaybeAll" )] pub struct GetUserById; @@ -60,7 +60,7 @@ pub struct GetUserById; #[endpoint( endpoint = "/users/by-acct/:user_id", method = Method::GET, - request = UserByIdReq, - response = PackUserMaybeAll + request = "UserByIdReq", + response = "PackUserMaybeAll" )] pub struct GetUserByAcct; diff --git a/magnetar_sdk/src/lib.rs b/magnetar_sdk/src/lib.rs index 79189ff..f2a954d 100644 --- a/magnetar_sdk/src/lib.rs +++ b/magnetar_sdk/src/lib.rs @@ -1,4 +1,6 @@ +use chrono::format; use serde::{Deserialize, Serialize}; +use std::ops::{Deref, DerefMut}; use ts_rs::TS; pub use magnetar_mmm_parser as mmm; @@ -6,10 +8,84 @@ pub mod endpoints; pub mod types; pub mod util_types; -#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize, TS)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)] #[repr(transparent)] pub struct Required(pub T); +impl TS for Required { + const EXPORT_TO: Option<&'static str> = None; + + fn decl() -> String { + unimplemented!() + } + + fn name() -> String { + T::name() + } + + fn inline() -> String { + T::name() + } + + fn inline_flattened() -> String { + unimplemented!() + } + + fn dependencies() -> Vec { + ts_rs::Dependency::from_ty::().into_iter().collect() + } + + fn transparent() -> bool { + false + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)] +#[repr(transparent)] +pub struct Optional(pub Option); + +impl TS for Optional { + const EXPORT_TO: Option<&'static str> = None; + + fn decl() -> String { + unimplemented!() + } + + fn name() -> String { + T::name() + } + + fn inline() -> String { + format!("Partial<{}>", T::name()) + } + + fn inline_flattened() -> String { + unimplemented!() + } + + fn dependencies() -> Vec { + ts_rs::Dependency::from_ty::().into_iter().collect() + } + + fn transparent() -> bool { + false + } +} + +impl Deref for Optional { + type Target = Option; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for Optional { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + pub trait Packed: 'static { type Input: 'static; diff --git a/magnetar_sdk/src/types/note.rs b/magnetar_sdk/src/types/note.rs index da569f3..30de780 100644 --- a/magnetar_sdk/src/types/note.rs +++ b/magnetar_sdk/src/types/note.rs @@ -1,7 +1,7 @@ use crate::types::emoji::EmojiContext; use crate::types::user::PackUserBase; use crate::types::{Id, MmXml}; -use crate::{Packed, Required}; +use crate::{Optional, Packed, Required}; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use ts_rs::TS; @@ -89,7 +89,7 @@ pub struct NoteAttachmentExt { pack!( PackNoteMaybeAttachments, - Required as id & Required as note & Option as user_context & Option as attachment + Required as id & Required as note & Optional as user_context & Optional as attachment ); #[derive(Clone, Debug, Deserialize, Serialize, TS)] @@ -100,10 +100,11 @@ pub struct NoteDetailExt { pack!( PackNoteMaybeFull, - Required as id & Required as note & Option as user_context & Option as attachment & Option as detail + Required as id & Required as note & Optional as user_context & Optional as attachment & Optional as detail ); #[derive(Clone, Debug, Deserialize, Serialize, TS)] +#[ts(export)] #[serde(untagged)] pub enum Reaction { Unicode(String), @@ -118,6 +119,7 @@ pub enum Reaction { } #[derive(Clone, Debug, Deserialize, Serialize, TS)] +#[ts(export)] #[serde(untagged)] pub enum ReactionPair { WithoutContext(Reaction, u64), diff --git a/magnetar_sdk/src/types/user.rs b/magnetar_sdk/src/types/user.rs index bcfdaa2..d4ee9bd 100644 --- a/magnetar_sdk/src/types/user.rs +++ b/magnetar_sdk/src/types/user.rs @@ -1,6 +1,6 @@ use crate::types::emoji::EmojiContext; use crate::types::{Id, MmXml, NotificationSettings}; -use crate::{Packed, Required}; +use crate::{Optional, Packed, Required}; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use ts_rs::TS; @@ -82,7 +82,6 @@ pub struct UserProfileExt { pub banner_blurhash: Option, pub has_public_reactions: bool, - pub emojis: EmojiContext, } #[derive(Clone, Debug, Deserialize, Serialize, TS)] @@ -164,16 +163,24 @@ pack!( PackUserMaybeAll, Required as id & Required as user - & Option as profile - & Option as pins - & Option as detail - & Option as relation - & Option as auth + & Optional as profile + & Optional as pins + & Optional as detail + & Optional as relation + & Optional as auth ); impl From for PackUserMaybeAll { fn from(value: PackUserBase) -> Self { - Self::pack_from((value.id, value.user, None, None, None, None, None)) + Self::pack_from(( + value.id, + value.user, + Optional(None), + Optional(None), + Optional(None), + Optional(None), + Optional(None), + )) } } @@ -181,14 +188,21 @@ pack!( PackUserSelfMaybeAll, Required as id & Required as user - & Option as profile - & Option as pins - & Option as detail - & Option as secrets + & Optional as profile + & Optional as pins + & Optional as detail + & Optional as secrets ); impl From for PackUserSelfMaybeAll { fn from(value: PackUserBase) -> Self { - Self::pack_from((value.id, value.user, None, None, None, None)) + Self::pack_from(( + value.id, + value.user, + Optional(None), + Optional(None), + Optional(None), + Optional(None), + )) } } diff --git a/magnetar_sdk/src/util_types.rs b/magnetar_sdk/src/util_types.rs index 220c207..d8547a1 100644 --- a/magnetar_sdk/src/util_types.rs +++ b/magnetar_sdk/src/util_types.rs @@ -17,7 +17,7 @@ impl TS for U64Range { } fn dependencies() -> Vec { - vec![ts_rs::Dependency::from_ty::().unwrap()] + vec![] } fn transparent() -> bool { diff --git a/src/model/data/user.rs b/src/model/data/user.rs index 608d07a..a49ea80 100644 --- a/src/model/data/user.rs +++ b/src/model/data/user.rs @@ -71,7 +71,6 @@ pub struct UserProfileExtSource<'a> { pub profile_fields: &'a Vec, pub description_mm: Option<&'a MmXml>, pub relation: Option<&'a UserRelationExt>, - pub emoji_context: &'a EmojiContext, } impl PackType> for UserProfileExt { @@ -83,7 +82,6 @@ impl PackType> for UserProfileExt { profile_fields, description_mm, relation, - emoji_context, }: UserProfileExtSource, ) -> Self { let follow_visibility = match profile.ff_visibility { @@ -114,7 +112,6 @@ impl PackType> for UserProfileExt { banner_color: None, banner_blurhash: None, has_public_reactions: profile.public_reactions, - emojis: emoji_context.clone(), } } } diff --git a/src/model/processing/note.rs b/src/model/processing/note.rs index 270d269..cc7dda5 100644 --- a/src/model/processing/note.rs +++ b/src/model/processing/note.rs @@ -27,7 +27,7 @@ use magnetar_sdk::types::note::{ PackNoteMaybeAttachments, PackNoteMaybeFull, PackPollBase, PollBase, Reaction, ReactionPair, }; use magnetar_sdk::types::{Id, MmXml}; -use magnetar_sdk::{mmm, Packed, Required}; +use magnetar_sdk::{mmm, Optional, Packed, Required}; use serde::Deserialize; use tokio::try_join; @@ -412,8 +412,8 @@ impl NoteModel { Ok(PackNoteMaybeAttachments::pack_from(( id, note, - self.extract_interaction(ctx, note_data)?, - attachments_pack.map(|attachments| { + Optional(self.extract_interaction(ctx, note_data)?), + Optional(attachments_pack.map(|attachments| { NoteAttachmentExt::extract( ctx, NoteAttachmentSource { @@ -421,7 +421,7 @@ impl NoteModel { poll: poll_pack.as_ref(), }, ) - }), + })), ))) } @@ -505,7 +505,7 @@ impl NoteModel { note, user_context, attachment, - detail, + Optional(detail), )))) } } diff --git a/src/nodeinfo.rs b/src/nodeinfo.rs index c82153d..6111307 100644 --- a/src/nodeinfo.rs +++ b/src/nodeinfo.rs @@ -104,6 +104,7 @@ mod tests { async fn test_nodeinfo() { std::env::set_var("MAG_C_HOST", "nattyarch.local"); std::env::set_var("MAG_C_DATABASE_URL", "dummy"); + std::env::set_var("MAG_C_REDIS_URL", "dummy"); let config = MagnetarConfig::default(); let config_ref = Box::leak(Box::new(config));