Skip to content

Commit

Permalink
fix(graphql): Fix filters to transform to expected type #317
Browse files Browse the repository at this point in the history
  • Loading branch information
doug-martin committed Jul 17, 2020
1 parent 1267a73 commit 0d28b0b
Show file tree
Hide file tree
Showing 5 changed files with 49 additions and 40 deletions.
13 changes: 3 additions & 10 deletions packages/query-graphql/src/resolvers/create.resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* @packageDocumentation
*/
// eslint-disable-next-line max-classes-per-file
import { Class, DeepPartial, applyFilter } from '@nestjs-query/core';
import { Class, DeepPartial } from '@nestjs-query/core';
import { Args, ArgsType, InputType, PartialType, Resolver } from '@nestjs/graphql';
import omit from 'lodash.omit';
import { DTONames, getDTONames } from '../common';
Expand All @@ -16,7 +16,7 @@ import {
SubscriptionArgsType,
SubscriptionFilterInputType,
} from '../types';
import { transformAndValidate } from './helpers';
import { createSubscriptionFilter, transformAndValidate } from './helpers';
import { BaseServiceResolver, ResolverClass, ServiceResolver, SubscriptionResolverOpts } from './resolver.interface';

export type CreatedEvent<DTO> = { [eventName: string]: DTO };
Expand Down Expand Up @@ -115,14 +115,7 @@ export const Creatable = <DTO, C extends DeepPartial<DTO>>(DTOClass: Class<DTO>,
class SA extends SubscriptionArgsType(SI) {}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const subscriptionFilter = (payload: any, variables: SA): boolean => {
if (variables.input?.filter) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
const dto = payload[createdEvent] as DTO;
return applyFilter(dto, variables.input.filter);
}
return true;
};
const subscriptionFilter = createSubscriptionFilter(SI, createdEvent);

@Resolver(() => DTOClass, { isAbstract: true })
class CreateResolverBase extends BaseClass {
Expand Down
13 changes: 3 additions & 10 deletions packages/query-graphql/src/resolvers/delete.resolver.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// eslint-disable-next-line max-classes-per-file
import { applyFilter, Class, DeleteManyResponse } from '@nestjs-query/core';
import { Class, DeleteManyResponse } from '@nestjs-query/core';
import omit from 'lodash.omit';
import { ObjectType, ArgsType, Resolver, Args, PartialType, InputType } from '@nestjs/graphql';
import { DTONames, getDTONames } from '../common';
Expand All @@ -14,7 +14,7 @@ import {
SubscriptionFilterInputType,
} from '../types';
import { ResolverMutation, ResolverSubscription } from '../decorators';
import { transformAndValidate } from './helpers';
import { createSubscriptionFilter, transformAndValidate } from './helpers';

export type DeletedEvent<DTO> = { [eventName: string]: DTO };
export interface DeleteResolverOpts<DTO> extends SubscriptionResolverOpts {
Expand Down Expand Up @@ -85,14 +85,7 @@ export const Deletable = <DTO>(DTOClass: Class<DTO>, opts: DeleteResolverOpts<DT
class DOSA extends SubscriptionArgsType(SI) {}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const deleteOneSubscriptionFilter = (payload: any, variables: DOSA): boolean => {
if (variables.input?.filter) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
const dto = payload[deletedOneEvent] as DTO;
return applyFilter(dto, variables.input.filter);
}
return true;
};
const deleteOneSubscriptionFilter = createSubscriptionFilter(SI, deletedOneEvent);

@Resolver(() => DTOClass, { isAbstract: true })
class DeleteResolverBase extends BaseClass {
Expand Down
20 changes: 19 additions & 1 deletion packages/query-graphql/src/resolvers/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Class } from '@nestjs-query/core';
import { applyFilter, Class } from '@nestjs-query/core';
import { plainToClass } from 'class-transformer';
import { validate } from 'class-validator';
import { BadRequestException } from '@nestjs/common';
import { SubscriptionArgsType, SubscriptionFilterInputType } from '../types';

/** @internal */
export const transformAndValidate = async <T>(TClass: Class<T>, partial: T): Promise<T> => {
Expand All @@ -15,3 +16,20 @@ export const transformAndValidate = async <T>(TClass: Class<T>, partial: T): Pro
}
return transformed;
};

export const createSubscriptionFilter = <DTO, Input extends SubscriptionFilterInputType<DTO>>(
InputClass: Class<Input>,
payloadKey: string,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
): ((payload: any, variables: SubscriptionArgsType<Input>, context: any) => boolean | Promise<boolean>) => {
return async (payload: any, variables: SubscriptionArgsType<Input>): Promise<boolean> => {
const { input } = variables;
if (input) {
const args = await transformAndValidate(InputClass, input);
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
const dto = payload[payloadKey] as DTO;
return applyFilter(dto, args.filter || {});
}
return true;
};
};
14 changes: 3 additions & 11 deletions packages/query-graphql/src/resolvers/update.resolver.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// eslint-disable-next-line max-classes-per-file
import { applyFilter, Class, DeepPartial, DeleteManyResponse, UpdateManyResponse } from '@nestjs-query/core';
import { Class, DeepPartial, DeleteManyResponse, UpdateManyResponse } from '@nestjs-query/core';
import { ArgsType, InputType, Resolver, Args, PartialType } from '@nestjs/graphql';
import omit from 'lodash.omit';
import { DTONames, getDTONames } from '../common';
Expand All @@ -14,7 +14,7 @@ import {
} from '../types';
import { BaseServiceResolver, ResolverClass, ServiceResolver, SubscriptionResolverOpts } from './resolver.interface';
import { ResolverMutation, ResolverSubscription } from '../decorators';
import { transformAndValidate } from './helpers';
import { createSubscriptionFilter, transformAndValidate } from './helpers';

export type UpdatedEvent<DTO> = { [eventName: string]: DTO };
export interface UpdateResolverOpts<DTO, U extends DeepPartial<DTO> = DeepPartial<DTO>>
Expand Down Expand Up @@ -113,15 +113,7 @@ export const Updateable = <DTO, U extends DeepPartial<DTO>>(DTOClass: Class<DTO>
@ArgsType()
class UOSA extends SubscriptionArgsType(SI) {}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const updateOneSubscriptionFilter = (payload: any, variables: UOSA): boolean => {
if (variables.input?.filter) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
const dto = payload[updateOneEvent] as DTO;
return applyFilter(dto, variables.input.filter);
}
return true;
};
const updateOneSubscriptionFilter = createSubscriptionFilter(SI, updateOneEvent);

@Resolver(() => DTOClass, { isAbstract: true })
class UpdateResolverBase extends BaseClass {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
GraphQLTimestamp,
GraphQLISODateTime,
} from '@nestjs/graphql';
import { Type } from 'class-transformer';
import { getMetadataStorage } from '../../../metadata';
import { IsUndefined } from '../../validators';
import { getOrCreateFloatFieldComparison } from './float-field-comparison.type';
Expand Down Expand Up @@ -46,24 +47,24 @@ const knownTypes: Set<ReturnTypeFuncValue> = new Set([
]);

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const isNamed = (Type: any): Type is { name: string } => {
const isNamed = (SomeType: any): SomeType is { name: string } => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
return 'name' in Type && typeof Type.name === 'string';
return 'name' in SomeType && typeof SomeType.name === 'string';
};

/** @internal */
const getTypeName = <T>(Type: ReturnTypeFuncValue): string => {
if (knownTypes.has(Type) || isNamed(Type)) {
const typeName = (Type as { name: string }).name;
const getTypeName = <T>(SomeType: ReturnTypeFuncValue): string => {
if (knownTypes.has(SomeType) || isNamed(SomeType)) {
const typeName = (SomeType as { name: string }).name;
return upperCaseFirst(typeName);
}
if (typeof Type === 'object') {
const enumType = getMetadataStorage().getGraphqlEnumMetadata(Type);
if (typeof SomeType === 'object') {
const enumType = getMetadataStorage().getGraphqlEnumMetadata(SomeType);
if (enumType) {
return upperCaseFirst(enumType.name);
}
}
throw new Error(`Unable to create filter comparison for ${JSON.stringify(Type)}.`);
throw new Error(`Unable to create filter comparison for ${JSON.stringify(SomeType)}.`);
};

/** @internal */
Expand Down Expand Up @@ -92,50 +93,62 @@ export function createFilterComparisonType<T>(

@Field(() => fieldType, { nullable: true })
@IsUndefined()
@Type(() => TClass)
eq?: T;

@Field(() => fieldType, { nullable: true })
@IsUndefined()
@Type(() => TClass)
neq?: T;

@Field(() => fieldType, { nullable: true })
@IsUndefined()
@Type(() => TClass)
gt?: T;

@Field(() => fieldType, { nullable: true })
@IsUndefined()
@Type(() => TClass)
gte?: T;

@Field(() => fieldType, { nullable: true })
@IsUndefined()
@Type(() => TClass)
lt?: T;

@Field(() => fieldType, { nullable: true })
@IsUndefined()
@Type(() => TClass)
lte?: T;

@Field(() => fieldType, { nullable: true })
@IsUndefined()
@Type(() => TClass)
like?: T;

@Field(() => fieldType, { nullable: true })
@IsUndefined()
@Type(() => TClass)
notLike?: T;

@Field(() => fieldType, { nullable: true })
@IsUndefined()
@Type(() => TClass)
iLike?: T;

@Field(() => fieldType, { nullable: true })
@IsUndefined()
@Type(() => TClass)
notILike?: T;

@Field(() => [fieldType], { nullable: true })
@IsUndefined()
@Type(() => TClass)
in?: T[];

@Field(() => [fieldType], { nullable: true })
@IsUndefined()
@Type(() => TClass)
notIn?: T[];
}

Expand Down

0 comments on commit 0d28b0b

Please sign in to comment.