diff --git a/examples/openapi/auth-apikey/src/main.rs b/examples/openapi/auth-apikey/src/main.rs index 8275c57dda..76d9288be2 100644 --- a/examples/openapi/auth-apikey/src/main.rs +++ b/examples/openapi/auth-apikey/src/main.rs @@ -37,7 +37,6 @@ async fn api_checker(req: &Request, api_key: ApiKey) -> Option { } #[derive(Object)] -#[oai(inline)] struct LoginRequest { username: String, } diff --git a/examples/openapi/generics/Cargo.toml b/examples/openapi/generics/Cargo.toml new file mode 100644 index 0000000000..1d94417308 --- /dev/null +++ b/examples/openapi/generics/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "example-openapi-generics" +version = "0.1.0" +edition = "2021" +publish = false + +[dependencies] +poem = { path = "../../../poem" } +poem-openapi = { path = "../../../poem-openapi", features = ["swagger-ui"] } +tokio = { version = "1.17.0", features = ["macros", "rt-multi-thread"] } +tracing-subscriber = { version = "0.3.9", features = ["env-filter"] } diff --git a/examples/openapi/generics/src/main.rs b/examples/openapi/generics/src/main.rs new file mode 100644 index 0000000000..9be68c62cc --- /dev/null +++ b/examples/openapi/generics/src/main.rs @@ -0,0 +1,42 @@ +use poem::{listener::TcpListener, Route, Server}; +use poem_openapi::{ + payload::Json, + types::{ParseFromJSON, ToJSON}, + Object, OpenApi, OpenApiService, +}; + +#[derive(Object)] +struct MyObject { + value: T, +} + +struct Api; + +#[OpenApi] +impl Api { + #[oai(path = "/i32", method = "post")] + async fn i32(&self, value: Json>) -> Json> { + value + } + + #[oai(path = "/string", method = "post")] + async fn string(&self, value: Json>) -> Json> { + value + } +} + +#[tokio::main] +async fn main() -> Result<(), std::io::Error> { + if std::env::var_os("RUST_LOG").is_none() { + std::env::set_var("RUST_LOG", "poem=debug"); + } + tracing_subscriber::fmt::init(); + + let api_service = + OpenApiService::new(Api, "Hello World", "1.0").server("http://localhost:3000/api"); + let ui = api_service.swagger_ui(); + + Server::new(TcpListener::bind("127.0.0.1:3000")) + .run(Route::new().nest("/api", api_service).nest("/", ui)) + .await +} diff --git a/examples/openapi/uniform-response/src/main.rs b/examples/openapi/uniform-response/src/main.rs index 6bca4a0adc..bcea502232 100644 --- a/examples/openapi/uniform-response/src/main.rs +++ b/examples/openapi/uniform-response/src/main.rs @@ -18,7 +18,6 @@ struct Resource { } #[derive(Object)] -#[oai(inline)] struct ResponseObject { code: i32, msg: String, diff --git a/poem-openapi-derive/Cargo.toml b/poem-openapi-derive/Cargo.toml index be443c169e..de863af586 100644 --- a/poem-openapi-derive/Cargo.toml +++ b/poem-openapi-derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "poem-openapi-derive" -version = "1.3.30" +version = "2.0.0-alpha.1" authors = ["sunli "] edition = "2021" description = "Macros for poem-openapi" @@ -9,10 +9,7 @@ documentation = "https://docs.rs/poem/" homepage = "https://github.com/poem-web/poem" repository = "https://github.com/poem-web/poem" keywords = ["http", "async", "openapi", "swagger"] -categories = [ - "network-programming", - "asynchronous", -] +categories = ["network-programming", "asynchronous"] [lib] proc-macro = true @@ -25,7 +22,7 @@ quote = "1.0.9" syn = { version = "1.0.77", features = ["full", "visit-mut"] } Inflector = "0.11.4" thiserror = "1.0.29" -indexmap = "~1.6.2" # https://github.com/tkaitchuck/aHash/issues/95 +indexmap = "~1.6.2" # https://github.com/tkaitchuck/aHash/issues/95 regex = "1.5.5" http = "0.2.5" mime = "0.3.16" diff --git a/poem-openapi-derive/src/common_args.rs b/poem-openapi-derive/src/common_args.rs index 331936b824..b354cf6eda 100644 --- a/poem-openapi-derive/src/common_args.rs +++ b/poem-openapi-derive/src/common_args.rs @@ -46,14 +46,6 @@ impl RenameRuleExt for Option { } } -#[derive(FromMeta)] -pub(crate) struct ConcreteType { - pub(crate) name: String, - pub(crate) params: PathList, - #[darling(default)] - pub(crate) example: Option, -} - pub(crate) struct PathList(pub(crate) Vec); impl FromMeta for PathList { diff --git a/poem-openapi-derive/src/enum.rs b/poem-openapi-derive/src/enum.rs index 962fcf089d..bb8e04938a 100644 --- a/poem-openapi-derive/src/enum.rs +++ b/poem-openapi-derive/src/enum.rs @@ -144,11 +144,11 @@ pub(crate) fn generate(args: DeriveInput) -> GeneratorResult { } fn schema_ref() -> #crate_name::registry::MetaSchemaRef { - #crate_name::registry::MetaSchemaRef::Reference(#oai_typename) + #crate_name::registry::MetaSchemaRef::Reference(Self::name().into_owned()) } fn register(registry: &mut #crate_name::registry::Registry) { - registry.create_schema::(#oai_typename, |registry| #crate_name::registry::MetaSchema { + registry.create_schema::(Self::name().into_owned(), |registry| #crate_name::registry::MetaSchema { description: #description, external_docs: #external_docs, deprecated: #deprecated, diff --git a/poem-openapi-derive/src/object.rs b/poem-openapi-derive/src/object.rs index 2372c1c166..73b27ad6fe 100644 --- a/poem-openapi-derive/src/object.rs +++ b/poem-openapi-derive/src/object.rs @@ -1,16 +1,12 @@ -use darling::{ - ast::Data, - util::{Ignored, SpannedValue}, - FromDeriveInput, FromField, -}; +use darling::{ast::Data, util::Ignored, FromDeriveInput, FromField}; use proc_macro2::{Ident, TokenStream}; use quote::quote; -use syn::{ext::IdentExt, Attribute, DeriveInput, Error, GenericParam, Generics, Path, Type}; +use syn::{ext::IdentExt, Attribute, DeriveInput, Error, Generics, Path, Type}; use crate::{ - common_args::{ConcreteType, DefaultValue, ExternalDocument, RenameRule, RenameRuleExt}, + common_args::{DefaultValue, ExternalDocument, RenameRule, RenameRuleExt}, error::GeneratorResult, - utils::{get_crate_name, get_description, optional_literal}, + utils::{create_object_name, get_crate_name, get_description, optional_literal}, validators::Validators, }; @@ -55,13 +51,9 @@ struct ObjectArgs { #[darling(default)] internal: bool, #[darling(default)] - inline: SpannedValue, - #[darling(default)] rename: Option, #[darling(default)] rename_all: Option, - #[darling(default, multiple, rename = "concrete")] - concretes: Vec, #[darling(default)] deprecated: bool, #[darling(default)] @@ -69,8 +61,6 @@ struct ObjectArgs { #[darling(default)] write_only_all: bool, #[darling(default)] - example: Option>, - #[darling(default)] deny_unknown_fields: bool, #[darling(default)] external_docs: Option, @@ -103,35 +93,7 @@ pub(crate) fn generate(args: DeriveInput) -> GeneratorResult { let mut fields = Vec::new(); let mut meta_fields = Vec::new(); let mut required_fields = Vec::new(); - - if *args.inline && !args.concretes.is_empty() { - return Err(Error::new( - args.inline.span(), - "Inline objects cannot have the `concretes` attribute.", - ) - .into()); - } - - let is_generic_object = args - .generics - .params - .iter() - .any(|param| matches!(param, GenericParam::Type(_))); - if is_generic_object && !*args.inline && args.concretes.is_empty() { - return Err(Error::new( - args.ident.span(), - "Generic objects either specify the `inline` attribute, or specify a name for each concrete type using the `concretes` attribute.", - ) - .into()); - } - - if args.example.is_some() && !args.concretes.is_empty() { - return Err(Error::new( - args.example.as_ref().unwrap().span(), - "The example should be specified with the `concretes.example` attribute.", - ) - .into()); - } + let object_name = create_object_name(&crate_name, &oai_typename, &args.generics); for field in &s.fields { let field_ident = field @@ -351,182 +313,59 @@ pub(crate) fn generate(args: DeriveInput) -> GeneratorResult { None }; - let define_obj = if args.concretes.is_empty() { - let example = match &args.example { - Some(path) => { - let path = &**path; - quote! { ::to_json(&#path()) } - } - None => quote!(::std::option::Option::None), - }; + let define_obj = quote! { + impl #impl_generics #crate_name::types::Type for #ident #ty_generics #where_clause { + const IS_REQUIRED: bool = true; - let (fn_schema_ref, fn_register) = if *args.inline { - ( - quote!(#crate_name::registry::MetaSchemaRef::Inline(Box::new({ - let mut meta = #meta; - meta.example = #example; - meta - }))), - quote! { - #(#register_types)* - }, - ) - } else { - ( - quote!(#crate_name::registry::MetaSchemaRef::Reference(#oai_typename)), - quote! { - registry.create_schema::(#oai_typename, |registry| { - #(#register_types)* - let mut meta = #meta; - meta.example = #example; - meta - }) - }, - ) - }; + type RawValueType = Self; - quote! { - impl #impl_generics #crate_name::types::Type for #ident #ty_generics #where_clause { - const IS_REQUIRED: bool = true; + type RawElementValueType = Self; - type RawValueType = Self; - - type RawElementValueType = Self; - - fn name() -> ::std::borrow::Cow<'static, str> { - ::std::convert::Into::into(#oai_typename) - } - - fn schema_ref() -> #crate_name::registry::MetaSchemaRef { - #fn_schema_ref - } - - fn register(registry: &mut #crate_name::registry::Registry) { - #fn_register - } - - fn as_raw_value(&self) -> ::std::option::Option<&Self::RawValueType> { - ::std::option::Option::Some(self) - } - - fn raw_element_iter<'a>(&'a self) -> ::std::boxed::Box + 'a> { - ::std::boxed::Box::new(::std::iter::IntoIterator::into_iter(self.as_raw_value())) - } + fn name() -> ::std::borrow::Cow<'static, str> { + ::std::convert::Into::into(#object_name) } - impl #impl_generics #crate_name::types::ParseFromJSON for #ident #ty_generics #where_clause { - fn parse_from_json(value: ::std::option::Option<#crate_name::__private::serde_json::Value>) -> ::std::result::Result> { - let value = value.unwrap_or_default(); - match value { - #crate_name::__private::serde_json::Value::Object(mut obj) => { - #(#deserialize_fields)* - #deny_unknown_fields - ::std::result::Result::Ok(Self { #(#fields),* }) - } - _ => ::std::result::Result::Err(#crate_name::types::ParseError::expected_type(value)), - } - } + fn schema_ref() -> #crate_name::registry::MetaSchemaRef { + #crate_name::registry::MetaSchemaRef::Reference(Self::name().into_owned()) } - impl #impl_generics #crate_name::types::ToJSON for #ident #ty_generics #where_clause { - fn to_json(&self) -> ::std::option::Option<#crate_name::__private::serde_json::Value> { - let mut object = #crate_name::__private::serde_json::Map::new(); - #(#serialize_fields)* - ::std::option::Option::Some(#crate_name::__private::serde_json::Value::Object(object)) - } - } - } - } else { - let mut code = Vec::new(); - - code.push(quote! { - impl #impl_generics #ident #ty_generics #where_clause { - fn __internal_create_schema(registry: &mut #crate_name::registry::Registry) -> #crate_name::registry::MetaSchema - where - Self: #crate_name::types::Type - { + fn register(registry: &mut #crate_name::registry::Registry) { + registry.create_schema::(Self::name().into_owned(), |registry| { #(#register_types)* #meta - } - - fn __internal_parse_from_json(value: ::std::option::Option<#crate_name::__private::serde_json::Value>) -> ::std::result::Result> where Self: #crate_name::types::Type { - let value = value.unwrap_or_default(); - match value { - #crate_name::__private::serde_json::Value::Object(mut obj) => { - #(#deserialize_fields)* - #deny_unknown_fields - ::std::result::Result::Ok(Self { #(#fields),* }) - } - _ => ::std::result::Result::Err(#crate_name::types::ParseError::expected_type(value)), - } - } - - fn __internal_to_json(&self) -> #crate_name::__private::serde_json::Value where Self: #crate_name::types::Type { - let mut object = ::serde_json::Map::new(); - #(#serialize_fields)* - #crate_name::__private::serde_json::Value::Object(object) - } + }) } - }); - - for concrete in &args.concretes { - let oai_typename = &concrete.name; - let params = &concrete.params.0; - let concrete_type = quote! { #ident<#(#params),*> }; - let example = match &concrete.example { - Some(path) => { - quote! { ::to_json(&#path()) } - } - None => quote!(::std::option::Option::None), - }; - - let expanded = quote! { - impl #crate_name::types::Type for #concrete_type { - const IS_REQUIRED: bool = true; - - type RawValueType = Self; - type RawElementValueType = Self; - - fn name() -> ::std::borrow::Cow<'static, str> { - ::std::convert::Into::into(#oai_typename) - } - - fn as_raw_value(&self) -> Option<&Self::RawValueType> { - ::std::option::Option::Some(self) - } - - fn schema_ref() -> #crate_name::registry::MetaSchemaRef { - #crate_name::registry::MetaSchemaRef::Reference(#oai_typename) - } - - fn register(registry: &mut #crate_name::registry::Registry) { - let mut meta = Self::__internal_create_schema(registry); - meta.example = #example; - registry.create_schema::(#oai_typename, move |registry| meta); - } - - fn raw_element_iter<'a>(&'a self) -> ::std::boxed::Box + 'a> { - ::std::boxed::Box::new(::std::iter::IntoIterator::into_iter(self.as_raw_value())) - } - } + fn as_raw_value(&self) -> ::std::option::Option<&Self::RawValueType> { + ::std::option::Option::Some(self) + } - impl #crate_name::types::ParseFromJSON for #concrete_type { - fn parse_from_json(value: ::std::option::Option<#crate_name::__private::serde_json::Value>) -> ::std::result::Result> { - Self::__internal_parse_from_json(value) - } - } + fn raw_element_iter<'a>(&'a self) -> ::std::boxed::Box + 'a> { + ::std::boxed::Box::new(::std::iter::IntoIterator::into_iter(self.as_raw_value())) + } + } - impl #crate_name::types::ToJSON for #concrete_type { - fn to_json(&self) -> ::std::option::Option<#crate_name::__private::serde_json::Value> { - ::std::option::Option::Some(Self::__internal_to_json(self)) + impl #impl_generics #crate_name::types::ParseFromJSON for #ident #ty_generics #where_clause { + fn parse_from_json(value: ::std::option::Option<#crate_name::__private::serde_json::Value>) -> ::std::result::Result> { + let value = value.unwrap_or_default(); + match value { + #crate_name::__private::serde_json::Value::Object(mut obj) => { + #(#deserialize_fields)* + #deny_unknown_fields + ::std::result::Result::Ok(Self { #(#fields),* }) } + _ => ::std::result::Result::Err(#crate_name::types::ParseError::expected_type(value)), } - }; - code.push(expanded); + } } - quote!(#(#code)*) + impl #impl_generics #crate_name::types::ToJSON for #ident #ty_generics #where_clause { + fn to_json(&self) -> ::std::option::Option<#crate_name::__private::serde_json::Value> { + let mut object = #crate_name::__private::serde_json::Map::new(); + #(#serialize_fields)* + ::std::option::Option::Some(#crate_name::__private::serde_json::Value::Object(object)) + } + } }; // remote @@ -545,6 +384,7 @@ pub(crate) fn generate(args: DeriveInput) -> GeneratorResult { } } + #[allow(clippy::from_over_into)] impl #impl_generics ::std::convert::Into<#remote> for #ident #ty_generics #where_clause { fn into(self) -> #remote { #remote { diff --git a/poem-openapi-derive/src/union.rs b/poem-openapi-derive/src/union.rs index 486ac22118..6c28ef72ec 100644 --- a/poem-openapi-derive/src/union.rs +++ b/poem-openapi-derive/src/union.rs @@ -1,16 +1,16 @@ use darling::{ ast::{Data, Fields}, - util::{Ignored, SpannedValue}, + util::Ignored, FromDeriveInput, FromVariant, }; use proc_macro2::{Ident, TokenStream}; use quote::quote; -use syn::{Attribute, DeriveInput, Error, GenericParam, Generics, Type}; +use syn::{Attribute, DeriveInput, Error, Generics, Type}; use crate::{ - common_args::{ConcreteType, ExternalDocument}, + common_args::ExternalDocument, error::GeneratorResult, - utils::{get_crate_name, get_description, optional_literal}, + utils::{create_object_name, get_crate_name, get_description, optional_literal}, }; #[derive(FromVariant)] @@ -34,8 +34,6 @@ struct UnionArgs { #[darling(default)] internal: bool, #[darling(default)] - inline: SpannedValue, - #[darling(default)] rename: Option, #[darling(default)] one_of: bool, @@ -43,8 +41,6 @@ struct UnionArgs { discriminator_name: Option, #[darling(default)] external_docs: Option, - #[darling(default, multiple, rename = "concrete")] - concretes: Vec, } pub(crate) fn generate(args: DeriveInput) -> GeneratorResult { @@ -62,27 +58,6 @@ pub(crate) fn generate(args: DeriveInput) -> GeneratorResult { _ => return Err(Error::new_spanned(ident, "AnyOf can only be applied to an enum.").into()), }; - if *args.inline && !args.concretes.is_empty() { - return Err(Error::new( - args.inline.span(), - "Inline objects cannot have the `concretes` attribute.", - ) - .into()); - } - - let is_generic_union = args - .generics - .params - .iter() - .any(|param| matches!(param, GenericParam::Type(_))); - if is_generic_union && !*args.inline && args.concretes.is_empty() { - return Err(Error::new( - args.ident.span(), - "Generic objects either specify the `inline` attribute, or specify a name for each concrete type using the `concretes` attribute.", - ) - .into()); - } - let mut types = Vec::new(); let mut from_json = Vec::new(); let mut to_json = Vec::new(); @@ -94,6 +69,7 @@ pub(crate) fn generate(args: DeriveInput) -> GeneratorResult { Some(discriminator_name) => quote!(::std::vec![#discriminator_name]), None => quote!(::std::vec![]), }; + let object_name = create_object_name(&crate_name, &oai_typename, &args.generics); for variant in e { let item_ident = &variant.ident; @@ -108,7 +84,6 @@ pub(crate) fn generate(args: DeriveInput) -> GeneratorResult { } }; names.push(quote!(#mapping_name)); - types.push(object_ty); if discriminator_name.is_some() { @@ -268,151 +243,52 @@ pub(crate) fn generate(args: DeriveInput) -> GeneratorResult { } }; - let (fn_schema_ref, fn_register) = if *args.inline { - let fn_schema_ref = - quote! { #crate_name::registry::MetaSchemaRef::Inline(Box::new(#meta)) }; - let fn_register = quote! { #(<#types as #crate_name::types::Type>::register(registry);)* }; - (fn_schema_ref, fn_register) - } else { - let fn_schema_ref = - quote! { #crate_name::registry::MetaSchemaRef::Reference(#oai_typename) }; - let fn_register = quote! { - registry.create_schema::(#oai_typename, |registry| { - #(<#types as #crate_name::types::Type>::register(registry);)* - #meta - }); - }; - (fn_schema_ref, fn_register) - }; - - let expanded = if args.concretes.is_empty() { - quote! { - impl #impl_generics #crate_name::types::Type for #ident #ty_generics #where_clause { - const IS_REQUIRED: bool = true; + let expanded = quote! { + impl #impl_generics #crate_name::types::Type for #ident #ty_generics #where_clause { + const IS_REQUIRED: bool = true; - type RawValueType = Self; + type RawValueType = Self; - type RawElementValueType = Self; - - fn name() -> ::std::borrow::Cow<'static, str> { - ::std::convert::Into::into("object") - } + type RawElementValueType = Self; - fn schema_ref() -> #crate_name::registry::MetaSchemaRef { - #fn_schema_ref - } - - fn register(registry: &mut #crate_name::registry::Registry) { - #fn_register - } - - fn as_raw_value(&self) -> ::std::option::Option<&Self::RawValueType> { - ::std::option::Option::Some(self) - } - - fn raw_element_iter<'a>(&'a self) -> ::std::boxed::Box + 'a> { - ::std::boxed::Box::new(::std::iter::IntoIterator::into_iter(self.as_raw_value())) - } + fn name() -> ::std::borrow::Cow<'static, str> { + ::std::convert::Into::into(#object_name) } - impl #impl_generics #crate_name::types::ParseFromJSON for #ident #ty_generics #where_clause { - fn parse_from_json(value: ::std::option::Option<#crate_name::__private::serde_json::Value>) -> ::std::result::Result> { - let value = value.unwrap_or_default(); - #parse_from_json - } + fn schema_ref() -> #crate_name::registry::MetaSchemaRef { + #crate_name::registry::MetaSchemaRef::Reference(Self::name().into_owned()) } - impl #impl_generics #crate_name::types::ToJSON for #ident #ty_generics #where_clause { - fn to_json(&self) -> ::std::option::Option<#crate_name::__private::serde_json::Value> { - match self { - #(#to_json),* - } - } - } - } - } else { - let mut code = Vec::new(); - - code.push(quote! { - impl #impl_generics #ident #ty_generics #where_clause { - fn __internal_create_schema(registry: &mut #crate_name::registry::Registry) -> #crate_name::registry::MetaSchema - where - Self: #crate_name::types::Type - { + fn register(registry: &mut #crate_name::registry::Registry) { + registry.create_schema::(Self::name().into_owned(), |registry| { #(<#types as #crate_name::types::Type>::register(registry);)* #meta - } - - fn __internal_parse_from_json(value: ::std::option::Option<#crate_name::__private::serde_json::Value>) -> ::std::result::Result> - where - Self: #crate_name::types::Type - { - let value = value.unwrap_or_default(); - #parse_from_json - } - - fn __internal_to_json(&self) -> ::std::option::Option<#crate_name::__private::serde_json::Value> - where - Self: #crate_name::types::Type - { - match self { - #(#to_json),* - } - } + }); } - }); - - for concrete in &args.concretes { - let oai_typename = &concrete.name; - let params = &concrete.params.0; - let concrete_type = quote! { #ident<#(#params),*> }; - - let expanded = quote! { - impl #crate_name::types::Type for #concrete_type { - const IS_REQUIRED: bool = true; - - type RawValueType = Self; - - type RawElementValueType = Self; - - fn name() -> ::std::borrow::Cow<'static, str> { - ::std::convert::Into::into(#oai_typename) - } - - fn as_raw_value(&self) -> Option<&Self::RawValueType> { - ::std::option::Option::Some(self) - } - - fn schema_ref() -> #crate_name::registry::MetaSchemaRef { - #crate_name::registry::MetaSchemaRef::Reference(#oai_typename) - } - fn register(registry: &mut #crate_name::registry::Registry) { - let mut meta = Self::__internal_create_schema(registry); - registry.create_schema::(#oai_typename, move |registry| meta); - } + fn as_raw_value(&self) -> ::std::option::Option<&Self::RawValueType> { + ::std::option::Option::Some(self) + } - fn raw_element_iter<'a>(&'a self) -> ::std::boxed::Box + 'a> { - ::std::boxed::Box::new(::std::iter::IntoIterator::into_iter(self.as_raw_value())) - } - } + fn raw_element_iter<'a>(&'a self) -> ::std::boxed::Box + 'a> { + ::std::boxed::Box::new(::std::iter::IntoIterator::into_iter(self.as_raw_value())) + } + } - impl #crate_name::types::ParseFromJSON for #concrete_type { - fn parse_from_json(value: ::std::option::Option<#crate_name::__private::serde_json::Value>) -> ::std::result::Result> { - Self::__internal_parse_from_json(value) - } - } + impl #impl_generics #crate_name::types::ParseFromJSON for #ident #ty_generics #where_clause { + fn parse_from_json(value: ::std::option::Option<#crate_name::__private::serde_json::Value>) -> ::std::result::Result> { + let value = value.unwrap_or_default(); + #parse_from_json + } + } - impl #crate_name::types::ToJSON for #concrete_type { - fn to_json(&self) -> ::std::option::Option<#crate_name::__private::serde_json::Value> { - Self::__internal_to_json(self) - } + impl #impl_generics #crate_name::types::ToJSON for #ident #ty_generics #where_clause { + fn to_json(&self) -> ::std::option::Option<#crate_name::__private::serde_json::Value> { + match self { + #(#to_json),* } - }; - code.push(expanded); + } } - - quote!(#(#code)*) }; Ok(expanded) diff --git a/poem-openapi-derive/src/utils.rs b/poem-openapi-derive/src/utils.rs index fb8d76c11d..b8fe40c67c 100644 --- a/poem-openapi-derive/src/utils.rs +++ b/poem-openapi-derive/src/utils.rs @@ -4,7 +4,10 @@ use darling::{util::SpannedValue, FromMeta}; use proc_macro2::{Ident, Span, TokenStream}; use proc_macro_crate::{crate_name, FoundCrate}; use quote::quote; -use syn::{visit_mut, visit_mut::VisitMut, Attribute, Error, Lifetime, Lit, Meta, Result}; +use syn::{ + visit_mut, visit_mut::VisitMut, Attribute, Error, GenericParam, Generics, Lifetime, Lit, Meta, + Result, +}; use crate::error::GeneratorResult; @@ -178,3 +181,41 @@ impl VisitMut for RemoveLifetime { visit_mut::visit_lifetime_mut(self, i); } } + +pub(crate) fn create_object_name( + crate_name: &TokenStream, + name: &str, + generics: &Generics, +) -> TokenStream { + let types = generics + .params + .iter() + .filter_map(|param| match param { + GenericParam::Type(ty) => Some(&ty.ident), + _ => None, + }) + .collect::>(); + + if types.is_empty() { + quote!({ + use ::std::convert::From; + ::std::string::String::from(#name) + }) + } else { + let (first, tail) = types.split_first().unwrap(); + quote!({ + use ::std::convert::From; + let mut name = ::std::string::String::from(#name); + + name.push('<'); + name.push_str(&<#first as #crate_name::types::Type>::name()); + #( + name.push_str(", "); + name.push_str(&<#tail as #crate_name::types::Type>::name()); + )* + name.push('>'); + + name + }) + } +} diff --git a/poem-openapi/CHANGELOG.md b/poem-openapi/CHANGELOG.md index 3599b33172..c22bb797c7 100644 --- a/poem-openapi/CHANGELOG.md +++ b/poem-openapi/CHANGELOG.md @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +# [2.0.0-alpha.1] 2022-05-15 + +- Remove `inline` and `concrete` attributes of `Object` and `Union` macros, now automatically generate reference names for generic objects. + # [1.3.28] 2022-04-16 - If the `inline` or `concretes` attribute of the generic object is not specified, the exact error will be reported at compile time. diff --git a/poem-openapi/Cargo.toml b/poem-openapi/Cargo.toml index 2ad2092bd4..42d18f7af9 100644 --- a/poem-openapi/Cargo.toml +++ b/poem-openapi/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "poem-openapi" -version = "1.3.30" +version = "2.0.0-alpha.1" authors = ["sunli "] edition = "2021" description = "OpenAPI support for Poem." @@ -21,7 +21,7 @@ hostname = ["hostname-validator"] static-files = ["poem/static-files"] [dependencies] -poem-openapi-derive = { path = "../poem-openapi-derive", version = "1.3.30" } +poem-openapi-derive = { path = "../poem-openapi-derive", version = "2.0.0-alpha.1" } poem = { path = "../poem", version = "1.3.30", features = [ "multipart", "tempfile", diff --git a/poem-openapi/src/docs/enum.md b/poem-openapi/src/docs/enum.md index 7e5303cc98..b478eb7d6d 100644 --- a/poem-openapi/src/docs/enum.md +++ b/poem-openapi/src/docs/enum.md @@ -12,9 +12,9 @@ Define a OpenAPI enum # Item parameters -| Attribute | description | Type | Optional | -|-------------|---------------------------|----------|----------| -| rename | Rename the item | string | Y | +| Attribute | description | Type | Optional | +|-----------|-----------------|--------|----------| +| rename | Rename the item | string | Y | # Examples diff --git a/poem-openapi/src/docs/multipart.md b/poem-openapi/src/docs/multipart.md index 1610bb4ca5..0e152bb12d 100644 --- a/poem-openapi/src/docs/multipart.md +++ b/poem-openapi/src/docs/multipart.md @@ -2,9 +2,9 @@ Define a OpenAPI payload. # Macro parameters -| Attribute | description | Type | Optional | -|---------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------|----------| -| rename_all | Rename all the fields according to the given case convention. The possible values are "lowercase", "UPPERCASE", "PascalCase", "camelCase", "snake_case", "SCREAMING_SNAKE_CASE". | string | Y | +| Attribute | description | Type | Optional | +|------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------|----------| +| rename_all | Rename all the fields according to the given case convention. The possible values are "lowercase", "UPPERCASE", "PascalCase", "camelCase", "snake_case", "SCREAMING_SNAKE_CASE". | string | Y | | deny_unknown_fields | Always error during parsing when encountering unknown fields. | bool Y | # Field parameters diff --git a/poem-openapi/src/docs/object.md b/poem-openapi/src/docs/object.md index 1ced74cc37..f2550555ef 100644 --- a/poem-openapi/src/docs/object.md +++ b/poem-openapi/src/docs/object.md @@ -2,21 +2,19 @@ Define a OpenAPI object # Macro parameters -| Attribute | description | Type | Optional | -|------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------|----------| -| rename | Rename the object | string | Y | -| rename_all | Rename all the fields according to the given case convention. The possible values are "lowercase", "UPPERCASE", "PascalCase", "camelCase", "snake_case", "SCREAMING_SNAKE_CASE". | string | Y | -| inline | Generate inline object. | bool | Y | -| concretes | Specify how the concrete type of the generic Schema should be implemented. | ConcreteType | Y | -| deprecated | Schema deprecated | bool | Y | -| read_only_all | Set all fields openapi readOnly property | bool | Y | -| write_only_all | Set all fields openapi writeOnly property | bool | Y | -| deny_unknown_fields | Always error during parsing when encountering unknown fields. | bool | Y | -| example | Specify a function to create an example object. | string | Y | -| external_docs | Specify a external resource for extended documentation | string | Y | -| remote | Derive a remote object | string | Y | -| skip_serializing_if_is_none | Skip serializing field if the value is none. | bool | Y | -| skip_serializing_if_is_empty | Skip serializing field if the value is empty. | bool | Y | +| Attribute | description | Type | Optional | +|------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------|----------| +| rename | Rename the object | string | Y | +| rename_all | Rename all the fields according to the given case convention. The possible values are "lowercase", "UPPERCASE", "PascalCase", "camelCase", "snake_case", "SCREAMING_SNAKE_CASE". | string | Y | +| deprecated | Schema deprecated | bool | Y | +| read_only_all | Set all fields openapi readOnly property | bool | Y | +| write_only_all | Set all fields openapi writeOnly property | bool | Y | +| deny_unknown_fields | Always error during parsing when encountering unknown fields. | bool | Y | +| example | Specify a function to create an example object. | string | Y | +| external_docs | Specify a external resource for extended documentation | string | Y | +| remote | Derive a remote object | string | Y | +| skip_serializing_if_is_none | Skip serializing field if the value is none. | bool | Y | +| skip_serializing_if_is_empty | Skip serializing field if the value is empty. | bool | Y | # Field parameters diff --git a/poem-openapi/src/docs/response.md b/poem-openapi/src/docs/response.md index a451e967fd..f468c2ab7e 100644 --- a/poem-openapi/src/docs/response.md +++ b/poem-openapi/src/docs/response.md @@ -23,12 +23,12 @@ Define a OpenAPI response. # Extra header parameters -| Attribute | description | Type | Optional | -|---------------|--------------------------------------------------------|--------|----------| -| name | Header name | String | N | -| type | Header type | String | N | -| description | Header description | String | Y | -| deprecated | Header deprecated | bool | Y | +| Attribute | description | Type | Optional | +|-------------|--------------------|--------|----------| +| name | Header name | String | N | +| type | Header type | String | N | +| description | Header description | String | Y | +| deprecated | Header deprecated | bool | Y | # Example response headers diff --git a/poem-openapi/src/docs/tags.md b/poem-openapi/src/docs/tags.md index 9a0ef3bbbe..cc9033204f 100644 --- a/poem-openapi/src/docs/tags.md +++ b/poem-openapi/src/docs/tags.md @@ -8,9 +8,9 @@ Define a OpenAPI Tags. # Item parameters -| Attribute | | description | Type | Optional | -|---------------|:-------------------------------------------------------|---------------------|--------|----------| -| rename | | Rename the tag name | string | Y | +| Attribute | | description | Type | Optional | +|-----------|---|---------------------|--------|----------| +| rename | | Rename the tag name | string | Y | | external_docs | Specify a external resource for extended documentation | string | Y | # Examples diff --git a/poem-openapi/src/docs/webhook.md b/poem-openapi/src/docs/webhook.md index c033580e6c..a8195c56fb 100644 --- a/poem-openapi/src/docs/webhook.md +++ b/poem-openapi/src/docs/webhook.md @@ -2,15 +2,15 @@ Define a OpenApi webhooks. # Macro parameters -| Attribute | description | Type | Optional | -|-------------|-------------------------------------------|--------|----------| -| tag | Define a tag for all operations. | string | Y | +| Attribute | description | Type | Optional | +|-----------|----------------------------------|--------|----------| +| tag | Define a tag for all operations. | string | Y | # Operation parameters | Attribute | description | Type | Optional | |---------------|----------------------------------------------------------------------------------------------------------------------|--------|----------| -| name | The key name of the webhook operation | bool | Y | +| name | The key name of the webhook operation | bool | Y | | method | HTTP method. The possible values are "get", "post", "put", "delete", "head", "options", "connect", "patch", "trace". | string | N | | deprecated | Operation deprecated | bool | Y | | external_docs | Specify a external resource for extended documentation | string | Y | diff --git a/poem-openapi/src/registry/clean_unused.rs b/poem-openapi/src/registry/clean_unused.rs index f14dcd4a7f..600e10036c 100644 --- a/poem-openapi/src/registry/clean_unused.rs +++ b/poem-openapi/src/registry/clean_unused.rs @@ -2,19 +2,19 @@ use std::collections::BTreeSet; use crate::registry::{Document, MetaMediaType, MetaOperation, MetaSchemaRef}; -type UsedTypes = BTreeSet<&'static str>; +type UsedTypes = BTreeSet; impl<'a> Document<'a> { - fn traverse_schema(&self, used_types: &mut UsedTypes, schema_ref: &MetaSchemaRef) { + fn traverse_schema(&self, used_types: &mut UsedTypes, schema_ref: &'a MetaSchemaRef) { let schema = match schema_ref { MetaSchemaRef::Reference(name) => { - if used_types.contains(name) { + if used_types.contains(name.as_str()) { return; } - used_types.insert(*name); + used_types.insert(name.clone()); self.registry .schemas - .get(name) + .get(name.as_str()) .unwrap_or_else(|| panic!("Schema `{}` does not registered", name)) } MetaSchemaRef::Inline(schema) => schema, @@ -45,13 +45,13 @@ impl<'a> Document<'a> { } } - fn traverse_media_types(&self, used_types: &mut UsedTypes, meta_types: &[MetaMediaType]) { + fn traverse_media_types(&self, used_types: &mut UsedTypes, meta_types: &'a [MetaMediaType]) { for meta_type in meta_types { self.traverse_schema(used_types, &meta_type.schema); } } - fn traverse_operation(&self, used_types: &mut UsedTypes, operation: &MetaOperation) { + fn traverse_operation(&self, used_types: &mut UsedTypes, operation: &'a MetaOperation) { for param in &operation.params { self.traverse_schema(used_types, ¶m.schema); } @@ -84,7 +84,7 @@ impl<'a> Document<'a> { .registry .schemas .keys() - .copied() + .cloned() .collect::>(); for name in all_schemas.difference(&used_types).collect::>() { self.registry.schemas.remove(name); diff --git a/poem-openapi/src/registry/mod.rs b/poem-openapi/src/registry/mod.rs index 0250b7d3b5..0938b8e892 100644 --- a/poem-openapi/src/registry/mod.rs +++ b/poem-openapi/src/registry/mod.rs @@ -295,7 +295,7 @@ impl MetaSchema { #[derive(Debug, Clone, PartialEq)] pub enum MetaSchemaRef { Inline(Box), - Reference(&'static str), + Reference(String), } impl MetaSchemaRef { @@ -314,7 +314,7 @@ impl MetaSchemaRef { } } - pub fn unwrap_reference(&self) -> &'static str { + pub fn unwrap_reference(&self) -> &str { match self { MetaSchemaRef::Inline(_) => panic!(), MetaSchemaRef::Reference(name) => name, @@ -631,7 +631,7 @@ pub struct MetaApi { #[derive(Default)] pub struct Registry { - pub schemas: BTreeMap<&'static str, MetaSchema>, + pub schemas: BTreeMap, pub tags: BTreeSet, pub security_schemes: BTreeMap<&'static str, MetaSecurityScheme>, } @@ -641,11 +641,11 @@ impl Registry { Default::default() } - pub fn create_schema(&mut self, name: &'static str, f: F) + pub fn create_schema(&mut self, name: String, f: F) where F: FnOnce(&mut Registry) -> MetaSchema, { - match self.schemas.get(name) { + match self.schemas.get(&name) { Some(schema) => { if let Some(prev_typename) = schema.rust_typename { if prev_typename != std::any::type_name::() { @@ -661,10 +661,10 @@ impl Registry { None => { // Inserting a fake type before calling the function allows recursive types to // exist. - self.schemas.insert(name, MetaSchema::new("fake")); + self.schemas.insert(name.clone(), MetaSchema::new("fake")); let mut meta_schema = f(self); meta_schema.rust_typename = Some(std::any::type_name::()); - *self.schemas.get_mut(name).unwrap() = meta_schema; + *self.schemas.get_mut(&name).unwrap() = meta_schema; } } } @@ -675,7 +675,7 @@ impl Registry { MetaSchemaRef::Reference(name) => { T::register(self); self.schemas - .get(name) + .get(&name) .cloned() .expect("You definitely encountered a bug!") } diff --git a/poem-openapi/src/registry/ser.rs b/poem-openapi/src/registry/ser.rs index b3ff49730b..eb5d2fc330 100644 --- a/poem-openapi/src/registry/ser.rs +++ b/poem-openapi/src/registry/ser.rs @@ -87,7 +87,7 @@ impl<'a> Serialize for Document<'a> { #[derive(Serialize)] #[serde(rename_all = "camelCase")] struct Components<'a> { - schemas: &'a BTreeMap<&'static str, MetaSchema>, + schemas: &'a BTreeMap, #[serde(skip_serializing_if = "BTreeMap::is_empty")] security_schemes: &'a BTreeMap<&'static str, MetaSecurityScheme>, } diff --git a/poem-openapi/src/types/external/btreemap.rs b/poem-openapi/src/types/external/btreemap.rs index 6a902eaad2..1d8db7deeb 100644 --- a/poem-openapi/src/types/external/btreemap.rs +++ b/poem-openapi/src/types/external/btreemap.rs @@ -19,7 +19,7 @@ where type RawElementValueType = V::RawValueType; fn name() -> Cow<'static, str> { - "object".into() + format!("map", V::name()).into() } fn schema_ref() -> MetaSchemaRef { diff --git a/poem-openapi/src/types/external/hashmap.rs b/poem-openapi/src/types/external/hashmap.rs index 821b85687e..f2ecee01b5 100644 --- a/poem-openapi/src/types/external/hashmap.rs +++ b/poem-openapi/src/types/external/hashmap.rs @@ -19,7 +19,7 @@ where type RawElementValueType = V::RawValueType; fn name() -> Cow<'static, str> { - "object".into() + format!("map", V::name()).into() } fn schema_ref() -> MetaSchemaRef { diff --git a/poem-openapi/src/types/external/optional.rs b/poem-openapi/src/types/external/optional.rs index 16a05dc453..1c7b90fb9f 100644 --- a/poem-openapi/src/types/external/optional.rs +++ b/poem-openapi/src/types/external/optional.rs @@ -19,7 +19,7 @@ impl Type for Option { type RawElementValueType = T::RawElementValueType; fn name() -> Cow<'static, str> { - T::name() + format!("optional<{}>", T::name()).into() } fn schema_ref() -> MetaSchemaRef { diff --git a/poem-openapi/src/types/maybe_undefined.rs b/poem-openapi/src/types/maybe_undefined.rs index 98f48d42ea..4d2beb54f4 100644 --- a/poem-openapi/src/types/maybe_undefined.rs +++ b/poem-openapi/src/types/maybe_undefined.rs @@ -316,7 +316,7 @@ impl Type for MaybeUndefined { type RawElementValueType = T::RawElementValueType; fn name() -> Cow<'static, str> { - T::name() + format!("optional<{}>", T::name()).into() } fn schema_ref() -> MetaSchemaRef { diff --git a/poem-openapi/tests/enum.rs b/poem-openapi/tests/enum.rs index 467a458a6e..2e83809fc8 100644 --- a/poem-openapi/tests/enum.rs +++ b/poem-openapi/tests/enum.rs @@ -35,7 +35,10 @@ fn rename() { MyEnum::register(&mut registry); let meta = registry.schemas.remove("AAA").unwrap(); assert_eq!(meta.ty, "string"); - assert_eq!(MyEnum::schema_ref(), MetaSchemaRef::Reference("AAA")); + assert_eq!( + MyEnum::schema_ref(), + MetaSchemaRef::Reference("AAA".to_string()) + ); } #[test] diff --git a/poem-openapi/tests/multipart.rs b/poem-openapi/tests/multipart.rs index 304fe550e5..3066687dbf 100644 --- a/poem-openapi/tests/multipart.rs +++ b/poem-openapi/tests/multipart.rs @@ -467,7 +467,7 @@ fn inline_field() { let meta_inner_obj = schema.properties[0].1.unwrap_inline(); assert_eq!( meta_inner_obj.all_of[0], - MetaSchemaRef::Reference("InlineObj") + MetaSchemaRef::Reference("InlineObj".to_string()) ); assert_eq!( meta_inner_obj.all_of[1], @@ -483,7 +483,7 @@ fn inline_field() { let meta_inner_enum = schema.properties[1].1.unwrap_inline(); assert_eq!( meta_inner_enum.all_of[0], - MetaSchemaRef::Reference("InlineEnum") + MetaSchemaRef::Reference("InlineEnum".to_string()) ); assert_eq!( meta_inner_enum.all_of[1], diff --git a/poem-openapi/tests/object.rs b/poem-openapi/tests/object.rs index 3917f920f0..f8a78a66e1 100644 --- a/poem-openapi/tests/object.rs +++ b/poem-openapi/tests/object.rs @@ -36,18 +36,17 @@ fn rename_all() { } #[test] -fn concretes() { +fn generics() { #[derive(Object)] - #[oai( - concrete(name = "Obj_i32_i64", params(i32, i64)), - concrete(name = "Obj_f32_f64", params(f32, f64)) - )] struct Obj { create_user: T1, delete_user: T2, } - assert_eq!(>::name(), "Obj_i32_i64"); + assert_eq!( + >::name(), + "Obj" + ); let meta = get_meta::>(); assert_eq!(meta.properties[0].1.unwrap_inline().ty, "integer"); assert_eq!(meta.properties[0].1.unwrap_inline().format, Some("int32")); @@ -55,7 +54,10 @@ fn concretes() { assert_eq!(meta.properties[1].1.unwrap_inline().ty, "integer"); assert_eq!(meta.properties[1].1.unwrap_inline().format, Some("int64")); - assert_eq!(>::name(), "Obj_f32_f64"); + assert_eq!( + >::name(), + "Obj" + ); let meta = get_meta::>(); assert_eq!(meta.properties[0].1.unwrap_inline().ty, "number"); assert_eq!(meta.properties[0].1.unwrap_inline().format, Some("float")); @@ -336,7 +338,6 @@ fn serde() { #[test] fn serde_generic() { #[derive(Object, Debug, Eq, PartialEq)] - #[oai(concrete(name = "Obj", params(i32)))] struct Obj { a: T, } @@ -460,7 +461,7 @@ fn inline_fields() { let meta_inner_obj = meta.properties[0].1.unwrap_inline(); assert_eq!( meta_inner_obj.all_of[0], - MetaSchemaRef::Reference("InlineObj") + MetaSchemaRef::Reference("InlineObj".to_string()) ); assert_eq!( meta_inner_obj.all_of[1], @@ -476,7 +477,7 @@ fn inline_fields() { let meta_inner_enum = meta.properties[1].1.unwrap_inline(); assert_eq!( meta_inner_enum.all_of[0], - MetaSchemaRef::Reference("InlineEnum") + MetaSchemaRef::Reference("InlineEnum".to_string()) ); assert_eq!( meta_inner_enum.all_of[1], @@ -488,30 +489,6 @@ fn inline_fields() { ); } -#[test] -fn inline() { - #[derive(Object)] - #[oai(inline)] - struct Obj { - a: i32, - } - - let schema_ref = Obj::schema_ref(); - let meta: &MetaSchema = schema_ref.unwrap_inline(); - assert_eq!(meta.properties[0].0, "a"); - - #[derive(Object)] - #[oai(inline)] - struct ObjGeneric { - a: T, - } - - let schema_ref = ObjGeneric::::schema_ref(); - let meta: &MetaSchema = schema_ref.unwrap_inline(); - assert_eq!(meta.properties[0].0, "a"); - assert_eq!(meta.properties[0].1.unwrap_inline().ty, "string"); -} - #[test] #[should_panic] fn duplicate_name() { @@ -534,79 +511,6 @@ fn duplicate_name() { t::ObjA::register(&mut registry); } -#[test] -fn example() { - #[derive(Object)] - #[oai(example = "obj_example")] - struct Obj { - a: i32, - b: String, - } - - fn obj_example() -> Obj { - Obj { - a: 100, - b: "abc".to_string(), - } - } - - let meta = get_meta::(); - assert_eq!( - meta.example, - Some(json!({ - "a": 100, - "b": "abc", - })) - ); -} - -#[test] -fn concrete_types() { - #[derive(Object)] - #[oai( - concrete( - name = "Obj_i32_i64", - params(i32, i64), - example = "obj_i32_i64_example" - ), - concrete( - name = "Obj_f32_f64", - params(f32, f64), - example = "obj_f32_f64_example" - ) - )] - struct Obj { - a: T1, - b: T2, - } - - fn obj_i32_i64_example() -> Obj { - Obj { a: 100, b: 200 } - } - - fn obj_f32_f64_example() -> Obj { - Obj { a: 32.5, b: 72.5 } - } - - let meta = get_meta::>(); - assert_eq!( - meta.example, - Some(json!({ - "a": 100, - "b": 200, - })) - ); - - let meta = get_meta::>(); - assert_eq!( - meta.example, - Some(json!({ - "a": 32.5, - "b": 72.5, - })) - ); -} - #[test] fn deny_unknown_fields() { #[derive(Object, Debug, Eq, PartialEq)] @@ -724,14 +628,16 @@ fn flatten_field() { assert_eq!(obj.to_json(), Some(json!({"a": 100, "b": 200, "c": 300}))); assert_eq!( - Obj::parse_from_json(Some(json!({"a": 100, "b": 200, "c": 300}))).unwrap(), + Obj::parse_from_json(Some(json!({"a": 100, "b": 200, "c": +300}))) + .unwrap(), obj ); } #[test] fn remote() { - mod remote { + mod remote_types { #[derive(Debug, Eq, PartialEq)] pub struct InternalMyObj { pub a: i32, @@ -740,14 +646,14 @@ fn remote() { } #[derive(Debug, Object, Eq, PartialEq)] - #[oai(remote = "remote::InternalMyObj")] + #[oai(remote = "remote_types::InternalMyObj")] struct MyObj { a: i32, b: String, } assert_eq!( - Into::::into(remote::InternalMyObj { + Into::::into(remote_types::InternalMyObj { a: 100, b: "abc".to_string() }), @@ -758,11 +664,11 @@ fn remote() { ); assert_eq!( - Into::::into(MyObj { + Into::::into(MyObj { a: 100, b: "abc".to_string() }), - remote::InternalMyObj { + remote_types::InternalMyObj { a: 100, b: "abc".to_string() } diff --git a/poem-openapi/tests/request.rs b/poem-openapi/tests/request.rs index e950e4abe3..1cbb8d46a1 100644 --- a/poem-openapi/tests/request.rs +++ b/poem-openapi/tests/request.rs @@ -29,7 +29,7 @@ fn meta() { content: vec![ MetaMediaType { content_type: "application/json", - schema: MetaSchemaRef::Reference("CreateUser"), + schema: MetaSchemaRef::Reference("CreateUser".to_string()), }, MetaMediaType { content_type: "text/plain", diff --git a/poem-openapi/tests/response.rs b/poem-openapi/tests/response.rs index 0a6955929a..2c6b4bd697 100644 --- a/poem-openapi/tests/response.rs +++ b/poem-openapi/tests/response.rs @@ -50,7 +50,7 @@ fn meta() { status: Some(400), content: vec![MetaMediaType { content_type: "application/json", - schema: MetaSchemaRef::Reference("BadRequestResult") + schema: MetaSchemaRef::Reference("BadRequestResult".to_string()) }], headers: vec![] }, diff --git a/poem-openapi/tests/union.rs b/poem-openapi/tests/union.rs index 30981e79a5..7cc013c8ab 100644 --- a/poem-openapi/tests/union.rs +++ b/poem-openapi/tests/union.rs @@ -7,6 +7,12 @@ use poem_openapi::{ }; use serde_json::json; +fn get_meta() -> MetaSchema { + let mut registry = Registry::new(); + T::register(&mut registry); + registry.schemas.remove(&*T::name()).unwrap() +} + #[test] fn with_discriminator() { #[derive(Object, Debug, PartialEq)] @@ -21,15 +27,18 @@ fn with_discriminator() { } #[derive(Union, Debug, PartialEq)] - #[oai(inline, discriminator_name = "type")] + #[oai(discriminator_name = "type")] enum MyObj { A(A), B(B), } + let schema = get_meta::(); + assert_eq!( - MyObj::schema_ref(), - MetaSchemaRef::Inline(Box::new(MetaSchema { + schema, + MetaSchema { + rust_typename: Some("union::with_discriminator::MyObj"), ty: "object", discriminator: Some(MetaDiscriminatorObject { property_name: "type", @@ -39,7 +48,7 @@ fn with_discriminator() { MetaSchemaRef::Inline(Box::new(MetaSchema { required: vec!["type"], all_of: vec![ - MetaSchemaRef::Reference("A"), + MetaSchemaRef::Reference("A".to_string()), MetaSchemaRef::Inline(Box::new(MetaSchema { title: Some("A".to_string()), properties: vec![( @@ -57,7 +66,7 @@ fn with_discriminator() { MetaSchemaRef::Inline(Box::new(MetaSchema { required: vec!["type"], all_of: vec![ - MetaSchemaRef::Reference("B"), + MetaSchemaRef::Reference("B".to_string()), MetaSchemaRef::Inline(Box::new(MetaSchema { title: Some("B".to_string()), properties: vec![( @@ -74,7 +83,7 @@ fn with_discriminator() { })) ], ..MetaSchema::ANY - })) + } ); let mut registry = Registry::new(); @@ -140,7 +149,7 @@ fn with_discriminator_mapping() { } #[derive(Union, Debug, PartialEq)] - #[oai(inline, discriminator_name = "type")] + #[oai(discriminator_name = "type")] enum MyObj { #[oai(mapping = "c")] A(A), @@ -148,9 +157,12 @@ fn with_discriminator_mapping() { B(B), } + let schema = get_meta::(); + assert_eq!( - MyObj::schema_ref(), - MetaSchemaRef::Inline(Box::new(MetaSchema { + schema, + MetaSchema { + rust_typename: Some("union::with_discriminator_mapping::MyObj"), ty: "object", discriminator: Some(MetaDiscriminatorObject { property_name: "type", @@ -163,7 +175,7 @@ fn with_discriminator_mapping() { MetaSchemaRef::Inline(Box::new(MetaSchema { required: vec!["type"], all_of: vec![ - MetaSchemaRef::Reference("A"), + MetaSchemaRef::Reference("A".to_string()), MetaSchemaRef::Inline(Box::new(MetaSchema { title: Some("c".to_string()), properties: vec![( @@ -181,7 +193,7 @@ fn with_discriminator_mapping() { MetaSchemaRef::Inline(Box::new(MetaSchema { required: vec!["type"], all_of: vec![ - MetaSchemaRef::Reference("B"), + MetaSchemaRef::Reference("B".to_string()), MetaSchemaRef::Inline(Box::new(MetaSchema { title: Some("d".to_string()), properties: vec![( @@ -198,7 +210,7 @@ fn with_discriminator_mapping() { })) ], ..MetaSchema::ANY - })) + } ); let mut registry = Registry::new(); @@ -259,20 +271,24 @@ fn without_discriminator() { } #[derive(Union, Debug, PartialEq)] - #[oai(inline)] enum MyObj { A(A), B(bool), } + let schema = get_meta::(); assert_eq!( - MyObj::schema_ref(), - MetaSchemaRef::Inline(Box::new(MetaSchema { + schema, + MetaSchema { + rust_typename: Some("union::without_discriminator::MyObj"), ty: "object", discriminator: None, - any_of: vec![MetaSchemaRef::Reference("A"), bool::schema_ref()], + any_of: vec![ + MetaSchemaRef::Reference("A".to_string()), + bool::schema_ref() + ], ..MetaSchema::ANY - })) + } ); assert_eq!( @@ -320,20 +336,24 @@ fn anyof() { } #[derive(Union, Debug, PartialEq)] - #[oai(inline)] enum MyObj { A(A), B(B), } + let schema = get_meta::(); assert_eq!( - MyObj::schema_ref(), - MetaSchemaRef::Inline(Box::new(MetaSchema { + schema, + MetaSchema { + rust_typename: Some("union::anyof::MyObj"), ty: "object", discriminator: None, - any_of: vec![MetaSchemaRef::Reference("A"), MetaSchemaRef::Reference("B")], + any_of: vec![ + MetaSchemaRef::Reference("A".to_string()), + MetaSchemaRef::Reference("B".to_string()) + ], ..MetaSchema::ANY - })) + } ); assert_eq!( @@ -371,20 +391,25 @@ fn oneof() { } #[derive(Union, Debug, PartialEq)] - #[oai(one_of, inline)] + #[oai(one_of)] enum MyObj { A(A), B(B), } + let schema = get_meta::(); assert_eq!( - MyObj::schema_ref(), - MetaSchemaRef::Inline(Box::new(MetaSchema { + schema, + MetaSchema { + rust_typename: Some("union::oneof::MyObj"), ty: "object", discriminator: None, - one_of: vec![MetaSchemaRef::Reference("A"), MetaSchemaRef::Reference("B")], + one_of: vec![ + MetaSchemaRef::Reference("A".to_string()), + MetaSchemaRef::Reference("B".to_string()) + ], ..MetaSchema::ANY - })) + } ); assert_eq!( @@ -402,7 +427,7 @@ fn oneof() { }))) .unwrap_err() .into_message(), - "Expected input type \"object\", found {\"v1\":100,\"v2\":\"hello\"}." + "Expected input type \"MyObj\", found {\"v1\":100,\"v2\":\"hello\"}." ); } @@ -413,33 +438,29 @@ fn title_and_description() { /// B /// C #[derive(Union, Debug, PartialEq)] - #[oai(inline)] - enum MyObj2 { + enum MyObj { A(i32), B(f32), } - let schema_ref: MetaSchemaRef = MyObj2::schema_ref(); - let meta_schema = schema_ref.unwrap_inline(); - assert_eq!(meta_schema.description, Some("A\n\nB\nC")); + let schema = get_meta::(); + assert_eq!(schema.description, Some("A\n\nB\nC")); } #[tokio::test] async fn external_docs() { #[derive(Union, Debug, PartialEq)] #[oai( - inline, external_docs = "https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md" )] - enum MyObj2 { + enum MyObj { A(i32), B(f32), } - let schema_ref: MetaSchemaRef = MyObj2::schema_ref(); - let meta_schema = schema_ref.unwrap_inline(); + let schema = get_meta::(); assert_eq!( - meta_schema.external_docs, + schema.external_docs, Some(MetaExternalDocument { url: "https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md" .to_string(), @@ -449,57 +470,28 @@ async fn external_docs() { } #[tokio::test] -async fn inline_generic() { - #[derive(Union, Debug, PartialEq)] - #[oai(inline)] - enum MyObj { - A(A), - B(B), - } - - let schema_ref: MetaSchemaRef = MyObj::::schema_ref(); - let meta_schema = schema_ref.unwrap_inline(); - assert_eq!(meta_schema.any_of[0], i32::schema_ref()); - assert_eq!(meta_schema.any_of[1], String::schema_ref()); -} - -#[tokio::test] -async fn concrete_types() { +async fn generics() { #[derive(Union, Debug, PartialEq)] - #[oai( - concrete(name = "Obj_i32_i64", params(i32, i64)), - concrete(name = "Obj_f32_f64", params(f32, f64)) - )] enum MyObj { A(A), B(B), } - let mut registry = Registry::new(); - >::register(&mut registry); - >::register(&mut registry); - - let meta_schema = registry.schemas.remove("Obj_i32_i64").unwrap(); - assert_eq!(meta_schema.any_of[0], i32::schema_ref()); - assert_eq!(meta_schema.any_of[1], i64::schema_ref()); + let schema_i32_i64 = get_meta::>(); + let schema_f32_f64 = get_meta::>(); - let meta_schema = registry.schemas.remove("Obj_f32_f64").unwrap(); - assert_eq!(meta_schema.any_of[0], f32::schema_ref()); - assert_eq!(meta_schema.any_of[1], f64::schema_ref()); -} - -#[tokio::test] -async fn no_inline() { - #[derive(Union, Debug, PartialEq)] - enum MyObj { - A(i32), - B(f32), - } + assert_eq!( + >::schema_ref(), + MetaSchemaRef::Reference("MyObj".to_string()) + ); + assert_eq!( + >::schema_ref(), + MetaSchemaRef::Reference("MyObj".to_string()) + ); - let schema_ref: MetaSchemaRef = MyObj::schema_ref(); - assert_eq!(schema_ref.unwrap_reference(), "MyObj"); + assert_eq!(schema_i32_i64.any_of[0], i32::schema_ref()); + assert_eq!(schema_i32_i64.any_of[1], i64::schema_ref()); - let mut registry = Registry::new(); - MyObj::register(&mut registry); - assert!(registry.schemas.contains_key("MyObj")); + assert_eq!(schema_f32_f64.any_of[0], f32::schema_ref()); + assert_eq!(schema_f32_f64.any_of[1], f64::schema_ref()); }