Fixed TS_RS

This commit is contained in:
Natty 2023-11-02 20:40:12 +01:00
parent cf04146d2f
commit 5a8dc04915
Signed by: natty
GPG Key ID: BF6CB659ADEE60EC
11 changed files with 272 additions and 68 deletions

View File

@ -1,8 +1,11 @@
use proc_macro::TokenStream; use proc_macro::TokenStream;
use quote::ToTokens;
use std::collections::HashSet; use std::collections::HashSet;
use syn::parse::Parse; use syn::parse::Parse;
use syn::punctuated::Punctuated; 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 { struct Field {
name: Ident, name: Ident,
@ -49,7 +52,9 @@ pub fn pack(item: TokenStream) -> TokenStream {
let fields = &parsed.fields; let fields = &parsed.fields;
let names = fields.iter().map(|f| &f.name); 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 types_packed = fields.iter().map(|f| &f.ty);
let names_packed = fields.iter().map(|f| &f.name); let names_packed = fields.iter().map(|f| &f.name);
@ -61,13 +66,19 @@ pub fn pack(item: TokenStream) -> TokenStream {
(#(#types_packed,)*) (#(#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! { let expanded = quote::quote! {
#[derive(Clone, Debug, Deserialize, Serialize, TS)] #[derive(Clone, Debug, Deserialize, Serialize)]
#[ts(export, export_to = #export_path)]
pub struct #struct_name { pub struct #struct_name {
#( #(
#[serde(flatten)] #[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<ts_rs::Dependency> {
vec![
#(
<#types_deps as TS>::dependencies()
),*
].into_iter().flatten().collect::<Vec<_>>()
}
fn transparent() -> bool {
false
}
}
}; };
TokenStream::from(expanded) TokenStream::from(expanded)
@ -109,6 +154,8 @@ pub fn derive_endpoint(item: TokenStream) -> TokenStream {
let mut method = None; let mut method = None;
let mut request = None; let mut request = None;
let mut response = None; let mut response = None;
let mut request_args = None;
let mut response_args = None;
let mut found = HashSet::new(); let mut found = HashSet::new();
@ -145,14 +192,22 @@ pub fn derive_endpoint(item: TokenStream) -> TokenStream {
match key.to_string().as_ref() { match key.to_string().as_ref() {
"name" => { "name" => {
let Expr::Lit(ExprLit { lit: Lit::Str(new_name), .. }) = value else { let Expr::Lit(ExprLit {
lit: Lit::Str(new_name),
..
}) = value
else {
panic!("expected a string literal"); panic!("expected a string literal");
}; };
name = new_name.value(); name = new_name.value();
} }
"endpoint" => { "endpoint" => {
let Expr::Lit(ExprLit { lit: Lit::Str(endpoint_val), .. }) = value else { let Expr::Lit(ExprLit {
lit: Lit::Str(endpoint_val),
..
}) = value
else {
panic!("expected a string literal"); panic!("expected a string literal");
}; };
@ -166,18 +221,44 @@ pub fn derive_endpoint(item: TokenStream) -> TokenStream {
method = Some(path); method = Some(path);
} }
"request" => { "request" => {
let Expr::Path(ExprPath { path, .. }) = value else { let Expr::Lit(ExprLit {
lit: Lit::Str(str_lit),
..
}) = value
else {
panic!("expected a an identifier"); panic!("expected a an identifier");
}; };
request = Some(path); let type_tok: TokenStream = str_lit.value().parse().unwrap();
let req_typ = syn::parse::<TypePath>(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::<Vec<_>>();
request_args = Some(args_res);
}
}
request = Some(req_typ);
} }
"response" => { "response" => {
let Expr::Path(ExprPath { path, .. }) = value else { let Expr::Lit(ExprLit {
panic!("expected an identifier") 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::<TypePath>(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::<Vec<_>>();
response_args = Some(args_res);
}
}
response = Some(res_typ);
} }
_ => panic!("unknown attribute: {}", key), _ => panic!("unknown attribute: {}", key),
} }
@ -197,6 +278,39 @@ pub fn derive_endpoint(item: TokenStream) -> TokenStream {
struct_name.span(), 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::<Vec<_>>();
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::<Vec<_>>();
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! { let expanded = quote::quote! {
impl Default for #struct_name { impl Default for #struct_name {
fn default() -> Self { fn default() -> Self {
@ -234,8 +348,8 @@ pub fn derive_endpoint(item: TokenStream) -> TokenStream {
Self::name(), Self::name(),
Self::ENDPOINT, Self::ENDPOINT,
Self::METHOD, Self::METHOD,
<#struct_name as Endpoint>::Request::name(), #call_name_req,
<#struct_name as Endpoint>::Response::name() #call_name_res
) )
} }
@ -245,9 +359,15 @@ pub fn derive_endpoint(item: TokenStream) -> TokenStream {
fn dependencies() -> Vec<ts_rs::Dependency> { fn dependencies() -> Vec<ts_rs::Dependency> {
vec![ vec![
ts_rs::Dependency::from_ty::<<#struct_name as Endpoint>::Request>().unwrap(), ts_rs::Dependency::from_ty::<<#struct_name as Endpoint>::Request>(),
ts_rs::Dependency::from_ty::<<#struct_name as Endpoint>::Response>().unwrap(), 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::<Vec<_>>()
} }
fn transparent() -> bool { fn transparent() -> bool {

View File

@ -19,7 +19,7 @@ pub struct NoteByIdReq {
#[endpoint( #[endpoint(
endpoint = "/notes/:id", endpoint = "/notes/:id",
method = Method::GET, method = Method::GET,
request = NoteByIdReq, request = "NoteByIdReq",
response = PackNoteMaybeFull response = "PackNoteMaybeFull"
)] )]
pub struct GetNoteById; pub struct GetNoteById;

View File

@ -12,7 +12,6 @@ use ts_rs::TS;
pub struct GetTimelineReq { pub struct GetTimelineReq {
#[serde(default = "default_timeline_limit")] #[serde(default = "default_timeline_limit")]
pub limit: U64Range<1, 100>, pub limit: U64Range<1, 100>,
#[serde(flatten)]
pub filter: Option<NoteListFilter>, pub filter: Option<NoteListFilter>,
} }
@ -20,16 +19,11 @@ fn default_timeline_limit<const MIN: u64, const MAX: u64>() -> U64Range<MIN, MAX
15.try_into().unwrap() 15.try_into().unwrap()
} }
#[derive(Serialize, Deserialize, TS)]
#[ts(export)]
#[repr(transparent)]
pub struct GetTimelineRes(pub Vec<PackNoteMaybeFull>);
#[derive(Endpoint)] #[derive(Endpoint)]
#[endpoint( #[endpoint(
endpoint = "/timeline", endpoint = "/timeline",
method = Method::GET, method = Method::GET,
request = GetTimelineReq, request = "GetTimelineReq",
response = Vec::<PackNoteMaybeFull>, response = "Vec<PackNoteMaybeFull>",
)] )]
pub struct GetTimeline; pub struct GetTimeline;

View File

@ -24,8 +24,8 @@ pub struct UserSelfReq {
#[endpoint( #[endpoint(
endpoint = "/users/@self", endpoint = "/users/@self",
method = Method::GET, method = Method::GET,
request = UserSelfReq, request = "UserSelfReq",
response = PackUserSelfMaybeAll response = "PackUserSelfMaybeAll"
)] )]
pub struct GetUserSelf; pub struct GetUserSelf;
@ -49,8 +49,8 @@ pub struct UserByIdReq {
#[endpoint( #[endpoint(
endpoint = "/users/:user_id", endpoint = "/users/:user_id",
method = Method::GET, method = Method::GET,
request = UserByIdReq, request = "UserByIdReq",
response = PackUserMaybeAll response = "PackUserMaybeAll"
)] )]
pub struct GetUserById; pub struct GetUserById;
@ -60,7 +60,7 @@ pub struct GetUserById;
#[endpoint( #[endpoint(
endpoint = "/users/by-acct/:user_id", endpoint = "/users/by-acct/:user_id",
method = Method::GET, method = Method::GET,
request = UserByIdReq, request = "UserByIdReq",
response = PackUserMaybeAll response = "PackUserMaybeAll"
)] )]
pub struct GetUserByAcct; pub struct GetUserByAcct;

View File

@ -1,4 +1,6 @@
use chrono::format;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::ops::{Deref, DerefMut};
use ts_rs::TS; use ts_rs::TS;
pub use magnetar_mmm_parser as mmm; pub use magnetar_mmm_parser as mmm;
@ -6,10 +8,84 @@ pub mod endpoints;
pub mod types; pub mod types;
pub mod util_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)] #[repr(transparent)]
pub struct Required<T>(pub T); pub struct Required<T>(pub T);
impl<T: TS + 'static> TS for Required<T> {
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> {
ts_rs::Dependency::from_ty::<T>().into_iter().collect()
}
fn transparent() -> bool {
false
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[repr(transparent)]
pub struct Optional<T>(pub Option<T>);
impl<T: TS + 'static> TS for Optional<T> {
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> {
ts_rs::Dependency::from_ty::<T>().into_iter().collect()
}
fn transparent() -> bool {
false
}
}
impl<T: TS> Deref for Optional<T> {
type Target = Option<T>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T: TS> DerefMut for Optional<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
pub trait Packed: 'static { pub trait Packed: 'static {
type Input: 'static; type Input: 'static;

View File

@ -1,7 +1,7 @@
use crate::types::emoji::EmojiContext; use crate::types::emoji::EmojiContext;
use crate::types::user::PackUserBase; use crate::types::user::PackUserBase;
use crate::types::{Id, MmXml}; use crate::types::{Id, MmXml};
use crate::{Packed, Required}; use crate::{Optional, Packed, Required};
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use ts_rs::TS; use ts_rs::TS;
@ -89,7 +89,7 @@ pub struct NoteAttachmentExt {
pack!( pack!(
PackNoteMaybeAttachments, PackNoteMaybeAttachments,
Required<Id> as id & Required<NoteBase> as note & Option<NoteSelfContextExt> as user_context & Option<NoteAttachmentExt> as attachment Required<Id> as id & Required<NoteBase> as note & Optional<NoteSelfContextExt> as user_context & Optional<NoteAttachmentExt> as attachment
); );
#[derive(Clone, Debug, Deserialize, Serialize, TS)] #[derive(Clone, Debug, Deserialize, Serialize, TS)]
@ -100,10 +100,11 @@ pub struct NoteDetailExt {
pack!( pack!(
PackNoteMaybeFull, PackNoteMaybeFull,
Required<Id> as id & Required<NoteBase> as note & Option<NoteSelfContextExt> as user_context & Option<NoteAttachmentExt> as attachment & Option<NoteDetailExt> as detail Required<Id> as id & Required<NoteBase> as note & Optional<NoteSelfContextExt> as user_context & Optional<NoteAttachmentExt> as attachment & Optional<NoteDetailExt> as detail
); );
#[derive(Clone, Debug, Deserialize, Serialize, TS)] #[derive(Clone, Debug, Deserialize, Serialize, TS)]
#[ts(export)]
#[serde(untagged)] #[serde(untagged)]
pub enum Reaction { pub enum Reaction {
Unicode(String), Unicode(String),
@ -118,6 +119,7 @@ pub enum Reaction {
} }
#[derive(Clone, Debug, Deserialize, Serialize, TS)] #[derive(Clone, Debug, Deserialize, Serialize, TS)]
#[ts(export)]
#[serde(untagged)] #[serde(untagged)]
pub enum ReactionPair { pub enum ReactionPair {
WithoutContext(Reaction, u64), WithoutContext(Reaction, u64),

View File

@ -1,6 +1,6 @@
use crate::types::emoji::EmojiContext; use crate::types::emoji::EmojiContext;
use crate::types::{Id, MmXml, NotificationSettings}; use crate::types::{Id, MmXml, NotificationSettings};
use crate::{Packed, Required}; use crate::{Optional, Packed, Required};
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use ts_rs::TS; use ts_rs::TS;
@ -82,7 +82,6 @@ pub struct UserProfileExt {
pub banner_blurhash: Option<String>, pub banner_blurhash: Option<String>,
pub has_public_reactions: bool, pub has_public_reactions: bool,
pub emojis: EmojiContext,
} }
#[derive(Clone, Debug, Deserialize, Serialize, TS)] #[derive(Clone, Debug, Deserialize, Serialize, TS)]
@ -164,16 +163,24 @@ pack!(
PackUserMaybeAll, PackUserMaybeAll,
Required<Id> as id Required<Id> as id
& Required<UserBase> as user & Required<UserBase> as user
& Option<UserProfileExt> as profile & Optional<UserProfileExt> as profile
& Option<UserProfilePinsEx> as pins & Optional<UserProfilePinsEx> as pins
& Option<UserDetailExt> as detail & Optional<UserDetailExt> as detail
& Option<UserRelationExt> as relation & Optional<UserRelationExt> as relation
& Option<UserAuthOverviewExt> as auth & Optional<UserAuthOverviewExt> as auth
); );
impl From<PackUserBase> for PackUserMaybeAll { impl From<PackUserBase> for PackUserMaybeAll {
fn from(value: PackUserBase) -> Self { 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, PackUserSelfMaybeAll,
Required<Id> as id Required<Id> as id
& Required<UserBase> as user & Required<UserBase> as user
& Option<UserProfileExt> as profile & Optional<UserProfileExt> as profile
& Option<UserProfilePinsEx> as pins & Optional<UserProfilePinsEx> as pins
& Option<UserDetailExt> as detail & Optional<UserDetailExt> as detail
& Option<UserSecretsExt> as secrets & Optional<UserSecretsExt> as secrets
); );
impl From<PackUserBase> for PackUserSelfMaybeAll { impl From<PackUserBase> for PackUserSelfMaybeAll {
fn from(value: PackUserBase) -> Self { 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),
))
} }
} }

View File

@ -17,7 +17,7 @@ impl<const MIN: u64, const MAX: u64> TS for U64Range<MIN, MAX> {
} }
fn dependencies() -> Vec<ts_rs::Dependency> { fn dependencies() -> Vec<ts_rs::Dependency> {
vec![ts_rs::Dependency::from_ty::<u64>().unwrap()] vec![]
} }
fn transparent() -> bool { fn transparent() -> bool {

View File

@ -71,7 +71,6 @@ pub struct UserProfileExtSource<'a> {
pub profile_fields: &'a Vec<ProfileField>, pub profile_fields: &'a Vec<ProfileField>,
pub description_mm: Option<&'a MmXml>, pub description_mm: Option<&'a MmXml>,
pub relation: Option<&'a UserRelationExt>, pub relation: Option<&'a UserRelationExt>,
pub emoji_context: &'a EmojiContext,
} }
impl PackType<UserProfileExtSource<'_>> for UserProfileExt { impl PackType<UserProfileExtSource<'_>> for UserProfileExt {
@ -83,7 +82,6 @@ impl PackType<UserProfileExtSource<'_>> for UserProfileExt {
profile_fields, profile_fields,
description_mm, description_mm,
relation, relation,
emoji_context,
}: UserProfileExtSource, }: UserProfileExtSource,
) -> Self { ) -> Self {
let follow_visibility = match profile.ff_visibility { let follow_visibility = match profile.ff_visibility {
@ -114,7 +112,6 @@ impl PackType<UserProfileExtSource<'_>> for 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,
emojis: emoji_context.clone(),
} }
} }
} }

View File

@ -27,7 +27,7 @@ use magnetar_sdk::types::note::{
PackNoteMaybeAttachments, PackNoteMaybeFull, PackPollBase, PollBase, Reaction, ReactionPair, PackNoteMaybeAttachments, PackNoteMaybeFull, PackPollBase, PollBase, Reaction, ReactionPair,
}; };
use magnetar_sdk::types::{Id, MmXml}; use magnetar_sdk::types::{Id, MmXml};
use magnetar_sdk::{mmm, Packed, Required}; use magnetar_sdk::{mmm, Optional, Packed, Required};
use serde::Deserialize; use serde::Deserialize;
use tokio::try_join; use tokio::try_join;
@ -412,8 +412,8 @@ impl NoteModel {
Ok(PackNoteMaybeAttachments::pack_from(( Ok(PackNoteMaybeAttachments::pack_from((
id, id,
note, note,
self.extract_interaction(ctx, note_data)?, Optional(self.extract_interaction(ctx, note_data)?),
attachments_pack.map(|attachments| { Optional(attachments_pack.map(|attachments| {
NoteAttachmentExt::extract( NoteAttachmentExt::extract(
ctx, ctx,
NoteAttachmentSource { NoteAttachmentSource {
@ -421,7 +421,7 @@ impl NoteModel {
poll: poll_pack.as_ref(), poll: poll_pack.as_ref(),
}, },
) )
}), })),
))) )))
} }
@ -505,7 +505,7 @@ impl NoteModel {
note, note,
user_context, user_context,
attachment, attachment,
detail, Optional(detail),
)))) ))))
} }
} }

View File

@ -104,6 +104,7 @@ mod tests {
async fn test_nodeinfo() { async fn test_nodeinfo() {
std::env::set_var("MAG_C_HOST", "nattyarch.local"); std::env::set_var("MAG_C_HOST", "nattyarch.local");
std::env::set_var("MAG_C_DATABASE_URL", "dummy"); std::env::set_var("MAG_C_DATABASE_URL", "dummy");
std::env::set_var("MAG_C_REDIS_URL", "dummy");
let config = MagnetarConfig::default(); let config = MagnetarConfig::default();
let config_ref = Box::leak(Box::new(config)); let config_ref = Box::leak(Box::new(config));