joi icon indicating copy to clipboard operation
joi copied to clipboard

Allow custom value type for ValidationResult

Open BSFishy opened this issue 2 years ago • 2 comments

Support plan

  • is this issue currently blocking your project? (yes/no): no
  • is this issue affecting a production system? (yes/no): no

Context

  • node version: 15.3.0
  • module version: 17.4.2
  • environment (e.g. node, browser, native): node
  • used with (e.g. hapi application, another framework, standalone, ...): Next.JS
  • any other relevant information: pretty much just using it to validate incoming API data

What problem are you trying to solve?

I'm currently writing a project in Typescript and am using Joi to validate incoming API data. What I would like to be able to do is cast the data to the correct type when I use the validate function. It just seems clunky to me to have to validate some data then cast it to the correct type afterwards.

For example:

type RequestData = {
  message: string
}

const schema = Joi.object<RequestData, true>({
  message: Joi.string(),
})

const handler = (req, res) => {
  const { value } = schema.validate(req.body)
  const realValue: RequestData = value

  // ... use realValue
}

I think it would be much nicer to just be able to do the following:

const handler = (req, res) => {
  const { value } = schema.validate<RequestData>(req.body)

  // ... use value
}

Do you have a new or modified API suggestion to solve the problem?

The Typescript definitions for ValidationResult and validate could be changed to include a generic type for the output. (Maybe even ObjectSchema could override validate to automatically use TSchema)

ValidationResult could be changed to:

interface ValidationResult<TValue = any> {
    error?: ValidationError;
    warning?: ValidationError;
    value: TValue;
}

Then AnySchema could be changed to:

interface AnySchema extends SchemaInternals {
    // ...

    validate<TValue = any>(value: any, options?: ValidationOptions): ValidationResult<TValue>;

    // ...
}

All the different Schema interfaces could also be modified to return the correct type, however that might introduce increased burden in terms of maintaining.

Additionally, a separate interface could be created that only includes the validate function, and uses a generic type as the TValue type. From there, all the separate Schema interfaces could extend it, using the correct type, but again, that might come at the cost of increased burden.

All of this is a relatively simple change, and I would have gone straight to a pull request, but I wanted to make sure this is something that actually should be added.

BSFishy avatar Sep 15 '21 20:09 BSFishy

This used to be the case for the types defined at @types/joi. I've been using a helper function to achieve this.

I wonder if the generic on ValidationResult was removed by any specific reason.

jsangilve avatar Nov 02 '21 16:11 jsangilve

This is a conversion function that will retain the types of the other properties in the validation result even if the type definition changes:

import { ValidationResult } from 'joi';

function castValidationResult<T> (
  result: ValidationResult
): {
  [Property in keyof ValidationResult]: Property extends 'value'
    ? T
    : ValidationResult[Property];
} {
  return result;
}

mstone121 avatar Nov 09 '21 20:11 mstone121