Code Monkey home page Code Monkey logo

utoipauto's Introduction

Utoipauto

Rust Macros to automate the addition of Paths/Schemas to Utoipa crate, simulating Reflection during the compilation phase

Crate presentation

Utoipa is a great crate for generating documentation (openapi/swagger) via source code.

But since Rust is a static programming language, we don't have the possibility of automatically discovering paths and dto in runtime and adding them to the documentation,

For APIs with just a few endpoints, it's not that much trouble to add controller functions one by one, and DTOs one by one.

But, if you have hundreds or even thousands of endpoints, the code becomes very verbose and difficult to maintain.

Ex :

#[derive(OpenApi)]
#[openapi(
    paths(
        // <================================ All functions  1 to N
        test_controller::service::func_get_1,
        test_controller::service::func_get_2,
        test_controller::service::func_get_3,
        test_controller::service::func_get_4,
       ....
       ....
       ....
        test_controller::service::func_get_N,

    ),
    components(
        // <====================== All DTO one by one
        schemas(TestDTO_1,  TestDTO_2, ........ , TestDTO_N)
    ),
    tags(
        (name = "todo", description = "Todo management endpoints.")
    ),
    modifiers(&SecurityAddon)
)]
pub struct ApiDoc;

The goal of this crate is to propose a macro that automates the detection of methods carrying Utoipa macros (#[utoipa::path(...]), and adds them automatically. (it also detects sub-modules.)

It also detects struct that derive or implement ToSchema for the components(schemas) section, and the ToResponse for the components(responses) section.

Features

  • Automatic recursive path detection
  • Automatic import from module
  • Automatic import from src folder
  • Automatic model detection
  • Automatic response detection
  • Works with workspaces

How to use it

Simply add the crate utoipauto to the project

cargo add utoipauto

Import macro

use utoipauto::utoipauto;

Then add the #[utoipauto] macro just before the #[derive(OpenApi)] and #[openapi] macros.

Important !!

Put #[utoipauto] before #[derive(OpenApi)] and #[openapi] macros.

#[utoipauto(paths = "MODULE_SRC_FILE_PATH, MODULE_SRC_FILE_PATH, ...")]

The paths receives a String which must respect this structure :

"MODULE_SRC_FILE_PATH, MODULE_SRC_FILE_PATH, ..."

You can add several paths by separating them with a coma ",".

Support for generic schemas

We support generic schemas, but with a few drawbacks.
If you want to use generics, you have three ways to do it.

  1. use the full path
#[aliases(GenericSchema = path::to::Generic<path::to::Schema>)]
  1. Import where utoipauto lives
use path::to::schema;
  1. use experimental generic_full_path feature

Please keep in mind that this is an experimental feature and causes more build-time overhead.
Higher RAM usage, longer compile times and excessive disk usage (especially on larger projects) are the consequences.
Also we currently do not support "partial" imports. The following is NOT supported:

use path::to::generic;

#[aliases(GenericSchema = generic::Schema)]

Please use the full path instead or the as keyword to rename the imported schemas.

utoipauto = { version = "0.2.0", feature = ["generic_full_path"] }

Usage with workspaces

If you are using a workspace, you must specify the name of the crate in the path.
This applies even if you are using #[utoipauto] in the same crate.

#[utoipauto(paths = "./utoipauto/src")]

You can specify that the specified paths are from another crate by using the from key work.

#[utoipauto(paths = "./utoipauto/src from utoipauto")]

Import from src folder

If no path is specified, the macro will automatically scan the src folder and add all the methods carrying the #[utoipa::path(...)] macro, and all structs deriving ToSchema and ToResponse. Here's an example of how to add all the methods contained in the src code.

...

use utoipauto::utoipauto;

...
#[utoipauto]
#[derive(OpenApi)]
#[openapi(
    tags(
        (name = "todo", description = "Todo management endpoints.")
    ),
    modifiers(&SecurityAddon)
)]

pub struct ApiDoc;

...

Import from module

Here's an example of how to add all the methods and structs contained in the rest module.

use utoipauto::utoipauto;

#[utoipauto(
  paths = "./src/rest"
  )]
#[derive(OpenApi)]
#[openapi(
    tags(
        (name = "todo", description = "Todo management endpoints.")
    ),
    modifiers(&SecurityAddon)
)]

pub struct ApiDoc;

Import from filename

Here's an example of how to add all the methods contained in the test_controller and test2_controller modules. you can also combine automatic and manual addition, as here we've added a method manually to the documentation "other_controller::get_users", and a schema "TestDTO".

use utoipauto::utoipauto;

#[utoipauto(
  paths = "./src/rest/test_controller.rs,./src/rest/test2_controller.rs "
  )]
#[derive(OpenApi)]
#[openapi(
    paths(

        crate::rest::other_controller::get_users,
    ),
    components(
        schemas(TestDTO)
    ),
    tags(
        (name = "todo", description = "Todo management endpoints.")
    ),
    modifiers(&SecurityAddon)
)]

pub struct ApiDoc;

Exclude a method from automatic scanning

you can exclude a function from the Doc Path list by adding the following macro #[utoipa_ignore] .

ex:

    /// Get all pets from database
    ///
    #[utoipa_ignore]  //<============== this Macro
    #[utoipa::path(
        responses(
            (status = 200, description = "List all Pets", body = [ListPetsDTO])
        )
    )]
    #[get("/pets")]
    async fn get_all_pets(req: HttpRequest, store: web::Data<AppState>) -> impl Responder {
        // your CODE
    }

Exclude a struct from automatic scanning

you can also exclude a struct from the models and reponses list by adding the following macro #[utoipa_ignore] .

ex:

    #[utoipa_ignore]  //<============== this Macro
    #[derive(ToSchema)]
    struct ModelToIgnore {
        // your CODE
    }

Note

Sub-modules within a module containing methods tagged with utoipa::path are also automatically detected.

Contributing

Contributions are welcomed, feel free to submit a PR or an issue.

Inspiration

Inspired by utoipa_auto_discovery

utoipauto's People

Contributors

denuxplays avatar j7nw4r avatar probablyclem avatar r0t3n avatar saman3d avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

utoipauto's Issues

Macro errors with `expected ','`

Hi, and thanks for making this crate!

When trying to replace my (working) manual #[openapi] annotation with utoipauto, I'm getting the error

error: expected `,`
  --> service-libs/web-backend/src/api/mod.rs:23:1
   |
23 | #[utoipauto::utoipauto(paths = "./service-libs/web-backend/src/api/internal")]
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = note: this error originates in the attribute macro `utoipauto::utoipauto` (in Nightly builds, run with -Z macro-backtrace for more info)

With -Z macro-backtrace, the compiler helpfully traces the error back to the macro:

::: /<PATH>/index.crates.io-6f17d22bba15001f/utoipauto-macro-0.1.4/src/lib.rs:15:1
   |
15 | / pub fn utoipauto(
16 | |     attributes: proc_macro::TokenStream, // #[utoipauto(paths = "(MODULE_TREE_PATH => MODULE_SRC_PATH) ;")]
17 | |     item: proc_macro::TokenStream,       // #[openapi(paths = "")]
18 | | ) -> proc_macro::TokenStream {
   | |____________________________- in this expansion of `#[utoipauto::utoipauto]`

The project is part of a workspace, so I'm using #[utoipauto::utoipauto(paths = "./service-libs/web-backend/src/api")] to make utoipauto look at the correct source path (there's nothing at ./src).

I've tried to figure out where the error occurs or which of my types or handlers causes it, but when I point it at one of the files with 1 ToResponse derive, 2 ToSchemas and 1 handler, the error only goes away if I remove the utoipa macros / derives from all of them. I also couldn't manage to reproduce the error by modifying your tests, which is weird. Could it be that the different file structure in a workspace messes things up?

Add partial import support & Stabilizing `generic_full_path` feature

Adding last missing feature

Add support for partial imports.
This is the last feature that we are missing and when this is implemented I think we can mark generic_full_path as stable.

Stabilizing the feature

I've now used the feature since I developed it and I have to say there isn't a case where it fails.
I do not know if someothers haved used so it would be great if someone could share their experience with it.

But I was sadly right about longer compile times and higher RAM usage.
The higher RAM usage can cause problems especially on PCs with a low amount of RAM. (8GB or less).

We cannot get rid of the higher RAM usage as we simply need to store more infromation.
Same with the disk usage we need to parse more to get the extra information needed.

I think we can say that this feature is stable.
But maybe you/others disagree or you miss some features.

Or maybe we should wait until the rust released they parallel front-end (see here) because this could potentially increase our RAM usage even more.
We have to test that though.

Recursive expansion seems to be broken?

Hello. Given this simple example: https://github.com/j7nw4r/utoipauto-bug the macro expansion seems to be broken. It fails with the error message:

error: unexpected attribute, expected any of: handlers, components, modifiers, security, tags, external_docs, servers, expected identifier at the #[utoipauto(paths = "./src")] annotation.

I believe this is because the expansion mistakenly expands to the below and includes an erroneous # [ openapi ].

#[derive(OpenApi)] 
#[openapi(paths   (), components   ( schemas   (), responses   () ), # [ openapi   ])]
struct ApiDoc;

I'm not super familiar with proc-macros, but if pointed in the correct direction, maybe I can look into it.

Detecting `ToSchema` fails when behind a `cfg_attr` flag

Hi! ๐Ÿ‘‹

I'm playing around with this crate and ran into an issue where schemas (and I'm guessing paths) aren't detected when behind a cfg_attr flag. E.g:
Works:

#[derive(ToSchema)]
struct Todo {
  // ...
}

Doesn't work:

#[cfg_attr(feature = "openapi", derive(ToSchema))]
struct Todo {
  // ...
}

Essentially, my use case is to only generate the OpenAPI spec when writing it to a file in my CI step, and I don't want to have to compile all my utoipa macros when running the webserver normally.

Thanks for the library, it's really nice!

Components lack full module paths

Hello, we're trying out this crate for a college assignment involving a backend server.

We've setup the project to use just utoipa then proceeded to replace the declarations with this crate's macro.
The derived components seem to lack the module path, so instead of crate::routing::api::audio::DeleteSamplesRequest the macro generates just DeleteSamplesRequest. And so it asks us to import it, as well as every other missing model.
This is the error
image

Intuition is telling me, the components should be added with the full paths. Not sure if this is a problem by design.

There are few examples where paths are added to the macro, but I don't think that's our use case. Do let me know if I'm doing something wrong.

Documentation improvement: Workspace usage

I just keep getting this error:
image

Here is the code that produces the error:

#[utoipauto(
	paths = "./src/controller/user.rs"
)]
#[derive(OpenApi)]
#[openapi(modifiers(&SecurityAddon))]
pub struct ApiDoc;

struct SecurityAddon;

impl Modify for SecurityAddon {
	fn modify(&self, openapi: &mut utoipa::openapi::OpenApi) {
		match openapi.components {
			Some(_) => {}
			None => {
				openapi.components = Some(Components::default());
			}
		}
		openapi.components.as_mut().unwrap();
	}
}

Everything works fine when adding paths manually like utoipa intended.

Working with external types

Let's consider the following example:

#[derive(ToSchema, Debug, Serialize)]
pub struct Solution {
    id: u64,
    #[serde_as(as = "HashMap<_, serialize::U256>")]
    prices: HashMap<String, ethereum_types::U256>,
}

Where ethereum_types::U256 is external type and serialize::U256 is a wrapper which implements the following:

#[derive(Debug)]
pub struct U256;

impl<'de> DeserializeAs<'de, ethereum_types::U256> for U256 {
  ...
}

impl SerializeAs<ethereum_types::U256> for U256 {
  ...
}

impl<'s> utoipa::ToSchema<'s> for U256 {
    fn schema() -> (&'s str, RefOr<Schema>) {
        (
            "string",
            ObjectBuilder::new()
                .schema_type(SchemaType::String)
                .into()
        )
    }
}

Is there a way to use utoipauto for this scenario? It looks like I need to explicitly declare which ToSchema to use for the type like it's done for serde: #[serde_as(as = "HashMap<_, serialize::U256>")]. Is there a workaround?

`#[derive(utoipa::ToSchema)]` detection fails

It seems the detection of utoipa::ToSchema is buggy.
I have uploaded an MRE here that I invite you to try out at your convenience.

Generating and comparing both openapi.working.yaml and openapi.broken.yaml indicate that the latter is missing the schema for the Person struct, whereas the former includes it.

$ diff src/broken.rs src/working.rs -U1000

--- src/broken.rs       2024-04-16 14:53:32
+++ src/working.rs      2024-04-16 14:53:35
@@ -1,19 +1,19 @@
-use utoipa::OpenApi;
+use utoipa::{OpenApi, ToSchema};
 use utoipauto;
 
-#[derive(utoipa::ToSchema)]
+#[derive(ToSchema)]
 pub struct Person {
     /// Id of person
     id: i64,
     /// Name of person
     name: String,
 }
 
 pub fn gen_my_openapi() -> String {
-    #[utoipauto::utoipauto(paths = "./src/broken.rs")]
+    #[utoipauto::utoipauto(paths = "./src/working.rs")]
     #[derive(OpenApi)]
     #[openapi()]
     struct ApiDoc;
 
     ApiDoc::openapi().to_pretty_json().unwrap()
 }

It seems the discovery mechanism of utoipauto fails to account for the fully qualified reference to utoipa::ToSchema, and only matches on ToSchema.
I suspect the issue lies somewhere here?
I also assume the same bug affects discovery of utoipa::ToResponse

Customize reported path

Hey, awesome library! In #2, a user asked to have full paths reported; but I want the opposite: I want to be able to customize the path so that the full thing isn't reported.

For example, if I add paths manually, this works- but I don't see a way to both auto-discover and not have the full path displayed.

In the below example (using Scalar to render the docs), the main ApiDoc is configured like so:

// Note: the below code snippet is inside `crate::api`.

#[utoipauto] // <-- This autodiscovers `crate::api::v1::ping`
#[derive(OpenApi)]
#[openapi(info(description = include_str!("../README.md")), paths(
    v0::health::handle, // <-- This is also autodiscovered, but overridden
))]
struct ApiDoc;

The manually added route correctly (for my preferences anyway) appears relative, but the autodiscovered one has the full path; I'm wondering if there's a way to override this behavior to make the path relative.

Screenshot 2024-05-16 at 4 41 58โ€ฏPM

Thanks for your time!

Need for maintainers

TLDR : I'll keep maintaining this crate but I need more people

Hey,
Since this crate as been growing in popularity, we've had more and more features request, and I can't keep up.
If you have an issue or feature request, feel free to submit an issue and a PR.
And if you'd like to get more involved in the maintenance of this crate, please answer to this issue or contact me at [email protected]

Thanks to everyone involved in the growth of this project

Importing structs through their public re-export

Hello ๐Ÿ‘‹๐Ÿป

I'm trying to use this crate to automatically list my routes and models in the the utoipa macro. I tried to modularize my API into nested modules, each containing a models and a routes private submodule, with their contents re-exported publicly by mod.rs (pub use models::*; pub use routes::*;). However, because the structures are defined in a private module, I get the following error:

error[E0603]: module `routes` is private
  --> src/v1/routes.rs:12:10
   |
12 | #[derive(OpenApi)]
   |          ^^^^^^^ private module
   |
note: the module `routes` is defined here
  --> src/v1/members/mod.rs:7:1
   |
7  | mod routes;
   | ^^^^^^^^^^^
help: consider importing this struct through its public re-export instead
   |
12 | #[derive(crate::v1::members::__path_get_members)]
   |          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

error[E0603]: module `models` is private
  --> src/v1/routes.rs:11:1
   |
11 | #[utoipauto]
   | ^^^^^^^^^^^^ private module
   |
note: the module `models` is defined here
  --> src/v1/members/mod.rs:6:1
   |
6  | mod models;
   | ^^^^^^^^^^^
help: consider importing this struct through its public re-export instead
   |
11 | crate::v1::members::Member
   |

warning: unused import: `super::members`
 --> src/v1/routes.rs:6:5
  |
6 | use super::members;
  |     ^^^^^^^^^^^^^^
  |
  = note: `#[warn(unused_imports)]` on by default

For more information about this error, try `rustc --explain E0603`.

My src folder has the following structure

src
โ”œโ”€โ”€ helpers
โ”‚ย ย  โ””โ”€โ”€ mod.rs
โ”œโ”€โ”€ main.rs
โ””โ”€โ”€ v1
    โ”œโ”€โ”€ members
    โ”‚ย ย  โ”œโ”€โ”€ mod.rs
    โ”‚ย ย  โ”œโ”€โ”€ models.rs
    โ”‚ย ย  โ””โ”€โ”€ routes.rs
    โ”œโ”€โ”€ mod.rs
    โ”œโ”€โ”€ routes.rs # Here is the `utoipa` route
    โ””โ”€โ”€ server
     ย ย  โ”œโ”€โ”€ features
     ย ย  โ”‚ย ย  โ”œโ”€โ”€ compliance_tests
     ย ย  โ”‚ย ย  โ”‚ย ย  โ”œโ”€โ”€ mod.rs
     ย ย  โ”‚ย ย  โ”‚ย ย  โ”œโ”€โ”€ models.rs
     ย ย  โ”‚ย ย  โ”‚ย ย  โ””โ”€โ”€ routes.rs
     ย ย  โ”‚ย ย  โ”œโ”€โ”€ config
     ย ย  โ”‚ย ย  โ”‚ย ย  โ”œโ”€โ”€ mod.rs
     ย ย  โ”‚ย ย  โ”‚ย ย  โ”œโ”€โ”€ models.rs
     ย ย  โ”‚ย ย  โ”‚ย ย  โ””โ”€โ”€ routes.rs
     ย ย  โ”‚ย ย  โ””โ”€โ”€ mod.rs
     ย ย  โ””โ”€โ”€ mod.rs

Do you know a way to work around this issue, other than putting routes and models in the same mod.rs file (which I'd like to avoid) or having the modules public (which generates a bad-looking OpenAPI spec)?

Edit: As a workaround, I switched the submodules to be public and I added a tag field in all utoipa::path macros so the generated tags are not ugly (i.e. members instead of crate::v1::members::routes).

Support for generics?

In utoipa we can do like:

#[derive(ToSchema)]
#[aliases(
    FooA = Foo<A>,
    FooB = Foo<B>
)]
pub struct Foo<T> {
    bar: T,
}

and generates two schema FooA and FooB,

and utoipauto has compile error with:

error[E0107]: missing generics for struct `Foo`
   --> src/mod.rs:123:5
    |
123 |     #[utoipauto]
    |     ^^^^^^^^^^^^ expected 1 generic argument
    |
note: struct defined here, with 1 generic parameter: `T`
   --> src/foo.rs:12:12
    |
12  | pub struct Foo<T> {
    |            ^^^^^^^^^^^ -
    = note: this error originates in the attribute macro `utoipauto` (in Nightly builds, run with -Z macro-backtrace for more info)
help: add missing generic argument
    |
123 |     #[utoipauto]<T>
    |                 +++

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.