ProbablyClem / utoipauto

Rust Macros to automate the addition of Paths/Schemas to Utoipa crate, simulating Reflection during the compilation phase
Apache License 2.0
108 stars 10 forks source link

Importing structs through their public re-export #3

Open RemiBardon opened 8 months ago

RemiBardon commented 8 months ago

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).

ProbablyClem commented 8 months ago

Hey, I don't really see how this can be solved easily... Do you know how you'd want to declare it ? Maybe a new macro on the public reexport ? But tbh I feel like it would start to be very complex for such simple tool.

As you said, you can make the routes public or use the utoipa_ignore macro and import the privates paths manually.

ProbablyClem commented 8 months ago

Or maybe we could add an argument to the #[utoipauto] macro to only add the function name to utoipa, and you would add the use by yourself.

This seems like a great idea, what do you think about it ?

RemiBardon commented 8 months ago

Thank you for your answers. However I'm not sure I understand how it would look like in your last proposed solution. Could you give a simple example?

ProbablyClem commented 8 months ago

right now, utoipauto add the function by full path.

#[utoipauto]
#[derive(OpenApi)]
#[openapi(
    paths(
        test_controller::service::func_get_1,
    ),
)]
pub struct ApiDoc;

This work as is, however, this means that the full path as to be guess by utoipauto

We could add a argument to the macro to only add function name

#[utoipauto(disable_full_path)] //terrible name but we can figure out something better
#[derive(OpenApi)]
#[openapi(
    paths(
        func_get_1, // here, utoipauto would only add the function name
    ),
)]
pub struct ApiDoc;

This obviously won't work because Rust won't find it, but it allows you to add the 'use' line by yourself. like

use crate::v1::members::routes::* //the reexported path, added by you

#[utoipauto(disable_full_path)] 
#[derive(OpenApi)]
#[openapi(
    paths(
        func_get_1, 
    ),
)]
pub struct ApiDoc;
DenuxPlays commented 3 weeks ago

I think this can be solved similiar to how I implemented the generic support. We coud build a map of all public reexports and then match if the struct has been reexported and replace it with the reexport.

This will increase compile times and ram usage but I am not really concered about it. The generic feature is only noticable in large projects and there we build a map of every import for every file. And we could put this feature behind a feature flag so only users who want this are effected.

Although we have to build the map across multiple files. We have to look how we solve this because currently the generic feature drops the map after a file is scanned. And we need to add support for public reexports for the utoipa_ignore macro so that we can define which reexports we want to ignore.

But maybe someone has a better idea or some critic to this approach.