Skip to content
This repository was archived by the owner on Apr 15, 2020. It is now read-only.

Commit 4624f5c

Browse files
committed
fix(stitching): add default value support
fixes ardatan#1121
1 parent f820eac commit 4624f5c

9 files changed

+363
-154
lines changed

src/Interfaces.ts

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import {
88
GraphQLIsTypeOfFn,
99
GraphQLTypeResolver,
1010
GraphQLScalarType,
11-
GraphQLNamedType,
1211
DocumentNode,
1312
} from 'graphql';
1413

@@ -176,23 +175,6 @@ export interface IMockServer {
176175
) => Promise<ExecutionResult>;
177176
}
178177

179-
export type MergeTypeCandidate = {
180-
schema?: GraphQLSchema;
181-
type: GraphQLNamedType;
182-
};
183-
184-
export type TypeWithResolvers = {
185-
type: GraphQLNamedType;
186-
resolvers?: IResolvers;
187-
};
188-
189-
export type VisitTypeResult = GraphQLNamedType | TypeWithResolvers | null;
190-
191-
export type VisitType = (
192-
name: string,
193-
candidates: Array<MergeTypeCandidate>,
194-
) => VisitTypeResult;
195-
196178
export type Operation = 'query' | 'mutation' | 'subscription';
197179

198180
export type Request = {

src/generate/addResolveFunctionsToSchema.ts

Lines changed: 45 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import {
1818
} from '../Interfaces';
1919
import { applySchemaTransforms } from '../transforms/transforms';
2020
import { checkForResolveTypeResolver, extendResolversFromInterfaces } from '.';
21-
import ConvertEnumValues from '../transforms/ConvertEnumValues';
21+
import AddEnumAndScalarResolvers from '../transforms/AddEnumAndScalarResolvers';
2222

2323
function addResolveFunctionsToSchema(
2424
options: IAddResolveFunctionsToSchemaOptions | GraphQLSchema,
@@ -55,6 +55,8 @@ function addResolveFunctionsToSchema(
5555
// Used to map the external value of an enum to its internal value, when
5656
// that internal value is provided by a resolver.
5757
const enumValueMap = Object.create(null);
58+
// Used to store custom scalar implementations.
59+
const scalarTypeMap = Object.create(null);
5860

5961
Object.keys(resolvers).forEach(typeName => {
6062
const resolverValue = resolvers[typeName];
@@ -63,7 +65,7 @@ function addResolveFunctionsToSchema(
6365
if (resolverType !== 'object' && resolverType !== 'function') {
6466
throw new SchemaError(
6567
`"${typeName}" defined in resolvers, but has invalid value "${resolverValue}". A resolver's value ` +
66-
`must be of type object or function.`,
68+
`must be of type object or function.`,
6769
);
6870
}
6971

@@ -79,19 +81,10 @@ function addResolveFunctionsToSchema(
7981
);
8082
}
8183

82-
Object.keys(resolverValue).forEach(fieldName => {
83-
if (fieldName.startsWith('__')) {
84-
// this is for isTypeOf and resolveType and all the other stuff.
85-
type[fieldName.substring(2)] = resolverValue[fieldName];
86-
return;
87-
}
88-
89-
if (type instanceof GraphQLScalarType) {
90-
type[fieldName] = resolverValue[fieldName];
91-
return;
92-
}
93-
94-
if (type instanceof GraphQLEnumType) {
84+
if (type instanceof GraphQLScalarType) {
85+
scalarTypeMap[type.name] = resolverValue;
86+
} else if (type instanceof GraphQLEnumType) {
87+
Object.keys(resolverValue).forEach(fieldName => {
9588
if (!type.getValue(fieldName)) {
9689
if (allowResolversNotInSchema) {
9790
return;
@@ -111,53 +104,61 @@ function addResolveFunctionsToSchema(
111104
// internal value.
112105
enumValueMap[type.name] = enumValueMap[type.name] || {};
113106
enumValueMap[type.name][fieldName] = resolverValue[fieldName];
114-
return;
115-
}
116-
107+
});
108+
} else {
117109
// object type
118-
const fields = getFieldsForType(type);
119-
if (!fields) {
120-
if (allowResolversNotInSchema) {
110+
Object.keys(resolverValue).forEach(fieldName => {
111+
if (fieldName.startsWith('__')) {
112+
// this is for isTypeOf and resolveType and all the other stuff.
113+
type[fieldName.substring(2)] = resolverValue[fieldName];
121114
return;
122115
}
123116

124-
throw new SchemaError(
125-
`${typeName} was defined in resolvers, but it's not an object`,
126-
);
127-
}
117+
const fields = getFieldsForType(type);
118+
if (!fields) {
119+
if (allowResolversNotInSchema) {
120+
return;
121+
}
128122

129-
if (!fields[fieldName]) {
130-
if (allowResolversNotInSchema) {
131-
return;
123+
throw new SchemaError(
124+
`${typeName} was defined in resolvers, but it's not an object`,
125+
);
132126
}
133127

134-
throw new SchemaError(
135-
`${typeName}.${fieldName} defined in resolvers, but not in schema`,
136-
);
137-
}
138-
const field = fields[fieldName];
139-
const fieldResolve = resolverValue[fieldName];
140-
if (typeof fieldResolve === 'function') {
141-
// for convenience. Allows shorter syntax in resolver definition file
142-
setFieldProperties(field, { resolve: fieldResolve });
143-
} else {
144-
if (typeof fieldResolve !== 'object') {
128+
if (!fields[fieldName]) {
129+
if (allowResolversNotInSchema) {
130+
return;
131+
}
132+
145133
throw new SchemaError(
146-
`Resolver ${typeName}.${fieldName} must be object or function`,
134+
`${typeName}.${fieldName} defined in resolvers, but not in schema`,
147135
);
148136
}
149-
setFieldProperties(field, fieldResolve);
150-
}
151-
});
137+
const field = fields[fieldName];
138+
const fieldResolve = resolverValue[fieldName];
139+
if (typeof fieldResolve === 'function') {
140+
// for convenience. Allows shorter syntax in resolver definition file
141+
setFieldProperties(field, { resolve: fieldResolve });
142+
} else {
143+
if (typeof fieldResolve !== 'object') {
144+
throw new SchemaError(
145+
`Resolver ${typeName}.${fieldName} must be object or function`,
146+
);
147+
}
148+
setFieldProperties(field, fieldResolve);
149+
}
150+
});
151+
}
152152
});
153153

154154
checkForResolveTypeResolver(schema, requireResolversForResolveType);
155155

156156
// If there are any enum resolver functions (that are used to return
157157
// internal enum values), create a new schema that includes enums with the
158158
// new internal facing values.
159+
// also parse all defaultValues in all input fields to use internal values for enums/scalars
159160
const updatedSchema = applySchemaTransforms(schema, [
160-
new ConvertEnumValues(enumValueMap),
161+
new AddEnumAndScalarResolvers(enumValueMap, scalarTypeMap),
161162
]);
162163

163164
return updatedSchema;

src/stitching/mergeSchemas.ts

Lines changed: 29 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,6 @@ import {
1818
IFieldResolver,
1919
IResolvers,
2020
MergeInfo,
21-
MergeTypeCandidate,
22-
TypeWithResolvers,
23-
VisitTypeResult,
2421
IResolversParameter,
2522
} from '../Interfaces';
2623
import {
@@ -43,7 +40,18 @@ import {
4340
import mergeDeep from '../mergeDeep';
4441
import { SchemaDirectiveVisitor } from '../schemaVisitor';
4542

46-
export type OnTypeConflict = (
43+
type MergeTypeCandidate = {
44+
schema?: GraphQLSchema;
45+
type: GraphQLNamedType;
46+
};
47+
48+
type MergeTypeCandidatesResult = {
49+
type?: GraphQLNamedType;
50+
resolvers?: IResolvers;
51+
candidate?: MergeTypeCandidate;
52+
};
53+
54+
type OnTypeConflict = (
4755
left: GraphQLNamedType,
4856
right: GraphQLNamedType,
4957
info?: {
@@ -76,35 +84,6 @@ export default function mergeSchemas({
7684
schemaDirectives?: { [name: string]: typeof SchemaDirectiveVisitor };
7785
inheritResolversFromInterfaces?: boolean;
7886
mergeDirectives?: boolean,
79-
80-
}): GraphQLSchema {
81-
return mergeSchemasImplementation({
82-
schemas,
83-
onTypeConflict,
84-
resolvers,
85-
schemaDirectives,
86-
inheritResolversFromInterfaces,
87-
mergeDirectives,
88-
});
89-
}
90-
91-
function mergeSchemasImplementation({
92-
schemas,
93-
onTypeConflict,
94-
resolvers,
95-
schemaDirectives,
96-
inheritResolversFromInterfaces,
97-
mergeDirectives,
98-
}: {
99-
schemas: Array<
100-
string | GraphQLSchema | DocumentNode | Array<GraphQLNamedType>
101-
>;
102-
onTypeConflict?: OnTypeConflict;
103-
resolvers?: IResolversParameter;
104-
schemaDirectives?: { [name: string]: typeof SchemaDirectiveVisitor };
105-
inheritResolversFromInterfaces?: boolean;
106-
mergeDirectives?: boolean,
107-
10887
}): GraphQLSchema {
10988
const allSchemas: Array<GraphQLSchema> = [];
11089
const typeCandidates: { [name: string]: Array<MergeTypeCandidate> } = {};
@@ -229,28 +208,22 @@ function mergeSchemasImplementation({
229208
let generatedResolvers = {};
230209

231210
Object.keys(typeCandidates).forEach(typeName => {
232-
const resultType: VisitTypeResult = defaultVisitType(
211+
const mergeResult: MergeTypeCandidatesResult = mergeTypeCandidates(
233212
typeName,
234213
typeCandidates[typeName],
235214
onTypeConflict ? onTypeConflictToCandidateSelector(onTypeConflict) : undefined
236215
);
237-
if (resultType === null) {
238-
types[typeName] = null;
216+
let type: GraphQLNamedType;
217+
let typeResolvers: IResolvers;
218+
if (mergeResult.type) {
219+
type = mergeResult.type;
220+
typeResolvers = mergeResult.resolvers;
239221
} else {
240-
let type: GraphQLNamedType;
241-
let typeResolvers: IResolvers;
242-
if (isNamedType(<GraphQLNamedType>resultType)) {
243-
type = <GraphQLNamedType>resultType;
244-
} else if ((<TypeWithResolvers>resultType).type) {
245-
type = (<TypeWithResolvers>resultType).type;
246-
typeResolvers = (<TypeWithResolvers>resultType).resolvers;
247-
} else {
248-
throw new Error(`Invalid visitType result for type ${typeName}`);
249-
}
250-
types[typeName] = recreateType(type, resolveType, false);
251-
if (typeResolvers) {
252-
generatedResolvers[typeName] = typeResolvers;
253-
}
222+
throw new Error(`Invalid mergeTypeCandidates result for type ${typeName}`);
223+
}
224+
types[typeName] = recreateType(type, resolveType, false);
225+
if (typeResolvers) {
226+
generatedResolvers[typeName] = typeResolvers;
254227
}
255228
});
256229

@@ -473,11 +446,11 @@ function onTypeConflictToCandidateSelector(onTypeConflict: OnTypeConflict): Cand
473446
});
474447
}
475448

476-
function defaultVisitType(
449+
function mergeTypeCandidates(
477450
name: string,
478451
candidates: Array<MergeTypeCandidate>,
479452
candidateSelector?: CandidateSelector
480-
) {
453+
): MergeTypeCandidatesResult {
481454
if (!candidateSelector) {
482455
candidateSelector = cands => cands[cands.length - 1];
483456
}
@@ -524,6 +497,9 @@ function defaultVisitType(
524497
};
525498
} else {
526499
const candidate = candidateSelector(candidates);
527-
return candidate.type;
500+
return {
501+
type: candidate.type,
502+
candidate
503+
};
528504
}
529505
}

src/stitching/schemaRecreation.ts

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
GraphQLFieldMap,
1010
GraphQLInputField,
1111
GraphQLInputFieldConfig,
12+
GraphQLInputType,
1213
GraphQLInputFieldConfigMap,
1314
GraphQLInputFieldMap,
1415
GraphQLInputObjectType,
@@ -35,6 +36,12 @@ import isSpecifiedScalarType from '../isSpecifiedScalarType';
3536
import { ResolveType } from '../Interfaces';
3637
import resolveFromParentTypename from './resolveFromParentTypename';
3738
import defaultMergedResolver from './defaultMergedResolver';
39+
import { isStub } from './typeFromAST';
40+
import {
41+
serializeInputValue,
42+
parseInputValue,
43+
parseInputValueLiteral
44+
} from '../transformInputValue';
3845

3946
export function recreateType(
4047
type: GraphQLNamedType,
@@ -265,9 +272,10 @@ export function argumentToArgumentConfig(
265272
return [
266273
argument.name,
267274
{
268-
type: type,
269-
defaultValue: argument.defaultValue,
275+
type,
276+
defaultValue: reparseDefaultValue(argument.defaultValue, argument.type, type),
270277
description: argument.description,
278+
astNode: argument.astNode,
271279
},
272280
];
273281
}
@@ -292,10 +300,22 @@ export function inputFieldToFieldConfig(
292300
field: GraphQLInputField,
293301
resolveType: ResolveType<any>,
294302
): GraphQLInputFieldConfig {
303+
const type = resolveType(field.type);
295304
return {
296-
type: resolveType(field.type),
297-
defaultValue: field.defaultValue,
305+
type,
306+
defaultValue: reparseDefaultValue(field.defaultValue, field.type, type),
298307
description: field.description,
299308
astNode: field.astNode,
300309
};
301310
}
311+
312+
function reparseDefaultValue(
313+
originalDefaultValue: any,
314+
originalType: GraphQLInputType,
315+
newType: GraphQLInputType,
316+
) {
317+
if (originalType instanceof GraphQLInputObjectType && isStub(originalType)) {
318+
return parseInputValueLiteral(newType, originalDefaultValue);
319+
}
320+
return parseInputValue(newType, serializeInputValue(originalType, originalDefaultValue));
321+
}

src/stitching/typeFromAST.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import {
2121
ScalarTypeDefinitionNode,
2222
TypeNode,
2323
UnionTypeDefinitionNode,
24-
valueFromAST,
2524
getDescription,
2625
GraphQLString,
2726
GraphQLDirective,
@@ -183,7 +182,7 @@ function makeValues(nodes: ReadonlyArray<InputValueDefinitionNode>) {
183182
const type = resolveType(node.type, 'input') as GraphQLInputType;
184183
result[node.name.value] = {
185184
type,
186-
defaultValue: valueFromAST(node.defaultValue, type),
185+
defaultValue: node.defaultValue,
187186
description: getDescription(node, backcompatOptions),
188187
};
189188
});
@@ -227,6 +226,12 @@ function createNamedStub(
227226
});
228227
}
229228

229+
export function isStub(type: GraphQLObjectType | GraphQLInputObjectType | GraphQLInterfaceType): boolean {
230+
const fields = type.getFields();
231+
const fieldNames = Object.keys(fields);
232+
return fieldNames.length === 1 && fields[fieldNames[0]].name === '__fake';
233+
}
234+
230235
function makeDirective(node: DirectiveDefinitionNode): GraphQLDirective {
231236
const locations: Array<DirectiveLocationEnum> = [];
232237
node.locations.forEach(location => {

0 commit comments

Comments
 (0)