Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

how to infer error responses? #145

Open
xblurx opened this issue Jul 22, 2024 · 5 comments
Open

how to infer error responses? #145

xblurx opened this issue Jul 22, 2024 · 5 comments

Comments

@xblurx
Copy link

xblurx commented Jul 22, 2024

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
Copy link

2vg commented Aug 8, 2024

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

@netthier
Copy link

netthier commented Aug 8, 2024

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
Copy link

2vg commented Aug 8, 2024

@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
Copy link
Author

xblurx commented Oct 17, 2024

@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
Copy link

2vg commented Oct 17, 2024

@xblurx thanks, that was i wanted!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants