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

Make API errors uniform #480

Merged
merged 5 commits into from
Mar 21, 2023
Merged

Conversation

JosephMarinier
Copy link
Contributor

@JosephMarinier JosephMarinier commented Mar 9, 2023

Description:

Don't worry, this is less than 50 lines of code if we ignore the generated types.

We used to have 3 different schemas of errors (as follows).

  1. The HTTPExceptions we raise in our code usually have a detail string that is used to produce a JSON response. This stays the same. For example:
{
  "detail": "Dataset split not found."
}
  1. The ValidationErrors FastAPI raises have a harder-to-read JSON response. For example
{
  "detail": [
    {
      "loc": [
        "query",
        "pipeline_index"
      ],
      "msg": "ensure this value is greater than or equal to 0",
      "type": "value_error.number.not_ge",
      "ctx": {
        "limit_value": 0
      }
    }
  ]
}

This PR transforms it to

{
  "detail": "1 validation error for Request\nquery -> pipeline_index\n  ensure this value is greater than or equal to 0 (type=value_error.number.not_ge; limit_value=0)"
}

so we can use the detail in the front end:

1 validation error for Request
query -> pipeline_index
  ensure this value is greater than or equal to 0 (type=value_error.number.not_ge; limit_value=0)
  1. The other exceptions are handled as unexpected "internal server error" and the response is plain text (not JSON):
Internal Server Error

This PR transforms it to

{
  "detail": "Internal Server Error"
}

Checklist:

You should check all boxes before the PR is ready. If a box does not apply, check it to acknowledge it.

  • ISSUE NUMBER. You linked the issue number (Ex: Resolve #XXX).
  • PRE-COMMIT. You ran pre-commit on all commits, or else, you
    ran pre-commit run --all-files at the end.
  • USER CHANGES. The changes are added to CHANGELOG.md and the documentation, if they impact
    our users.
  • DEV CHANGES.
    • Update the documentation if this PR changes how to develop/launch on the app.
    • Update the README files and our wiki for any big design decisions, if relevant.
    • Add unit tests, docstrings, typing and comments for complex sections.



async def handle_internal_error(request: Request, exception: Exception):
# Don't expose this unexpected internal error as that could expose a security vulnerability.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could also change that behavior and expose our internal errors. Considering our code is open source, that might not be a real threat.

Copy link
Contributor

@gabegma gabegma Mar 9, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm interesting, maybe we start with not exposing them, and see if it happens often enough?

@JosephMarinier JosephMarinier marked this pull request as ready for review March 9, 2023 04:27
Comment on lines +62 to +67
return JSONResponse(
status_code=HTTP_404_NOT_FOUND # for errors in paths, e.g., /dataset_splits/potato
if "path" in (error["loc"][0] for error in exception.errors())
else HTTP_400_BAD_REQUEST, # for other errors like in query params, e.g., pipeline_index=-1
content={"detail": str(exception)},
)
Copy link
Contributor Author

@JosephMarinier JosephMarinier Mar 9, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure the { "detail": "..." } JSON object wrapping the error message is useful. I could instead return a plain text error message.

Suggested change
return JSONResponse(
status_code=HTTP_404_NOT_FOUND # for errors in paths, e.g., /dataset_splits/potato
if "path" in (error["loc"][0] for error in exception.errors())
else HTTP_400_BAD_REQUEST, # for other errors like in query params, e.g., pipeline_index=-1
content={"detail": str(exception)},
)
return PlainTextResponse(
status_code=HTTP_404_NOT_FOUND # for errors in paths, e.g., /dataset_splits/potato
if "path" in (error["loc"][0] for error in exception.errors())
else HTTP_400_BAD_REQUEST, # for other errors like in query params, e.g., pipeline_index=-1
content=str(exception),
)

I would simply add an exception handler for HTTPException that does the same thing. I could then remove the 500 exception handler, as it was already returning plain text.
However, FastAPI's http_exception_handler() for HTTPException is a little bit more sophisticated as it supports setting response headers, so I thought it was cool to keep it as is, but it's also not the end of the world; just a couple of lines to copy-paste.
Let me know if you have an opinion on this.

Copy link
Contributor

@gabegma gabegma left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll let @christyler3030 make the official review, it's a bit over my head :)

@JosephMarinier JosephMarinier enabled auto-merge (squash) March 21, 2023 16:31
@JosephMarinier JosephMarinier merged commit ae3f592 into main Mar 21, 2023
@JosephMarinier JosephMarinier deleted the joseph/make-api-errors-uniform branch March 21, 2023 17:06
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

Successfully merging this pull request may close these issues.

3 participants