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 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<ts_rs::Dependency> {
vec![
#(
<#types_deps as TS>::dependencies()
),*
].into_iter().flatten().collect::<Vec<_>>()
}
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::<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" => {
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::<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),
}
@ -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::<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! {
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<ts_rs::Dependency> {
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::<Vec<_>>()
}
fn transparent() -> bool {

View File

@ -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;

View File

@ -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<NoteListFilter>,
}
@ -20,16 +19,11 @@ fn default_timeline_limit<const MIN: u64, const MAX: u64>() -> U64Range<MIN, MAX
15.try_into().unwrap()
}
#[derive(Serialize, Deserialize, TS)]
#[ts(export)]
#[repr(transparent)]
pub struct GetTimelineRes(pub Vec<PackNoteMaybeFull>);
#[derive(Endpoint)]
#[endpoint(
endpoint = "/timeline",
method = Method::GET,
request = GetTimelineReq,
response = Vec::<PackNoteMaybeFull>,
request = "GetTimelineReq",
response = "Vec<PackNoteMaybeFull>",
)]
pub struct GetTimeline;

View File

@ -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;

View File

@ -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<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 {
type Input: 'static;

View File

@ -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<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)]
@ -100,10 +100,11 @@ pub struct NoteDetailExt {
pack!(
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)]
#[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),

View File

@ -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<String>,
pub has_public_reactions: bool,
pub emojis: EmojiContext,
}
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
@ -164,16 +163,24 @@ pack!(
PackUserMaybeAll,
Required<Id> as id
& Required<UserBase> as user
& Option<UserProfileExt> as profile
& Option<UserProfilePinsEx> as pins
& Option<UserDetailExt> as detail
& Option<UserRelationExt> as relation
& Option<UserAuthOverviewExt> as auth
& Optional<UserProfileExt> as profile
& Optional<UserProfilePinsEx> as pins
& Optional<UserDetailExt> as detail
& Optional<UserRelationExt> as relation
& Optional<UserAuthOverviewExt> as auth
);
impl From<PackUserBase> 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<Id> as id
& Required<UserBase> as user
& Option<UserProfileExt> as profile
& Option<UserProfilePinsEx> as pins
& Option<UserDetailExt> as detail
& Option<UserSecretsExt> as secrets
& Optional<UserProfileExt> as profile
& Optional<UserProfilePinsEx> as pins
& Optional<UserDetailExt> as detail
& Optional<UserSecretsExt> as secrets
);
impl From<PackUserBase> 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),
))
}
}

View File

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

View File

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

View File

@ -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));