@theguild/federation-composition 0.20.2 → 0.21.0
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/cjs/subgraph/state.js +21 -32
- package/cjs/subgraph/validation/rules/elements/authenticated.js +23 -2
- package/cjs/subgraph/validation/rules/elements/cost.js +12 -2
- package/cjs/subgraph/validation/rules/elements/list-size.js +10 -8
- package/cjs/subgraph/validation/rules/elements/policy.js +25 -5
- package/cjs/subgraph/validation/rules/elements/requires-scopes.js +23 -2
- package/cjs/supergraph/composition/ast.js +5 -14
- package/cjs/supergraph/composition/enum-type.js +3 -2
- package/cjs/supergraph/composition/interface-type.js +32 -11
- package/cjs/supergraph/composition/object-type.js +46 -7
- package/cjs/supergraph/composition/scalar-type.js +3 -2
- package/cjs/supergraph/state.js +43 -6
- package/cjs/supergraph/validation/rules/auth-on-requires-rule.js +211 -0
- package/cjs/supergraph/validation/rules/external-missing-on-base-rule.js +26 -1
- package/cjs/supergraph/validation/rules/list-size-slicing-arguments-rule.js +69 -0
- package/cjs/supergraph/validation/validate-supergraph.js +5 -0
- package/cjs/utils/auth.js +34 -0
- package/esm/subgraph/state.js +21 -32
- package/esm/subgraph/validation/rules/elements/authenticated.js +24 -3
- package/esm/subgraph/validation/rules/elements/cost.js +12 -2
- package/esm/subgraph/validation/rules/elements/list-size.js +10 -8
- package/esm/subgraph/validation/rules/elements/policy.js +26 -6
- package/esm/subgraph/validation/rules/elements/requires-scopes.js +24 -3
- package/esm/supergraph/composition/ast.js +5 -14
- package/esm/supergraph/composition/enum-type.js +3 -2
- package/esm/supergraph/composition/interface-type.js +33 -12
- package/esm/supergraph/composition/object-type.js +46 -7
- package/esm/supergraph/composition/scalar-type.js +3 -2
- package/esm/supergraph/state.js +43 -6
- package/esm/supergraph/validation/rules/auth-on-requires-rule.js +208 -0
- package/esm/supergraph/validation/rules/external-missing-on-base-rule.js +26 -1
- package/esm/supergraph/validation/rules/list-size-slicing-arguments-rule.js +66 -0
- package/esm/supergraph/validation/validate-supergraph.js +5 -0
- package/esm/utils/auth.js +31 -0
- package/package.json +1 -1
- package/typings/subgraph/state.d.cts +1 -6
- package/typings/subgraph/state.d.ts +1 -6
- package/typings/subgraph/validation/validation-context.d.cts +0 -3
- package/typings/subgraph/validation/validation-context.d.ts +0 -3
- package/typings/supergraph/composition/ast.d.cts +1 -0
- package/typings/supergraph/composition/ast.d.ts +1 -0
- package/typings/supergraph/composition/common.d.cts +4 -0
- package/typings/supergraph/composition/common.d.ts +4 -0
- package/typings/supergraph/composition/interface-type.d.cts +3 -0
- package/typings/supergraph/composition/interface-type.d.ts +3 -0
- package/typings/supergraph/composition/object-type.d.cts +6 -0
- package/typings/supergraph/composition/object-type.d.ts +6 -0
- package/typings/supergraph/state.d.cts +1 -0
- package/typings/supergraph/state.d.ts +1 -0
- package/typings/supergraph/validation/rules/auth-on-requires-rule.d.cts +5 -0
- package/typings/supergraph/validation/rules/auth-on-requires-rule.d.ts +5 -0
- package/typings/supergraph/validation/rules/external-missing-on-base-rule.d.cts +2 -1
- package/typings/supergraph/validation/rules/external-missing-on-base-rule.d.ts +2 -1
- package/typings/supergraph/validation/rules/list-size-slicing-arguments-rule.d.cts +5 -0
- package/typings/supergraph/validation/rules/list-size-slicing-arguments-rule.d.ts +5 -0
- package/typings/utils/auth.d.cts +2 -0
- package/typings/utils/auth.d.ts +2 -0
package/cjs/supergraph/state.js
CHANGED
|
@@ -49,6 +49,12 @@ function createSupergraphStateBuilder() {
|
|
|
49
49
|
const unionType = (0, union_type_js_1.unionTypeBuilder)();
|
|
50
50
|
const subgraphStates = new Map();
|
|
51
51
|
const graphNameToIdMap = {};
|
|
52
|
+
const helpers = {
|
|
53
|
+
graphNameToId(graphName) {
|
|
54
|
+
return graphNameToIdMap[graphName] ?? null;
|
|
55
|
+
},
|
|
56
|
+
supergraphState: state,
|
|
57
|
+
};
|
|
52
58
|
return {
|
|
53
59
|
addSubgraph(subgraph) {
|
|
54
60
|
if (state.subgraphs.has(subgraph.graph.id)) {
|
|
@@ -60,6 +66,43 @@ function createSupergraphStateBuilder() {
|
|
|
60
66
|
getGraph(id) {
|
|
61
67
|
return state.subgraphs.get(id);
|
|
62
68
|
},
|
|
69
|
+
composeSupergraphState() {
|
|
70
|
+
if (directive.composeSupergraphState) {
|
|
71
|
+
state.directives.forEach((s) => {
|
|
72
|
+
directive.composeSupergraphState(s, state.subgraphs, helpers);
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
if (scalarType.composeSupergraphState) {
|
|
76
|
+
state.scalarTypes.forEach((s) => {
|
|
77
|
+
scalarType.composeSupergraphState(s, state.subgraphs, helpers);
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
if (enumType.composeSupergraphState) {
|
|
81
|
+
state.enumTypes.forEach((s) => {
|
|
82
|
+
enumType.composeSupergraphState(s, state.subgraphs, helpers);
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
if (inputObjectType.composeSupergraphState) {
|
|
86
|
+
state.inputObjectTypes.forEach((s) => {
|
|
87
|
+
inputObjectType.composeSupergraphState(s, state.subgraphs, helpers);
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
if (objectType.composeSupergraphState) {
|
|
91
|
+
state.objectTypes.forEach((s) => {
|
|
92
|
+
objectType.composeSupergraphState(s, state.subgraphs, helpers);
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
if (interfaceType.composeSupergraphState) {
|
|
96
|
+
state.interfaceTypes.forEach((s) => {
|
|
97
|
+
interfaceType.composeSupergraphState(s, state.subgraphs, helpers);
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
if (unionType.composeSupergraphState) {
|
|
101
|
+
state.unionTypes.forEach((s) => {
|
|
102
|
+
unionType.composeSupergraphState(s, state.subgraphs, helpers);
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
},
|
|
63
106
|
visitSubgraphState(subgraphState) {
|
|
64
107
|
subgraphStates.set(subgraphState.graph.id, subgraphState);
|
|
65
108
|
for (const link of subgraphState.links) {
|
|
@@ -134,12 +177,6 @@ function createSupergraphStateBuilder() {
|
|
|
134
177
|
},
|
|
135
178
|
build() {
|
|
136
179
|
const transformFields = createFieldsTransformer(state);
|
|
137
|
-
const helpers = {
|
|
138
|
-
graphNameToId(graphName) {
|
|
139
|
-
return graphNameToIdMap[graphName] ?? null;
|
|
140
|
-
},
|
|
141
|
-
supergraphState: state,
|
|
142
|
-
};
|
|
143
180
|
for (const directiveState of state.directives.values()) {
|
|
144
181
|
if (!directiveState.isExecutable || !directiveState.byGraph.size) {
|
|
145
182
|
continue;
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AuthOnRequiresRule = AuthOnRequiresRule;
|
|
4
|
+
const graphql_1 = require("graphql");
|
|
5
|
+
const helpers_js_1 = require("../../../subgraph/helpers.js");
|
|
6
|
+
const auth_js_1 = require("../../../utils/auth.js");
|
|
7
|
+
function AuthOnRequiresRule(context, supergraph) {
|
|
8
|
+
return {
|
|
9
|
+
ObjectTypeField(objectTypeState, fieldState) {
|
|
10
|
+
for (const [graphId, fieldInGraph] of fieldState.byGraph) {
|
|
11
|
+
if (!fieldInGraph.requires) {
|
|
12
|
+
continue;
|
|
13
|
+
}
|
|
14
|
+
const selectionSet = (0, helpers_js_1.parseFields)(fieldInGraph.requires);
|
|
15
|
+
if (!selectionSet) {
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
const provisionedAccess = new ProvisionedAccess(objectTypeState, fieldState);
|
|
19
|
+
let fieldLackingAccess = ensureAccessToSelectionSet(supergraph, objectTypeState, selectionSet, provisionedAccess);
|
|
20
|
+
if (fieldLackingAccess) {
|
|
21
|
+
context.reportError(createAccessRequirementError(context.graphIdToName(graphId), `${objectTypeState.name}.${fieldState.name}`, fieldLackingAccess));
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function ensureAccessToSelectionSet(supergraph, currentType, selectionSet, provisionedAccess) {
|
|
29
|
+
for (const selection of selectionSet.selections) {
|
|
30
|
+
switch (selection.kind) {
|
|
31
|
+
case graphql_1.Kind.FIELD: {
|
|
32
|
+
if (currentType.kind === "union") {
|
|
33
|
+
throw new Error("Cannot select fields directly on union types.");
|
|
34
|
+
}
|
|
35
|
+
const fieldLackingAccess = ensureAccessToField(supergraph, currentType, selection, provisionedAccess);
|
|
36
|
+
if (fieldLackingAccess) {
|
|
37
|
+
return fieldLackingAccess;
|
|
38
|
+
}
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
case graphql_1.Kind.INLINE_FRAGMENT: {
|
|
42
|
+
const fieldLackingAccess = ensureAccessToInlineFragment(supergraph, currentType, selection, provisionedAccess);
|
|
43
|
+
if (fieldLackingAccess) {
|
|
44
|
+
return fieldLackingAccess;
|
|
45
|
+
}
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
case graphql_1.Kind.FRAGMENT_SPREAD: {
|
|
49
|
+
throw new Error("Fragment spreads are not supported in @requires.");
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function ensureAccessToField(supergraph, currentType, fieldNode, provisionedAccess) {
|
|
55
|
+
if (fieldNode.name.value === "__typename") {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
let fieldName = fieldNode.name.value;
|
|
59
|
+
let fieldState = currentType.fields.get(fieldNode.name.value);
|
|
60
|
+
let fieldDetails = null;
|
|
61
|
+
if (fieldState) {
|
|
62
|
+
fieldDetails = {
|
|
63
|
+
type: fieldState.type,
|
|
64
|
+
scopes: fieldState.scopes,
|
|
65
|
+
policies: fieldState.policies,
|
|
66
|
+
authenticated: fieldState.authenticated,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
if (currentType.kind === "interface") {
|
|
71
|
+
throw new Error(`Field "${fieldNode.name.value}" not found on interface type "${currentType.name}".`);
|
|
72
|
+
}
|
|
73
|
+
for (const interfaceName of currentType.interfaces) {
|
|
74
|
+
const interfaceType = supergraph.interfaceTypes.get(interfaceName);
|
|
75
|
+
if (!interfaceType) {
|
|
76
|
+
throw new Error(`Interface "${interfaceName}" implemented by "${currentType.name}" not found in supergraph.`);
|
|
77
|
+
}
|
|
78
|
+
const interfaceFieldState = interfaceType.fields.get(fieldNode.name.value);
|
|
79
|
+
if (interfaceFieldState) {
|
|
80
|
+
if (!fieldDetails) {
|
|
81
|
+
fieldDetails = {
|
|
82
|
+
type: interfaceFieldState.type,
|
|
83
|
+
scopes: (0, auth_js_1.mergeScopePolicies)([], interfaceFieldState.scopes),
|
|
84
|
+
policies: (0, auth_js_1.mergeScopePolicies)([], interfaceFieldState.policies),
|
|
85
|
+
authenticated: interfaceFieldState.authenticated,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
fieldDetails = {
|
|
90
|
+
type: interfaceFieldState.type,
|
|
91
|
+
scopes: (0, auth_js_1.mergeScopePolicies)(fieldDetails.scopes, interfaceFieldState.scopes),
|
|
92
|
+
policies: (0, auth_js_1.mergeScopePolicies)(fieldDetails.policies, interfaceFieldState.policies),
|
|
93
|
+
authenticated: interfaceFieldState.authenticated
|
|
94
|
+
? true
|
|
95
|
+
: fieldDetails.authenticated,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (fieldDetails === null) {
|
|
102
|
+
throw new Error(`Field "${fieldName}" not found on type "${currentType.name}".`);
|
|
103
|
+
}
|
|
104
|
+
if (!provisionedAccess.canAccess(fieldDetails)) {
|
|
105
|
+
return `${currentType.name}.${fieldName}`;
|
|
106
|
+
}
|
|
107
|
+
const outputTypeName = extractNamedTypeName(fieldDetails.type);
|
|
108
|
+
if (!outputTypeName) {
|
|
109
|
+
throw new Error(`Unable to extract output type name from field "${currentType.name}.${fieldName}" type "${fieldDetails.type}".`);
|
|
110
|
+
}
|
|
111
|
+
if (graphql_1.specifiedScalarTypes.some((s) => s.name === outputTypeName)) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
const outputType = supergraph.objectTypes.get(outputTypeName) ??
|
|
115
|
+
supergraph.interfaceTypes.get(outputTypeName) ??
|
|
116
|
+
supergraph.enumTypes.get(outputTypeName) ??
|
|
117
|
+
supergraph.scalarTypes.get(outputTypeName) ??
|
|
118
|
+
supergraph.unionTypes.get(outputTypeName);
|
|
119
|
+
if (!outputType) {
|
|
120
|
+
throw new Error(`Output type "${outputTypeName}" of field "${currentType.name}.${fieldName}" not found in supergraph.`);
|
|
121
|
+
}
|
|
122
|
+
if (outputType.kind !== "union" && !provisionedAccess.canAccess(outputType)) {
|
|
123
|
+
return `${currentType.name}.${fieldName}`;
|
|
124
|
+
}
|
|
125
|
+
if (!fieldNode.selectionSet) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
if (outputType.kind === "enum" || outputType.kind === "scalar") {
|
|
129
|
+
throw new Error(`Field "${currentType.name}.${fieldName}" of type "${outputType.name}" cannot have a selection set.`);
|
|
130
|
+
}
|
|
131
|
+
return ensureAccessToSelectionSet(supergraph, outputType, fieldNode.selectionSet, provisionedAccess);
|
|
132
|
+
}
|
|
133
|
+
function ensureAccessToInlineFragment(supergraph, currentType, inlineFragment, provisionedAccess) {
|
|
134
|
+
const concreteType = inlineFragment.typeCondition
|
|
135
|
+
? (supergraph.objectTypes.get(inlineFragment.typeCondition.name.value) ??
|
|
136
|
+
supergraph.interfaceTypes.get(inlineFragment.typeCondition.name.value))
|
|
137
|
+
: currentType;
|
|
138
|
+
if (!concreteType) {
|
|
139
|
+
throw new Error(`Type "${currentType.name}" not found in supergraph for inline fragment.`);
|
|
140
|
+
}
|
|
141
|
+
if (concreteType.kind == "union") {
|
|
142
|
+
throw new Error("Cannot have inline fragments on union types without type conditions.");
|
|
143
|
+
}
|
|
144
|
+
if (!provisionedAccess.canAccess(concreteType)) {
|
|
145
|
+
return concreteType.name;
|
|
146
|
+
}
|
|
147
|
+
return ensureAccessToSelectionSet(supergraph, concreteType, inlineFragment.selectionSet, provisionedAccess);
|
|
148
|
+
}
|
|
149
|
+
function createAccessRequirementError(graphName, fieldWithRequiresCoordinate, authCoordinate) {
|
|
150
|
+
const strDataRef = authCoordinate.includes(".")
|
|
151
|
+
? `field "${authCoordinate}"`
|
|
152
|
+
: `type "${authCoordinate}"`;
|
|
153
|
+
return new graphql_1.GraphQLError(`[${graphName}] Field "${fieldWithRequiresCoordinate}" does not specify necessary @authenticated, @requiresScopes and/or @policy auth requirements to access the transitive ${strDataRef} data from @requires selection set.`, {
|
|
154
|
+
extensions: {
|
|
155
|
+
code: "MISSING_TRANSITIVE_AUTH_REQUIREMENTS",
|
|
156
|
+
},
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
function extractNamedTypeName(typeStr) {
|
|
160
|
+
let typeName = typeStr;
|
|
161
|
+
if (!typeName)
|
|
162
|
+
return null;
|
|
163
|
+
typeName = typeName.replace(/[![\]]/g, "");
|
|
164
|
+
return typeName || null;
|
|
165
|
+
}
|
|
166
|
+
class ProvisionedAccess {
|
|
167
|
+
scopes;
|
|
168
|
+
policies;
|
|
169
|
+
authenticated;
|
|
170
|
+
constructor(objectTypeState, fieldState) {
|
|
171
|
+
this.scopes = [];
|
|
172
|
+
this.policies = [];
|
|
173
|
+
this.authenticated = false;
|
|
174
|
+
if (objectTypeState.authenticated) {
|
|
175
|
+
this.authenticated = true;
|
|
176
|
+
}
|
|
177
|
+
if (objectTypeState.scopes.length > 0) {
|
|
178
|
+
this.scopes = objectTypeState.scopes.slice();
|
|
179
|
+
}
|
|
180
|
+
if (objectTypeState.policies.length > 0) {
|
|
181
|
+
this.policies = objectTypeState.policies.slice();
|
|
182
|
+
}
|
|
183
|
+
if (fieldState.authenticated) {
|
|
184
|
+
this.authenticated = true;
|
|
185
|
+
}
|
|
186
|
+
if (fieldState.scopes.length > 0) {
|
|
187
|
+
this.scopes = (0, auth_js_1.mergeScopePolicies)(this.scopes, fieldState.scopes);
|
|
188
|
+
}
|
|
189
|
+
if (fieldState.policies.length > 0) {
|
|
190
|
+
this.policies = (0, auth_js_1.mergeScopePolicies)(this.policies, fieldState.policies);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
canAccess(required) {
|
|
194
|
+
if (required.authenticated && !this.authenticated) {
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
for (const requiredScopeGroup of required.scopes) {
|
|
198
|
+
const satisfiedByAny = this.scopes.some((providedGroup) => requiredScopeGroup.every((scope) => providedGroup.includes(scope)));
|
|
199
|
+
if (!satisfiedByAny) {
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
for (const requiredPolicyGroup of required.policies) {
|
|
204
|
+
const satisfiedByAny = this.policies.some((providedGroup) => requiredPolicyGroup.every((policy) => providedGroup.includes(policy)));
|
|
205
|
+
if (!satisfiedByAny) {
|
|
206
|
+
return false;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return true;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.ExternalMissingOnBaseRule = ExternalMissingOnBaseRule;
|
|
4
4
|
const graphql_1 = require("graphql");
|
|
5
5
|
const format_js_1 = require("../../../utils/format.js");
|
|
6
|
-
function ExternalMissingOnBaseRule(context) {
|
|
6
|
+
function ExternalMissingOnBaseRule(context, supergraph) {
|
|
7
7
|
return {
|
|
8
8
|
ObjectType(objectTypeState) {
|
|
9
9
|
if (Array.from(objectTypeState.byGraph).every(([_, stateInGraph]) => stateInGraph.external === true)) {
|
|
@@ -32,6 +32,31 @@ function ExternalMissingOnBaseRule(context) {
|
|
|
32
32
|
}
|
|
33
33
|
return fieldStateInGraph.external === true;
|
|
34
34
|
})) {
|
|
35
|
+
for (let interfaceName of objectState.interfaces) {
|
|
36
|
+
let interfaceState = supergraph.interfaceTypes.get(interfaceName);
|
|
37
|
+
if (!interfaceState) {
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
if (!interfaceState.hasInterfaceObject) {
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
for (let [graphId, interfaceStateInGraph] of interfaceState.byGraph) {
|
|
44
|
+
if (!interfaceStateInGraph.isInterfaceObject) {
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
let interfaceFieldState = interfaceState.fields.get(fieldState.name);
|
|
48
|
+
if (!interfaceFieldState) {
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
let interfaceFieldInGraph = interfaceFieldState.byGraph.get(graphId);
|
|
52
|
+
if (!interfaceFieldInGraph) {
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (interfaceFieldInGraph.external !== true) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
35
60
|
const subgraphs = fieldState.byGraph.size > 1 ? "subgraphs" : "subgraph";
|
|
36
61
|
context.reportError(new graphql_1.GraphQLError(`Field "${objectState.name}.${fieldState.name}" is marked @external on all the subgraphs in which it is listed (${subgraphs} ${(0, format_js_1.andList)(Array.from(fieldState.byGraph.keys()).map(context.graphIdToName), true, '"')}).`, {
|
|
37
62
|
extensions: {
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ListSizeSlicingArgumentsRule = ListSizeSlicingArgumentsRule;
|
|
4
|
+
const graphql_1 = require("graphql");
|
|
5
|
+
function ListSizeSlicingArgumentsRule(context, supergraph) {
|
|
6
|
+
return {
|
|
7
|
+
ObjectTypeField(objectState, fieldState) {
|
|
8
|
+
let error = ensureSlicingArgumentsExist(objectState.name, fieldState);
|
|
9
|
+
if (error) {
|
|
10
|
+
context.reportError(error);
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
InterfaceTypeField(interfaceState, fieldState) {
|
|
14
|
+
let error = ensureSlicingArgumentsExist(interfaceState.name, fieldState);
|
|
15
|
+
if (error) {
|
|
16
|
+
context.reportError(error);
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
if (!fieldState?.listSize ||
|
|
20
|
+
!fieldState?.listSize.slicingArguments?.length) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
for (const implementorName of interfaceState.implementedBy) {
|
|
24
|
+
const implementorType = supergraph.objectTypes.get(implementorName);
|
|
25
|
+
if (!implementorType) {
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
let implementorField = implementorType.fields.get(fieldState.name);
|
|
29
|
+
if (!implementorField) {
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
if (!implementorField.listSize) {
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
if (!implementorField.listSize.slicingArguments?.length) {
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
fieldState.listSize.printRequireOneSlicingArgument = true;
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
function ensureSlicingArgumentsExist(typeName, fieldState) {
|
|
44
|
+
if (!fieldState.listSize?.slicingArguments?.length) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const newslicingArguments = [];
|
|
48
|
+
for (const argName of fieldState.listSize.slicingArguments) {
|
|
49
|
+
const argState = fieldState.args.get(argName);
|
|
50
|
+
if (!argState) {
|
|
51
|
+
throw new Error("Could not find the argument in the field annotated with @listSize");
|
|
52
|
+
}
|
|
53
|
+
if (argState.byGraph.size !== fieldState.byGraph.size) {
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
newslicingArguments.push(argName);
|
|
57
|
+
}
|
|
58
|
+
if (!newslicingArguments.length) {
|
|
59
|
+
return new graphql_1.GraphQLError([
|
|
60
|
+
`All arguments for @listSize(slicingArguments:) on field "${typeName}.${fieldState.name}" were disregarded.`,
|
|
61
|
+
`For an argument to be valid, it must be defined in every subgraph where the field exists.`,
|
|
62
|
+
].join(" "), {
|
|
63
|
+
extensions: {
|
|
64
|
+
code: "LIST_SIZE_INVALID_SLICING_ARGUMENT",
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
fieldState.listSize.slicingArguments = newslicingArguments;
|
|
69
|
+
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.validateSupergraph = validateSupergraph;
|
|
4
4
|
const visitor_js_1 = require("../composition/visitor.js");
|
|
5
|
+
const auth_on_requires_rule_js_1 = require("./rules/auth-on-requires-rule.js");
|
|
5
6
|
const default_value_uses_inaccessible_rule_js_1 = require("./rules/default-value-uses-inaccessible-rule.js");
|
|
6
7
|
const directive_composition_rule_js_1 = require("./rules/directive-composition-rule.js");
|
|
7
8
|
const enum_values_rule_js_1 = require("./rules/enum-values-rule.js");
|
|
@@ -20,6 +21,7 @@ const interface_object_usage_error_js_1 = require("./rules/interface-object-usag
|
|
|
20
21
|
const interface_subtype_rule_js_1 = require("./rules/interface-subtype-rule.js");
|
|
21
22
|
const invalid_field_sharing_rule_js_1 = require("./rules/invalid-field-sharing-rule.js");
|
|
22
23
|
const link_import_name_mismatch_rule_js_1 = require("./rules/link-import-name-mismatch-rule.js");
|
|
24
|
+
const list_size_slicing_arguments_rule_js_1 = require("./rules/list-size-slicing-arguments-rule.js");
|
|
23
25
|
const no_inaccessible_on_implemented_interface_fields_rule_js_1 = require("./rules/no-inaccessible-on-implemented-interface-fields-rule.js");
|
|
24
26
|
const only_inaccessible_children_rule_js_1 = require("./rules/only-inaccessible-children-rule.js");
|
|
25
27
|
const override_source_has_override_js_1 = require("./rules/override-source-has-override.js");
|
|
@@ -52,6 +54,7 @@ function validateSupergraph(subgraphStates, state, __internal) {
|
|
|
52
54
|
for (const subgraphState of subgraphStates.values()) {
|
|
53
55
|
state.visitSubgraphState(subgraphState);
|
|
54
56
|
}
|
|
57
|
+
state.composeSupergraphState();
|
|
55
58
|
const postSupergraphRules = [
|
|
56
59
|
interface_field_no_implementation_rule_js_1.InterfaceFieldNoImplementationRule,
|
|
57
60
|
extension_with_base_js_1.ExtensionWithBaseRule,
|
|
@@ -79,6 +82,8 @@ function validateSupergraph(subgraphStates, state, __internal) {
|
|
|
79
82
|
required_argument_or_field_is_not_inaccessible_rule_js_1.RequiredArgumentOrFieldIsNotInaccessibleRule,
|
|
80
83
|
interface_subtype_rule_js_1.InterfaceSubtypeRule,
|
|
81
84
|
no_inaccessible_on_implemented_interface_fields_rule_js_1.NoInaccessibleOnImplementedInterfaceFieldsRule,
|
|
85
|
+
list_size_slicing_arguments_rule_js_1.ListSizeSlicingArgumentsRule,
|
|
86
|
+
auth_on_requires_rule_js_1.AuthOnRequiresRule,
|
|
82
87
|
];
|
|
83
88
|
const supergraph = state.getSupergraphState();
|
|
84
89
|
(0, visitor_js_1.visitSupergraphState)(supergraph, postSupergraphRules.map((rule) => {
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.mergeScopePolicies = mergeScopePolicies;
|
|
4
|
+
function mergeScopePolicies(policyA, policyB) {
|
|
5
|
+
if (policyA.length === 0 || policyB.length === 0) {
|
|
6
|
+
return policyA.length
|
|
7
|
+
? policyA.map((group) => [...group])
|
|
8
|
+
: policyB.map((group) => [...group]);
|
|
9
|
+
}
|
|
10
|
+
const groupMap = new Map();
|
|
11
|
+
for (const groupA of policyA) {
|
|
12
|
+
for (const groupB of policyB) {
|
|
13
|
+
const merged = Array.from(new Set([...groupA, ...groupB])).sort();
|
|
14
|
+
const key = merged.join(",");
|
|
15
|
+
groupMap.set(key, merged);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
const candidates = Array.from(groupMap.values()).sort((a, b) => a.length - b.length);
|
|
19
|
+
const finalGroups = [];
|
|
20
|
+
for (const candidate of candidates) {
|
|
21
|
+
if (!finalGroups.some((existing) => isSubset(existing, candidate))) {
|
|
22
|
+
finalGroups.push(candidate);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
finalGroups.sort((a, b) => a.length - b.length || a.join(",").localeCompare(b.join(",")));
|
|
26
|
+
return finalGroups;
|
|
27
|
+
}
|
|
28
|
+
function isSubset(subset, superset) {
|
|
29
|
+
if (subset.length > superset.length) {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
const supersetSet = new Set(superset);
|
|
33
|
+
return subset.every((item) => supersetSet.has(item));
|
|
34
|
+
}
|
package/esm/subgraph/state.js
CHANGED
|
@@ -2,6 +2,7 @@ import { Kind, OperationTypeNode, specifiedScalarTypes, DirectiveLocation, } fro
|
|
|
2
2
|
import { print } from "../graphql/printer.js";
|
|
3
3
|
import { isFederationLink, } from "../specifications/federation.js";
|
|
4
4
|
import { printOutputType } from "./helpers.js";
|
|
5
|
+
import { mergeScopePolicies } from "../utils/auth.js";
|
|
5
6
|
export var TypeKind;
|
|
6
7
|
(function (TypeKind) {
|
|
7
8
|
TypeKind["OBJECT"] = "OBJECT";
|
|
@@ -630,10 +631,12 @@ function scalarTypeFactory(state) {
|
|
|
630
631
|
getOrCreateScalarType(state, typeName).authenticated = true;
|
|
631
632
|
},
|
|
632
633
|
setPolicies(typeName, policies) {
|
|
633
|
-
getOrCreateScalarType(state, typeName)
|
|
634
|
+
const existing = getOrCreateScalarType(state, typeName);
|
|
635
|
+
existing.policies = mergeScopePolicies(existing.policies, policies);
|
|
634
636
|
},
|
|
635
637
|
setScopes(typeName, scopes) {
|
|
636
|
-
getOrCreateScalarType(state, typeName)
|
|
638
|
+
const existing = getOrCreateScalarType(state, typeName);
|
|
639
|
+
existing.scopes = mergeScopePolicies(existing.scopes, scopes);
|
|
637
640
|
},
|
|
638
641
|
setCost(typeName, cost) {
|
|
639
642
|
getOrCreateScalarType(state, typeName).cost = cost;
|
|
@@ -712,23 +715,16 @@ function objectTypeFactory(state, renameObject, interfaceTypeBuilder, isInterfac
|
|
|
712
715
|
objectType.inaccessible = true;
|
|
713
716
|
},
|
|
714
717
|
setAuthenticated(typeName) {
|
|
715
|
-
if (isInterfaceObject(typeName)) {
|
|
716
|
-
return interfaceTypeBuilder.setAuthenticated(typeName);
|
|
717
|
-
}
|
|
718
718
|
const objectType = getOrCreateObjectType(state, renameObject, typeName);
|
|
719
719
|
objectType.authenticated = true;
|
|
720
720
|
},
|
|
721
721
|
setPolicies(typeName, policies) {
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
}
|
|
725
|
-
getOrCreateObjectType(state, renameObject, typeName).policies.push(...policies);
|
|
722
|
+
const existing = getOrCreateObjectType(state, renameObject, typeName);
|
|
723
|
+
existing.policies = mergeScopePolicies(existing.policies, policies);
|
|
726
724
|
},
|
|
727
725
|
setScopes(typeName, scopes) {
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
}
|
|
731
|
-
getOrCreateObjectType(state, renameObject, typeName).scopes.push(...scopes);
|
|
726
|
+
const existing = getOrCreateObjectType(state, renameObject, typeName);
|
|
727
|
+
existing.scopes = mergeScopePolicies(existing.scopes, scopes);
|
|
732
728
|
},
|
|
733
729
|
setCost(typeName, cost) {
|
|
734
730
|
if (isInterfaceObject(typeName)) {
|
|
@@ -805,13 +801,15 @@ function objectTypeFactory(state, renameObject, interfaceTypeBuilder, isInterfac
|
|
|
805
801
|
if (isInterfaceObject(typeName)) {
|
|
806
802
|
return interfaceTypeBuilder.field.setPolicies(typeName, fieldName, policies);
|
|
807
803
|
}
|
|
808
|
-
getOrCreateObjectField(state, renameObject, typeName, fieldName)
|
|
804
|
+
let existing = getOrCreateObjectField(state, renameObject, typeName, fieldName);
|
|
805
|
+
existing.policies = mergeScopePolicies(existing.policies, policies);
|
|
809
806
|
},
|
|
810
807
|
setScopes(typeName, fieldName, scopes) {
|
|
811
808
|
if (isInterfaceObject(typeName)) {
|
|
812
809
|
return interfaceTypeBuilder.field.setScopes(typeName, fieldName, scopes);
|
|
813
810
|
}
|
|
814
|
-
getOrCreateObjectField(state, renameObject, typeName, fieldName)
|
|
811
|
+
let existing = getOrCreateObjectField(state, renameObject, typeName, fieldName);
|
|
812
|
+
existing.scopes = mergeScopePolicies(existing.scopes, scopes);
|
|
815
813
|
},
|
|
816
814
|
setCost(typeName, fieldName, cost) {
|
|
817
815
|
if (isInterfaceObject(typeName)) {
|
|
@@ -976,16 +974,6 @@ function interfaceTypeFactory(state) {
|
|
|
976
974
|
const objectType = getOrCreateInterfaceType(state, typeName);
|
|
977
975
|
objectType.inaccessible = true;
|
|
978
976
|
},
|
|
979
|
-
setAuthenticated(typeName) {
|
|
980
|
-
const t = getOrCreateInterfaceType(state, typeName);
|
|
981
|
-
t.authenticated = true;
|
|
982
|
-
},
|
|
983
|
-
setPolicies(typeName, policies) {
|
|
984
|
-
getOrCreateInterfaceType(state, typeName).policies.push(...policies);
|
|
985
|
-
},
|
|
986
|
-
setScopes(typeName, scopes) {
|
|
987
|
-
getOrCreateInterfaceType(state, typeName).scopes.push(...scopes);
|
|
988
|
-
},
|
|
989
977
|
setTag(typeName, tag) {
|
|
990
978
|
getOrCreateInterfaceType(state, typeName).tags.add(tag);
|
|
991
979
|
},
|
|
@@ -1014,10 +1002,12 @@ function interfaceTypeFactory(state) {
|
|
|
1014
1002
|
true;
|
|
1015
1003
|
},
|
|
1016
1004
|
setPolicies(typeName, fieldName, policies) {
|
|
1017
|
-
getOrCreateInterfaceField(state, typeName, fieldName)
|
|
1005
|
+
const existing = getOrCreateInterfaceField(state, typeName, fieldName);
|
|
1006
|
+
existing.policies = mergeScopePolicies(existing.policies, policies);
|
|
1018
1007
|
},
|
|
1019
1008
|
setScopes(typeName, fieldName, scopes) {
|
|
1020
|
-
getOrCreateInterfaceField(state, typeName, fieldName)
|
|
1009
|
+
const existing = getOrCreateInterfaceField(state, typeName, fieldName);
|
|
1010
|
+
existing.scopes = mergeScopePolicies(existing.scopes, scopes);
|
|
1021
1011
|
},
|
|
1022
1012
|
setCost(typeName, fieldName, cost) {
|
|
1023
1013
|
getOrCreateInterfaceField(state, typeName, fieldName).cost = cost;
|
|
@@ -1189,10 +1179,12 @@ function enumTypeFactory(state) {
|
|
|
1189
1179
|
getOrCreateEnumType(state, typeName).authenticated = true;
|
|
1190
1180
|
},
|
|
1191
1181
|
setPolicies(typeName, policies) {
|
|
1192
|
-
getOrCreateEnumType(state, typeName)
|
|
1182
|
+
const existing = getOrCreateEnumType(state, typeName);
|
|
1183
|
+
existing.policies = mergeScopePolicies(existing.policies, policies);
|
|
1193
1184
|
},
|
|
1194
1185
|
setScopes(typeName, scopes) {
|
|
1195
|
-
getOrCreateEnumType(state, typeName)
|
|
1186
|
+
const existing = getOrCreateEnumType(state, typeName);
|
|
1187
|
+
existing.scopes = mergeScopePolicies(existing.scopes, scopes);
|
|
1196
1188
|
},
|
|
1197
1189
|
setCost(typeName, cost) {
|
|
1198
1190
|
getOrCreateEnumType(state, typeName).cost = cost;
|
|
@@ -1368,9 +1360,6 @@ function getOrCreateInterfaceType(state, typeName) {
|
|
|
1368
1360
|
extension: false,
|
|
1369
1361
|
keys: [],
|
|
1370
1362
|
inaccessible: false,
|
|
1371
|
-
authenticated: false,
|
|
1372
|
-
policies: [],
|
|
1373
|
-
scopes: [],
|
|
1374
1363
|
tags: new Set(),
|
|
1375
1364
|
interfaces: new Set(),
|
|
1376
1365
|
implementedBy: new Set(),
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Kind } from "graphql";
|
|
1
|
+
import { GraphQLError, Kind } from "graphql";
|
|
2
2
|
import { validateDirectiveAgainstOriginal } from "../../../helpers.js";
|
|
3
3
|
export function AuthenticatedRule(context) {
|
|
4
4
|
return {
|
|
@@ -30,6 +30,15 @@ export function AuthenticatedRule(context) {
|
|
|
30
30
|
if (!typeDef) {
|
|
31
31
|
throw new Error("Could not find the parent type of the field annotated with @authenticated");
|
|
32
32
|
}
|
|
33
|
+
if (typeDef.kind === Kind.INTERFACE_TYPE_DEFINITION ||
|
|
34
|
+
typeDef.kind === Kind.INTERFACE_TYPE_EXTENSION) {
|
|
35
|
+
context.reportError(new GraphQLError(`Invalid use of @authenticated on field "${typeDef.name.value}.${parent.name.value}": @authenticated cannot be applied on interfaces, interface fields and interface objects`, {
|
|
36
|
+
extensions: {
|
|
37
|
+
code: "AUTH_REQUIREMENTS_APPLIED_ON_INTERFACE",
|
|
38
|
+
},
|
|
39
|
+
}));
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
33
42
|
if (typeDef.kind === Kind.OBJECT_TYPE_DEFINITION ||
|
|
34
43
|
typeDef.kind === Kind.OBJECT_TYPE_EXTENSION) {
|
|
35
44
|
context.stateBuilder.objectType.field.setAuthenticated(typeDef.name.value, parent.name.value);
|
|
@@ -38,12 +47,24 @@ export function AuthenticatedRule(context) {
|
|
|
38
47
|
}
|
|
39
48
|
case Kind.OBJECT_TYPE_DEFINITION:
|
|
40
49
|
case Kind.OBJECT_TYPE_EXTENSION:
|
|
50
|
+
if (context.stateBuilder.isInterfaceObject(parent.name.value)) {
|
|
51
|
+
context.reportError(new GraphQLError(`Invalid use of @authenticated on interface object "${parent.name.value}": @authenticated cannot be applied on interfaces, interface fields and interface objects`, {
|
|
52
|
+
extensions: {
|
|
53
|
+
code: "AUTH_REQUIREMENTS_APPLIED_ON_INTERFACE",
|
|
54
|
+
},
|
|
55
|
+
}));
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
41
58
|
context.stateBuilder.objectType.setAuthenticated(parent.name.value);
|
|
42
59
|
break;
|
|
43
60
|
case Kind.INTERFACE_TYPE_DEFINITION:
|
|
44
61
|
case Kind.INTERFACE_TYPE_DEFINITION:
|
|
45
|
-
context.
|
|
46
|
-
|
|
62
|
+
context.reportError(new GraphQLError(`Invalid use of @authenticated on interface "${parent.name.value}": @authenticated cannot be applied on interfaces, interface fields and interface objects`, {
|
|
63
|
+
extensions: {
|
|
64
|
+
code: "AUTH_REQUIREMENTS_APPLIED_ON_INTERFACE",
|
|
65
|
+
},
|
|
66
|
+
}));
|
|
67
|
+
return;
|
|
47
68
|
case Kind.SCALAR_TYPE_DEFINITION:
|
|
48
69
|
case Kind.SCALAR_TYPE_EXTENSION:
|
|
49
70
|
context.stateBuilder.scalarType.setAuthenticated(parent.name.value);
|
|
@@ -55,7 +55,12 @@ export function CostRule(context) {
|
|
|
55
55
|
}
|
|
56
56
|
if (typeDef.kind === Kind.INTERFACE_TYPE_DEFINITION ||
|
|
57
57
|
typeDef.kind === Kind.INTERFACE_TYPE_EXTENSION) {
|
|
58
|
-
context.
|
|
58
|
+
context.reportError(new GraphQLError(`@cost cannot be applied to interface "${typeDef.name.value}.${parent.name.value}"`, {
|
|
59
|
+
extensions: {
|
|
60
|
+
code: "COST_APPLIED_TO_INTERFACE_FIELD",
|
|
61
|
+
},
|
|
62
|
+
}));
|
|
63
|
+
return;
|
|
59
64
|
}
|
|
60
65
|
break;
|
|
61
66
|
}
|
|
@@ -86,7 +91,12 @@ export function CostRule(context) {
|
|
|
86
91
|
if (!argDef) {
|
|
87
92
|
throw new Error("Could not find the argument annotated with @cost");
|
|
88
93
|
}
|
|
89
|
-
context.
|
|
94
|
+
context.reportError(new GraphQLError(`@cost cannot be applied to interface "${typeDef.name.value}.${fieldDef.name.value}" argument "${argDef.name.value}"`, {
|
|
95
|
+
extensions: {
|
|
96
|
+
code: "COST_APPLIED_TO_INTERFACE_FIELD",
|
|
97
|
+
},
|
|
98
|
+
}));
|
|
99
|
+
return;
|
|
90
100
|
}
|
|
91
101
|
break;
|
|
92
102
|
case Kind.OBJECT_TYPE_DEFINITION:
|