rocket_autodocu_codegen/
lib.rs

1#![forbid(unsafe_code)]
2#![deny(clippy::all)]
3
4//! This crate is used by [`rocket_autodocu`](https://crates.io/crates/rocket_autodocu)
5//! for code generation. This crate includes the procedural macros like:
6//! - `#[openapi]`: To generate the documentation for an endpoint/route.
7//! - `openapi_routes![...]`: Returns a closure for generating routes.
8//! - `openapi_spec![...]`: Returns a closure for generating OpenApi objects.
9//! - `#[derive(OpenApiFromRequest)]`: Implement `OpenApiFromRequest` trait for a given struct.
10//!
11
12mod openapi_attr;
13mod openapi_spec;
14mod parse_routes;
15
16use proc_macro::TokenStream;
17use quote::quote;
18use syn::Ident;
19
20/// A proc macro to be used in tandem with one of `Rocket`'s endpoint macros. It requires that all
21/// of the arguments of the route implement one of the traits in `rocket_autodocu::request`, and that
22/// the return type implements `OpenApiResponder`.
23/// ### Example
24/// ```rust,ignore
25/// use rocket_autodocu::openapi;
26/// use rocket::get;
27///
28/// #[openapi]
29/// #[get("/hello/<number>")]
30/// fn hello_world(number: i32) -> String {
31///     format!("Hello world number {}", number)
32/// }
33/// ```
34#[proc_macro_attribute]
35pub fn openapi(args: TokenStream, mut input: TokenStream) -> TokenStream {
36    // We don't need to modify/replace the input TokenStream,
37    // we just need to append to it.
38    input.extend(openapi_attr::parse(args, input.clone()));
39    input
40}
41
42/// Generate and return a closure that can be used to generate the routes.
43///
44/// This closure take 2 arguments:
45/// - `spec_opt`: `Option<rocket_autodocu::autodocu::openapi3::OpenApi>`
46/// - `settings`: `rocket_autodocu::settings::OpenApiSettings`
47///
48/// It returns `Vec<::rocket::Route>`.
49///
50/// If `spec_opt` is set to `None` it will not add a route to serve the `openapi.json` file.
51///
52/// Example:
53/// ```rust,ignore
54/// let settings = rocket_autodocu::settings::OpenApiSettings::new();
55/// let spec = rocket_autodocu::openapi_spec![get_message, post_message](settings.clone());
56/// let routes = rocket_autodocu::openapi_routes![get_message, post_message](Some(spec), settings);
57/// ```
58#[proc_macro]
59pub fn openapi_routes(input: TokenStream) -> TokenStream {
60    let routes = parse_routes::parse_routes(input).unwrap_or_else(|e| e.to_compile_error());
61    (quote! {
62        #routes
63    })
64    .into()
65}
66
67/// Generate and return a closure that can be used to generate the OpenAPI specification.
68///
69/// This closure take 1 argument:
70/// - `settings`: `rocket_autodocu::settings::OpenApiSettings`
71///
72/// It returns `rocket_autodocu::autodocu::openapi3::OpenApi`.
73///
74/// Example:
75/// ```rust,ignore
76/// let settings = rocket_autodocu::settings::OpenApiSettings::new();
77/// let spec = rocket_autodocu::openapi_spec![get_message, post_message](settings);
78/// ```
79#[proc_macro]
80pub fn openapi_spec(input: TokenStream) -> TokenStream {
81    let spec = openapi_spec::create_openapi_spec(input).unwrap_or_else(|e| e.to_compile_error());
82    (quote! {
83        #spec
84    })
85    .into()
86}
87
88/// Derive marco for the `OpenApiFromRequest` trait.
89///
90/// This derive trait is a very simple implementation for anything that does not
91/// require any other special headers or parameters to be validated.
92///
93/// Use:
94/// ```rust,ignore
95/// use rocket_autodocu::request::OpenApiFromRequest;
96///
97/// #[derive(OpenApiFromRequest)]
98/// pub struct MyStructName;
99/// ```
100///
101/// This code is equivalent to:
102/// ```rust,ignore
103/// use rocket_autodocu::request::{OpenApiFromRequest, RequestHeaderInput};
104/// use rocket_autodocu::gen::OpenApiGenerator;
105///
106/// pub struct MyStructName;
107///
108/// impl<'r> OpenApiFromRequest<'r> for MyStructName {
109///     fn from_request_input(
110///         _gen: &mut OpenApiGenerator,
111///         _name: String,
112///         _required: bool,
113///     ) -> rocket_autodocu::Result<RequestHeaderInput> {
114///         Ok(RequestHeaderInput::None)
115///     }
116/// }
117/// ```
118#[proc_macro_derive(OpenApiFromRequest)]
119pub fn open_api_from_request_derive(input: TokenStream) -> TokenStream {
120    // Construct a representation of Rust code as a syntax tree
121    // that we can manipulate
122    let ast: syn::DeriveInput = syn::parse(input).unwrap();
123    let name = &ast.ident;
124
125    let gen = quote! {
126        impl<'r> rocket_autodocu::request::OpenApiFromRequest<'r> for #name {
127            fn from_request_input(
128                _gen: &mut rocket_autodocu::gen::OpenApiGenerator,
129                _name: String,
130                _required: bool,
131            ) -> rocket_autodocu::Result<rocket_autodocu::request::RequestHeaderInput> {
132                Ok(rocket_autodocu::request::RequestHeaderInput::None)
133            }
134        }
135    };
136    gen.into()
137}
138
139fn get_add_operation_fn_name(route_fn_name: &Ident) -> Ident {
140    Ident::new(
141        &format!("autodocu_add_operation_for_{}_", route_fn_name),
142        route_fn_name.span(),
143    )
144}