Skip to content

Commit

Permalink
Move schemas into ToSchema for schemas (#1112)
Browse files Browse the repository at this point in the history
This commit moves the `schemas` into `ToSchema` trait for schemas.
`SchemaReferences` will still exist for types that does not implement
`ToSchema` and is used internally by `utoipa`. This enables users to
manually implement `ToSchema` without extra hassle with references if
there is no need to.

Fixes #1093
  • Loading branch information
juhaku authored Oct 10, 2024
1 parent 43224fc commit 18e96c9
Show file tree
Hide file tree
Showing 7 changed files with 99 additions and 6 deletions.
1 change: 1 addition & 0 deletions utoipa-gen/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@

### Changed

* Move `schemas` into `ToSchema` for schemas (https://github.com/juhaku/utoipa/pull/1112)
* Refactor `KnownFormat`
* Add path rewrite support (https://github.com/juhaku/utoipa/pull/1110)
* Fix broken tests
Expand Down
8 changes: 5 additions & 3 deletions utoipa-gen/src/component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1209,7 +1209,8 @@ impl ComponentSchema {
}
};
object_schema_reference.tokens = items_tokens.clone();
object_schema_reference.references = quote! { <#rewritten_path as utoipa::__dev::SchemaReferences>::schemas(schemas) };
object_schema_reference.references =
quote! { <#rewritten_path as utoipa::ToSchema>::schemas(schemas) };

let description_tokens = description_stream.to_token_stream();
let schema = if default.is_some()
Expand Down Expand Up @@ -1242,7 +1243,8 @@ impl ComponentSchema {
quote! { <#rewritten_path as utoipa::PartialSchema>::schema() }
};
object_schema_reference.tokens = reference_tokens;
object_schema_reference.references = quote! { <#rewritten_path as utoipa::__dev::SchemaReferences>::schemas(schemas) };
object_schema_reference.references =
quote! { <#rewritten_path as utoipa::ToSchema>::schemas(schemas) };
}
let composed_or_ref = |item_tokens: TokenStream| -> TokenStream {
if let Some(index) = &index {
Expand Down Expand Up @@ -1414,7 +1416,7 @@ impl ComponentSchema {
Ok(ChildRefIter::Once(std::iter::once(SchemaReference {
name: quote! { String::from(< #rewritten_path as utoipa::ToSchema >::name().as_ref()) },
tokens: quote! { <#rewritten_path as utoipa::PartialSchema>::schema() },
references: quote !{ <#rewritten_path as utoipa::__dev::SchemaReferences>::schemas(schemas) },
references: quote !{ <#rewritten_path as utoipa::ToSchema>::schemas(schemas) },
}))
)
} else {
Expand Down
2 changes: 0 additions & 2 deletions utoipa-gen/src/component/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,9 +138,7 @@ impl ToTokensDiagnostics for Schema<'_> {
fn name() -> std::borrow::Cow<'static, str> {
std::borrow::Cow::Borrowed(#name)
}
}

impl #impl_generics utoipa::__dev::SchemaReferences for #ident #ty_generics #where_clause {
fn schemas(schemas: &mut Vec<(String, utoipa::openapi::RefOr<utoipa::openapi::schema::Schema>)>) {
schemas.extend(#schema_refs);
#references;
Expand Down
2 changes: 1 addition & 1 deletion utoipa-gen/src/openapi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -656,7 +656,7 @@ impl crate::ToTokensDiagnostics for Components {

components.extend(quote! { .schemas_from_iter( {
let mut schemas = Vec::<(String, utoipa::openapi::RefOr<utoipa::openapi::schema::Schema>)>::new();
<#type_path as utoipa::__dev::SchemaReferences>::schemas(&mut schemas);
<#type_path as utoipa::ToSchema>::schemas(&mut schemas);
schemas
} )});
components.extend(quote! { .schema(#name, #schema) });
Expand Down
38 changes: 38 additions & 0 deletions utoipa-gen/tests/schema_derive_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5816,3 +5816,41 @@ fn derive_struct_inline_with_description() {
})
);
}

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

struct Newtype(String);

impl ToSchema for Newtype {
fn name() -> std::borrow::Cow<'static, str> {
std::borrow::Cow::Borrowed("Newtype")
}
}

impl utoipa::PartialSchema for Newtype {
fn schema() -> utoipa::openapi::RefOr<utoipa::openapi::schema::Schema> {
String::schema()
}
}

let value = api_doc! {
struct Dto {
customer: Newtype
}
};

assert_json_eq!(
value,
json!({
"properties": {
"customer": {
"$ref": "#/components/schemas/Newtype"
}
},
"required": ["customer"],
"type": "object"
})
)
}
1 change: 1 addition & 0 deletions utoipa/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ to look into changes introduced to **`utoipa-gen`**.

### Changed

* Move `schemas` into `ToSchema` for schemas (https://github.com/juhaku/utoipa/pull/1112)
* List only `utoipa` related changes in `utoipa` CHANGELOG
* Remove commit commit id from changelogs (https://github.com/juhaku/utoipa/pull/1077)
* Update to rc
Expand Down
53 changes: 53 additions & 0 deletions utoipa/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,58 @@ pub trait ToSchema: PartialSchema {
.unwrap_or(type_name_without_generic);
Cow::Borrowed(type_name)
}

/// Implement reference [`utoipa::openapi::schema::Schema`]s for this type.
///
/// When [`ToSchema`] is being derived this is implemented automatically but if one needs to
/// manually implement [`ToSchema`] trait then this is needed for `utoipa` to know
/// referencing schemas that need to be present in the resulting OpenAPI spec.
///
/// The implementation should push to `schemas` [`Vec`] all such field and variant types that
/// implement `ToSchema` and then call `<MyType as ToSchema>::schemas(schemas)` on that type
/// to forward the recursive reference collection call on that type.
///
/// # Examples
///
/// _**Implement `ToSchema` manually with references.**_
///
/// ```rust
/// # use utoipa::{ToSchema, PartialSchema};
/// #
/// #[derive(ToSchema)]
/// struct Owner {
/// name: String
/// }
///
/// struct Pet {
/// owner: Owner,
/// name: String
/// }
/// impl PartialSchema for Pet {
/// fn schema() -> utoipa::openapi::RefOr<utoipa::openapi::schema::Schema> {
/// utoipa::openapi::schema::Object::builder()
/// .property("owner", Owner::schema())
/// .property("name", String::schema())
/// .into()
/// }
/// }
/// impl ToSchema for Pet {
/// fn schemas(schemas:
/// &mut Vec<(String, utoipa::openapi::RefOr<utoipa::openapi::schema::Schema>)>) {
/// schemas.push((Owner::name().into(), Owner::schema()));
/// <Owner as ToSchema>::schemas(schemas);
/// }
/// }
/// ```
#[allow(unused)]
fn schemas(
schemas: &mut Vec<(
String,
utoipa::openapi::RefOr<utoipa::openapi::schema::Schema>,
)>,
) {
// nothing by default
}
}

impl<T: ToSchema> From<T> for openapi::RefOr<openapi::schema::Schema> {
Expand Down Expand Up @@ -1332,6 +1384,7 @@ pub mod __dev {
}
}

// For types not implementing `ToSchema`
pub trait SchemaReferences {
fn schemas(
schemas: &mut Vec<(
Expand Down

0 comments on commit 18e96c9

Please sign in to comment.