Skip to content

Commit 29a4175

Browse files
committed
implement auto-documenting routes
1 parent fe23eae commit 29a4175

File tree

10 files changed

+279
-1
lines changed

10 files changed

+279
-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.to_string(),
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: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
mod array_impls;
2+
mod box_impls;
3+
mod number_impls;
4+
mod string_impls;
5+
6+
pub enum SchemaKind {
7+
Null,
8+
Map,
9+
List,
10+
String,
11+
Num,
12+
Int,
13+
Bool,
14+
}
15+
16+
pub struct Schema<T> {
17+
pub min_value: Option<T>,
18+
pub max_value: Option<T>,
19+
pub description: Option<String>,
20+
pub example: Option<T>,
21+
pub name: String,
22+
pub kind: SchemaKind,
23+
24+
}
25+
26+
pub trait HasSchema: Sized {
27+
fn schema() -> Schema<Self>;
28+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
2+
3+
impl<T: super::HasSchema, const N: usize> super::HasSchema for [T; N] {
4+
fn schema() -> super::Schema<Self> {
5+
let base_schema = T::schema();
6+
super::Schema {
7+
min_value: None,
8+
max_value: None,
9+
description: None,
10+
example: None, // making an array example requires that T be Copy...
11+
name: format!("Array of {} {}'s", N, base_schema.name),
12+
kind: super::SchemaKind::List,
13+
}
14+
}
15+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
2+
3+
impl<T: super::HasSchema> super::HasSchema for Box<T> {
4+
fn schema() -> super::Schema<Self> {
5+
let base_schema = T::schema();
6+
super::Schema {
7+
min_value: base_schema.min_value.map(Box::new),
8+
max_value: base_schema.max_value.map(Box::new),
9+
description: base_schema.description,
10+
example: base_schema.example.map(Box::new),
11+
name: base_schema.name,
12+
kind: base_schema.kind,
13+
}
14+
}
15+
}
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
impl super::HasSchema for i8 {
2+
fn schema() -> super::Schema<Self> {
3+
super::Schema {
4+
min_value: Some(i8::MIN),
5+
max_value: Some(i8::MAX),
6+
description: None,
7+
example: Some(1),
8+
name: "signed 8-bits integer".to_string(),
9+
kind: super::SchemaKind::Int,
10+
}
11+
}
12+
}
13+
14+
impl super::HasSchema for i16 {
15+
fn schema() -> super::Schema<Self> {
16+
super::Schema {
17+
min_value: Some(i16::MIN),
18+
max_value: Some(i16::MAX),
19+
description: None,
20+
example: Some(1),
21+
name: "signed 16-bits integer".to_string(),
22+
kind: super::SchemaKind::Int,
23+
}
24+
}
25+
}
26+
27+
impl super::HasSchema for i32 {
28+
fn schema() -> super::Schema<Self> {
29+
super::Schema {
30+
min_value: Some(i32::MIN),
31+
max_value: Some(i32::MAX),
32+
description: None,
33+
example: Some(1),
34+
name: "signed 32-bits integer".to_string(),
35+
kind: super::SchemaKind::Int,
36+
}
37+
}
38+
}
39+
40+
impl super::HasSchema for i64 {
41+
fn schema() -> super::Schema<Self> {
42+
super::Schema {
43+
min_value: Some(i64::MIN),
44+
max_value: Some(i64::MAX),
45+
description: None,
46+
example: Some(1),
47+
name: "signed 64-bits integer".to_string(),
48+
kind: super::SchemaKind::Int,
49+
}
50+
}
51+
}
52+
53+
impl super::HasSchema for i128 {
54+
fn schema() -> super::Schema<Self> {
55+
super::Schema {
56+
min_value: Some(i128::MIN),
57+
max_value: Some(i128::MAX),
58+
description: None,
59+
example: Some(1),
60+
name: "signed 128-bits integer".to_string(),
61+
kind: super::SchemaKind::Int,
62+
}
63+
}
64+
}
65+
66+
impl super::HasSchema for u8 {
67+
fn schema() -> super::Schema<Self> {
68+
super::Schema {
69+
min_value: Some(u8::MIN),
70+
max_value: Some(u8::MAX),
71+
description: None,
72+
example: Some(1),
73+
name: "unsigned 8-bits integer".to_string(),
74+
kind: super::SchemaKind::Int,
75+
}
76+
}
77+
}
78+
79+
impl super::HasSchema for u16 {
80+
fn schema() -> super::Schema<Self> {
81+
super::Schema {
82+
min_value: Some(u16::MIN),
83+
max_value: Some(u16::MAX),
84+
description: None,
85+
example: Some(1),
86+
name: "unsigned 16-bits integer".to_string(),
87+
kind: super::SchemaKind::Int,
88+
}
89+
}
90+
}
91+
92+
impl super::HasSchema for u32 {
93+
fn schema() -> super::Schema<Self> {
94+
super::Schema {
95+
min_value: Some(u32::MIN),
96+
max_value: Some(u32::MAX),
97+
description: None,
98+
example: Some(1),
99+
name: "unsigned 32-bits integer".to_string(),
100+
kind: super::SchemaKind::Int,
101+
}
102+
}
103+
}
104+
105+
impl super::HasSchema for u64 {
106+
fn schema() -> super::Schema<Self> {
107+
super::Schema {
108+
min_value: Some(u64::MIN),
109+
max_value: Some(u64::MAX),
110+
description: None,
111+
example: Some(1),
112+
name: "unsigned 64-bits integer".to_string(),
113+
kind: super::SchemaKind::Int,
114+
}
115+
}
116+
}
117+
118+
impl super::HasSchema for u128 {
119+
fn schema() -> super::Schema<Self> {
120+
super::Schema {
121+
min_value: Some(u128::MIN),
122+
max_value: Some(u128::MAX),
123+
description: None,
124+
example: Some(1),
125+
name: "unsigned 128-bits integer".to_string(),
126+
kind: super::SchemaKind::Int,
127+
}
128+
}
129+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
2+
3+
impl<'a> super::HasSchema for &'a str {
4+
fn schema() -> super::Schema<Self> {
5+
super::Schema {
6+
min_value: None,
7+
max_value: None,
8+
description: None,
9+
example: Some("string"),
10+
name: "signed 8-bits integer".to_string(),
11+
kind: super::SchemaKind::String,
12+
}
13+
}
14+
}
15+
16+
impl<'a> super::HasSchema for String {
17+
fn schema() -> super::Schema<Self> {
18+
super::Schema {
19+
min_value: None,
20+
max_value: None,
21+
description: None,
22+
example: Some("string".to_string()),
23+
name: "signed 8-bits integer".to_string(),
24+
kind: super::SchemaKind::String,
25+
}
26+
}
27+
}

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 {

core/lib/src/route/route.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,8 @@ pub struct Route {
190190
pub format: Option<MediaType>,
191191
/// The discovered sentinels.
192192
pub(crate) sentinels: Vec<Sentry>,
193+
/// The route's docstring, which may be empty.
194+
pub docstring: String,
193195
}
194196

195197
impl Route {
@@ -253,6 +255,7 @@ impl Route {
253255
sentinels: Vec::new(),
254256
handler: Box::new(handler),
255257
rank, uri, method,
258+
docstring: String::new(),
256259
}
257260
}
258261

@@ -345,6 +348,9 @@ pub struct StaticInfo {
345348
/// Route-derived sentinels, if any.
346349
/// This isn't `&'static [SentryInfo]` because `type_name()` isn't `const`.
347350
pub sentinels: Vec<Sentry>,
351+
/// The doc comment associated with this route.
352+
pub docstring: String,
353+
348354
}
349355

350356
#[doc(hidden)]
@@ -361,6 +367,7 @@ impl From<StaticInfo> for Route {
361367
format: info.format,
362368
sentinels: info.sentinels.into_iter().collect(),
363369
uri,
370+
docstring: info.docstring,
364371
}
365372
}
366373
}

0 commit comments

Comments
 (0)