Valparadores externos con @Validate
El nuevo decorador de esquemas externos es @Validate(...). Si estás buscando @Validator, el nombre del decorador en tsoa-next es @Validate. Referencia pertinente de API: @Validate, @Body, @BodyProp, @Query, @Queries, @Path, @Header, @FormField, @UploadedFile, @UploadedFiles, y File.
¿Qué? @Validate cambios
@Validate(...) hace un esquema externo la fuente de la verdad del tiempo de ejecución para un parámetro decorado.
- TypeScript tipos de unidad todavía OpenAPI generación
- El esquema externo reemplaza la validación de tiempo de ejecución incorporada para el subárbol de parámetro decorado
- Rutas que no utilizan
@Validate(...)mantener su comportamiento de validación existente
Objetivos de apoyo
En esta liberación, @Validate(...) es compatible con los parámetros del método del controlador que usan:
@Body()-@BodyProp()-@Query()-@Queries()-@Path()-@Header()-@FormField()-@UploadedFile()-@UploadedFiles()
Bibliotecas de apoyo
zod-joi-yup-superstruct-io-ts
Instala sólo la biblioteca validadora que utiliza su aplicación.
npm install zod
npm install joi
npm install yup
npm install superstruct
npm install io-ts fp-ts io-ts-typespnpm add zod
pnpm add joi
pnpm add yup
pnpm add superstruct
pnpm add io-ts fp-ts io-ts-typesyarn add zod
yarn add joi
yarn add yup
yarn add superstruct
yarn add io-ts fp-ts io-ts-typesFormularios de decoración compatibles
Todas las bibliotecas validadoras soportadas se pueden utilizar con cualquiera de estas formas:
@Validate(schema)
@Validate('zod', schema)
@Validate({ kind: 'zod', schema })Cuando pasas sólo el esquema, tsoa-next tratará de inferir el tipo de validador del objeto de esquema y su fuente de importación. Si la inferencia es ambigua, utilice el explícito kind formas.
Modelo de ejemplo común
Los ejemplos que figuran a continuación utilizan un TypeScript forma para que pueda ver lo que permanece igual mientras que la validación de tiempo de ejecución cambia.
type ExternalLiteralUnion = 'active' | 'disabled'
type ExternalObject = {
name: string
status: ExternalLiteralUnion
tags: string[]
}Zod
Zod trabaja bien con la forma inferida:
import { Body, Controller, Post, Route, Validate } from 'tsoa-next'
import { z } from 'zod'
type ExternalLiteralUnion = 'active' | 'disabled'
type ExternalObject = {
name: string
status: ExternalLiteralUnion
tags: string[]
}
const ZodBodySchema = z.object({
name: z.string().min(3, 'validation.external.zod.name.min'),
status: z.enum(['active', 'disabled']),
tags: z.array(z.string()).min(1, 'validation.external.zod.tags.min'),
})
@Route('external-validation')
export class ExternalValidationController extends Controller {
@Post('zod')
public zod(@Body() @Validate(ZodBodySchema) payload: ExternalObject): ExternalObject {
return payload
}
}Joi
Joi se puede utilizar con un tipo de validador explícito:
import * as Joi from 'joi'
import { Body, Controller, Post, Route, Validate } from 'tsoa-next'
type ExternalLiteralUnion = 'active' | 'disabled'
type ExternalObject = {
name: string
status: ExternalLiteralUnion
tags: string[]
}
const JoiBodySchema = Joi.object({
name: Joi.string().min(3).required(),
status: Joi.string().valid('active', 'disabled').required(),
tags: Joi.array().items(Joi.string()).min(1).required(),
})
@Route('external-validation')
export class ExternalValidationController extends Controller {
@Post('joi')
public joi(@Body() @Validate('joi', JoiBodySchema) payload: ExternalObject): ExternalObject {
return payload
}
}Joi también es útil para campos multipart y archivos cargados:
import * as Joi from 'joi'
import { Controller, File, Post, Route, UploadedFile, Validate } from 'tsoa-next'
@Route('assets')
export class AssetsController extends Controller {
@Post('upload')
public upload(@UploadedFile('asset') @Validate('joi', Joi.any()) asset: File): File {
return asset
}
}Para más detalles de carga, vea Uploading files.
Yup
Yup trabaja con la forma objeto:
import * as yup from 'yup'
import { Body, Controller, Post, Route, Validate } from 'tsoa-next'
type ExternalLiteralUnion = 'active' | 'disabled'
type ExternalObject = {
name: string
status: ExternalLiteralUnion
tags: string[]
}
const schema = yup
.object({
name: yup.string().required().min(3),
status: yup.mixed<ExternalLiteralUnion>().oneOf(['active', 'disabled']).required(),
tags: yup.array(yup.string().required()).min(1).required(),
})
.required()
@Route('external-validation')
export class ExternalValidationController extends Controller {
@Post('yup')
public yupBody(@Body() @Validate({ kind: 'yup', schema }) payload: ExternalObject): ExternalObject {
return payload
}
}Superestructura
Superestructura trabaja con la forma explícita:
import { array, object, size, string } from 'superstruct'
import { Body, Controller, Post, Route, Validate } from 'tsoa-next'
type ExternalLiteralUnion = 'active' | 'disabled'
type ExternalObject = {
name: string
status: ExternalLiteralUnion
tags: string[]
}
const SuperstructBodySchema = object({
name: size(string(), 3, 50),
status: string(),
tags: size(array(string()), 1, 10),
})
@Route('external-validation')
export class ExternalValidationController extends Controller {
@Post('superstruct')
public superstruct(@Body() @Validate('superstruct', SuperstructBodySchema) payload: ExternalObject): ExternalObject {
return payload
}
}io-ts
io-ts funciona bien cuando desea que un codec sea autorizado en el tiempo de ejecución mientras preserva una primera clase TypeScript alias through TypeOf.
import * as t from 'io-ts'
import { withMessage } from 'io-ts-types'
import { Body, Controller, Post, Route, Validate } from 'tsoa-next'
interface PositiveFloatBrand {
readonly PositiveFloat: unique symbol
}
const PositiveFloat = withMessage(
t.brand(
t.number,
(n): n is t.Branded<number, PositiveFloatBrand> => Number.isFinite(n) && n > 0,
'PositiveFloat',
),
() => 'validation.wager.amount.mustBePositiveFloat',
)
const WagerCodec = t.type({
amount: PositiveFloat,
outcome: t.Int,
})
type Wager = t.TypeOf<typeof WagerCodec>
@Route('wagers')
export class WagersController extends Controller {
@Post()
public createWager(@Body() @Validate({ kind: 'io-ts', schema: WagerCodec }) wager: Wager): Wager {
return wager
}
}Ganchos de validación
Generado RegisterRoutes(...) las funciones aceptan un contexto de validación opcional. Esto es útil cuando desea que los mensajes de validador externo sean traducidos o reformados antes de que sean devueltos a los clientes.
RegisterRoutes(app, {
validation: {
translate: (key, params) => translateMessage(key, params),
errorFormatter: failure => failure,
},
})Orientación práctica
- Mantén tu TypeScript tipos y esquemas externos alineados. OpenAPI a continuación TypeScript tipo, pero la validación de tiempo de ejecución sigue el esquema externo.
- Asegúrese de que la aplicación importa módulos controlador antes
RegisterRoutes(...)corre, así que los metadatos de decorador están disponibles en tiempo de ejecución. - Si utiliza plantillas de ruta personalizadas, mantenga intacta la validación de plazos de ejecución de metadatos para que la clase de controlador, el nombre de método y el índice de parámetro estén disponibles durante la validación.
- Utilice la forma inferida cuando es obvia, y cambiar a las formas explícitas cuando desea la claridad de la documentación o el tipo de esquema no es fácil de inferir.
