Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Missing actix::web::scope support #121

Closed
bhoudebert opened this issue Apr 28, 2022 · 4 comments · Fixed by #1158
Closed

Missing actix::web::scope support #121

bhoudebert opened this issue Apr 28, 2022 · 4 comments · Fixed by #1158
Labels
enhancement New feature or request

Comments

@bhoudebert
Copy link

There is many way to declare route with actix::web but for one of them is using scope (which is a must have imho, even better with macro route usage)
https://docs.rs/actix-web/3.3.3/actix_web/struct.Scope.html

/// main.rs
// app definition
  ...
  .service(web::scope("/health").service(ping_pong))

/// health.rs
#[utoipa::path(
    tag = "Health",
    responses(
        (status = 200
         , description = "It should respond with \"pong\""
         , body = String),
    ),
)]
#[get("/ping")]
pub async fn ping_pong() -> impl Responder {
  HttpResponse::Ok().body("pong")
}

What will happen here is that if we do not explicitly declared the path (with full value) it will just be generate on openapi with /ping instead of /health/ping.

Example here is simple but in an application with a lot of subscope: version, resources, action, sub resources it becomes more and more tricky to be sure to keep everything aligned (people will obviously drop declaring partial scope in favor or routes hidden in some other files),

Would it be possible to support scope?

@juhaku
Copy link
Owner

juhaku commented Apr 28, 2022

This is supported. But needs manual intervention since unfortunately there is no way to access to information given to web::scope(...) function.

  1. Yo could declare context_path within utoipa::path macro: See example here: https://github.com/juhaku/utoipa/blob/master/examples/actix-web-multiple-api-docs-with-scopes/src/main.rs
...
  .service(web::scope("/health").service(ping_pong))
/// ....
#[utoipa::path(
    context_path = "/health",
    tag = "Health",
    responses(
        (status = 200
         , description = "It should respond with \"pong\""
         , body = String),
    ),
)]
  1. You could use Server type found within OpenAPI https://docs.rs/utoipa/1.0.1/utoipa/openapi/server/struct.Server.html
impl Modify for CustomServer {
  fn modify(&self, openapi: &mut OpenApi) {
    openapi.servers.as_mut().map(|servers| {
      servers.push(Server::new("/api/v1"))
    })
  }
}

Server can also be defined in PathItem level, but accessing it might be cumber some so far since I haven't yet added ability to add it via utoipa::path or #[openapi(...)] attributes.

Context path is exactly to counter this scoped paths issue and it will concatenated to the path. And servers can be used to change the actual path or even server where the api request would go but will generage the same paths without the scope.

@bhoudebert
Copy link
Author

Yes I got that helper but was hoping to get something magic. To me, this far from safe as it would mean the scope is duplicated between the actix macro and the app definition and there is no guarantee you wrote something correct.

NB: The only solution I had in mind, did not used it yet, was to use several global variables but it is far from a gold solution.

If we imagine writing something like this (totally random code/entity, it does not make sense):

App::new()
    .service(ping_pong)
    .service(
      web::scope("/v1")
        .service(web::scope("/resource_one").service(resource_one_r::get_all))
        .service(web::scope("/games").service(games_r::get_all))
        .service(
          web::scope("/auth")
            .service(auth_r::login)
            .service(auth_r::register)
            .service(users_r::get_me)
            .service(auth_r::verify),
        )
        .service(
          web::scope("/users")
            .service(
              web::scope("/me/requests")
                .service(current_crate::routes::user_requests_r::add_request)
                .service(current_crate::routes::user_requests_r::cancel_my_request)
                .service(current_crate::routes::user_requests_r::get_closed_request)
                .service(
                  current_crate::routes::user_requests_r::get_pending_request_from_user,
                ),
            )
            .service(
              web::scope("/me/sidekicks")
                .service(current_crate::routes::user_sidekick_r::get_all)
                .service(
                  web::scope("/sidekick_requests")
                    .service(
                      current_crate::routes::user_sidekick_r::validate_request,
                    )
                    .service(current_crate::routes::user_sidekick_r::refuse_request),
                )
                .service(web::scope("/auto_requests").service(
                  current_crate::routes::user_requests_r::auto_user_request,
                )),

In that peculiar case would you agree that even using context_path is cumbersome and prone to errors? I understand that it might not be possible as if I compare with other crates that does this, they create an actix::web around all service definition which I did find a little bit too intrusive.

@juhaku
Copy link
Owner

juhaku commented Apr 29, 2022

Yes I agree its is far from perfect and is prone to errors and unnecessary duplication. Currently context_path is not able to receive a variable reference and only expects literal string. Yeah I originally thought about this and didn't want to implement with the context_path but I found no alternative ways to do it. So so far it is best we got.

Also I want to keep the the utoipa library seprated as much as possible from the internals and management of other frameworks as possible. It could be possible if we wrap the application or the scopes with utoipa specific macro function that would do the context_path gathering from scopes something like this:

App::new()
    .service(ping_pong)
    .service(
       utoipa::scoped!(
           web::scope("/v1")
               .service(resource_one_r::get_all)
       ) 

But then not sure how to attach the scope to the get_all endpoint. And with this approach people need to mix and match the application with some random external functions.

juhaku added a commit that referenced this issue Oct 21, 2024
Implements `ServiceConfig` for actix-web with scope support. This allows
users to register `services` directly to OpenApi via `utiopa` App and
custom service config without the need to register paths via
`#[openapi(paths(...))]`.

Closes #121
juhaku added a commit that referenced this issue Oct 22, 2024
Implements wrappers for `ServiceConfig`, `App`, `Scope` of actix-web.
This allows users to create `App` with collecting `paths` and `schemas`
recursively without registering them to `#[openapi(...)]` attribute.

Example of new supported syntax.
```rust
 use actix_web::{get, App};
 use utoipa_actix_web::{scope, AppExt};

 #[derive(utoipa::ToSchema)]
 struct User {
     id: i32,
 }

 #[utoipa::path(responses((status = OK, body = User)))]
 #[get("/user")]
 async fn get_user() -> Json<User> {
     Json(User { id: 1 })
 }

 let (_, mut api) = App::new()
     .into_utoipa_app()
     .service(scope::scope("/api/v1").service(get_user))
     .split_for_parts();
```

Relates #283 Relates #662
Closes #121 Closes #657
@juhaku
Copy link
Owner

juhaku commented Oct 22, 2024

@bhoudebert Finally there is coming actix_web::scope support. PR #1158

@juhaku juhaku added the enhancement New feature or request label Oct 22, 2024
juhaku added a commit that referenced this issue Oct 22, 2024
Implements wrappers for `ServiceConfig`, `App`, `Scope` of actix-web.
This allows users to create `App` with collecting `paths` and `schemas`
recursively without registering them to `#[openapi(...)]` attribute.

Example of new supported syntax.
```rust
 use actix_web::{get, App};
 use utoipa_actix_web::{scope, AppExt};

 #[derive(utoipa::ToSchema)]
 struct User {
     id: i32,
 }

 #[utoipa::path(responses((status = OK, body = User)))]
 #[get("/user")]
 async fn get_user() -> Json<User> {
     Json(User { id: 1 })
 }

 let (_, mut api) = App::new()
     .into_utoipa_app()
     .service(scope::scope("/api/v1").service(get_user))
     .split_for_parts();
```

Relates #283 Relates #662
Closes #121 Closes #657
juhaku added a commit that referenced this issue Oct 22, 2024
Implements wrappers for `ServiceConfig`, `App`, `Scope` of actix-web.
This allows users to create `App` with collecting `paths` and `schemas`
recursively without registering them to `#[openapi(...)]` attribute.

Example of new supported syntax.
```rust
 use actix_web::{get, App};
 use utoipa_actix_web::{scope, AppExt};

 #[derive(utoipa::ToSchema)]
 struct User {
     id: i32,
 }

 #[utoipa::path(responses((status = OK, body = User)))]
 #[get("/user")]
 async fn get_user() -> Json<User> {
     Json(User { id: 1 })
 }

 let (_, mut api) = App::new()
     .into_utoipa_app()
     .service(scope::scope("/api/v1").service(get_user))
     .split_for_parts();
```

Relates #283 Relates #662
Closes #121 Closes #657
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
Status: Released
Development

Successfully merging a pull request may close this issue.

2 participants