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. Whereas, error formatters exposes 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 and that 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, // 👈 using json reporter
})

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 own 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 a validation fails. It receives the following arguments.

ArgumentDescription
pointerThe path to the field name. Nested properties are represented with a dot notation. user.profile.username
ruleThe name of the validation rule
messageThe failure message
arrayExpressionPointerThis 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.
argsArguments passed by the failed validation rule.

toError

The toError method must return a 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 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.

app/Validators/Reporters/MyReporter.ts
// collapse: 6
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