Skip to content

Commit 4f35e40

Browse files
authored
Merge pull request #26 from wayfair-incubator/gwardwell_fix_enum_support
fix: include all enum values in FROID schema
2 parents 58ab7b4 + 30875e6 commit 4f35e40

File tree

4 files changed

+128
-13
lines changed

4 files changed

+128
-13
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to
77
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
88

9+
## [v3.1.1] - 2024-02-15
10+
11+
### Fix
12+
13+
- The `FroidSchema` class does not include all enum values found across
14+
subgraphs when enum definitions differ.
15+
916
## [v3.1.0] - 2023-11-09
1017

1118
- Added a new `FroidSchema` class to test the next version of FROID schema

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@wayfair/node-froid",
3-
"version": "3.1.0",
3+
"version": "3.1.1",
44
"description": "Federated GQL Relay Object Identification implementation",
55
"main": "dist/index.js",
66
"types": "dist/index.d.ts",

src/schema/FroidSchema.ts

Lines changed: 46 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
DefinitionNode,
66
DocumentNode,
77
EnumTypeDefinitionNode,
8+
EnumValueDefinitionNode,
89
FieldDefinitionNode,
910
InterfaceTypeDefinitionNode,
1011
Kind,
@@ -423,7 +424,7 @@ export class FroidSchema {
423424
*
424425
* Enum values with @inaccessible tags are stripped in Federation 2.
425426
*
426-
* Contract @tag directives are NOt applied when generating non-native scalar
427+
* Contract @tag directives are NOT applied when generating non-native scalar
427428
* return types in the Froid subgraph. Contract @tag directives are merged
428429
* during supergraph composition so Froid subgraph can rely on @tag directives
429430
* defined by the owning subgraph(s), UNLESS an enum value is marked @inaccessible,
@@ -450,8 +451,12 @@ export class FroidSchema {
450451
});
451452
});
452453

453-
// De-dupe non-native scalar return types. Any definitions of scalars and enums
454-
// will work since they can be guaranteed to be consistent across subgraphs
454+
// De-dupe non-native scalar return types.
455+
//
456+
// Any definitions of scalars and enums will work since they are
457+
// consistent across subgraphs.
458+
//
459+
// Enums must be combined across all subgraphs since they can deviate.
455460
const nonNativeScalarFieldTypes = new Map<
456461
string,
457462
SupportedFroidReturnTypes
@@ -462,30 +467,60 @@ export class FroidSchema {
462467
) as SupportedFroidReturnTypes[]
463468
).forEach((nonNativeScalarType) => {
464469
const returnTypeName = nonNativeScalarType.name.value;
465-
if (
466-
!nonNativeScalarDefinitionNames.has(returnTypeName) ||
467-
nonNativeScalarFieldTypes.has(returnTypeName)
468-
) {
470+
if (!nonNativeScalarDefinitionNames.has(returnTypeName)) {
469471
// Don't get types that are not returned in froid schema
470472
return;
471473
}
472474

473475
if (nonNativeScalarType.kind === Kind.ENUM_TYPE_DEFINITION) {
476+
let finalEnumValues: readonly EnumValueDefinitionNode[] = [];
477+
478+
// Collect the enum values from the enum that is currently being inspected,
479+
// omitting all applied directives.
474480
const enumValues = nonNativeScalarType.values?.map((enumValue) => ({
475481
...enumValue,
476-
directives: enumValue.directives?.filter(
477-
(directive) => directive.name.value === 'inaccessible'
478-
),
482+
directives: [],
479483
}));
484+
485+
// Get the enum definition we've created so far if one exists
486+
const existingEnum = nonNativeScalarFieldTypes.get(
487+
returnTypeName
488+
) as EnumTypeDefinitionNode;
489+
490+
// If there are existing enum values, use them for the final enum
491+
if (existingEnum?.values) {
492+
finalEnumValues = existingEnum.values;
493+
}
494+
495+
// If there are enum values associated with the enum definition
496+
// we're currently inspecting, include them in the final list of
497+
// enum values
498+
if (enumValues) {
499+
// Deduplicate enum values
500+
const dedupedEnumValues = enumValues.filter(
501+
(value) =>
502+
!finalEnumValues.some(
503+
(existingValue) => existingValue.name.value === value.name.value
504+
)
505+
);
506+
// Update the final enum value list
507+
finalEnumValues = [...finalEnumValues, ...dedupedEnumValues];
508+
}
509+
480510
nonNativeScalarFieldTypes.set(returnTypeName, {
481511
...nonNativeScalarType,
482-
values: enumValues,
512+
values: finalEnumValues.length ? finalEnumValues : undefined,
483513
directives: [],
484514
description: undefined,
485515
} as EnumTypeDefinitionNode);
486516
return;
487517
}
488518

519+
if (nonNativeScalarFieldTypes.has(returnTypeName)) {
520+
// Don't duplicate scalars
521+
return;
522+
}
523+
489524
nonNativeScalarFieldTypes.set(returnTypeName, {
490525
...nonNativeScalarType,
491526
description: undefined,

src/schema/__tests__/FroidSchema.test.ts

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1739,7 +1739,7 @@ describe('FroidSchema class', () => {
17391739
enum UsedEnum {
17401740
VALUE_ONE
17411741
VALUE_THREE
1742-
VALUE_TWO @inaccessible
1742+
VALUE_TWO
17431743
}
17441744
17451745
type User implements Node @key(fields: "customEnum1 customEnum2 customField1 customField2 userId") {
@@ -1755,6 +1755,79 @@ describe('FroidSchema class', () => {
17551755
);
17561756
});
17571757

1758+
it('includes a combined enum definition when enum values differ across subgraphs', () => {
1759+
const userSchema = gql`
1760+
enum UsedEnum {
1761+
VALUE_ONE
1762+
VALUE_TWO
1763+
}
1764+
`;
1765+
const todoSchema = gql`
1766+
enum UsedEnum {
1767+
VALUE_THREE
1768+
VALUE_FOUR
1769+
}
1770+
1771+
type User @key(fields: "userId enumField") {
1772+
userId: String!
1773+
enumField: UsedEnum!
1774+
}
1775+
`;
1776+
const bookSchema = gql`
1777+
enum UsedEnum {
1778+
VALUE_THREE
1779+
VALUE_FIVE
1780+
}
1781+
`;
1782+
const subgraphs = new Map();
1783+
subgraphs.set('user-subgraph', userSchema);
1784+
subgraphs.set('todo-subgraph', todoSchema);
1785+
subgraphs.set('book-subgraph', bookSchema);
1786+
1787+
const actual = generateSchema({
1788+
subgraphs,
1789+
froidSubgraphName: 'relay-subgraph',
1790+
contractTags: ['storefront', 'internal'],
1791+
federationVersion: FED2_DEFAULT_VERSION,
1792+
});
1793+
1794+
expect(actual).toEqual(
1795+
// prettier-ignore
1796+
gql`
1797+
extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key", "@tag", "@external"])
1798+
1799+
"The global identification interface implemented by all entities."
1800+
interface Node @tag(name: "internal") @tag(name: "storefront") {
1801+
"The globally unique identifier."
1802+
id: ID!
1803+
}
1804+
1805+
type Query {
1806+
"Fetches an entity by its globally unique identifier."
1807+
node(
1808+
"A globally unique entity identifier."
1809+
id: ID!
1810+
): Node @tag(name: "internal") @tag(name: "storefront")
1811+
}
1812+
1813+
enum UsedEnum {
1814+
VALUE_FIVE
1815+
VALUE_FOUR
1816+
VALUE_ONE
1817+
VALUE_THREE
1818+
VALUE_TWO
1819+
}
1820+
1821+
type User implements Node @key(fields: "enumField userId") {
1822+
"The globally unique identifier."
1823+
id: ID!
1824+
enumField: UsedEnum!
1825+
userId: String!
1826+
}
1827+
`
1828+
);
1829+
});
1830+
17581831
it('ignores keys that use the `id` field', () => {
17591832
const productSchema = gql`
17601833
type Query {

0 commit comments

Comments
 (0)