netwo-io/apistos

`.build()` Loading only the last component definitions

Closed this issue · 4 comments

I have an axtic-web API that is supposed to handle the uploading of files. The API has the following routes:

  • GET /ping - Check to see if the service is available and also return the version
  • POST /files/upload - Upload a new file
  • GET /files/{id}/stream - Download a file
  • DELETE /files/{id} - Delete a file

The /ping route looks something like this

#[derive(Serialize, ApiComponent, JsonSchema)]
enum Status {
    Available,
}

#[derive(Serialize, ApiComponent, JsonSchema)]
struct PingResponse<'a> {
    status: Status,
    version: &'a str,
}

#[api_operation(
    summary = "Route for applications to call to see if this service is available",
    description = "Route for applications to call to see if this service is available",
    produces = "application/json"
)]
pub async fn ping() -> Json<PingResponse<'static>> {
    let version = match LOGGER.get() {
        None => "Unknown",
        Some(l) => match &l.config {
            None => "Unknown",
            Some(c) => &c.app_version,
        },
    };

    return Json(PingResponse {
        status: Status::Available,
        version,
    });
}

This is how i am building the app with apistos:

// ...
let app = app
    .document(spec)
    .service(resource("/ping").route(web::get().to(ping)))
    .service(
        scope("/files")
            .service(resource("/upload").route(web::post().to(upload::<
                FilesystemContext,
                FilesystemContext,
                ClientSession,
                Auth,
                FilesCollection,
                UsersCollection,
                Storage,
            >)))
            .service(resource("/{object_id}/stream").route(web::post().to(stream::<
                FilesystemContext,
                FilesystemContext,
                ClientSession,
                Auth,
                FilesCollection,
                Storage,
            >)))
            .service(resource("/{object_id}").route(web::delete().to(delete::<
                FilesystemContext,
                FilesystemContext,
                ClientSession,
                Auth,
                FilesCollection,
                FilesDatabase,
                Storage,
            >))),
    )
    .build("/spec");
// ...

When i now call the /spec route to get the spec i only get the following schemas with the PingResponse not being included.

image

I already tried to group the two services in a new scope with the path "" which made the /spec route unreachable

// ...
 .service(
    scope("")
        .service(resource("/ping").route(web::get().to(ping)))
        .service(
            scope("/files")
                .service(resource("/upload").route(web::post().to(upload::<
                    FilesystemContext,
                    FilesystemContext,
                    ClientSession,
                    Auth,
                    FilesCollection,
                    UsersCollection,
                    Storage,
                >)))
                .service(resource("/{object_id}/stream").route(web::post().to(stream::<
                    FilesystemContext,
                    FilesystemContext,
                    ClientSession,
                    Auth,
                    FilesCollection,
                    Storage,
                >)))
                .service(resource("/{object_id}").route(web::delete().to(delete::<
                    FilesystemContext,
                    FilesystemContext,
                    ClientSession,
                    Auth,
                    FilesCollection,
                    FilesDatabase,
                    Storage,
                >))),
            ),
    )
   .build("/spec");
// ...

I also tried adding a / to the top level scope and removing it from the children, but that also didn't work.
How can i have these two routes add their models to the component definitions?

Hi, I'm having a look.

Can second on having this issue.

I am using multiple .configures to setup the routes, and only definitions from the last .configure get into the resulting openapi.json

Also, was able to pin down the likely source of the issue to update_from_def_holder function.
A quick and dirty patch to let mut components = definition_holder.components().into_iter().chain(open_api_spec.components.clone()).reduce(...) results in the resulting definitions being fully defined

ryukyi commented

Same issue for me!

I have:

    HttpServer::new(move || {
        let cors = Cors::default()
            .allowed_origin("http://localhost:8080")
            .allowed_methods(vec!["GET", "POST"])
            .allowed_headers(vec![
                header::CONTENT_TYPE,
                header::AUTHORIZATION,
                header::ACCEPT,
            ])
            .supports_credentials();

        App::new()
            .document(spec.clone())
            .wrap(Logger::default())
            .app_data(web::Data::new(AppState {
                pool: pool.clone(),
                config: config.clone(),
            }))
            .service(
                scope("/v1/auth")
                    .service(resource("/signup").route(post().to(route::auth::signup)))
                    .service(resource("/login").route(post().to(route::auth::login_user)))
                    .service(resource("/logout").route(get().to(route::auth::logout_user))),
            )
            .service(
                scope("/api/v1")
                    .service(
                        scope("/user")
                            .service(resource("/whoami").route(get().to(route::auth::who_am_i))),
                    )
                    .service(
                        scope("/scenario")
                            .service(
                                resource("/get_scenarios")
                                    .route(get().to(route::scenario::get_scenarios)),
                            )
                            .service(
                                resource("/create_scenario")
                                    .route(post().to(route::scenario::post_scenario)),
                            )
                            .service(
                                resource("/update_scenario")
                                    .route(put().to(route::scenario::update_scenario)),
                            ),
                    )
                    .service(
                        scope("/detection")
                            .service(
                                resource("/get_detections")
                                    .route(get().to(route::detection::get_detections)),
                            )
                            .service(
                                resource("/create_detection")
                                    .route(post().to(route::detection::post_detection)),
                            )
                            .service(
                                resource("/create_detections")
                                    .route(post().to(route::detection::post_detections)),
                            )
                            .service(
                                resource("/update_detection")
                                    .route(put().to(route::detection::put_detection)),
                            ),
                    )
                    .service(
                        scope("/health").service(
                            resource("/check").route(get().to(route::health::health_check)),
                        ),
                    ),
            )
            .build_with(
                "/v1/openapi.json",
                BuildConfig::default()
                    .with(RapidocConfig::new(&"/v1/docs/rapidoc"))
                    .with(RedocConfig::new(&"/v1/docs/redoc"))
                    .with(ScalarConfig::new(&"/v1/docs/scalar"))
                    .with(SwaggerUIConfig::new(&"/v1/docs/swagger")),
            )
            .wrap(cors)
    })
    .bind(server_address)?
    .run()
    .await?;

So sorry about the long delay since this issue as been reported.
I spend more time than planned trying to include new things into v1.

A fix is on it's way and will be release tonight for v0.x.