juhaku / utoipa

Simple, Fast, Code first and Compile time generated OpenAPI documentation for Rust
Apache License 2.0
2.46k stars 191 forks source link

Option to always inline custom `ToSchema` impls #962

Open har7an opened 4 months ago

har7an commented 4 months ago

Hello,

I've been trying to find a way to have custom ToSchema impls be inlined automatically in the generated OpenAPI specification. Is that possible and if so, how exactly?

Background

We have a codebase where we define our own DateTime and Duration types since we need them to interact with a few other components of ours and chronos own types don't suit our needs entirely. However, previously we had the chrono feature flag enabled which made our DateTime-type magically appear with the correct schema. On the other hand, I tried defining ToSchema on our own Duration type only to realize that apparently it doesn't have any effect on the generated spec.

Eventually I traced this issue back to impl ToTokensDiagnostics for Type<'_> from utoipa-gen/src/schema_type.rs (I assume that's where it comes from at least), where it appears to be looking at the last component of the fully qualified type name and then decides what to put in.

Obviously this collides with our own types and after removing the chrono flag things work as expected and it picks up our own ToSchema impls.

The Problem

With that settled, I had to realize that all the places in the code where we use a DateTime as part of a struct that has ToSchema derived, the previously inlined schemas have now become references. That of course requires me to either:

  1. Export the types in the OpenAPI docs as component, or
  2. Annotate all mentions of DateTime with #[inline]

But none of these solutions is entirely satisfying because in either case I lose the description from the associated docstring.

The way things were before, the schema definition of chrono::DateTime was being inlined and when deriving ToSchema on a struct it would complement the docstring of the respective struct field into that schema (unless I'm mistaken). So I knew that my field has type=string, format=date-time and description=....

I had a glance at the source code, but couldn't find any location where this inlining/merging is performed. I haven't written proc-macros before, so I'm a little lost there...

What I'd like to see

Basically I'd like to have a way to always inline a custom ToSchema impl. In addition, if somehow possible, I'd love to see a way to integrate docstrings (of e.g. struct members) into that generated specification in the description field, so we can get something like the default chrono::DateTime can already provide us with.

juhaku commented 1 month ago

@har7an

We could add possibility to use the schema(inline) attribute on container types as well instead of only on fields and variants.

docstrings (of e.g. struct members) into that generated specification in the description field, so we can get something like the default chrono::DateTime can already provide us with.

In OpenAPI 3.0 there is no description field on ref element. However in OpenAPI 3.1 they have added that field so in 5.0.0 version the description will persist on fields that have a reference as well.

I'd like to have a way to always inline a custom ToSchema impl.

This could be done in a way of global aliases, where the custom DateTime would be set to be alias for chrono::DateTime then naturally it would get inlined. Still this has not been implemented and work has not been started. But some early plans already exist for the feature.

juhaku commented 1 month ago

Global aliases has been implemented and released already https://github.com/juhaku/utoipa/tree/master/utoipa-config https://github.com/juhaku/utoipa/pull/1048

@har7an If you are looking for always inline for custom types implementing ToSchema. This has not been done nor planned yet. But such config option could be made as well in future.

JMLX42 commented 5 days ago

Maybe a newtype with #[schema(inline)] ?

#[derive(ToSchema]
struct MyDateTime(#[schema(inline) DateTime);

Then use MyDateTime instead of DateTime.