Skip to content

Commit

Permalink
feat: add validationMetadataArrayToSchemas and targetConstructorToSch…
Browse files Browse the repository at this point in the history
…ema functions (epiphone#62)

* feat: add validationMetadataArrayToSchemas and targetConstructorToSchema functions

* Update src/index.ts

Co-authored-by: Aleksi Pekkala <[email protected]>

* Update src/index.ts

Co-authored-by: Aleksi Pekkala <[email protected]>
  • Loading branch information
NexZhu and epiphone authored Jul 24, 2021
1 parent f63a5c4 commit d8cbda6
Show file tree
Hide file tree
Showing 2 changed files with 115 additions and 2 deletions.
66 changes: 65 additions & 1 deletion __tests__/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
ValidateNested,
} from 'class-validator'
import { ValidationMetadata } from 'class-validator/types/metadata/ValidationMetadata'
import { validationMetadatasToSchemas } from '../src'
import { targetConstructorToSchema, validationMetadatasToSchemas } from '../src'

class User {
@IsString() id: string
Expand Down Expand Up @@ -159,6 +159,70 @@ describe('classValidatorConverter', () => {
})
})

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',
Expand Down
51 changes: 50 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ export { JSONSchema } from './decorators'
/**
* Convert class-validator metadata into JSON Schema definitions.
*/
export function validationMetadatasToSchemas(userOptions?: Partial<IOptions>) {
export function validationMetadatasToSchemas(
userOptions?: Partial<IOptions>
): Record<string, SchemaObject> {
const options: IOptions = {
...defaultOptions,
...userOptions,
Expand All @@ -25,6 +27,21 @@ export function validationMetadatasToSchemas(userOptions?: Partial<IOptions>) {
options.classValidatorMetadataStorage
)

return validationMetadataArrayToSchemas(metadatas, userOptions)
}

/**
* Convert an array of class-validator metadata into JSON Schema definitions.
*/
export function validationMetadataArrayToSchemas(
metadatas: ValidationMetadata[],
userOptions?: Partial<IOptions>
): Record<string, SchemaObject> {
const options: IOptions = {
...defaultOptions,
...userOptions,
}

const schemas: { [key: string]: SchemaObject } = {}
Object.entries(
_groupBy(
Expand Down Expand Up @@ -74,13 +91,45 @@ export function validationMetadatasToSchemas(userOptions?: Partial<IOptions>) {
return schemas
}

/**
* Generate JSON Schema definitions from the target object constructor.
*/
export function targetConstructorToSchema(
targetConstructor: Function,
userOptions?: Partial<IOptions>
): SchemaObject {
const options: IOptions = {
...defaultOptions,
...userOptions,
}

const storage = options.classValidatorMetadataStorage
let metadatas = storage.getTargetValidationMetadatas(
targetConstructor,
'',
true,
false
)
metadatas = populateMetadatasWithConstraints(storage, metadatas)

const schemas = validationMetadataArrayToSchemas(metadatas, userOptions)
return Object.values(schemas).length ? Object.values(schemas)[0] : {}
}

/**
* Return `storage.validationMetadatas` populated with `constraintMetadatas`.
*/
function getMetadatasFromStorage(
storage: cv.MetadataStorage
): ValidationMetadata[] {
const metadatas: ValidationMetadata[] = (storage as any).validationMetadatas
return populateMetadatasWithConstraints(storage, metadatas)
}

function populateMetadatasWithConstraints(
storage: cv.MetadataStorage,
metadatas: ValidationMetadata[]
): ValidationMetadata[] {
const constraints: ConstraintMetadata[] = (storage as any).constraintMetadatas

return metadatas.map((meta) => {
Expand Down

0 comments on commit d8cbda6

Please sign in to comment.