Skip to content

Commit

Permalink
implement auto-documenting routes
Browse files Browse the repository at this point in the history
  • Loading branch information
ThouCheese committed Apr 30, 2021
1 parent fe23eae commit 29a4175
Show file tree
Hide file tree
Showing 10 changed files with 279 additions and 1 deletion.
4 changes: 4 additions & 0 deletions core/codegen/src/attribute/route/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,9 @@ fn codegen_route(route: Route) -> Result<TokenStream> {
let rank = Optional(route.attr.rank);
let format = Optional(route.attr.format.as_ref());

// Get the doc comment
let docstring = &route.docstring;

Ok(quote! {
#handler_fn

Expand Down Expand Up @@ -353,6 +356,7 @@ fn codegen_route(route: Route) -> Result<TokenStream> {
format: #format,
rank: #rank,
sentinels: #sentinels,
docstring: #docstring.to_string(),
}
}

Expand Down
6 changes: 5 additions & 1 deletion core/codegen/src/attribute/route/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ pub struct Route {
pub handler: syn::ItemFn,
/// The parsed arguments to the user's function.
pub arguments: Arguments,
/// The doc comment describing this route
pub docstring: String
}

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

let docstring = String::from_attrs("doc", &handler.attrs)?.join("\n");

diags.head_err_or(Route {
attr, path_params, query_params, data_guard, request_guards,
handler, arguments,
handler, arguments, docstring
})
}
}
Expand Down
28 changes: 28 additions & 0 deletions core/lib/src/doc/has_schema.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
mod array_impls;
mod box_impls;
mod number_impls;
mod string_impls;

pub enum SchemaKind {
Null,
Map,
List,
String,
Num,
Int,
Bool,
}

pub struct Schema<T> {
pub min_value: Option<T>,
pub max_value: Option<T>,
pub description: Option<String>,
pub example: Option<T>,
pub name: String,
pub kind: SchemaKind,

}

pub trait HasSchema: Sized {
fn schema() -> Schema<Self>;
}
15 changes: 15 additions & 0 deletions core/lib/src/doc/has_schema/array_impls.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@


impl<T: super::HasSchema, const N: usize> super::HasSchema for [T; N] {
fn schema() -> super::Schema<Self> {
let base_schema = T::schema();
super::Schema {
min_value: None,
max_value: None,
description: None,
example: None, // making an array example requires that T be Copy...
name: format!("Array of {} {}'s", N, base_schema.name),
kind: super::SchemaKind::List,
}
}
}
15 changes: 15 additions & 0 deletions core/lib/src/doc/has_schema/box_impls.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@


impl<T: super::HasSchema> super::HasSchema for Box<T> {
fn schema() -> super::Schema<Self> {
let base_schema = T::schema();
super::Schema {
min_value: base_schema.min_value.map(Box::new),
max_value: base_schema.max_value.map(Box::new),
description: base_schema.description,
example: base_schema.example.map(Box::new),
name: base_schema.name,
kind: base_schema.kind,
}
}
}
129 changes: 129 additions & 0 deletions core/lib/src/doc/has_schema/number_impls.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
impl super::HasSchema for i8 {
fn schema() -> super::Schema<Self> {
super::Schema {
min_value: Some(i8::MIN),
max_value: Some(i8::MAX),
description: None,
example: Some(1),
name: "signed 8-bits integer".to_string(),
kind: super::SchemaKind::Int,
}
}
}

impl super::HasSchema for i16 {
fn schema() -> super::Schema<Self> {
super::Schema {
min_value: Some(i16::MIN),
max_value: Some(i16::MAX),
description: None,
example: Some(1),
name: "signed 16-bits integer".to_string(),
kind: super::SchemaKind::Int,
}
}
}

impl super::HasSchema for i32 {
fn schema() -> super::Schema<Self> {
super::Schema {
min_value: Some(i32::MIN),
max_value: Some(i32::MAX),
description: None,
example: Some(1),
name: "signed 32-bits integer".to_string(),
kind: super::SchemaKind::Int,
}
}
}

impl super::HasSchema for i64 {
fn schema() -> super::Schema<Self> {
super::Schema {
min_value: Some(i64::MIN),
max_value: Some(i64::MAX),
description: None,
example: Some(1),
name: "signed 64-bits integer".to_string(),
kind: super::SchemaKind::Int,
}
}
}

impl super::HasSchema for i128 {
fn schema() -> super::Schema<Self> {
super::Schema {
min_value: Some(i128::MIN),
max_value: Some(i128::MAX),
description: None,
example: Some(1),
name: "signed 128-bits integer".to_string(),
kind: super::SchemaKind::Int,
}
}
}

impl super::HasSchema for u8 {
fn schema() -> super::Schema<Self> {
super::Schema {
min_value: Some(u8::MIN),
max_value: Some(u8::MAX),
description: None,
example: Some(1),
name: "unsigned 8-bits integer".to_string(),
kind: super::SchemaKind::Int,
}
}
}

impl super::HasSchema for u16 {
fn schema() -> super::Schema<Self> {
super::Schema {
min_value: Some(u16::MIN),
max_value: Some(u16::MAX),
description: None,
example: Some(1),
name: "unsigned 16-bits integer".to_string(),
kind: super::SchemaKind::Int,
}
}
}

impl super::HasSchema for u32 {
fn schema() -> super::Schema<Self> {
super::Schema {
min_value: Some(u32::MIN),
max_value: Some(u32::MAX),
description: None,
example: Some(1),
name: "unsigned 32-bits integer".to_string(),
kind: super::SchemaKind::Int,
}
}
}

impl super::HasSchema for u64 {
fn schema() -> super::Schema<Self> {
super::Schema {
min_value: Some(u64::MIN),
max_value: Some(u64::MAX),
description: None,
example: Some(1),
name: "unsigned 64-bits integer".to_string(),
kind: super::SchemaKind::Int,
}
}
}

impl super::HasSchema for u128 {
fn schema() -> super::Schema<Self> {
super::Schema {
min_value: Some(u128::MIN),
max_value: Some(u128::MAX),
description: None,
example: Some(1),
name: "unsigned 128-bits integer".to_string(),
kind: super::SchemaKind::Int,
}
}
}
27 changes: 27 additions & 0 deletions core/lib/src/doc/has_schema/string_impls.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@


impl<'a> super::HasSchema for &'a str {
fn schema() -> super::Schema<Self> {
super::Schema {
min_value: None,
max_value: None,
description: None,
example: Some("string"),
name: "signed 8-bits integer".to_string(),
kind: super::SchemaKind::String,
}
}
}

impl<'a> super::HasSchema for String {
fn schema() -> super::Schema<Self> {
super::Schema {
min_value: None,
max_value: None,
description: None,
example: Some("string".to_string()),
name: "signed 8-bits integer".to_string(),
kind: super::SchemaKind::String,
}
}
}
48 changes: 48 additions & 0 deletions core/lib/src/doc/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
//! Traits and structs related to automagically generating documentation for your Rocket routes

use std::{collections::HashMap, marker::PhantomData};

use rocket_http::ContentType;

mod has_schema;

#[derive(Default)]
pub struct Docs(HashMap<ContentType, DocContent>);

#[derive(Default)]
pub struct DocContent {
title: Option<String>,
description: Option<String>,
content_type: Option<String>,
}

pub struct Resolve<T: ?Sized>(PhantomData<T>);

pub trait Documented {
fn docs() -> Docs;
}

trait Undocumented {
fn docs() -> Docs {
Docs::default()
}
}

impl<T: ?Sized> Undocumented for T { }

impl<T: Documented + ?Sized> Resolve<T> {
pub const DOCUMENTED: bool = true;

pub fn docs() -> Docs {
T::docs()
}
}

// impl<T: Documented + ?Sized> Documented for Json<T> {
// fn docs() -> Docs {
// Docs {
// content_type: Some("application/json".to_string()),
// ..Self::docs()
// }
// }
// }
1 change: 1 addition & 0 deletions core/lib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ pub mod fairing;
pub mod error;
pub mod catcher;
pub mod route;
pub mod doc;

// Reexport of HTTP everything.
pub mod http {
Expand Down
7 changes: 7 additions & 0 deletions core/lib/src/route/route.rs
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,8 @@ pub struct Route {
pub format: Option<MediaType>,
/// The discovered sentinels.
pub(crate) sentinels: Vec<Sentry>,
/// The route's docstring, which may be empty.
pub docstring: String,
}

impl Route {
Expand Down Expand Up @@ -253,6 +255,7 @@ impl Route {
sentinels: Vec::new(),
handler: Box::new(handler),
rank, uri, method,
docstring: String::new(),
}
}

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

}

#[doc(hidden)]
Expand All @@ -361,6 +367,7 @@ impl From<StaticInfo> for Route {
format: info.format,
sentinels: info.sentinels.into_iter().collect(),
uri,
docstring: info.docstring,
}
}
}

0 comments on commit 29a4175

Please sign in to comment.