Skip to content

Commit

Permalink
chore: support abstract type field coercion (wundergraph#1229)
Browse files Browse the repository at this point in the history
  • Loading branch information
Aenimus authored Oct 14, 2024
1 parent 5d67c4b commit 3984d9a
Show file tree
Hide file tree
Showing 10 changed files with 1,905 additions and 395 deletions.
440 changes: 223 additions & 217 deletions composition-go/index.global.js

Large diffs are not rendered by default.

52 changes: 41 additions & 11 deletions composition/src/ast/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
EnumTypeDefinitionNode,
EnumTypeExtensionNode,
FieldNode,
InlineFragmentNode,
InputObjectTypeDefinitionNode,
InputObjectTypeExtensionNode,
InterfaceTypeDefinitionNode,
Expand All @@ -19,6 +20,7 @@ import {
ScalarTypeExtensionNode,
SchemaDefinitionNode,
SchemaExtensionNode,
SelectionNode,
SelectionSetNode,
StringValueNode,
TypeDefinitionNode,
Expand All @@ -42,6 +44,7 @@ import {
INTERFACE_UPPER,
KEY,
MUTATION,
NAME,
OBJECT_UPPER,
QUERY,
SCALAR_UPPER,
Expand Down Expand Up @@ -231,27 +234,54 @@ export function formatDescription(description?: StringValueNode): StringValueNod
return { ...description, value: value, block: true };
}

export function lexicographicallySortArgumentNodes(fieldNode: FieldNode): ArgumentNode[] | undefined {
export function lexicographicallySortArgumentNodes(fieldNode: FieldNode): Array<ArgumentNode> | undefined {
if (!fieldNode.arguments) {
return fieldNode.arguments;
}
const argumentNodes = fieldNode.arguments as ArgumentNode[];
const argumentNodes = fieldNode.arguments as Array<ArgumentNode>;
return argumentNodes.sort((a, b) => a.name.value.localeCompare(b.name.value));
}

export function lexicographicallySortSelectionSetNode(selectionSetNode: SelectionSetNode): SelectionSetNode {
const selections = selectionSetNode.selections as FieldNode[];
const selections = selectionSetNode.selections as Array<SelectionNode>;
return {
...selectionSetNode,
selections: selections
.sort((a, b) => a.name.value.localeCompare(b.name.value))
.map((selection) => ({
...selection,
arguments: lexicographicallySortArgumentNodes(selection),
selectionSet: selection.selectionSet
? lexicographicallySortSelectionSetNode(selection.selectionSet)
: selection.selectionSet,
})),
.sort((a, b) => {
if (NAME in a) {
if (!(NAME in b)) {
return -1;
}
return a.name.value.localeCompare(b.name.value);
}
if (NAME in b) {
return 1;
}
const aName = a.typeCondition?.name.value ?? '';
return aName.localeCompare(b.typeCondition?.name.value ?? '');
})
.map((selection) => {
switch (selection.kind) {
case Kind.FIELD: {
return {
...selection,
arguments: lexicographicallySortArgumentNodes(selection),
selectionSet: selection.selectionSet
? lexicographicallySortSelectionSetNode(selection.selectionSet)
: selection.selectionSet,
};
}
case Kind.FRAGMENT_SPREAD: {
return selection;
}
case Kind.INLINE_FRAGMENT: {
return {
...selection,
selectionSet: lexicographicallySortSelectionSetNode(selection.selectionSet),
};
}
}
}),
};
}

Expand Down
73 changes: 64 additions & 9 deletions composition/src/errors/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ export function duplicateEnumValueDefinitionError(enumTypeName: string, valueNam
}

export function duplicateFieldDefinitionError(typeString: string, typeName: string, fieldName: string): Error {
return new Error(`The ${typeString} "${typeName}" must only define Field definition "${fieldName}" once.`);
return new Error(`The ${typeString} "${typeName}" must only define the Field definition "${fieldName}" once.`);
}

export function duplicateInputFieldDefinitionError(inputObjectTypeName: string, fieldName: string): Error {
Expand All @@ -172,20 +172,22 @@ export function duplicateOperationTypeDefinitionError(
oldTypeName: string,
): Error {
return new Error(
`The operation type "${operationTypeName}" cannot be defined as "${newTypeName}" because it has already been defined as "${oldTypeName}".`,
`The operation type "${operationTypeName}" cannot be defined as "${newTypeName}"` +
` because it has already been defined as "${oldTypeName}".`,
);
}

export function noBaseDefinitionForExtensionError(typeString: string, typeName: string): Error {
return new Error(
`The ${typeString} "${typeName}" is an extension,` +
` but there is no base ${typeString} definition of "${typeName}" in any subgraph.`,
` but no base ${typeString} definition of "${typeName}" is defined in any subgraph.`,
);
}

export function noBaseScalarDefinitionError(typeName: string): Error {
return new Error(
`The scalar extension "${typeName}" is invalid because no base definition is defined in the subgraph.`,
`The Scalar extension "${typeName}" is invalid because no base Scalar definition` +
` of "${typeName} is defined in the subgraph.`,
);
}

Expand Down Expand Up @@ -769,10 +771,10 @@ export function invalidConfigurationDataErrorMessage(typeName: string, fieldName
);
}

export function unknownProvidedObjectErrorMessage(fieldPath: string, responseType: string): string {
export function incompatibleTypeWithProvidesErrorMessage(fieldCoordinates: string, responseType: string): string {
return (
` A @provides directive is declared on "${fieldPath}".\n` +
` However, the response type "${responseType}" object or object extension definition was not found.`
` A "@provides" directive is declared on Field "${fieldCoordinates}".\n` +
` However, the response type "${responseType}" is not an Object not Interface.`
);
}

Expand Down Expand Up @@ -1566,10 +1568,63 @@ export function nonExternalConditionalFieldError(
fieldSetDirective: FieldSetDirective,
): Error {
return new Error(
`The field "${originCoords}" in subgraph "${subgraphName}" defines a "@${fieldSetDirective}" directive with the following` +
` field set:\n "${fieldSet}".` +
`The Field "${originCoords}" in subgraph "${subgraphName}" defines a "@${fieldSetDirective}" directive` +
` with the following field set:\n "${fieldSet}".` +
`\nHowever, neither the field "${targetCoords}" nor any of its field set ancestors are declared @external.` +
`\nConsequently, "${targetCoords}" is already provided by subgraph "${subgraphName}" and should not form part of` +
` a "@${fieldSetDirective}" directive field set.`,
);
}

export function incompatibleFederatedFieldNamedTypeError(
fieldCoordinates: string,
subgraphNamesByNamedTypeName: Map<string, Set<string>>,
): Error {
const instances: Array<string> = [];
for (const [namedTypeName, subgraphNames] of subgraphNamesByNamedTypeName) {
const names = [...subgraphNames];
instances.push(
` The Named Type "${namedTypeName}" is returned by the following subgraph` +
(names.length > 1 ? `s` : ``) +
`: "` +
names.join(QUOTATION_JOIN) +
`".`,
);
}
return new Error(
`Each instance of a shared Field must resolve identically across subgraphs.\n` +
`The Field "${fieldCoordinates}" could not be federated due to incompatible types across subgraphs.\n` +
`The discrepancies are as follows:\n` +
instances.join(`\n`),
);
}

export function unknownNamedTypeErrorMessage(fieldCoordinates: string, namedTypeName: string): string {
return `The Field "${fieldCoordinates}" returns the unknown named type "${namedTypeName}".`;
}

export function unknownNamedTypeError(fieldCoordinates: string, namedTypeName: string): Error {
return new Error(unknownNamedTypeErrorMessage(fieldCoordinates, namedTypeName));
}

export function unknownFieldDataError(fieldCoordinates: string): Error {
return new Error(
`Could not find FieldData for Field "${fieldCoordinates}"\n.` +
`This should never happen. Please report this issue on GitHub.`,
);
}

export function unexpectedNonCompositeOutputTypeError(namedTypeName: string, actualTypeString: string): Error {
return new Error(
`Expected named type "${namedTypeName}" to be a composite output type (Object or Interface)` +
` but received "${actualTypeString}".\nThis should never happen. Please report this issue on GitHub.`,
);
}

// TODO Temporarily only used as a warning
export function unimplementedInterfaceOutputTypeError(interfaceTypeName: string): Error {
return new Error(
`The Interface "${interfaceTypeName}" is used as an output type` +
` without at least one Object type implementation defined in the schema.`,
);
}
Loading

0 comments on commit 3984d9a

Please sign in to comment.