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