diff --git a/core/codegen/src/attribute/route/mod.rs b/core/codegen/src/attribute/route/mod.rs index a24a2c4326..2dbc0aff4f 100644 --- a/core/codegen/src/attribute/route/mod.rs +++ b/core/codegen/src/attribute/route/mod.rs @@ -319,6 +319,9 @@ fn codegen_route(route: Route) -> Result { 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 @@ -353,6 +356,7 @@ fn codegen_route(route: Route) -> Result { format: #format, rank: #rank, sentinels: #sentinels, + docstring: #docstring.to_string(), } } diff --git a/core/codegen/src/attribute/route/parse.rs b/core/codegen/src/attribute/route/parse.rs index 0c83c26170..ee969c925c 100644 --- a/core/codegen/src/attribute/route/parse.rs +++ b/core/codegen/src/attribute/route/parse.rs @@ -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; @@ -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 }) } } diff --git a/core/lib/src/doc/has_schema.rs b/core/lib/src/doc/has_schema.rs new file mode 100644 index 0000000000..aff956665f --- /dev/null +++ b/core/lib/src/doc/has_schema.rs @@ -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 { + pub min_value: Option, + pub max_value: Option, + pub description: Option, + pub example: Option, + pub name: String, + pub kind: SchemaKind, + +} + +pub trait HasSchema: Sized { + fn schema() -> Schema; +} diff --git a/core/lib/src/doc/has_schema/array_impls.rs b/core/lib/src/doc/has_schema/array_impls.rs new file mode 100644 index 0000000000..8f3e2a0c9e --- /dev/null +++ b/core/lib/src/doc/has_schema/array_impls.rs @@ -0,0 +1,15 @@ + + +impl super::HasSchema for [T; N] { + fn schema() -> super::Schema { + 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, + } + } +} diff --git a/core/lib/src/doc/has_schema/box_impls.rs b/core/lib/src/doc/has_schema/box_impls.rs new file mode 100644 index 0000000000..e3af43fea4 --- /dev/null +++ b/core/lib/src/doc/has_schema/box_impls.rs @@ -0,0 +1,15 @@ + + +impl super::HasSchema for Box { + fn schema() -> super::Schema { + 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, + } + } +} diff --git a/core/lib/src/doc/has_schema/number_impls.rs b/core/lib/src/doc/has_schema/number_impls.rs new file mode 100644 index 0000000000..3956ac5e43 --- /dev/null +++ b/core/lib/src/doc/has_schema/number_impls.rs @@ -0,0 +1,129 @@ +impl super::HasSchema for i8 { + fn schema() -> super::Schema { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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, + } + } +} \ No newline at end of file diff --git a/core/lib/src/doc/has_schema/string_impls.rs b/core/lib/src/doc/has_schema/string_impls.rs new file mode 100644 index 0000000000..9c930d6501 --- /dev/null +++ b/core/lib/src/doc/has_schema/string_impls.rs @@ -0,0 +1,27 @@ + + +impl<'a> super::HasSchema for &'a str { + fn schema() -> super::Schema { + 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 { + 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, + } + } +} diff --git a/core/lib/src/doc/mod.rs b/core/lib/src/doc/mod.rs new file mode 100644 index 0000000000..1320cc8c1b --- /dev/null +++ b/core/lib/src/doc/mod.rs @@ -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); + +#[derive(Default)] +pub struct DocContent { + title: Option, + description: Option, + content_type: Option, +} + +pub struct Resolve(PhantomData); + +pub trait Documented { + fn docs() -> Docs; +} + +trait Undocumented { + fn docs() -> Docs { + Docs::default() + } +} + +impl Undocumented for T { } + +impl Resolve { + pub const DOCUMENTED: bool = true; + + pub fn docs() -> Docs { + T::docs() + } +} + +// impl Documented for Json { +// fn docs() -> Docs { +// Docs { +// content_type: Some("application/json".to_string()), +// ..Self::docs() +// } +// } +// } diff --git a/core/lib/src/lib.rs b/core/lib/src/lib.rs index cf64ecf8e3..c62f49aef9 100644 --- a/core/lib/src/lib.rs +++ b/core/lib/src/lib.rs @@ -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 { diff --git a/core/lib/src/route/route.rs b/core/lib/src/route/route.rs index 618d5d1b2a..ec7ca58b48 100644 --- a/core/lib/src/route/route.rs +++ b/core/lib/src/route/route.rs @@ -190,6 +190,8 @@ pub struct Route { pub format: Option, /// The discovered sentinels. pub(crate) sentinels: Vec, + /// The route's docstring, which may be empty. + pub docstring: String, } impl Route { @@ -253,6 +255,7 @@ impl Route { sentinels: Vec::new(), handler: Box::new(handler), rank, uri, method, + docstring: String::new(), } } @@ -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, + /// The doc comment associated with this route. + pub docstring: String, + } #[doc(hidden)] @@ -361,6 +367,7 @@ impl From for Route { format: info.format, sentinels: info.sentinels.into_iter().collect(), uri, + docstring: info.docstring, } } }