utoipa icon indicating copy to clipboard operation
utoipa copied to clipboard

(Master) Issue with namespaced Components

Open RemiKalbe opened this issue 2 years ago • 4 comments

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 avatar Jul 25 '22 09:07 RemiKalbe

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

RemiKalbe avatar Jul 25 '22 10:07 RemiKalbe

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

juhaku avatar Jul 25 '22 20:07 juhaku

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 avatar Jul 25 '22 22:07 kellpossible

@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

kellpossible avatar Jul 25 '22 22:07 kellpossible

Closing this issue due inactivity. This is related to this https://github.com/juhaku/utoipa/issues/435#issuecomment-1405448818 and https://github.com/juhaku/utoipa/pull/459 where the behavior has been changed to be more clear.

juhaku avatar Feb 15 '23 22:02 juhaku