From 815d38762a0a05765c1a4aa50bde859ec8107d02 Mon Sep 17 00:00:00 2001 From: Juha Kukkonen Date: Tue, 10 Sep 2024 12:45:17 +0300 Subject: [PATCH] Full generics support Clean up the code and enhance documentation. This commit removes the old `aliases` functionality completely. --- examples/generics-actix/src/main.rs | 7 +- utoipa-gen/src/component.rs | 94 ++------ utoipa-gen/src/component/into_params.rs | 5 +- utoipa-gen/src/component/schema.rs | 204 +----------------- utoipa-gen/src/ext.rs | 3 +- utoipa-gen/src/lib.rs | 32 +-- utoipa-gen/src/openapi.rs | 4 +- utoipa-gen/src/path/parameter.rs | 4 +- utoipa-gen/src/path/request_body.rs | 3 +- utoipa-gen/src/path/response.rs | 2 - utoipa-gen/src/path/response/derive.rs | 6 +- utoipa-gen/tests/openapi_derive_test.rs | 7 +- utoipa-gen/tests/path_response_derive_test.rs | 12 +- utoipa-gen/tests/request_body_derive_test.rs | 1 + utoipa/src/lib.rs | 45 ++-- utoipa/src/openapi.rs | 6 + utoipa/src/openapi/content.rs | 1 + utoipa/src/openapi/path.rs | 1 + utoipa/src/openapi/response.rs | 1 + utoipa/src/openapi/schema.rs | 52 +++-- utoipa/src/openapi/security.rs | 2 + 21 files changed, 125 insertions(+), 367 deletions(-) diff --git a/examples/generics-actix/src/main.rs b/examples/generics-actix/src/main.rs index d9d38866..1288e9d3 100644 --- a/examples/generics-actix/src/main.rs +++ b/examples/generics-actix/src/main.rs @@ -24,7 +24,6 @@ fn get_coord_schema() -> Object { } #[derive(Serialize, ToSchema)] -#[aliases(MyFloatObject = MyObject, MyIntegerObject = MyObject)] pub struct MyObject { #[schema(schema_with=get_coord_schema::)] at: geo_types::Coord, @@ -45,7 +44,7 @@ pub struct FloatParams { FloatParams ), responses( - (status = 200, description = "OK", body = MyFloatObject), + (status = 200, description = "OK", body = MyObject), ), security( ("api_key" = []) @@ -75,7 +74,7 @@ pub struct IntegerParams { IntegerParams, ), responses( - (status = 200, description = "OK", body = MyIntegerObject), + (status = 200, description = "OK", body = MyObject), ), security( ("api_key" = []) @@ -99,7 +98,7 @@ async fn main() -> Result<(), impl Error> { #[derive(OpenApi)] #[openapi( paths(coord_f64, coord_u64), - components(schemas(MyFloatObject, MyIntegerObject)) + components(schemas(MyObject, MyObject)) )] struct ApiDoc; diff --git a/utoipa-gen/src/component.rs b/utoipa-gen/src/component.rs index 96c5632b..e9c3470d 100644 --- a/utoipa-gen/src/component.rs +++ b/utoipa-gen/src/component.rs @@ -16,12 +16,11 @@ use crate::{ }; use crate::{schema_type::SchemaType, Deprecated}; -use self::features::attributes::{As, Description, Nullable}; +use self::features::attributes::{Description, Nullable}; use self::features::validation::Minimum; use self::features::{ pop_feature, Feature, FeaturesExt, IntoInner, IsInline, ToTokensExt, Validatable, }; -use self::schema::format_path_ref; use self::serde::{RenameRule, SerdeContainer, SerdeValue}; pub mod into_params; @@ -334,24 +333,6 @@ impl<'t> TypeTree<'t> { is } - fn find_mut(&mut self, type_tree: &TypeTree) -> Option<&mut Self> { - let is = self - .path - .as_mut() - .map(|p| matches!(&type_tree.path, Some(path) if path.as_ref() == p.as_ref())) - .unwrap_or(false); - - if is { - Some(self) - } else { - self.children.as_mut().and_then(|children| { - children - .iter_mut() - .find_map(|child| Self::find_mut(child, type_tree)) - }) - } - } - /// `Object` virtual type is used when generic object is required in OpenAPI spec. Typically used /// with `value_type` attribute to hinder the actual type. pub fn is_object(&self) -> bool { @@ -374,31 +355,8 @@ impl<'t> TypeTree<'t> { matches!(self.generic_type, Some(GenericType::Map)) } - pub fn match_ident(&self, ident: &Ident) -> bool { - let Some(ref path) = self.path else { - return false; - }; - - let matches = path - .segments - .iter() - .last() - .map(|segment| &segment.ident == ident) - .unwrap_or_default(); - - matches - || self - .children - .iter() - .flatten() - .any(|child| child.match_ident(ident)) - } - - /// Get path type `Ident` and `Generics` of the `TypeTree` path value. - pub fn get_path_type_and_generics( - &self, - generic_arguments: GenericArguments, - ) -> syn::Result<(&Ident, Generics)> { + /// Get [`syn::Generics`] for current [`TypeTree`]'s [`syn::Path`]. + pub fn get_path_generics(&self) -> syn::Result { let mut generics = Generics::default(); let segment = self .path @@ -408,10 +366,7 @@ impl<'t> TypeTree<'t> { .last() .expect("Path must have segments"); - fn type_to_generic_params( - ty: &Type, - generic_arguments: &GenericArguments, - ) -> Vec { + fn type_to_generic_params(ty: &Type) -> Vec { match &ty { Type::Path(path) => { let mut params_vec: Vec = Vec::new(); @@ -423,38 +378,22 @@ impl<'t> TypeTree<'t> { let ident = &last_segment.ident; params_vec.push(syn::parse_quote!(#ident)); - if matches!(generic_arguments, GenericArguments::All) { - // we are only interested of angle bracket arguments - if let PathArguments::AngleBracketed(ref args) = last_segment.arguments { - params_vec.extend(angle_bracket_args_to_params(args, generic_arguments)) - } - } params_vec } - Type::Reference(reference) => { - type_to_generic_params(reference.elem.as_ref(), generic_arguments) - } + Type::Reference(reference) => type_to_generic_params(reference.elem.as_ref()), _ => Vec::new(), } } - fn angle_bracket_args_to_params<'a>( - args: &'a AngleBracketedGenericArguments, - generic_arguments: &'a GenericArguments, - ) -> impl Iterator + 'a { + fn angle_bracket_args_to_params( + args: &AngleBracketedGenericArguments, + ) -> impl Iterator + '_ { args.args .iter() .filter_map(move |generic_argument| { match generic_argument { - GenericArgument::Type(ty) => { - Some(type_to_generic_params(ty, generic_arguments)) - } - GenericArgument::Lifetime(life) - if matches!( - generic_arguments, - GenericArguments::CurrentTypeOnly | GenericArguments::All - ) => - { + GenericArgument::Type(ty) => Some(type_to_generic_params(ty)), + GenericArgument::Lifetime(life) => { Some(vec![GenericParam::Lifetime(syn::parse_quote!(#life))]) } _ => None, // other wise ignore @@ -465,22 +404,14 @@ impl<'t> TypeTree<'t> { if let PathArguments::AngleBracketed(angle_bracketed_args) = &segment.arguments { generics.lt_token = Some(angle_bracketed_args.lt_token); - generics.params = - angle_bracket_args_to_params(angle_bracketed_args, &generic_arguments).collect(); + generics.params = angle_bracket_args_to_params(angle_bracketed_args).collect(); generics.gt_token = Some(angle_bracketed_args.gt_token); }; - Ok((&segment.ident, generics)) + Ok(generics) } } -#[allow(unused)] -pub enum GenericArguments { - All, - CurrentTypeOnly, - CurrentOnlyNoLifetimes, -} - impl PartialEq for TypeTree<'_> { #[cfg(feature = "debug")] fn eq(&self, other: &Self) -> bool { @@ -585,7 +516,6 @@ impl Rename for FieldRename { #[cfg_attr(feature = "debug", derive(Debug))] pub struct Container<'c> { - pub ident: &'c Ident, pub generics: &'c Generics, } diff --git a/utoipa-gen/src/component/into_params.rs b/utoipa-gen/src/component/into_params.rs index 880ad2e4..30ba349c 100644 --- a/utoipa-gen/src/component/into_params.rs +++ b/utoipa-gen/src/component/into_params.rs @@ -1,6 +1,6 @@ use std::borrow::Cow; -use proc_macro2::{Span, TokenStream}; +use proc_macro2::TokenStream; use quote::quote; use syn::{ parse::Parse, punctuated::Punctuated, spanned::Spanned, token::Comma, Attribute, Data, Field, @@ -25,7 +25,7 @@ use crate::{ FieldRename, }, doc_comment::CommentAttributes, - Array, Diagnostics, GenericsExt, OptionExt, Required, ToTokensDiagnostics, + Array, Diagnostics, OptionExt, Required, ToTokensDiagnostics, }; use super::{ @@ -461,7 +461,6 @@ impl ToTokensDiagnostics for Param<'_> { description: None, deprecated: None, container: &Container { - ident: &Ident::new("empty_param", Span::call_site()), generics: self.generics, }, })?; diff --git a/utoipa-gen/src/component/schema.rs b/utoipa-gen/src/component/schema.rs index f8cb4196..c92a476b 100644 --- a/utoipa-gen/src/component/schema.rs +++ b/utoipa-gen/src/component/schema.rs @@ -1,19 +1,17 @@ use std::borrow::{Borrow, Cow}; -use proc_macro2::{Ident, Span, TokenStream}; +use proc_macro2::{Ident, TokenStream}; use quote::{format_ident, quote, ToTokens}; use syn::{ - parse::Parse, parse_quote, punctuated::Punctuated, spanned::Spanned, token::Comma, Attribute, - Constraint, Data, Field, Fields, FieldsNamed, FieldsUnnamed, GenericArgument, GenericParam, - Generics, Lifetime, LifetimeParam, Path, PathArguments, Token, Type, TypeParam, Variant, - Visibility, + punctuated::Punctuated, spanned::Spanned, token::Comma, Attribute, Data, Field, Fields, + FieldsNamed, FieldsUnnamed, Generics, Path, PathArguments, Variant, }; use crate::{ as_tokens_or_diagnostics, component::features::attributes::{Example, Rename, ValueType}, doc_comment::CommentAttributes, - Array, Deprecated, Diagnostics, GenericsExt, OptionExt, ToTokensDiagnostics, + Deprecated, Diagnostics, OptionExt, ToTokensDiagnostics, }; use self::{ @@ -47,40 +45,27 @@ pub struct Parent<'p> { pub ident: &'p Ident, pub generics: &'p Generics, pub attributes: &'p [Attribute], - // type_tree: &'p TypeTree<'p>, } pub struct Schema<'a> { ident: &'a Ident, attributes: &'a [Attribute], generics: &'a Generics, - aliases: Option>, data: &'a Data, - vis: &'a Visibility, } impl<'a> Schema<'a> { - const TO_SCHEMA_LIFETIME: &'static str = "'__s"; pub fn new( data: &'a Data, attributes: &'a [Attribute], ident: &'a Ident, generics: &'a Generics, - vis: &'a Visibility, ) -> Result { - let aliases = if generics.type_params().count() > 0 { - parse_aliases(attributes)? - } else { - None - }; - Ok(Self { data, ident, attributes, generics, - aliases, - vis, }) } } @@ -88,83 +73,14 @@ impl<'a> Schema<'a> { impl ToTokensDiagnostics for Schema<'_> { fn to_tokens(&self, tokens: &mut TokenStream) -> Result<(), Diagnostics> { let ident = self.ident; - let (_, ty_generics, where_clause) = self.generics.split_for_impl(); - - let schema_ty: Type = parse_quote!(#ident #ty_generics); - let schema_type_tree = TypeTree::from_type(&schema_ty)?; + let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl(); let parent = Parent { ident, generics: self.generics, attributes: self.attributes, - // type_tree: &schema_type_tree, }; - let variant = SchemaVariant::new(self.data, &parent, None::>)?; - let life = &Lifetime::new(Schema::TO_SCHEMA_LIFETIME, Span::call_site()); - - // TODO remove this duplicate TypeTree::from_type(...) thing once complete - let schema_children = &*TypeTree::from_type(&schema_ty)? - .children - .unwrap_or_default(); - - let aliases = self.aliases.as_ref().map_try(|aliases| { - let alias_schemas = aliases - .iter() - .map(|alias| { - let name = &*alias.name; - let alias_type_tree = TypeTree::from_type(&alias.ty); - - // TODO remove this duplicate TypeTree::from_type(...) thing once complete - SchemaVariant::new( - self.data, - &Parent { - ident, - generics: &Generics::default(), - attributes: self.attributes, - // type_tree: &TypeTree::from_type(&alias.ty)?, - }, - alias_type_tree? - .children - .map(|children| children.into_iter().zip(schema_children)), - ) - .and_then(|variant| { - let mut alias_tokens = TokenStream::new(); - match variant.to_tokens(&mut alias_tokens) { - Ok(_) => Ok(quote! { (#name, #alias_tokens.into()) }), - Err(diagnostics) => Err(diagnostics), - } - }) - }) - .collect::, Diagnostics>>()?; - - Result::::Ok(quote! { - fn aliases() -> Vec<(&'static str, utoipa::openapi::schema::Schema)> { - #alias_schemas.to_vec() - } - }) - })?; - - let type_aliases = self.aliases.as_ref().map_try(|aliases| { - aliases - .iter() - .map(|alias| { - let name = quote::format_ident!("{}", alias.name); - let ty = &alias.ty; - let vis = self.vis; - let name_generics = alias.get_lifetimes()?.fold( - Punctuated::<&GenericArgument, Comma>::new(), - |mut acc, lifetime| { - acc.push(lifetime); - acc - }, - ); - - Ok(quote! { - #vis type #name < #name_generics > = #ty; - }) - }) - .collect::>() - })?; + let variant = SchemaVariant::new(self.data, &parent)?; let name = if let Some(schema_as) = variant.get_schema_as() { format_path_ref(&schema_as.0.path) @@ -172,16 +88,6 @@ impl ToTokensDiagnostics for Schema<'_> { ident.to_string() }; - let schema_lifetime: GenericParam = LifetimeParam::new(life.clone()).into(); - let schema_generics = Generics { - params: [schema_lifetime.clone()].into_iter().collect(), - ..Default::default() - }; - - // let mut impl_generics = self.generics.clone(); - // impl_generics.params.push(schema_lifetime); - let (impl_generics, _, _) = self.generics.split_for_impl(); - let mut variant_tokens = TokenStream::new(); variant.to_tokens(&mut variant_tokens)?; @@ -198,11 +104,7 @@ impl ToTokensDiagnostics for Schema<'_> { fn name() -> std::borrow::Cow<'static, str> { std::borrow::Cow::Borrowed(#name) } - - #aliases } - - #type_aliases }); Ok(()) } @@ -217,11 +119,7 @@ enum SchemaVariant<'a> { } impl<'a> SchemaVariant<'a> { - pub fn new, &'a TypeTree<'a>)>>( - data: &'a Data, - parent: &'a Parent<'a>, - aliases: Option, - ) -> Result, Diagnostics> { + pub fn new(data: &'a Data, parent: &'a Parent<'a>) -> Result, Diagnostics> { match data { Data::Struct(content) => match &content.fields { Fields::Unnamed(fields) => { @@ -259,7 +157,6 @@ impl<'a> SchemaVariant<'a> { features: named_features, fields: named, schema_as, - aliases: aliases.map(|aliases| aliases.into_iter().collect()), })) } Fields::Unit => Ok(Self::Unit(UnitStructVariant)), @@ -314,7 +211,6 @@ pub struct NamedStructSchema<'a> { pub description: Option, pub features: Option>, pub rename_all: Option, - pub aliases: Option, &'a TypeTree<'a>)>>, pub schema_as: Option, } @@ -334,13 +230,6 @@ impl NamedStructSchema<'_> { container_rules: &SerdeContainer, ) -> Result, Diagnostics> { let type_tree = &mut TypeTree::from_type(&field.ty)?; - if let Some(aliases) = &self.aliases { - for (new_generic, old_generic_matcher) in aliases.iter() { - if let Some(generic_match) = type_tree.find_mut(old_generic_matcher) { - *generic_match = new_generic.clone(); - } - } - } let mut field_features = field .attrs @@ -410,7 +299,6 @@ impl NamedStructSchema<'_> { description: Some(description), deprecated: deprecated.as_ref(), container: &super::Container { - ident: self.parent.ident, generics: self.parent.generics, }, }; @@ -675,8 +563,7 @@ impl ToTokensDiagnostics for UnnamedStructSchema<'_> { description: description.as_ref(), deprecated: deprecated.as_ref(), container: &super::Container { - ident: &self.parent.ident, - generics: &self.parent.generics, + generics: self.parent.generics, }, })? .to_token_stream(), @@ -1130,13 +1017,11 @@ impl ComplexEnum<'_> { ident: self.parent.ident, attributes: &variant.attrs, generics: self.parent.generics, - // type_tree: self.parent.type_tree, }, description: None, rename_all: pop_feature!(named_struct_features => Feature::RenameAll(_) as Option), features: Some(named_struct_features), fields: &named_fields.named, - aliases: None, schema_as: None, }), })) @@ -1169,7 +1054,6 @@ impl ComplexEnum<'_> { ident: self.parent.ident, attributes: &variant.attrs, generics: self.parent.generics, - // type_tree: self.parent.type_tree, }, description: None, features: Some(unnamed_struct_features), @@ -1239,13 +1123,11 @@ impl ComplexEnum<'_> { ident: self.parent.ident, attributes: &variant.attrs, generics: self.parent.generics, - // type_tree: self.parent.type_tree, }, description: None, rename_all: pop_feature!(named_struct_features => Feature::RenameAll(_) as Option), features: Some(named_struct_features), fields: &named_fields.named, - aliases: None, schema_as: None, })) } @@ -1261,7 +1143,6 @@ impl ComplexEnum<'_> { ident: self.parent.ident, attributes: &variant.attrs, generics: self.parent.generics, - // type_tree: self.parent.type_tree, }, description: None, features: Some(unnamed_struct_features), @@ -1314,13 +1195,11 @@ impl ComplexEnum<'_> { ident: self.parent.ident, attributes: &variant.attrs, generics: self.parent.generics, - // type_tree: self.parent.type_tree, }, description: None, rename_all: pop_feature!(named_struct_features => Feature::RenameAll(_) as Option), features: Some(named_struct_features), fields: &named_fields.named, - aliases: None, schema_as: None, }; let named_enum_tokens = as_tokens_or_diagnostics!(&named_enum); @@ -1361,7 +1240,6 @@ impl ComplexEnum<'_> { ident: self.parent.ident, attributes: &variant.attrs, generics: self.parent.generics, - // type_tree: self.parent.type_tree, }, description: None, features: Some(unnamed_struct_features), @@ -1485,13 +1363,11 @@ impl ComplexEnum<'_> { ident: self.parent.ident, attributes: &variant.attrs, generics: self.parent.generics, - // type_tree: self.parent.type_tree, }, description: None, rename_all: pop_feature!(named_struct_features => Feature::RenameAll(_) as Option), features: Some(named_struct_features), fields: &named_fields.named, - aliases: None, schema_as: None, }; let named_enum_tokens = as_tokens_or_diagnostics!(&named_enum); @@ -1535,7 +1411,6 @@ impl ComplexEnum<'_> { ident: self.parent.ident, attributes: &variant.attrs, generics: self.parent.generics, - // type_tree: self.parent.type_tree, }, description: None, features: Some(unnamed_struct_features), @@ -1736,66 +1611,3 @@ fn is_not_skipped(rule: &SerdeValue) -> bool { fn is_flatten(rule: &SerdeValue) -> bool { rule.flatten } - -#[cfg_attr(feature = "debug", derive(Debug))] -pub struct AliasSchema { - pub name: String, - pub ty: Type, -} - -impl AliasSchema { - fn get_lifetimes(&self) -> Result, Diagnostics> { - fn lifetimes_from_type( - ty: &Type, - ) -> Result, Diagnostics> { - match ty { - Type::Path(type_path) => Ok(type_path - .path - .segments - .iter() - .flat_map(|segment| match &segment.arguments { - PathArguments::AngleBracketed(angle_bracketed_args) => { - Some(angle_bracketed_args.args.iter()) - } - _ => None, - }) - .flatten() - .flat_map(|arg| match arg { - GenericArgument::Type(type_argument) => { - lifetimes_from_type(type_argument).map(|iter| iter.collect::>()) - } - _ => Ok(vec![arg]), - }) - .flat_map(|args| args.into_iter().filter(|generic_arg| matches!(generic_arg, syn::GenericArgument::Lifetime(lifetime) if lifetime.ident != "'static"))), - ), - _ => Err(Diagnostics::with_span(ty.span(), "AliasSchema `get_lifetimes` only supports syn::TypePath types")) - } - } - - lifetimes_from_type(&self.ty) - } -} - -impl Parse for AliasSchema { - fn parse(input: syn::parse::ParseStream) -> syn::Result { - let name = input.parse::()?; - input.parse::()?; - - Ok(Self { - name: name.to_string(), - ty: input.parse::()?, - }) - } -} - -fn parse_aliases( - attributes: &[Attribute], -) -> Result>, Diagnostics> { - attributes - .iter() - .find(|attribute| attribute.path().is_ident("aliases")) - .map_try(|aliases| { - aliases.parse_args_with(Punctuated::::parse_terminated) - }) - .map_err(Into::into) -} diff --git a/utoipa-gen/src/ext.rs b/utoipa-gen/src/ext.rs index 5c63ad2c..7efffe70 100644 --- a/utoipa-gen/src/ext.rs +++ b/utoipa-gen/src/ext.rs @@ -1,6 +1,6 @@ use std::borrow::Cow; -use proc_macro2::{Ident, Span, TokenStream}; +use proc_macro2::TokenStream; use quote::{quote, quote_spanned}; use syn::spanned::Spanned; use syn::{parse_quote, Generics}; @@ -137,7 +137,6 @@ impl ToTokensDiagnostics for RequestBody<'_> { description: None, deprecated: None, container: &Container { - ident: &Ident::new("empty_request_body", Span::call_site()), generics: &Generics::default(), } })?); diff --git a/utoipa-gen/src/lib.rs b/utoipa-gen/src/lib.rs index c8310cf9..dfa93b5a 100644 --- a/utoipa-gen/src/lib.rs +++ b/utoipa-gen/src/lib.rs @@ -65,7 +65,7 @@ use self::{ path::response::derive::{IntoResponses, ToResponse}, }; -#[proc_macro_derive(ToSchema, attributes(schema, aliases))] +#[proc_macro_derive(ToSchema, attributes(schema))] /// Generate reusable OpenAPI schema to be used /// together with [`OpenApi`][openapi_derive]. /// @@ -182,7 +182,7 @@ use self::{ /// This is useful in cases where the default type does not correspond to the actual type e.g. when /// any third-party types are used which are not [`ToSchema`][to_schema]s nor [`primitive` types][primitive]. /// The value can be any Rust type what normally could be used to serialize to JSON or either virtual type _`Object`_ -/// or _`Value`_, or an alias defined using `#[aliases(..)]`. +/// or _`Value`_. /// _`Object`_ will be rendered as generic OpenAPI object _(`type: object`)_. /// _`Value`_ will be rendered as any OpenAPI value (i.e. no `type` restriction). /// * `title = ...` Literal string value. Can be used to define title for struct in OpenAPI @@ -209,7 +209,7 @@ use self::{ /// This is useful in cases where the default type does not correspond to the actual type e.g. when /// any third-party types are used which are not [`ToSchema`][to_schema]s nor [`primitive` types][primitive]. /// The value can be any Rust type what normally could be used to serialize to JSON, or either virtual type _`Object`_ -/// or _`Value`_, or an alias defined using `#[aliases(..)]`. +/// or _`Value`_. /// _`Object`_ will be rendered as generic OpenAPI object _(`type: object`)_. /// _`Value`_ will be rendered as any OpenAPI value (i.e. no `type` restriction). /// * `inline` If the type of this field implements [`ToSchema`][to_schema], then the schema definition @@ -441,7 +441,7 @@ use self::{ /// } /// ``` /// When generic types are registered to the `OpenApi` the full type declaration must be provided. -/// See the full example in test [schema_generic.rs](https://github.com/juhaku/utoipa/blob/master/utoipa-gen/tests/schema_generics.rs) +/// See the full example in test [schema_generics.rs](https://github.com/juhaku/utoipa/blob/master/utoipa-gen/tests/schema_generics.rs) /// /// # Examples /// @@ -699,10 +699,10 @@ pub fn derive_to_schema(input: TokenStream) -> TokenStream { ident, data, generics, - vis, + .. } = syn::parse_macro_input!(input); - Schema::new(&data, &attrs, &ident, &generics, &vis) + Schema::new(&data, &attrs, &ident, &generics) .as_ref() .map_or_else(Diagnostics::to_token_stream, Schema::to_token_stream) .into() @@ -1827,7 +1827,7 @@ pub fn openapi(input: TokenStream) -> TokenStream { /// This is useful in cases where the default type does not correspond to the actual type e.g. when /// any third-party types are used which are not [`ToSchema`][to_schema]s nor [`primitive` types][primitive]. /// The value can be any Rust type what normally could be used to serialize to JSON, or either virtual type _`Object`_ -/// or _`Value`_, or an alias defined using `#[aliases(..)]`. +/// or _`Value`_. /// _`Object`_ will be rendered as generic OpenAPI object _(`type: object`)_. /// _`Value`_ will be rendered as any OpenAPI value (i.e. no `type` restriction). /// @@ -2601,11 +2601,10 @@ pub fn schema(input: TokenStream) -> TokenStream { Err(diagnostics) => return diagnostics.into_token_stream().into(), }; - let (ident, generics) = - match type_tree.get_path_type_and_generics(component::GenericArguments::CurrentTypeOnly) { - Ok(type_and_generics) => type_and_generics, - Err(error) => return error.into_compile_error().into(), - }; + let generics = match type_tree.get_path_generics() { + Ok(generics) => generics, + Err(error) => return error.into_compile_error().into(), + }; let schema = ComponentSchema::new(ComponentSchemaProps { features: Some(vec![Feature::Inline(schema.inline.into())]), @@ -2613,7 +2612,6 @@ pub fn schema(input: TokenStream) -> TokenStream { deprecated: None, description: None, container: &component::Container { - ident, generics: &generics, }, }); @@ -2943,19 +2941,11 @@ impl OptionExt for Option { } trait GenericsExt { - fn any_match_type_tree(&self, type_tree: &TypeTree) -> bool; /// Get index of `GenericParam::Type` ignoring other generic param types. fn get_generic_type_param_index(&self, type_tree: &TypeTree) -> Option; } impl<'g> GenericsExt for &'g syn::Generics { - fn any_match_type_tree(&self, type_tree: &TypeTree) -> bool { - self.params.iter().any(|generic| match generic { - syn::GenericParam::Type(generic_type) => type_tree.match_ident(&generic_type.ident), - _ => false, - }) - } - fn get_generic_type_param_index(&self, type_tree: &TypeTree) -> Option { let ident = &type_tree .path diff --git a/utoipa-gen/src/openapi.rs b/utoipa-gen/src/openapi.rs index 3be87551..05b9c87d 100644 --- a/utoipa-gen/src/openapi.rs +++ b/utoipa-gen/src/openapi.rs @@ -150,11 +150,9 @@ impl Schema { fn get_component(&self) -> Result { let ty = syn::Type::Path(self.0.clone()); let type_tree = TypeTree::from_type(&ty)?; - let (ident, generics) = type_tree - .get_path_type_and_generics(crate::component::GenericArguments::CurrentTypeOnly)?; + let generics = type_tree.get_path_generics()?; let container = Container { - ident, generics: &generics, }; let component_schema = ComponentSchema::new(crate::component::ComponentSchemaProps { diff --git a/utoipa-gen/src/path/parameter.rs b/utoipa-gen/src/path/parameter.rs index 8ccd926e..a31c67aa 100644 --- a/utoipa-gen/src/path/parameter.rs +++ b/utoipa-gen/src/path/parameter.rs @@ -1,6 +1,6 @@ use std::{borrow::Cow, fmt::Display}; -use proc_macro2::{Ident, Span, TokenStream}; +use proc_macro2::{Ident, TokenStream}; use quote::{quote, quote_spanned, ToTokens}; use syn::{ parenthesized, @@ -185,7 +185,6 @@ impl ToTokensDiagnostics for ParameterSchema<'_> { description: None, deprecated: None, container: &Container { - ident: &Ident::new("empty_parameter_external", Span::call_site()), generics: &Generics::default(), } } @@ -209,7 +208,6 @@ impl ToTokensDiagnostics for ParameterSchema<'_> { description: None, deprecated: None, container: &Container { - ident: &Ident::new("empty_parameter_parsed", Span::call_site()), generics: &Generics::default(), } } diff --git a/utoipa-gen/src/path/request_body.rs b/utoipa-gen/src/path/request_body.rs index da242feb..6db274b9 100644 --- a/utoipa-gen/src/path/request_body.rs +++ b/utoipa-gen/src/path/request_body.rs @@ -1,4 +1,4 @@ -use proc_macro2::{Ident, Span, TokenStream as TokenStream2}; +use proc_macro2::{Ident, TokenStream as TokenStream2}; use quote::{quote, ToTokens}; use syn::punctuated::Punctuated; use syn::token::Comma; @@ -170,7 +170,6 @@ impl ToTokensDiagnostics for RequestBodyAttr<'_> { description: None, deprecated: None, container: &Container { - ident: &Ident::new("empty_request_body", Span::call_site()), generics: &Generics::default(), }, })? diff --git a/utoipa-gen/src/path/response.rs b/utoipa-gen/src/path/response.rs index 905ff034..d255be17 100644 --- a/utoipa-gen/src/path/response.rs +++ b/utoipa-gen/src/path/response.rs @@ -294,7 +294,6 @@ impl ToTokensDiagnostics for ResponseTuple<'_> { description: None, deprecated: None, container: &Container { - ident: &Ident::new("empty_repopnse_tuple", Span::call_site()), generics: &Generics::default(), }, })? @@ -869,7 +868,6 @@ impl ToTokensDiagnostics for Header { description: None, deprecated: None, container: &Container { - ident: &Ident::new("empty_header", Span::call_site()), generics: &Generics::default(), }, })? diff --git a/utoipa-gen/src/path/response/derive.rs b/utoipa-gen/src/path/response/derive.rs index 6e0d7157..07b0cfb6 100644 --- a/utoipa-gen/src/path/response/derive.rs +++ b/utoipa-gen/src/path/response/derive.rs @@ -8,8 +8,8 @@ use syn::punctuated::Punctuated; use syn::spanned::Spanned; use syn::token::Comma; use syn::{ - Attribute, Data, Field, Fields, Generics, Lifetime, LifetimeParam, LitStr, - ParenthesizedGenericArguments, Path, Type, TypePath, Variant, + Attribute, Data, Field, Fields, Generics, Lifetime, LifetimeParam, LitStr, Path, Type, + TypePath, Variant, }; use crate::component::schema::{EnumSchema, NamedStructSchema, Parent}; @@ -350,7 +350,6 @@ impl NamedStructResponse<'_> { generics: &Generics::default(), }, fields, - aliases: None, description: None, features: None, rename_all: None, @@ -438,7 +437,6 @@ impl<'p> ToResponseNamedStructResponse<'p> { attributes, generics: &Generics::default(), }, - aliases: None, description: None, fields, features: None, diff --git a/utoipa-gen/tests/openapi_derive_test.rs b/utoipa-gen/tests/openapi_derive_test.rs index 7d82eb69..1feecbc6 100644 --- a/utoipa-gen/tests/openapi_derive_test.rs +++ b/utoipa-gen/tests/openapi_derive_test.rs @@ -1,4 +1,3 @@ -#![cfg(feature = "yaml")] #![allow(dead_code)] use serde::{Deserialize, Serialize}; @@ -54,7 +53,7 @@ mod pet_api { #[derive(Default, OpenApi)] #[openapi( paths(pet_api::get_pet_by_id), - components(schemas(Pet, GenericC, GenericD)), + components(schemas(Pet, C, C)), modifiers(&Foo), security( (), @@ -75,8 +74,7 @@ struct B { } #[derive(Deserialize, Serialize, ToSchema)] -#[aliases(GenericC = C, GenericD = C)] -struct C { +struct C { field_1: R, field_2: T, } @@ -115,6 +113,7 @@ impl Modify for Foo { } #[test] +#[cfg(feature = "yaml")] fn stable_yaml() { let left = ApiDoc::openapi().to_yaml().unwrap(); let right = ApiDoc::openapi().to_yaml().unwrap(); diff --git a/utoipa-gen/tests/path_response_derive_test.rs b/utoipa-gen/tests/path_response_derive_test.rs index 1efe2fc5..f9cd10b6 100644 --- a/utoipa-gen/tests/path_response_derive_test.rs +++ b/utoipa-gen/tests/path_response_derive_test.rs @@ -28,7 +28,7 @@ macro_rules! api_doc { #[openapi(paths($module::get_foo))] struct ApiDoc; - let doc = serde_json::to_value(&ApiDoc::openapi()).unwrap(); + let doc = serde_json::to_value(ApiDoc::openapi()).unwrap(); doc.pointer("/paths/~1foo/get") .unwrap_or(&serde_json::Value::Null) .clone() @@ -364,7 +364,7 @@ fn derive_path_with_multiple_responses_via_content_attribute() { #[openapi(paths(get_item), components(schemas(User, User2)))] struct ApiDoc; - let doc = serde_json::to_value(&ApiDoc::openapi()).unwrap(); + let doc = serde_json::to_value(ApiDoc::openapi()).unwrap(); let responses = doc.pointer("/paths/~1foo/get/responses").unwrap(); assert_json_eq!( @@ -424,7 +424,7 @@ fn derive_path_with_multiple_examples() { #[openapi(paths(get_item), components(schemas(User)))] struct ApiDoc; - let doc = serde_json::to_value(&ApiDoc::openapi()).unwrap(); + let doc = serde_json::to_value(ApiDoc::openapi()).unwrap(); let responses = doc.pointer("/paths/~1foo/get/responses").unwrap(); assert_json_eq!( @@ -502,7 +502,7 @@ fn derive_path_with_multiple_responses_with_multiple_examples() { #[openapi(paths(get_item), components(schemas(User, User2)))] struct ApiDoc; - let doc = serde_json::to_value(&ApiDoc::openapi()).unwrap(); + let doc = serde_json::to_value(ApiDoc::openapi()).unwrap(); let responses = doc.pointer("/paths/~1foo/get/responses").unwrap(); assert_json_eq!( @@ -568,7 +568,7 @@ fn path_response_with_external_ref() { #[openapi(paths(get_item))] struct ApiDoc; - let doc = serde_json::to_value(&ApiDoc::openapi()).unwrap(); + let doc = serde_json::to_value(ApiDoc::openapi()).unwrap(); let responses = doc.pointer("/paths/~1foo/get/responses").unwrap(); assert_json_eq!( @@ -611,7 +611,7 @@ fn path_response_with_inline_ref_type() { #[openapi(paths(get_item), components(schemas(User)))] struct ApiDoc; - let doc = serde_json::to_value(&ApiDoc::openapi()).unwrap(); + let doc = serde_json::to_value(ApiDoc::openapi()).unwrap(); let responses = doc.pointer("/paths/~1foo/get/responses").unwrap(); assert_json_eq!( diff --git a/utoipa-gen/tests/request_body_derive_test.rs b/utoipa-gen/tests/request_body_derive_test.rs index 79ca431a..7d282de0 100644 --- a/utoipa-gen/tests/request_body_derive_test.rs +++ b/utoipa-gen/tests/request_body_derive_test.rs @@ -475,6 +475,7 @@ fn derive_request_body_ref_path_success() { (status = 200, description = "success response") ) )] + #[allow(unused)] fn post_foo() {} #[derive(OpenApi, Default)] diff --git a/utoipa/src/lib.rs b/utoipa/src/lib.rs index 40fce9d4..3924c59e 100644 --- a/utoipa/src/lib.rs +++ b/utoipa/src/lib.rs @@ -1,3 +1,5 @@ +#![warn(missing_docs)] +#![warn(rustdoc::broken_intra_doc_links)] #![cfg_attr(doc_cfg, feature(doc_cfg))] //! Want to have your API documented with OpenAPI? But you don't want to see the //! trouble with manual yaml or json tweaking? Would like it to be so easy that it would almost @@ -321,6 +323,8 @@ pub use utoipa_gen::*; /// ``` /// [derive]: derive.OpenApi.html pub trait OpenApi { + /// Return the [`openapi::OpenApi`] instance which can be parsed with serde or served via + /// OpenAPI visualization tool such as Swagger UI. fn openapi() -> openapi::OpenApi; } @@ -394,20 +398,14 @@ pub trait OpenApi { /// } /// ``` pub trait ToSchema: PartialSchema { - /// Return a tuple of name and schema or reference to a schema that can be referenced by the - /// name or inlined directly to responses, request bodies or parameters. - fn name() -> Cow<'static, str>; - // /// Return a tuple of name and schema or reference to a schema that can be referenced by the - // /// name or inlined directly to responses, request bodies or parameters. - // fn schema() -> (&'__s str, openapi::RefOr); - - /// Optional set of alias schemas for the [`PartialSchema::schema`]. + /// Return name of the schema. /// - /// Typically there is no need to manually implement this method but it is instead implemented - /// by derive [`macro@ToSchema`] when `#[aliases(...)]` attribute is defined. - fn aliases() -> Vec<(&'static str, openapi::schema::Schema)> { - Vec::new() - } + /// Name is used by referencing objects to point to this schema object returned with + /// [`PartialSchema::schema`] within the OpenAPI document. + /// + /// In case a generic schema the _`name`_ will be used as prefix for the name in the OpenAPI + /// documentation. + fn name() -> Cow<'static, str>; } impl From for openapi::RefOr { @@ -555,9 +553,6 @@ pub trait PartialSchema { #[rustfmt::skip] impl_partial_schema_primitive!( i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize, bool, f32, f64, String, str, char - // Option, Option, Option, Option, Option, Option, Option, Option, - // Option, Option, Option, Option, Option, Option, Option, - // Option, Option<&str>, Option ); impl_partial_schema!(&str); @@ -801,10 +796,14 @@ impl PartialSchema for std::collections::HashMap< /// /// [derive]: attr.path.html pub trait Path { + /// List of HTTP methods this path operation is served at. fn methods() -> Vec; + /// The path this operation is served at. fn path() -> String; + /// [`openapi::path::Operation`] describing http operation details such as request bodies, + /// parameters and responses. fn operation() -> openapi::path::Operation; } @@ -872,6 +871,11 @@ pub trait Path { /// /// [server]: https://spec.openapis.org/oas/latest.html#server-object pub trait Modify { + /// Apply mutation for [`openapi::OpenApi`] instance before it is returned by + /// [`openapi::OpenApi::openapi`] method call. + /// + /// This function allows users to run arbitrary code to change the generated + /// [`utoipa::OpenApi`] before it is served. fn modify(&self, openapi: &mut openapi::OpenApi); } @@ -1047,8 +1051,11 @@ pub trait ToResponse<'__r> { #[cfg_attr(feature = "debug", derive(Debug))] #[serde(untagged)] pub enum Number { + /// Signed integer e.g. `1` or `-2` Int(isize), + /// Unsigned integer value e.g. `0`. Unsigned integer cannot be below zero. UInt(usize), + /// Floating point number e.g. `1.34` Float(f64), } @@ -1057,9 +1064,9 @@ impl Eq for Number {} impl PartialEq for Number { fn eq(&self, other: &Self) -> bool { match (self, other) { - (Self::Int(l0), Self::Int(r0)) => l0 == r0, - (Self::UInt(l0), Self::UInt(r0)) => l0 == r0, - (Self::Float(l0), Self::Float(r0)) => l0 == r0, + (Self::Int(left), Self::Int(right)) => left == right, + (Self::UInt(left), Self::UInt(right)) => left == right, + (Self::Float(left), Self::Float(right)) => left == right, _ => false, } } diff --git a/utoipa/src/openapi.rs b/utoipa/src/openapi.rs index 6a679f1e..c8fbafeb 100644 --- a/utoipa/src/openapi.rs +++ b/utoipa/src/openapi.rs @@ -423,6 +423,7 @@ impl<'de> Deserialize<'de> for OpenApiVersion { /// The value will serialize to boolean. #[derive(PartialEq, Eq, Clone, Default)] #[cfg_attr(feature = "debug", derive(Debug))] +#[allow(missing_docs)] pub enum Deprecated { True, #[default] @@ -469,6 +470,7 @@ impl<'de> Deserialize<'de> for Deprecated { /// /// The value will serialize to boolean. #[derive(PartialEq, Eq, Clone, Default)] +#[allow(missing_docs)] #[cfg_attr(feature = "debug", derive(Debug))] pub enum Required { True, @@ -520,7 +522,11 @@ impl<'de> Deserialize<'de> for Required { #[cfg_attr(feature = "debug", derive(Debug))] #[serde(untagged)] pub enum RefOr { + /// Represents [`Ref`] reference to another OpenAPI object instance. e.g. + /// `$ref: #/components/schemas/Hello` Ref(Ref), + /// Represents any value that can be added to the [`struct@Components`] e.g. [`enum@Schema`] + /// or [`struct@Response`]. T(T), } diff --git a/utoipa/src/openapi/content.rs b/utoipa/src/openapi/content.rs index 96aba954..210c035f 100644 --- a/utoipa/src/openapi/content.rs +++ b/utoipa/src/openapi/content.rs @@ -46,6 +46,7 @@ builder! { } impl Content { + /// Construct a new [`Content`] object for provided _`schema`_. pub fn new>>(schema: I) -> Self { Self { schema: schema.into(), diff --git a/utoipa/src/openapi/path.rs b/utoipa/src/openapi/path.rs index 71075059..bf5c59d5 100644 --- a/utoipa/src/openapi/path.rs +++ b/utoipa/src/openapi/path.rs @@ -473,6 +473,7 @@ builder! { pub responses: Responses, // TODO + #[allow(missing_docs)] #[serde(skip_serializing_if = "Option::is_none")] pub callbacks: Option, diff --git a/utoipa/src/openapi/response.rs b/utoipa/src/openapi/response.rs index 7b51ede8..958e00d8 100644 --- a/utoipa/src/openapi/response.rs +++ b/utoipa/src/openapi/response.rs @@ -31,6 +31,7 @@ builder! { } impl Responses { + /// Construct a new [`Responses`]. pub fn new() -> Self { Default::default() } diff --git a/utoipa/src/openapi/schema.rs b/utoipa/src/openapi/schema.rs index 2b1d9210..9806f3a6 100644 --- a/utoipa/src/openapi/schema.rs +++ b/utoipa/src/openapi/schema.rs @@ -134,24 +134,28 @@ impl ComponentsBuilder { self } + /// Add [`Schema`] to [`Components`]. + /// + /// This is effectively same as calling [`ComponentsBuilder::schema`] but expects to be called + /// with one generic argument that implements [`ToSchema`][trait@ToSchema] trait. + /// + /// # Examples + /// + /// _**Add schema from `Value` type that derives `ToSchema`.**_ + /// + /// ```rust + /// # use utoipa::{ToSchema, openapi::schema::ComponentsBuilder}; + /// #[derive(ToSchema)] + /// struct Value(String); + /// + /// let _ = ComponentsBuilder::new().schema_from::().build(); + /// ``` pub fn schema_from(mut self) -> Self { - let aliases = I::aliases(); - - // TODO this need to call similar to schema! macro call with inline always to get the full - // schema which then need to be composed with generic args of the schema!!! - - // TODO a temporal hack to add the main schema only if there are no aliases pre-defined. - // Eventually aliases functionality should be extracted out from the `ToSchema`. Aliases - // are created when the main schema is a generic type which should be included in OpenAPI - // spec in its generic form. - if aliases.is_empty() { - let name = I::name(); - let schema = I::schema(); - // let (name, schema) = I::schema(); - self.schemas.insert(name.to_string(), schema); - } + let name = I::name(); + let schema = I::schema(); + self.schemas.insert(name.to_string(), schema); - self.schemas_from_iter(aliases) + self } /// Add [`Schema`]s from iterator. @@ -189,6 +193,10 @@ impl ComponentsBuilder { self } + /// Add [`struct@Response`] to [`Components`]. + /// + /// Method accepts tow arguments; `name` of the reusable response and `response` which is the + /// reusable response itself. pub fn response, R: Into>>( mut self, name: S, @@ -198,11 +206,20 @@ impl ComponentsBuilder { self } + /// Add [`struct@Response`] to [`Components`]. + /// + /// This behaves the same way as [`ComponentsBuilder::schema_from`] but for responses. It + /// allows adding response from type implementing [`trait@ToResponse`] trait. Method is + /// expected to be called with one generic argument that implements the trait. pub fn response_from<'r, I: ToResponse<'r>>(self) -> Self { let (name, response) = I::response(); self.response(name, response) } + /// Add multiple [`struct@Response`]s to [`Components`] from iterator. + /// + /// Like the [`ComponentsBuilder::schemas_from_iter`] this allows adding multiple responses by + /// any iterator what returns tuples of (name, response) values. pub fn responses_from_iter< I: IntoIterator, S: Into, @@ -1041,6 +1058,7 @@ impl ObjectBuilder { self } + /// Add additional [`Schema`] for non specified fields (Useful for typed maps). pub fn additional_properties>>( mut self, additional_properties: Option, @@ -1613,11 +1631,13 @@ impl From for RefOr { impl ToArray for Array {} +/// This convenience trait allows quick way to wrap any `RefOr` with [`Array`] schema. pub trait ToArray where RefOr: From, Self: Sized, { + /// Wrap this `RefOr` with [`Array`]. fn to_array(self) -> Array { Array::new(self) } diff --git a/utoipa/src/openapi/security.rs b/utoipa/src/openapi/security.rs index aa5fffb7..8ad56a47 100644 --- a/utoipa/src/openapi/security.rs +++ b/utoipa/src/openapi/security.rs @@ -185,6 +185,7 @@ pub enum SecurityScheme { /// OpenApi 3.1 type #[serde(rename = "mutualTLS")] MutualTls { + #[allow(missing_docs)] #[serde(skip_serializing_if = "Option::is_none")] description: Option, }, @@ -343,6 +344,7 @@ impl HttpBuilder { #[derive(Serialize, Deserialize, Clone, PartialEq, Eq)] #[cfg_attr(feature = "debug", derive(Debug))] #[serde(rename_all = "lowercase")] +#[allow(missing_docs)] pub enum HttpAuthScheme { Basic, Bearer,