@apollo/gateway 2.2.2 → 2.3.0-alpha.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.
@@ -147,7 +147,7 @@ describe('lifecycle hooks', () => {
147
147
  // the supergraph (even just formatting differences), this ID will change
148
148
  // and this test will have to updated.
149
149
  expect(secondCall[0]!.compositionId).toEqual(
150
- '55f11e687010f75c791b917d9a46745b967c5b19d20443463ed075e500bff6c8',
150
+ 'ed8cb418d55e7cd069f11d093b2ea29316e1a913a5757f383cc78ed399414104',
151
151
  );
152
152
  // second call should have previous info in the second arg
153
153
  expect(secondCall[1]!.compositionId).toEqual(expectedFirstId);
@@ -24,12 +24,14 @@ import {
24
24
  QueryPlanSelectionNode,
25
25
  QueryPlanFieldNode,
26
26
  getResponseName,
27
+ FetchDataInputRewrite,
28
+ FetchDataOutputRewrite,
27
29
  } from '@apollo/query-planner';
28
30
  import { deepMerge } from './utilities/deepMerge';
29
31
  import { isNotNullOrUndefined } from './utilities/array';
30
32
  import { SpanStatusCode } from "@opentelemetry/api";
31
33
  import { OpenTelemetrySpanNames, tracer } from "./utilities/opentelemetry";
32
- import { assert, defaultRootName, errorCodeDef, ERRORS } from '@apollo/federation-internals';
34
+ import { assert, defaultRootName, errorCodeDef, ERRORS, isDefined } from '@apollo/federation-internals';
33
35
  import { GatewayGraphQLRequestContext, GatewayExecutionResult } from '@apollo/server-gateway-interface';
34
36
 
35
37
  export type ServiceMap = {
@@ -307,7 +309,7 @@ async function executeFetch(
307
309
  );
308
310
 
309
311
  for (const entity of entities) {
310
- deepMerge(entity, dataReceivedFromService);
312
+ deepMerge(entity, withFetchRewrites(dataReceivedFromService, fetch.outputRewrites));
311
313
  }
312
314
  } else {
313
315
  const requires = fetch.requires;
@@ -323,6 +325,7 @@ async function executeFetch(
323
325
  context.supergraphSchema,
324
326
  entity,
325
327
  requires,
328
+ fetch.inputRewrites,
326
329
  );
327
330
  if (representation && representation[TypeNameMetaFieldDef.name]) {
328
331
  representations.push(representation);
@@ -368,7 +371,7 @@ async function executeFetch(
368
371
  }
369
372
 
370
373
  for (let i = 0; i < entities.length; i++) {
371
- deepMerge(entities[representationToEntity[i]], receivedEntities[i]);
374
+ deepMerge(entities[representationToEntity[i]], withFetchRewrites(receivedEntities[i], filterEntityRewrites(representations[i], fetch.outputRewrites)));
372
375
  }
373
376
  }
374
377
  }
@@ -484,6 +487,84 @@ async function executeFetch(
484
487
  }
485
488
  }
486
489
 
490
+ function applyOrMapRecursive(value: any | any[], fct: (v: any) => any | undefined): any | any[] | undefined {
491
+ if (Array.isArray(value)) {
492
+ const res = value.map((elt) => applyOrMapRecursive(elt, fct)).filter(isDefined);
493
+ return res.length === 0 ? undefined : res;
494
+ }
495
+ return fct(value);
496
+ }
497
+
498
+ function withFetchRewrites(fetchResult: ResultMap | null | void, rewrites: FetchDataOutputRewrite[] | undefined): ResultMap | null | void {
499
+ if (!rewrites || !fetchResult) {
500
+ return fetchResult;
501
+ }
502
+
503
+ for (const rewrite of rewrites) {
504
+ let obj: any = fetchResult;
505
+ let i = 0;
506
+ while (obj && i < rewrite.path.length - 1) {
507
+ const p = rewrite.path[i++];
508
+ if (p.startsWith('... on ')) {
509
+ const typename = p.slice('... on '.length);
510
+ // Filter only objects that match the condition.
511
+ obj = applyOrMapRecursive(obj, (elt) => elt[TypeNameMetaFieldDef.name] === typename ? elt : undefined);
512
+ } else {
513
+ obj = applyOrMapRecursive(obj, (elt) => elt[p]);
514
+ }
515
+ }
516
+ if (obj) {
517
+ applyOrMapRecursive(obj, (elt) => {
518
+ if (typeof elt === 'object') {
519
+ // We need to move the value at path[i] to `renameKeyTo`.
520
+ const removedKey = rewrite.path[i];
521
+ elt[rewrite.renameKeyTo] = elt[removedKey];
522
+ elt[removedKey] = undefined;
523
+ }
524
+ });
525
+ }
526
+ }
527
+ return fetchResult;
528
+ }
529
+
530
+ function filterEntityRewrites(entity: Record<string, any>, rewrites: FetchDataOutputRewrite[] | undefined): FetchDataOutputRewrite[] | undefined {
531
+ if (!rewrites) {
532
+ return undefined;
533
+ }
534
+
535
+ const typename = entity[TypeNameMetaFieldDef.name] as string;
536
+ const typenameAsFragment = `... on ${typename}`;
537
+ return rewrites.map((r) => r.path[0] === typenameAsFragment ? { ...r, path: r.path.slice(1) } : undefined).filter(isDefined)
538
+ }
539
+
540
+ function updateRewrites(rewrites: FetchDataInputRewrite[] | undefined, pathElement: string): {
541
+ updated: FetchDataInputRewrite[],
542
+ completeRewrite?: any,
543
+ } | undefined {
544
+ if (!rewrites) {
545
+ return undefined;
546
+ }
547
+
548
+ let completeRewrite: any = undefined;
549
+ const updated = rewrites
550
+ .map((r) => {
551
+ let u: FetchDataInputRewrite | undefined = undefined;
552
+ if (r.path[0] === pathElement) {
553
+ const updatedPath = r.path.slice(1);
554
+ if (updatedPath.length === 0) {
555
+ completeRewrite = r.setValueTo;
556
+ } else {
557
+ u = { ...r, path: updatedPath };
558
+ }
559
+ }
560
+ return u;
561
+ })
562
+ .filter(isDefined);
563
+ return updated.length === 0 && completeRewrite === undefined
564
+ ? undefined
565
+ : { updated, completeRewrite };
566
+ }
567
+
487
568
  /**
488
569
  *
489
570
  * @param source Result of GraphQL execution.
@@ -493,6 +574,7 @@ function executeSelectionSet(
493
574
  schema: GraphQLSchema,
494
575
  source: Record<string, any> | null,
495
576
  selections: QueryPlanSelectionNode[],
577
+ activeRewrites?: FetchDataInputRewrite[],
496
578
  ): Record<string, any> | null {
497
579
 
498
580
  // If the underlying service has returned null for the parent (source)
@@ -522,10 +604,17 @@ function executeSelectionSet(
522
604
  // here.
523
605
  return null;
524
606
  }
607
+
608
+ const updatedRewrites = updateRewrites(activeRewrites, responseName);
609
+ if (updatedRewrites?.completeRewrite !== undefined) {
610
+ result[responseName] = updatedRewrites.completeRewrite;
611
+ continue;
612
+ }
613
+
525
614
  if (Array.isArray(source[responseName])) {
526
615
  result[responseName] = source[responseName].map((value: any) =>
527
616
  selections
528
- ? executeSelectionSet(schema, value, selections)
617
+ ? executeSelectionSet(schema, value, selections, updatedRewrites?.updated)
529
618
  : value,
530
619
  );
531
620
  } else if (selections) {
@@ -533,6 +622,7 @@ function executeSelectionSet(
533
622
  schema,
534
623
  source[responseName],
535
624
  selections,
625
+ updatedRewrites?.updated,
536
626
  );
537
627
  } else {
538
628
  result[responseName] = source[responseName];
@@ -545,9 +635,10 @@ function executeSelectionSet(
545
635
  if (!typename) continue;
546
636
 
547
637
  if (doesTypeConditionMatch(schema, selection.typeCondition, typename)) {
638
+ const updatedRewrites = activeRewrites ? updateRewrites(activeRewrites, `... on ${selection.typeCondition}`) : undefined;
548
639
  deepMerge(
549
640
  result,
550
- executeSelectionSet(schema, source, selection.selections),
641
+ executeSelectionSet(schema, source, selection.selections, updatedRewrites?.updated),
551
642
  );
552
643
  }
553
644
  break;