Error reporters
Error formatters are helpful when you are writing an API server following a pre-defined spec like JSON:API
Without error formatters, you have to manually loop over the error messages and re-shape them as per the spec followed by your API team. At the same time, error formatters expose an API to collect and structure error messages within the validation lifecycle (without any extra performance overhead).
Using error reporters
The validations performed using the request.validate
method uses content negotiation to find the best possible error reporter
for a given HTTP request.
However, you can also define the error reporter explicitly, which will turn off the content negotiation checks.
Both the validator.validate
and request.validate
method accepts a reporter to use. Either you can use one of the pre-existing reporters
or create/use a custom reporter.
import { schema, validator } from '@ioc:Adonis/Core/Validator'
validator.validate({
schema: schema.create({}),
reporter: validator.reporters.api,
})
Inside validator classes, you can define the reporter as an instance property.
import { schema, validator } from '@ioc:Adonis/Core/Validator'
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
export default class CreateUserValidator {
constructor (protected ctx: HttpContextContract) {}
public reporter = validator.reporters.api
public schema = schema.create({
// ... schema properties
})
}
Creating your error reporter
Every reporter report must adhere to the ErrorReporterContract interface and define the following properties/methods on it.
export interface ErrorReporterContract<Messages extends any = any> {
hasErrors: boolean
report(
pointer: string,
rule: string,
message: string,
arrayExpressionPointer?: string,
args?: any
): void
toError(): any
toJSON(): Messages
}
report
The report
method is called by the validator when validation fails. It receives the following arguments.
Argument | Description |
---|---|
pointer | The path to the field name. Nested properties are represented with a dot notation. user.profile.username |
rule | The name of the validation rule |
message | The failure message |
arrayExpressionPointer | This property exists when the current field under validation is nested inside an array. For example: users.*.username is the array expression pointer, and users.0.pointer is the pointer. |
args | Arguments passed by the failed validation rule. |
toError
The toError
method must return an instance of the error class, and the validator will throw this exception.
toJSON
The toJSON
method must return the collection of errors reported by the validator so far.
hasErrors
A boolean to know if the error reporter has received any errors so far.
Create a new file app/Validators/Reporters/MyReporter.ts
and paste the following contents inside it.
Dummy implementation
Following is a dummy implementation of a custom error reporter. Feel free to tweak it further to match your needs.
import {
ValidationException,
MessagesBagContract,
ErrorReporterContract,
} from '@ioc:Adonis/Core/Validator'
/**
* The shape of an individual error
*/
type ErrorNode = {
message: string,
field: string,
}
export class MyReporter implements ErrorReporterContract<{ errors: ErrorNode[] }> {
public hasErrors = false
/**
* Tracking reported errors
*/
private errors: ErrorNode[] = []
constructor (
private messages: MessagesBagContract,
private bail: boolean,
) {
}
/**
* Invoked by the validation rules to
* report the error
*/
public report (
pointer: string,
rule: string,
message: string,
arrayExpressionPointer?: string,
args?: any
) {
/**
* Turn on the flag
*/
this.hasErrors = true
/**
* Use messages bag to get the error message. The messages
* bag also checks for the user-defined error messages and
* hence must always be used
*/
const errorMessage = this.messages.get(
pointer,
rule,
message,
arrayExpressionPointer,
args,
)
/**
* Track error message
*/
this.errors.push({ message: errorMessage, field: pointer })
/**
* Bail mode means stop validation on the first
* error itself
*/
if (this.bail) {
throw this.toError()
}
}
/**
* Converts validation failures to an exception
*/
public toError () {
throw new ValidationException(false, this.toJSON())
}
/**
* Get error messages as JSON
*/
public toJSON () {
return {
errors: this.errors,
}
}
}
Points to note
- You must always use the MessagesBag to retrieve the error. It checks the user-defined custom error messages and returns the best match for a given field and validation rule.
- You should always raise an exception within the
report
method whenthis.bail
is set to true. - When in confusion, do check the implementation of existing error reporters .