Skip to content

Commit 521216c

Browse files
committed
implement auto-documenting routes
1 parent fe23eae commit 521216c

File tree

6 files changed

+350
-1
lines changed

6 files changed

+350
-1
lines changed

core/codegen/src/attribute/route/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,9 @@ fn codegen_route(route: Route) -> Result<TokenStream> {
319319
let rank = Optional(route.attr.rank);
320320
let format = Optional(route.attr.format.as_ref());
321321

322+
// Get the doc comment
323+
let docstring = &route.docstring;
324+
322325
Ok(quote! {
323326
#handler_fn
324327

@@ -353,6 +356,7 @@ fn codegen_route(route: Route) -> Result<TokenStream> {
353356
format: #format,
354357
rank: #rank,
355358
sentinels: #sentinels,
359+
docstring: #docstring,
356360
}
357361
}
358362

core/codegen/src/attribute/route/parse.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ pub struct Route {
2929
pub handler: syn::ItemFn,
3030
/// The parsed arguments to the user's function.
3131
pub arguments: Arguments,
32+
/// The doc comment describing this route
33+
pub docstring: String,
3234
}
3335

3436
type ArgumentMap = IndexMap<Name, (syn::Ident, syn::Type)>;
@@ -209,9 +211,11 @@ impl Route {
209211
})
210212
.collect();
211213

214+
let docstring = String::from_attrs("doc", &handler.attrs)?.join("\n");
215+
212216
diags.head_err_or(Route {
213217
attr, path_params, query_params, data_guard, request_guards,
214-
handler, arguments,
218+
handler, arguments, docstring
215219
})
216220
}
217221
}

core/lib/src/doc/has_schema.rs

Lines changed: 285 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,285 @@
1+
pub enum SchemaKind {
2+
Null,
3+
Map,
4+
List,
5+
String,
6+
Num,
7+
Int,
8+
Bool,
9+
Set,
10+
}
11+
12+
pub struct Schema<T> {
13+
pub description: Option<String>,
14+
pub example: Option<T>,
15+
pub name: String,
16+
pub kind: SchemaKind,
17+
18+
}
19+
20+
pub trait HasSchema: Sized {
21+
fn schema() -> Schema<Self>;
22+
}
23+
24+
// impls for the entire serde data model:
25+
26+
// 14 primitve types
27+
impl HasSchema for i8 {
28+
fn schema() -> Schema<Self> {
29+
Schema {
30+
description: None,
31+
example: Some(1),
32+
name: "signed 8-bits integer".to_string(),
33+
kind: SchemaKind::Int,
34+
}
35+
}
36+
}
37+
38+
impl HasSchema for i16 {
39+
fn schema() -> Schema<Self> {
40+
Schema {
41+
description: None,
42+
example: Some(1),
43+
name: "signed 16-bits integer".to_string(),
44+
kind: SchemaKind::Int,
45+
}
46+
}
47+
}
48+
49+
impl HasSchema for i32 {
50+
fn schema() -> Schema<Self> {
51+
Schema {
52+
description: None,
53+
example: Some(1),
54+
name: "signed 32-bits integer".to_string(),
55+
kind: SchemaKind::Int,
56+
}
57+
}
58+
}
59+
60+
impl HasSchema for i64 {
61+
fn schema() -> Schema<Self> {
62+
Schema {
63+
description: None,
64+
example: Some(1),
65+
name: "signed 64-bits integer".to_string(),
66+
kind: SchemaKind::Int,
67+
}
68+
}
69+
}
70+
71+
impl HasSchema for i128 {
72+
fn schema() -> Schema<Self> {
73+
Schema {
74+
description: None,
75+
example: Some(1),
76+
name: "signed 128-bits integer".to_string(),
77+
kind: SchemaKind::Int,
78+
}
79+
}
80+
}
81+
82+
impl HasSchema for u8 {
83+
fn schema() -> Schema<Self> {
84+
Schema {
85+
description: None,
86+
example: Some(1),
87+
name: "unsigned 8-bits integer".to_string(),
88+
kind: SchemaKind::Int,
89+
}
90+
}
91+
}
92+
93+
impl HasSchema for u16 {
94+
fn schema() -> Schema<Self> {
95+
Schema {
96+
description: None,
97+
example: Some(1),
98+
name: "unsigned 16-bits integer".to_string(),
99+
kind: SchemaKind::Int,
100+
}
101+
}
102+
}
103+
104+
impl HasSchema for u32 {
105+
fn schema() -> Schema<Self> {
106+
Schema {
107+
description: None,
108+
example: Some(1),
109+
name: "unsigned 32-bits integer".to_string(),
110+
kind: SchemaKind::Int,
111+
}
112+
}
113+
}
114+
115+
impl HasSchema for u64 {
116+
fn schema() -> Schema<Self> {
117+
Schema {
118+
description: None,
119+
example: Some(1),
120+
name: "unsigned 64-bits integer".to_string(),
121+
kind: SchemaKind::Int,
122+
}
123+
}
124+
}
125+
126+
impl HasSchema for u128 {
127+
fn schema() -> Schema<Self> {
128+
Schema {
129+
description: None,
130+
example: Some(1),
131+
name: "unsigned 128-bits integer".to_string(),
132+
kind: SchemaKind::Int,
133+
}
134+
}
135+
}
136+
137+
// string
138+
impl<'a> HasSchema for &'a str {
139+
fn schema() -> Schema<Self> {
140+
Schema {
141+
description: None,
142+
example: Some("string"),
143+
name: "signed 8-bits integer".to_string(),
144+
kind: SchemaKind::String,
145+
}
146+
}
147+
}
148+
149+
impl<'a> HasSchema for String {
150+
fn schema() -> Schema<Self> {
151+
Schema {
152+
description: None,
153+
example: Some("string".to_string()),
154+
name: "signed 8-bits integer".to_string(),
155+
kind: SchemaKind::String,
156+
}
157+
}
158+
}
159+
160+
// byte array
161+
impl<'a> HasSchema for &'a [u8] {
162+
fn schema() -> Schema<Self> {
163+
Schema {
164+
description: None,
165+
example: None,
166+
name: "An array of bytes".to_string(),
167+
kind: SchemaKind::List,
168+
}
169+
}
170+
}
171+
172+
// option
173+
impl<T: HasSchema> HasSchema for Option<T> {
174+
fn schema() -> Schema<Self> {
175+
let base_schema = T::schema();
176+
Schema {
177+
description: None,
178+
example: Some(base_schema.example),
179+
name: format!("Optional: {}", base_schema.name),
180+
kind: base_schema.kind,
181+
}
182+
}
183+
}
184+
185+
// unit
186+
impl HasSchema for () {
187+
fn schema() -> Schema<Self> {
188+
Schema {
189+
description: None,
190+
example: Some(()),
191+
name: "Nothing".to_string(),
192+
kind: SchemaKind::Null,
193+
}
194+
}
195+
}
196+
197+
// seq
198+
impl<T: HasSchema, const N: usize> HasSchema for [T; N] {
199+
fn schema() -> Schema<Self> {
200+
let base_schema = T::schema();
201+
Schema {
202+
description: None,
203+
example: None, // making an array example requires that T be Copy...
204+
name: format!("Array of {} {}'s", N, base_schema.name),
205+
kind: SchemaKind::List,
206+
}
207+
}
208+
}
209+
210+
impl<T: HasSchema> HasSchema for Vec<T> {
211+
fn schema() -> Schema<Self> {
212+
let base_schema = T::schema();
213+
Schema {
214+
description: None,
215+
example: None, // making an array example requires that T be Copy...
216+
name: format!("Unsized array of {}'s", base_schema.name),
217+
kind: SchemaKind::List,
218+
}
219+
}
220+
}
221+
222+
impl<T: HasSchema> HasSchema for std::collections::HashSet<T> {
223+
fn schema() -> Schema<Self> {
224+
Schema {
225+
description: None,
226+
example: None, // making an array example requires that T be Copy...
227+
name: format!("Set of {}'s", T::schema().name),
228+
kind: SchemaKind::Set,
229+
}
230+
}
231+
}
232+
233+
// tuple
234+
impl<T1: HasSchema> HasSchema for (T1, ) {
235+
fn schema() -> Schema<Self> {
236+
Schema {
237+
description: None,
238+
example: None, // making an array example requires that T be Copy...
239+
name: format!("Unary tuple of an {}", T1::schema().name),
240+
kind: SchemaKind::Set,
241+
}
242+
}
243+
}
244+
245+
impl<T1: HasSchema, T2: HasSchema> HasSchema for (T1, T2) {
246+
fn schema() -> Schema<Self> {
247+
Schema {
248+
description: None,
249+
example: None, // making an array example requires that T be Copy...
250+
name: format!("Tuple of the form ({}, {})", T1::schema().name, T2::schema().name),
251+
kind: SchemaKind::Set,
252+
}
253+
}
254+
}
255+
256+
// todo: extend with macros
257+
258+
// map
259+
impl<K: HasSchema, V: HasSchema> HasSchema for std::collections::HashMap<K, V> {
260+
fn schema() -> Schema<Self> {
261+
Schema {
262+
description: None,
263+
example: None, // making an array example requires that T be Copy...
264+
name: format!("Map from {} to {}", K::schema().name, V::schema().name),
265+
kind: SchemaKind::Map,
266+
}
267+
}
268+
}
269+
270+
271+
272+
// impl<T: HasSchema> HasSchema for Box<T> {
273+
// fn schema() -> Schema<Self> {
274+
// let base_schema = T::schema();
275+
// Schema {
276+
// description: base_schema.description,
277+
// example: base_schema.example.map(Box::new),
278+
// name: base_schema.name,
279+
// kind: base_schema.kind,
280+
// }
281+
// }
282+
// }
283+
284+
285+

core/lib/src/doc/mod.rs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
//! Traits and structs related to automagically generating documentation for your Rocket routes
2+
3+
use std::{collections::HashMap, marker::PhantomData};
4+
5+
use rocket_http::ContentType;
6+
7+
mod has_schema;
8+
9+
#[derive(Default)]
10+
pub struct Docs(HashMap<ContentType, DocContent>);
11+
12+
#[derive(Default)]
13+
pub struct DocContent {
14+
title: Option<String>,
15+
description: Option<String>,
16+
content_type: Option<String>,
17+
}
18+
19+
pub struct Resolve<T: ?Sized>(PhantomData<T>);
20+
21+
pub trait Documented {
22+
fn docs() -> Docs;
23+
}
24+
25+
trait Undocumented {
26+
fn docs() -> Docs {
27+
Docs::default()
28+
}
29+
}
30+
31+
impl<T: ?Sized> Undocumented for T { }
32+
33+
impl<T: Documented + ?Sized> Resolve<T> {
34+
pub const DOCUMENTED: bool = true;
35+
36+
pub fn docs() -> Docs {
37+
T::docs()
38+
}
39+
}
40+
41+
// impl<T: Documented + ?Sized> Documented for Json<T> {
42+
// fn docs() -> Docs {
43+
// Docs {
44+
// content_type: Some("application/json".to_string()),
45+
// ..Self::docs()
46+
// }
47+
// }
48+
// }

core/lib/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ pub mod fairing;
124124
pub mod error;
125125
pub mod catcher;
126126
pub mod route;
127+
pub mod doc;
127128

128129
// Reexport of HTTP everything.
129130
pub mod http {

0 commit comments

Comments
 (0)