I use OpenAPI Generator's typescript-fetch client in most projects I build. For the most part, it does the job, but it wasn't super obvious how I could capture errors as part of my response.
Let's use this code as a jumping off point:
try {
let something = await doSomething()
} catch (error) {
console.log(error)
}
A lot of examples that you'll find on the internet do exactly this and then stop there. The result of such an error is generally, ~"An error response was received." This seems easy enough and probably works for many naive usecases, but I have an editor page that talks to an API that can give pretty descriptive errors depending on the response code. I wanted to be able to pop those in a toast and update the form so that I knew what went wrong because I forget my own business logic all the time. I was also kind of tired of looking in Developer Tools to know what went wrong.
I started by making my error schema official. Django-Ninja comes with a built-in ValidationError
that has a single field detail
.
from ninja import Schema
class ValidationErrorResponse(Schema):
detail: str
Then I started catching the exceptions that aren't caught by Schema (example: uniqueness errors)
@posts_router.post(
"/",
response={200: PostDetails, 422: ValidationErrorResponse},
tags=["posts"],
auth=JWTAuth(permissions=StaffOnlyModify),
)
def create_post(request: HttpRequest, post: PostMutate):
try:
post = Post.objects.create(**post.dict(), author=request.user)
except IntegrityError as err:
logger.error("Error creating post", error=err)
raise ValidationError("Post with this slug already exists") from err
return post
You'll also notice I specified a 422
response code and applied the schema to it. Since Django
Ninja validation errors always map to the schema, I didn't need to do any extra work there. There is
one last bit of work I needed to do though, and that's on the actual typescript-fetch catch
branch:
try {
const post = await api.blogApiCreatePost({
postMutate: {
title: title,
slug: slug,
published: parseDate(published_at),
content: content,
},
})
id = post.id
addToast({
message: 'Created post!',
})
} catch (error) {
if (error instanceof ResponseError) {
console.error('Error saving post: ', error.cause, error.message)
const json = await error.response.json()
const body = ValidationErrorResponseFromJSONTyped(json, false)
addToast({
message: 'Error saving post: ' + body.detail,
})
}
}
First, error
is always unknown
so you have to type assert it. Typescript-fetch comes with
this type called ResponseError
. There's also FetchError
and RequiredError
. In my case
ResponseError
is what I needed and from that I can garner my message using one of the utility
functions that is generated with the response schema. In this case I get some fresh, typed goodness.
Now, when I create a duplicate slug for a given year it tells me so in plain old English.