1mod codegen;
2mod generator;
3mod parser;
4mod utils;
5
6use heck::ToPascalCase;
7use proc_macro::TokenStream;
8use proc_macro2::{Span, TokenStream as TokenStream2};
9use quote::{format_ident, quote};
10use syn::parse_macro_input;
11
12use generator::*;
13use parser::*;
14
15#[proc_macro]
38pub fn openapi_client(input: TokenStream) -> TokenStream {
39 let input = parse_macro_input!(input as OpenApiInput);
40
41 match generate_client(&input) {
42 Ok(tokens) => tokens.into(),
43 Err(e) => syn::Error::new(Span::call_site(), e)
44 .to_compile_error()
45 .into(),
46 }
47}
48
49fn generate_client(input: &OpenApiInput) -> Result<TokenStream2, String> {
50 let spec = load_openapi_spec(input)?;
52
53 let client_name = if let Some(name) = &input.client_name {
54 format_ident!("{}", name)
55 } else {
56 let title = spec.info.title.clone();
58 let sanitized_title = title
59 .chars()
60 .filter(|c| c.is_alphanumeric() || c.is_whitespace())
61 .collect::<String>()
62 .to_pascal_case();
63 format_ident!("{}Api", sanitized_title)
64 };
65
66 let structs = generate_structs(&spec)?;
68 let client_impl = generate_client_impl(&spec, &client_name)?;
69 let error_types = generate_error_types();
70
71 let client_doc = generate_client_doc_comment(&spec, &client_name.to_string());
73
74 Ok(quote! {
75 use serde::{Deserialize, Serialize};
76 use std::collections::HashMap;
77
78 #error_types
79
80 #structs
81
82 #client_doc
83 #[derive(Clone)]
84 pub struct #client_name {
85 base_url: String,
86 client: reqwest::Client,
87 }
88
89 #client_impl
90 })
91}