@apollo/federation-internals 2.11.2 → 2.11.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/argumentCompositionStrategies.d.ts +6 -0
- package/dist/argumentCompositionStrategies.d.ts.map +1 -1
- package/dist/argumentCompositionStrategies.js +77 -0
- package/dist/argumentCompositionStrategies.js.map +1 -1
- package/dist/buildSchema.d.ts.map +1 -1
- package/dist/buildSchema.js +42 -2
- package/dist/buildSchema.js.map +1 -1
- package/dist/directiveAndTypeSpecification.d.ts +8 -3
- package/dist/directiveAndTypeSpecification.d.ts.map +1 -1
- package/dist/directiveAndTypeSpecification.js +2 -1
- package/dist/directiveAndTypeSpecification.js.map +1 -1
- package/dist/error.d.ts +2 -0
- package/dist/error.d.ts.map +1 -1
- package/dist/error.js +4 -0
- package/dist/error.js.map +1 -1
- package/dist/federation.d.ts.map +1 -1
- package/dist/federation.js +37 -8
- package/dist/federation.js.map +1 -1
- package/dist/specs/authenticatedSpec.d.ts +2 -0
- package/dist/specs/authenticatedSpec.d.ts.map +1 -1
- package/dist/specs/authenticatedSpec.js +3 -0
- package/dist/specs/authenticatedSpec.js.map +1 -1
- package/dist/specs/connectSpec.d.ts +0 -3
- package/dist/specs/connectSpec.d.ts.map +1 -1
- package/dist/specs/connectSpec.js +228 -60
- package/dist/specs/connectSpec.js.map +1 -1
- package/dist/specs/policySpec.d.ts +4 -0
- package/dist/specs/policySpec.d.ts.map +1 -1
- package/dist/specs/policySpec.js +4 -1
- package/dist/specs/policySpec.js.map +1 -1
- package/dist/specs/requiresScopesSpec.d.ts +4 -0
- package/dist/specs/requiresScopesSpec.d.ts.map +1 -1
- package/dist/specs/requiresScopesSpec.js +4 -1
- package/dist/specs/requiresScopesSpec.js.map +1 -1
- package/package.json +1 -1
- package/src/argumentCompositionStrategies.ts +114 -2
- package/src/buildSchema.ts +51 -0
- package/src/directiveAndTypeSpecification.ts +8 -2
- package/src/error.ts +14 -0
- package/src/federation.ts +45 -8
- package/src/specs/authenticatedSpec.ts +5 -0
- package/src/specs/connectSpec.ts +364 -122
- package/src/specs/policySpec.ts +6 -2
- package/src/specs/requiresScopesSpec.ts +6 -2
package/src/buildSchema.ts
CHANGED
|
@@ -56,6 +56,9 @@ import {
|
|
|
56
56
|
} from "./definitions";
|
|
57
57
|
import { ERRORS, errorCauses, withModifiedErrorNodes } from "./error";
|
|
58
58
|
import { introspectionTypeNames } from "./introspection";
|
|
59
|
+
import { coreFeatureDefinitionIfKnown } from "./knownCoreFeatures";
|
|
60
|
+
import { connectIdentity } from "./specs/connectSpec";
|
|
61
|
+
|
|
59
62
|
|
|
60
63
|
function buildValue(value?: ValueNode): any {
|
|
61
64
|
return value ? valueFromASTUntyped(value) : undefined;
|
|
@@ -143,6 +146,48 @@ export function buildSchemaFromAST(
|
|
|
143
146
|
buildSchemaDefinitionInner(schemaExtension, schema.schemaDefinition, errors, schema.schemaDefinition.newExtension());
|
|
144
147
|
}
|
|
145
148
|
|
|
149
|
+
// The following block of code is a one-off to support input objects in the
|
|
150
|
+
// connect spec. It will be non-maintainable/bug-prone to do this again, and
|
|
151
|
+
// has various limitations/unsupported edge cases already.
|
|
152
|
+
//
|
|
153
|
+
// There's work to be done to support input objects more generally; please see
|
|
154
|
+
// https://github.com/apollographql/federation/pull/3311 for more information.
|
|
155
|
+
const connectFeature = schema.coreFeatures?.getByIdentity(connectIdentity);
|
|
156
|
+
const handledConnectTypeNames = new Set<string>();
|
|
157
|
+
if (connectFeature) {
|
|
158
|
+
const connectFeatureDefinition =
|
|
159
|
+
coreFeatureDefinitionIfKnown(connectFeature.url);
|
|
160
|
+
if (connectFeatureDefinition) {
|
|
161
|
+
const connectTypeNamesInSchema = new Set(
|
|
162
|
+
connectFeatureDefinition.typeSpecs()
|
|
163
|
+
.map(({ name }) => connectFeature.typeNameInSchema(name))
|
|
164
|
+
);
|
|
165
|
+
for (const typeNode of typeDefinitions) {
|
|
166
|
+
if (connectTypeNamesInSchema.has(typeNode.name.value)
|
|
167
|
+
&& typeNode.kind === 'InputObjectTypeDefinition'
|
|
168
|
+
) {
|
|
169
|
+
handledConnectTypeNames.add(typeNode.name.value)
|
|
170
|
+
} else {
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
buildNamedTypeInner(typeNode, schema.type(typeNode.name.value)!, schema.blueprint, errors);
|
|
174
|
+
}
|
|
175
|
+
for (const typeExtensionNode of typeExtensions) {
|
|
176
|
+
if (connectTypeNamesInSchema.has(typeExtensionNode.name.value)
|
|
177
|
+
&& typeExtensionNode.kind === 'InputObjectTypeExtension'
|
|
178
|
+
) {
|
|
179
|
+
handledConnectTypeNames.add(typeExtensionNode.name.value)
|
|
180
|
+
} else {
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
const toExtend = schema.type(typeExtensionNode.name.value)!;
|
|
184
|
+
const extension = toExtend.newExtension();
|
|
185
|
+
extension.sourceAST = typeExtensionNode;
|
|
186
|
+
buildNamedTypeInner(typeExtensionNode, toExtend, schema.blueprint, errors, extension);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
146
191
|
// The following is a no-op for "standard" schema, but for federation subgraphs, this is where we handle the auto-addition
|
|
147
192
|
// of imported federation directive definitions. That is why we have avoid looking at directive applications within
|
|
148
193
|
// directive definition earlier: if one of those application was of an imported federation directive, the definition
|
|
@@ -155,9 +200,15 @@ export function buildSchemaFromAST(
|
|
|
155
200
|
}
|
|
156
201
|
|
|
157
202
|
for (const typeNode of typeDefinitions) {
|
|
203
|
+
if (handledConnectTypeNames.has(typeNode.name.value)) {
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
158
206
|
buildNamedTypeInner(typeNode, schema.type(typeNode.name.value)!, schema.blueprint, errors);
|
|
159
207
|
}
|
|
160
208
|
for (const typeExtensionNode of typeExtensions) {
|
|
209
|
+
if (handledConnectTypeNames.has(typeExtensionNode.name.value)) {
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
161
212
|
const toExtend = schema.type(typeExtensionNode.name.value)!;
|
|
162
213
|
const extension = toExtend.newExtension();
|
|
163
214
|
extension.sourceAST = typeExtensionNode;
|
|
@@ -66,7 +66,13 @@ export type FieldSpecification = {
|
|
|
66
66
|
args?: ResolvedArgumentSpecification[],
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
type ResolvedArgumentSpecification = {
|
|
69
|
+
export type ResolvedArgumentSpecification = {
|
|
70
|
+
name: string,
|
|
71
|
+
type: InputType,
|
|
72
|
+
defaultValue?: any,
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export type InputFieldSpecification = {
|
|
70
76
|
name: string,
|
|
71
77
|
type: InputType,
|
|
72
78
|
defaultValue?: any,
|
|
@@ -338,7 +344,7 @@ export function createEnumTypeSpecification({
|
|
|
338
344
|
}
|
|
339
345
|
}
|
|
340
346
|
|
|
341
|
-
function ensureSameTypeKind(expected: NamedType['kind'], actual: NamedType): GraphQLError[] {
|
|
347
|
+
export function ensureSameTypeKind(expected: NamedType['kind'], actual: NamedType): GraphQLError[] {
|
|
342
348
|
return expected === actual.kind
|
|
343
349
|
? []
|
|
344
350
|
: [
|
package/src/error.ts
CHANGED
|
@@ -633,6 +633,18 @@ const MAX_VALIDATION_SUBGRAPH_PATHS_EXCEEDED = makeCodeDefinition(
|
|
|
633
633
|
{ addedIn: '2.8.0' },
|
|
634
634
|
);
|
|
635
635
|
|
|
636
|
+
const AUTHENTICATION_APPLIED_ON_INTERFACE = makeCodeDefinition(
|
|
637
|
+
'AUTHENTICATION_APPLIED_ON_INTERFACE',
|
|
638
|
+
'The @authenticated, @requiresScopes and @policy directive cannot be applied on interface, interface object or their fields.',
|
|
639
|
+
{ addedIn: '2.9.4' },
|
|
640
|
+
);
|
|
641
|
+
|
|
642
|
+
const MISSING_TRANSITIVE_AUTH_REQUIREMENTS = makeCodeDefinition(
|
|
643
|
+
'MISSING_TRANSITIVE_AUTH_REQUIREMENTS',
|
|
644
|
+
'Field missing transitive @authenticated, @requiresScopes and/or @policy auth requirements needed to access dependent data.',
|
|
645
|
+
{ addedIn: '2.9.4' },
|
|
646
|
+
)
|
|
647
|
+
|
|
636
648
|
export const ERROR_CATEGORIES = {
|
|
637
649
|
DIRECTIVE_FIELDS_MISSING_EXTERNAL,
|
|
638
650
|
DIRECTIVE_UNSUPPORTED_ON_INTERFACE,
|
|
@@ -734,6 +746,8 @@ export const ERRORS = {
|
|
|
734
746
|
LIST_SIZE_INVALID_SIZED_FIELD,
|
|
735
747
|
LIST_SIZE_INVALID_SLICING_ARGUMENT,
|
|
736
748
|
MAX_VALIDATION_SUBGRAPH_PATHS_EXCEEDED,
|
|
749
|
+
AUTHENTICATION_APPLIED_ON_INTERFACE,
|
|
750
|
+
MISSING_TRANSITIVE_AUTH_REQUIREMENTS,
|
|
737
751
|
};
|
|
738
752
|
|
|
739
753
|
const codeDefByCode = Object.values(ERRORS).reduce((obj: {[code: string]: ErrorCodeDefinition}, codeDef: ErrorCodeDefinition) => { obj[codeDef.code] = codeDef; return obj; }, {});
|
package/src/federation.ts
CHANGED
|
@@ -37,7 +37,7 @@ import {
|
|
|
37
37
|
isWrapperType,
|
|
38
38
|
possibleRuntimeTypes,
|
|
39
39
|
isIntType,
|
|
40
|
-
Type,
|
|
40
|
+
Type, isFieldDefinition,
|
|
41
41
|
} from "./definitions";
|
|
42
42
|
import { assert, MultiMap, printHumanReadableList, OrderedMap, mapValues, assertUnreachable } from "./utils";
|
|
43
43
|
import { SDLValidationRule } from "graphql/validation/ValidationContext";
|
|
@@ -97,7 +97,7 @@ import { createObjectTypeSpecification, createScalarTypeSpecification, createUni
|
|
|
97
97
|
import { didYouMean, suggestionList } from "./suggestions";
|
|
98
98
|
import { coreFeatureDefinitionIfKnown } from "./knownCoreFeatures";
|
|
99
99
|
import { joinIdentity } from "./specs/joinSpec";
|
|
100
|
-
import {
|
|
100
|
+
import { CostDirectiveArguments, ListSizeDirectiveArguments } from "./specs/costSpec";
|
|
101
101
|
|
|
102
102
|
const linkSpec = LINK_VERSIONS.latest();
|
|
103
103
|
const tagSpec = TAG_VERSIONS.latest();
|
|
@@ -1820,18 +1820,16 @@ export class FederationBlueprint extends SchemaBlueprint {
|
|
|
1820
1820
|
}
|
|
1821
1821
|
}
|
|
1822
1822
|
|
|
1823
|
-
const
|
|
1824
|
-
const
|
|
1825
|
-
const costDirective = costSpec?.costDirective(schema);
|
|
1826
|
-
const listSizeDirective = costSpec?.listSizeDirective(schema);
|
|
1823
|
+
const costDirective = metadata.costDirective();
|
|
1824
|
+
const listSizeDirective = metadata.listSizeDirective();
|
|
1827
1825
|
|
|
1828
1826
|
// Validate @cost
|
|
1829
|
-
for (const application of costDirective
|
|
1827
|
+
for (const application of costDirective.applications()) {
|
|
1830
1828
|
validateCostNotAppliedToInterface(application, errorCollector);
|
|
1831
1829
|
}
|
|
1832
1830
|
|
|
1833
1831
|
// Validate @listSize
|
|
1834
|
-
for (const application of listSizeDirective
|
|
1832
|
+
for (const application of listSizeDirective.applications()) {
|
|
1835
1833
|
const parent = application.parent;
|
|
1836
1834
|
assert(parent instanceof FieldDefinition, "@listSize can only be applied to FIELD_DEFINITION");
|
|
1837
1835
|
validateListSizeAppliedToList(application, parent, errorCollector);
|
|
@@ -1840,6 +1838,9 @@ export class FederationBlueprint extends SchemaBlueprint {
|
|
|
1840
1838
|
validateSizedFieldsAreValidLists(application, parent, errorCollector);
|
|
1841
1839
|
}
|
|
1842
1840
|
|
|
1841
|
+
// Validate @authenticated, @requireScopes and @policy
|
|
1842
|
+
validateNoAuthenticationOnInterfaces(metadata, errorCollector);
|
|
1843
|
+
|
|
1843
1844
|
return errorCollector;
|
|
1844
1845
|
}
|
|
1845
1846
|
|
|
@@ -2891,3 +2892,39 @@ function withoutNonExternalLeafFields(selectionSet: SelectionSet): SelectionSet
|
|
|
2891
2892
|
return undefined;
|
|
2892
2893
|
});
|
|
2893
2894
|
}
|
|
2895
|
+
|
|
2896
|
+
function validateNoAuthenticationOnInterfaces(metadata: FederationMetadata, errorCollector: GraphQLError[]) {
|
|
2897
|
+
const authenticatedDirective = metadata.authenticatedDirective();
|
|
2898
|
+
const requiresScopesDirective = metadata.requiresScopesDirective();
|
|
2899
|
+
const policyDirective = metadata.policyDirective();
|
|
2900
|
+
[authenticatedDirective, requiresScopesDirective, policyDirective].forEach((directive) => {
|
|
2901
|
+
for (const application of directive.applications()) {
|
|
2902
|
+
const element = application.parent;
|
|
2903
|
+
function isAppliedOnInterface(type: Type) {
|
|
2904
|
+
return isInterfaceType(type) || isInterfaceObjectType(baseType(type));
|
|
2905
|
+
}
|
|
2906
|
+
function isAppliedOnInterfaceField(elem: SchemaElement<any, any>) {
|
|
2907
|
+
return isFieldDefinition(elem) && isAppliedOnInterface(elem.parent);
|
|
2908
|
+
}
|
|
2909
|
+
|
|
2910
|
+
if (isAppliedOnInterface(element) || isAppliedOnInterfaceField(element)) {
|
|
2911
|
+
let kind = '';
|
|
2912
|
+
switch (element.kind) {
|
|
2913
|
+
case 'FieldDefinition':
|
|
2914
|
+
kind = 'field';
|
|
2915
|
+
break;
|
|
2916
|
+
case 'InterfaceType':
|
|
2917
|
+
kind = 'interface';
|
|
2918
|
+
break;
|
|
2919
|
+
case 'ObjectType':
|
|
2920
|
+
kind = 'interface object';
|
|
2921
|
+
break;
|
|
2922
|
+
}
|
|
2923
|
+
errorCollector.push(ERRORS.AUTHENTICATION_APPLIED_ON_INTERFACE.err(
|
|
2924
|
+
`Invalid use of @${directive.name} on ${kind} "${element.coordinate}": @${directive.name} cannot be applied on interfaces, interface objects or their fields`,
|
|
2925
|
+
{nodes: sourceASTs(application, element.parent)},
|
|
2926
|
+
));
|
|
2927
|
+
}
|
|
2928
|
+
}
|
|
2929
|
+
});
|
|
2930
|
+
}
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
} from "./coreSpec";
|
|
9
9
|
import { createDirectiveSpecification } from "../directiveAndTypeSpecification";
|
|
10
10
|
import { registerKnownFeature } from "../knownCoreFeatures";
|
|
11
|
+
import {DirectiveDefinition, Schema} from "../definitions";
|
|
11
12
|
|
|
12
13
|
export class AuthenticatedSpecDefinition extends FeatureDefinition {
|
|
13
14
|
public static readonly directiveName = "authenticated";
|
|
@@ -37,6 +38,10 @@ export class AuthenticatedSpecDefinition extends FeatureDefinition {
|
|
|
37
38
|
}));
|
|
38
39
|
}
|
|
39
40
|
|
|
41
|
+
authenticatedDirective(schema: Schema): DirectiveDefinition | undefined {
|
|
42
|
+
return this.directive(schema, AuthenticatedSpecDefinition.directiveName);
|
|
43
|
+
}
|
|
44
|
+
|
|
40
45
|
get defaultCorePurpose(): CorePurpose {
|
|
41
46
|
return 'SECURITY';
|
|
42
47
|
}
|