Request Middleware
How it works
A Middleware in this context is a function, that gets some data in a request context, does what it needs to do and returns a given state.
You can think of them like Layers on an Onion - each Middleware is another Layer which you will only get to, if the previous one succeeded.
There are 3 types of Request-Middleware:
- Request
- Response
- Exception
INFO
We need the additional name key on a Middleware object since minification will scramble the actual name of the Middleware.
Request Middleware
A Request Middleware can either check a given request for its options passed, or add data to the Request.
Examples:
- AddAuthorizationHeader
- AddBaseUrl
- ResetValidationErrors
import type { RequestMiddleware } from "@/toolkit"
export const addBaseUrl: RequestMiddleware = {
name: "addBaseUrl",
handle: async (options, config) => {
options.baseURL ??= config.baseUrl
return Promise.resolve(true)
},
}import type { RequestMiddleware } from "@/toolkit"
import { useImpersonation } from "@/toolkit"
const { hasImpersonation, getImpersonation } = useImpersonation()
export const impersonateUser: RequestMiddleware = {
name: "impersonateUser",
handle: async (options) => {
if (hasImpersonation()) {
options.headers["x-impersonate"] = String(getImpersonation())
}
return Promise.resolve(true)
},
}Response Middleware
A Response Middleware gets the transformed response and can either reject (and create an exception) or resolve. It can also modify the response data down the chain.
Examples:
- convertAllDatesToDayJsObjects
- checkSchema
import type { ResponseMiddleware } from "@/toolkit"
export const convertAllDatesToDayJsObjects: ResponseMiddleware = {
name: "convertAllDatesToDayJsObjects",
handle: async (response) => {
const data = response.data as Record<string, any>
const replaceDateRecursive = (container: Record<string, any>) => {
for (const [key, value] of Object.entries(container)) {
if (typeof value === "object" && value) {
replaceDateRecursive(value as Record<string, any>)
continue
}
if (typeof value === "string" && value.match(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z/gm)) {
container[key] = dayjs(value)
}
}
}
replaceDateRecursive(data)
response.data = data as typeof response.data
return Promise.resolve(response)
},
}WARNING
A response Middleware that resolves must return the response object.
Exception Middleware
An Exception Middleware gets the transformed response and can either reject (and create an exception) or resolve. It can also modify the response data down the chain.
Examples:
- catchNetworkErrorException
- catchValidationException
- catchAuthorizationException
- catchAuthenticationException
- catchThrottlerException
import type { ExceptionMiddleware } from "@/toolkit"
export const catchAuthorizationException: ExceptionMiddleware = {
name: "catchAuthorizationException",
handle: async (exception) => {
if (exception.is(AuthorizationException)) {
await goTo({ name: "login" })
}
return Promise.reject()
},
}import type { ExceptionMiddleware } from "@/toolkit"
export const catchThrottlerException: ExceptionMiddleware = {
name: "catchThrottlerException",
handle: async (exception, validations, retry) => {
if (exception.is(ThrottlerException)) {
const retryAfter = exception.response.headers["retry-after"]
const shouldRetry = retryAfter <= 5
const currentTime = new Date().getTime()
if(shouldRetry) {
await until(currentTime + retryAfter >= currentTime)
await retry()
}
}
},
}import type { ExceptionMiddleware } from "@/toolkit"
export const catchValidationException: ExceptionMiddleware = {
name: "catchValidationException",
handle: async (exception, validations) => {
if (exception.is(ValidationException)) {
const { setErrors } = useFormState()
setErrors(validations)
}
},
}INFO
The Exception, even if caught by middleware, will still get propagated down the chain so you can still check them in your request try/catch blocks.
TIP
You can use the integrated .is() Method on an Error in a catch block to easily and declaratively check if it's a given exception.