Skip to main content

oxapi_impl/
lib.rs

1//! Core implementation for the oxapi OpenAPI server stub generator.
2
3use 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
44/// Main generator that coordinates all the pieces.
45pub struct Generator {
46    spec: ParsedSpec,
47    type_gen: TypeGenerator,
48}
49
50impl Generator {
51    /// Create a new generator from an OpenAPI spec.
52    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    /// Load and parse an OpenAPI spec from a file path.
63    pub fn from_file(path: &std::path::Path) -> Result<Self> {
64        let spec = openapi::load_spec(path)?;
65        Self::new(spec)
66    }
67
68    /// Get the parsed spec.
69    pub fn spec(&self) -> &ParsedSpec {
70        &self.spec
71    }
72
73    /// Get the type generator.
74    pub fn type_generator(&self) -> &TypeGenerator {
75        &self.type_gen
76    }
77
78    /// Generate all types as a TokenStream.
79    pub fn generate_types(&self) -> TokenStream {
80        self.type_gen.generate_all_types()
81    }
82
83    /// Generate response enums for all operations.
84    pub fn generate_responses(&self) -> TokenStream {
85        ResponseGenerator::new(&self.spec, &self.type_gen).generate_all()
86    }
87
88    /// Look up an operation by HTTP method and path.
89    pub fn get_operation(&self, method: HttpMethod, path: &str) -> Option<&Operation> {
90        self.spec.get_operation(method, path)
91    }
92
93    /// Get all operations.
94    pub fn operations(&self) -> impl Iterator<Item = &Operation> {
95        self.spec.operations()
96    }
97
98    /// Validate that all operations are covered by trait methods.
99    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}