Validation

status: working in progress

What is Validation?

Validation ensures an input data conforms to a schema or, in general, a set of rules. Validation is a gate that allows only expected data to pass and continuing the execution.


Validation is an essential tool when data crosses different boundaries and to check predefined expectations are met.

Because an httpc server exposes functions, a special focus is put on function arguments validation.


@httpc/kit provides the Validate middleware to easily check function parameters.

Core components

Validate middleware

To perform function arguments validation, you can use the Validate middleware.


You can apply the Validate middleware to a function and specify in order the schema for each argument the function defines.

import { httpCall, Validate } from "@httpc/kit";

const add = httpCall(
    Validate(Number, Number),
    async (x: number, y: number) {
        // here x and y are validated
    }
);

In the previous example, the schema Number is being used by the NativeTypeValidator to check the relative argument against the primitive type Number.


httpc/kit provides multiple validators. Each one accepts a specific schema to perform the validation. By default NativeTypeValidator and PredicateValidator are enabled. You can also add 3rd party validators using popular validation packages.


While you can specify one schema per argument, you can mix different schema types for different arguments.

import { httpCall, Validate } from "@httpc/kit";

const sentNotification = httpCall(
    Validate(
        Number,
        priority => priority === "normal" || priority === "important",
        String,
    ),
    async (destination: number, priority: string, message: string) {
        // here arguments are validated
    }
);

In the previous example:


When the validation fails, that is, at least one argument doesn’t pass the validation, the Validate middleware throws a ValidationError, which translates into an HTTP 400 response.

ValidationService service

Behind the scenes, the Validate middleware uses the builtin ValidationService. The ValidationService manages all the enabled validators and runs the right one for the relative schema.


Usually you don’t need to interact directly with the ValidationService, but if you need to perform manual validation, you can resolve it via injection:

import { useInjected, ValidationService } from "@httpc/kit";
import { ProfileSchema } from "./schemas";

function manualValidation(profile: unknown) {
    const validation = useInjected(ValidationService);
    const result = validation.validate(profile, ProfileSchema);
    if (result.error) {
        throw new ValidationError(result.errors);
    }

    return result.object as ProfileSchema;
}

Builtin validators

@httpc/kit provides two first-party validators:

Both validators are enabled by default. Which means, you can use both to validate function arguments via Validate middleware.


Check out 3rd party validators for builtin integrations with popular libraries.

NativeTypeValidator

The NativeTypeValidator offers a quick but effective way to perform validation against primitive types.


You can use javascript native object as schema to validate function arguments. The supported native object are:

import { httpCall, Validate } from "@httpc/kit";

const add = httpCall(
    Validate(Number, Number),
    async (x: number, y: number) {
        // here x and y are validated
    }
);

const great = httpCall(
    Validate(String, Boolean),
    async (name: string, isFirstTime: boolean) {
        // here name and isFirstTime are validated
    }
);

The NativeTypeValidator goes a little beyond the strict checking. For some types it tries to parse them into the target native type:


You can disable this extended behavior, by setting the option:

import { NativeTypeValidator, NativeTypeValidatorOptions, REGISTER_OPTIONS } from "@httpc/kit";

REGISTER_OPTIONS<NativeTypeValidatorOptions>(NativeTypeValidator, {
    disableParsing: true
});

PredicateValidator

The PredicateValidator allows to validate arguments with custom code. This validation is activated when the schema is a function.

const divideBy = httpCall(
    Validate(
        // use the NativeType validation for `dividend`
        Number, 
        // use a custom function to validate `divisor`
        divisor => typeof divisor === "number" && divisor > 0,
    ),
    (dividend: number, divisor: number) => {
        return dividend / divisor;
    }
);

The validating function, aka the predicate, accepts the argument and returns the result of the validation.

type Predicate = (argument: unknown) => boolean | ValidationResult

The result can be:

3rd party validators

@httpc/kit offers some pre-configured integration with 3rd party libraries.


Usually a 3rd party validator can be enabled with a single import:

import "@httpc/kit/validation-*";

where the * is the 3rd party package name.



The pre-configured validator libraries are:

class-validator

@httpc/kit provides out of the box an integration with the class-validator package.


To enable the integration, use the import:

import "@httpc/kit/validation-class";

And to do parameter validation, use the Validate middleware:

import { httpCall, Validate } from "@httpc/kit";
import { ProfileSchema } from "./schemas";

const updateProfile = httpCall(
    Validate(ProfileSchema),
    async (profile: ProfileSchema) => {
        // here the profile is validated
    }
);

Options

The class-validator integration is pre-configured with the default options:

import { ValidatorOptions } from "class-validator";

const DEFAULT_OPTIONS: ValidatorOptions = {
    whitelist: true, // allows only schema-defined property, stripping all the rest
}

For more details, checkout the class-validator documentation;


You can override the defaults with a new configuration. For convenience, the integration provides the ClassValidatorOptions type as an alias to the ValidatorOptions.


In your import file:

import { REGISTER_OPTIONS } from "@httpc/kit";
import "@httpc/kit/validation-class";
import { ClassValidator, ClassValidatorOptions } from "@httpc/kit/validation-class";

REGISTER_OPTIONS<ClassValidatorOptions>(ClassValidator, {
    // custom configuration
    whitelist: true,
});

Standalone use

If you need to run a validation manually, you can get the class-validation validator via injection.

import { useInjected, ValidationError } from "@httpc/kit";
import { ClassValidator } from "@httpc/kit/validation-class";
import { ProfileSchema } from "./schemas";

function manualValidation(profile: any) {
    const validator = useInjected(ClassValidator);
    const result = validator.validate(profile, ProfileSchema);
    if (!result.success) {
        throw new ValidationError(result.errors);
    }

    return result.object as ProfileSchema;
}

zod

@httpc/kit provides out of the box an integration with the zod package.


To enable the integration, use the import:

import "@httpc/kit/validation-zod";

And to do parameter validation, use the Validate middleware:

import { httpCall, Validate } from "@httpc/kit";
import { z } from "zod";

const ProfileSchema = z.object({
    username: z.string().min(10).max(20),
    email: z.string().email()
    website: z.string().optional()
});

const updateProfile = httpCall(
    Validate(ProfileSchema),
    async (profile: z.infer<typeof ProfileSchema>) => {
        // here the profile is validated
    }
);

Standalone use

If you need to run a validation manually, you can get the zod validator via injection.

import { useInjected } from "@httpc/kit";
import { ZodValidator } from "@httpc/kit/validation-class";
import { z } from "zod";

const ProfileSchema = z.object({
    username: z.string().min(10).max(20),
    email: z.string().email(),
    website: z.string().optional(),
});

function manualValidation(order: any) {
    const validator = useInjected(ZodValidator);
    const result = validator.validate(order, ProfileSchema);
    if (!result.success) {
        throw new ValidationError(result.errors);
    }

    return result.object as z.infer<typeof ProfileSchema>;
}

Custom validator

// TODO

Interfaces

IValidator

// TODO