Skip to content

Commit d8aeb2a

Browse files
committed
Align function signature/return type for compose and composeAndValidate
1 parent 282cd8b commit d8aeb2a

11 files changed

+231
-171
lines changed

federation-js/CHANGELOG.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
> The changes noted within this `vNEXT` section have not been released yet. New PRs and commits which introduce changes should include an entry in this `vNEXT` section as part of their development. When a release is being prepared, a new header will be (manually) created below and the appropriate changes within that release will be moved into the new section.
66
77
- Export `GraphQLSchemaModule` type. [PR #293](https://github.com/apollographql/federation/pull/293)
8-
- __BREAKING__: Remove `ComposedGraphQLSchema` type as it's no longer needed. This is breaking because it was part of the public API, though we strongly believe nobody was or should have had any need for this type. [PR #278](https://github.com/apollographql/federation/pull/278)
8+
- __BREAKING__: Remove `ComposedGraphQLSchema` type as it's no longer needed. This is breaking because it was part of the public API, though we strongly believe nobody was or should have had any need for this type. Update `composeAndValidate` function signature for better typing, and align the `compose` function signature with that of `composeAndValidate` [PR #278](https://github.com/apollographql/federation/pull/278)
99
## v0.20.7
1010

1111
- Fix check for value types when having fields and arguments with the same name [PR #280](https://github.com/apollographql/federation/pull/280)

federation-js/src/composition/__tests__/compose.test.ts

+105-71
Large diffs are not rendered by default.

federation-js/src/composition/__tests__/composeAndValidate.test.ts

+2-30
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,8 @@ import {
1212
typeSerializer,
1313
graphqlErrorSerializer,
1414
} from '../../snapshotSerializers';
15-
import {
16-
compositionHasErrors,
17-
CompositionResult,
18-
CompositionSuccess,
19-
CompositionFailure,
20-
} from '@apollo/federation';
15+
import { assertCompositionFailure, assertCompositionSuccess } from '../utils';
16+
import { compositionHasErrors } from '../compose';
2117

2218
expect.addSnapshotSerializer(astSerializer);
2319
expect.addSnapshotSerializer(typeSerializer);
@@ -105,30 +101,6 @@ function permutateList<T>(inputArr: T[]) {
105101
return result;
106102
}
107103

108-
// This assertion function should be used for the sake of convenient type refinement.
109-
// It should not be depended on for causing a test to fail. If an error is thrown
110-
// from here, its use should be reconsidered.
111-
export function assertCompositionSuccess(
112-
compositionResult: CompositionResult,
113-
message?: string,
114-
): asserts compositionResult is CompositionSuccess {
115-
if (compositionHasErrors(compositionResult)) {
116-
throw new Error(message || 'Unexpected test failure');
117-
}
118-
}
119-
120-
// This assertion function should be used for the sake of convenient type refinement.
121-
// It should not be depended on for causing a test to fail. If an error is thrown
122-
// from here, its use should be reconsidered.
123-
export function assertCompositionFailure(
124-
compositionResult: CompositionResult,
125-
message?: string,
126-
): asserts compositionResult is CompositionFailure {
127-
if (!compositionHasErrors(compositionResult)) {
128-
throw new Error(message || 'Unexpected test failure');
129-
}
130-
}
131-
132104
it('composes and validates all (24) permutations without error', () => {
133105
permutateList([
134106
inventoryService,

federation-js/src/composition/compose.ts

+32-7
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ import {
4545
} from './types';
4646
import { validateSDL } from 'graphql/validation/validate';
4747
import { compositionRules } from './rules';
48+
import { printComposedSdl } from '../service/printComposedSdl';
4849

4950
const EmptyQueryDefinition = {
5051
kind: Kind.OBJECT_TYPE_DEFINITION,
@@ -120,6 +121,28 @@ export interface KeyDirectivesMap {
120121
*/
121122
type ValueTypes = Set<string>;
122123

124+
export type CompositionResult = CompositionFailure | CompositionSuccess;
125+
126+
// Yes, it's a bit awkward that we still return a schema when errors occur.
127+
// This is old behavior that I'm choosing not to modify for now.
128+
export interface CompositionFailure {
129+
/** @deprecated Use composedSdl instead */
130+
schema: GraphQLSchema;
131+
errors: GraphQLError[];
132+
}
133+
134+
export interface CompositionSuccess {
135+
/** @deprecated Use composedSdl instead */
136+
schema: GraphQLSchema;
137+
composedSdl: string;
138+
}
139+
140+
export function compositionHasErrors(
141+
compositionResult: CompositionResult,
142+
): compositionResult is CompositionFailure {
143+
return 'errors' in compositionResult;
144+
}
145+
123146
/**
124147
* Loop over each service and process its typeDefs (`definitions`)
125148
* - build up typeToServiceMap
@@ -589,7 +612,7 @@ export function addFederationMetadataToSchemaNodes({
589612
}
590613
}
591614

592-
export function composeServices(services: ServiceDefinition[]) {
615+
export function composeServices(services: ServiceDefinition[]): CompositionResult {
593616
const {
594617
typeToServiceMap,
595618
typeDefinitionsMap,
@@ -644,10 +667,12 @@ export function composeServices(services: ServiceDefinition[]) {
644667
directiveDefinitionsMap,
645668
});
646669

647-
/**
648-
* At the end, we're left with a full GraphQLSchema that _also_ has `serviceName` fields for every type,
649-
* and every field that was extended. Fields that were _not_ extended (added on the base type by the owner),
650-
* there is no `serviceName`, and we should refer to the type's `serviceName`
651-
*/
652-
return { schema, errors };
670+
if (errors.length > 0) {
671+
return { schema, errors };
672+
} else {
673+
return {
674+
schema,
675+
composedSdl: printComposedSdl(schema, services),
676+
};
677+
}
653678
}
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,15 @@
1-
import { composeServices } from './compose';
1+
import {
2+
composeServices,
3+
compositionHasErrors,
4+
CompositionResult,
5+
} from './compose';
26
import {
37
validateComposedSchema,
48
validateServicesBeforeComposition,
59
validateServicesBeforeNormalization,
610
} from './validate';
711
import { ServiceDefinition } from './types';
812
import { normalizeTypeDefs } from './normalize';
9-
import { printComposedSdl } from '../service/printComposedSdl';
10-
import { GraphQLError, GraphQLSchema } from 'graphql';
11-
12-
export type CompositionResult = CompositionFailure | CompositionSuccess;
13-
14-
// Yes, it's a bit awkward that we still return a schema when errors occur.
15-
// This is old behavior that I'm choosing not to modify for now.
16-
export interface CompositionFailure {
17-
/** @deprecated Use composedSdl instead */
18-
schema: GraphQLSchema;
19-
errors: GraphQLError[];
20-
}
21-
22-
export interface CompositionSuccess {
23-
/** @deprecated Use composedSdl instead */
24-
schema: GraphQLSchema;
25-
composedSdl: string;
26-
}
27-
28-
export function compositionHasErrors(
29-
compositionResult: CompositionResult,
30-
): compositionResult is CompositionFailure {
31-
return 'errors' in compositionResult;
32-
}
3313

3414
export function composeAndValidate(
3515
serviceList: ServiceDefinition[],
@@ -46,7 +26,10 @@ export function composeAndValidate(
4626

4727
// generate a schema and any errors or warnings
4828
const compositionResult = composeServices(normalizedServiceList);
49-
errors.push(...compositionResult.errors);
29+
30+
if (compositionHasErrors(compositionResult)) {
31+
errors.push(...compositionResult.errors);
32+
}
5033

5134
// validate the composed schema based on service information
5235
errors.push(
@@ -61,10 +44,7 @@ export function composeAndValidate(
6144
schema: compositionResult.schema,
6245
errors,
6346
};
47+
} else {
48+
return compositionResult;
6449
}
65-
66-
return {
67-
schema: compositionResult.schema,
68-
composedSdl: printComposedSdl(compositionResult.schema, serviceList),
69-
};
7050
}

federation-js/src/composition/utils.ts

+30
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,12 @@ import {
4343
FederationField,
4444
} from './types';
4545
import federationDirectives from '../directives';
46+
import {
47+
CompositionFailure,
48+
compositionHasErrors,
49+
CompositionResult,
50+
CompositionSuccess,
51+
} from './compose';
4652

4753
export function isStringValueNode(node: any): node is StringValueNode {
4854
return node.kind === Kind.STRING;
@@ -614,3 +620,27 @@ export function getFederationMetadata(obj: any) {
614620
else if (isDirective(obj)) return obj.extensions?.federation as FederationDirective | undefined;
615621
else return obj.extensions?.federation as FederationField | undefined;
616622
}
623+
624+
// This assertion function should be used for the sake of convenient type refinement.
625+
// It should not be depended on for causing a test to fail. If an error is thrown
626+
// from here, its use should be reconsidered.
627+
export function assertCompositionSuccess(
628+
compositionResult: CompositionResult,
629+
message?: string,
630+
): asserts compositionResult is CompositionSuccess {
631+
if (compositionHasErrors(compositionResult)) {
632+
throw new Error(message || 'Unexpected test failure');
633+
}
634+
}
635+
636+
// This assertion function should be used for the sake of convenient type refinement.
637+
// It should not be depended on for causing a test to fail. If an error is thrown
638+
// from here, its use should be reconsidered.
639+
export function assertCompositionFailure(
640+
compositionResult: CompositionResult,
641+
message?: string,
642+
): asserts compositionResult is CompositionFailure {
643+
if (!compositionHasErrors(compositionResult)) {
644+
throw new Error(message || 'Unexpected test failure');
645+
}
646+
}

federation-js/src/composition/validate/postComposition/__tests__/keyFieldsMissingOnBase.test.ts

+10-7
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import gql from 'graphql-tag';
22
import { composeServices } from '../../../compose';
33
import { keyFieldsMissingOnBase as validateKeyFieldsMissingOnBase } from '../';
44
import { graphqlErrorSerializer } from '../../../../snapshotSerializers';
5+
import { assertCompositionSuccess } from '../../../utils';
56

67
expect.addSnapshotSerializer(graphqlErrorSerializer);
78

@@ -36,9 +37,9 @@ describe('keyFieldsMissingOnBase', () => {
3637
};
3738

3839
const serviceList = [serviceA, serviceB];
39-
const { schema, errors } = composeServices(serviceList);
40-
expect(errors).toHaveLength(0);
41-
40+
const compositionResult = composeServices(serviceList);
41+
assertCompositionSuccess(compositionResult);
42+
const { schema } = compositionResult;
4243
const warnings = validateKeyFieldsMissingOnBase({ schema, serviceList });
4344
expect(warnings).toHaveLength(0);
4445
});
@@ -66,8 +67,9 @@ describe('keyFieldsMissingOnBase', () => {
6667
};
6768

6869
const serviceList = [serviceA, serviceB];
69-
const { schema, errors } = composeServices(serviceList);
70-
expect(errors).toHaveLength(0);
70+
const compositionResult = composeServices(serviceList);
71+
assertCompositionSuccess(compositionResult);
72+
const { schema } = compositionResult;
7173

7274
const warnings = validateKeyFieldsMissingOnBase({ schema, serviceList });
7375
expect(warnings).toMatchInlineSnapshot(`
@@ -104,8 +106,9 @@ describe('keyFieldsMissingOnBase', () => {
104106
};
105107

106108
const serviceList = [serviceA, serviceB];
107-
const { schema, errors } = composeServices(serviceList);
108-
expect(errors).toHaveLength(0);
109+
const compositionResult = composeServices(serviceList);
110+
assertCompositionSuccess(compositionResult);
111+
const { schema } = compositionResult;
109112

110113
const warnings = validateKeyFieldsMissingOnBase({ schema, serviceList });
111114
expect(warnings).toMatchInlineSnapshot();

federation-js/src/composition/validate/postComposition/__tests__/keyFieldsSelectInvalidType.test.ts

+10-6
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import gql from 'graphql-tag';
22
import { composeServices } from '../../../compose';
33
import { keyFieldsSelectInvalidType as validateKeyFieldsSelectInvalidType } from '../';
44
import { graphqlErrorSerializer } from '../../../../snapshotSerializers';
5+
import { assertCompositionSuccess } from '../../../utils';
56

67
expect.addSnapshotSerializer(graphqlErrorSerializer);
78

@@ -36,8 +37,9 @@ describe('keyFieldsSelectInvalidType', () => {
3637
};
3738

3839
const serviceList = [serviceA, serviceB];
39-
const { schema, errors } = composeServices(serviceList);
40-
expect(errors).toHaveLength(0);
40+
const compositionResult = composeServices(serviceList);
41+
assertCompositionSuccess(compositionResult);
42+
const { schema } = compositionResult;
4143

4244
const warnings = validateKeyFieldsSelectInvalidType({
4345
schema,
@@ -72,8 +74,9 @@ describe('keyFieldsSelectInvalidType', () => {
7274
};
7375

7476
const serviceList = [serviceA, serviceB];
75-
const { schema, errors } = composeServices(serviceList);
76-
expect(errors).toHaveLength(0);
77+
const compositionResult = composeServices(serviceList);
78+
assertCompositionSuccess(compositionResult);
79+
const { schema } = compositionResult;
7780

7881
const warnings = validateKeyFieldsSelectInvalidType({
7982
schema,
@@ -113,8 +116,9 @@ describe('keyFieldsSelectInvalidType', () => {
113116
};
114117

115118
const serviceList = [serviceA, serviceB];
116-
const { schema, errors } = composeServices(serviceList);
117-
expect(errors).toHaveLength(0);
119+
const compositionResult = composeServices(serviceList);
120+
assertCompositionSuccess(compositionResult);
121+
const { schema } = compositionResult;
118122

119123
const warnings = validateKeyFieldsSelectInvalidType({
120124
schema,

federation-js/src/composition/validate/postComposition/__tests__/keysMatchBaseService.test.ts

+10-6
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import gql from 'graphql-tag';
22
import { composeServices } from '../../../compose';
33
import { keysMatchBaseService as validateKeysMatchBaseService } from '../';
44
import { graphqlErrorSerializer } from '../../../../snapshotSerializers';
5+
import { assertCompositionSuccess } from '../../../utils';
56

67
expect.addSnapshotSerializer(graphqlErrorSerializer);
78

@@ -28,8 +29,9 @@ describe('keysMatchBaseService', () => {
2829
};
2930

3031
const serviceList = [serviceA, serviceB];
31-
const { schema, errors } = composeServices(serviceList);
32-
expect(errors).toHaveLength(0);
32+
const compositionResult = composeServices(serviceList);
33+
assertCompositionSuccess(compositionResult);
34+
const { schema } = compositionResult;
3335

3436
const validationErrors = validateKeysMatchBaseService({
3537
schema,
@@ -60,8 +62,9 @@ describe('keysMatchBaseService', () => {
6062
};
6163

6264
const serviceList = [serviceA, serviceB];
63-
const { schema, errors } = composeServices(serviceList);
64-
expect(errors).toHaveLength(0);
65+
const compositionResult = composeServices(serviceList);
66+
assertCompositionSuccess(compositionResult);
67+
const { schema } = compositionResult;
6568

6669
const validationErrors = validateKeysMatchBaseService({
6770
schema,
@@ -98,8 +101,9 @@ describe('keysMatchBaseService', () => {
98101
};
99102

100103
const serviceList = [serviceA, serviceB];
101-
const { schema, errors } = composeServices(serviceList);
102-
expect(errors).toHaveLength(0);
104+
const compositionResult = composeServices(serviceList);
105+
assertCompositionSuccess(compositionResult);
106+
const { schema } = compositionResult;
103107

104108
const validationErrors = validateKeysMatchBaseService({
105109
schema,

federation-js/src/composition/validate/postComposition/__tests__/providesFieldsMissingExternals.test.ts

+7-4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import gql from 'graphql-tag';
22
import { composeServices } from '../../../compose';
33
import { providesFieldsMissingExternal as validateProdivesFieldsMissingExternal } from '../';
44
import { graphqlErrorSerializer } from '../../../../snapshotSerializers';
5+
import { assertCompositionSuccess } from '../../../utils';
56

67
expect.addSnapshotSerializer(graphqlErrorSerializer);
78

@@ -51,8 +52,9 @@ describe('providesFieldsMissingExternal', () => {
5152
};
5253

5354
const serviceList = [serviceA, serviceB, serviceC];
54-
const { schema, errors } = composeServices(serviceList);
55-
expect(errors).toEqual([]);
55+
const compositionResult = composeServices(serviceList);
56+
assertCompositionSuccess(compositionResult);
57+
const { schema } = compositionResult;
5658
const warnings = validateProdivesFieldsMissingExternal({
5759
schema,
5860
serviceList,
@@ -88,8 +90,9 @@ describe('providesFieldsMissingExternal', () => {
8890
};
8991

9092
const serviceList = [serviceA, serviceB];
91-
const { schema, errors } = composeServices(serviceList);
92-
expect(errors).toEqual([]);
93+
const compositionResult = composeServices(serviceList);
94+
assertCompositionSuccess(compositionResult);
95+
const { schema } = compositionResult;
9396
const warnings = validateProdivesFieldsMissingExternal({
9497
schema,
9598
serviceList,

0 commit comments

Comments
 (0)