working example
This commit is contained in:
parent
aaf9781fe8
commit
e69bcfc23d
18 changed files with 1290 additions and 252 deletions
|
|
@ -1,21 +1,67 @@
|
|||
use proc_macro::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::{parse_macro_input, ItemFn, FnArg, Pat, PatType, PatIdent, Type, LitStr};
|
||||
use syn::{parse_macro_input, ItemFn, LitStr};
|
||||
|
||||
use syn::{punctuated::Punctuated, token::Comma};
|
||||
|
||||
fn extract_path_args(inputs: &Punctuated<syn::FnArg, Comma>) -> Vec<(syn::Ident, syn::Type)> {
|
||||
let mut args = Vec::new();
|
||||
for arg in inputs {
|
||||
#[derive(Copy, Clone, PartialEq)]
|
||||
enum Class {
|
||||
Query,
|
||||
Path,
|
||||
Body,
|
||||
}
|
||||
|
||||
impl Class {
|
||||
fn classify(pat_type: &syn::PatType, default: Self) -> Self {
|
||||
let mut result = None;
|
||||
for a in pat_type.attrs.iter() {
|
||||
let class = if a.path().is_ident("path") {
|
||||
Some(Self::Path)
|
||||
} else if a.path().is_ident("query") {
|
||||
Some(Self::Query)
|
||||
} else if a.path().is_ident("body") {
|
||||
Some(Self::Body)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if class.is_some() {
|
||||
if result.is_some() && result != class {
|
||||
panic!("can only have one class!");
|
||||
}
|
||||
result = class;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(result) = result {
|
||||
result
|
||||
} else {
|
||||
default
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_args(inputs: &Punctuated<syn::FnArg, Comma>, class: Class, default: Class) -> (Vec<syn::Ident>, Vec<syn::Type>, Vec<proc_macro2::TokenStream>) {
|
||||
let mut idents = Vec::new();
|
||||
let mut types = Vec::new();
|
||||
let mut attrs = Vec::new();
|
||||
for arg in inputs.iter().skip(1) {
|
||||
if let syn::FnArg::Typed(pat_type) = arg {
|
||||
if pat_type.attrs.iter().any(|a| a.path().is_ident("path")) {
|
||||
//if pat_type.attrs.iter().any(|a| a.path().is_ident(path)) {
|
||||
if Class::classify(&pat_type, default) == class {
|
||||
if let syn::Pat::Ident(pat_ident) = &*pat_type.pat {
|
||||
args.push((pat_ident.ident.clone(), (*pat_type.ty).clone()));
|
||||
idents.push(pat_ident.ident.clone());
|
||||
types.push((*pat_type.ty).clone());
|
||||
let meta = pat_type.attrs.iter()
|
||||
.filter(|a| !["path", "query", "body"].iter().any(|p| a.path().is_ident(p)));
|
||||
let meta = quote! {
|
||||
#( #meta )*
|
||||
};
|
||||
attrs.push(meta);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
args
|
||||
(idents, types, attrs)
|
||||
}
|
||||
|
||||
fn generate_endpoint(attr: TokenStream, item: TokenStream, method: &str) -> TokenStream {
|
||||
|
|
@ -26,24 +72,50 @@ fn generate_endpoint(attr: TokenStream, item: TokenStream, method: &str) -> Toke
|
|||
let vis = &item_fn.vis;
|
||||
let generics = &item_fn.sig.generics;
|
||||
|
||||
let path_args = extract_path_args(&item_fn.sig.inputs);
|
||||
let path_idents: Vec<_> = path_args.iter().map(|(id, _)| id).collect();
|
||||
let path_types: Vec<_> = path_args.iter().map(|(_, ty)| ty).collect();
|
||||
let default = Class::Body;
|
||||
let (path_idents, path_types, path_attrs) = extract_args(&item_fn.sig.inputs, Class::Path, default);
|
||||
let (query_idents, query_types, query_attrs) = extract_args(&item_fn.sig.inputs, Class::Query, default);
|
||||
let (body_idents, body_types, body_attrs) = extract_args(&item_fn.sig.inputs, Class::Body, default);
|
||||
|
||||
let ret_type = match &item_fn.sig.output {
|
||||
syn::ReturnType::Default => quote! { () },
|
||||
syn::ReturnType::Type(_, ty) => quote! { #ty },
|
||||
};
|
||||
|
||||
let queries = query_idents.len();
|
||||
let expanded = quote! {
|
||||
#[bon::builder]
|
||||
#vis async fn #fn_name #generics(
|
||||
#[builder(finish_fn)] client: restson::RestClient,
|
||||
#( #[builder(finish_fn)] #path_idents: #path_types, )*
|
||||
#[builder(finish_fn)] client: &restson::RestClient,
|
||||
#( #[builder(finish_fn)] #path_attrs #path_idents: #path_types, )*
|
||||
#( #query_attrs #query_idents: #query_types, )*
|
||||
#( #body_attrs #body_idents: #body_types, )*
|
||||
) -> Result<#ret_type, restson::Error> {
|
||||
let path = format!(#path_lit, #( #path_idents = #path_idents, )* );
|
||||
|
||||
todo!("Replace this with client.{} call", #method)
|
||||
let mut query = Vec::with_capacity(#queries);
|
||||
|
||||
|
||||
|
||||
#[derive(serde::Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct Q {
|
||||
#(
|
||||
#query_attrs #query_idents: #query_types,
|
||||
)*
|
||||
}
|
||||
let q = Q { #( #query_idents, )* };
|
||||
let s = serde_qs::to_string(&q).expect("serde_qs serialization");
|
||||
for pair in s.split('&') {
|
||||
let mut kv = pair.splitn(2, '=');
|
||||
match (kv.next(), kv.next()) {
|
||||
(Some(k), Some(v)) => query.push((k, v)),
|
||||
(Some(k), None) => query.push((k, "")),
|
||||
_ => panic!("should never happen!"),
|
||||
}
|
||||
}
|
||||
|
||||
todo!("client.{}({path}, {query:?})", #method)
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue