-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor: Improve API cache macro (#32)
- Loading branch information
Showing
28 changed files
with
586 additions
and
336 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,27 +1,38 @@ | ||
[package] | ||
name = "ledger-tui" | ||
[workspace] | ||
|
||
resolver = "2" | ||
|
||
members = ["./app", "./app/api_proc_macro"] | ||
|
||
[workspace.package] | ||
version = "0.1.0" | ||
edition = "2021" | ||
authors = ["mertwole"] | ||
|
||
[dependencies] | ||
[workspace.dependencies] | ||
api-proc-macro = { path = "./app/api_proc_macro" } | ||
|
||
binance_spot_connector_rust = "1.1.0" | ||
bs58 = "0.5.1" | ||
chrono = "0.4.38" | ||
copypasta = "0.10.1" | ||
futures = "0.3.30" | ||
itertools = "0.13.0" | ||
ledger_bitcoin_client = "0.4.1" | ||
ledger-lib = "0.1.0" | ||
ledger-proto = "0.1.0" | ||
log = "0.4.22" | ||
paste = "1.0.15" | ||
pretty_env_logger = "0.5.0" | ||
proc-macro2 = "1.0.86" | ||
qrcode = "0.14.1" | ||
quote = "1.0.36" | ||
ratatui = "0.27.0" | ||
rust_decimal = "1.35.0" | ||
rust_decimal_macros = "1.35.0" | ||
serde = "1.0.204" | ||
serde_json = "1.0.120" | ||
strum = { version = "0.26.3", features = ["derive"] } | ||
strum = "0.26.3" | ||
syn = "2.0.72" | ||
tokio = "1.38.0" | ||
tui-widget-list = "0.10.0" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
[package] | ||
name = "ledger-tui" | ||
version.workspace = true | ||
edition.workspace = true | ||
authors.workspace = true | ||
|
||
[dependencies] | ||
api-proc-macro.workspace = true | ||
|
||
binance_spot_connector_rust.workspace = true | ||
bs58.workspace = true | ||
chrono.workspace = true | ||
copypasta.workspace = true | ||
futures.workspace = true | ||
ledger_bitcoin_client.workspace = true | ||
ledger-lib.workspace = true | ||
ledger-proto.workspace = true | ||
log.workspace = true | ||
paste.workspace = true | ||
pretty_env_logger.workspace = true | ||
qrcode.workspace = true | ||
ratatui.workspace = true | ||
rust_decimal.workspace = true | ||
rust_decimal_macros.workspace = true | ||
serde.workspace = true | ||
serde_json.workspace = true | ||
strum = { workspace = true, features = ["derive"] } | ||
tokio.workspace = true | ||
tui-widget-list.workspace = true |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
[package] | ||
name = "api-proc-macro" | ||
version.workspace = true | ||
edition.workspace = true | ||
authors.workspace = true | ||
|
||
[lib] | ||
proc-macro = true | ||
|
||
[dependencies] | ||
itertools.workspace = true | ||
proc-macro2.workspace = true | ||
quote.workspace = true | ||
syn.workspace = true |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,269 @@ | ||
#[macro_use] | ||
extern crate quote; | ||
extern crate proc_macro; | ||
extern crate syn; | ||
|
||
use proc_macro2::TokenStream; | ||
use syn::{ | ||
parse_macro_input, FnArg, Ident, ItemTrait, Pat, PatType, ReturnType, TraitItem, Type, | ||
Visibility, | ||
}; | ||
|
||
#[proc_macro] | ||
pub fn implement_cache(input: proc_macro::TokenStream) -> proc_macro::TokenStream { | ||
let mut proc_macro_2_input = TokenStream::from(input.clone()); | ||
|
||
let trait_info = parse_macro_input!(input as ItemTrait); | ||
let trait_info = TraitInfo::from_item_trait(trait_info); | ||
|
||
let cache_impl = trait_info.generate_cache_impl(); | ||
proc_macro_2_input.extend(cache_impl); | ||
|
||
proc_macro::TokenStream::from(proc_macro_2_input) | ||
} | ||
|
||
struct TraitInfo { | ||
visibility: Visibility, | ||
name: Ident, | ||
|
||
methods: Vec<TraitMethodInfo>, | ||
} | ||
|
||
struct TraitMethodInfo { | ||
name: Ident, | ||
arguments: Vec<ArgumentInfo>, | ||
return_type: Type, | ||
} | ||
|
||
struct ArgumentInfo { | ||
name: Ident, | ||
ty: Type, | ||
} | ||
|
||
impl TraitInfo { | ||
fn from_item_trait(item_trait: ItemTrait) -> Self { | ||
Self { | ||
visibility: item_trait.vis, | ||
name: item_trait.ident, | ||
|
||
methods: item_trait | ||
.items | ||
.into_iter() | ||
.map(TraitMethodInfo::from_trait_item) | ||
.collect(), | ||
} | ||
} | ||
|
||
fn generate_cache_impl(&self) -> TokenStream { | ||
let trait_name = &self.name; | ||
let vis = &self.visibility; | ||
|
||
let (cache_fields, cache_field_default_assigns, mode_setters, api_method_wrappers): ( | ||
TokenStream, | ||
TokenStream, | ||
TokenStream, | ||
TokenStream, | ||
) = itertools::multiunzip(self.methods.iter().map(|method| { | ||
( | ||
method.generate_cache_fields(), | ||
method.generate_cache_field_default_assign(), | ||
method.generate_mode_setter(), | ||
method.generate_api_method_wrapper(), | ||
) | ||
})); | ||
|
||
quote! { | ||
#vis mod cache { | ||
use crate::api::cache_utils::{Mode, ModePlan}; | ||
use super::*; | ||
|
||
pub struct Cache<A: super::#trait_name> { | ||
api: A, | ||
|
||
#cache_fields | ||
} | ||
|
||
impl<A: super::#trait_name> Cache<A> { | ||
pub async fn new(api: A) -> Self { | ||
Self { | ||
api, | ||
|
||
#cache_field_default_assigns | ||
} | ||
} | ||
|
||
pub fn set_all_modes(&mut self, mode_plan: ModePlan) { | ||
#mode_setters | ||
} | ||
} | ||
|
||
impl<A: super::#trait_name> super::#trait_name for Cache<A> { | ||
#api_method_wrappers | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
impl TraitMethodInfo { | ||
fn from_trait_item(trait_item: TraitItem) -> Self { | ||
match trait_item { | ||
TraitItem::Fn(fun) => { | ||
let sig = fun.sig; | ||
|
||
let mut arguments = vec![]; | ||
for arg in sig.inputs { | ||
match arg { | ||
FnArg::Receiver(_) => {} | ||
FnArg::Typed(ty) => { | ||
arguments.push(ArgumentInfo::from_pat_type(ty)); | ||
} | ||
} | ||
} | ||
|
||
let return_type = match sig.output { | ||
ReturnType::Type(_, ty) => *ty, | ||
_ => unimplemented!(), | ||
}; | ||
|
||
Self { | ||
name: sig.ident, | ||
arguments, | ||
return_type, | ||
} | ||
} | ||
_ => unimplemented!(), | ||
} | ||
} | ||
|
||
fn generate_cache_fields(&self) -> TokenStream { | ||
let name = &self.name; | ||
let mode_field_name = make_mode_field_name(&self.name); | ||
|
||
let return_type = &self.return_type; | ||
|
||
let arg_types = self | ||
.arguments | ||
.iter() | ||
.map(|arg| remove_type_reference(arg.ty.clone())); | ||
let args_tuple = quote! { ( #(#arg_types),* ) }; | ||
|
||
quote! { | ||
#[allow(unused_parens)] | ||
#name: ::std::cell::RefCell<::std::collections::HashMap<#args_tuple, #return_type>>, | ||
#[allow(unused_parens)] | ||
#mode_field_name : ::std::cell::RefCell<Mode<#args_tuple>>, | ||
} | ||
} | ||
|
||
fn generate_cache_field_default_assign(&self) -> TokenStream { | ||
let name = &self.name; | ||
let mode_field_name = make_mode_field_name(&self.name); | ||
|
||
quote! { | ||
#name: ::std::default::Default::default(), | ||
#mode_field_name : ::std::default::Default::default(), | ||
} | ||
} | ||
|
||
fn generate_mode_setter(&self) -> TokenStream { | ||
let mode_field_name = make_mode_field_name(&self.name); | ||
|
||
quote! { | ||
(*self. #mode_field_name .borrow_mut()) = mode_plan.into_mode(); | ||
} | ||
} | ||
|
||
fn generate_api_method_wrapper(&self) -> TokenStream { | ||
let name = &self.name; | ||
let mode_field_name = make_mode_field_name(&self.name); | ||
let ret = &self.return_type; | ||
let arg_tuple = self.generate_arg_tuple(); | ||
let api_call_args = self.generate_api_call_args(); | ||
|
||
let args: Vec<_> = self | ||
.arguments | ||
.iter() | ||
.map(ArgumentInfo::generate_argument) | ||
.collect(); | ||
|
||
quote! { | ||
#[allow(clippy::await_holding_refcell_ref)] | ||
async fn #name(&self, #(#args),*) -> #ret { | ||
let api_result = self.api.#name(#api_call_args); | ||
let api_result = ::std::boxed::Box::pin(api_result); | ||
|
||
let mut cache = self.#name.borrow_mut(); | ||
let cache = cache.entry(#arg_tuple); | ||
|
||
let mut mode = self.#mode_field_name.borrow_mut(); | ||
|
||
crate::api::cache_utils::use_cache( | ||
#arg_tuple, | ||
cache, | ||
api_result, | ||
&mut *mode | ||
).await | ||
} | ||
} | ||
} | ||
|
||
fn generate_arg_tuple(&self) -> TokenStream { | ||
let args = self.arguments.iter().map(ArgumentInfo::generate_name); | ||
quote! { | ||
( #(#args.clone()),* ) | ||
} | ||
} | ||
|
||
fn generate_api_call_args(&self) -> TokenStream { | ||
self.arguments | ||
.iter() | ||
.map(|info| { | ||
let arg = info.generate_name(); | ||
let add_clone = !matches!(info.ty, Type::Reference(_)); | ||
|
||
if add_clone { | ||
quote! { #arg.clone(), } | ||
} else { | ||
quote! { #arg, } | ||
} | ||
}) | ||
.collect() | ||
} | ||
} | ||
|
||
impl ArgumentInfo { | ||
fn from_pat_type(pat_type: PatType) -> Self { | ||
let name = match *pat_type.pat { | ||
Pat::Ident(iden) => iden.ident, | ||
_ => unimplemented!(), | ||
}; | ||
|
||
Self { | ||
name, | ||
ty: *pat_type.ty, | ||
} | ||
} | ||
|
||
fn generate_argument(&self) -> TokenStream { | ||
let name = &self.name; | ||
let ty = &self.ty; | ||
quote! { #name : #ty } | ||
} | ||
|
||
fn generate_name(&self) -> TokenStream { | ||
let name = &self.name; | ||
quote! { #name } | ||
} | ||
} | ||
|
||
fn remove_type_reference(ty: Type) -> Type { | ||
match ty { | ||
Type::Reference(reference) => *reference.elem, | ||
_ => ty, | ||
} | ||
} | ||
|
||
fn make_mode_field_name(ident: &Ident) -> Ident { | ||
format_ident!("__{}_mode", ident) | ||
} |
Oops, something went wrong.