@apollo/federation-internals 2.12.0-preview.3 → 2.12.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/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 +240 -64
- 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 +380 -126
- package/src/specs/policySpec.ts +6 -2
- package/src/specs/requiresScopesSpec.ts +6 -2
package/src/specs/connectSpec.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DirectiveLocation
|
|
1
|
+
import { DirectiveLocation } from 'graphql';
|
|
2
2
|
import {
|
|
3
3
|
CorePurpose,
|
|
4
4
|
FeatureDefinition,
|
|
@@ -7,23 +7,33 @@ import {
|
|
|
7
7
|
FeatureVersion,
|
|
8
8
|
} from './coreSpec';
|
|
9
9
|
import {
|
|
10
|
-
|
|
11
|
-
NonNullType,
|
|
10
|
+
CoreFeature,
|
|
12
11
|
InputObjectType,
|
|
13
|
-
|
|
12
|
+
isInputObjectType,
|
|
13
|
+
isNonNullType,
|
|
14
14
|
ListType,
|
|
15
|
+
NamedType,
|
|
16
|
+
NonNullType,
|
|
17
|
+
ScalarType,
|
|
18
|
+
Schema,
|
|
15
19
|
} from '../definitions';
|
|
16
20
|
import { registerKnownFeature } from '../knownCoreFeatures';
|
|
17
21
|
import {
|
|
18
22
|
createDirectiveSpecification,
|
|
19
23
|
createScalarTypeSpecification,
|
|
24
|
+
ensureSameTypeKind,
|
|
25
|
+
InputFieldSpecification,
|
|
26
|
+
TypeSpecification,
|
|
20
27
|
} from '../directiveAndTypeSpecification';
|
|
28
|
+
import { ERRORS } from '../error';
|
|
29
|
+
import { sameType } from '../types';
|
|
30
|
+
import { assert } from '../utils';
|
|
31
|
+
import { valueEquals, valueToString } from '../values';
|
|
21
32
|
|
|
22
33
|
export const connectIdentity = 'https://specs.apollo.dev/connect';
|
|
23
34
|
|
|
24
35
|
const CONNECT = 'connect';
|
|
25
36
|
const SOURCE = 'source';
|
|
26
|
-
const ID = 'id';
|
|
27
37
|
const URL_PATH_TEMPLATE = 'URLPathTemplate';
|
|
28
38
|
const JSON_SELECTION = 'JSONSelection';
|
|
29
39
|
const CONNECT_HTTP = 'ConnectHTTP';
|
|
@@ -42,62 +52,47 @@ export class ConnectSpecDefinition extends FeatureDefinition {
|
|
|
42
52
|
minimumFederationVersion,
|
|
43
53
|
);
|
|
44
54
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
// here implies.
|
|
53
|
-
composes: false,
|
|
54
|
-
}),
|
|
55
|
-
);
|
|
55
|
+
function lookupFeatureTypeInSchema<T extends NamedType>(name: string, kind: T['kind'], schema: Schema, feature?: CoreFeature): T {
|
|
56
|
+
assert(feature, `Shouldn't be added without being attached to a @connect spec`);
|
|
57
|
+
const typeName = feature.typeNameInSchema(name);
|
|
58
|
+
const type = schema.typeOfKind<T>(typeName, kind);
|
|
59
|
+
assert(type, () => `Expected "${typeName}" to be defined`);
|
|
60
|
+
return type;
|
|
61
|
+
}
|
|
56
62
|
|
|
57
|
-
this.registerDirective(
|
|
58
|
-
createDirectiveSpecification({
|
|
59
|
-
name: SOURCE,
|
|
60
|
-
locations: [DirectiveLocation.SCHEMA],
|
|
61
|
-
repeatable: true,
|
|
62
|
-
composes: false,
|
|
63
|
-
}),
|
|
64
|
-
);
|
|
65
63
|
|
|
64
|
+
/* scalar URLPathTemplate */
|
|
66
65
|
this.registerType(
|
|
67
|
-
|
|
66
|
+
createScalarTypeSpecification({ name: URL_PATH_TEMPLATE }),
|
|
68
67
|
);
|
|
69
|
-
this.registerType(createScalarTypeSpecification({ name: JSON_SELECTION }));
|
|
70
|
-
this.registerType({ name: CONNECT_HTTP, checkOrAdd: () => [] });
|
|
71
|
-
this.registerType({ name: SOURCE_HTTP, checkOrAdd: () => [] });
|
|
72
|
-
this.registerType({ name: HTTP_HEADER_MAPPING, checkOrAdd: () => [] });
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
addElementsToSchema(schema: Schema): GraphQLError[] {
|
|
76
|
-
/* scalar URLPathTemplate */
|
|
77
|
-
const URLPathTemplate = this.addScalarType(schema, URL_PATH_TEMPLATE);
|
|
78
|
-
|
|
79
68
|
/* scalar JSONSelection */
|
|
80
|
-
|
|
69
|
+
this.registerType(createScalarTypeSpecification({ name: JSON_SELECTION }));
|
|
81
70
|
|
|
82
71
|
/*
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
entity: Boolean = false
|
|
88
|
-
errors: ConnectorErrors
|
|
89
|
-
isSuccess: JSONSelection
|
|
90
|
-
) repeatable on FIELD_DEFINITION
|
|
91
|
-
| OBJECT # added in v0.2, validation enforced in rust
|
|
72
|
+
input ConnectorErrors {
|
|
73
|
+
message: JSONSelection
|
|
74
|
+
extensions: JSONSelection
|
|
75
|
+
}
|
|
92
76
|
*/
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
77
|
+
this.registerType(
|
|
78
|
+
createInputObjectTypeSpecification({
|
|
79
|
+
name: CONNECTOR_ERRORS,
|
|
80
|
+
inputFieldsFct: (schema, feature) => {
|
|
81
|
+
const jsonSelectionType =
|
|
82
|
+
lookupFeatureTypeInSchema<ScalarType>(JSON_SELECTION, 'ScalarType', schema, feature);
|
|
83
|
+
return [
|
|
84
|
+
{
|
|
85
|
+
name: 'message',
|
|
86
|
+
type: jsonSelectionType
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
name: 'extensions',
|
|
90
|
+
type: jsonSelectionType
|
|
91
|
+
},
|
|
92
|
+
]
|
|
93
|
+
}
|
|
94
|
+
})
|
|
96
95
|
);
|
|
97
|
-
connect.repeatable = true;
|
|
98
|
-
|
|
99
|
-
connect.addArgument(SOURCE, schema.stringType());
|
|
100
|
-
connect.addArgument(ID, schema.stringType());
|
|
101
96
|
|
|
102
97
|
/*
|
|
103
98
|
input HTTPHeaderMapping {
|
|
@@ -106,15 +101,82 @@ export class ConnectSpecDefinition extends FeatureDefinition {
|
|
|
106
101
|
value: String
|
|
107
102
|
}
|
|
108
103
|
*/
|
|
109
|
-
|
|
110
|
-
|
|
104
|
+
this.registerType(
|
|
105
|
+
createInputObjectTypeSpecification({
|
|
106
|
+
name: HTTP_HEADER_MAPPING,
|
|
107
|
+
inputFieldsFct: (schema) => [
|
|
108
|
+
{
|
|
109
|
+
name: 'name',
|
|
110
|
+
type: new NonNullType(schema.stringType())
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
name: 'from',
|
|
114
|
+
type: schema.stringType()
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
name: 'value',
|
|
118
|
+
type: schema.stringType()
|
|
119
|
+
},
|
|
120
|
+
]
|
|
121
|
+
})
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
/*
|
|
125
|
+
input ConnectBatch {
|
|
126
|
+
maxSize: Int
|
|
127
|
+
}
|
|
128
|
+
*/
|
|
129
|
+
this.registerType(
|
|
130
|
+
createInputObjectTypeSpecification({
|
|
131
|
+
name: CONNECT_BATCH,
|
|
132
|
+
inputFieldsFct: (schema) => [
|
|
133
|
+
{
|
|
134
|
+
name: 'maxSize',
|
|
135
|
+
type: schema.intType()
|
|
136
|
+
}
|
|
137
|
+
]
|
|
138
|
+
})
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
/*
|
|
142
|
+
input SourceHTTP {
|
|
143
|
+
baseURL: String!
|
|
144
|
+
headers: [HTTPHeaderMapping!]
|
|
145
|
+
|
|
146
|
+
# added in v0.2
|
|
147
|
+
path: JSONSelection
|
|
148
|
+
queryParams: JSONSelection
|
|
149
|
+
}
|
|
150
|
+
*/
|
|
151
|
+
this.registerType(
|
|
152
|
+
createInputObjectTypeSpecification({
|
|
153
|
+
name: SOURCE_HTTP,
|
|
154
|
+
inputFieldsFct: (schema, feature) => {
|
|
155
|
+
const jsonSelectionType =
|
|
156
|
+
lookupFeatureTypeInSchema<ScalarType>(JSON_SELECTION, 'ScalarType', schema, feature);
|
|
157
|
+
const httpHeaderMappingType =
|
|
158
|
+
lookupFeatureTypeInSchema<InputObjectType>(HTTP_HEADER_MAPPING, 'InputObjectType', schema, feature);
|
|
159
|
+
return [
|
|
160
|
+
{
|
|
161
|
+
name: 'baseURL',
|
|
162
|
+
type: new NonNullType(schema.stringType())
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
name: 'headers',
|
|
166
|
+
type: new ListType(new NonNullType(httpHeaderMappingType))
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
name: 'path',
|
|
170
|
+
type: jsonSelectionType
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
name: 'queryParams',
|
|
174
|
+
type: jsonSelectionType
|
|
175
|
+
}
|
|
176
|
+
];
|
|
177
|
+
}
|
|
178
|
+
})
|
|
111
179
|
);
|
|
112
|
-
HTTPHeaderMapping.addField(new InputFieldDefinition('name')).type =
|
|
113
|
-
new NonNullType(schema.stringType());
|
|
114
|
-
HTTPHeaderMapping.addField(new InputFieldDefinition('from')).type =
|
|
115
|
-
schema.stringType();
|
|
116
|
-
HTTPHeaderMapping.addField(new InputFieldDefinition('value')).type =
|
|
117
|
-
schema.stringType();
|
|
118
180
|
|
|
119
181
|
/*
|
|
120
182
|
input ConnectHTTP {
|
|
@@ -128,85 +190,172 @@ export class ConnectSpecDefinition extends FeatureDefinition {
|
|
|
128
190
|
|
|
129
191
|
# added in v0.2
|
|
130
192
|
path: JSONSelection
|
|
131
|
-
|
|
193
|
+
queryParams: JSONSelection
|
|
132
194
|
}
|
|
133
195
|
*/
|
|
134
|
-
|
|
135
|
-
|
|
196
|
+
this.registerType(
|
|
197
|
+
createInputObjectTypeSpecification({
|
|
198
|
+
name: CONNECT_HTTP,
|
|
199
|
+
inputFieldsFct: (schema, feature) => {
|
|
200
|
+
const urlPathTemplateType =
|
|
201
|
+
lookupFeatureTypeInSchema<ScalarType>(URL_PATH_TEMPLATE, 'ScalarType', schema, feature);
|
|
202
|
+
const jsonSelectionType =
|
|
203
|
+
lookupFeatureTypeInSchema<ScalarType>(JSON_SELECTION, 'ScalarType', schema, feature);
|
|
204
|
+
const httpHeaderMappingType =
|
|
205
|
+
lookupFeatureTypeInSchema<InputObjectType>(HTTP_HEADER_MAPPING, 'InputObjectType', schema, feature);
|
|
206
|
+
return [
|
|
207
|
+
{
|
|
208
|
+
name: 'GET',
|
|
209
|
+
type: urlPathTemplateType
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
name: 'POST',
|
|
213
|
+
type: urlPathTemplateType
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
name: 'PUT',
|
|
217
|
+
type: urlPathTemplateType
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
name: 'PATCH',
|
|
221
|
+
type: urlPathTemplateType
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
name: 'DELETE',
|
|
225
|
+
type: urlPathTemplateType
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
name: 'body',
|
|
229
|
+
type: jsonSelectionType
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
name: 'headers',
|
|
233
|
+
type: new ListType(new NonNullType(httpHeaderMappingType))
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
name: 'path',
|
|
237
|
+
type: jsonSelectionType
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
name: 'queryParams',
|
|
241
|
+
type: jsonSelectionType
|
|
242
|
+
},
|
|
243
|
+
];
|
|
244
|
+
}
|
|
245
|
+
})
|
|
136
246
|
);
|
|
137
|
-
ConnectHTTP.addField(new InputFieldDefinition('GET')).type =
|
|
138
|
-
URLPathTemplate;
|
|
139
|
-
ConnectHTTP.addField(new InputFieldDefinition('POST')).type =
|
|
140
|
-
URLPathTemplate;
|
|
141
|
-
ConnectHTTP.addField(new InputFieldDefinition('PUT')).type =
|
|
142
|
-
URLPathTemplate;
|
|
143
|
-
ConnectHTTP.addField(new InputFieldDefinition('PATCH')).type =
|
|
144
|
-
URLPathTemplate;
|
|
145
|
-
ConnectHTTP.addField(new InputFieldDefinition('DELETE')).type =
|
|
146
|
-
URLPathTemplate;
|
|
147
|
-
ConnectHTTP.addField(new InputFieldDefinition('body')).type = JSONSelection;
|
|
148
|
-
ConnectHTTP.addField(new InputFieldDefinition('headers')).type =
|
|
149
|
-
new ListType(new NonNullType(HTTPHeaderMapping));
|
|
150
|
-
|
|
151
|
-
ConnectHTTP.addField(new InputFieldDefinition('path')).type = JSONSelection;
|
|
152
|
-
ConnectHTTP.addField(new InputFieldDefinition('queryParams')).type =
|
|
153
|
-
JSONSelection;
|
|
154
|
-
|
|
155
|
-
connect.addArgument('http', new NonNullType(ConnectHTTP));
|
|
156
|
-
|
|
157
|
-
const ConnectBatch = schema.addType(new InputObjectType(this.typeNameInSchema(schema, CONNECT_BATCH)!));
|
|
158
|
-
ConnectBatch.addField(new InputFieldDefinition('maxSize')).type = schema.intType();
|
|
159
|
-
connect.addArgument('batch', ConnectBatch);
|
|
160
|
-
|
|
161
|
-
const ConnectorErrors = schema.addType(new InputObjectType(this.typeNameInSchema(schema, CONNECTOR_ERRORS)!));
|
|
162
|
-
ConnectorErrors.addField(new InputFieldDefinition('message')).type = JSONSelection;
|
|
163
|
-
ConnectorErrors.addField(new InputFieldDefinition('extensions')).type = JSONSelection;
|
|
164
|
-
connect.addArgument('errors', ConnectorErrors);
|
|
165
|
-
|
|
166
|
-
connect.addArgument('selection', new NonNullType(JSONSelection));
|
|
167
|
-
connect.addArgument('entity', schema.booleanType(), false);
|
|
168
|
-
connect.addArgument('isSuccess', JSONSelection);
|
|
169
247
|
|
|
170
248
|
/*
|
|
171
|
-
directive @
|
|
172
|
-
|
|
173
|
-
|
|
249
|
+
directive @connect(
|
|
250
|
+
source: String
|
|
251
|
+
id: String
|
|
252
|
+
http: ConnectHTTP!
|
|
253
|
+
batch: ConnectBatch
|
|
174
254
|
errors: ConnectorErrors
|
|
255
|
+
selection: JSONSelection!
|
|
256
|
+
entity: Boolean = false
|
|
175
257
|
isSuccess: JSONSelection
|
|
176
|
-
) repeatable on
|
|
258
|
+
) repeatable on FIELD_DEFINITION
|
|
259
|
+
| OBJECT # added in v0.2, validation enforced in rust
|
|
177
260
|
*/
|
|
178
|
-
|
|
179
|
-
|
|
261
|
+
this.registerDirective(
|
|
262
|
+
createDirectiveSpecification({
|
|
263
|
+
name: CONNECT,
|
|
264
|
+
locations: [DirectiveLocation.FIELD_DEFINITION, DirectiveLocation.OBJECT],
|
|
265
|
+
repeatable: true,
|
|
266
|
+
args: [
|
|
267
|
+
{
|
|
268
|
+
name: 'source',
|
|
269
|
+
type: (schema) => schema.stringType()
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
name: 'id',
|
|
273
|
+
type: (schema) => schema.stringType()
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
name: 'http',
|
|
277
|
+
type: (schema, feature) => {
|
|
278
|
+
const connectHttpType =
|
|
279
|
+
lookupFeatureTypeInSchema<InputObjectType>(CONNECT_HTTP, 'InputObjectType', schema, feature);
|
|
280
|
+
return new NonNullType(connectHttpType);
|
|
281
|
+
}
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
name: 'batch',
|
|
285
|
+
type: (schema, feature) =>
|
|
286
|
+
lookupFeatureTypeInSchema<InputObjectType>(CONNECT_BATCH, 'InputObjectType', schema, feature)
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
name: 'errors',
|
|
290
|
+
type: (schema, feature) =>
|
|
291
|
+
lookupFeatureTypeInSchema<InputObjectType>(CONNECTOR_ERRORS, 'InputObjectType', schema, feature)
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
name: 'selection',
|
|
295
|
+
type: (schema, feature) => {
|
|
296
|
+
const jsonSelectionType =
|
|
297
|
+
lookupFeatureTypeInSchema<ScalarType>(JSON_SELECTION, 'ScalarType', schema, feature);
|
|
298
|
+
return new NonNullType(jsonSelectionType);
|
|
299
|
+
}
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
name: 'entity',
|
|
303
|
+
type: (schema) => schema.booleanType(),
|
|
304
|
+
defaultValue: false
|
|
305
|
+
},
|
|
306
|
+
{
|
|
307
|
+
name: 'isSuccess',
|
|
308
|
+
type: (schema, feature) =>
|
|
309
|
+
lookupFeatureTypeInSchema<ScalarType>(JSON_SELECTION, 'ScalarType', schema, feature)
|
|
310
|
+
}
|
|
311
|
+
],
|
|
312
|
+
// We "compose" these directives using the `@join__directive` mechanism,
|
|
313
|
+
// so they do not need to be composed in the way passing `composes: true`
|
|
314
|
+
// here implies.
|
|
315
|
+
composes: false,
|
|
316
|
+
}),
|
|
180
317
|
);
|
|
181
|
-
source.repeatable = true;
|
|
182
|
-
source.addArgument('name', new NonNullType(schema.stringType()));
|
|
183
318
|
|
|
184
319
|
/*
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
query: JSONSelection
|
|
192
|
-
}
|
|
320
|
+
directive @source(
|
|
321
|
+
name: String!
|
|
322
|
+
http: SourceHTTP!
|
|
323
|
+
errors: ConnectorErrors
|
|
324
|
+
isSuccess: JSONSelection
|
|
325
|
+
) repeatable on SCHEMA
|
|
193
326
|
*/
|
|
194
|
-
|
|
195
|
-
|
|
327
|
+
this.registerDirective(
|
|
328
|
+
createDirectiveSpecification({
|
|
329
|
+
name: SOURCE,
|
|
330
|
+
locations: [DirectiveLocation.SCHEMA],
|
|
331
|
+
repeatable: true,
|
|
332
|
+
composes: false,
|
|
333
|
+
args: [
|
|
334
|
+
{
|
|
335
|
+
name: 'name',
|
|
336
|
+
type: (schema) => new NonNullType(schema.stringType())
|
|
337
|
+
},
|
|
338
|
+
{
|
|
339
|
+
name: 'http',
|
|
340
|
+
type: (schema, feature) => {
|
|
341
|
+
const sourceHttpType =
|
|
342
|
+
lookupFeatureTypeInSchema<InputObjectType>(SOURCE_HTTP, 'InputObjectType', schema, feature);
|
|
343
|
+
return new NonNullType(sourceHttpType);
|
|
344
|
+
}
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
name: 'errors',
|
|
348
|
+
type: (schema, feature) =>
|
|
349
|
+
lookupFeatureTypeInSchema<InputObjectType>(CONNECTOR_ERRORS, 'InputObjectType', schema, feature)
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
name: 'isSuccess',
|
|
353
|
+
type: (schema, feature) =>
|
|
354
|
+
lookupFeatureTypeInSchema<ScalarType>(JSON_SELECTION, 'ScalarType', schema, feature)
|
|
355
|
+
}
|
|
356
|
+
]
|
|
357
|
+
}),
|
|
196
358
|
);
|
|
197
|
-
SourceHTTP.addField(new InputFieldDefinition('baseURL')).type =
|
|
198
|
-
new NonNullType(schema.stringType());
|
|
199
|
-
SourceHTTP.addField(new InputFieldDefinition('headers')).type =
|
|
200
|
-
new ListType(new NonNullType(HTTPHeaderMapping));
|
|
201
|
-
|
|
202
|
-
SourceHTTP.addField(new InputFieldDefinition('path')).type = JSONSelection;
|
|
203
|
-
SourceHTTP.addField(new InputFieldDefinition('queryParams')).type = JSONSelection;
|
|
204
|
-
|
|
205
|
-
source.addArgument('http', new NonNullType(SourceHTTP));
|
|
206
|
-
source.addArgument('errors', ConnectorErrors);
|
|
207
|
-
source.addArgument('isSuccess', JSONSelection);
|
|
208
|
-
|
|
209
|
-
return [];
|
|
210
359
|
}
|
|
211
360
|
|
|
212
361
|
get defaultCorePurpose(): CorePurpose {
|
|
@@ -237,3 +386,108 @@ export const CONNECT_VERSIONS = new FeatureDefinitions<ConnectSpecDefinition>(
|
|
|
237
386
|
);
|
|
238
387
|
|
|
239
388
|
registerKnownFeature(CONNECT_VERSIONS);
|
|
389
|
+
|
|
390
|
+
// This function is purposefully declared only in this file and without export.
|
|
391
|
+
//
|
|
392
|
+
// Do NOT add this to "internals-js/src/directiveAndTypeSpecification.ts", and
|
|
393
|
+
// do NOT export this function.
|
|
394
|
+
//
|
|
395
|
+
// Subgraph schema building, at this time of writing, does not really support
|
|
396
|
+
// input objects in specs. We did a number of one-off things to support them in
|
|
397
|
+
// the connect spec's case, and it will be non-maintainable/bug-prone to do them
|
|
398
|
+
// again.
|
|
399
|
+
//
|
|
400
|
+
// There's work to be done to support input objects more generally; please see
|
|
401
|
+
// https://github.com/apollographql/federation/pull/3311 for more information.
|
|
402
|
+
function createInputObjectTypeSpecification({
|
|
403
|
+
name,
|
|
404
|
+
inputFieldsFct,
|
|
405
|
+
}: {
|
|
406
|
+
name: string,
|
|
407
|
+
inputFieldsFct: (schema: Schema, feature?: CoreFeature) => InputFieldSpecification[],
|
|
408
|
+
}): TypeSpecification {
|
|
409
|
+
return {
|
|
410
|
+
name,
|
|
411
|
+
checkOrAdd: (schema: Schema, feature?: CoreFeature, asBuiltIn?: boolean) => {
|
|
412
|
+
const actualName = feature?.typeNameInSchema(name) ?? name;
|
|
413
|
+
const expectedFields = inputFieldsFct(schema, feature);
|
|
414
|
+
const existing = schema.type(actualName);
|
|
415
|
+
if (existing) {
|
|
416
|
+
let errors = ensureSameTypeKind('InputObjectType', existing);
|
|
417
|
+
if (errors.length > 0) {
|
|
418
|
+
return errors;
|
|
419
|
+
}
|
|
420
|
+
assert(isInputObjectType(existing), 'Should be an input object type');
|
|
421
|
+
// The following mimics `ensureSameArguments()`, but with some changes.
|
|
422
|
+
for (const { name: fieldName, type, defaultValue } of expectedFields) {
|
|
423
|
+
const existingField = existing.field(fieldName);
|
|
424
|
+
if (!existingField) {
|
|
425
|
+
// Not declaring an optional input field is ok: that means you won't
|
|
426
|
+
// be able to pass a non-default value in your schema, but we allow
|
|
427
|
+
// you that. But missing a required input field it not ok.
|
|
428
|
+
if (isNonNullType(type) && defaultValue === undefined) {
|
|
429
|
+
errors.push(ERRORS.TYPE_DEFINITION_INVALID.err(
|
|
430
|
+
`Invalid definition for type ${name}: missing required input field "${fieldName}"`,
|
|
431
|
+
{ nodes: existing.sourceAST },
|
|
432
|
+
));
|
|
433
|
+
}
|
|
434
|
+
continue;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
let existingType = existingField.type!;
|
|
438
|
+
if (isNonNullType(existingType) && !isNonNullType(type)) {
|
|
439
|
+
// It's ok to redefine an optional input field as mandatory. For
|
|
440
|
+
// instance, if you want to force people on your team to provide a
|
|
441
|
+
// "maxSize", you can redefine ConnectBatch as
|
|
442
|
+
// `input ConnectBatch { maxSize: Int! }` to get validation. In
|
|
443
|
+
// other words, you are allowed to always pass an input field that
|
|
444
|
+
// is optional if you so wish.
|
|
445
|
+
existingType = existingType.ofType;
|
|
446
|
+
}
|
|
447
|
+
// Note that while `ensureSameArguments()` allows input type
|
|
448
|
+
// redefinitions (e.g. allowing users to declare `String` instead of a
|
|
449
|
+
// custom scalar), this behavior can be confusing/error-prone more
|
|
450
|
+
// generally, so we forbid this for now. We can relax this later on a
|
|
451
|
+
// case-by-case basis if needed.
|
|
452
|
+
//
|
|
453
|
+
// Further, `ensureSameArguments()` would skip default value checking
|
|
454
|
+
// if the input type was non-nullable. It's unclear why this is there;
|
|
455
|
+
// it may have been a mistake due to the impression that non-nullable
|
|
456
|
+
// inputs can't have default values (they can), or this may have been
|
|
457
|
+
// to avoid some breaking change, but there's no such limitation in
|
|
458
|
+
// the case of input objects, so we always validate default values
|
|
459
|
+
// here.
|
|
460
|
+
if (!sameType(type, existingType)) {
|
|
461
|
+
errors.push(ERRORS.TYPE_DEFINITION_INVALID.err(
|
|
462
|
+
`Invalid definition for type ${name}: input field "${fieldName}" should have type "${type}" but found type "${existingField.type!}"`,
|
|
463
|
+
{ nodes: existingField.sourceAST },
|
|
464
|
+
));
|
|
465
|
+
} else if (!valueEquals(defaultValue, existingField.defaultValue)) {
|
|
466
|
+
errors.push(ERRORS.TYPE_DEFINITION_INVALID.err(
|
|
467
|
+
`Invalid definition type ${name}: input field "${fieldName}" should have default value ${valueToString(defaultValue)} but found default value ${valueToString(existingField.defaultValue)}`,
|
|
468
|
+
{ nodes: existingField.sourceAST },
|
|
469
|
+
));
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
for (const existingField of existing.fields()) {
|
|
473
|
+
// If it's an expected input field, we already validated it. But we
|
|
474
|
+
// still need to reject unknown input fields.
|
|
475
|
+
if (!expectedFields.some((field) => field.name === existingField.name)) {
|
|
476
|
+
errors.push(ERRORS.TYPE_DEFINITION_INVALID.err(
|
|
477
|
+
`Invalid definition for type ${name}: unknown/unsupported input field "${existingField.name}"`,
|
|
478
|
+
{ nodes: existingField.sourceAST },
|
|
479
|
+
));
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
return errors;
|
|
483
|
+
} else {
|
|
484
|
+
const createdType = schema.addType(new InputObjectType(actualName, asBuiltIn));
|
|
485
|
+
for (const { name, type, defaultValue } of expectedFields) {
|
|
486
|
+
const newField = createdType.addField(name, type);
|
|
487
|
+
newField.defaultValue = defaultValue;
|
|
488
|
+
}
|
|
489
|
+
return [];
|
|
490
|
+
}
|
|
491
|
+
},
|
|
492
|
+
}
|
|
493
|
+
}
|
package/src/specs/policySpec.ts
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
FeatureUrl,
|
|
7
7
|
FeatureVersion,
|
|
8
8
|
} from "./coreSpec";
|
|
9
|
-
import { ListType, NonNullType } from "../definitions";
|
|
9
|
+
import {DirectiveDefinition, ListType, NonNullType, Schema} from "../definitions";
|
|
10
10
|
import { createDirectiveSpecification, createScalarTypeSpecification } from "../directiveAndTypeSpecification";
|
|
11
11
|
import { registerKnownFeature } from "../knownCoreFeatures";
|
|
12
12
|
import { ARGUMENT_COMPOSITION_STRATEGIES } from "../argumentCompositionStrategies";
|
|
@@ -42,7 +42,7 @@ export class PolicySpecDefinition extends FeatureDefinition {
|
|
|
42
42
|
assert(PolicyType, () => `Expected "${policyName}" to be defined`);
|
|
43
43
|
return new NonNullType(new ListType(new NonNullType(new ListType(new NonNullType(PolicyType)))));
|
|
44
44
|
},
|
|
45
|
-
compositionStrategy: ARGUMENT_COMPOSITION_STRATEGIES.
|
|
45
|
+
compositionStrategy: ARGUMENT_COMPOSITION_STRATEGIES.DNF_CONJUNCTION,
|
|
46
46
|
}],
|
|
47
47
|
locations: [
|
|
48
48
|
DirectiveLocation.FIELD_DEFINITION,
|
|
@@ -56,6 +56,10 @@ export class PolicySpecDefinition extends FeatureDefinition {
|
|
|
56
56
|
}));
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
policyDirective(schema: Schema): DirectiveDefinition<{policies: string[][]}> | undefined {
|
|
60
|
+
return this.directive(schema, PolicySpecDefinition.directiveName);
|
|
61
|
+
}
|
|
62
|
+
|
|
59
63
|
get defaultCorePurpose(): CorePurpose {
|
|
60
64
|
return 'SECURITY';
|
|
61
65
|
}
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
FeatureUrl,
|
|
7
7
|
FeatureVersion,
|
|
8
8
|
} from "./coreSpec";
|
|
9
|
-
import { ListType, NonNullType } from "../definitions";
|
|
9
|
+
import {DirectiveDefinition, ListType, NonNullType, Schema} from "../definitions";
|
|
10
10
|
import { createDirectiveSpecification, createScalarTypeSpecification } from "../directiveAndTypeSpecification";
|
|
11
11
|
import { registerKnownFeature } from "../knownCoreFeatures";
|
|
12
12
|
import { ARGUMENT_COMPOSITION_STRATEGIES } from "../argumentCompositionStrategies";
|
|
@@ -43,7 +43,7 @@ export class RequiresScopesSpecDefinition extends FeatureDefinition {
|
|
|
43
43
|
assert(scopeType, () => `Expected "${scopeName}" to be defined`);
|
|
44
44
|
return new NonNullType(new ListType(new NonNullType(new ListType(new NonNullType(scopeType)))));
|
|
45
45
|
},
|
|
46
|
-
compositionStrategy: ARGUMENT_COMPOSITION_STRATEGIES.
|
|
46
|
+
compositionStrategy: ARGUMENT_COMPOSITION_STRATEGIES.DNF_CONJUNCTION,
|
|
47
47
|
}],
|
|
48
48
|
locations: [
|
|
49
49
|
DirectiveLocation.FIELD_DEFINITION,
|
|
@@ -57,6 +57,10 @@ export class RequiresScopesSpecDefinition extends FeatureDefinition {
|
|
|
57
57
|
}));
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
requiresScopesDirective(schema: Schema): DirectiveDefinition<{scopes: string[][]}> | undefined {
|
|
61
|
+
return this.directive(schema, RequiresScopesSpecDefinition.directiveName);
|
|
62
|
+
}
|
|
63
|
+
|
|
60
64
|
get defaultCorePurpose(): CorePurpose {
|
|
61
65
|
return 'SECURITY';
|
|
62
66
|
}
|