Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 46 additions & 46 deletions composition-go/index.global.js

Large diffs are not rendered by default.

96 changes: 47 additions & 49 deletions composition/src/resolvability-graph/graph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
type EntityResolvabilityErrorsParams,
type EntitySharedRootFieldResolvabilityErrorsParams,
} from './utils/types/params';
import { type EntityAncestorCollection } from './utils/types/types';

export class Graph {
edgeId = -1;
Expand Down Expand Up @@ -250,6 +251,7 @@ export class Graph {
if (rootFieldWalker.unresolvablePaths.size < 1 && !involvesEntities) {
continue;
}

const fieldData = getOrThrowError(rootNode.fieldDataByName, rootFieldName, 'fieldDataByName');
const rootFieldData = newRootFieldData(rootNode.typeName, rootFieldName, fieldData.subgraphNames);
// If there are no nested entities, then the unresolvable fields must be impossible to resolve.
Expand All @@ -263,6 +265,7 @@ export class Graph {
success: false,
};
}

const result = this.validateEntities({ isSharedRootField, rootFieldData, walker: rootFieldWalker });
if (!result.success) {
return result;
Expand Down Expand Up @@ -336,6 +339,11 @@ export class Graph {
validateSharedRootFieldEntities({ rootFieldData, walker }: ValidateEntitiesParams): ValidationResult {
const resolvedPaths = new Set<SelectionPath>();
for (const [pathFromRoot, entityNodeNames] of walker.entityNodeNamesByPath) {
if (walker.unresolvablePaths.size < 1) {
return {
success: true,
};
}
const subgraphNameByUnresolvablePath = new Map<SelectionPath, SubgraphName>();
// Shared fields are unique contexts, so the resolution data cannot be reused.
const resDataByRelativeOriginPath = new Map<SelectionPath, NodeResolutionData>();
Expand All @@ -361,41 +369,33 @@ export class Graph {
subgraphNameByUnresolvablePath,
walker,
});

// Check nothing further needs to be done
if (subgraphNameByUnresolvablePath.size < 1) {
continue;
}

// Only do this if we have to
this.consolidateUnresolvableEntityWithRootPaths({
pathFromRoot,
resDataByRelativeOriginPath,
subgraphNameByUnresolvablePath,
walker,
});
const errors = new Array<Error>();
if (subgraphNameByUnresolvablePath.size > 0) {
errors.push(
...this.getSharedEntityResolvabilityErrors({
entityNodeNames,
resDataByPath: resDataByRelativeOriginPath,
pathFromRoot,
rootFieldData,
subgraphNameByUnresolvablePath,
}),
);
}
if (walker.unresolvablePaths.size > 0) {
errors.push(
...generateRootResolvabilityErrors({
unresolvablePaths: walker.unresolvablePaths,
resDataByPath: walker.resDataByPath,
rootFieldData,
}),
);
}
if (errors.length < 1) {

// Check again before returning an error
if (subgraphNameByUnresolvablePath.size < 1) {
continue;
}

return {
errors,
errors: generateSharedEntityResolvabilityErrors({
entityAncestors: this.getEntityAncestorCollection(entityNodeNames),
pathFromRoot,
resDataByPath: resDataByRelativeOriginPath,
rootFieldData: rootFieldData,
subgraphNameByUnresolvablePath,
}),
success: false,
};
}
Expand Down Expand Up @@ -456,6 +456,7 @@ export class Graph {
success: false,
};
}

return {
success: true,
};
Expand Down Expand Up @@ -498,35 +499,32 @@ export class Graph {
});
}

getSharedEntityResolvabilityErrors({
entityNodeNames,
pathFromRoot,
rootFieldData,
resDataByPath,
subgraphNameByUnresolvablePath,
}: EntitySharedRootFieldResolvabilityErrorsParams): Array<Error> {
let entityTypeName: string | undefined = undefined;
const subgraphNames = new Array<SubgraphName>();
for (const entityNodeName of entityNodeNames) {
const segments = entityNodeName.split(LITERAL_PERIOD);
entityTypeName ??= segments[1];
subgraphNames.push(segments[0]);
}
getEntityAncestorCollection(entityNodeNames: Set<NodeName>): EntityAncestorCollection {
const typeName = getFirstEntry(entityNodeNames)!.split(LITERAL_PERIOD)[1];
const { fieldSetsByTargetSubgraphName } = getOrThrowError(
this.entityDataNodeByTypeName,
entityTypeName,
typeName,
'entityDataNodeByTypeName',
);
return generateSharedEntityResolvabilityErrors({
entityAncestors: {
fieldSetsByTargetSubgraphName,
subgraphNames,
typeName: entityTypeName!,
},
pathFromRoot,
resDataByPath,
rootFieldData: rootFieldData,
subgraphNameByUnresolvablePath,
});
const subgraphNames = new Array<SubgraphName>();
const sourceSubgraphNamesBySatisfiedFieldSet = new Map<string, Array<SubgraphName>>();
for (const entityNodeName of entityNodeNames) {
const { satisfiedFieldSets, subgraphName } = getOrThrowError(
this.nodeByNodeName,
entityNodeName,
'nodeByNodeName',
);
for (const fieldSet of satisfiedFieldSets) {
getValueOrDefault(sourceSubgraphNamesBySatisfiedFieldSet, fieldSet, () => []).push(subgraphName);
}
subgraphNames.push(subgraphName);
}

return {
fieldSetsByTargetSubgraphName,
sourceSubgraphNamesBySatisfiedFieldSet,
subgraphNames,
typeName,
};
}
}
4 changes: 3 additions & 1 deletion composition/src/resolvability-graph/types/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ export type VisitNodeResult = {

export type FieldName = string;

export type FieldCoords = `${TypeName}.${FieldName}`;

export type NodeName = `${SubgraphName}.${TypeName}`;

export type SelectionPath = string;
Expand All @@ -16,7 +18,7 @@ export type SubgraphName = string;
export type TypeName = string;

export type RootFieldData = {
coords: `${TypeName}.${FieldName}`;
coords: FieldCoords;
message: string;
subgraphNames: Set<SubgraphName>;
};
Expand Down
16 changes: 15 additions & 1 deletion composition/src/resolvability-graph/utils/types/params.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { type NodeName, type RootFieldData, type SelectionPath, type SubgraphName } from '../../types/types';
import {
type FieldCoords,
type NodeName,
type RootFieldData,
type SelectionPath,
type SubgraphName,
} from '../../types/types';
import { type NodeResolutionData } from '../../node-resolution-data/node-resolution-data';

import { type EntityAncestorCollection, type EntityAncestorData } from './types';
Expand Down Expand Up @@ -57,3 +63,11 @@ export type GenerateSharedResolvabilityErrorReasonsParams = {
unresolvableFieldData: UnresolvableFieldData;
entityAncestors: EntityAncestorCollection;
};

export type GetEntityReasonsParams = {
coords: FieldCoords;
entityAncestors: EntityAncestorCollection;
fieldSets: Set<string>;
reasons: Array<string>;
targetSubgraphName: SubgraphName;
};
1 change: 1 addition & 0 deletions composition/src/resolvability-graph/utils/types/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export type EntityAncestorData = {

export type EntityAncestorCollection = {
fieldSetsByTargetSubgraphName: Map<SubgraphName, Set<string>>;
sourceSubgraphNamesBySatisfiedFieldSet: Map<string, Array<SubgraphName>>;
subgraphNames: Array<SubgraphName>;
typeName: TypeName;
};
Expand Down
99 changes: 86 additions & 13 deletions composition/src/resolvability-graph/utils/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { unresolvablePathError } from '../../errors/errors';
import { getOrThrowError } from '../../utils/utils';
import { type GraphFieldData } from '../../utils/types';
import {
type FieldCoords,
type FieldName,
type RootFieldData,
type SelectionPath,
Expand All @@ -12,6 +13,7 @@ import {
import {
type GenerateResolvabilityErrorReasonsParams,
type GenerateSharedResolvabilityErrorReasonsParams,
type GetEntityReasonsParams,
type GetMultipliedRelativeOriginPathsParams,
type ResolvabilityErrorsParams,
type RootResolvabilityErrorsParams,
Expand Down Expand Up @@ -124,12 +126,74 @@ export function generateResolvabilityErrorReasons({
return reasons;
}

function getSelfEntityReasons({
entityAncestors: { subgraphNames, typeName },
fieldSets,
reasons,
targetSubgraphName,
}: GetEntityReasonsParams) {
for (const fieldSet of fieldSets) {
const filteredSubgraphNames = subgraphNames.filter((subgraphName) => subgraphName !== targetSubgraphName);
if (filteredSubgraphNames.length < 1) {
continue;
}

const isSubsetPlural = filteredSubgraphNames.length > 1;
reasons.push(
`The entity ancestor${isSubsetPlural ? 's' : ''} "${typeName}" in` +
` subgraph${isSubsetPlural ? `s` : ``} "${filteredSubgraphNames.join(QUOTATION_JOIN)}"` +
` do${isSubsetPlural ? `` : `es`} not satisfy the key field set "${fieldSet}"` +
` to access subgraph "${targetSubgraphName}".`,
);
}
}

function getAncestorEntityReasons({
coords,
entityAncestors: { sourceSubgraphNamesBySatisfiedFieldSet, subgraphNames, typeName },
fieldSets,
reasons,
targetSubgraphName,
}: GetEntityReasonsParams) {
const unsatisfiedFieldSetReasons: Array<string> = [];
const satisfiedFieldSetReasons: Array<string> = [];
for (const fieldSet of fieldSets) {
const sourceSubgraphNames = sourceSubgraphNamesBySatisfiedFieldSet.get(fieldSet);
if (!sourceSubgraphNames) {
const filteredSubgraphNames = subgraphNames.filter((subgraphName) => subgraphName !== targetSubgraphName);
const isSubsetPlural = filteredSubgraphNames.length > 1;
unsatisfiedFieldSetReasons.push(
`The entity ancestor${isSubsetPlural ? 's' : ''} "${typeName}" in` +
` subgraph${isSubsetPlural ? `s` : ``} "${filteredSubgraphNames.join(QUOTATION_JOIN)}" ` +
` do${isSubsetPlural ? `` : `es`} not satisfy the key field set "${fieldSet}"` +
` to access subgraph "${targetSubgraphName}".`,
);
continue;
}

const filteredSubgraphNames = sourceSubgraphNames.filter((subgraphName) => subgraphName !== targetSubgraphName);
if (filteredSubgraphNames.length < 1) {
continue;
}

const isSubsetPlural = filteredSubgraphNames.length > 1;
satisfiedFieldSetReasons.push(
`The entity ancestor "${typeName}" in subgraph${isSubsetPlural ? `s` : ``}` +
` "${filteredSubgraphNames.join(QUOTATION_JOIN)}" ${isSubsetPlural ? 'are' : 'is'} able to satisfy at least` +
` one key field set to access subgraph "${targetSubgraphName}", but this still does not provide a route` +
` to resolve "${coords}".`,
);
}
reasons.push(...(satisfiedFieldSetReasons.length > 0 ? satisfiedFieldSetReasons : unsatisfiedFieldSetReasons));
}

export function generateSharedResolvabilityErrorReasons({
entityAncestors,
rootFieldData,
unresolvableFieldData,
}: GenerateSharedResolvabilityErrorReasonsParams): Array<string> {
const { externalSubgraphNames, fieldName, typeName, subgraphNames } = unresolvableFieldData;
const coords: FieldCoords = `${typeName}.${fieldName}`;
const reasons: Array<string> = [rootFieldData.message];
if (externalSubgraphNames.size > 0) {
const nonExternalSubgraphNames = subgraphNames.difference(externalSubgraphNames);
Expand All @@ -148,38 +212,47 @@ export function generateSharedResolvabilityErrorReasons({
`: "${[...subgraphNames].join(QUOTATION_JOIN)}".`,
);
}
const isEntity = typeName === entityAncestors.typeName;
let hasIntersectingTargetSubgraph = false;
for (const [targetSubgraphName, fieldSets] of entityAncestors.fieldSetsByTargetSubgraphName) {
if (!subgraphNames.has(targetSubgraphName)) {
continue;
}
const filteredSubgraphNames = entityAncestors.subgraphNames.filter(
(subgraphName) => subgraphName !== targetSubgraphName,
);
const isSubsetPlural = filteredSubgraphNames.length > 1;

hasIntersectingTargetSubgraph = true;
for (const fieldSet of fieldSets) {
reasons.push(
`The entity ancestor "${entityAncestors.typeName}" in subgraph${isSubsetPlural ? `s` : ``}` +
` "${filteredSubgraphNames.join(QUOTATION_JOIN)}" do${isSubsetPlural ? `` : `es`} not satisfy` +
` the key field set "${fieldSet}" to access subgraph "${targetSubgraphName}".`,
);
if (isEntity) {
getSelfEntityReasons({
coords,
entityAncestors,
fieldSets,
reasons,
targetSubgraphName,
});
} else {
getAncestorEntityReasons({
coords,
entityAncestors,
fieldSets,
reasons,
targetSubgraphName,
});
}
}
if (!hasIntersectingTargetSubgraph) {
const isPlural = entityAncestors.subgraphNames.length > 1;
reasons.push(
`The entity ancestor "${entityAncestors.typeName}" in subgraph${isPlural ? `s` : ``}` +
` "${entityAncestors.subgraphNames.join(QUOTATION_JOIN)}" ha${isPlural ? `ve` : `s`} no accessible target` +
` entities (resolvable @key directives) in the subgraphs where "${typeName}.${fieldName}" is defined.`,
` "${entityAncestors.subgraphNames.join(QUOTATION_JOIN)}" has no accessible target` +
` entities (resolvable @key directives) in the subgraphs where "${coords}" is defined.`,
);
}
reasons.push(
`The type "${typeName}" is not a descendant of any other entity ancestors that can provide a shared route to access "${fieldName}".`,
);
if (typeName !== entityAncestors.typeName) {
reasons.push(
`The type "${typeName}" has no accessible target entities (resolvable @key directives) in any other subgraph, so accessing other subgraphs is not possible.`,
`The type "${typeName}" has no accessible target entities (resolvable @key directives) in any` +
` other subgraph, so accessing other subgraphs is not possible.`,
);
}
return reasons;
Expand Down
Loading
Loading