Skip to content

Commit c25f3f3

Browse files
authored
fix: support __typename in field sets (#2348)
1 parent df8a0e3 commit c25f3f3

File tree

11 files changed

+1232
-276
lines changed

11 files changed

+1232
-276
lines changed

composition-go/index.global.js

Lines changed: 180 additions & 179 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

composition/src/errors/errors.ts

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@ import {
88
import {
99
IncompatibleMergedTypesErrorParams,
1010
IncompatibleParentTypeMergeErrorParams,
11+
IncompatibleTypeWithProvidesErrorMessageParams,
1112
InvalidNamedTypeErrorParams,
1213
InvalidRootTypeFieldEventsDirectiveData,
14+
NonExternalConditionalFieldErrorParams,
1315
OneOfRequiredFieldsErrorParams,
1416
SemanticNonNullLevelsIndexOutOfBoundsErrorParams,
1517
SemanticNonNullLevelsNonNullErrorParams,
@@ -25,13 +27,15 @@ import {
2527
INTERFACE,
2628
LEVELS,
2729
LITERAL_NEW_LINE,
30+
LITERAL_PERIOD,
2831
NOT_UPPER,
2932
OR_UPPER,
3033
QUOTATION_JOIN,
3134
SUBSCRIPTION_FIELD_CONDITION,
3235
SUBSCRIPTION_FILTER,
3336
SUBSCRIPTION_FILTER_CONDITION,
3437
SUBSCRIPTION_FILTER_VALUE,
38+
TYPENAME,
3539
UNION,
3640
VALUES,
3741
} from '../utils/string-constants';
@@ -684,9 +688,13 @@ export function invalidConfigurationDataErrorMessage(typeName: string, fieldName
684688
);
685689
}
686690

687-
export function incompatibleTypeWithProvidesErrorMessage(fieldCoords: string, responseType: string): string {
691+
export function incompatibleTypeWithProvidesErrorMessage({
692+
fieldCoords,
693+
responseType,
694+
subgraphName,
695+
}: IncompatibleTypeWithProvidesErrorMessageParams): string {
688696
return (
689-
` A "@provides" directive is declared on field "${fieldCoords}".\n` +
697+
` A "@provides" directive is declared on field "${fieldCoords}" in subgraph "${subgraphName}".\n` +
690698
` However, the response type "${responseType}" is not an Object nor Interface.`
691699
);
692700
}
@@ -1472,19 +1480,23 @@ export function externalInterfaceFieldsError(typeName: string, fieldNames: Array
14721480
);
14731481
}
14741482

1475-
export function nonExternalConditionalFieldError(
1476-
directiveCoords: string,
1477-
subgraphName: string,
1478-
targetCoords: string,
1479-
fieldSet: string,
1480-
fieldSetDirectiveName: string,
1481-
): Error {
1483+
export function nonExternalConditionalFieldError({
1484+
directiveCoords,
1485+
fieldSet,
1486+
directiveName,
1487+
subgraphName,
1488+
targetCoords,
1489+
}: NonExternalConditionalFieldErrorParams): Error {
1490+
const segments = targetCoords.split(LITERAL_PERIOD);
1491+
const isTypeName = segments[segments.length - 1] === TYPENAME;
14821492
return new Error(
1483-
`The field "${directiveCoords}" in subgraph "${subgraphName}" defines a "@${fieldSetDirectiveName}"` +
1493+
`The field "${directiveCoords}" in subgraph "${subgraphName}" defines a "@${directiveName}"` +
14841494
` directive with the following field set:\n "${fieldSet}".` +
1485-
`\nHowever, neither the field "${targetCoords}" nor any of its field set ancestors are declared "@external".` +
1495+
(isTypeName
1496+
? `\nHowever, none of the field set ancestors of "__typename" is declared "@external".`
1497+
: `\nHowever, neither the field "${targetCoords}" nor any of its field set ancestors are declared "@external".`) +
14861498
`\nConsequently, "${targetCoords}" is already provided by subgraph "${subgraphName}" and should not form part of` +
1487-
` a "@${fieldSetDirectiveName}" directive field set.`,
1499+
` a "@${directiveName}" directive field set.`,
14881500
);
14891501
}
14901502

@@ -1586,6 +1598,13 @@ export function invalidDirectiveDefinitionError(directiveName: string, errorMess
15861598
);
15871599
}
15881600

1601+
export function typeNameAlreadyProvidedErrorMessage(fieldCoords: string, subgraphName: string): string {
1602+
return (
1603+
` The field "${fieldCoords}" is unconditionally provided by subgraph "${subgraphName}" and should not form` +
1604+
` part of any "@provides" field set.`
1605+
);
1606+
}
1607+
15891608
export function fieldAlreadyProvidedErrorMessage(
15901609
fieldCoords: string,
15911610
subgraphName: string,

composition/src/errors/types.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { FieldData, InputValueData, ParentDefinitionData } from '../schema-building/types';
2-
import { FieldName, SubgraphName, TypeName } from '../types/types';
2+
import { DirectiveName, FieldName, SubgraphName, TypeName } from '../types/types';
33

44
export type InvalidRootTypeFieldEventsDirectiveData = {
55
definesDirectives: boolean;
6-
invalidDirectiveNames: string[];
6+
invalidDirectiveNames: Array<string>;
77
};
88

99
export type IncompatibleMergedTypesErrorParams = {
@@ -40,3 +40,17 @@ export type IncompatibleParentTypeMergeErrorParams = {
4040
incomingSubgraphName: SubgraphName;
4141
incomingNodeType?: string;
4242
};
43+
44+
export type IncompatibleTypeWithProvidesErrorMessageParams = {
45+
fieldCoords: string;
46+
responseType: TypeName;
47+
subgraphName: SubgraphName;
48+
};
49+
50+
export type NonExternalConditionalFieldErrorParams = {
51+
directiveCoords: string;
52+
fieldSet: string;
53+
directiveName: DirectiveName;
54+
subgraphName: SubgraphName;
55+
targetCoords: string;
56+
};

composition/src/utils/string-constants.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ export const LINK_PURPOSE = 'link__Purpose';
8686
export const LIST = 'list';
8787
export const LITERAL_SPACE = ' ';
8888
export const LITERAL_NEW_LINE = '\n';
89+
export const LITERAL_PERIOD = '.';
8990
export const NUMBER = 'number';
9091
export const MUTATION = 'Mutation';
9192
export const MUTATION_UPPER = 'MUTATION';
@@ -110,7 +111,6 @@ export const OVERRIDE = 'override';
110111
export const PARENT_DEFINITION_DATA = 'parentDefinitionDataByTypeName';
111112
export const PARENT_DEFINITION_DATA_MAP = 'parentDefinitionDataByParentTypeName';
112113
export const PARENT_EXTENSION_DATA_MAP = 'parentExtensionDataByParentTypeName';
113-
export const PERIOD = '.';
114114
export const PROVIDER_ID = 'providerId';
115115
export const PROVIDES = 'provides';
116116
export const PUBLISH = 'publish';
@@ -153,6 +153,7 @@ export const SUCCESS = 'success';
153153
export const TAG = 'tag';
154154
export const TOPIC = 'topic';
155155
export const TOPICS = 'topics';
156+
export const TYPENAME = '__typename';
156157
export const UNION = 'Union';
157158
export const UNION_UPPER = 'UNION';
158159
export const URL_LOWER = 'url';

composition/src/v1/federation/federation-factory.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ import {
205205
ONE_OF,
206206
OR_UPPER,
207207
PARENT_DEFINITION_DATA,
208-
PERIOD,
208+
LITERAL_PERIOD,
209209
QUERY,
210210
REQUIRES_SCOPES,
211211
SEMANTIC_NON_NULL,
@@ -598,7 +598,7 @@ export class FederationFactory {
598598

599599
generateTagData() {
600600
for (const [path, tagNames] of this.tagNamesByCoords) {
601-
const paths = path.split(PERIOD);
601+
const paths = path.split(LITERAL_PERIOD);
602602
if (paths.length < 1) {
603603
continue;
604604
}
@@ -1482,7 +1482,7 @@ export class FederationFactory {
14821482

14831483
handleDisparateFieldNamedTypes() {
14841484
for (const [fieldCoordinates, subgraphNamesByNamedTypeName] of this.subgraphNamesByNamedTypeNameByFieldCoords) {
1485-
const coordinates = fieldCoordinates.split(PERIOD);
1485+
const coordinates = fieldCoordinates.split(LITERAL_PERIOD);
14861486
if (coordinates.length !== 2) {
14871487
continue;
14881488
}
@@ -2241,7 +2241,7 @@ export class FederationFactory {
22412241
}
22422242
for (const coords of fieldCoords) {
22432243
// The coords should all be exactly <parentTypeName>.<fieldName>
2244-
const segments = coords.split(PERIOD);
2244+
const segments = coords.split(LITERAL_PERIOD);
22452245
switch (segments.length) {
22462246
case 2: {
22472247
const parentAuthData = getValueOrDefault(this.authorizationDataByParentTypeName, segments[0], () =>
@@ -2348,7 +2348,7 @@ export class FederationFactory {
23482348
return false;
23492349
}
23502350
const coordinates = path.split(LEFT_PARENTHESIS)[0];
2351-
const segments = coordinates.split(PERIOD);
2351+
const segments = coordinates.split(LITERAL_PERIOD);
23522352
let segment = segments[0];
23532353
for (let i = 0; i < segments.length; i++) {
23542354
if (this.inaccessibleCoords.has(segment)) {
@@ -2399,7 +2399,7 @@ export class FederationFactory {
23992399
directiveSubgraphName: string,
24002400
fieldErrorMessages: Array<string>,
24012401
): string[] {
2402-
const paths = conditionFieldPath.split(PERIOD);
2402+
const paths = conditionFieldPath.split(LITERAL_PERIOD);
24032403
if (paths.length < 1) {
24042404
fieldErrorMessages.push(
24052405
invalidSubscriptionFieldConditionFieldPathErrorMessage(inputFieldPath, conditionFieldPath),

composition/src/v1/federation/utils.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { ContractTagOptions } from '../../federation/types';
2323
import { getOrThrowError, getValueOrDefault } from '../../utils/utils';
2424
import { KeyFieldSetData } from '../normalization/types';
2525
import { SubgraphName, TypeName } from '../../types/types';
26+
import { TYPENAME } from '../../utils/string-constants';
2627

2728
export type FederationFactoryParams = {
2829
authorizationDataByParentTypeName: Map<TypeName, AuthorizationData>;
@@ -137,6 +138,9 @@ export function validateImplicitFieldSets({
137138
return BREAK;
138139
}
139140
const fieldName = node.name.value;
141+
if (fieldName === TYPENAME) {
142+
return;
143+
}
140144
const fieldData = parentData.fieldDataByName.get(fieldName);
141145
// undefined if the field does not exist on the parent
142146
if (!fieldData || fieldData.argumentDataByName.size || definedFields[currentDepth].has(fieldName)) {

0 commit comments

Comments
 (0)