openapi_from_source/extractor/
mod.rs

1//! Route extraction module for parsing web framework route definitions.
2//!
3//! This module provides a unified interface for extracting route information from different
4//! web frameworks. Each framework has its own extractor implementation that knows how to
5//! parse framework-specific route definitions.
6//!
7//! # Supported Frameworks
8//!
9//! - **Axum**: See [`axum::AxumExtractor`]
10//! - **Actix-Web**: See [`actix::ActixExtractor`]
11//!
12//! # Example
13//!
14//! ```no_run
15//! use openapi_from_source::extractor::{RouteExtractor, axum::AxumExtractor};
16//! use openapi_from_source::parser::AstParser;
17//! use std::path::Path;
18//!
19//! let parsed = AstParser::parse_file(Path::new("src/main.rs")).unwrap();
20//! let extractor = AxumExtractor;
21//! let routes = extractor.extract_routes(&[parsed]);
22//! println!("Found {} routes", routes.len());
23//! ```
24
25pub mod axum;
26pub mod actix;
27
28use crate::parser::ParsedFile;
29
30/// Trait for extracting route information from parsed Rust files.
31///
32/// Implementations of this trait know how to analyze the AST of a specific web framework
33/// and extract route definitions, including paths, HTTP methods, parameters, and type information.
34pub trait RouteExtractor {
35    /// Extracts all route information from parsed Rust files.
36    ///
37    /// # Arguments
38    ///
39    /// * `parsed_files` - All successfully parsed Rust source files in the project
40    ///
41    /// # Returns
42    ///
43    /// Returns a vector of `RouteInfo` structs, one for each discovered route across all files.
44    fn extract_routes(&self, parsed_files: &[ParsedFile]) -> Vec<RouteInfo>;
45}
46
47/// Complete information about a single API endpoint.
48///
49/// This structure contains all the metadata needed to generate an OpenAPI operation,
50/// including the path, HTTP method, parameters, and request/response types.
51#[derive(Debug, Clone)]
52pub struct RouteInfo {
53    /// The URL path pattern (e.g., "/users/:id" or "/users/{id}")
54    pub path: String,
55    /// The HTTP method for this route
56    pub method: HttpMethod,
57    /// The name of the handler function
58    pub handler_name: String,
59    /// List of parameters extracted from the handler signature
60    pub parameters: Vec<Parameter>,
61    /// Type information for the request body, if present
62    pub request_body: Option<TypeInfo>,
63    /// Type information for the response, if it can be determined
64    pub response_type: Option<TypeInfo>,
65}
66
67/// HTTP methods supported by route extractors.
68///
69/// These correspond to standard HTTP methods used in RESTful APIs.
70#[derive(Debug, Clone, PartialEq, Eq, Hash)]
71pub enum HttpMethod {
72    /// HTTP GET method
73    Get,
74    /// HTTP POST method
75    Post,
76    /// HTTP PUT method
77    Put,
78    /// HTTP DELETE method
79    Delete,
80    /// HTTP PATCH method
81    Patch,
82    /// HTTP OPTIONS method
83    Options,
84    /// HTTP HEAD method
85    Head,
86}
87
88/// Information about a single parameter in a route handler.
89///
90/// Parameters can come from different locations (path, query string, headers)
91/// and have associated type information for schema generation.
92#[derive(Debug, Clone)]
93pub struct Parameter {
94    /// The parameter name
95    pub name: String,
96    /// Where the parameter is extracted from (path, query, header)
97    pub location: ParameterLocation,
98    /// Type information for generating the parameter schema
99    pub type_info: TypeInfo,
100    /// Whether the parameter is required (non-optional)
101    pub required: bool,
102}
103
104/// The location where a parameter value is extracted from in an HTTP request.
105#[derive(Debug, Clone, PartialEq, Eq)]
106pub enum ParameterLocation {
107    /// Path parameter embedded in the URL (e.g., `/users/:id`)
108    Path,
109    /// Query string parameter (e.g., `?page=1&limit=10`)
110    Query,
111    /// HTTP header parameter
112    Header,
113}
114
115/// Type information extracted from Rust code for OpenAPI schema generation.
116///
117/// This structure captures the essential information about a Rust type needed to
118/// generate an appropriate OpenAPI schema, including generic arguments and wrapper types.
119#[derive(Debug, Clone, PartialEq, Eq)]
120pub struct TypeInfo {
121    /// The base type name (e.g., "String", "User", "i32")
122    pub name: String,
123    /// Whether this is a generic type with type parameters
124    pub is_generic: bool,
125    /// Generic type arguments (e.g., for `Vec<String>`, contains TypeInfo for String)
126    pub generic_args: Vec<TypeInfo>,
127    /// Whether this type is wrapped in `Option<T>`
128    pub is_option: bool,
129    /// Whether this type is a `Vec<T>` (array type)
130    pub is_vec: bool,
131}
132
133impl TypeInfo {
134    /// Create a new TypeInfo for a simple type
135    pub fn new(name: String) -> Self {
136        Self {
137            name,
138            is_generic: false,
139            generic_args: Vec::new(),
140            is_option: false,
141            is_vec: false,
142        }
143    }
144
145    /// Create a TypeInfo for an `Option<T>` type
146    pub fn option(inner: TypeInfo) -> Self {
147        Self {
148            name: inner.name.clone(),
149            is_generic: false,
150            generic_args: vec![inner],
151            is_option: true,
152            is_vec: false,
153        }
154    }
155
156    /// Create a TypeInfo for a `Vec<T>` type
157    pub fn vec(inner: TypeInfo) -> Self {
158        Self {
159            name: inner.name.clone(),
160            is_generic: false,
161            generic_args: vec![inner],
162            is_option: false,
163            is_vec: true,
164        }
165    }
166}
167
168impl RouteInfo {
169    /// Create a new RouteInfo with minimal required fields
170    pub fn new(path: String, method: HttpMethod, handler_name: String) -> Self {
171        Self {
172            path,
173            method,
174            handler_name,
175            parameters: Vec::new(),
176            request_body: None,
177            response_type: None,
178        }
179    }
180}
181
182impl Parameter {
183    /// Create a new Parameter
184    pub fn new(name: String, location: ParameterLocation, type_info: TypeInfo, required: bool) -> Self {
185        Self {
186            name,
187            location,
188            type_info,
189            required,
190        }
191    }
192}