utoipa icon indicating copy to clipboard operation
utoipa copied to clipboard

Is there a way to concatenate context_path attributes?

Open valnoel opened this issue 2 years ago • 2 comments

Hi !

First, thanks for your work on on this very useful crate!

I have a lib that exposes warp routes, that I can use in several warp-based server projects, but I cannot find the way to concatenate paths between utoipa::path definitions to make them consistent with the warp route paths.

Here is a more explicit example:

my_lib:

use warp::{Filter, Rejection, Reply};

pub fn services() -> impl Filter<Extract = (impl Reply,), Error = Rejection> + Clone {
    warp::path("my_lib").and(action_1().or(action_2()))
}

/// Action 1 description
#[utoipa::path(
  get,
  context_path = "/my_lib",
  path = "/action_1",
  responses((status = 200, description = "Action 1"))
)]
fn action_1() -> impl Filter<Extract = (impl Reply,), Error = Rejection> + Clone {
    warp::path("action_1").and(warp::get()).map(|| "Action 1")
}

/// Action 2 description
#[utoipa::path(
  get,
  context_path = "/my_lib",
  path = "/action_2",
  responses((status = 200, description = "Action 2"))
)]
fn action_2() -> impl Filter<Extract = (impl Reply,), Error = Rejection> + Clone {
    warp::path("action_2").and(warp::get()).map(|| "Action 2")
}

Exposed routes are:

GET /my_lib/action_1
GET /my_lib/action_2

my_server:

use utoipa::OpenApi;
use warp::{Filter, Rejection, Reply};

#[derive(OpenApi)]
#[openapi(paths(action_3, my_lib::action_1, my_lib::action_2))]
struct ApiDoc;

#[tokio::main]
async fn main() {
    let routes = warp::path("server").and(my_lib::services().or(action_3()));

    let doc = warp::path("doc")
        .and(warp::get())
        .map(|| warp::reply::json(&ApiDoc::openapi()));

    warp::serve(routes.or(doc))
        .run(([127, 0, 0, 1], 8080))
        .await;
}

/// Action 3 description
#[utoipa::path(
  get,
  context_path = "/server",
  path = "/action_3",
  responses((status = 200, description = "Action 3"))
)]
fn action_3() -> impl Filter<Extract = (impl Reply,), Error = Rejection> + Clone {
    warp::path("action_3").and(warp::get()).map(|| "Action 3")
}

Exposed routes are:

GET /server/my_lib/action_1
GET /server/my_lib/action_2
GET /server/action_3

Problem:

but the OpenAPI generated document shows routes as they were:

GET /my_lib/action_1
GET /my_lib/action_2
GET /server/action_3

Would you have a proposal to solve my issue?

Thanks!

valnoel avatar Jan 11 '23 11:01 valnoel

Hey @valnoel,

Sorry for late reply,

First, thanks for your work on on this very useful crate!

Thanks :slightly_smiling_face: Good that it is useful.

This is a bit tricky area and will most likely need some focus in future. Currently there is no correct answer to this and bigger APIs have implemented them in various ways.

Instead of context_path or along with it you can use Servers. You can define custom servers for your API to use that have different API path. And then you can use that with your endpoint from Swagger UI. By default when there is no servers defined Swagger UI will define a single Server with url / pointing to root.

You could define at least server that points to /server. If you define one server then that is the default and all the paths will be expected to be prefixed with the /server when called from Swagger UI.

juhaku avatar Jan 25 '23 00:01 juhaku

Hi @juhaku ,

Thanks for your solution proposal!

Actually, my example was incomplete since, in real life, I don't want all the paths to be prefixed, so the Server usage doesn't give the expected result. But for the example it makes the trick! :wink:

So I'm going to keep my way to merge the API documentations, maybe I'll share it if it turns out to be generic enough...

valnoel avatar Jan 30 '23 15:01 valnoel