Validation
More often than not you will have the need to validate some data that is passed to your plugin, like configuration or data that should be persisted where an invalid format could have serious consequences.
The default validator that ships with Core implements @hapi/joi under the hood to provide an easy to use syntax for building validation schemas.
Prerequisites
Before we start, we need to establish what a few recurring variables and imports in this document refer to when they are used.
1import { app, Container, Services } from "@arkecosystem/core-kernel";
- The
app
import refers to the application instance which grants access to the container, configurations, system information and more. - The
Container
import refers to a namespace that contains all of the container specific entities like binding symbols and interfaces. - The
Services
import refers to a namespace that contains all of the core services. This generally will only be needed for type hints as Core is responsible for service creation and maintenance.
Usage
Get an instance of the Validator
1const validator: Services.Validation.ValidationService = app2 .get<Services.Validation.ValidationService>(Container.Identifiers.ValidationService)
Run the validator’s rules against its data
1validator.validate({ username: "johndoe" }, Joi.object({ username: Joi.string() }));
Determine if the data passes the validation rules
1validator.passes();
Determine if the data fails the validation rules
1validator.fails();
Get the failed validation rules
1validator.failed();
Get all of the validation error messages
1validator.errors();
Returns the data which was valid
1validator.valid();
Returns the data which was invalid
1validator.invalid();
Get the data under validation
1validator.attributes();
Extending
As explained in a previous article it is possible to extend Core services due to the fact that a Manager pattern is used. Lets go over a quick example of how you could implement your own validator.
Implementing the Driver
Implementing a new driver is as simple as importing the validator contract that needs to be satisfied and implement the methods specified in it.
Information
In this example we will use Joi which is a developer experienced focus validation library.
1import { Contracts } from "@arkecosystem/core-kernel"; 2 3export class MemoryValidator implements Contracts.Validation.Validator { 4 private data: JsonObject; 5 private resultValue: JsonObject | undefined; 6 private resultError: ValidationErrorItem[] | undefined; 7 8 public validate(data: JsonObject, schema: object): void { 9 this.data = data;10 11 const { error, value } = (schema as AnySchema).validate(this.data);12 13 this.resultValue = error ? undefined : value;14 15 if (error) {16 this.resultError = error.details;17 }18 }19 20 public passes(): boolean {21 return !this.resultError;22 }23 24 public fails(): boolean {25 return !this.passes();26 }27 28 public failed(): Record<string, string[]> {29 return this.groupErrors("type");30 }31 32 public errors(): Record<string, string[]> {33 return this.groupErrors("message");34 }35 36 public valid(): JsonObject {37 return this.resultValue;38 }39 40 public invalid(): JsonObject {41 const errors: JsonObject = {};42 43 for (const error of this.resultError) {44 errors[error.context.key] = error.context.value;45 }46 47 return errors;48 }49 50 public attributes(): JsonObject {51 return this.data;52 }53 54 private groupErrors(attribute: string): Record<string, string[]> {55 const errors: Record<string, string[]> = {};56 57 for (const error of this.resultError) {58 const errorKey: string | number = error.path[0];59 60 if (!Array.isArray(errors[errorKey])) {61 errors[errorKey] = [];62 }63 64 errors[errorKey].push(error[attribute]);65 }66 67 return errors;68 }
Implementing the service provider
Now that we have implemented our memory driver for the validation service we can create a service provider to register it.
1import { Container, Contracts, Providers, Services } from "@arkecosystem/core-kernel"; 2 3export class ServiceProvider extends Providers.ServiceProvider { 4 public async register(): Promise<void> { 5 const validationManager: Services.Validation.ValidationManager = this.app.get<Services.Validation.ValidationManager>( 6 Container.Identifiers.ValidationManager, 7 ); 8 9 await validationManager.extend("memory", async () =>10 this.app.resolve<Contracts.Validation.ValidationManager>(MemoryValidator).make(this.config().all()),11 );12 13 validationManager.setDefaultDriver("memory");14 }15}
- We retrieve an instance of the validation manager that is responsible for managing validation drivers.
- We call the
extend
method with an asynchronous function which is responsible for creating the validator instance. - We call the
setDefaultDriver
method which will tell Core to usememory
as the new default validator.
If you do not call
setDefaultDriver
you’ll need to manually retrieve the memory store cache instance viaapp.get<ValidationManager>(ValidationManager).driver("memory")
.