Skip to content

Commit

Permalink
Implement juhaku#151 style container attribute for IntoParams
Browse files Browse the repository at this point in the history
  • Loading branch information
kellpossible committed Jun 15, 2022
1 parent 6a78f10 commit 314e0cc
Show file tree
Hide file tree
Showing 5 changed files with 119 additions and 55 deletions.
11 changes: 3 additions & 8 deletions tests/path_derive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -237,23 +237,18 @@ fn derive_path_with_security_requirements() {
#[test]
fn derive_path_params_intoparams() {
#[derive(serde::Deserialize, IntoParams)]
#[param(style = Form)]
struct MyParams {
/// Foo database id.
#[param(style = Form, example = 1)]
#[param(example = 1)]
#[allow(unused)]
id: u64,
/// Datetime since foo is updated.
#[param(style = Form, example = "2020-04-12T10:23:00Z")]
#[param(example = "2020-04-12T10:23:00Z")]
#[allow(unused)]
since: Option<String>,
}

impl utoipa::ParameterIn for MyParams {
fn parameter_in() -> Option<utoipa::openapi::path::ParameterIn> {
Some(utoipa::openapi::path::ParameterIn::Query)
}
}

#[utoipa::path(
get,
path = "/list",
Expand Down
4 changes: 2 additions & 2 deletions utoipa-gen/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -892,18 +892,18 @@ pub fn openapi(input: TokenStream) -> TokenStream {
/// [^json]: **json** feature need to be enabled for `json!(...)` type to work.
pub fn into_params(input: TokenStream) -> TokenStream {
let DeriveInput {
attrs,
ident,
generics,
data,
attrs,
..
} = syn::parse_macro_input!(input);

let into_params = IntoParams {
attrs,
generics,
data,
ident,
attrs,
};

into_params.to_token_stream().into()
Expand Down
2 changes: 2 additions & 0 deletions utoipa-gen/src/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ pub struct PathAttr<'p> {
}

/// The [`PathAttr::params`] field definition.
#[cfg_attr(feature = "debug", derive(Debug))]
enum Params<'p> {
/// A list of tuples of attributes that defines a parameter.
List(Vec<Parameter<'p>>),
Expand Down Expand Up @@ -541,6 +542,7 @@ impl ToTokens for Operation<'_> {
}
});
}
// TODO: adjust this for the new into_params() signature.
Some(Params::Struct(parameters)) => tokens.extend(quote! {
.parameters(Some(<#parameters as utoipa::IntoParams>::into_params()))
}),
Expand Down
17 changes: 15 additions & 2 deletions utoipa-gen/src/path/parameter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ use syn::{

#[cfg(any(feature = "actix_extras", feature = "rocket_extras"))]
use crate::ext::{Argument, ArgumentIn};
use crate::{parse_utils, AnyValue, Deprecated, Required, Type};
use crate::{
parse_utils, schema::into_params::{IntoParamsAttr, FieldParamContainerAttributes}, AnyValue, Deprecated, Required,
Type,
};

use super::property::Property;

Expand Down Expand Up @@ -260,8 +263,17 @@ pub struct ParameterExt {
pub(crate) example: Option<AnyValue>,
}

impl From<&'_ FieldParamContainerAttributes<'_>> for ParameterExt {
fn from(attributes: &FieldParamContainerAttributes) -> Self {
Self {
style: attributes.style,
..ParameterExt::default()
}
}
}

impl ParameterExt {
fn merge(&mut self, from: ParameterExt) {
pub fn merge(&mut self, from: ParameterExt) {
if from.style.is_some() {
self.style = from.style
}
Expand Down Expand Up @@ -334,6 +346,7 @@ impl Parse for ParameterExt {
}

/// See definitions from `utoipa` crate path.rs
#[derive(Copy, Clone)]
#[cfg_attr(feature = "debug", derive(Debug))]
pub enum ParameterStyle {
Matrix,
Expand Down
140 changes: 97 additions & 43 deletions utoipa-gen/src/schema/into_params.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,47 +9,81 @@ use crate::{
component_type::{ComponentFormat, ComponentType},
doc_comment::CommentAttributes,
parse_utils,
path::parameter::ParameterExt,
path::parameter::{ParameterExt, ParameterStyle},
Array, Required,
};

use super::{ComponentPart, GenericType, ValueType};

/// Container attribute `#[into_params(...)]`.
#[derive(Default)]
#[cfg_attr(feature = "debug", derive(Debug))]
pub struct IntoParamsAttr {
/// See [`ParameterStyle`].
style: Option<ParameterStyle>,
/// Specify names of unnamed fields with `names(...) attribute.`
names: Vec<String>,
}

impl IntoParamsAttr {
fn merge(&mut self, other: Self) -> &Self {
if other.style.is_some() {
self.style = other.style;
}

if !other.names.is_empty() {
self.names = other.names;
}

self
}
}

impl Parse for IntoParamsAttr {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
const EXPECTED_ATTRIBUTE: &str = "unexpected token, expected: names";

input
.parse::<Ident>()
.map_err(|error| Error::new(error.span(), format!("{EXPECTED_ATTRIBUTE}, {error}")))
.and_then(|ident| {
if ident != "names" {
Err(Error::new(ident.span(), EXPECTED_ATTRIBUTE))
} else {
Ok(ident)
}
const EXPECTED_ATTRIBUTE: &str = "unexpected token, expected any of: names, style";

let mut attributes = Self::default();

while !input.is_empty() {
let ident: Ident = input.parse::<Ident>().map_err(|error| {
Error::new(error.span(), format!("{EXPECTED_ATTRIBUTE}, {error}"))
})?;

Ok(IntoParamsAttr {
names: parse_utils::parse_punctuated_within_parenthesis::<LitStr>(input)?
.into_iter()
.map(|name| name.value())
.collect(),
})
attributes.merge(match ident.to_string().as_str() {
"names" => Ok(IntoParamsAttr {
names: parse_utils::parse_punctuated_within_parenthesis::<LitStr>(input)?
.into_iter()
.map(|name| name.value())
.collect(),
..IntoParamsAttr::default()
}),
"style" => {
let style: ParameterStyle =
parse_utils::parse_next(input, || input.parse::<ParameterStyle>())?;
Ok(IntoParamsAttr {
style: Some(style),
..IntoParamsAttr::default()
})
}
_ => Err(Error::new(ident.span(), EXPECTED_ATTRIBUTE)),
}?);
}

Ok(attributes)
}
}

#[cfg_attr(feature = "debug", derive(Debug))]
pub struct IntoParams {
/// Attributes tagged on the whole struct or enum.
pub attrs: Vec<Attribute>,
/// Generics required to complete the definition.
pub generics: Generics,
/// Data within the struct or enum.
pub data: Data,
/// Name of the struct or enum.
pub ident: Ident,
pub attrs: Vec<Attribute>,
}

impl ToTokens for IntoParams {
Expand All @@ -71,12 +105,16 @@ impl ToTokens for IntoParams {
)
.enumerate()
.map(|(index, field)| {
Param(
Param {
field,
into_params_attrs
.as_ref()
.and_then(|param| param.names.get(index)),
)
container_attributes: FieldParamContainerAttributes {
style: into_params_attrs.as_ref().and_then(|attrs| attrs.style),
// TODO: turn this into an error.
name: into_params_attrs
.as_ref()
.map(|attrs| attrs.names.get(index).unwrap()),
},
}
})
.collect::<Array<Param>>();

Expand Down Expand Up @@ -155,17 +193,31 @@ impl IntoParams {
}
}

struct Param<'a>(&'a Field, Option<&'a String>);
#[cfg_attr(feature = "debug", derive(Debug))]
pub struct FieldParamContainerAttributes<'a> {
/// See [`IntoParamsAttr::style`].
pub style: Option<ParameterStyle>,
/// See [`IntoParamsAttr::names`]. The name that applies to this field.
pub name: Option<&'a String>,
}

#[cfg_attr(feature = "debug", derive(Debug))]
struct Param<'a> {
/// Field in the container used to create a single parameter.
field: &'a Field,
/// Attributes on the container which are relevant for this macro.
container_attributes: FieldParamContainerAttributes<'a>,
}

impl ToTokens for Param<'_> {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
let unnamed_field_name = self.1;
let field = self.0;
let field = self.field;
let ident = &field.ident;
let name = ident
.as_ref()
.map(|ident| ident.to_string())
.or_else(|| unnamed_field_name.map(ToString::to_string))
// TODO: add proper error handling
.or_else(|| self.container_attributes.name.cloned())
.unwrap_or_default();
let component_part = ComponentPart::from_type(&field.ty);
let required: Required =
Expand All @@ -187,25 +239,27 @@ impl ToTokens for Param<'_> {
})
}

let parameter_ext = field
let mut parameter_ext = ParameterExt::from(&self.container_attributes);

// Apply the field attributes if they exist.
field
.attrs
.iter()
.find(|attribute| attribute.path.is_ident("param"))
.map(|attribute| attribute.parse_args::<ParameterExt>().unwrap_or_abort());
.map(|attribute| attribute.parse_args::<ParameterExt>().unwrap_or_abort())
.map(|p| parameter_ext.merge(p));

if let Some(ext) = parameter_ext {
if let Some(ref style) = ext.style {
tokens.extend(quote! { .style(Some(#style)) });
}
if let Some(ref explode) = ext.explode {
tokens.extend(quote! { .explode(Some(#explode)) });
}
if let Some(ref allow_reserved) = ext.allow_reserved {
tokens.extend(quote! { .allow_reserved(Some(#allow_reserved)) });
}
if let Some(ref example) = ext.example {
tokens.extend(quote! { .example(Some(#example)) });
}
if let Some(ref style) = parameter_ext.style {
tokens.extend(quote! { .style(Some(#style)) });
}
if let Some(ref explode) = parameter_ext.explode {
tokens.extend(quote! { .explode(Some(#explode)) });
}
if let Some(ref allow_reserved) = parameter_ext.allow_reserved {
tokens.extend(quote! { .allow_reserved(Some(#allow_reserved)) });
}
if let Some(ref example) = parameter_ext.example {
tokens.extend(quote! { .example(Some(#example)) });
}

let param_type = ParamType(&component_part);
Expand Down

0 comments on commit 314e0cc

Please sign in to comment.