From d3c2ca3f293f0f6ebe7eb6d9492ec49c8e1fcc2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Petlu=C5=A1?= Date: Sat, 18 May 2024 10:03:29 +0200 Subject: [PATCH] fix empty child classes issue (#106) --- __tests__/inheritedProperties.test.ts | 49 +++++++++++++++++++++++++-- src/index.ts | 22 +++++++++++- 2 files changed, 68 insertions(+), 3 deletions(-) diff --git a/__tests__/inheritedProperties.test.ts b/__tests__/inheritedProperties.test.ts index b0291ac..9cbd337 100644 --- a/__tests__/inheritedProperties.test.ts +++ b/__tests__/inheritedProperties.test.ts @@ -13,7 +13,11 @@ import { } from 'class-validator' import _get from 'lodash.get' -import { JSONSchema, validationMetadatasToSchemas } from '../src' +import { + JSONSchema, + targetConstructorToSchema, + validationMetadatasToSchemas, +} from '../src' @JSONSchema({ description: 'Contains email, password and phone', @@ -43,7 +47,6 @@ class BaseContent { @JSONSchema({ title: 'User object', }) -// @ts-ignore: not referenced class User extends BaseContent { @MinLength(10) @MaxLength(20) @@ -65,6 +68,9 @@ class User extends BaseContent { @Contains('hello') welcome: string } +// @ts-ignore: not referenced +class Admin extends User {} + const metadatas = _get(getFromContainer(MetadataStorage), 'validationMetadatas') describe('Inheriting validation decorators', () => { @@ -140,4 +146,43 @@ describe('Inheriting validation decorators', () => { expect(schemas.BaseContent.required).toEqual(['email', 'phone']) expect(schemas.User.required).toEqual(['password', 'email']) }) + + it('inherits and merges validation decorators from multiple parent classes and empty child class', () => { + const schema = targetConstructorToSchema(Admin) + + expect(schema).toEqual({ + properties: { + email: { + default: 'some@email.com', + format: 'email', + type: 'string', + not: { type: 'null' }, + }, + name: { + maxLength: 20, + minLength: 10, + type: 'string', + }, + password: { + description: 'Password field - required!', + minLength: 20, + type: 'string', + not: { type: 'null' }, + }, + phone: { + format: 'mobile-phone', + title: 'Mobile phone number', + type: 'string', + not: { type: 'null' }, + }, + welcome: { + pattern: 'hello', + type: 'string', + }, + }, + required: ['name', 'welcome', 'email'], + title: 'User object', + type: 'object', + }) + }) }) diff --git a/src/index.ts b/src/index.ts index 8fdc08d..6ffa99b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -100,6 +100,26 @@ export function validationMetadataArrayToSchemas( return schemas } +/** + * Search for the JSON Schema definition from child class up to the + * top parent class until empty function name is found. + */ +function getTargetConstructorSchema( + schemas: Record, + targetConstructor: Function +): SchemaObject { + if (!targetConstructor.name) { + return {} + } else if (schemas[targetConstructor.name]) { + return schemas[targetConstructor.name] + } else { + return getTargetConstructorSchema( + schemas, + Object.getPrototypeOf(targetConstructor) + ) + } +} + /** * Generate JSON Schema definitions from the target object constructor. */ @@ -122,7 +142,7 @@ export function targetConstructorToSchema( metadatas = populateMetadatasWithConstraints(storage, metadatas) const schemas = validationMetadataArrayToSchemas(metadatas, userOptions) - return Object.values(schemas).length ? Object.values(schemas)[0] : {} + return getTargetConstructorSchema(schemas, targetConstructor) } /**