Skip to content

Commit

Permalink
Add description attribute on ToSchema (#949)
Browse files Browse the repository at this point in the history
Add support for overriding description with `description` attribute on
`ToSchema` macro. Description attributes can be used type level of
struts and enums.

Example of enum which uses description overriding.
```rust
 #[schema(
     description = include_str!("./testdata/description_override")
 )]
 enum SimpleEnum {
     Value1
 }
```

Resolves #802
  • Loading branch information
juhaku authored May 24, 2024
1 parent f7750fc commit 674d0b9
Show file tree
Hide file tree
Showing 10 changed files with 328 additions and 84 deletions.
69 changes: 41 additions & 28 deletions utoipa-gen/src/component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ use crate::{as_tokens_or_diagnostics, Diagnostics, OptionExt, ToTokensDiagnostic
use crate::{schema_type::SchemaType, Deprecated};

use self::features::{
pop_feature, Feature, FeaturesExt, IsInline, Minimum, Nullable, ToTokensExt, Validatable,
pop_feature, Description, Feature, FeaturesExt, IsInline, Minimum, Nullable, ToTokensExt,
Validatable,
};
use self::schema::format_path_ref;
use self::serde::{RenameRule, SerdeContainer, SerdeValue};
Expand Down Expand Up @@ -477,11 +478,38 @@ impl Rename for FieldRename {
pub struct ComponentSchemaProps<'c> {
pub type_tree: &'c TypeTree<'c>,
pub features: Option<Vec<Feature>>,
pub(crate) description: Option<&'c CommentAttributes>,
pub(crate) description: Option<&'c ComponentDescription<'c>>,
pub(crate) deprecated: Option<&'c Deprecated>,
pub object_name: &'c str,
}

#[cfg_attr(feature = "debug", derive(Debug))]
pub enum ComponentDescription<'c> {
CommentAttributes(&'c CommentAttributes),
Description(&'c Description),
}

impl ToTokens for ComponentDescription<'_> {
fn to_tokens(&self, tokens: &mut TokenStream) {
let description = match self {
Self::CommentAttributes(attributes) => {
if attributes.is_empty() {
TokenStream::new()
} else {
attributes.as_formatted_string().to_token_stream()
}
}
Self::Description(description) => description.to_token_stream(),
};

if !description.is_empty() {
tokens.extend(quote! {
.description(Some(#description))
});
}
}
}

#[cfg_attr(feature = "debug", derive(Debug))]
pub struct ComponentSchema {
tokens: TokenStream,
Expand All @@ -500,39 +528,38 @@ impl<'c> ComponentSchema {
let mut tokens = TokenStream::new();
let mut features = features.unwrap_or(Vec::new());
let deprecated_stream = ComponentSchema::get_deprecated(deprecated);
let description_stream = ComponentSchema::get_description(description);

match type_tree.generic_type {
Some(GenericType::Map) => ComponentSchema::map_to_tokens(
&mut tokens,
features,
type_tree,
object_name,
description_stream,
description,
deprecated_stream,
)?,
Some(GenericType::Vec) => ComponentSchema::vec_to_tokens(
&mut tokens,
features,
type_tree,
object_name,
description_stream,
description,
deprecated_stream,
)?,
Some(GenericType::LinkedList) => ComponentSchema::vec_to_tokens(
&mut tokens,
features,
type_tree,
object_name,
description_stream,
description,
deprecated_stream,
)?,
Some(GenericType::Set) => ComponentSchema::vec_to_tokens(
&mut tokens,
features,
type_tree,
object_name,
description_stream,
description,
deprecated_stream,
)?,
#[cfg(feature = "smallvec")]
Expand All @@ -541,7 +568,7 @@ impl<'c> ComponentSchema {
features,
type_tree,
object_name,
description_stream,
description,
deprecated_stream,
)?,
Some(GenericType::Option) => {
Expand Down Expand Up @@ -606,7 +633,7 @@ impl<'c> ComponentSchema {
features,
type_tree,
object_name,
description_stream,
description,
deprecated_stream,
)?,
};
Expand All @@ -619,7 +646,7 @@ impl<'c> ComponentSchema {
mut features: Vec<Feature>,
type_tree: &TypeTree,
object_name: &str,
description_stream: Option<TokenStream>,
description_stream: Option<&ComponentDescription<'_>>,
deprecated_stream: Option<TokenStream>,
) -> Result<(), Diagnostics> {
let example = features.pop_by(|feature| matches!(feature, Feature::Example(_)));
Expand Down Expand Up @@ -673,7 +700,7 @@ impl<'c> ComponentSchema {
mut features: Vec<Feature>,
type_tree: &TypeTree,
object_name: &str,
description_stream: Option<TokenStream>,
description_stream: Option<&ComponentDescription<'_>>,
deprecated_stream: Option<TokenStream>,
) -> Result<(), Diagnostics> {
let example = pop_feature!(features => Feature::Example(_));
Expand Down Expand Up @@ -780,7 +807,7 @@ impl<'c> ComponentSchema {
mut features: Vec<Feature>,
type_tree: &TypeTree,
object_name: &str,
description_stream: Option<TokenStream>,
description_stream: Option<&ComponentDescription<'_>>,
deprecated_stream: Option<TokenStream>,
) -> Result<(), Diagnostics> {
let nullable = pop_feature!(features => Feature::Nullable(_));
Expand Down Expand Up @@ -813,7 +840,7 @@ impl<'c> ComponentSchema {
})
}

tokens.extend(description_stream);
description_stream.to_tokens(tokens);
tokens.extend(deprecated_stream);
for feature in features.iter().filter(|feature| feature.is_validatable()) {
feature.validate(&schema_type, type_tree);
Expand Down Expand Up @@ -935,19 +962,6 @@ impl<'c> ComponentSchema {
Ok(())
}

fn get_description(comments: Option<&'c CommentAttributes>) -> Option<TokenStream> {
comments
.and_then(|comments| {
let comment = CommentAttributes::as_formatted_string(comments);
if comment.is_empty() {
None
} else {
Some(comment)
}
})
.map(|description| quote! { .description(Some(#description)) })
}

fn get_deprecated(deprecated: Option<&'c Deprecated>) -> Option<TokenStream> {
deprecated.map(|deprecated| quote! { .deprecated(Some(#deprecated)) })
}
Expand Down Expand Up @@ -978,7 +992,6 @@ impl FlattenedMapSchema {
let mut tokens = TokenStream::new();
let mut features = features.unwrap_or(Vec::new());
let deprecated_stream = ComponentSchema::get_deprecated(deprecated);
let description_stream = ComponentSchema::get_description(description);

let example = features.pop_by(|feature| matches!(feature, Feature::Example(_)));
let nullable = pop_feature!(features => Feature::Nullable(_));
Expand Down Expand Up @@ -1006,7 +1019,7 @@ impl FlattenedMapSchema {

tokens.extend(quote! {
#schema_tokens
#description_stream
#description
#deprecated_stream
#default_tokens
});
Expand Down
75 changes: 64 additions & 11 deletions utoipa-gen/src/component/features.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ pub enum Feature {
AllowReserved(AllowReserved),
Explode(Explode),
ParameterIn(ParameterIn),
IntoParamsNames(Names),
IntoParamsNames(IntoParamsNames),
MultipleOf(MultipleOf),
Maximum(Maximum),
Minimum(Minimum),
Expand Down Expand Up @@ -369,7 +369,7 @@ is_validatable! {
RenameAll => false,
ValueType => false,
Inline => false,
Names => false,
IntoParamsNames => false,
MultipleOf => true,
Maximum => true,
Minimum => true,
Expand Down Expand Up @@ -853,15 +853,15 @@ name!(ParameterIn = "parameter_in");
/// Specify names of unnamed fields with `names(...) attribute for `IntoParams` derive.
#[cfg_attr(feature = "debug", derive(Debug))]
#[derive(Clone)]
pub struct Names(Vec<String>);
pub struct IntoParamsNames(Vec<String>);

impl Names {
impl IntoParamsNames {
pub fn into_values(self) -> Vec<String> {
self.0
}
}

impl Parse for Names {
impl Parse for IntoParamsNames {
fn parse(input: syn::parse::ParseStream, _: Ident) -> syn::Result<Self> {
Ok(Self(
parse_utils::parse_punctuated_within_parenthesis::<LitStr>(input)?
Expand All @@ -872,13 +872,13 @@ impl Parse for Names {
}
}

impl From<Names> for Feature {
fn from(value: Names) -> Self {
impl From<IntoParamsNames> for Feature {
fn from(value: IntoParamsNames) -> Self {
Feature::IntoParamsNames(value)
}
}

name!(Names = "names");
name!(IntoParamsNames = "names");

#[cfg_attr(feature = "debug", derive(Debug))]
#[derive(Clone)]
Expand Down Expand Up @@ -1340,14 +1340,14 @@ name!(SchemaWith = "schema_with");

#[cfg_attr(feature = "debug", derive(Debug))]
#[derive(Clone)]
pub struct Description(String);
pub struct Description(parse_utils::Value);

impl Parse for Description {
fn parse(input: ParseStream, _: Ident) -> syn::Result<Self>
where
Self: std::marker::Sized,
{
parse_utils::parse_next_literal_str(input).map(Self)
parse_utils::parse_next_literal_str_or_expr(input).map(Self)
}
}

Expand All @@ -1359,7 +1359,7 @@ impl ToTokens for Description {

impl From<String> for Description {
fn from(value: String) -> Self {
Self(value)
Self(value.into())
}
}

Expand Down Expand Up @@ -1826,6 +1826,59 @@ pub trait IntoInner<T> {
fn into_inner(self) -> T;
}

macro_rules! impl_feature_into_inner {
( $( $feat:ident , )* ) => {
$(
impl IntoInner<Option<$feat>> for Option<Feature> {
fn into_inner(self) -> Option<$feat> {
self.and_then(|feature| match feature {
Feature::$feat(value) => Some(value),
_ => None,
})
}
}
)*
};
}

impl_feature_into_inner! {
Example,
Default,
Inline,
XmlAttr,
Format,
ValueType,
WriteOnly,
ReadOnly,
Title,
Nullable,
Rename,
RenameAll,
Style,
AllowReserved,
Explode,
ParameterIn,
IntoParamsNames,
MultipleOf,
Maximum,
Minimum,
ExclusiveMaximum,
ExclusiveMinimum,
MaxLength,
MinLength,
Pattern,
MaxItems,
MinItems,
MaxProperties,
MinProperties,
SchemaWith,
Description,
Deprecated,
As,
AdditionalProperties,
Required,
}

macro_rules! impl_into_inner {
($ident:ident) => {
impl crate::component::features::IntoInner<Vec<Feature>> for $ident {
Expand Down
17 changes: 7 additions & 10 deletions utoipa-gen/src/component/into_params.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ use crate::{
self,
features::{
self, AdditionalProperties, AllowReserved, Example, ExclusiveMaximum, ExclusiveMinimum,
Explode, Format, Inline, MaxItems, MaxLength, Maximum, MinItems, MinLength, Minimum,
MultipleOf, Names, Nullable, Pattern, ReadOnly, Rename, RenameAll, SchemaWith, Style,
WriteOnly, XmlAttr,
Explode, Format, Inline, IntoParamsNames, MaxItems, MaxLength, Maximum, MinItems,
MinLength, Minimum, MultipleOf, Nullable, Pattern, ReadOnly, Rename, RenameAll,
SchemaWith, Style, WriteOnly, XmlAttr,
},
FieldRename,
},
Expand All @@ -41,7 +41,7 @@ impl Parse for IntoParamsFeatures {
Ok(Self(parse_features!(
input as Style,
features::ParameterIn,
Names,
IntoParamsNames,
RenameAll
)))
}
Expand Down Expand Up @@ -91,12 +91,9 @@ impl ToTokensDiagnostics for IntoParams {
}

let names = into_params_features.as_mut().and_then(|features| {
features
.pop_by(|feature| matches!(feature, Feature::IntoParamsNames(_)))
.and_then(|feature| match feature {
Feature::IntoParamsNames(names) => Some(names.into_values()),
_ => None,
})
let into_params_names = pop_feature!(features => Feature::IntoParamsNames(_));
IntoInner::<Option<IntoParamsNames>>::into_inner(into_params_names)
.map(|names| names.into_values())
});

let style = pop_feature!(into_params_features => Feature::Style(_));
Expand Down
Loading

0 comments on commit 674d0b9

Please sign in to comment.