hapi icon indicating copy to clipboard operation
hapi copied to clipboard

Remove Joi from types

Open kanongil opened this issue 10 months ago • 6 comments

This PR removes the explicit import of Joi in the typings. As it is, any typescript users that use Hapi, will need to add a dependency of Joi, just to resolve the typings.

The explicit dependency is replaced by a generic validator that can be set on the Server object, or inferred from the config. This has the additional advantage, that other validators will be able to be typed instead of assuming that Joi is used.

The new types will require a bit of work to adapt to for projects that use server.validate().

This fixes #4491.

Definition

const MySimpleValidator = {
    compile(schema: MySchema) {

        return {
            validate(value: unknown, options: MyOptions & { context?: Validation.Context }) {

                return { value: 'everything is awesome' };
            }
        };
    }
};

The base Joi object already implement this interface.

Registration

Explicit:

const server = new Server<ServerApplicationState, typeof Joi>({});
server.validator(Joi);

Inferred from config:

const server = new Server({
    routes: { validate: { validator: Joi } }
});

Using the new validator() returned value:

const server = new Server({})
    .validator(Joi);

For plugins

Explicit:

register(server: Server<unknown, typeof Joi>, options: any) {

    server.validator(Joi);
    …
}

Using the new validator() returned value:

register(_server: Server, options: any) {

    const server = _server.validator(Joi);
    …
}

Usage

Once registered, the validator typings are used to help type the validation options:

server.route({
    …,
    options: {
        validate: {
            options: {
                // Validated against the options that can be passed to the registered `validate()` method
            },
            query: {
                // Validated against the type of the `schema´ from the registered `compile()` method
            }
        }
    }
});

It can be overriden at the route level:

server.route({
    …,
    options: {
        validate: {
            validator: Joi,   // Route-only validator
            options: {
                // Validated against the options that can be passed to the validator `validate()` method
            },
            query: {
                // Validated against the type of the `schema´ from the validator `compile()` method
            }
        }
    }
});

It also allows custom inline validators:

server.route({
    …,
    options: {
        validate: {
            query: (value, optionsAndContext) {

                // optionsAndContext is typed with the `context` object
            }
        }
    }
});

It is also used to validate rules:

server.rules(processor, {
    validate: {
        schema: validateSchema,    // Validated against the type of the `schema´ from the registered `compile()` method
        options: {
            // Validated against the options that can be passed to the registered `validate()` method
        }
    }
});

As it currently is, pre-compiled Joi schemas continue to work, even with no registered validator (though any options won't be validated):

server.route({
    …,
    options: {
        validate: {
            query: Joi.object({
                …
            })
        }
    }
});

FYI, it's possible that this could be revised to extract the allowed options from the passed Joi object.

kanongil avatar Apr 09 '24 12:04 kanongil