use std::borrow::Cow; use std::marker::PhantomData; use assert_json_diff::assert_json_eq; use fastapi::openapi::{Info, RefOr, Schema}; use fastapi::{schema, OpenApi, PartialSchema, ToSchema}; use serde::Serialize; use serde_json::json; #[test] fn generic_schema_custom_bound() { #![allow(unused)] #[derive(Serialize, ToSchema)] #[schema(bound = "T: Clone + Sized, T: Sized")] struct Type { #[serde(skip)] t: PhantomData, } #[derive(Clone)] struct NoToSchema; fn assert_is_to_schema() {} assert_is_to_schema::>(); } #[test] fn generic_request_body_schema() { #![allow(unused)] #[derive(ToSchema)] #[schema(as = path::MyType)] struct Type { #[schema(inline)] t: T, } #[derive(ToSchema)] struct Person { field: T, #[schema(inline)] t: P, } #[fastapi::path( get, path = "/handler", request_body = inline(Person>), )] async fn handler() {} #[derive(OpenApi)] #[openapi( components( schemas( Person::>, ) ), paths( handler ) )] struct ApiDoc; let mut doc = ApiDoc::openapi(); doc.info = Info::new("title", "version"); let actual = serde_json::to_value(&doc).expect("operation is JSON serializable"); let json = serde_json::to_string_pretty(&actual).unwrap(); println!("{json}"); assert_json_eq!( actual, json!({ "openapi": "3.1.0", "info": { "title": "title", "version": "version" }, "paths": { "/handler": { "get": { "tags": [], "operationId": "handler", "requestBody": { "content": { "application/json": { "schema": { "type": "object", "required": [ "field", "t" ], "properties": { "field": { "type": "string" }, "t": { "type": "object", "required": [ "t" ], "properties": { "t": { "type": "integer", "format": "int32" } } } } } } }, "required": true }, "responses": {} } } }, "components": { "schemas": { "Person_String_path.MyType_i32": { "type": "object", "required": [ "field", "t" ], "properties": { "field": { "type": "string" }, "t": { "type": "object", "required": [ "t" ], "properties": { "t": { "type": "integer", "format": "int32" } } } } } } } }) ); } #[test] fn generic_schema_full_api() { #![allow(unused)] #[derive(ToSchema)] #[schema(as = path::MyType)] struct Type { t: T, } #[derive(ToSchema)] struct Person<'p, T: Sized, P> { id: usize, name: Option>, field: T, t: P, } #[derive(ToSchema)] #[schema(as = path::to::PageList)] struct Page { total: usize, page: usize, pages: usize, items: Vec, } #[derive(ToSchema)] #[schema(as = path::to::Element)] enum E { One(T), Many(Vec), } struct NoToSchema; fn assert_no_need_to_schema_outside_api(_: Type) {} #[fastapi::path( get, path = "/handler", request_body = inline(Person<'_, String, Type>), responses( (status = OK, body = inline(Page>>)), (status = 400, body = Page>>) ) )] async fn handler() {} #[derive(OpenApi)] #[openapi( components( schemas( Person::<'_, String, Type>, Page::>>, E::, ) ), paths( handler ) )] struct ApiDoc; let mut doc = ApiDoc::openapi(); doc.info = Info::new("title", "version"); let actual = doc.to_pretty_json().expect("OpenApi is JSON serializable"); println!("{actual}"); let expected = include_str!("./testdata/schema_generics_openapi"); assert_eq!(expected.trim(), actual.trim()); } #[test] fn schema_with_non_generic_root() { #![allow(unused)] #[derive(ToSchema)] struct Foo { bar: Bar, } #[derive(ToSchema)] struct Bar { #[schema(inline)] value: T, } #[derive(ToSchema)] struct Top { foo1: Foo, foo2: Foo, } #[derive(OpenApi)] #[openapi(components(schemas(Top)))] struct ApiDoc; let mut api = ApiDoc::openapi(); api.info = Info::new("title", "version"); let actual = api.to_pretty_json().expect("schema is JSON serializable"); println!("{actual}"); let expected = include_str!("./testdata/schema_non_generic_root_generic_references"); assert_eq!(actual.trim(), expected.trim()) } #[test] fn derive_generic_schema_enum_variants() { #![allow(unused)] #[derive(ToSchema)] pub struct FooStruct { pub foo: B, } #[derive(ToSchema)] enum FoosEnum { ThingNoAliasOption(FooStruct>), FooEnumThing(#[schema(inline)] FooStruct>), FooThingOptionVec(#[schema(inline)] FooStruct>>), FooThingLinkedList(#[schema(inline)] FooStruct>), FooThingBTreeMap(#[schema(inline)] FooStruct>), FooThingHashMap(#[schema(inline)] FooStruct>), FooThingHashSet(#[schema(inline)] FooStruct>), FooThingBTreeSet(#[schema(inline)] FooStruct>), } let schema = FoosEnum::schema(); let json = serde_json::to_string_pretty(&schema).expect("Schema is JSON serializable"); let value = json.trim(); #[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 derive_generic_schema_collect_recursive_schema_not_inlined() { #![allow(unused)] #[derive(ToSchema)] pub struct FooStruct { pub foo: B, } #[derive(ToSchema)] pub struct Value(String); #[derive(ToSchema)] pub struct Person { name: String, account: Account, t: T, } #[derive(ToSchema)] pub struct Account { name: String, } #[derive(ToSchema, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Ty { t: T, } #[derive(ToSchema, PartialEq, Eq, PartialOrd, Ord, Hash)] enum Ky { One, Two, } #[derive(ToSchema)] enum FoosEnum { LinkedList(std::collections::LinkedList>), BTreeMap(FooStruct>>), HashMap(FooStruct>>), HashSet(FooStruct>), Btre(FooStruct, Person>>), } let schema = FoosEnum::schema(); let json = serde_json::to_string_pretty(&schema).expect("Schema is JSON serializable"); let value = json.trim(); #[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_collect_non_inlined_schema"); assert_eq!(expected.trim(), api_json.trim()); } #[test] fn high_order_types() { #![allow(unused)] #[derive(ToSchema)] pub struct High { #[schema(inline)] high: T, } #[derive(ToSchema)] pub struct HighBox { value: High>, } #[derive(ToSchema)] pub struct HighCow(High>); #[derive(ToSchema)] pub struct HighRefCell(High>); #[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 { high: T, } #[derive(ToSchema)] pub struct HighArc(High>); #[derive(ToSchema)] pub struct HighRc(High>); #[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] #[cfg(feature = "uuid")] fn uuid_type_generic_argument() { #![allow(unused)] #[derive(ToSchema)] pub struct High { high: T, } #[derive(ToSchema)] pub struct HighUuid(High>); #[derive(OpenApi)] #[openapi(components(schemas(HighUuid)))] 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/uuid_type_generic_argument"); 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 { 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() { #![allow(unused)] #[derive(ToSchema)] #[schema(as = path::MyType)] struct Type { t: T, } #[derive(ToSchema)] struct Person<'p, T: Sized, P> { id: usize, name: Option>, field: T, t: P, } #[derive(ToSchema)] #[schema(as = path::to::PageList)] struct Page { total: usize, page: usize, pages: usize, items: Vec, } let schema: RefOr = schema!(Page>>).into(); // let schema: RefOr = schema!(Person<'_, String, Type>).into(); // let schema: RefOr = schema!(Vec>>).into(); println!( "{}", serde_json::to_string_pretty(&schema).expect("schema is JSON serializable") ); }