@apollo/composition
Version:
Apollo Federation composition utilities
292 lines (251 loc) • 12.3 kB
text/typescript
import { NamedSchemaElement, SubgraphASTNode } from "@apollo/federation-internals";
import { printLocation } from "graphql";
export enum HintLevel {
WARN = 60,
INFO = 40,
DEBUG = 20,
}
export type HintCodeDefinition = {
code: string,
// Note that we keep the name separately, because while it can be obtained easily enough
// with `HintLevel[value]` on the JS/TS-side, the name would otherwise be lost when
// serializing the related objects to JSON for rover.
level: { value: HintLevel, name: string},
description: string,
}
function makeCodeDefinition({
code,
level,
description,
}: {
code: string,
level: HintLevel,
description: string,
}): HintCodeDefinition {
return ({
code,
level: { value: level, name: HintLevel[level]},
description,
});
}
const INCONSISTENT_BUT_COMPATIBLE_FIELD_TYPE = makeCodeDefinition({
code: 'INCONSISTENT_BUT_COMPATIBLE_FIELD_TYPE',
level: HintLevel.INFO,
description: 'Indicates that a field does not have the exact same types in all subgraphs, but that the types are "compatible"'
+ ' (2 types are compatible if one is a non-nullable version of the other, a list version, a subtype, or a'
+ ' combination of the former).',
});
const INCONSISTENT_BUT_COMPATIBLE_ARGUMENT_TYPE = makeCodeDefinition({
code: 'INCONSISTENT_BUT_COMPATIBLE_ARGUMENT_TYPE',
level: HintLevel.INFO,
description: 'Indicates that an argument type (of a field/input field/directive definition) does not have the exact same type'
+ ' in all subgraphs, but that the types are "compatible" (two types are compatible if one is a non-nullable'
+ ' version of the other, a list version, a subtype, or a combination of the former).',
});
const INCONSISTENT_DEFAULT_VALUE_PRESENCE = makeCodeDefinition({
code: 'INCONSISTENT_DEFAULT_VALUE_PRESENCE',
level: HintLevel.WARN,
description: 'Indicates that an argument definition (of a field/input field/directive definition) has a default value in only'
+ ' some of the subgraphs that define the argument.',
});
const INCONSISTENT_ENTITY = makeCodeDefinition({
code: 'INCONSISTENT_ENTITY',
level: HintLevel.INFO,
description: 'Indicates that an object is declared as an entity (has a `@key`) in only some of the subgraphs in which the object is defined.',
});
const INCONSISTENT_OBJECT_VALUE_TYPE_FIELD = makeCodeDefinition({
code: 'INCONSISTENT_OBJECT_VALUE_TYPE_FIELD',
level: HintLevel.DEBUG,
description: 'Indicates that a field of an object "value type" (has no `@key` in any subgraph) is not defined in all the subgraphs that declare the type.',
});
const INCONSISTENT_INTERFACE_VALUE_TYPE_FIELD = makeCodeDefinition({
code: 'INCONSISTENT_INTERFACE_VALUE_TYPE_FIELD',
level: HintLevel.DEBUG,
description: 'Indicates that a field of an interface "value type" (has no `@key` in any subgraph) is not defined in all the subgraphs that declare the type.',
});
const INCONSISTENT_INPUT_OBJECT_FIELD = makeCodeDefinition({
code: 'INCONSISTENT_INPUT_OBJECT_FIELD',
level: HintLevel.WARN,
description: 'Indicates that a field of an input object type definition is only defined in a subset of the subgraphs that declare the input object.',
});
const INCONSISTENT_UNION_MEMBER = makeCodeDefinition({
code: 'INCONSISTENT_UNION_MEMBER',
level: HintLevel.DEBUG,
description: 'Indicates that a member of a union type definition is only defined in a subset of the subgraphs that declare the union.',
});
const INCONSISTENT_ENUM_VALUE_FOR_INPUT_ENUM = makeCodeDefinition({
code: 'INCONSISTENT_ENUM_VALUE_FOR_INPUT_ENUM',
level: HintLevel.WARN,
description: 'Indicates that a value of an enum type definition (that is only used as an Input type) has not been merged into the supergraph because it is defined in only a subset of the subgraphs that declare the enum',
});
const INCONSISTENT_ENUM_VALUE_FOR_OUTPUT_ENUM = makeCodeDefinition({
code: 'INCONSISTENT_ENUM_VALUE_FOR_OUTPUT_ENUM',
level: HintLevel.DEBUG,
description: 'Indicates that a value of an enum type definition (that is only used as an Output type, or is unused) has been merged in the supergraph but is defined in only a subset of the subgraphs that declare the enum',
});
const INCONSISTENT_TYPE_SYSTEM_DIRECTIVE_REPEATABLE = makeCodeDefinition({
code: 'INCONSISTENT_TYPE_SYSTEM_DIRECTIVE_REPEATABLE',
level: HintLevel.DEBUG,
description: 'Indicates that a type system directive definition is marked repeatable in only a subset of the subgraphs that declare the directive (and will be repeatable in the supergraph).',
});
const INCONSISTENT_TYPE_SYSTEM_DIRECTIVE_LOCATIONS = makeCodeDefinition({
code: 'INCONSISTENT_TYPE_SYSTEM_DIRECTIVE_LOCATIONS',
level: HintLevel.DEBUG,
description: 'Indicates that a type system directive definition is declared with inconsistent locations across subgraphs (and will use the union of all locations in the supergraph).',
});
const INCONSISTENT_EXECUTABLE_DIRECTIVE_PRESENCE = makeCodeDefinition({
code: 'INCONSISTENT_EXECUTABLE_DIRECTIVE_PRESENCE',
level: HintLevel.WARN,
description: 'Indicates that an executable directive definition is declared in only some of the subgraphs.',
});
const NO_EXECUTABLE_DIRECTIVE_LOCATIONS_INTERSECTION = makeCodeDefinition({
code: 'NO_EXECUTABLE_DIRECTIVE_INTERSECTION',
level: HintLevel.WARN,
description: 'Indicates that, for an executable directive definition, no location for it appears in all subgraphs.',
});
const INCONSISTENT_EXECUTABLE_DIRECTIVE_REPEATABLE = makeCodeDefinition({
code: 'INCONSISTENT_EXECUTABLE_DIRECTIVE_REPEATABLE',
level: HintLevel.WARN,
description: 'Indicates that an executable directive definition is marked repeatable in only a subset of the subgraphs (and will not be repeatable in the supergraph).',
});
const INCONSISTENT_EXECUTABLE_DIRECTIVE_LOCATIONS = makeCodeDefinition({
code: 'INCONSISTENT_EXECUTABLE_DIRECTIVE_LOCATIONS',
level: HintLevel.WARN,
description: 'Indicates that an executiable directive definition is declared with inconsistent locations across subgraphs (and will use the intersection of all locations in the supergraph).',
});
const INCONSISTENT_DESCRIPTION = makeCodeDefinition({
code: 'INCONSISTENT_DESCRIPTION',
level: HintLevel.WARN,
description: 'Indicates that an element has a description in more than one subgraph, and the descriptions are not equal.',
});
const INCONSISTENT_ARGUMENT_PRESENCE = makeCodeDefinition({
code: 'INCONSISTENT_ARGUMENT_PRESENCE',
level: HintLevel.WARN,
description: 'Indicates that an optional argument (of a field or directive definition) is not present in all subgraphs and will not be part of the supergraph.',
});
const FROM_SUBGRAPH_DOES_NOT_EXIST = makeCodeDefinition({
code: 'FROM_SUBGRAPH_DOES_NOT_EXIST',
level: HintLevel.WARN,
description: 'Source subgraph specified by @override directive does not exist',
});
const OVERRIDDEN_FIELD_CAN_BE_REMOVED = makeCodeDefinition({
code: 'OVERRIDDEN_FIELD_CAN_BE_REMOVED',
level: HintLevel.INFO,
description: 'Field has been overridden by another subgraph. Consider removing.',
});
const OVERRIDE_DIRECTIVE_CAN_BE_REMOVED = makeCodeDefinition({
code: 'OVERRIDE_DIRECTIVE_CAN_BE_REMOVED',
level: HintLevel.INFO,
description: 'Field with @override directive no longer exists in source subgraph, the directive can be safely removed',
});
const OVERRIDE_MIGRATION_IN_PROGRESS = makeCodeDefinition({
code: 'OVERRIDE_MIGRATION_IN_PROGRESS',
level: HintLevel.INFO,
description: 'Field is currently being migrated with progressive @override. Once the migration is complete, remove the field from the original subgraph.',
});
const UNUSED_ENUM_TYPE = makeCodeDefinition({
code: 'UNUSED_ENUM_TYPE',
level: HintLevel.DEBUG,
description: 'Indicates that an enum type is defined in some subgraphs but is unused (no field/argument references it). All the values from subgraphs defining that enum will be included in the supergraph.',
});
const INCONSISTENT_NON_REPEATABLE_DIRECTIVE_ARGUMENTS = makeCodeDefinition({
code: 'INCONSISTENT_NON_REPEATABLE_DIRECTIVE_ARGUMENTS',
level: HintLevel.WARN,
description: 'A non-repeatable directive is applied to a schema element in different subgraphs but with arguments that are different.',
});
const MERGED_NON_REPEATABLE_DIRECTIVE_ARGUMENTS = makeCodeDefinition({
code: 'MERGED_NON_REPEATABLE_DIRECTIVE_ARGUMENTS',
level: HintLevel.INFO,
description: 'A non-repeatable directive has been applied to a schema element in different subgraphs with different arguments and the arguments values were merged using the directive configured strategies.',
});
const DIRECTIVE_COMPOSITION_INFO = makeCodeDefinition({
code: 'DIRECTIVE_COMPOSITION_INFO',
level: HintLevel.INFO,
description: 'Indicates that an issue was detected when composing custom directives.',
});
const DIRECTIVE_COMPOSITION_WARN = makeCodeDefinition({
code: 'DIRECTIVE_COMPOSITION_WARN',
level: HintLevel.WARN,
description: 'Indicates that an issue was detected when composing custom directives.',
});
const INCONSISTENT_RUNTIME_TYPES_FOR_SHAREABLE_RETURN = makeCodeDefinition({
code: 'INCONSISTENT_RUNTIME_TYPES_FOR_SHAREABLE_RETURN',
level: HintLevel.WARN,
description: 'Indicates that a @shareable field returns different sets of runtime types in the different subgraphs in which it is defined.',
});
const IMPLICITLY_UPGRADED_FEDERATION_VERSION = makeCodeDefinition({
code: 'IMPLICITLY_UPGRADED_FEDERATION_VERSION',
level: HintLevel.INFO,
description: 'Indicates that a directive requires a higher federation version than is explicitly linked.'
+ ' In this case, the supergraph uses the federation version required by the directive.'
});
const CONTEXTUAL_ARGUMENT_NOT_CONTEXTUAL_IN_ALL_SUBGRAPHS = makeCodeDefinition({
code: 'CONTEXTUAL_ARGUMENT_NOT_CONTEXTUAL_IN_ALL_SUBGRAPHS',
level: HintLevel.INFO,
description: 'Indicates that the argument will not be present in the supergraph because it is contextual in at least one subgraph.'
});
export const HINTS = {
INCONSISTENT_BUT_COMPATIBLE_FIELD_TYPE,
INCONSISTENT_BUT_COMPATIBLE_ARGUMENT_TYPE,
INCONSISTENT_DEFAULT_VALUE_PRESENCE,
INCONSISTENT_ENTITY,
INCONSISTENT_OBJECT_VALUE_TYPE_FIELD,
INCONSISTENT_INTERFACE_VALUE_TYPE_FIELD,
INCONSISTENT_INPUT_OBJECT_FIELD,
INCONSISTENT_UNION_MEMBER,
INCONSISTENT_ENUM_VALUE_FOR_INPUT_ENUM,
INCONSISTENT_ENUM_VALUE_FOR_OUTPUT_ENUM,
INCONSISTENT_TYPE_SYSTEM_DIRECTIVE_REPEATABLE,
INCONSISTENT_TYPE_SYSTEM_DIRECTIVE_LOCATIONS,
INCONSISTENT_EXECUTABLE_DIRECTIVE_PRESENCE,
NO_EXECUTABLE_DIRECTIVE_LOCATIONS_INTERSECTION,
INCONSISTENT_EXECUTABLE_DIRECTIVE_REPEATABLE,
INCONSISTENT_EXECUTABLE_DIRECTIVE_LOCATIONS,
INCONSISTENT_DESCRIPTION,
INCONSISTENT_ARGUMENT_PRESENCE,
FROM_SUBGRAPH_DOES_NOT_EXIST,
OVERRIDDEN_FIELD_CAN_BE_REMOVED,
OVERRIDE_DIRECTIVE_CAN_BE_REMOVED,
OVERRIDE_MIGRATION_IN_PROGRESS,
UNUSED_ENUM_TYPE,
INCONSISTENT_NON_REPEATABLE_DIRECTIVE_ARGUMENTS,
MERGED_NON_REPEATABLE_DIRECTIVE_ARGUMENTS,
DIRECTIVE_COMPOSITION_INFO,
DIRECTIVE_COMPOSITION_WARN,
INCONSISTENT_RUNTIME_TYPES_FOR_SHAREABLE_RETURN,
IMPLICITLY_UPGRADED_FEDERATION_VERSION,
CONTEXTUAL_ARGUMENT_NOT_CONTEXTUAL_IN_ALL_SUBGRAPHS,
}
export class CompositionHint {
public readonly nodes?: readonly SubgraphASTNode[];
public readonly coordinate?: string;
constructor(
readonly definition: HintCodeDefinition,
readonly message: string,
readonly element: NamedSchemaElement<any, any, any> | undefined,
nodes?: readonly SubgraphASTNode[] | SubgraphASTNode
) {
this.nodes = nodes
? (Array.isArray(nodes) ? (nodes.length === 0 ? undefined : nodes) : [nodes])
: undefined;
this.coordinate = element?.coordinate;
}
toString(): string {
return `[${this.definition.code}]: ${this.message}`
}
}
/**
* Prints a composition hint to a string, alongside useful location information
* about relevant positions in the subgraph sources.
*/
export function printHint(hint: CompositionHint): string {
let output = hint.toString();
if (hint.nodes) {
for (const node of hint.nodes) {
if (node.loc) {
output += '\n\n' + printLocation(node.loc);
}
}
}
return output;
}