1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
use crate::get_add_operation_fn_name;
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use syn::{parse::Parser, punctuated::Punctuated, token::Comma, Path, Result};
pub fn parse(routes: TokenStream) -> TokenStream {
parse_inner(routes)
.unwrap_or_else(|e| e.to_compile_error())
.into()
}
fn parse_inner(routes: TokenStream) -> Result<TokenStream2> {
let paths = <Punctuated<Path, Comma>>::parse_terminated.parse(routes)?;
let add_operations = create_add_operations(paths.clone())?;
let openapi_object = get_new_openapi_object();
Ok(quote! {
{
let settings = ::rocket_okapi::settings::OpenApiSettings::new();
let mut gen = ::rocket_okapi::gen::OpenApiGenerator::new(settings.clone());
#add_operations
#openapi_object
let mut routes = ::rocket::routes![#paths];
routes.push(::rocket_okapi::handlers::OpenApiHandler::new(spec).into_route(&settings.json_path));
routes
}
})
}
fn get_new_openapi_object() -> TokenStream2 {
quote! {
let mut spec = gen.into_openapi();
let mut info = ::okapi::openapi3::Info {
title: APPLICATION_NAME.to_string(),
version: env!("CARGO_PKG_VERSION").to_owned(),
..Default::default()
};
info.description = Some(APPLICATION_DESCRIPTION.to_string());
if !env!("CARGO_PKG_REPOSITORY").is_empty() {
info.contact = Some(::okapi::openapi3::Contact{
name: Some("Repository".to_owned()),
url: Some(env!("CARGO_PKG_REPOSITORY").to_owned()),
..Default::default()
});
}
if !env!("CARGO_PKG_HOMEPAGE").is_empty() {
info.contact = Some(::okapi::openapi3::Contact{
name: Some("Homepage".to_owned()),
url: Some(env!("CARGO_PKG_HOMEPAGE").to_owned()),
..Default::default()
});
}
info.license = Some(::okapi::openapi3::License{
name: APPLICATION_LICENSE.to_string(),
url: Some(APPLICATION_LICENSE_URL.to_string()),
..Default::default()
});
spec.info = info;
spec.external_docs = Some(::okapi::openapi3::ExternalDocs{
description: Some(APPLICATION_DOCS_NAME.to_string()),
url: APPLICATION_DOCS_URL.to_string(),
..Default::default()
});
}
}
pub fn parse_spec_and_routes(routes: TokenStream) -> TokenStream {
get_spec_and_routes(routes)
.unwrap_or_else(|e| e.to_compile_error())
.into()
}
fn get_spec_and_routes(routes: TokenStream) -> Result<TokenStream2> {
let paths = <Punctuated<Path, Comma>>::parse_terminated.parse(routes)?;
let add_operations = create_add_operations(paths.clone())?;
let openapi_object = get_new_openapi_object();
Ok(quote! {
{
let settings = ::rocket_okapi::settings::OpenApiSettings::new();
let mut gen = ::rocket_okapi::gen::OpenApiGenerator::new(settings.clone());
#add_operations
#openapi_object
let mut routes = ::rocket::routes![#paths];
routes.push(::rocket_okapi::handlers::OpenApiHandler::new(spec.clone()).into_route(&settings.json_path));
(spec, routes)
}
})
}
fn create_add_operations(paths: Punctuated<Path, Comma>) -> Result<TokenStream2> {
let function_calls = paths.into_iter().map(|path| {
let fn_name = fn_name_for_add_operation(path.clone());
let operation_id = operation_id(&path);
quote! {
#fn_name(&mut gen, #operation_id.to_owned())
.expect(&format!("Could not generate OpenAPI operation for `{}`.", stringify!(#path)));
}
});
Ok(quote! {
#(#function_calls)*
})
}
fn fn_name_for_add_operation(mut fn_path: Path) -> Path {
let mut last_seg = fn_path.segments.last_mut().expect("syn::Path has segments");
last_seg.ident = get_add_operation_fn_name(&last_seg.ident);
fn_path
}
fn operation_id(fn_path: &Path) -> String {
let idents: Vec<String> = fn_path
.segments
.iter()
.map(|s| s.ident.to_string())
.collect();
idents.join("_")
}