class-validator-jsonschema
Version:
Convert class-validator-decorated classes into JSON schema
295 lines (270 loc) • 7.08 kB
text/typescript
// tslint:disable:no-submodule-imports
import {
Allow,
ArrayMaxSize,
ArrayNotContains,
IsBoolean,
IsEmpty,
IsNotEmptyObject,
IsObject,
IsOptional,
IsString,
Length,
MaxLength,
MetadataStorage,
MinLength,
ValidateNested,
} from 'class-validator'
import { ValidationMetadata } from 'class-validator/types/metadata/ValidationMetadata'
import { targetConstructorToSchema, validationMetadatasToSchemas } from '../src'
class User {
@IsString() id: string
@MinLength(5)
firstName: string
@IsOptional()
@MaxLength(20, { each: true })
@ArrayMaxSize(5)
@ArrayNotContains(['admin'])
tags: string[]
@IsEmpty() empty: string
@IsObject() object: object
@IsNotEmptyObject()
@IsOptional()
nonEmptyObject: {}
@Allow()
any: unknown
}
// @ts-ignore: not referenced
class Post {
static schemaName = 'ChangedPost'
@IsOptional()
@ValidateNested()
user: User
@Length(2, 100)
@IsOptional()
title: string
@IsBoolean()
@IsOptional()
published: true
}
describe('classValidatorConverter', () => {
it('handles empty metadata', () => {
const emptyStorage: any = {
constraintMetadatas: new Map(),
validationMetadatas: new Map(),
getTargetValidatorConstraints: () => [],
}
expect(
validationMetadatasToSchemas({
classValidatorMetadataStorage: emptyStorage,
})
).toEqual({})
})
it('derives schema from property type when no converter is found', () => {
const customMetadata: ValidationMetadata = {
always: false,
constraintCls: () => undefined,
constraints: [],
each: false,
groups: [],
message: '',
propertyName: 'id',
target: User,
type: 'NON_EXISTENT_METADATA_TYPE',
validationTypeOptions: {},
}
const storage = {
constraintMetadatas: new Map(),
validationMetadatas: new Map([[User, [customMetadata]]]),
getTargetValidatorConstraints: () => [],
} as unknown as MetadataStorage
const schemas = validationMetadatasToSchemas({
classValidatorMetadataStorage: storage,
})
expect(schemas.User.properties!.id).toEqual({ type: 'string' })
})
it('combines converted class-validator metadata into JSON Schemas', () => {
const schemas = validationMetadatasToSchemas()
expect(schemas).toEqual({
Post: {
properties: {
published: {
type: 'boolean',
},
title: {
maxLength: 100,
minLength: 2,
type: 'string',
},
user: {
$ref: '#/definitions/User',
},
},
type: 'object',
},
User: {
properties: {
empty: {
anyOf: [
{ type: 'string', enum: [''] },
{
not: {
anyOf: [
{ type: 'string' },
{ type: 'number' },
{ type: 'boolean' },
{ type: 'integer' },
{ type: 'array' },
{ type: 'object' },
],
},
nullable: true,
},
],
},
firstName: { minLength: 5, type: 'string' },
id: { type: 'string' },
object: { type: 'object' },
nonEmptyObject: { type: 'object', minProperties: 1 },
any: {},
tags: {
items: {
maxLength: 20,
not: {
anyOf: [{ enum: ['admin'], type: 'string' }],
},
type: 'string',
},
maxItems: 5,
type: 'array',
},
},
required: ['id', 'firstName', 'object', 'any'],
type: 'object',
},
})
})
it('combines converted class-validator metadata for one object into JSON Schemas', () => {
const postSchema = targetConstructorToSchema(Post)
expect(postSchema).toEqual({
properties: {
published: {
type: 'boolean',
},
title: {
maxLength: 100,
minLength: 2,
type: 'string',
},
user: {
$ref: '#/definitions/User',
},
},
type: 'object',
})
const userSchema = targetConstructorToSchema(User)
expect(userSchema).toEqual({
properties: {
empty: {
anyOf: [
{ type: 'string', enum: [''] },
{
not: {
anyOf: [
{ type: 'string' },
{ type: 'number' },
{ type: 'boolean' },
{ type: 'integer' },
{ type: 'array' },
{ type: 'object' },
],
},
nullable: true,
},
],
},
firstName: { minLength: 5, type: 'string' },
id: { type: 'string' },
object: { type: 'object' },
nonEmptyObject: { type: 'object', minProperties: 1 },
any: {},
tags: {
items: {
maxLength: 20,
not: {
anyOf: [{ enum: ['admin'], type: 'string' }],
},
type: 'string',
},
maxItems: 5,
type: 'array',
},
},
required: ['id', 'firstName', 'object', 'any'],
type: 'object',
})
})
it('should use custom schema name field', () => {
const schemas = validationMetadatasToSchemas({
schemaNameField: 'schemaName',
})
expect(schemas).toEqual({
ChangedPost: {
properties: {
published: {
type: 'boolean',
},
title: {
maxLength: 100,
minLength: 2,
type: 'string',
},
user: {
$ref: '#/definitions/User',
},
},
type: 'object',
},
User: {
properties: {
empty: {
anyOf: [
{ type: 'string', enum: [''] },
{
not: {
anyOf: [
{ type: 'string' },
{ type: 'number' },
{ type: 'boolean' },
{ type: 'integer' },
{ type: 'array' },
{ type: 'object' },
],
},
nullable: true,
},
],
},
firstName: { minLength: 5, type: 'string' },
id: { type: 'string' },
object: { type: 'object' },
nonEmptyObject: { type: 'object', minProperties: 1 },
any: {},
tags: {
items: {
maxLength: 20,
not: {
anyOf: [{ enum: ['admin'], type: 'string' }],
},
type: 'string',
},
maxItems: 5,
type: 'array',
},
},
required: ['id', 'firstName', 'object', 'any'],
type: 'object',
},
})
})
})