Skip to content

Commit

Permalink
Fix trait support for TypeTree (#294)
Browse files Browse the repository at this point in the history
Previously using `dyn` in handler arguments caused the TypeTree
resolvation to fail because there wa no support for trait. This will fix
that by adding support for trait types for TypeTree.
  • Loading branch information
juhaku authored Sep 24, 2022
1 parent 61a99f7 commit 416e9eb
Show file tree
Hide file tree
Showing 13 changed files with 124 additions and 94 deletions.
70 changes: 42 additions & 28 deletions utoipa-gen/src/component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::borrow::Cow;

use proc_macro2::Ident;
use proc_macro_error::{abort, abort_call_site};
use syn::{Attribute, GenericArgument, PathArguments, PathSegment, Type, TypePath};
use syn::{Attribute, GenericArgument, Path, PathArguments, PathSegment, Type, TypePath};

use crate::{schema_type::SchemaType, Deprecated};

Expand All @@ -22,11 +22,17 @@ fn get_deprecated(attributes: &[Attribute]) -> Option<Deprecated> {
})
}

#[cfg_attr(feature = "debug", derive(Debug, PartialEq))]
enum TypeTreeValue<'t> {
TypePath(&'t TypePath),
Path(&'t Path),
}

/// [`TypeTree`] of items which represents a single parsed `type` of a
/// `Schema`, `Parameter` or `FnArg`
#[cfg_attr(feature = "debug", derive(Debug, PartialEq))]
pub struct TypeTree<'t> {
pub path: Option<Cow<'t, TypePath>>,
pub path: Option<Cow<'t, Path>>,
pub value_type: ValueType,
pub generic_type: Option<GenericType>,
pub children: Option<Vec<TypeTree<'t>>>,
Expand All @@ -37,20 +43,34 @@ impl<'t> TypeTree<'t> {
Self::from_type_paths(Self::get_type_paths(ty))
}

fn get_type_paths(ty: &'t Type) -> Vec<&'t TypePath> {
fn get_type_paths(ty: &'t Type) -> Vec<TypeTreeValue> {
match ty {
Type::Path(path) => vec![path],
Type::Path(path) => {
vec![TypeTreeValue::TypePath(path)]
},
Type::Reference(reference) => Self::get_type_paths(reference.elem.as_ref()),
Type::Tuple(tuple) => tuple.elems.iter().flat_map(Self::get_type_paths).collect(),
Type::Group(group) => Self::get_type_paths(group.elem.as_ref()),
Type::Array(array) => Self::get_type_paths(&array.elem),
Type::TraitObject(trait_object) => {
trait_object
.bounds
.iter()
.find_map(|bound| {
match bound {
syn::TypeParamBound::Trait(trait_bound) => Some(&trait_bound.path),
syn::TypeParamBound::Lifetime(_) => None
}
})
.map(|path| vec![TypeTreeValue::Path(path)]).unwrap_or_else(Vec::new)
}
_ => abort_call_site!(
"unexpected type in component part get type path, expected one of: Path, Reference, Group"
),
}
}

fn from_type_paths(paths: Vec<&'t TypePath>) -> TypeTree<'t> {
fn from_type_paths(paths: Vec<TypeTreeValue<'t>>) -> TypeTree<'t> {
if paths.len() > 1 {
TypeTree {
path: None,
Expand All @@ -66,25 +86,28 @@ impl<'t> TypeTree<'t> {
}
}

fn convert_types(paths: Vec<&'t TypePath>) -> impl Iterator<Item = TypeTree<'t>> {
paths.into_iter().map(|path| {
fn convert_types(paths: Vec<TypeTreeValue<'t>>) -> impl Iterator<Item = TypeTree<'t>> {
paths.into_iter().map(|value| {
let path = match value {
TypeTreeValue::TypePath(type_path) => &type_path.path,
TypeTreeValue::Path(path) => path,
};
// there will always be one segment at least
let last_segment = path
.path
.segments
.last()
.expect("at least one segment within path in TypeTree::convert_types");

if last_segment.arguments.is_empty() {
Self::convert(Cow::Borrowed(path), last_segment)
Self::convert(path, last_segment)
} else {
Self::resolve_schema_type(Cow::Borrowed(path), last_segment)
Self::resolve_schema_type(path, last_segment)
}
})
}

// Only when type is a generic type we get to this function.
fn resolve_schema_type(path: Cow<'t, TypePath>, last_segment: &'t PathSegment) -> TypeTree<'t> {
fn resolve_schema_type(path: &'t Path, last_segment: &'t PathSegment) -> TypeTree<'t> {
if last_segment.arguments.is_empty() {
abort!(
last_segment.ident,
Expand Down Expand Up @@ -132,12 +155,12 @@ impl<'t> TypeTree<'t> {
generic_schema_type
}

fn convert(path: Cow<'t, TypePath>, last_segment: &'t PathSegment) -> TypeTree<'t> {
fn convert(path: &'t Path, last_segment: &'t PathSegment) -> TypeTree<'t> {
let generic_type = Self::get_generic_type(last_segment);
let is_primitive = SchemaType(&*path).is_primitive();
let is_primitive = SchemaType(path).is_primitive();

Self {
path: Some(path),
path: Some(Cow::Borrowed(path)),
value_type: if is_primitive {
ValueType::Primitive
} else {
Expand Down Expand Up @@ -170,8 +193,7 @@ impl<'t> TypeTree<'t> {
.path
.as_ref()
.map(|path| {
path.path
.segments
path.segments
.last()
.expect("expected at least one segment in TreeTypeValue path")
.ident
Expand All @@ -190,12 +212,7 @@ impl<'t> TypeTree<'t> {
let is = self
.path
.as_mut()
.map(|path| {
path.path
.segments
.iter()
.any(|segment| &segment.ident == ident)
})
.map(|path| path.segments.iter().any(|segment| &segment.ident == ident))
.unwrap_or(false);

if is {
Expand All @@ -209,11 +226,8 @@ impl<'t> TypeTree<'t> {
}
}

fn update_path(&mut self, ident: &'t Ident) {
self.path = Some(Cow::Owned(TypePath {
qself: None,
path: syn::Path::from(ident.clone()),
}))
fn update_path(&mut self, ident: &'_ Ident) {
self.path = Some(Cow::Owned(Path::from(ident.clone())))
}

/// `Object` virtual type is used when generic object is required in OpenAPI spec. Typically used
Expand All @@ -233,7 +247,7 @@ impl PartialEq for TypeTree<'_> {
== other_path.into_token_stream().to_string()
}
(Some(Cow::Owned(self_path)), Some(Cow::Owned(other_path))) => {
self_path.path.to_token_stream().to_string()
self_path.to_token_stream().to_string()
== other_path.into_token_stream().to_string()
}
(None, None) => true,
Expand Down
3 changes: 1 addition & 2 deletions utoipa-gen/src/component/into_params.rs
Original file line number Diff line number Diff line change
Expand Up @@ -471,7 +471,7 @@ impl ToTokens for ParamType<'_> {
}
}
ValueType::Object => {
let component_path = component
let component_path = &**component
.path
.as_ref()
.expect("component should have a path");
Expand All @@ -485,7 +485,6 @@ impl ToTokens for ParamType<'_> {
});
} else {
let name: String = component_path
.path
.segments
.last()
.expect("Expected there to be at least one element in the path")
Expand Down
17 changes: 9 additions & 8 deletions utoipa-gen/src/component/schema.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
use std::fmt::Debug;

use proc_macro2::{Ident, TokenStream};
use proc_macro_error::{abort, ResultExt};
use quote::{quote, quote_spanned, ToTokens};
use syn::{
parse::Parse, parse_quote, punctuated::Punctuated, spanned::Spanned, token::Comma, Attribute,
Data, Field, Fields, FieldsNamed, FieldsUnnamed, Generics, PathArguments, Token, TypePath,
Variant, Visibility,
Data, Field, Fields, FieldsNamed, FieldsUnnamed, Generics, Path, PathArguments, Token,
TypePath, Variant, Visibility,
};

use crate::{
Expand Down Expand Up @@ -824,7 +822,10 @@ where
} = self;
let len = values.len();
let (schema_type, enum_type) = if let Some(ty) = *enum_type {
(SchemaType(ty).to_token_stream(), ty.into_token_stream())
(
SchemaType(&ty.path).to_token_stream(),
ty.into_token_stream(),
)
} else {
(
SchemaType(&parse_quote!(str)).to_token_stream(),
Expand Down Expand Up @@ -1133,11 +1134,11 @@ where

/// Reformat a path reference string that was generated using [`quote`] to be used as a nice compact schema reference,
/// by removing spaces between colon punctuation and `::` and the path segments.
pub(crate) fn format_path_ref(path: &TypePath) -> String {
let mut path: TypePath = path.clone();
pub(crate) fn format_path_ref(path: &Path) -> String {
let mut path = path.clone();

// Generics and path arguments are unsupported
if let Some(last_segment) = path.path.segments.last_mut() {
if let Some(last_segment) = path.segments.last_mut() {
last_segment.arguments = PathArguments::None;
}

Expand Down
13 changes: 7 additions & 6 deletions utoipa-gen/src/ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::borrow::Cow;
use std::cmp::Ordering;

use proc_macro2::TokenStream;
use syn::{punctuated::Punctuated, token::Comma, ItemFn, TypePath};
use syn::{punctuated::Punctuated, token::Comma, ItemFn, Path};

use crate::path::PathOperation;

Expand All @@ -22,7 +22,7 @@ pub mod rocket;
pub struct ValueArgument<'a> {
pub name: Option<Cow<'a, str>>,
pub argument_in: ArgumentIn,
pub type_path: Option<Cow<'a, TypePath>>,
pub type_path: Option<Cow<'a, Path>>,
pub is_array: bool,
pub is_option: bool,
}
Expand All @@ -32,7 +32,7 @@ pub struct ValueArgument<'a> {
#[cfg_attr(feature = "debug", derive(Debug))]
pub struct IntoParamsType<'a> {
pub parameter_in_provider: TokenStream,
pub type_path: Option<Cow<'a, TypePath>>,
pub type_path: Option<Cow<'a, syn::Path>>,
}

#[cfg_attr(feature = "debug", derive(Debug))]
Expand Down Expand Up @@ -131,13 +131,14 @@ impl PathOperationResolver for PathOperations {}
feature = "rocket_extras"
))]
pub mod fn_arg {

use std::borrow::Cow;

use proc_macro2::{Ident, TokenStream};
use proc_macro_error::abort;
#[cfg(any(feature = "actix_extras", feature = "axum_extras"))]
use quote::quote;
use syn::{punctuated::Punctuated, token::Comma, Pat, PatType, TypePath};
use syn::{punctuated::Punctuated, token::Comma, Pat, PatType};

use crate::component::TypeTree;
#[cfg(any(feature = "actix_extras", feature = "axum_extras"))]
Expand Down Expand Up @@ -217,7 +218,7 @@ pub mod fn_arg {
#[cfg(any(feature = "actix_extras", feature = "axum_extras"))]
pub(super) fn with_parameter_in(
arg: FnArg<'_>,
) -> Option<(Option<Cow<'_, TypePath>>, TokenStream)> {
) -> Option<(Option<std::borrow::Cow<'_, syn::Path>>, TokenStream)> {
let parameter_in_provider = if arg.ty.is("Path") {
quote! { || Some (utoipa::openapi::path::ParameterIn::Path) }
} else if arg.ty.is("Query") {
Expand All @@ -239,7 +240,7 @@ pub mod fn_arg {
}

pub(super) fn into_into_params_type(
(type_path, parameter_in_provider): (Option<Cow<'_, TypePath>>, TokenStream),
(type_path, parameter_in_provider): (Option<Cow<'_, syn::Path>>, TokenStream),
) -> IntoParamsType<'_> {
IntoParamsType {
parameter_in_provider,
Expand Down
6 changes: 3 additions & 3 deletions utoipa-gen/src/ext/rocket.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use proc_macro2::{Ident, TokenStream};
use proc_macro_error::abort;
use quote::quote;
use regex::{Captures, Regex};
use syn::{parse::Parse, LitStr, Token, TypePath};
use syn::{parse::Parse, LitStr, Token};

use crate::{
component::{GenericType, TypeTree, ValueType},
Expand Down Expand Up @@ -90,7 +90,7 @@ fn to_anonymous_value_arg<'a>(macro_arg: MacroArg) -> ValueArgument<'a> {

fn with_parameter_in(
named_args: &[MacroArg],
) -> impl Fn(FnArg) -> Option<(Option<Cow<'_, TypePath>>, TokenStream)> + '_ {
) -> impl Fn(FnArg) -> Option<(Option<Cow<'_, syn::Path>>, TokenStream)> + '_ {
move |arg: FnArg| {
let parameter_in = named_args.iter().find_map(|macro_arg| match macro_arg {
MacroArg::Path(path) => {
Expand Down Expand Up @@ -137,7 +137,7 @@ fn with_argument_in(named_args: &[MacroArg]) -> impl Fn(FnArg) -> Option<(FnArg,
}

#[inline]
fn get_value_type(ty: TypeTree<'_>) -> Option<Cow<TypePath>> {
fn get_value_type(ty: TypeTree<'_>) -> Option<Cow<syn::Path>> {
// TODO abort if map
match ty.generic_type {
Some(GenericType::Vec)
Expand Down
8 changes: 4 additions & 4 deletions utoipa-gen/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1398,7 +1398,7 @@ impl ToTokens for Required {
#[cfg_attr(feature = "debug", derive(Debug))]
#[derive(Clone)]
struct Type<'a> {
ty: Cow<'a, TypePath>,
ty: Cow<'a, syn::Path>,
is_array: bool,
is_option: bool,
is_inline: bool,
Expand All @@ -1410,9 +1410,9 @@ impl<'a> Type<'a> {
feature = "rocket_extras",
feature = "axum_extras"
))]
pub fn new(type_path: Cow<'a, TypePath>, is_array: bool, is_option: bool) -> Self {
pub fn new(path: Cow<'a, syn::Path>, is_array: bool, is_option: bool) -> Self {
Self {
ty: type_path,
ty: path,
is_array,
is_option,
is_inline: false,
Expand Down Expand Up @@ -1560,7 +1560,7 @@ impl Parse for ArrayOrOptionType<'_> {
};

Ok(Type {
ty: Cow::Owned(path),
ty: Cow::Owned(path.path),
is_array,
is_option,
is_inline: false,
Expand Down
1 change: 1 addition & 0 deletions utoipa-gen/src/openapi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,7 @@ impl ToTokens for Components {
let component_name: String = component
.alias
.as_ref()
.map(|path| &path.path)
.map(schema::format_path_ref)
.unwrap_or_else(|| ident.to_token_stream().to_string());

Expand Down
2 changes: 1 addition & 1 deletion utoipa-gen/src/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ impl<'p> PathAttr<'p> {
if let Some(into_params_argument) =
into_params_types
.iter_mut()
.find(|argument| matches!(&argument.type_path, Some(path) if path.path == parameter.path.path))
.find(|argument| matches!(&argument.type_path, Some(path) if path.as_ref() == &parameter.path.path))
{
parameter.update_parameter_in(
&mut into_params_argument.parameter_in_provider,
Expand Down
2 changes: 1 addition & 1 deletion utoipa-gen/src/path/parameter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ impl<'p> ValueParameter<'p> {
))]
pub fn update_parameter_type(
&mut self,
type_path: Option<Cow<'p, syn::TypePath>>,
type_path: Option<Cow<'p, syn::Path>>,
is_array: bool,
is_option: bool,
) {
Expand Down
2 changes: 1 addition & 1 deletion utoipa-gen/src/path/property.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ impl<'a> Property<'a> {
}

pub fn schema_type(&'a self) -> SchemaType<'a> {
SchemaType(&*self.0.ty)
SchemaType(&self.0.ty)
}
}

Expand Down
Loading

0 comments on commit 416e9eb

Please sign in to comment.