openapi_trait_client/
lib.rs1mod codegen;
10mod reqwest_derive;
12
13use proc_macro::TokenStream;
14use proc_macro2::Span;
15use quote::quote;
16use syn::{parse_macro_input, DeriveInput, ItemMod, LitStr};
17
18#[proc_macro_attribute]
38pub fn openapi_trait(attr: TokenStream, item: TokenStream) -> TokenStream {
39 let path_lit = parse_macro_input!(attr as LitStr);
40 run_macro(&path_lit, item, cfg!(feature = "reqwest-client"))
41}
42
43#[proc_macro_derive(ReqwestClient, attributes(openapi_trait))]
49pub fn derive_reqwest_client(item: TokenStream) -> TokenStream {
50 let input = parse_macro_input!(item as DeriveInput);
51
52 match reqwest_derive::expand_reqwest_client(input) {
53 Ok(tokens) => tokens.into(),
54 Err(error) => error.to_compile_error().into(),
55 }
56}
57
58fn run_macro(path_lit: &LitStr, item: TokenStream, include_reqwest: bool) -> TokenStream {
60 let module = parse_macro_input!(item as ItemMod);
61 let mod_ident = &module.ident;
62 let mod_vis = &module.vis;
63
64 let Ok(manifest_dir) = std::env::var("CARGO_MANIFEST_DIR") else {
65 return syn::Error::new(
66 Span::call_site(),
67 "CARGO_MANIFEST_DIR is not set; cannot resolve spec path",
68 )
69 .to_compile_error()
70 .into();
71 };
72
73 let spec_path = std::path::PathBuf::from(&manifest_dir).join(path_lit.value());
74 let spec_path_str = spec_path.to_string_lossy().into_owned();
75
76 let content = match std::fs::read_to_string(&spec_path) {
77 Ok(value) => value,
78 Err(error) => {
79 let msg = format!("cannot read OpenAPI spec `{spec_path_str}`: {error}");
80 return syn::Error::new(path_lit.span(), msg)
81 .to_compile_error()
82 .into();
83 }
84 };
85
86 let openapi: openapiv3::OpenAPI = match serde_yaml::from_str(&content) {
87 Ok(value) => value,
88 Err(error) => {
89 let msg = format!("cannot parse OpenAPI spec `{spec_path_str}`: {error}");
90 return syn::Error::new(path_lit.span(), msg)
91 .to_compile_error()
92 .into();
93 }
94 };
95
96 let body = codegen::generate_client(mod_ident, &openapi, include_reqwest);
97
98 let expanded = quote! {
99 const _: &str = ::core::include_str!(#spec_path_str);
100
101 #[allow(
102 missing_docs,
103 missing_debug_implementations,
104 dead_code,
105 unused_imports,
106 clippy::all,
107 clippy::nursery,
108 clippy::pedantic,
109 )]
110 #mod_vis mod #mod_ident {
111 #body
112 }
113 };
114
115 expanded.into()
116}