1use std::collections::HashMap;
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;
16pub use openapi::{HttpMethod, Operation, OperationParam, ParamLocation, ParsedSpec};
17pub use responses::ResponseGenerator;
18pub use router::RouterGenerator;
19pub use types::TypeGenerator;
20
21#[derive(Error, Debug)]
22pub enum Error {
23 #[error("failed to parse OpenAPI spec: {0}")]
24 ParseError(String),
25
26 #[error("operation not found: {method} {path}")]
27 OperationNotFound { method: String, path: String },
28
29 #[error("missing operations in trait: {0:?}")]
30 MissingOperations(Vec<String>),
31
32 #[error("type generation error: {0}")]
33 TypeGenError(String),
34
35 #[error("invalid attribute: {0}")]
36 InvalidAttribute(String),
37
38 #[error("unsupported feature: {0}")]
39 Unsupported(String),
40}
41
42pub type Result<T> = std::result::Result<T, Error>;
43
44pub struct Generator {
46 spec: ParsedSpec,
47 type_gen: TypeGenerator,
48}
49
50impl Generator {
51 pub fn new(spec: OpenAPI) -> Result<Self> {
53 let parsed = ParsedSpec::from_openapi(spec)?;
54 let type_gen = TypeGenerator::new(&parsed)?;
55
56 Ok(Self {
57 spec: parsed,
58 type_gen,
59 })
60 }
61
62 pub fn from_file(path: &std::path::Path) -> Result<Self> {
64 let spec = openapi::load_spec(path)?;
65 Self::new(spec)
66 }
67
68 pub fn spec(&self) -> &ParsedSpec {
70 &self.spec
71 }
72
73 pub fn type_generator(&self) -> &TypeGenerator {
75 &self.type_gen
76 }
77
78 pub fn generate_types(&self) -> TokenStream {
80 self.type_gen.generate_all_types()
81 }
82
83 pub fn generate_responses(&self) -> TokenStream {
85 ResponseGenerator::new(&self.spec, &self.type_gen).generate_all()
86 }
87
88 pub fn get_operation(&self, method: HttpMethod, path: &str) -> Option<&Operation> {
90 self.spec.get_operation(method, path)
91 }
92
93 pub fn operations(&self) -> impl Iterator<Item = &Operation> {
95 self.spec.operations()
96 }
97
98 pub fn validate_coverage(&self, covered: &HashMap<(HttpMethod, String), ()>) -> Result<()> {
100 let mut missing = Vec::new();
101
102 for op in self.spec.operations() {
103 let key = (op.method, op.path.clone());
104 if !covered.contains_key(&key) {
105 missing.push(format!("{} {}", op.method, op.path));
106 }
107 }
108
109 if missing.is_empty() {
110 Ok(())
111 } else {
112 Err(Error::MissingOperations(missing))
113 }
114 }
115}