kvapi_macros_internals/api/
builder.rs1use super::{common::Separator, dict::Dict, headers::Headers};
2use convert_case::{Case, Casing};
3use proc_macro2::TokenStream;
4use quote::{format_ident, quote};
5use syn::{
6 parse::{Parse, ParseStream},
7 Expr, Ident, LitStr,
8};
9
10pub struct ApiBuilder {
21 pub name: Option<Ident>,
23 pub dict: Option<Dict>,
24
25 pub base: Option<TokenStream>,
27 pub headers: Option<Headers>,
28 pub query: Option<Expr>,
29}
30
31impl ApiBuilder {
32 pub fn build(self) -> TokenStream {
33 let mut fields: Vec<TokenStream> = vec![];
34 let mut nodes: Vec<TokenStream> = vec![];
35
36 let dict = self.dict.expect("dict entry is required").inner;
37 let api_name = self.name.expect("name entry is required");
38
39 for (name, node) in dict {
41 let (snake, pascal) = (
43 format_ident!("{}", name.to_case(Case::Snake)),
44 format_ident!("{}{}", api_name, name.to_case(Case::Pascal)),
45 );
46
47 if node.is_root() {
49 fields.push(quote! { #snake: #pascal });
50 }
51
52 let fields = node.children_fields(api_name.clone());
54 if node.is_http() {
55 let url = node.build_url(self.base.clone());
56 let http = node.build_http(url, self.headers.clone());
57
58 let node = quote! {
60 pub struct #pascal {
61 client: kvapi::Client,
62 url: String,
63 #( pub #fields, )*
64 }
65 impl #pascal {
66 pub fn new() -> Self {
67 Self {
68 client: Self::build_client().unwrap(),
69 url: Self::build_url(),
70 #( #fields::new(), )*
71 }
72 }
73 #http
74 }
75 };
76
77 nodes.push(node)
78 } else {
79 let node = quote! {
81 pub struct #pascal {
82 #( pub #fields, )*
83 }
84
85 impl #pascal {
86 pub fn new() -> Self {
87 Self {
88 #( #fields::new(), )*
89 }
90 }
91 }
92 };
93 nodes.push(node);
94 }
95 }
96
97 quote! {
99 pub struct #api_name {
100 #( pub #fields, )*
101 }
102 impl #api_name {
103 pub fn new() -> Self {
104 Self {
105 #( #fields::new(), )*
106 }
107 }
108 }
109 #( #nodes )*
110 }
111 }
112}
113
114impl Parse for ApiBuilder {
115 fn parse(input: ParseStream) -> syn::Result<Self> {
116 let mut api = Self {
117 name: None,
119 dict: None,
120
121 base: None,
123 headers: None,
124 query: None,
125 };
126
127 while !input.is_empty() {
128 let ident: Ident = input.parse()?;
129 input.parse::<Separator>()?;
130 match ident.to_string().as_str() {
131 "name" | "N" => {
132 let name: Ident = input.parse()?;
133 api.name = Some(name);
134 }
135 "base" | "B" => {
136 let base: LitStr = input.parse()?;
137 let base = base.value();
138 api.base = Some(quote!(#base));
139 }
140 "dict" | "D" => {
141 let dict: Dict = input.parse()?;
142 api.dict = Some(dict);
143 }
144 "headers" | "head" | "hdrs" | "H" => {
145 let headers: Headers = input.parse()?;
146 api.headers = Some(headers);
147 }
148 "query" | "Q" => {
149 let query: Expr = input.parse()?;
150 api.query = Some(query);
151 }
152 _ => return Err(syn::Error::new(ident.span(), "unknown input to `api!`")),
153 }
154 }
155
156 if api.name.is_none() {
158 panic!("`name` is required; the name of the struct identity")
159 }
160
161 if api.dict.is_none() {
162 panic!("`dict` is required; a list of endpoints and output types")
163 }
164
165 Ok(api)
166 }
167}