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