Skip to content

Commit 933c3d4

Browse files
hannesjtrevor-scheer
authored andcommitted
Add support for custom enum resolvers and scalar types (#1345)
* buildSchemaFromSDL - support custom scalar resolvers Fixes #2832 * Add enum renaming ability to federated services. * Test server->client encoding enum internal values * Fix enum internal/external value mapping * Use GraphQLEnumType#toConfig() in clone
1 parent 22fc3ee commit 933c3d4

4 files changed

Lines changed: 133 additions & 12 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
- `apollo-env`
1818
- <First `apollo-env` related entry goes here>
1919
- `apollo-graphql`
20-
- <First `apollo-graphql` related entry goes here>
20+
- buildSchemaFromSDL - Add support for merging Scalar and Enum resolvers to schema [#1345](https://github.com/apollographql/apollo-tooling/pull/1345)
2121
- `apollo-language-server`
2222
- <First `apollo-language-server` related entry goes here>
2323
- `apollo-tools`

packages/apollo-graphql/src/schema/__tests__/buildSchemaFromSDL.test.ts

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,13 @@ import {
55
GraphQLDirective,
66
DirectiveLocation,
77
GraphQLObjectType,
8-
GraphQLAbstractType
8+
GraphQLAbstractType,
9+
GraphQLScalarType,
10+
GraphQLScalarTypeConfig,
11+
GraphQLEnumType,
12+
Kind,
13+
execute,
14+
ExecutionResult
915
} from "graphql";
1016

1117
import astSerializer from "./snapshotSerializers/astSerializer";
@@ -422,5 +428,80 @@ type MutationRoot {
422428
expect(animalUnion.resolveType).toBe(resolveTypeUnion);
423429
expect(creatureInterface.resolveType).toBe(resolveTypeInterface);
424430
});
431+
432+
it(`should add resolvers for scalar types`, () => {
433+
const typeDefs = gql`
434+
scalar Custom
435+
`;
436+
437+
const customTypeConfig: GraphQLScalarTypeConfig<string, string> = {
438+
name: "Custom",
439+
serialize: value => value,
440+
parseValue: value => value,
441+
parseLiteral: input => {
442+
if (input.kind !== Kind.STRING) {
443+
throw new Error("Expected value to be string");
444+
}
445+
return input.value;
446+
}
447+
};
448+
449+
const CustomType = new GraphQLScalarType(customTypeConfig);
450+
451+
const resolvers = { Custom: CustomType };
452+
453+
const schema = buildSchemaFromSDL([{ typeDefs, resolvers }]);
454+
const custom = schema.getType("Custom") as GraphQLScalarType;
455+
456+
expect(custom.parseLiteral).toBe(CustomType.parseLiteral);
457+
expect(custom.parseValue).toBe(CustomType.parseValue);
458+
expect(custom.serialize).toBe(CustomType.serialize);
459+
});
460+
461+
it(`should add resolvers to enum types`, () => {
462+
const typeDefs = gql`
463+
enum AllowedColor {
464+
RED
465+
GREEN
466+
BLUE
467+
}
468+
469+
type Query {
470+
favoriteColor: AllowedColor
471+
avatar(borderColor: AllowedColor): String
472+
}
473+
`;
474+
475+
const mockResolver = jest.fn();
476+
477+
const resolvers = {
478+
AllowedColor: {
479+
RED: "#f00",
480+
GREEN: "#0f0",
481+
BLUE: "#00f"
482+
},
483+
Query: {
484+
favoriteColor: () => "#f00",
485+
avatar: (_: any, params: any) => mockResolver(_, params)
486+
}
487+
};
488+
489+
const schema = buildSchemaFromSDL([{ typeDefs, resolvers }]);
490+
const colorEnum = schema.getType("AllowedColor") as GraphQLEnumType;
491+
492+
let result = execute(
493+
schema,
494+
gql`
495+
query {
496+
favoriteColor
497+
avatar(borderColor: RED)
498+
}
499+
`
500+
);
501+
502+
expect((result as ExecutionResult).data!.favoriteColor).toBe("RED");
503+
expect(colorEnum.getValue("RED")!.value).toBe("#f00");
504+
expect(mockResolver).toBeCalledWith(undefined, { borderColor: "#f00" });
505+
});
425506
});
426507
});

packages/apollo-graphql/src/schema/buildSchemaFromSDL.ts

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,11 @@ import {
1414
SchemaExtensionNode,
1515
OperationTypeNode,
1616
GraphQLObjectType,
17-
isAbstractType
17+
GraphQLEnumType,
18+
isAbstractType,
19+
isScalarType,
20+
isEnumType,
21+
GraphQLEnumValue
1822
} from "graphql";
1923
import { validateSDL } from "graphql/validation/validate";
2024
import { isDocumentNode, isNode } from "../utilities/graphql";
@@ -216,6 +220,37 @@ export function addResolversToSchema(
216220
}
217221
}
218222

223+
if (isScalarType(type)) {
224+
for (const fn in fieldConfigs) {
225+
(type as any)[fn] = (fieldConfigs as any)[fn];
226+
}
227+
}
228+
229+
if (isEnumType(type)) {
230+
const values = type.getValues();
231+
const newValues: { [key: string]: GraphQLEnumValue } = {};
232+
values.forEach(value => {
233+
const newValue = (fieldConfigs as any)[value.name] || value.name;
234+
newValues[value.name] = {
235+
value: newValue,
236+
deprecationReason: value.deprecationReason,
237+
description: value.description,
238+
astNode: value.astNode,
239+
name: value.name
240+
};
241+
});
242+
243+
// In place updating hack to get around pulling in the full
244+
// schema walking and immutable updating machinery from graphql-tools
245+
Object.assign(
246+
type,
247+
new GraphQLEnumType({
248+
...type.toConfig(),
249+
values: newValues
250+
})
251+
);
252+
}
253+
219254
if (!isObjectType(type)) continue;
220255

221256
const fieldMap = type.getFields();
Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
1-
import { GraphQLFieldResolver } from "graphql";
1+
import { GraphQLFieldResolver, GraphQLScalarType } from "graphql";
22

33
export interface GraphQLResolverMap<TContext = {}> {
4-
[typeName: string]: {
5-
[fieldName: string]:
6-
| GraphQLFieldResolver<any, TContext>
7-
| {
8-
requires?: string;
9-
resolve: GraphQLFieldResolver<any, TContext>;
10-
};
11-
};
4+
[typeName: string]:
5+
| {
6+
[fieldName: string]:
7+
| GraphQLFieldResolver<any, TContext>
8+
| {
9+
requires?: string;
10+
resolve: GraphQLFieldResolver<any, TContext>;
11+
};
12+
}
13+
| GraphQLScalarType
14+
| {
15+
[enumValue: string]: string | number;
16+
};
1217
}

0 commit comments

Comments
 (0)