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}
55
56#[derive(Debug, Clone, PartialEq, Eq, Hash)]
58pub struct TypeOverrideKey {
59 pub method: HttpMethod,
60 pub path: String,
61 pub kind: GeneratedTypeKind,
62}
63
64#[derive(Debug, Clone)]
66pub struct VariantOverride {
67 pub name: proc_macro2::Ident,
69 pub inner_type_name: Option<proc_macro2::Ident>,
72 pub attrs: Vec<TokenStream>,
74}
75
76#[derive(Debug, Clone)]
78pub enum TypeOverride {
79 Rename {
81 name: proc_macro2::Ident,
83 attrs: Vec<TokenStream>,
86 variant_overrides: HashMap<u16, VariantOverride>,
88 },
89 Replace(TokenStream),
91}
92
93#[derive(Debug, Clone, Default)]
95pub struct TypeOverrides {
96 overrides: HashMap<TypeOverrideKey, TypeOverride>,
97}
98
99impl TypeOverrides {
100 pub fn new() -> Self {
102 Self::default()
103 }
104
105 pub fn add_rename(
107 &mut self,
108 method: HttpMethod,
109 path: impl Into<String>,
110 kind: GeneratedTypeKind,
111 new_name: proc_macro2::Ident,
112 ) {
113 self.overrides.insert(
114 TypeOverrideKey {
115 method,
116 path: path.into(),
117 kind,
118 },
119 TypeOverride::Rename {
120 name: new_name,
121 attrs: Vec::new(),
122 variant_overrides: HashMap::new(),
123 },
124 );
125 }
126
127 pub fn add_rename_with_overrides(
129 &mut self,
130 method: HttpMethod,
131 path: impl Into<String>,
132 kind: GeneratedTypeKind,
133 new_name: proc_macro2::Ident,
134 attrs: Vec<TokenStream>,
135 variant_overrides: HashMap<u16, VariantOverride>,
136 ) {
137 self.overrides.insert(
138 TypeOverrideKey {
139 method,
140 path: path.into(),
141 kind,
142 },
143 TypeOverride::Rename {
144 name: new_name,
145 attrs,
146 variant_overrides,
147 },
148 );
149 }
150
151 pub fn add_replace(
153 &mut self,
154 method: HttpMethod,
155 path: impl Into<String>,
156 kind: GeneratedTypeKind,
157 replacement: TokenStream,
158 ) {
159 self.overrides.insert(
160 TypeOverrideKey {
161 method,
162 path: path.into(),
163 kind,
164 },
165 TypeOverride::Replace(replacement),
166 );
167 }
168
169 pub fn get(
171 &self,
172 method: HttpMethod,
173 path: &str,
174 kind: GeneratedTypeKind,
175 ) -> Option<&TypeOverride> {
176 self.overrides.get(&TypeOverrideKey {
177 method,
178 path: path.to_string(),
179 kind,
180 })
181 }
182
183 pub fn is_replaced(&self, method: HttpMethod, path: &str, kind: GeneratedTypeKind) -> bool {
185 matches!(self.get(method, path, kind), Some(TypeOverride::Replace(_)))
186 }
187}
188
189#[derive(Error, Debug)]
190pub enum Error {
191 #[error("failed to parse OpenAPI spec: {0}")]
192 ParseError(String),
193
194 #[error("operation not found: {method} {path}")]
195 OperationNotFound { method: String, path: String },
196
197 #[error("missing operations in trait: {0:?}")]
198 MissingOperations(Vec<String>),
199
200 #[error("type generation error: {0}")]
201 TypeGenError(String),
202
203 #[error("invalid attribute: {0}")]
204 InvalidAttribute(String),
205
206 #[error("unsupported feature: {0}")]
207 Unsupported(String),
208
209 #[error("unknown schema '{name}'. Available schemas: {available}")]
210 UnknownSchema { name: String, available: String },
211}
212
213pub type Result<T> = std::result::Result<T, Error>;
214
215pub struct GeneratorBuilder {
217 spec: OpenAPI,
218 settings: TypeSpaceSettings,
219 type_overrides: TypeOverrides,
220 response_suffixes: ResponseSuffixes,
221 schema_renames: HashMap<String, String>,
222}
223
224impl GeneratorBuilder {
225 pub fn new(spec: OpenAPI) -> Self {
227 Self {
228 spec,
229 settings: TypeSpaceSettings::default(),
230 type_overrides: TypeOverrides::default(),
231 response_suffixes: ResponseSuffixes::default(),
232 schema_renames: HashMap::new(),
233 }
234 }
235
236 pub fn from_file(path: &std::path::Path) -> Result<Self> {
238 let spec = openapi::load_spec(path)?;
239 Ok(Self::new(spec))
240 }
241
242 pub fn settings(mut self, settings: TypeSpaceSettings) -> Self {
244 self.settings = settings;
245 self
246 }
247
248 pub fn type_overrides(mut self, overrides: TypeOverrides) -> Self {
250 self.type_overrides = overrides;
251 self
252 }
253
254 pub fn response_suffixes(mut self, suffixes: ResponseSuffixes) -> Self {
256 self.response_suffixes = suffixes;
257 self
258 }
259
260 pub fn schema_renames(mut self, renames: HashMap<String, String>) -> Self {
262 self.schema_renames = renames;
263 self
264 }
265
266 pub fn build(self) -> Result<Generator> {
268 let parsed = ParsedSpec::from_openapi(self.spec)?;
269 let type_gen = TypeGenerator::with_settings(&parsed, self.settings, self.schema_renames)?;
270
271 Ok(Generator {
272 spec: parsed,
273 type_gen,
274 type_overrides: self.type_overrides,
275 response_suffixes: self.response_suffixes,
276 })
277 }
278}
279
280pub struct Generator {
282 spec: ParsedSpec,
283 type_gen: TypeGenerator,
284 type_overrides: TypeOverrides,
285 response_suffixes: ResponseSuffixes,
286}
287
288impl Generator {
289 pub fn builder(spec: OpenAPI) -> GeneratorBuilder {
291 GeneratorBuilder::new(spec)
292 }
293
294 pub fn builder_from_file(path: &std::path::Path) -> Result<GeneratorBuilder> {
296 GeneratorBuilder::from_file(path)
297 }
298
299 pub fn new(spec: OpenAPI) -> Result<Self> {
301 GeneratorBuilder::new(spec).build()
302 }
303
304 pub fn with_settings(spec: OpenAPI, settings: TypeSpaceSettings) -> Result<Self> {
306 GeneratorBuilder::new(spec).settings(settings).build()
307 }
308
309 pub fn from_file(path: &std::path::Path) -> Result<Self> {
311 GeneratorBuilder::from_file(path)?.build()
312 }
313
314 pub fn spec(&self) -> &ParsedSpec {
316 &self.spec
317 }
318
319 pub fn type_generator(&self) -> &TypeGenerator {
321 &self.type_gen
322 }
323
324 pub fn generate_types(&self) -> TokenStream {
326 self.type_gen.generate_all_types()
327 }
328
329 pub fn generate_responses(&self) -> TokenStream {
331 ResponseGenerator::new(
332 &self.spec,
333 &self.type_gen,
334 &self.type_overrides,
335 &self.response_suffixes,
336 )
337 .generate_all()
338 }
339
340 pub fn generate_query_structs(&self) -> TokenStream {
342 let mut structs = Vec::new();
343 for op in self.spec.operations() {
344 if let Some((_, definition)) = self
345 .type_gen
346 .generate_query_struct(op, &self.type_overrides)
347 {
348 structs.push(definition);
349 }
350 }
351 quote::quote! { #(#structs)* }
352 }
353
354 pub fn type_overrides(&self) -> &TypeOverrides {
356 &self.type_overrides
357 }
358
359 pub fn response_suffixes(&self) -> &ResponseSuffixes {
361 &self.response_suffixes
362 }
363
364 pub fn get_operation(&self, method: HttpMethod, path: &str) -> Option<&Operation> {
366 self.spec.get_operation(method, path)
367 }
368
369 pub fn operations(&self) -> impl Iterator<Item = &Operation> {
371 self.spec.operations()
372 }
373
374 pub fn validate_coverage(&self, covered: &HashSet<(HttpMethod, String)>) -> Result<()> {
376 let mut missing = Vec::new();
377
378 for op in self.spec.operations() {
379 let key = (op.method, op.path.clone());
380 if !covered.contains(&key) {
381 missing.push(format!("{} {}", op.method, op.path));
382 }
383 }
384
385 if missing.is_empty() {
386 Ok(())
387 } else {
388 Err(Error::MissingOperations(missing))
389 }
390 }
391}