utoipa
utoipa copied to clipboard
(Master) Issue with namespaced Components
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.
I think it would be good to have the same as
syntax for the handlers.
Yeah. This indeed is something worth thinking. @kellpossible Any takes on this? If there is something that could be improved regarding the namespacing?
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.
@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
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.