-
Notifications
You must be signed in to change notification settings - Fork 35
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
classValidatorToJsonSchema - Get JSON Schema for a specific class #48
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,3 +6,4 @@ coverage | |
*.key | ||
*.secret | ||
yarn-error.log | ||
.idea |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
import { IsString, MinLength } from 'class-validator' | ||
import { classValidatorToJsonSchema, validationMetadatasToSchemas } from '../src' | ||
|
||
export class User { | ||
@MinLength(5) | ||
@IsString() | ||
name: string; | ||
} | ||
Comment on lines
+4
to
+8
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we also test that if the two User classes both have a property with the same name but different class-validator decorators, only the desired decorators get applied in the resulting schema? |
||
|
||
describe('classValidatorToJsonSchema', () => { | ||
it('handles default object', async () => { | ||
const schema = classValidatorToJsonSchema(User) | ||
|
||
expect(schema).toStrictEqual({ | ||
properties: { | ||
name: { minLength: 5, type: 'string' } | ||
}, | ||
required: ['name'], | ||
type: 'object' | ||
}) | ||
|
||
// Import another User class. | ||
const alternativeUserImport = await import('./classes/User') | ||
|
||
/** | ||
* By importing another User class with the same name JSON schemas get merged. | ||
* User JSON schema now contains properties from the classes/User.ts class as | ||
* well (firstName) | ||
*/ | ||
const schemas = validationMetadatasToSchemas() | ||
expect(Boolean(schemas.User!.properties!.name)).toBeTruthy() | ||
expect(Boolean(schemas.User!.properties!.firstName)).toBeTruthy() | ||
|
||
/** | ||
* When we get JSON schema for a specific class, | ||
* it returns properties specific for that class (without merging) | ||
*/ | ||
const schema2 = classValidatorToJsonSchema(User) | ||
// Schema stays the same | ||
expect(schema).toStrictEqual(schema2) | ||
|
||
const alternativeUserSchema = classValidatorToJsonSchema(alternativeUserImport.User) | ||
|
||
expect(alternativeUserSchema).toStrictEqual({ | ||
properties: { | ||
firstName: { minLength: 5, type: 'string' } | ||
}, | ||
required: ['firstName'], | ||
type: 'object' | ||
}) | ||
}) | ||
}) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import { IsString, MinLength } from 'class-validator' | ||
|
||
export class User { | ||
@MinLength(5) | ||
@IsString() | ||
firstName: string; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,7 +12,7 @@ module.exports = { | |
moduleDirectories: ['node_modules', 'src'], | ||
moduleFileExtensions: ['ts', 'js', 'json'], | ||
roots: ['<rootDir>/__tests__'], | ||
testPathIgnorePatterns: ['/node_modules/'], | ||
testPathIgnorePatterns: ['/node_modules/', '/__tests__/classes'], | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is this necessary? |
||
coverageDirectory: 'coverage', | ||
collectCoverageFrom: ['src/**/*.{ts,tsx,js,jsx}', '!src/**/*.d.ts'] | ||
} |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -30,29 +30,58 @@ export function validationMetadatasToSchemas(userOptions?: Partial<IOptions>) { | |||||
const target = ownMetas[0].target as Function | ||||||
const metas = ownMetas.concat(getInheritedMetadatas(target, metadatas)) | ||||||
|
||||||
const properties = _(metas) | ||||||
.groupBy('propertyName') | ||||||
.mapValues((propMetas, propKey) => { | ||||||
const schema = applyConverters(propMetas, options) | ||||||
return applyDecorators(schema, target, options, propKey) | ||||||
}) | ||||||
.value() | ||||||
|
||||||
const definitionSchema: SchemaObject = { | ||||||
properties, | ||||||
type: 'object', | ||||||
} | ||||||
return buildJsonSchemaForSpecificTarget(metadatas, target, metas, options) | ||||||
}) | ||||||
.value() | ||||||
|
||||||
const required = getRequiredPropNames(target, metas, options) | ||||||
if (required.length > 0) { | ||||||
definitionSchema.required = required | ||||||
} | ||||||
return schemas | ||||||
} | ||||||
|
||||||
return applyDecorators(definitionSchema, target, options, target.name) | ||||||
/** | ||||||
* Convert class-validator class into JSON Schema definition. | ||||||
*/ | ||||||
export type ClassType<T> = new (...args: any[]) => T; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
We can omit the unused generic here and below I think. |
||||||
|
||||||
function buildJsonSchemaForSpecificTarget<T>( metadatas: ValidationMetadata[], target: ClassType<T> | Function, targetMetadatas: ValidationMetadata[], options: IOptions) { | ||||||
const metas = targetMetadatas.concat(getInheritedMetadatas(target, metadatas)) | ||||||
|
||||||
const properties = _(metas) | ||||||
.groupBy('propertyName') | ||||||
.mapValues((propMetas, propKey) => { | ||||||
const schema = applyConverters(propMetas, options) | ||||||
return applyDecorators(schema, target, options, propKey) | ||||||
}) | ||||||
.value() | ||||||
|
||||||
return schemas | ||||||
const definitionSchema: SchemaObject = { | ||||||
properties, | ||||||
type: 'object' | ||||||
} | ||||||
|
||||||
const required = getRequiredPropNames(target, metas, options) | ||||||
if (required.length > 0) { | ||||||
definitionSchema.required = required | ||||||
} | ||||||
|
||||||
return applyDecorators(definitionSchema, target, options, target.name) | ||||||
} | ||||||
|
||||||
export function classValidatorToJsonSchema<T extends object>( target: ClassType<T>, userOptions?: Partial<IOptions>) { | ||||||
const options: IOptions = { | ||||||
...defaultOptions, | ||||||
...userOptions, | ||||||
} | ||||||
|
||||||
const metadatas = getMetadatasFromStorage( | ||||||
options.classValidatorMetadataStorage | ||||||
) | ||||||
|
||||||
const targetMetadatas = _(metadatas) | ||||||
.filter(metadata => { | ||||||
return metadata.target === target | ||||||
}).value(); | ||||||
Comment on lines
+79
to
+82
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's use the native |
||||||
|
||||||
return buildJsonSchemaForSpecificTarget(metadatas, target, targetMetadatas, options) | ||||||
} | ||||||
|
||||||
/** | ||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it possible to use namespaces in this file to import two classes with the same name? Might be clearer than declaring one here and another in a separate file.
I mean something like