tamasfe / aide

An API documentation library
Apache License 2.0
417 stars 70 forks source link

how to infer error responses? #145

Open xblurx opened 4 months ago

xblurx commented 4 months ago

Hi there and thank you for this awesome tool!
In one of the projects that is using aide I saw that their generated json schema contained not only 200 response but all of the error responses too, however I could not achieve similar results (I am kind of new to this)
I implemented IntoResponse from my Error enum and all of my handlers share the same behaviour. Here's a short presentation of my current setup:

/// main.rs
fn router(cp) {
    ApiRouter::new()
        .api_route("/", api::get(read)).layer(Extension(cp))
}
let routes = router(cp);
let mut api = OpenApi::default();
_ = routes.finish_api(&mut api);
fs::write(
        "openapi/schema.json",
        serde_json::to_string_pretty(&api).expect("Failed to serialize JSON"),
    )
    .expect("Unable to write file");

/// api.rs
async fn read(
    Extension(cp): Extension<SqlitePool>,
    Query(params): Query<ReadParams>,
) -> Result<Json<Vec<Out>>> {
    let res = db::read()
    .await
    .map_err(|_| Error::NotFound)?; // 

    Ok(Json(res))
}

I'd like to see this json schema generated:

"openapi": "3.0.2",
    "paths": {
        "/api/v1/app": {
            "get": {
                "parameters": [...],
                "requestBody": {
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/ApplicationIn"
                            }
                        }
                    },
                    "required": true
                },
                "responses": {
                    "200": {...},
                    "404": {...},
                },
            }
        }

and i only have responses 200 in my case. Any help would be greatly appreciated!

EDIT:
turned out, guys from the project I was referring to implemented some custom schema processing, when those error responses were added This can be closed, but if there is something similar in functionality in aide out of the box, I'll be glad to find out!

2vg commented 3 months ago

@xblurx how do u resolve it, could u show me the code..? thanks!

netthier commented 3 months ago

No idea about inferring error responses, but I usually explicitly add my errors to documentation by just adding responses to a TransformOperation, i.e.:

ApiRouter::new()
  .api_route("/whatever", get_with(whatever_handler, whatever_handler_docs))
  // etc.

fn whatever_handler_docs(op: TransformOperation) -> TransformOperation {
  op.summary("This endpoint does whatever")
    .response::<200, Json<SomeResponse>>()
    .response_with::<404, Json<ApiErrorResponse>, _>(|res| {
      res.description("Whatever you're doing, it's not there")
        .example(ApiError::NotFound)
    })
    .response_with::<500, Json<ApiErrorResponse>, _>(|res| {
      res.description("Internal server error")
        .example(ApiError::internal_error())
    })
}

where ApiError: IntoResponse + Into<ApiErrorResponse> with the output of IntoResponse being Json<ApiErrorResponse>. Repetitive error messages can be factored out using TransformOperation::with. IMO this pattern is more useful, as not all endpoints can always return any error, and some errors might warrant extra endpoint-specific context.

2vg commented 3 months ago

@netthier yeah i know it, but I was just curious about what xblurx said about "custom schema processing". I wanted to know how to automatically reflect in the documentation any changes in the errors returned, like a if we changed 400 -> 500, schema is should be:

"responses": {
    "200": {...},
    "500": {...},
    // ↓ that was removed, i expect
    "400": {...},
},

But what you say is of course helpful, and these will depend on how error handling is done in the project.

xblurx commented 1 month ago

@2vg here is the code that i used for implementing this, in other words I implemented OperationOutput for ServerError that i return from routes handlers

/// The way to add enum of ServerError status codes
/// to generated json schema
/// the other one is to use impl IntoApiResponse return type in handlers
/// and implement a struct that will contain either result or error
impl OperationOutput for ServerError {
    type Inner = ();

    fn inferred_responses(
        ctx: &mut aide::gen::GenContext,
        operation: &mut aide::openapi::Operation,
    ) -> Vec<(Option<u16>, aide::openapi::Response)> {
        if let Some(res) = Self::operation_response(ctx, operation) {
            vec![
                (
                    Some(400),
                    aide::openapi::Response {
                        description: "The request is invalid".to_owned(),
                        ..res.clone()
                    },
                ),
                (
                    Some(403),
                    aide::openapi::Response {
                        description: "Insufficient permissions".to_owned(),
                        ..res.clone()
                    },
                ),
                (
                    Some(404),
                    aide::openapi::Response {
                        description: "The requested data was not found".to_owned(),
                        ..res.clone()
                    },
                ),
                (
                    Some(500),
                    aide::openapi::Response {
                        description: "An internal server error occured".to_owned(),
                        ..res
                    },
                ),
            ]
        } else {
            Vec::new()
        }
    }

    fn operation_response(
        _ctx: &mut aide::gen::GenContext,
        _operation: &mut aide::openapi::Operation,
    ) -> Option<aide::openapi::Response> {
        Some(aide::openapi::Response::default())
    }
}
2vg commented 1 month ago

@xblurx thanks, that was i wanted!