mod codegen;
mod reqwest_derive;
use proc_macro::TokenStream;
use proc_macro2::Span;
use quote::quote;
use syn::{parse_macro_input, DeriveInput, ItemMod, LitStr};
#[proc_macro_attribute]
pub fn openapi_trait(attr: TokenStream, item: TokenStream) -> TokenStream {
let path_lit = parse_macro_input!(attr as LitStr);
run_macro(&path_lit, item, cfg!(feature = "reqwest-client"))
}
#[proc_macro_derive(ReqwestClient, attributes(openapi_trait))]
pub fn derive_reqwest_client(item: TokenStream) -> TokenStream {
let input = parse_macro_input!(item as DeriveInput);
match reqwest_derive::expand_reqwest_client(input) {
Ok(tokens) => tokens.into(),
Err(error) => error.to_compile_error().into(),
}
}
fn run_macro(path_lit: &LitStr, item: TokenStream, include_reqwest: bool) -> TokenStream {
let module = parse_macro_input!(item as ItemMod);
let mod_ident = &module.ident;
let mod_vis = &module.vis;
let Ok(manifest_dir) = std::env::var("CARGO_MANIFEST_DIR") else {
return syn::Error::new(
Span::call_site(),
"CARGO_MANIFEST_DIR is not set; cannot resolve spec path",
)
.to_compile_error()
.into();
};
let spec_path = std::path::PathBuf::from(&manifest_dir).join(path_lit.value());
let spec_path_str = spec_path.to_string_lossy().into_owned();
let content = match std::fs::read_to_string(&spec_path) {
Ok(value) => value,
Err(error) => {
let msg = format!("cannot read OpenAPI spec `{spec_path_str}`: {error}");
return syn::Error::new(path_lit.span(), msg)
.to_compile_error()
.into();
}
};
let openapi: openapiv3::OpenAPI = match serde_yaml::from_str(&content) {
Ok(value) => value,
Err(error) => {
let msg = format!("cannot parse OpenAPI spec `{spec_path_str}`: {error}");
return syn::Error::new(path_lit.span(), msg)
.to_compile_error()
.into();
}
};
let body = codegen::generate_client(mod_ident, &openapi, include_reqwest);
let expanded = quote! {
const _: &str = ::core::include_str!(#spec_path_str);
#[allow(
missing_docs,
missing_debug_implementations,
dead_code,
unused_imports,
clippy::all,
clippy::nursery,
clippy::pedantic,
)]
#mod_vis mod #mod_ident {
#body
}
};
expanded.into()
}