@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.
Files changed (44) hide show
  1. package/dist/argumentCompositionStrategies.d.ts +6 -0
  2. package/dist/argumentCompositionStrategies.d.ts.map +1 -1
  3. package/dist/argumentCompositionStrategies.js +77 -0
  4. package/dist/argumentCompositionStrategies.js.map +1 -1
  5. package/dist/buildSchema.d.ts.map +1 -1
  6. package/dist/buildSchema.js +42 -2
  7. package/dist/buildSchema.js.map +1 -1
  8. package/dist/directiveAndTypeSpecification.d.ts +8 -3
  9. package/dist/directiveAndTypeSpecification.d.ts.map +1 -1
  10. package/dist/directiveAndTypeSpecification.js +2 -1
  11. package/dist/directiveAndTypeSpecification.js.map +1 -1
  12. package/dist/error.d.ts +2 -0
  13. package/dist/error.d.ts.map +1 -1
  14. package/dist/error.js +4 -0
  15. package/dist/error.js.map +1 -1
  16. package/dist/federation.d.ts.map +1 -1
  17. package/dist/federation.js +37 -8
  18. package/dist/federation.js.map +1 -1
  19. package/dist/specs/authenticatedSpec.d.ts +2 -0
  20. package/dist/specs/authenticatedSpec.d.ts.map +1 -1
  21. package/dist/specs/authenticatedSpec.js +3 -0
  22. package/dist/specs/authenticatedSpec.js.map +1 -1
  23. package/dist/specs/connectSpec.d.ts +0 -3
  24. package/dist/specs/connectSpec.d.ts.map +1 -1
  25. package/dist/specs/connectSpec.js +240 -64
  26. package/dist/specs/connectSpec.js.map +1 -1
  27. package/dist/specs/policySpec.d.ts +4 -0
  28. package/dist/specs/policySpec.d.ts.map +1 -1
  29. package/dist/specs/policySpec.js +4 -1
  30. package/dist/specs/policySpec.js.map +1 -1
  31. package/dist/specs/requiresScopesSpec.d.ts +4 -0
  32. package/dist/specs/requiresScopesSpec.d.ts.map +1 -1
  33. package/dist/specs/requiresScopesSpec.js +4 -1
  34. package/dist/specs/requiresScopesSpec.js.map +1 -1
  35. package/package.json +1 -1
  36. package/src/argumentCompositionStrategies.ts +114 -2
  37. package/src/buildSchema.ts +51 -0
  38. package/src/directiveAndTypeSpecification.ts +8 -2
  39. package/src/error.ts +14 -0
  40. package/src/federation.ts +45 -8
  41. package/src/specs/authenticatedSpec.ts +5 -0
  42. package/src/specs/connectSpec.ts +380 -126
  43. package/src/specs/policySpec.ts +6 -2
  44. package/src/specs/requiresScopesSpec.ts +6 -2
@@ -1,4 +1,4 @@
1
- import { DirectiveLocation, GraphQLError } from 'graphql';
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
- Schema,
11
- NonNullType,
10
+ CoreFeature,
12
11
  InputObjectType,
13
- InputFieldDefinition,
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
- this.registerDirective(
46
- createDirectiveSpecification({
47
- name: CONNECT,
48
- locations: [DirectiveLocation.FIELD_DEFINITION],
49
- repeatable: true,
50
- // We "compose" these directives using the `@join__directive` mechanism,
51
- // so they do not need to be composed in the way passing `composes: true`
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
- createScalarTypeSpecification({ name: URL_PATH_TEMPLATE }),
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
- const JSONSelection = this.addScalarType(schema, JSON_SELECTION);
69
+ this.registerType(createScalarTypeSpecification({ name: JSON_SELECTION }));
81
70
 
82
71
  /*
83
- directive @connect(
84
- source: String
85
- http: ConnectHTTP
86
- selection: JSONSelection!
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
- const connect = this.addDirective(schema, CONNECT).addLocations(
94
- DirectiveLocation.FIELD_DEFINITION,
95
- DirectiveLocation.OBJECT,
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
- const HTTPHeaderMapping = schema.addType(
110
- new InputObjectType(this.typeNameInSchema(schema, HTTP_HEADER_MAPPING)!),
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
- query: JSONSelection
193
+ queryParams: JSONSelection
132
194
  }
133
195
  */
134
- const ConnectHTTP = schema.addType(
135
- new InputObjectType(this.typeNameInSchema(schema, CONNECT_HTTP)!),
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 @source(
172
- name: String!
173
- http: ConnectHTTP
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 SCHEMA
258
+ ) repeatable on FIELD_DEFINITION
259
+ | OBJECT # added in v0.2, validation enforced in rust
177
260
  */
178
- const source = this.addDirective(schema, SOURCE).addLocations(
179
- DirectiveLocation.SCHEMA,
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
- input SourceHTTP {
186
- baseURL: String!
187
- headers: [HTTPHeaderMapping!]
188
-
189
- # added in v0.2
190
- path: JSONSelection
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
- const SourceHTTP = schema.addType(
195
- new InputObjectType(this.typeNameInSchema(schema, SOURCE_HTTP)!),
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
+ }
@@ -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.UNION,
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.UNION,
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
  }