Skip to content

Commit

Permalink
Merge pull request #2227 from kimdj2/fix-intersect-more-than-2-classes
Browse files Browse the repository at this point in the history
feat: intersect more than 2 classes
  • Loading branch information
kamilmysliwiec committed Feb 6, 2023
2 parents c74091f + d64c1ad commit daf477d
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 48 deletions.
89 changes: 44 additions & 45 deletions lib/type-helpers/intersection-type.helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import { Type } from '@nestjs/common';
import {
inheritTransformationMetadata,
inheritValidationMetadata,
inheritPropertyInitializers
inheritPropertyInitializers,
MappedType
} from '@nestjs/mapped-types';
import { DECORATORS } from '../constants';
import { ApiProperty } from '../decorators';
Expand All @@ -11,59 +12,57 @@ import { clonePluginMetadataFactory } from './mapped-types.utils';

const modelPropertiesAccessor = new ModelPropertiesAccessor();

export function IntersectionType<A, B>(
classARef: Type<A>,
classBRef: Type<B>
): Type<A & B> {
const fieldsOfA = modelPropertiesAccessor.getModelProperties(
classARef.prototype
);
const fieldsOfB = modelPropertiesAccessor.getModelProperties(
classBRef.prototype
);
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
k: infer I,
) => void
? I
: never;

abstract class IntersectionTypeClass {
type ClassRefsToConstructors<T extends Type[]> = {
[U in keyof T]: T[U] extends Type<infer V> ? V : never;
};

type Intersection<T extends Type[]> = MappedType<
UnionToIntersection<ClassRefsToConstructors<T>[number]>
>;

export function IntersectionType<T extends Type[]>(...classRefs: T) {
abstract class IntersectionClassType {
constructor() {
inheritPropertyInitializers(this, classARef);
inheritPropertyInitializers(this, classBRef);
classRefs.forEach((classRef) => {
inheritPropertyInitializers(this, classRef);
});
}
}
inheritValidationMetadata(classARef, IntersectionTypeClass);
inheritTransformationMetadata(classARef, IntersectionTypeClass);
inheritValidationMetadata(classBRef, IntersectionTypeClass);
inheritTransformationMetadata(classBRef, IntersectionTypeClass);

clonePluginMetadataFactory(
IntersectionTypeClass as Type<unknown>,
classARef.prototype
);
clonePluginMetadataFactory(
IntersectionTypeClass as Type<unknown>,
classBRef.prototype
);

fieldsOfA.forEach((propertyKey) => {
const metadata = Reflect.getMetadata(
DECORATORS.API_MODEL_PROPERTIES,
classARef.prototype,
propertyKey
classRefs.forEach((classRef) => {
const fields = modelPropertiesAccessor.getModelProperties(
classRef.prototype
);
const decoratorFactory = ApiProperty(metadata);
decoratorFactory(IntersectionTypeClass.prototype, propertyKey);
});

inheritValidationMetadata(classRef, IntersectionClassType);
inheritTransformationMetadata(classRef, IntersectionClassType);

fieldsOfB.forEach((propertyKey) => {
const metadata = Reflect.getMetadata(
DECORATORS.API_MODEL_PROPERTIES,
classBRef.prototype,
propertyKey
clonePluginMetadataFactory(
IntersectionClassType as Type<unknown>,
classRef.prototype
);
const decoratorFactory = ApiProperty(metadata);
decoratorFactory(IntersectionTypeClass.prototype, propertyKey);

fields.forEach((propertyKey) => {
const metadata = Reflect.getMetadata(
DECORATORS.API_MODEL_PROPERTIES,
classRef.prototype,
propertyKey
);
const decoratorFactory = ApiProperty(metadata);
decoratorFactory(IntersectionClassType.prototype, propertyKey);
});

});

Object.defineProperty(IntersectionTypeClass, 'name', {
value: `Intersection${classARef.name}${classBRef.name}`
const intersectedNames = classRefs.reduce((prev, ref) => prev + ref.name, '');
Object.defineProperty(IntersectionClassType, 'name', {
value: `Intersection${intersectedNames}`,
});
return IntersectionTypeClass as Type<A & B>;
return IntersectionClassType as Intersection<T>;
}
18 changes: 15 additions & 3 deletions test/type-helpers/intersection-type.helper.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Type } from '@nestjs/common';
import { Expose, Transform } from 'class-transformer';
import { IsString } from 'class-validator';
import { IsBoolean, IsString } from 'class-validator';
import { ApiProperty } from '../../lib/decorators';
import { METADATA_FACTORY_NAME } from '../../lib/plugin/plugin-constants';
import { ModelPropertiesAccessor } from '../../lib/services/model-properties-accessor';
Expand Down Expand Up @@ -32,7 +32,17 @@ describe('IntersectionType', () => {
}
}

class UpdateUserDto extends IntersectionType(UserDto, CreateUserDto) {}
class AuthorityDto {
@IsBoolean()
@ApiProperty({ required: true })
isAdmin: boolean;

static [METADATA_FACTORY_NAME]() {
return { dateOfBirth3: { required: true, type: () => String } };
}
}

class UpdateUserDto extends IntersectionType(UserDto, CreateUserDto, AuthorityDto) {}

let modelPropertiesAccessor: ModelPropertiesAccessor;

Expand All @@ -49,8 +59,10 @@ describe('IntersectionType', () => {
'firstName',
'login',
'password',
'isAdmin',
'dateOfBirth2',
'dateOfBirth'
'dateOfBirth',
'dateOfBirth3'
]);
});
});
Expand Down

0 comments on commit daf477d

Please sign in to comment.