Skip to content

Commit

Permalink
Add automatic query parameter recognition (#666)
Browse files Browse the repository at this point in the history
Add automatic query parameter recognition. And improve existing code
related to path and query parameters recognition and elsewhere.
  • Loading branch information
juhaku authored Jul 5, 2023
1 parent 2979ce9 commit d008ff4
Show file tree
Hide file tree
Showing 12 changed files with 224 additions and 162 deletions.
5 changes: 2 additions & 3 deletions utoipa-gen/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ syn = { version = "2.0", features = ["full", "extra-traits"] }
quote = "1.0"
proc-macro-error = "1.0"
regex = { version = "1.7", optional = true }
lazy_static = { version = "1.4", optional = true }
uuid = { version = "1", optional = true }

[dev-dependencies]
Expand All @@ -39,11 +38,11 @@ serde_with = "3.0"
[features]
# See README.md for list and explanations of features
debug = ["syn/extra-traits"]
actix_extras = ["regex", "lazy_static", "syn/extra-traits"]
actix_extras = ["regex", "syn/extra-traits"]
chrono = []
yaml = []
decimal = []
rocket_extras = ["regex", "lazy_static", "syn/extra-traits"]
rocket_extras = ["regex", "syn/extra-traits"]
non_strict_integers = []
uuid = ["dep:uuid", "utoipa/uuid"]
axum_extras = ["syn/extra-traits"]
Expand Down
2 changes: 1 addition & 1 deletion utoipa-gen/src/component/features.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1444,7 +1444,7 @@ pub struct Required(pub bool);

impl Required {
pub fn is_true(&self) -> bool {
self.0 == true
self.0
}
}

Expand Down
31 changes: 15 additions & 16 deletions utoipa-gen/src/ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,15 @@ pub struct IntoParamsType<'a> {
pub type_path: Option<Cow<'a, syn::Path>>,
}

impl<'i> From<(Option<Cow<'i, syn::Path>>, TokenStream)> for IntoParamsType<'i> {
fn from((type_path, parameter_in_provider): (Option<Cow<'i, syn::Path>>, TokenStream)) -> Self {
IntoParamsType {
parameter_in_provider,
type_path,
}
}
}

#[cfg(any(
feature = "actix_extras",
feature = "rocket_extras",
Expand Down Expand Up @@ -276,9 +285,7 @@ impl PathOperationResolver for PathOperations {}
))]
pub mod fn_arg {

use std::borrow::Cow;

use proc_macro2::{Ident, TokenStream};
use proc_macro2::Ident;
use proc_macro_error::abort;
#[cfg(any(feature = "actix_extras", feature = "axum_extras"))]
use quote::quote;
Expand All @@ -289,8 +296,6 @@ pub mod fn_arg {
#[cfg(any(feature = "actix_extras", feature = "axum_extras"))]
use crate::component::ValueType;

use super::IntoParamsType;

/// Http operation handler functions fn argument.
#[cfg_attr(feature = "debug", derive(Debug))]
pub struct FnArg<'a> {
Expand Down Expand Up @@ -410,11 +415,14 @@ pub mod fn_arg {
#[cfg(any(feature = "actix_extras", feature = "axum_extras"))]
pub(super) fn with_parameter_in(
arg: FnArg<'_>,
) -> Option<(Option<std::borrow::Cow<'_, syn::Path>>, TokenStream)> {
) -> Option<(
Option<std::borrow::Cow<'_, syn::Path>>,
proc_macro2::TokenStream,
)> {
let parameter_in_provider = if arg.ty.is("Path") {
quote! { || Some (utoipa::openapi::path::ParameterIn::Path) }
} else if arg.ty.is("Query") {
quote! { || Some( utoipa::openapi::path::ParameterIn::Query) }
quote! { || Some(utoipa::openapi::path::ParameterIn::Query) }
} else {
quote! { || None }
};
Expand All @@ -431,15 +439,6 @@ pub mod fn_arg {
Some((type_path, parameter_in_provider))
}

pub(super) fn into_into_params_type(
(type_path, parameter_in_provider): (Option<Cow<'_, syn::Path>>, TokenStream),
) -> IntoParamsType<'_> {
IntoParamsType {
parameter_in_provider,
type_path,
}
}

// if type is either Path or Query with direct children as Object types without generics
#[cfg(any(feature = "actix_extras", feature = "axum_extras"))]
pub(super) fn is_into_params(fn_arg: &FnArg) -> bool {
Expand Down
13 changes: 5 additions & 8 deletions utoipa-gen/src/ext/actix.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use std::borrow::Cow;

use lazy_static::lazy_static;
use proc_macro2::Ident;
use proc_macro_error::abort;
use regex::{Captures, Regex};
Expand Down Expand Up @@ -45,7 +44,7 @@ impl ArgumentResolver for PathOperations {
into_params_args
.into_iter()
.flat_map(fn_arg::with_parameter_in)
.map(fn_arg::into_into_params_type)
.map(Into::into)
.collect(),
),
body.into_iter().next().map(Into::into),
Expand All @@ -58,7 +57,7 @@ impl ArgumentResolver for PathOperations {
into_params_args
.into_iter()
.flat_map(fn_arg::with_parameter_in)
.map(fn_arg::into_into_params_type)
.map(Into::into)
.collect(),
),
body.into_iter().next().map(Into::into),
Expand Down Expand Up @@ -158,13 +157,11 @@ impl Parse for Path {
impl PathResolver for PathOperations {
fn resolve_path(path: &Option<String>) -> Option<MacroPath> {
path.as_ref().map(|path| {
lazy_static! {
static ref RE: Regex = Regex::new(r"\{[a-zA-Z0-9_][^{}]*}").unwrap();
}
let regex = Regex::new(r"\{[a-zA-Z0-9_][^{}]*}").unwrap();

let mut args = Vec::<MacroArg>::with_capacity(RE.find_iter(path).count());
let mut args = Vec::<MacroArg>::with_capacity(regex.find_iter(path).count());
MacroPath {
path: RE
path: regex
.replace_all(path, |captures: &Captures| {
let mut capture = &captures[0];
let original_name = String::from(capture);
Expand Down
2 changes: 1 addition & 1 deletion utoipa-gen/src/ext/axum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ impl ArgumentResolver for PathOperations {
into_params_args
.into_iter()
.flat_map(fn_arg::with_parameter_in)
.map(fn_arg::into_into_params_type)
.map(Into::into)
.collect(),
),
None,
Expand Down
13 changes: 5 additions & 8 deletions utoipa-gen/src/ext/rocket.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use std::{borrow::Cow, str::FromStr};

use lazy_static::lazy_static;
use proc_macro2::{Ident, TokenStream};
use proc_macro_error::abort;
use quote::quote;
Expand Down Expand Up @@ -54,7 +53,7 @@ impl ArgumentResolver for PathOperations {
into_params_args
.into_iter()
.flat_map(with_parameter_in(&named_args))
.map(fn_arg::into_into_params_type)
.map(Into::into)
.collect(),
),
None,
Expand Down Expand Up @@ -220,16 +219,14 @@ fn is_valid_route_type(ident: Option<&Ident>) -> bool {
impl PathResolver for PathOperations {
fn resolve_path(path: &Option<String>) -> Option<MacroPath> {
path.as_ref().map(|whole_path| {
lazy_static! {
static ref RE: Regex = Regex::new(r"<[a-zA-Z0-9_][^<>]*>").unwrap();
}
let regex = Regex::new(r"<[a-zA-Z0-9_][^<>]*>").unwrap();

whole_path
.split_once('?')
.or(Some((whole_path, "")))
.map(|(path, query)| {
let mut names =
Vec::<MacroArg>::with_capacity(RE.find_iter(whole_path).count());
Vec::<MacroArg>::with_capacity(regex.find_iter(whole_path).count());
let mut underscore_count = 0;

let mut format_arg =
Expand Down Expand Up @@ -259,12 +256,12 @@ impl PathResolver for PathOperations {
arg
};

let path = RE.replace_all(path, |captures: &Captures| {
let path = regex.replace_all(path, |captures: &Captures| {
format_arg(captures, MacroArg::Path)
});

if !query.is_empty() {
RE.replace_all(query, |captures: &Captures| {
regex.replace_all(query, |captures: &Captures| {
format_arg(captures, MacroArg::Query)
});
}
Expand Down
9 changes: 7 additions & 2 deletions utoipa-gen/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1333,12 +1333,17 @@ pub fn path(attr: TokenStream, item: TokenStream) -> TokenStream {
))]
{
use ext::ArgumentResolver;
use path::parameter::Parameter;
let args = resolved_path.as_mut().map(|path| mem::take(&mut path.args));
let (arguments, into_params_types, body) =
PathOperations::resolve_arguments(&ast_fn.sig.inputs, args);

path_attribute.update_parameters(arguments);
path_attribute.update_parameters_parameter_in(into_params_types);
let parameters = arguments
.into_iter()
.flatten()
.map(Parameter::from)
.chain(into_params_types.into_iter().flatten().map(Parameter::from));
path_attribute.update_parameters_ext(parameters);

path_attribute.update_request_body(body);
}
Expand Down
104 changes: 16 additions & 88 deletions utoipa-gen/src/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,6 @@ use crate::{schema_type::SchemaType, security_requirement::SecurityRequirementAt
use self::response::Response;
use self::{parameter::Parameter, request_body::RequestBodyAttr, response::Responses};

#[cfg(any(
feature = "actix_extras",
feature = "rocket_extras",
feature = "axum_extras"
))]
use self::parameter::ValueParameter;

#[cfg(any(
feature = "actix_extras",
feature = "rocket_extras",
feature = "axum_extras"
))]
use crate::ext::{IntoParamsType, ValueArgument};

pub mod example;
pub mod parameter;
mod request_body;
Expand All @@ -56,55 +42,6 @@ pub struct PathAttr<'p> {
}

impl<'p> PathAttr<'p> {
#[cfg(any(
feature = "actix_extras",
feature = "rocket_extras",
feature = "axum_extras"
))]
pub fn update_parameters<'a>(&mut self, arguments: Option<Vec<ValueArgument<'a>>>)
where
'a: 'p,
{
if let Some(arguments) = arguments {
if !self.params.is_empty() {
let mut value_parameters: Vec<&mut ValueParameter> = self
.params
.iter_mut()
.filter_map(|parameter| match parameter {
Parameter::Value(value) => Some(value),
Parameter::Struct(_) => None,
})
.collect::<Vec<_>>();
let (existing_arguments, new_arguments): (Vec<ValueArgument>, Vec<ValueArgument>) =
arguments.into_iter().partition(|argument| {
value_parameters.iter().any(|parameter| {
Some(parameter.name.as_ref()) == argument.name.as_deref()
})
});

for argument in existing_arguments {
if let Some(parameter) = value_parameters
.iter_mut()
.find(|parameter| Some(parameter.name.as_ref()) == argument.name.as_deref())
{
parameter.update_parameter_type(argument.type_tree);
}
}
self.params
.extend(new_arguments.into_iter().map(Parameter::from));
} else {
// no parameters at all, add arguments to the parameters
let mut parameters = Vec::with_capacity(arguments.len());

arguments
.into_iter()
.map(Parameter::from)
.for_each(|parameter| parameters.push(parameter));
self.params = parameters;
}
}
}

#[cfg(feature = "auto_into_responses")]
pub fn responses_from_into_responses(&mut self, ty: &'p syn::TypePath) {
self.responses
Expand All @@ -124,40 +61,31 @@ impl<'p> PathAttr<'p> {
.or(mem::take(&mut self.request_body));
}

/// Update path with external parameters from extensions.
#[cfg(any(
feature = "actix_extras",
feature = "rocket_extras",
feature = "axum_extras"
))]
pub fn update_parameters_parameter_in(
pub fn update_parameters_ext<I: IntoIterator<Item = Parameter<'p>>>(
&mut self,
into_params_types: Option<Vec<IntoParamsType>>,
ext_parameters: I,
) {
fn path_segments(path: &syn::Path) -> Vec<&'_ Ident> {
path.segments.iter().map(|segment| &segment.ident).collect()
}
let ext_params = ext_parameters.into_iter();

if !self.params.is_empty() {
if let Some(mut into_params_types) = into_params_types {
self.params
.iter_mut()
.filter_map(|parameter| match parameter {
Parameter::Value(_) => None,
Parameter::Struct(parameter) => Some(parameter),
})
.for_each(|parameter| {
if let Some(into_params_argument) =
into_params_types
.iter_mut()
.find(|argument| matches!(&argument.type_path, Some(path) if path_segments(path.as_ref()) == path_segments(&parameter.path.path))
)
{
parameter.update_parameter_in(
&mut into_params_argument.parameter_in_provider,
);
}
})
if self.params.is_empty() {
self.params = ext_params.collect();
} else {
let (existing_params, new_params): (Vec<Parameter>, Vec<Parameter>) =
ext_params.partition(|param| self.params.iter().any(|p| p == param));

for existing in existing_params {
if let Some(param) = self.params.iter_mut().find(|p| **p == existing) {
param.merge(existing);
}
}

self.params.extend(new_params.into_iter());
}
}
}
Expand Down
Loading

0 comments on commit d008ff4

Please sign in to comment.