2

I have the following FastAPI backend:

from fastapi import FastAPI

app = FastAPI

class Demo(BaseModel):
    content: str = None
    
@app.post("/demo")
async def demoFunc(d:Demo):
    return d.content

The issue is that when I send a request to this API with extra data like:

data = {"content":"some text here"}aaaa

or

data = {"content":"some text here"aaaaaa}

resp = requests.post(url, json=data)

it throws an error with status code 422 unprocessable entity error with Actual("some text here") and Extra("aaaaa") data in the return field in case of data = {"content":"some text here"}aaaa:

{
  "detail": [
    {
      "loc": [
        "body",
        47
      ],
      "msg": "Extra data: line 4 column 2 (char 47)",
      "type": "value_error.jsondecode",
      "ctx": {
        "msg": "Extra data",
        "doc": "{\n  \"content\": \"some text here\"}aaaaa",
        "pos": 47,
        "lineno": 4,
        "colno": 2
      }
    }
  ]
}

I tried to put the line app=FastAPI() in a try-catch block, however, it doesn't work. Is there any way I can handle this issue with own response instead of the above mentioned auto response? Something like this:

{"error": {"message": "Invalid JSON body"},
                         "status": 0}
Chris
  • 18,724
  • 6
  • 46
  • 80
draaken
  • 25
  • 1
  • 5
  • What do you expect the result to be? This is invalid JSON, so how do you want to parse that? – MatsLindh Feb 02 '22 at 08:51
  • I want to show custom response instead of the auto response from the api itself. – draaken Feb 02 '22 at 12:57
  • Have you seen https://fastapi.tiangolo.com/tutorial/handling-errors/ - it tells you how to override specific errors and handle the response yourself. – MatsLindh Feb 02 '22 at 13:02
  • I saw that but was not able to get it properly. But It is solved now thanks to Chris, Thank you too @MatsLindh – draaken Feb 02 '22 at 13:52

2 Answers2

3

You are passing an invalid JSON, and hence, the server correctly responds with the 422 Unprocessable Entity error. Your test client shouldn't be able to run at all, without throwing an invalid syntax error. So, I'm guessing you posted the request through the interactive autodocs provided by Swagger UI at /docs, and received the relevant 422 error.

If what you actually want is to handle the error, in order to customise the error or something, you can override the request validation exception handler, as described in the documentation (have a look at this discussion, as well as this answer and this answer that demonstrates how to customise the RequestValidationError for specific routes only).

Working Example:

from fastapi import FastAPI, Body, Request, status
from fastapi.encoders import jsonable_encoder
from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse
from pydantic import BaseModel

app = FastAPI()

@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request: Request, exc: RequestValidationError):
    return JSONResponse(
        status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
        content=jsonable_encoder({"detail": exc.errors(),  # optionally include the errors
                "body": exc.body,
                 "custom msg": {"Your error message"}}),
    )

class Demo(BaseModel):
    content: str = None

@app.post("/demo")
async def some_func(d: Demo):
    return d.content

Or, you could also return a PlainTextResponse with a custom message:

from fastapi.responses import PlainTextResponse

@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request, exc):
    return PlainTextResponse(str(exc), status_code=422) 
Chris
  • 18,724
  • 6
  • 46
  • 80
  • I know its a invalid JSON data and that is the whole point. Actually it is the case of Cross-Site Scripting test, where a tester uses different method to get information in using the request body itself. For example one can put a script in request body like : `{"content":"some text"}` So, I know that the API will throw 422 error, but it also returns the request body itself which I don't want to show. That's why I asked how can I catch this and display my own error msg – draaken Feb 02 '22 at 12:42
  • and I was using JSONResponse to display my very own messages like mentioned below: `return JSONResponse( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, content={"error": {"message": "Invalid JSON body"}, "status": 0} )` – draaken Feb 02 '22 at 12:49
  • Chris, I tried your code but it is still the same `{ "detail": [ { "loc": [ "body", 45 ], "msg": "Expecting ',' delimiter: line 3 column 24 (char 45)", "type": "value_error.jsondecode", "ctx": { "msg": "Expecting ',' delimiter", "doc": "{\n \"gender\": \"male\",\n \"language\": \"English\"aaaaa\n}", "pos": 45, "lineno": 3, "colno": 24 } } ], "custom msg": { "error": { "message": "Invalid json format" }, "status": 0 } }` – draaken Feb 02 '22 at 12:54
  • 1
    Never mind I modified the answer provided and it works for now. Thankyou @Chris – draaken Feb 02 '22 at 13:24
0

When you send a request with body, the FastAPI will try to deserialize the body with loads function from built-in json module and the error you get was raised by this function.

So if you want to customize the response, you must make an exception handler in the same way that Chris said and handle it. Something like that:

from fastapi import status
from fastapi.responses import JSONResponse
from fastapi.exceptions import RequestValidationError


app = FastAPI()

@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request: Request, exc: RequestValidationError):
   errors = exc.errors()
   response = []
   for error in errors:
      if error['type'] == 'value_error.jsondecode':
         response.append({'error': {'message': 'Invalid JSON body'}, 'status': 0})
      else:
      response.append(error)
   return JSONResponse(content=response, status_code=status.HTTP_422_UNPROCESSABLE_ENTITY)