@apollo/federation-internals 2.11.2 → 2.11.4

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