UNPKG

@apollo/composition

Version:
292 lines (251 loc) 12.3 kB
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; }