1use std::collections::{HashMap, HashSet};
4
5use openapiv3::OpenAPI;
6use proc_macro2::TokenStream;
7use thiserror::Error;
8
9mod method;
10mod openapi;
11mod responses;
12mod router;
13mod types;
14
15pub use method::{MethodTransformer, ParamRole};
16pub use openapi::{HttpMethod, Operation, OperationParam, ParamLocation, ParsedSpec};
17pub use responses::ResponseGenerator;
18pub use router::RouterGenerator;
19pub use types::TypeGenerator;
20
21pub use typify::{TypeSpacePatch, TypeSpaceSettings};
23
24#[derive(Debug, Clone)]
26pub struct ResponseSuffixes {
27 pub ok_suffix: String,
29 pub err_suffix: String,
31 pub default_derives: TokenStream,
33}
34
35impl Default for ResponseSuffixes {
36 fn default() -> Self {
37 Self {
38 ok_suffix: "Response".to_string(),
39 err_suffix: "Error".to_string(),
40 default_derives: quote::quote! { #[derive(Debug)] },
41 }
42 }
43}
44
45#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
47pub enum GeneratedTypeKind {
48 Ok,
50 Err,
52 Query,
54 Path,
56}
57
58#[derive(Debug, Clone, PartialEq, Eq, Hash)]
60pub struct TypeOverrideKey {
61 pub method: HttpMethod,
62 pub path: String,
63 pub kind: GeneratedTypeKind,
64}
65
66#[derive(Debug, Clone)]
68pub struct VariantOverride {
69 pub name: proc_macro2::Ident,
71 pub inner_type_name: Option<proc_macro2::Ident>,
74 pub attrs: Vec<TokenStream>,
76}
77
78#[derive(Debug, Clone)]
80pub enum TypeOverride {
81 Rename {
83 name: proc_macro2::Ident,
85 attrs: Vec<TokenStream>,
88 variant_overrides: HashMap<u16, VariantOverride>,
90 },
91 Replace(TokenStream),
93}
94
95#[derive(Debug, Clone, PartialEq, Eq, Hash)]
97struct QueryUnknownFieldKey {
98 method: HttpMethod,
99 path: String,
100}
101
102#[derive(Debug, Clone, Default)]
104pub struct TypeOverrides {
105 overrides: HashMap<TypeOverrideKey, TypeOverride>,
106 query_unknown_fields: HashMap<QueryUnknownFieldKey, proc_macro2::Ident>,
108}
109
110impl TypeOverrides {
111 pub fn new() -> Self {
113 Self::default()
114 }
115
116 pub fn add_rename(
118 &mut self,
119 method: HttpMethod,
120 path: impl Into<String>,
121 kind: GeneratedTypeKind,
122 new_name: proc_macro2::Ident,
123 ) {
124 self.overrides.insert(
125 TypeOverrideKey {
126 method,
127 path: path.into(),
128 kind,
129 },
130 TypeOverride::Rename {
131 name: new_name,
132 attrs: Vec::new(),
133 variant_overrides: HashMap::new(),
134 },
135 );
136 }
137
138 pub fn add_rename_with_overrides(
140 &mut self,
141 method: HttpMethod,
142 path: impl Into<String>,
143 kind: GeneratedTypeKind,
144 new_name: proc_macro2::Ident,
145 attrs: Vec<TokenStream>,
146 variant_overrides: HashMap<u16, VariantOverride>,
147 ) {
148 self.overrides.insert(
149 TypeOverrideKey {
150 method,
151 path: path.into(),
152 kind,
153 },
154 TypeOverride::Rename {
155 name: new_name,
156 attrs,
157 variant_overrides,
158 },
159 );
160 }
161
162 pub fn add_replace(
164 &mut self,
165 method: HttpMethod,
166 path: impl Into<String>,
167 kind: GeneratedTypeKind,
168 replacement: TokenStream,
169 ) {
170 self.overrides.insert(
171 TypeOverrideKey {
172 method,
173 path: path.into(),
174 kind,
175 },
176 TypeOverride::Replace(replacement),
177 );
178 }
179
180 pub fn get(
182 &self,
183 method: HttpMethod,
184 path: &str,
185 kind: GeneratedTypeKind,
186 ) -> Option<&TypeOverride> {
187 self.overrides.get(&TypeOverrideKey {
188 method,
189 path: path.to_string(),
190 kind,
191 })
192 }
193
194 pub fn is_replaced(&self, method: HttpMethod, path: &str, kind: GeneratedTypeKind) -> bool {
196 matches!(self.get(method, path, kind), Some(TypeOverride::Replace(_)))
197 }
198
199 pub fn set_query_unknown_field(
203 &mut self,
204 method: HttpMethod,
205 path: impl Into<String>,
206 field_name: proc_macro2::Ident,
207 ) {
208 self.query_unknown_fields.insert(
209 QueryUnknownFieldKey {
210 method,
211 path: path.into(),
212 },
213 field_name,
214 );
215 }
216
217 pub fn get_query_unknown_field(
219 &self,
220 method: HttpMethod,
221 path: &str,
222 ) -> Option<&proc_macro2::Ident> {
223 self.query_unknown_fields.get(&QueryUnknownFieldKey {
224 method,
225 path: path.to_string(),
226 })
227 }
228}
229
230#[derive(Error, Debug)]
231pub enum Error {
232 #[error("failed to parse OpenAPI spec: {0}")]
233 ParseError(String),
234
235 #[error("operation not found: {method} {path}")]
236 OperationNotFound { method: String, path: String },
237
238 #[error("missing operations in trait: {0:?}")]
239 MissingOperations(Vec<String>),
240
241 #[error("type generation error: {0}")]
242 TypeGenError(String),
243
244 #[error("invalid attribute: {0}")]
245 InvalidAttribute(String),
246
247 #[error("unsupported feature: {0}")]
248 Unsupported(String),
249
250 #[error("unknown schema '{name}'. Available schemas: {available}")]
251 UnknownSchema { name: String, available: String },
252}
253
254pub type Result<T> = std::result::Result<T, Error>;
255
256pub struct GeneratorBuilder {
258 spec: OpenAPI,
259 settings: TypeSpaceSettings,
260 type_overrides: TypeOverrides,
261 response_suffixes: ResponseSuffixes,
262 schema_renames: HashMap<String, String>,
263}
264
265impl GeneratorBuilder {
266 pub fn new(spec: OpenAPI) -> Self {
268 Self {
269 spec,
270 settings: TypeSpaceSettings::default(),
271 type_overrides: TypeOverrides::default(),
272 response_suffixes: ResponseSuffixes::default(),
273 schema_renames: HashMap::new(),
274 }
275 }
276
277 pub fn from_file(path: &std::path::Path) -> Result<Self> {
279 let spec = openapi::load_spec(path)?;
280 Ok(Self::new(spec))
281 }
282
283 pub fn settings(mut self, settings: TypeSpaceSettings) -> Self {
285 self.settings = settings;
286 self
287 }
288
289 pub fn type_overrides(mut self, overrides: TypeOverrides) -> Self {
291 self.type_overrides = overrides;
292 self
293 }
294
295 pub fn response_suffixes(mut self, suffixes: ResponseSuffixes) -> Self {
297 self.response_suffixes = suffixes;
298 self
299 }
300
301 pub fn schema_renames(mut self, renames: HashMap<String, String>) -> Self {
303 self.schema_renames = renames;
304 self
305 }
306
307 pub fn build(self) -> Result<Generator> {
309 let parsed = ParsedSpec::from_openapi(self.spec)?;
310 let type_gen = TypeGenerator::with_settings(&parsed, self.settings, self.schema_renames)?;
311
312 Ok(Generator {
313 spec: parsed,
314 type_gen,
315 type_overrides: self.type_overrides,
316 response_suffixes: self.response_suffixes,
317 })
318 }
319}
320
321pub struct Generator {
323 spec: ParsedSpec,
324 type_gen: TypeGenerator,
325 type_overrides: TypeOverrides,
326 response_suffixes: ResponseSuffixes,
327}
328
329impl Generator {
330 pub fn builder(spec: OpenAPI) -> GeneratorBuilder {
332 GeneratorBuilder::new(spec)
333 }
334
335 pub fn builder_from_file(path: &std::path::Path) -> Result<GeneratorBuilder> {
337 GeneratorBuilder::from_file(path)
338 }
339
340 pub fn new(spec: OpenAPI) -> Result<Self> {
342 GeneratorBuilder::new(spec).build()
343 }
344
345 pub fn with_settings(spec: OpenAPI, settings: TypeSpaceSettings) -> Result<Self> {
347 GeneratorBuilder::new(spec).settings(settings).build()
348 }
349
350 pub fn from_file(path: &std::path::Path) -> Result<Self> {
352 GeneratorBuilder::from_file(path)?.build()
353 }
354
355 pub fn spec(&self) -> &ParsedSpec {
357 &self.spec
358 }
359
360 pub fn type_generator(&self) -> &TypeGenerator {
362 &self.type_gen
363 }
364
365 pub fn generate_types(&self) -> TokenStream {
367 self.type_gen.generate_all_types()
368 }
369
370 pub fn generate_responses(&self) -> TokenStream {
372 ResponseGenerator::new(
373 &self.spec,
374 &self.type_gen,
375 &self.type_overrides,
376 &self.response_suffixes,
377 )
378 .generate_all()
379 }
380
381 pub fn generate_query_structs(&self) -> TokenStream {
383 let mut structs = Vec::new();
384 for op in self.spec.operations() {
385 let unknown_field = self
387 .type_overrides
388 .get_query_unknown_field(op.method, &op.path);
389 if let Some((_, definition)) =
390 self.type_gen
391 .generate_query_struct(op, &self.type_overrides, unknown_field)
392 {
393 structs.push(definition);
394 }
395 }
396 quote::quote! { #(#structs)* }
397 }
398
399 pub fn generate_path_structs(&self) -> TokenStream {
401 let mut structs = Vec::new();
402 for op in self.spec.operations() {
403 if let Some((_, definition)) =
404 self.type_gen.generate_path_struct(op, &self.type_overrides)
405 {
406 structs.push(definition);
407 }
408 }
409 quote::quote! { #(#structs)* }
410 }
411
412 pub fn type_overrides(&self) -> &TypeOverrides {
414 &self.type_overrides
415 }
416
417 pub fn response_suffixes(&self) -> &ResponseSuffixes {
419 &self.response_suffixes
420 }
421
422 pub fn get_operation(&self, method: HttpMethod, path: &str) -> Option<&Operation> {
424 self.spec.get_operation(method, path)
425 }
426
427 pub fn operations(&self) -> impl Iterator<Item = &Operation> {
429 self.spec.operations()
430 }
431
432 pub fn validate_coverage(&self, covered: &HashSet<(HttpMethod, String)>) -> Result<()> {
434 let mut missing = Vec::new();
435
436 for op in self.spec.operations() {
437 let key = (op.method, op.path.clone());
438 if !covered.contains(&key) {
439 missing.push(format!("{} {}", op.method, op.path));
440 }
441 }
442
443 if missing.is_empty() {
444 Ok(())
445 } else {
446 Err(Error::MissingOperations(missing))
447 }
448 }
449}