/// This module is designed to automatically add the essential types to a LunaNodeRequest struct, without developer intervention.
/// You should apply this to every LunaNodeRequest struct *BEFORE* any other macros, and especially before any derive macros.
/// Usages:
/// ```rust
/// #[lunanode_request(response="ImageListResponse", endpoint="image/list/")]
/// #[derive(Serialize, Deserialize, Debug, ...)]
/// struct MyStruct {
/// ...
/// ```
///
/// TODO: Improve error messages.
use proc_macro ::TokenStream ;
use quote ::quote ;
use syn ::parse ::Parser ;
use syn ::{ parse_macro_input , AttributeArgs , ItemStruct , Lit , NestedMeta , Meta , MetaNameValue , Type } ;
#[ derive(Debug, Hash, Eq, PartialEq) ]
enum LunanodeRequestParam {
Invalid ,
Response ( Type ) ,
EndPoint ( String ) ,
}
impl LunanodeRequestParam {
fn from ( key : String , val : String ) -> Self {
match ( key . as_str ( ) , val . as_str ( ) ) {
( "response" , tp ) = > Self ::Response ( syn ::parse_str ( tp ) . expect ( "The value given to the 'response' parameter must be a valid type." ) ) ,
( "endpoint" , ep ) = > Self ::EndPoint ( ep . to_string ( ) ) ,
_ = > Self ::Invalid
}
}
}
#[ proc_macro_attribute ]
pub fn lunanode_response ( _attr : TokenStream , input : TokenStream ) -> TokenStream {
// Get the structs below the macro
let mut item_struct = parse_macro_input ! ( input as ItemStruct ) ;
// clone the name to keep compiler happy :)
let name = item_struct . ident . clone ( ) ;
// Add one field to the struct, see quote! macro below
if let syn ::Fields ::Named ( ref mut fields ) = item_struct . fields {
fields . named . push (
syn ::Field ::parse_named
. parse2 ( quote ! {
#[ serde(with= " success " ) ]
/// Wheather the request was successful or not. This can be either true or false. If it is false, then .make_request() will produce an Err(LNError::LunaNodeError(LNErrorResponse)).
pub success : bool
} )
. unwrap ( ) ,
) ;
}
return quote ! {
#[ serde_as ]
#[ derive(Serialize, Deserialize, Debug) ]
#item_struct
impl LunaNodeResponse for #name { }
} . into ( ) ;
}
#[ proc_macro_attribute ]
pub fn lunanode_request ( attr : TokenStream , input : TokenStream ) -> TokenStream {
// Get the structs below the macro
let mut item_struct = parse_macro_input ! ( input as ItemStruct ) ;
// parse the arguments using a custom type
let args_parsed : Vec < LunanodeRequestParam > = parse_macro_input ! ( attr as AttributeArgs )
. into_iter ( )
. filter_map ( | nm | match nm {
// Only select certain tokens
NestedMeta ::Meta ( Meta ::NameValue ( MetaNameValue { path , eq_token : _ , lit : Lit ::Str ( lstr ) } ) ) = > Some (
// Convert the sigment of the path to a string
( path . segments
. into_iter ( )
. map ( | seg | seg . ident . to_string ( ) )
. collect ::< Vec < String > > ( )
. swap_remove ( 0 ) ,
// get the raw value of the LitStr
lstr . value ( ) )
) ,
_ = > None
} )
// convert the (String, LitStr) tuple to a custom type which only accepts certain key/value pairs
. map ( | ( k , v ) | LunanodeRequestParam ::from ( k , v ) )
. collect ( ) ;
// if the first item in the list is not a response type
let response_type = match args_parsed . get ( 0 ) . expect ( "There must be two argument for the macro." ) {
LunanodeRequestParam ::Response ( res ) = > res ,
_ = > panic! ( "The response parameter must be a type; it must also be first." ) ,
} ;
// if the second item in the list is not a url nedpoint
let url_endpoint = match args_parsed . get ( 1 ) . expect ( "There must be two arguments for the macro." ) {
LunanodeRequestParam ::EndPoint ( ep ) = > ep ,
_ = > panic! ( "The endpoint parameter must be a string; it must also be last." )
} ;
// clone the name to keep compiler happy :)
let name = item_struct . ident . clone ( ) ;
// Add one field to the struct, see quote! macro below
if let syn ::Fields ::Named ( ref mut fields ) = item_struct . fields {
fields . named . push (
syn ::Field ::parse_named
. parse2 ( quote ! {
#[ serde(flatten) ]
#[ clap(flatten) ]
pub keys : ApiKeys
} )
. unwrap ( ) ,
) ;
}
// return this entire section as generated code
return quote ! {
#[ derive(Serialize, Deserialize, Debug, Hash, PartialEq, Eq, clap::Args) ]
#item_struct
impl LunaNodeRequest for #name {
type Response = #response_type ; // TODO: Last section that needs to be dynamic
fn get_keys ( & self ) -> ApiKeys {
self . keys . clone ( )
}
fn url_endpoint ( & self ) -> String {
#url_endpoint . to_string ( )
}
}
}
. into ( ) ;
}