Skip to content

Commit

Permalink
Fix impl ToSchema for container types (#1107)
Browse files Browse the repository at this point in the history
This PR fixes implementation of `ToSchema` on container types like
`Vec<T>` or `Option<T>`. Prior to this commit it was not implemented
which resulted errors when such types was used as generic argument.

This commit fixes this which correctly allows following syntax.
```rust
 #[derive(ToSchema)]
 pub struct FooStruct<B> {
     pub foo: B,
 }

 #[derive(ToSchema)]
 enum FoosEnum {
     ThingNoAliasOption(FooStruct<Option<i32>>),
     FooEnumThing(#[schema(inline)] FooStruct<Vec<i32>>),
     FooThingOptionVec(#[schema(inline)] FooStruct<Option<Vec<i32>>>),
     FooThingLinkedList(#[schema(inline)] FooStruct<std::collections::LinkedList<i32>>),
     FooThingBTreeMap(#[schema(inline)] FooStruct<std::collections::BTreeMap<String, String>>),
     FooThingHashMap(#[schema(inline)] FooStruct<std::collections::HashMap<i32, String>>),
     FooThingHashSet(#[schema(inline)] FooStruct<std::collections::HashSet<i32>>),
     FooThingBTreeSet(#[schema(inline)] FooStruct<std::collections::BTreeSet<i32>>),
 }
```

**Note!** Currently generic argument cannot be `slice`, `array` or
`tuple`. That is a hard limitation of current `TypeTree` implementation
and would need some work before such types were supported.

Relates #1034

Fixes #1099
  • Loading branch information
juhaku authored Oct 8, 2024
1 parent 3d6b1f9 commit 773a015
Show file tree
Hide file tree
Showing 8 changed files with 850 additions and 162 deletions.
1 change: 1 addition & 0 deletions utoipa-gen/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@

### Fixed

* Fix impl `ToSchema` for container types (https://github.com/juhaku/utoipa/pull/1107)
* Fix description on `inline` field (https://github.com/juhaku/utoipa/pull/1102)
* Fix `title` on unnamed struct and references (https://github.com/juhaku/utoipa/pull/1101)
* Fix generic references (https://github.com/juhaku/utoipa/pull/1097)
Expand Down
2 changes: 1 addition & 1 deletion utoipa-gen/src/component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1234,7 +1234,7 @@ impl ComponentSchema {
utoipa::openapi::schema::ArrayBuilder::new()
#nullable_schema_type
.items(utoipa::openapi::schema::ArrayItems::False)
.prefix_items(#prefix_items.to_vec())
.prefix_items(#prefix_items)
#description_stream
#deprecated
})
Expand Down
127 changes: 126 additions & 1 deletion utoipa-gen/tests/schema_generics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::marker::PhantomData;

use serde::Serialize;
use utoipa::openapi::{Info, RefOr, Schema};
use utoipa::{schema, OpenApi, ToSchema};
use utoipa::{schema, OpenApi, PartialSchema, ToSchema};

#[test]
fn generic_schema_custom_bound() {
Expand Down Expand Up @@ -129,6 +129,131 @@ fn schema_with_non_generic_root() {
assert_eq!(actual.trim(), expected.trim())
}

#[test]
fn derive_generic_schema_enum_variants() {
#![allow(unused)]

#[derive(ToSchema)]
pub struct FooStruct<B> {
pub foo: B,
}

#[derive(ToSchema)]
enum FoosEnum {
ThingNoAliasOption(FooStruct<Option<i32>>),
FooEnumThing(#[schema(inline)] FooStruct<Vec<i32>>),
FooThingOptionVec(#[schema(inline)] FooStruct<Option<Vec<i32>>>),
FooThingLinkedList(#[schema(inline)] FooStruct<std::collections::LinkedList<i32>>),
FooThingBTreeMap(#[schema(inline)] FooStruct<std::collections::BTreeMap<String, String>>),
FooThingHashMap(#[schema(inline)] FooStruct<std::collections::HashMap<i32, String>>),
FooThingHashSet(#[schema(inline)] FooStruct<std::collections::HashSet<i32>>),
FooThingBTreeSet(#[schema(inline)] FooStruct<std::collections::BTreeSet<i32>>),
}

let schema = FoosEnum::schema();
let json = serde_json::to_string_pretty(&schema).expect("Schema is JSON serializable");
let value = json.trim();
// println!("{value}");

#[derive(OpenApi)]
#[openapi(components(schemas(FoosEnum)))]
struct Api;

let mut api = Api::openapi();
api.info = Info::new("title", "version");
let api_json = api.to_pretty_json().expect("OpenAPI is JSON serializable");
println!("{api_json}");
let expected = include_str!("./testdata/schema_generic_enum_variant_with_generic_type");
assert_eq!(expected.trim(), api_json.trim());
}

#[test]
fn high_order_types() {
#![allow(unused)]

#[derive(ToSchema)]
pub struct High<T> {
high: T,
}

#[derive(ToSchema)]
pub struct HighBox {
value: High<Box<i32>>,
}

#[derive(ToSchema)]
pub struct HighCow(High<Cow<'static, i32>>);

#[derive(ToSchema)]
pub struct HighRefCell(High<std::cell::RefCell<i32>>);

#[derive(OpenApi)]
#[openapi(components(schemas(HighBox, HighCow, HighRefCell)))]
struct Api;

let mut api = Api::openapi();
api.info = Info::new("title", "version");
let api_json = api.to_pretty_json().expect("OpenAPI is JSON serializable");
println!("{api_json}");
let expected = include_str!("./testdata/schema_high_order_types");
assert_eq!(expected.trim(), api_json.trim());
}

#[test]
#[cfg(feature = "rc_schema")]
fn rc_schema_high_order_types() {
#![allow(unused)]

#[derive(ToSchema)]
pub struct High<T> {
high: T,
}

#[derive(ToSchema)]
pub struct HighArc(High<std::sync::Arc<i32>>);

#[derive(ToSchema)]
pub struct HighRc(High<std::rc::Rc<i32>>);

#[derive(OpenApi)]
#[openapi(components(schemas(HighArc, HighRc)))]
struct Api;

let mut api = Api::openapi();
api.info = Info::new("title", "version");
let api_json = api.to_pretty_json().expect("OpenAPI is JSON serializable");
println!("{api_json}");

let expected = include_str!("./testdata/rc_schema_high_order_types");
assert_eq!(expected.trim(), api_json.trim());
}

#[test]
#[ignore = "arrays, slices, tuples as generic argument is not supported at the moment"]
fn slice_generic_args() {
#![allow(unused)]

#[derive(ToSchema)]
pub struct High<T> {
high: T,
}

// // #[derive(ToSchema)]
// pub struct HighSlice(High<&'static [i32]>);
//
// #[derive(OpenApi)]
// // #[openapi(components(schemas(HighSlice)))]
// struct Api;
//
// let mut api = Api::openapi();
// api.info = Info::new("title", "version");
// let api_json = api.to_pretty_json().expect("OpenAPI is JSON serializable");
// println!("{api_json}");
//
// let expected = include_str!("./testdata/rc_schema_high_order_types");
// assert_eq!(expected.trim(), api_json.trim());
}

#[test]
#[ignore = "For debugging only"]
fn schema_macro_run() {
Expand Down
42 changes: 42 additions & 0 deletions utoipa-gen/tests/testdata/rc_schema_high_order_types
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"openapi": "3.1.0",
"info": {
"title": "title",
"version": "version"
},
"paths": {},
"components": {
"schemas": {
"HighArc": {
"$ref": "#/components/schemas/High_Arc_i32"
},
"HighRc": {
"$ref": "#/components/schemas/High_Rc_i32"
},
"High_Arc_i32": {
"type": "object",
"required": [
"high"
],
"properties": {
"high": {
"type": "integer",
"format": "int32"
}
}
},
"High_Rc_i32": {
"type": "object",
"required": [
"high"
],
"properties": {
"high": {
"type": "integer",
"format": "int32"
}
}
}
}
}
}
Loading

0 comments on commit 773a015

Please sign in to comment.