Skip to content

Commit

Permalink
Fixes for resolvers plugins: override optional declaration of a… (#2122)
Browse files Browse the repository at this point in the history
  • Loading branch information
dotansimha committed Jul 8, 2019
1 parent 557f890 commit 6cf9846
Show file tree
Hide file tree
Showing 9 changed files with 92 additions and 28 deletions.
1 change: 1 addition & 0 deletions packages/plugins/flow/resolvers/src/flow-util-types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const FLOW_REQUIRE_FIELDS_TYPE = `export type $RequireFields<Origin, Keys> = $Diff<Args, Keys> & $ObjMapi<Keys, <Key>(k: Key) => $NonMaybeType<$ElementType<Origin, Key>>>;`;
2 changes: 1 addition & 1 deletion packages/plugins/flow/resolvers/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ ${resolverTypeWrapper}
}

return {
prepend: [gqlImports, ...mappersImports],
prepend: [gqlImports, ...mappersImports, ...visitor.globalDeclarations],
content: [header, resolversTypeMapping, resolversParentTypeMapping, ...visitorResult.definitions.filter(d => typeof d === 'string'), getRootResolver(), getAllDirectiveResolvers()].join('\n'),
};
};
8 changes: 7 additions & 1 deletion packages/plugins/flow/resolvers/src/visitor.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { FlowResolversPluginConfig } from './index';
import { ListTypeNode, NamedTypeNode, NonNullTypeNode, GraphQLSchema, ScalarTypeDefinitionNode } from 'graphql';
import { ListTypeNode, NamedTypeNode, NonNullTypeNode, GraphQLSchema, ScalarTypeDefinitionNode, InputValueDefinitionNode } from 'graphql';
import * as autoBind from 'auto-bind';
import { indent, ParsedResolversConfig, BaseResolversVisitor, DeclarationBlock } from '@graphql-codegen/visitor-plugin-common';
import { FlowOperationVariablesToObject } from '@graphql-codegen/flow';
import { FLOW_REQUIRE_FIELDS_TYPE } from './flow-util-types';

export interface ParsedFlorResolversConfig extends ParsedResolversConfig {}

Expand All @@ -17,6 +18,11 @@ export class FlowResolversVisitor extends BaseResolversVisitor<FlowResolversPlug
return `$ElementType<Scalars, '${name}'>`;
}

protected applyRequireFields(argsType: string, fields: InputValueDefinitionNode[]): string {
this._globalDeclarations.add(FLOW_REQUIRE_FIELDS_TYPE);
return `$RequireFields<${argsType}, { ${fields.map(f => `${f.name.value}: *`).join(', ')} }>`;
}

protected buildMapperImport(source: string, types: { identifier: string; asDefault?: boolean }[]): string {
if (types[0] && types[0].asDefault) {
return `import type ${types[0].identifier} from '${source}';`;
Expand Down
18 changes: 16 additions & 2 deletions packages/plugins/flow/resolvers/tests/flow-resolvers.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,22 @@ describe('Flow Resolvers Plugin', () => {
expect(result).toMatchSnapshot();
});

it('Default values of args and compatibility with typescript plugin', async () => {
const testSchema = buildSchema(/* GraphQL */ `
type Query {
something(arg: String = "default_value"): String
}
`);

const config: any = { noSchemaStitching: true };
const result = (await plugin(testSchema, [], config, { outputFile: '' })) as Types.ComplexPluginOutput;

expect(result.prepend).toContain(`export type $RequireFields<Origin, Keys> = $Diff<Args, Keys> & $ObjMapi<Keys, <Key>(k: Key) => $NonMaybeType<$ElementType<Origin, Key>>>;`);
expect(result.content).toContain(`something?: Resolver<?$ElementType<ResolversTypes, 'String'>, ParentType, ContextType, $RequireFields<QuerySomethingArgs, { arg: * }>>,`);
});

it('Should generate ResolversParentTypes', () => {
const result = plugin(schema, [], {}, { outputFile: '' });
const result = plugin(schema, [], {}, { outputFile: '' }) as Types.ComplexPluginOutput;

expect(result.content).toBeSimilarStringTo(`
/** Mapping between all available schema types and the resolvers parents */
Expand Down Expand Up @@ -72,7 +86,7 @@ describe('Flow Resolvers Plugin', () => {
{
rootValueType: 'MyRoot',
asyncResolverTypes: true,
},
} as any,
{ outputFile: 'graphql.ts' }
)) as Types.ComplexPluginOutput;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { ParsedConfig, RawConfig, BaseVisitor } from './base-visitor';
import * as autoBind from 'auto-bind';
import { DEFAULT_SCALARS } from './scalars';
import { ScalarsMap, EnumValuesMap, ParsedEnumValuesMap } from './types';
import { DeclarationBlock, DeclarationBlockConfig, indent, getBaseTypeNode, buildScalars, getConfigValue, getBaseType, getRootTypeNames, stripMapperTypeInterpolation } from './utils';
import { DeclarationBlock, DeclarationBlockConfig, indent, getBaseTypeNode, buildScalars, getConfigValue, getBaseType, getRootTypeNames, stripMapperTypeInterpolation, OMIT_TYPE, REQUIRE_FIELDS_TYPE } from './utils';
import {
NameNode,
ListTypeNode,
Expand Down Expand Up @@ -206,6 +206,7 @@ export class BaseResolversVisitor<TRawConfig extends RawResolversConfig = RawRes
protected _resolversTypes: ResolverTypes = {};
protected _resolversParentTypes: ResolverParentTypes = {};
protected _rootTypeNames: string[] = [];
protected _globalDeclarations: Set<string> = new Set();

constructor(rawConfig: TRawConfig, additionalConfig: TPluginConfig, private _schema: GraphQLSchema, defaultScalars: ScalarsMap = DEFAULT_SCALARS) {
super(
Expand Down Expand Up @@ -377,6 +378,7 @@ export class BaseResolversVisitor<TRawConfig extends RawResolversConfig = RawRes
}

protected replaceFieldsInType(typeName: string, relevantFields: { addOptionalSign: boolean; fieldName: string; replaceWithType: string }[]): string {
this._globalDeclarations.add(OMIT_TYPE);
return `Omit<${typeName}, ${relevantFields.map(f => `'${f.fieldName}'`).join(' | ')}> & { ${relevantFields.map(f => `${f.fieldName}${f.addOptionalSign ? '?' : ''}: ${f.replaceWithType}`).join(', ')} }`;
}

Expand Down Expand Up @@ -455,6 +457,10 @@ export class BaseResolversVisitor<TRawConfig extends RawResolversConfig = RawRes
return Object.keys(this.config.mappers).filter(name => !this._usedMappers[name]);
}

public get globalDeclarations(): string[] {
return Array.from(this._globalDeclarations);
}

public get mappersImports(): string[] {
const groupedMappers: { [sourceFile: string]: { identifier: string; asDefault?: boolean }[] } = {};

Expand Down Expand Up @@ -632,31 +638,41 @@ export type IDirectiveResolvers${contextType} = ${name}<ContextType>;`
const hasArguments = node.arguments && node.arguments.length > 0;

return (parentName: string) => {
const original = parent[key];
const original: FieldDefinitionNode = parent[key];
const baseType = getBaseTypeNode(original.type);
const realType = baseType.name.value;
const typeToUse = this.getTypeToUse(realType);
const mappedType = this._variablesTransfomer.wrapAstTypeWithModifiers(typeToUse, original.type);
const subscriptionType = this._schema.getSubscriptionType();
const isSubscriptionType = subscriptionType && subscriptionType.name === parentName;
let argsType = hasArguments
? `${this.convertName(parentName, {
useTypesPrefix: true,
}) +
(this.config.addUnderscoreToArgsType ? '_' : '') +
this.convertName(node.name, {
useTypesPrefix: false,
}) +
'Args'}`
: null;

if (argsType !== null) {
const argsToForceRequire = original.arguments.filter(arg => !!arg.defaultValue);

if (argsToForceRequire.length > 0) {
argsType = this.applyRequireFields(argsType, argsToForceRequire);
}
}

return indent(
`${node.name}${this.config.avoidOptionals ? '' : '?'}: ${isSubscriptionType ? 'SubscriptionResolver' : 'Resolver'}<${mappedType}, ParentType, ContextType${
hasArguments
? `, ${this.convertName(parentName, {
useTypesPrefix: true,
}) +
(this.config.addUnderscoreToArgsType ? '_' : '') +
this.convertName(node.name, {
useTypesPrefix: false,
}) +
'Args'}`
: ''
}>,`
);
return indent(`${node.name}${this.config.avoidOptionals ? '' : '?'}: ${isSubscriptionType ? 'SubscriptionResolver' : 'Resolver'}<${mappedType}, ParentType, ContextType${argsType ? `, ${argsType}` : ''}>,`);
};
}

protected applyRequireFields(argsType: string, fields: InputValueDefinitionNode[]): string {
this._globalDeclarations.add(REQUIRE_FIELDS_TYPE);
return `RequireFields<${argsType}, ${fields.map(f => `'${f.name.value}'`).join(', ')}>`;
}

ObjectTypeDefinition(node: ObjectTypeDefinitionNode) {
const name = this.convertName(node, {
suffix: 'Resolvers',
Expand Down
1 change: 1 addition & 0 deletions packages/plugins/other/visitor-plugin-common/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -273,3 +273,4 @@ export function stripMapperTypeInterpolation(identifier: string): string {
}

export const OMIT_TYPE = 'export type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;';
export const REQUIRE_FIELDS_TYPE = `export type RequireFields<T, K extends keyof T> = { [X in Exclude<keyof T, K>]?: T[X] } & { [P in K]-?: NonNullable<T[P]> };`;
4 changes: 2 additions & 2 deletions packages/plugins/typescript/resolvers/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { RawResolversConfig, OMIT_TYPE } from '@graphql-codegen/visitor-plugin-common';
import { RawResolversConfig } from '@graphql-codegen/visitor-plugin-common';
import { Types, PluginFunction } from '@graphql-codegen/plugin-helpers';
import { isScalarType, parse, printSchema, visit, GraphQLSchema } from 'graphql';
import { TypeScriptResolversVisitor } from './visitor';
Expand Down Expand Up @@ -172,7 +172,7 @@ export type DirectiveResolverFn<TResult = {}, TParent = {}, TContext = {}, TArgs
}

return {
prepend: [`import { ${imports.join(', ')} } from 'graphql';`, ...mappersImports, OMIT_TYPE],
prepend: [`import { ${imports.join(', ')} } from 'graphql';`, ...mappersImports, ...visitor.globalDeclarations],
content: [header, resolversTypeMapping, resolversParentTypeMapping, ...visitorResult.definitions.filter(d => typeof d === 'string'), getRootResolver(), getAllDirectiveResolvers()].join('\n'),
};
};
Expand Down
10 changes: 5 additions & 5 deletions packages/plugins/typescript/resolvers/src/visitor.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { TypeScriptResolversPluginConfig } from './index';
import { ListTypeNode, NamedTypeNode, NonNullTypeNode, GraphQLSchema } from 'graphql';
import * as autoBind from 'auto-bind';
import { ParsedResolversConfig, BaseResolversVisitor } from '@graphql-codegen/visitor-plugin-common';
import { ParsedResolversConfig, BaseResolversVisitor, getConfigValue } from '@graphql-codegen/visitor-plugin-common';
import { TypeScriptOperationVariablesToObject } from '@graphql-codegen/typescript';

export interface ParsedTypeScriptResolversConfig extends ParsedResolversConfig {
Expand All @@ -15,10 +15,10 @@ export class TypeScriptResolversVisitor extends BaseResolversVisitor<TypeScriptR
super(
pluginConfig,
{
avoidOptionals: pluginConfig.avoidOptionals || false,
immutableTypes: pluginConfig.immutableTypes || false,
useIndexSignature: pluginConfig.useIndexSignature || false,
} as any,
avoidOptionals: getConfigValue(pluginConfig.avoidOptionals, false),
immutableTypes: getConfigValue(pluginConfig.immutableTypes, false),
useIndexSignature: getConfigValue(pluginConfig.useIndexSignature, false),
} as ParsedTypeScriptResolversConfig,
schema
);
autoBind(this);
Expand Down
28 changes: 27 additions & 1 deletion packages/plugins/typescript/resolvers/tests/ts-resolvers.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,32 @@ describe('TypeScript Resolvers Plugin', () => {
await validate(result);
});

it('Default values of args and compatibility with typescript plugin', async () => {
const testSchema = buildSchema(/* GraphQL */ `
type Query {
something(arg: String = "default_value"): String
}
`);

const config: any = { noSchemaStitching: true };
const result = (await plugin(testSchema, [], config, { outputFile: '' })) as Types.ComplexPluginOutput;
const mergedOutputs = mergeOutputs([
result,
{
content: `
const resolvers: QueryResolvers = {
something: (root, args, context, info) => {
return args.arg; // This should work becuase "args.arg" is now forced
}
};`,
},
]);

expect(mergedOutputs).toContain(`export type RequireFields`);
expect(mergedOutputs).toContain(`something?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType, RequireFields<QuerySomethingArgs, 'arg'>>,`);
validate(mergedOutputs);
});

it('Test for enum usage in resolvers (to verify compatibility with enumValues)', async () => {
const testSchema = buildSchema(/* GraphQL */ `
type Query {
Expand Down Expand Up @@ -1072,7 +1098,7 @@ describe('TypeScript Resolvers Plugin', () => {
{
rootValueType: 'MyRoot',
asyncResolverTypes: true,
},
} as any,
{ outputFile: 'graphql.ts' }
)) as Types.ComplexPluginOutput;

Expand Down

0 comments on commit 6cf9846

Please sign in to comment.