Skip to content

Commit

Permalink
Add automatic query parameter recognition
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 committed Jul 5, 2023
1 parent 2979ce9 commit 1810c86
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 1810c86

Please sign in to comment.