Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support entity targets (implicit keys) #724

Merged
merged 1 commit into from
Apr 11, 2024
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
4 changes: 2 additions & 2 deletions aws-lambda-router/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ require (
github.com/akrylysov/algnhsa v1.1.0
github.com/aws/aws-lambda-go v1.43.0
github.com/stretchr/testify v1.9.0
github.com/wundergraph/cosmo/router v0.0.0-20240409135420-d570aea9dbbb
github.com/wundergraph/cosmo/router v0.0.0-20240410195325-1bcf716de716
go.uber.org/zap v1.26.0
)

Expand Down Expand Up @@ -78,7 +78,7 @@ require (
github.com/tidwall/sjson v1.2.5 // indirect
github.com/tklauser/go-sysconf v0.3.12 // indirect
github.com/tklauser/numcpus v0.6.1 // indirect
github.com/wundergraph/graphql-go-tools/v2 v2.0.0-rc.20 // indirect
github.com/wundergraph/graphql-go-tools/v2 v2.0.0-rc.21 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 // indirect
go.opentelemetry.io/contrib/propagators/b3 v1.23.0 // indirect
Expand Down
8 changes: 4 additions & 4 deletions aws-lambda-router/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -271,10 +271,10 @@ github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4d
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/vektah/gqlparser/v2 v2.5.10 h1:6zSM4azXC9u4Nxy5YmdmGu4uKamfwsdKTwp5zsEealU=
github.com/vektah/gqlparser/v2 v2.5.10/go.mod h1:1rCcfwB2ekJofmluGWXMSEnPMZgbxzwj6FaZ/4OT8Cc=
github.com/wundergraph/cosmo/router v0.0.0-20240409135420-d570aea9dbbb h1:kEFM31ybfo4LrOiZ4TmShyWBxF5CW70NQWZ0UL1MT1g=
github.com/wundergraph/cosmo/router v0.0.0-20240409135420-d570aea9dbbb/go.mod h1:EdPBko2KvL63ipXsdw/kpE5mOevHQsuislPH2vKoPec=
github.com/wundergraph/graphql-go-tools/v2 v2.0.0-rc.20 h1:F1qinNuiWvdghY2NhKDsg4kaMbLYe7+56gbVvOXMZOk=
github.com/wundergraph/graphql-go-tools/v2 v2.0.0-rc.20/go.mod h1:hNR2C7S1M+c9Ap24tHCEMe9gFY9K3smX46x5E1U1NQw=
github.com/wundergraph/cosmo/router v0.0.0-20240410195325-1bcf716de716 h1:QcPsOSNAwxrZ1gBnyTaRNz63AwjCZ0qKMyjKuQiKtPw=
github.com/wundergraph/cosmo/router v0.0.0-20240410195325-1bcf716de716/go.mod h1:EdPBko2KvL63ipXsdw/kpE5mOevHQsuislPH2vKoPec=
github.com/wundergraph/graphql-go-tools/v2 v2.0.0-rc.21 h1:eaIfp7Oq718cFebKu4cjDYSdovybxxuwDTc9BL94sLw=
github.com/wundergraph/graphql-go-tools/v2 v2.0.0-rc.21/go.mod h1:hNR2C7S1M+c9Ap24tHCEMe9gFY9K3smX46x5E1U1NQw=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 h1:aFJWCqJMNjENlcleuuOkGAPH82y0yULBScfXcIEdS24=
Expand Down
96 changes: 48 additions & 48 deletions composition-go/index.global.js

Large diffs are not rendered by default.

82 changes: 48 additions & 34 deletions composition/src/federation/federation-factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ import {
DefinitionWithFieldsData,
ObjectDefinitionData,
ParentDefinitionData,
ParentWithFieldsData as NormalizationObjectLikeData,
ParentWithFieldsData,
PersistedDirectiveDefinitionData,
} from '../schema-building/type-definition-data';
import {
Expand Down Expand Up @@ -128,7 +128,7 @@ export class FederationFactory {
persistedDirectiveDefinitions = new Set<string>([AUTHENTICATED, DEPRECATED, INACCESSIBLE, TAG, REQUIRES_SCOPES]);
currentSubgraphName = '';
childName = '';
entityContainersByTypeName: EntityDataByTypeName;
entityDataByTypeName: Map<string, EntityData>;
errors: Error[] = [];
evaluatedObjectLikesBySubgraph = new Map<string, Set<string>>();
graph: MultiGraph;
Expand Down Expand Up @@ -167,7 +167,7 @@ export class FederationFactory {
) {
this.authorizationDataByParentTypeName = authorizationDataByParentTypeName;
this.concreteTypeNamesByAbstractTypeName = concreteTypeNamesByAbstractTypeName;
this.entityContainersByTypeName = entityContainersByTypeName;
this.entityDataByTypeName = entityContainersByTypeName;
this.entityInterfaceFederationDataByTypeName = entityInterfaceFederationDataByTypeName;
this.graph = graph;
this.internalSubgraphBySubgraphName = internalSubgraphBySubgraphName;
Expand Down Expand Up @@ -295,7 +295,7 @@ export class FederationFactory {
if (entityAncestorName === parentTypeName) {
const hasOverlap = doSetsHaveAnyOverlap(
fieldSubgraphs,
getOrThrowError(this.entityContainersByTypeName, entityAncestorName, ENTITIES).subgraphNames,
getOrThrowError(this.entityDataByTypeName, entityAncestorName, ENTITIES).subgraphNames,
);
this.graphPaths.set(path, hasOverlap);
return hasOverlap;
Expand Down Expand Up @@ -402,7 +402,7 @@ export class FederationFactory {
doSetsHaveAnyOverlap(rootTypeFieldData.subgraphs, fieldData.subgraphNames) ||
this.isFieldResolvableByEntityAncestor(entityAncestors, fieldData.subgraphNames, parentTypeName);
const newCurrentFieldPath = currentFieldPath + (isParentAbstract ? ' ' : '.') + fieldName;
const entity = this.entityContainersByTypeName.get(namedFieldTypeName);
const entity = this.entityDataByTypeName.get(namedFieldTypeName);
if (isFieldResolvable) {
// The base scalars are not in this.parentMap
if (BASE_SCALARS.has(namedFieldTypeName)) {
Expand Down Expand Up @@ -530,7 +530,7 @@ export class FederationFactory {
if (!doSetsHaveAnyOverlap(concreteTypeData.subgraphNames, rootTypeFieldData.subgraphs)) {
continue;
}
const entity = this.entityContainersByTypeName.get(concreteTypeName);
const entity = this.entityDataByTypeName.get(concreteTypeName);
this.evaluateResolvabilityOfObject(
concreteTypeData,
rootTypeFieldData,
Expand All @@ -542,25 +542,27 @@ export class FederationFactory {
}
}

validateKeyFieldSetsForImplicitEntity(entityData: EntityData) {
addValidPrimaryKeyTargetsToEntityData(entityData?: EntityData) {
if (!entityData) {
return;
}
const internalSubgraph = getOrThrowError(
this.internalSubgraphBySubgraphName,
this.currentSubgraphName,
'internalSubgraphBySubgraphName',
);
const parentContainerByTypeName = internalSubgraph.parentDefinitionDataByTypeName;
const extensionContainerByTypeName = internalSubgraph.parentExtensionDataByTypeName;
const implicitEntityContainer =
parentContainerByTypeName.get(entityData.typeName) || extensionContainerByTypeName.get(entityData.typeName);
const parentDefinitionDataByTypeName = internalSubgraph.parentDefinitionDataByTypeName;
const parentExtensionDataByTypeName = internalSubgraph.parentExtensionDataByTypeName;
const objectData =
parentDefinitionDataByTypeName.get(entityData.typeName) || parentExtensionDataByTypeName.get(entityData.typeName);
if (
!implicitEntityContainer ||
(implicitEntityContainer.kind !== Kind.OBJECT_TYPE_DEFINITION &&
implicitEntityContainer.kind !== Kind.OBJECT_TYPE_EXTENSION)
!objectData ||
(objectData.kind !== Kind.OBJECT_TYPE_DEFINITION && objectData.kind !== Kind.OBJECT_TYPE_EXTENSION)
) {
throw incompatibleParentKindFatalError(
entityData.typeName,
Kind.OBJECT_TYPE_DEFINITION,
implicitEntityContainer?.kind || Kind.NULL,
objectData?.kind || Kind.NULL,
);
}
const configurationData = getOrThrowError(
Expand All @@ -569,7 +571,7 @@ export class FederationFactory {
'internalSubgraph.configurationDataMap',
);
const keyFieldNames = new Set<string>();
const keys: RequiredFieldConfiguration[] = [];
const implicitKeys: RequiredFieldConfiguration[] = [];
// Any errors in the field sets would be caught when evaluating the explicit entities, so they are ignored here
for (const fieldSet of entityData.keyFieldSets) {
// Create a new selection set so that the value can be parsed as a new DocumentNode
Expand All @@ -578,7 +580,7 @@ export class FederationFactory {
// This would be caught as an error elsewhere
continue;
}
const parentContainers: NormalizationObjectLikeData[] = [implicitEntityContainer];
const parentDatas: ParentWithFieldsData[] = [objectData];
const definedFields: Set<string>[] = [];
let currentDepth = -1;
let shouldDefineSelectionSet = true;
Expand All @@ -594,14 +596,14 @@ export class FederationFactory {
},
Field: {
enter(node) {
const parentContainer = parentContainers[currentDepth];
const parentData = parentDatas[currentDepth];
// If an object-like was just visited, a selection set should have been entered
if (shouldDefineSelectionSet) {
shouldAddKeyFieldSet = false;
return BREAK;
}
const fieldName = node.name.value;
const fieldData = parentContainer.fieldDataByFieldName.get(fieldName);
const fieldData = parentData.fieldDataByFieldName.get(fieldName);
// undefined if the field does not exist on the parent
if (!fieldData || fieldData.argumentDataByArgumentName.size || definedFields[currentDepth].has(fieldName)) {
shouldAddKeyFieldSet = false;
Expand All @@ -619,22 +621,22 @@ export class FederationFactory {
return;
}
// The child could itself be a parent and could exist as an object extension
const childContainer =
parentContainerByTypeName.get(namedTypeName) || extensionContainerByTypeName.get(namedTypeName);
if (!childContainer) {
const fieldNamedTypeData =
parentDefinitionDataByTypeName.get(namedTypeName) || parentExtensionDataByTypeName.get(namedTypeName);
if (!fieldNamedTypeData) {
shouldAddKeyFieldSet = false;
return BREAK;
}
if (
childContainer.kind === Kind.OBJECT_TYPE_DEFINITION ||
childContainer.kind === Kind.OBJECT_TYPE_EXTENSION
fieldNamedTypeData.kind === Kind.OBJECT_TYPE_DEFINITION ||
fieldNamedTypeData.kind === Kind.OBJECT_TYPE_EXTENSION
) {
shouldDefineSelectionSet = true;
parentContainers.push(childContainer);
parentDatas.push(fieldNamedTypeData);
return;
}
// interfaces and unions are invalid in a key directive
if (isKindAbstract(childContainer.kind)) {
if (isKindAbstract(fieldNamedTypeData.kind)) {
shouldAddKeyFieldSet = false;
return BREAK;
}
Expand All @@ -654,7 +656,7 @@ export class FederationFactory {
}
currentDepth += 1;
shouldDefineSelectionSet = false;
if (currentDepth < 0 || currentDepth >= parentContainers.length) {
if (currentDepth < 0 || currentDepth >= parentDatas.length) {
shouldAddKeyFieldSet = false;
return BREAK;
}
Expand All @@ -667,7 +669,7 @@ export class FederationFactory {
}
// Empty selection sets would be a parse error, so it is unnecessary to handle them
currentDepth -= 1;
parentContainers.pop();
parentDatas.pop();
definedFields.pop();
},
},
Expand All @@ -677,15 +679,27 @@ export class FederationFactory {
}
// Add any top-level fields that compose the key in case they are external
addIterableValuesToSet(keyFieldNames, configurationData.fieldNames);
keys.push({
implicitKeys.push({
fieldName: '',
selectionSet: getNormalizedFieldSet(documentNode),
disableEntityResolver: true,
});
}
if (keys.length > 0) {
if (implicitKeys.length < 1) {
return;
}
if (!configurationData.keys || configurationData.keys.length < 1) {
configurationData.isRootNode = true;
configurationData.keys = keys;
configurationData.keys = implicitKeys;
return;
}
const existingKeys = new Set<string>(configurationData.keys.map((key) => key.selectionSet));
for (const implicitKey of implicitKeys) {
if (existingKeys.has(implicitKey.selectionSet)) {
continue;
}
configurationData.keys.push(implicitKey);
existingKeys.add(implicitKey.selectionSet);
}
}

Expand Down Expand Up @@ -838,7 +852,7 @@ export class FederationFactory {
continue;
}
// The subgraph locations of the interface object must be added to the concrete types that implement it
const entity = this.entityContainersByTypeName.get(concreteTypeName);
const entity = this.entityDataByTypeName.get(concreteTypeName);
if (entity) {
// TODO error if not an entity
entity.subgraphNames.add(subgraphName);
Expand Down Expand Up @@ -1243,7 +1257,7 @@ export class FederationFactory {
rootTypeFieldData,
fieldPath,
new Set<string>(),
this.entityContainersByTypeName.has(namedRootFieldTypeName) ? [namedRootFieldTypeName] : [],
this.entityDataByTypeName.has(namedRootFieldTypeName) ? [namedRootFieldTypeName] : [],
);
continue;
case Kind.INTERFACE_TYPE_DEFINITION:
Expand All @@ -1255,7 +1269,7 @@ export class FederationFactory {
rootTypeFieldData,
fieldPath,
new Set<string>(),
this.entityContainersByTypeName.has(namedRootFieldTypeName) ? [namedRootFieldTypeName] : [],
this.entityDataByTypeName.has(namedRootFieldTypeName) ? [namedRootFieldTypeName] : [],
);
continue;
default:
Expand Down
7 changes: 2 additions & 5 deletions composition/src/federation/walkers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export function createMultiGraphAndRenameRootTypes(ff: FederationFactory, subgra
// If the parent node is never an entity, add the child edge
// Otherwise, only add the child edge if the child is a field on a subgraph where the object is an entity
// TODO resolvable false
const entity = ff.entityContainersByTypeName.get(parentTypeName);
const entity = ff.entityDataByTypeName.get(parentTypeName);
if (entity && !entity.fieldNames.has(fieldName)) {
return false;
}
Expand Down Expand Up @@ -99,10 +99,7 @@ export function createMultiGraphAndRenameRootTypes(ff: FederationFactory, subgra
if (ff.entityInterfaceFederationDataByTypeName.get(originalTypeName)) {
return;
}
const entityContainer = ff.entityContainersByTypeName.get(originalTypeName);
if (entityContainer && !parentData.isEntity) {
ff.validateKeyFieldSetsForImplicitEntity(entityContainer);
}
ff.addValidPrimaryKeyTargetsToEntityData(ff.entityDataByTypeName.get(originalTypeName));
overriddenFieldNames = subgraph.overriddenFieldNamesByParentTypeName.get(originalTypeName);
if (originalTypeName === parentTypeName) {
return;
Expand Down
Loading
Loading