Skip to content

Commit 4653320

Browse files
fix(composition): Don't remove scalar properties in buildSubgraphSchema() when providing resolvers (#3285)
When a `GraphQLScalarType` is provided in resolvers to `buildSubgraphSchema()`, its properties overwrite the existing one in the schema created from `typeDefs`. However, this also includes non-provided/`undefined` properties, which probably isn't what users expect. This PR updates resolver addition so that `undefined` properties are skipped; if users want to unset/clear scalar properties, they should use an explicit `null` for those properties.
1 parent 60bc299 commit 4653320

File tree

3 files changed

+123
-17
lines changed

3 files changed

+123
-17
lines changed

.changeset/smooth-terms-decide.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@apollo/subgraph": patch
3+
---
4+
5+
When a `GraphQLScalarType` resolver is provided to `buildSubgraphSchema()`, omitted configuration options in the `GraphQLScalarType` no longer cause the corresponding properties in the GraphQL document/AST to be cleared. To explicitly clear these properties, use `null` for the configuration option instead.

subgraph-js/src/__tests__/buildSubgraphSchema.test.ts

Lines changed: 108 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@ import {
66
execute,
77
type DefinitionNode,
88
OperationTypeNode,
9+
GraphQLScalarType,
910
GraphQLUnionType,
1011
printType,
1112
} from 'graphql';
1213
import { buildSubgraphSchema } from '../buildSubgraphSchema';
14+
import { printSubgraphSchema } from '../printSubgraphSchema';
1315
import { typeSerializer } from 'apollo-federation-integration-testsuite';
1416
import { errorCauses } from '@apollo/federation-internals';
1517

@@ -964,10 +966,12 @@ describe('buildSubgraphSchema', () => {
964966

965967
const validateTag = async (
966968
header: string,
969+
printedHeader: string,
967970
directiveDefinitions: string,
968971
typeDefinitions: string,
972+
withResolvers: boolean,
969973
) => {
970-
const schema = buildSubgraphSchema(gql`
974+
const typeDefs = gql`
971975
${header}
972976
type User @key(fields: "email") @tag(name: "tagOnType") {
973977
email: String @tag(name: "tagOnField")
@@ -978,19 +982,15 @@ describe('buildSubgraphSchema', () => {
978982
}
979983
980984
union UserButAUnion @tag(name: "tagOnUnion") = User
981-
`);
982985
983-
const { data, errors } = await graphql({ schema, source: query });
984-
expect(errors).toBeUndefined();
985-
expect((data?._service as any).sdl).toMatchString(
986-
(header.length === 0
987-
? ''
988-
: `
989-
${header.trim()}
990-
`) +
991-
`
992-
${directiveDefinitions.trim()}
986+
scalar CustomScalar @tag(name: "tagOnCustomScalar")
993987
988+
enum Enum @tag(name: "tagOnEnum") {
989+
ENUM_VALUE @tag(name: "tagOnEnumValue")
990+
}
991+
`;
992+
993+
const printedTypeDefsWithoutHeader = `
994994
type User
995995
@key(fields: "email")
996996
@tag(name: "tagOnType")
@@ -1008,15 +1008,72 @@ describe('buildSubgraphSchema', () => {
10081008
@tag(name: "tagOnUnion")
10091009
= User
10101010
1011+
scalar CustomScalar
1012+
@tag(name: "tagOnCustomScalar")
1013+
1014+
enum Enum
1015+
@tag(name: "tagOnEnum")
1016+
{
1017+
ENUM_VALUE @tag(name: "tagOnEnumValue")
1018+
}
1019+
`;
1020+
1021+
const schema = withResolvers
1022+
? buildSubgraphSchema({
1023+
typeDefs,
1024+
resolvers: {
1025+
User: {
1026+
email: () => 1,
1027+
},
1028+
CustomScalar: new GraphQLScalarType({
1029+
name: 'CustomScalar',
1030+
serialize: () => 1,
1031+
parseValue: () => 1,
1032+
parseLiteral: () => 1,
1033+
}),
1034+
Enum: {
1035+
ENUM_VALUE: 1,
1036+
},
1037+
},
1038+
})
1039+
: buildSubgraphSchema(typeDefs);
1040+
1041+
// Check the sdl field's schema.
1042+
const { data, errors } = await graphql({ schema, source: query });
1043+
expect(errors).toBeUndefined();
1044+
expect((data?._service as any).sdl).toMatchString(
1045+
(header.length === 0
1046+
? ''
1047+
: `
1048+
${header.trim()}
1049+
`) +
1050+
`
1051+
${directiveDefinitions.trim()}
1052+
1053+
${printedTypeDefsWithoutHeader.trim()}
1054+
10111055
${typeDefinitions.trim()}
10121056
`,
10131057
);
1058+
1059+
// Check the printSubgraphSchema's schema.
1060+
expect(printSubgraphSchema(schema)).toMatchString(
1061+
(printedHeader.length === 0
1062+
? ''
1063+
: `
1064+
${printedHeader.trim()}
1065+
`) +
1066+
`
1067+
${printedTypeDefsWithoutHeader.trim()}
1068+
`,
1069+
);
10141070
};
10151071

1016-
it.each([
1072+
const testCases = [
10171073
{
10181074
name: 'fed1',
10191075
header: '',
1076+
printedHeader: '',
10201077
directiveDefinitions: `
10211078
directive @key(fields: _FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE
10221079
@@ -1053,6 +1110,10 @@ describe('buildSubgraphSchema', () => {
10531110
extend schema
10541111
@link(url: "https://specs.apollo.dev/link/v1.0")
10551112
@link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key", "@tag"])
1113+
`,
1114+
printedHeader: `
1115+
extend schema
1116+
@link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key", "@tag"])
10561117
`,
10571118
directiveDefinitions: `
10581119
directive @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA
@@ -1106,10 +1167,41 @@ describe('buildSubgraphSchema', () => {
11061167
}
11071168
`,
11081169
},
1109-
])(
1170+
];
1171+
1172+
it.each(testCases)(
11101173
'adds it for $name schema',
1111-
async ({ header, directiveDefinitions, typesDefinitions }) => {
1112-
await validateTag(header, directiveDefinitions, typesDefinitions);
1174+
async ({
1175+
header,
1176+
printedHeader,
1177+
directiveDefinitions,
1178+
typesDefinitions,
1179+
}) => {
1180+
await validateTag(
1181+
header,
1182+
printedHeader,
1183+
directiveDefinitions,
1184+
typesDefinitions,
1185+
false,
1186+
);
1187+
},
1188+
);
1189+
1190+
it.each(testCases)(
1191+
'adds it for $name schema with resolvers',
1192+
async ({
1193+
header,
1194+
printedHeader,
1195+
directiveDefinitions,
1196+
typesDefinitions,
1197+
}) => {
1198+
await validateTag(
1199+
header,
1200+
printedHeader,
1201+
directiveDefinitions,
1202+
typesDefinitions,
1203+
true,
1204+
);
11131205
},
11141206
);
11151207
});

subgraph-js/src/schema-helper/buildSchemaFromSDL.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,16 @@ export function addResolversToSchema(
133133

134134
if (isScalarType(type)) {
135135
for (const fn in fieldConfigs) {
136-
(type as any)[fn] = (fieldConfigs as any)[fn];
136+
const fnValue = (fieldConfigs as any)[fn];
137+
// When users provide a `GraphQLScalarType` for resolvers, they often
138+
// omit several config options (effectively providing `undefined`), but
139+
// they probably don't mean for this to unset values in the existing
140+
// `GraphQLScalarType` (e.g., clearing the AST nodes or description).
141+
// So we explicitly ignore `undefined` values here; if users do want to
142+
// unset existing values, they can use `null` instead.
143+
if (fnValue !== undefined) {
144+
(type as any)[fn] = fnValue;
145+
}
137146
}
138147
}
139148

0 commit comments

Comments
 (0)