Skip to content

Commit d8cbda6

Browse files
NexZhuepiphone
andauthored
feat: add validationMetadataArrayToSchemas and targetConstructorToSchema functions (#62)
* feat: add validationMetadataArrayToSchemas and targetConstructorToSchema functions * Update src/index.ts Co-authored-by: Aleksi Pekkala <aleksipekkala@gmail.com> * Update src/index.ts Co-authored-by: Aleksi Pekkala <aleksipekkala@gmail.com>
1 parent f63a5c4 commit d8cbda6

File tree

2 files changed

+115
-2
lines changed

2 files changed

+115
-2
lines changed

__tests__/index.test.ts

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import {
1515
ValidateNested,
1616
} from 'class-validator'
1717
import { ValidationMetadata } from 'class-validator/types/metadata/ValidationMetadata'
18-
import { validationMetadatasToSchemas } from '../src'
18+
import { targetConstructorToSchema, validationMetadatasToSchemas } from '../src'
1919

2020
class User {
2121
@IsString() id: string
@@ -159,6 +159,70 @@ describe('classValidatorConverter', () => {
159159
})
160160
})
161161

162+
it('combines converted class-validator metadata for one object into JSON Schemas', () => {
163+
const postSchema = targetConstructorToSchema(Post)
164+
165+
expect(postSchema).toEqual({
166+
properties: {
167+
published: {
168+
type: 'boolean',
169+
},
170+
title: {
171+
maxLength: 100,
172+
minLength: 2,
173+
type: 'string',
174+
},
175+
user: {
176+
$ref: '#/definitions/User',
177+
},
178+
},
179+
type: 'object',
180+
})
181+
182+
const userSchema = targetConstructorToSchema(User)
183+
184+
expect(userSchema).toEqual({
185+
properties: {
186+
empty: {
187+
anyOf: [
188+
{ type: 'string', enum: [''] },
189+
{
190+
not: {
191+
anyOf: [
192+
{ type: 'string' },
193+
{ type: 'number' },
194+
{ type: 'boolean' },
195+
{ type: 'integer' },
196+
{ type: 'array' },
197+
{ type: 'object' },
198+
],
199+
},
200+
nullable: true,
201+
},
202+
],
203+
},
204+
firstName: { minLength: 5, type: 'string' },
205+
id: { type: 'string' },
206+
object: { type: 'object' },
207+
nonEmptyObject: { type: 'object', minProperties: 1 },
208+
any: {},
209+
tags: {
210+
items: {
211+
maxLength: 20,
212+
not: {
213+
anyOf: [{ enum: ['admin'], type: 'string' }],
214+
},
215+
type: 'string',
216+
},
217+
maxItems: 5,
218+
type: 'array',
219+
},
220+
},
221+
required: ['id', 'firstName', 'object', 'any'],
222+
type: 'object',
223+
})
224+
})
225+
162226
it('should use custom schema name field', () => {
163227
const schemas = validationMetadatasToSchemas({
164228
schemaNameField: 'schemaName',

src/index.ts

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ export { JSONSchema } from './decorators'
1515
/**
1616
* Convert class-validator metadata into JSON Schema definitions.
1717
*/
18-
export function validationMetadatasToSchemas(userOptions?: Partial<IOptions>) {
18+
export function validationMetadatasToSchemas(
19+
userOptions?: Partial<IOptions>
20+
): Record<string, SchemaObject> {
1921
const options: IOptions = {
2022
...defaultOptions,
2123
...userOptions,
@@ -25,6 +27,21 @@ export function validationMetadatasToSchemas(userOptions?: Partial<IOptions>) {
2527
options.classValidatorMetadataStorage
2628
)
2729

30+
return validationMetadataArrayToSchemas(metadatas, userOptions)
31+
}
32+
33+
/**
34+
* Convert an array of class-validator metadata into JSON Schema definitions.
35+
*/
36+
export function validationMetadataArrayToSchemas(
37+
metadatas: ValidationMetadata[],
38+
userOptions?: Partial<IOptions>
39+
): Record<string, SchemaObject> {
40+
const options: IOptions = {
41+
...defaultOptions,
42+
...userOptions,
43+
}
44+
2845
const schemas: { [key: string]: SchemaObject } = {}
2946
Object.entries(
3047
_groupBy(
@@ -74,13 +91,45 @@ export function validationMetadatasToSchemas(userOptions?: Partial<IOptions>) {
7491
return schemas
7592
}
7693

94+
/**
95+
* Generate JSON Schema definitions from the target object constructor.
96+
*/
97+
export function targetConstructorToSchema(
98+
targetConstructor: Function,
99+
userOptions?: Partial<IOptions>
100+
): SchemaObject {
101+
const options: IOptions = {
102+
...defaultOptions,
103+
...userOptions,
104+
}
105+
106+
const storage = options.classValidatorMetadataStorage
107+
let metadatas = storage.getTargetValidationMetadatas(
108+
targetConstructor,
109+
'',
110+
true,
111+
false
112+
)
113+
metadatas = populateMetadatasWithConstraints(storage, metadatas)
114+
115+
const schemas = validationMetadataArrayToSchemas(metadatas, userOptions)
116+
return Object.values(schemas).length ? Object.values(schemas)[0] : {}
117+
}
118+
77119
/**
78120
* Return `storage.validationMetadatas` populated with `constraintMetadatas`.
79121
*/
80122
function getMetadatasFromStorage(
81123
storage: cv.MetadataStorage
82124
): ValidationMetadata[] {
83125
const metadatas: ValidationMetadata[] = (storage as any).validationMetadatas
126+
return populateMetadatasWithConstraints(storage, metadatas)
127+
}
128+
129+
function populateMetadatasWithConstraints(
130+
storage: cv.MetadataStorage,
131+
metadatas: ValidationMetadata[]
132+
): ValidationMetadata[] {
84133
const constraints: ConstraintMetadata[] = (storage as any).constraintMetadatas
85134

86135
return metadatas.map((meta) => {

0 commit comments

Comments
 (0)