#[macro_use]
extern crate synstructure;
extern crate proc_macro;
mod error;
mod params;
mod parse;
use std::{collections::HashMap, convert::TryFrom};
use error::Error;
use params::Parameters;
use proc_macro2::Span;
use quote::quote;
use regex::Regex;
use syn::{self, spanned::Spanned, Field, Generics, Ident, Meta};
const MACRO_NAME: &str = "Endpoint";
const ATTR_NAME: &str = "endpoint";
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) enum EndpointAttribute {
Body,
Query,
Raw,
Skip,
Untagged,
}
impl TryFrom<&Meta> for EndpointAttribute {
type Error = Error;
fn try_from(m: &Meta) -> Result<Self, Self::Error> {
match m.path().get_ident() {
Some(i) => match i.to_string().to_lowercase().as_str() {
"body" => Ok(EndpointAttribute::Body),
"query" => Ok(EndpointAttribute::Query),
"raw" => Ok(EndpointAttribute::Raw),
"skip" => Ok(EndpointAttribute::Skip),
_ => Err(Error::new(
m.span(),
format!("Unknown attribute: {}", i).as_str(),
)),
},
None => Err(Error::new(m.span(), "Invalid attribute")),
}
}
}
fn gen_path(path: &syn::LitStr) -> Result<proc_macro2::TokenStream, Error> {
let re = Regex::new(r"\{(.*?)\}").unwrap();
let mut fmt_args: Vec<syn::Expr> = Vec::new();
for cap in re.captures_iter(path.value().as_str()) {
let expr = syn::parse_str(&cap[1]);
match expr {
Ok(ex) => fmt_args.push(ex),
Err(_) => {
return Err(Error::new(
path.span(),
format!("Failed parsing format argument as expression: {}", &cap[1]).as_str(),
));
}
}
}
let path = syn::LitStr::new(
re.replace_all(path.value().as_str(), "{}")
.to_string()
.as_str(),
Span::call_site(),
);
if !fmt_args.is_empty() {
Ok(quote! {
format!(#path, #(#fmt_args),*)
})
} else {
Ok(quote! {
String::from(#path)
})
}
}
fn gen_query(
fields: &HashMap<EndpointAttribute, Vec<Field>>,
serde_attrs: &[Meta],
) -> proc_macro2::TokenStream {
let query_fields = fields.get(&EndpointAttribute::Query);
if let Some(v) = query_fields {
let temp = parse::fields_to_struct(v, serde_attrs);
quote! {
fn query(&self) -> Result<Option<String>, ClientError> {
#temp
Ok(Some(build_query(&__temp)?))
}
}
} else {
quote! {}
}
}
fn gen_body(
fields: &HashMap<EndpointAttribute, Vec<Field>>,
serde_attrs: &[Meta],
) -> Result<proc_macro2::TokenStream, Error> {
if let Some(v) = fields.get(&EndpointAttribute::Raw) {
if v.len() > 1 {
return Err(Error::new(v[1].span(), "May only mark one field as raw"));
}
let id = v[0].ident.clone().unwrap();
Ok(quote! {
fn body(&self) -> Result<Option<Vec<u8>>, ClientError>{
Ok(Some(self.#id.clone()))
}
})
} else if let Some(v) = fields.get(&EndpointAttribute::Body) {
let temp = parse::fields_to_struct(v, serde_attrs);
Ok(quote! {
fn body(&self) -> Result<Option<Vec<u8>>, ClientError> {
#temp
Ok(Some(build_body(&__temp, Self::REQUEST_BODY_TYPE)?))
}
})
} else if let Some(v) = fields.get(&EndpointAttribute::Untagged) {
let temp = parse::fields_to_struct(v, serde_attrs);
Ok(quote! {
fn body(&self) -> Result<Option<Vec<u8>>, ClientError> {
#temp
Ok(Some(build_body(&__temp, Self::REQUEST_BODY_TYPE)?))
}
})
} else {
Ok(quote! {})
}
}
fn gen_builder(id: &Ident, generics: &Generics) -> proc_macro2::TokenStream {
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let builder_id: syn::Type = syn::parse_str(format!("{}Builder", id).as_str()).unwrap();
let builder_func: syn::Expr =
syn::parse_str(format!("{}Builder::default()", id).as_str()).unwrap();
quote! {
impl #impl_generics #id #ty_generics #where_clause {
pub fn builder() -> #builder_id #ty_generics {
#builder_func
}
}
}
}
fn parse_params(attr: &Meta) -> Result<Parameters, Error> {
let kv = parse::attr_kv(attr)?;
let map = parse::to_map(&kv)?;
params::Parameters::new(map)
}
fn endpoint_derive(s: synstructure::Structure) -> proc_macro2::TokenStream {
let attrs = match parse::attributes(&s.ast().attrs, ATTR_NAME) {
Ok(v) => v,
Err(e) => return e.into_tokens(),
};
let field_attrs = match parse::field_attributes(&s.ast().data) {
Ok(v) => v,
Err(e) => return e.into_tokens(),
};
if attrs.is_empty() {
return Error::new(
Span::call_site(),
format!(
"Deriving `{}` requires attaching an `{}` attribute",
MACRO_NAME, ATTR_NAME
)
.as_str(),
)
.into_tokens();
}
if attrs.len() > 1 {
return Error::new(
Span::call_site(),
format!("Cannot define the {} attribute more than once", ATTR_NAME).as_str(),
)
.into_tokens();
}
let params = match parse_params(&attrs[0]) {
Ok(v) => v,
Err(e) => return e.into_tokens(),
};
let path = params.path;
let method = params.method;
let response = params.response;
let request_type = params.request_type;
let response_type = params.response_type;
let id = &s.ast().ident;
let serde_attrs = parse::attributes(&s.ast().attrs, "serde");
let serde_attrs = if let Ok(v) = serde_attrs {
v
} else {
Vec::<Meta>::new()
};
let path = match gen_path(&path) {
Ok(a) => a,
Err(e) => return e.into_tokens(),
};
let query = gen_query(&field_attrs, &serde_attrs);
let body = match gen_body(&field_attrs, &serde_attrs) {
Ok(d) => d,
Err(e) => return e.into_tokens(),
};
let builder = match params.builder {
true => gen_builder(&s.ast().ident, &s.ast().generics),
false => quote! {},
};
let (impl_generics, ty_generics, where_clause) = s.ast().generics.split_for_impl();
let const_name = format!("_DERIVE_Endpoint_FOR_{}", id);
let const_ident = Ident::new(const_name.as_str(), Span::call_site());
quote! {
const #const_ident: () = {
use rustified::__private::serde::Serialize;
use rustified::http::{build_body, build_query};
use rustified::client::Client;
use rustified::endpoint::Endpoint;
use rustified::enums::{RequestMethod, RequestType, ResponseType};
use rustified::errors::ClientError;
impl #impl_generics Endpoint for #id #ty_generics #where_clause {
type Response = #response;
const REQUEST_BODY_TYPE: RequestType = RequestType::#request_type;
const RESPONSE_BODY_TYPE: ResponseType = ResponseType::#response_type;
fn path(&self) -> String {
#path
}
fn method(&self) -> RequestMethod {
RequestMethod::#method
}
#query
#body
}
#builder
};
}
}
synstructure::decl_derive!([Endpoint, attributes(endpoint, serde)] => endpoint_derive);