juhaku / utoipa

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

Break down `#[derive(OpenApi)] #[openapi(...)]` into smaller chunks #1204

Open jacques-kigo opened 6 hours ago

jacques-kigo commented 6 hours ago

I'm working on a large project using utoipa with hundreds of paths and component schemas.

My docs.rs file where I keep the utoipa macros is thousands of lines long.

Adding / organizing new and existing paths and types to #[derive(OpenApi)] #[openapi(...)] is becoming a very large pain point in my codebase.

I'd like to split them up into separate files. At minimum something like:

docs/
├── external.rs
├── internal.rs
├── mod.rs
├── modifiers.rs
├── paths.rs
├── schemas.rs
└── tags.rs

I've tried macros and functions to break down the file into the proposed structure above but nothing seems to work, it keeps breaking the utoipa macro and won't compile unless it's all inline in one file.

Is there something I'm missing here? A better workflow perhaps? This is becoming very frustrating.

juhaku commented 4 hours ago

If you are using utoipa-5.0.0 you can use nest(...) attribute (https://docs.rs/utoipa/latest/utoipa/derive.OpenApi.html#nest-attribute-syntax) to nest other OpenAPIs to a single one. Or nest / merge manually OpenApi to a single one with nest() or merge() methods on OpenApi. https://docs.rs/utoipa/latest/utoipa/openapi/struct.OpenApi.html

You can create multiple OpenApi instances and finally merge them into one. E.g. you can create one within paths.rs one in schemas.rs etc. in order to split the functionality.

If you are using axum or actix-web you can also use the utoipa-axum or utoipa-actix-web to register the handlers simultaneously to the OpenApi without registering the paths in paths(...) attribute. Of course this has a drawback that then axum or actix-web dependency needs to be there as well when the OpenAPI schema is created as it ties utoipa and axum / actix-web together which makes it little harder to have a separate command to dump the generated OpenAPI spec to file e.g. in build step as explained here https://github.com/juhaku/utoipa/issues/214#issuecomment-1179589373.

Utoipa 5.0.0 will automatically collect schemas from the usages points recursive. This requires that all types that derive ToSchema need to make sure all fields and variant types also implement the ToSchema trait. The schemas are automatically collected from request_body or response body or from schemas(...) attribute, But there is no need to define those schemas anymore in #[openapi(schemas(...))] attribute as long as they are defined somewhere for request body or response body. For axum and actix-web the request body is also resolved from the handler function argument as well.

Currently the IntoParams which have type that implements ToSchema is not automatically collected and such type needs to be manually added to the OpenApi via schemas(..) attribute.

Example of utoipa axum bindings: https://github.com/juhaku/utoipa/tree/master/examples/actix-web-scopes-binding Example of utoipa actix-web bindings: https://github.com/juhaku/utoipa/tree/master/examples/actix-web-scopes-binding Example of using nesting: https://github.com/juhaku/utoipa/tree/master/examples/axum-utoipa-nesting-vendored

Also the latest todo examples for axum and actix-web has been updated to use the bindings instead of registering handlers via paths(...) attribute. It is true that when there are plenty of types and handlers to register, it does get burden some the register them all via schemas(...) or paths(...) as they also need to be accessible by the macro as well. By way of using nesting or merging OpenApi instances into single one there is no need expose all paths and types publicly to single OpenApi but instead only the sub OpenApis can be exposed publicly.