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

(Master) Issue with namespaced Components #230

Closed
RemiKalbe opened this issue Jul 25, 2022 · 5 comments
Closed

(Master) Issue with namespaced Components #230

RemiKalbe opened this issue Jul 25, 2022 · 5 comments

Comments

@RemiKalbe
Copy link

RemiKalbe commented Jul 25, 2022

Say I have the following handler

#[utoipa::path(
    request_body = request::SomeRequestBody,
    responses(
        (status = 200, body = response::SomeResponseBody),
    ),
)]

And it is defined like so in the OpenApiDoc

#[openapi
    (handlers(
        handlers::some_mod::another_one::one_last::my_handler,
    ),
    components(
        handlers::some_mod::another_one::one_last::request::SomeRequestBody,
        handlers::some_mod::another_one::one_last::response::SomeResponseBody,
    )
)]

This will result in the following path in the spec

"/api/v1/some/path": {
      "post": {
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/request.SomeRequestBody"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/response.SomeResponseBody"
                }
              }
            }
          }
        },
        "deprecated": false
      }
    },

But, the component will not be named the same, there is no response.

"SomeResponseBody": {
        "type": "object",
        "required": ["something"],
        "properties": { "something": { "type": "string" } }
      },

If I change my OpenApiDoc like so it solves the problem

#[openapi
    (handlers(
        handlers::some_mod::another_one::one_last::my_handler,
    ),
    components(
        handlers::some_mod::another_one::one_last::request::SomeRequestBody as request::SomeRequestBody,
        handlers::some_mod::another_one::one_last::response::SomeResponseBody as response::SomeResponseBody,
    )
)]

Not sure what's the best approach for this, but I think I should point that out.

@RemiKalbe
Copy link
Author

I think it would be good to have the same as syntax for the handlers.

@juhaku
Copy link
Owner

juhaku commented Jul 25, 2022

Yeah. This indeed is something worth thinking. @kellpossible Any takes on this? If there is something that could be improved regarding the namespacing?

@kellpossible
Copy link
Contributor

kellpossible commented Jul 25, 2022

I think there's not really any other way, it seems like we do need something like the proposed as syntax.

As a side note, I'm not using the #[openapi( macro any more, I found it was less flexible, more magical, and not easier than using the OpenApiBuilder directly, and making my own little convenience macros for defining components, tags, and paths.

macro_rules! tag {
    ($name:literal, $description:literal) => {
        utoipa::openapi::tag::TagBuilder::new()
            .name($name)
            .description(Some($description))
            .build()
    };
}

macro_rules! component {
    ($c:path) => {
        (stringify!($c).replace("::", "."), <$c as utoipa::Component>::component())
    };
}

pub struct ApiDoc;

impl OpenApi for ApiDoc {
    fn openapi() -> utoipa::openapi::OpenApi {
        let tags = vec![
            tag!("mytag", "descrfiption"),
        ];

        let components = vec![
            component!(path::to::MyComponent),
        ];

        let components = components 
            .into_iter()
            .fold(ComponentsBuilder::new(), |acc, (name, component)| {
                acc.schema(name, component) // this could be called acc.component() currently I think
            })
            .build();

        OpenApiBuilder::new()
            .info(InfoBuilder::new().title("my-service").build())
            .paths(paths)
            .components(Some(components))
            .tags(Some(tags))
            .build()
    }
}

My paths procedural macro (not included here) creates a warp filter with all the paths, and also generates the openapi paths at the same time with the same definition which is neat.

I'm guessing component! and/or the usage thereof could probably be trivially extended to support the as syntax.

@kellpossible
Copy link
Contributor

@RemiKalbe Here's my version of the as syntax:

macro_rules! component {
    ($c:path) => {
        (stringify!($c).replace("::", "."), <$c as utoipa::Component>::component())
    };
    ($c:path as $p:path) => {
        (stringify!($p).replace("::", "."), <$c as utoipa::Component>::component())
    };
}

Playground example (you can use the macro expansion tool to see what it does):
https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=5955a96db6f6e94198cc026e25b96a0d

@juhaku
Copy link
Owner

juhaku commented Feb 15, 2023

Closing this issue due inactivity. This is related to this #435 (comment) and #459 where the behavior has been changed to be more clear.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants