awslabs / aws-lambda-rust-runtime

A Rust runtime for AWS Lambda
Apache License 2.0
3.3k stars 335 forks source link

Question: Handling custome error types that impl `IntoResponse` #822

Closed JuxhinDB closed 5 months ago

JuxhinDB commented 6 months ago

Take the following lambda_http runtime invokation.

lambda_http::run(service_fn(|event: Request| {
    apigateway::domain::list_domains(&store, event)
}))
.await?;

Which calls the following api gateway handler:

pub async fn list_domains<S: DomainStore>(
    store: &S,
    event: Request,
) -> Result<DomainCollectionResponse, ResponseError> {
    let user_id = ListDomains::try_extract(event)?.user_id;
    let domains = domain::get_domains(store, &user_id).await?;
    Ok(DomainCollectionResponse(domains))
}

Where ResponseError is a custom error type that impls IntoResponse. This handles scenarios such as user context ids being invalid (i.e., not valid uuids) which returns

impl IntoResponse for ResponseError {
    fn into_response(self) -> ResponseFuture {
        let res: (StatusCode, Body) = match self {
            ResponseError::BadRequest(e) => {
                let msg = json!({"msg": e});
                (StatusCode::BAD_REQUEST, serialize(&msg))
            }
        // Truncated
    }
}

When calling the function which triggers a 400/401, we instead get a 500 internal server from the lambda runtime:

Request
curl -vvv -H "X-User: 1" http://localhost:9000/lambda-url/list-domains/
Response
{"type":"https://httpstatuses.com/500","status":500,"title":"Internal Server Error","detail":"missing field `statusCode` at line 1 column 94"}

It's unclear if this is an issue with cargo lambda, or with lambda_http, but I was hoping to get an idea on how to simply call into_response on error variants.

calavera commented 6 months ago

Have you tried deploying your function to AWS and see what the response is? At first glance, this might be a problem with how Cargo Lambda deserializes the response. You can get a lot more logs if your run the watch command with the -vv flag.

JuxhinDB commented 6 months ago

Hey @calavera apologies for the late reply. Unfortunately the behaviour is the same when deploying the lambda.

{
    "timestamp": "2024-02-27T21:31:42.693853Z",
    "level": "ERROR",
    "fields": {
        "message": "Unauthorized"
    },
    "target": "lambda_runtime",
    "span": {
        "requestId": "73c19e8f-8fac-4d4f-9f63-2a3b540509f3",
        "xrayTraceId": "Root=1-65de54be-2286435158768b4f12a54417;Parent=75b5f0cd57cf879e;Sampled=0;Lineage=ba571b3e:0",
        "name": "Lambda runtime invoke"
    },
    "spans": [
        {
            "requestId": "73c19e8f-8fac-4d4f-9f63-2a3b540509f3",
            "xrayTraceId": "Root=1-65de54be-2286435158768b4f12a54417;Parent=75b5f0cd57cf879e;Sampled=0;Lineage=ba571b3e:0",
            "name": "Lambda runtime invoke"
        }
    ]
}

Which externally just emits, Internal Server Error. Any thoughts?

calavera commented 5 months ago

Sorry for the super late reply to this. I wanted to make sure I understood the situation. Unfortunately the behavior that you're experiencing is correct, although poorly documented.

When you return an errors in Lambda, it's considered a catastrophic failure. The response code is 500 because lambda will ignore any traits or transformation in the error, and it will only take an error body with errorType and errorMessage as fields. The Rust runtime takes the value of implementing Display as the errorMessage, and the error type itself as errorType. If you want to return a different status code in your API, you actually have to return an OK value, with the error code that you want your user to see.

So basically, the answer to this is that IntoResponse is never taken into account in error types because Lambda doesn't consider any status code in error types, it only allows those two error fields.

Let me know if this makes sense. I believe we cannot do anything about this, but I'd like to document it a little bit better.

calavera commented 5 months ago

Btw, I'm pretty sure you're getting this error in Cargo Lambda because you're using an old version. I just reproduced your code and I get a different result:

{"type":"https://httpstatuses.com/500","status":500,"title":"Internal Server Error","detail":"missing field `statusCode` at line 1 column 94"}

image

JuxhinDB commented 5 months ago

Got it, thanks for the clarification @calavera. But annoying but it's something we can work around eventually. Closing off the issue.

github-actions[bot] commented 5 months ago

This issue is now closed. Comments on closed issues are hard for our team to see. If you need more assistance, please either tag a team member or open a new issue that references this one.