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 = app
2 .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}
  1. We retrieve an instance of the validation manager that is responsible for managing validation drivers.
  2. We call the extend method with an asynchronous function which is responsible for creating the validator instance.
  3. We call the setDefaultDriver method which will tell Core to use memory as the new default validator.

If you do not call setDefaultDriver you’ll need to manually retrieve the memory store cache instance via app.get<ValidationManager>(ValidationManager).driver("memory").

Last updated 2 years ago
Edit Page
Share: