You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
126 lines
4.7 KiB
126 lines
4.7 KiB
/// 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();
|
|
}
|