Skip to content
Closed
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
37 changes: 37 additions & 0 deletions composition/src/errors/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,43 @@ import {
type TypeName,
} from '../types/types';

// @composeDirective(name: "myDirective") — missing @ prefix
export function composeDirectiveNameMissingAtPrefixError(name: string): Error {
return new Error(
`The name argument of "@composeDirective" must start with "@", but received "${name}".`
);
}

// @composeDirective(name: "@unknownDirective") — directive not defined
export function undefinedComposeDirectiveNameError(name: string): Error {
return new Error(
`The directive "@${name}" declared in "@composeDirective" is not defined in this subgraph.`
);
}

// @composeDirective(name: "@key") — built-in federation directive
export function composeDirectiveBuiltInError(name: string): Error {
return new Error(
`The directive "@${name}" is a built-in federation directive and cannot be used with "@composeDirective".`
);
}

// @composeDirective — two subgraphs define the directive with disjoint location sets
export function composeDirectiveNoMutualLocationsError(name: string, subgraphNames: Set<string>): Error {
return new Error(
`The composed directive "@${name}" has no mutually supported locations across subgraphs: [${[...subgraphNames].join(', ')}].` +
` All subgraphs that define a composed directive must share at least one location.`,
);
}

// @composeDirective with conflicting repeatable declarations across subgraphs
export function composeDirectiveRepeatableConflictError(name: string, subgraphNames: Set<string>): Error {
return new Error(
`The composed directive "@${name}" has conflicting "repeatable" declarations across subgraphs: [${[...subgraphNames].join(', ')}].` +
` All subgraphs that define a composed directive must agree on whether it is repeatable.`,
);
}

export const minimumSubgraphRequirementError = new Error('At least one subgraph is required for federation.');

export function multipleNamedTypeDefinitionError(
Expand Down
1 change: 1 addition & 0 deletions composition/src/normalization/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export type NormalizationFailure = {

export type NormalizationSuccess = {
authorizationDataByParentTypeName: Map<string, AuthorizationData>;
composedDirectiveDefinitionDataByDirectiveName: Map<DirectiveName, PersistedDirectiveDefinitionData>;
concreteTypeNamesByAbstractTypeName: Map<string, Set<string>>;
conditionalFieldDataByCoordinates: Map<string, ConditionalFieldData>;
configurationDataByTypeName: Map<TypeName, ConfigurationData>;
Expand Down
1 change: 1 addition & 0 deletions composition/src/schema-building/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@ export type ObjectDefinitionData = {
export type PersistedDirectiveDefinitionData = {
argumentDataByName: Map<string, InputValueData>;
executableLocations: Set<string>;
locations?: Set<string>;
name: DirectiveName;
repeatable: boolean;
subgraphNames: Set<SubgraphName>;
Expand Down
51 changes: 39 additions & 12 deletions composition/src/schema-building/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,12 @@ import {
type PersistedDirectivesData,
type SchemaData,
} from './types';
import { type MutableDefinitionNode, type MutableFieldNode, type MutableInputValueNode } from './ast';
import {
type MutableDefinitionNode,
type MutableDirectiveDefinitionNode,
type MutableFieldNode,
type MutableInputValueNode,
} from './ast';
import { type ObjectTypeNode, setToNameNodeArray, stringToNameNode } from '../ast/utils';
import {
incompatibleInputValueDefaultValuesError,
Expand Down Expand Up @@ -439,7 +444,10 @@ function getRouterPersistedDirectiveNodes<T extends NodeData>(
return persistedDirectiveNodes;
}

export function getClientPersistedDirectiveNodes<T extends NodeData>(nodeData: T): ConstDirectiveNode[] {
export function getClientPersistedDirectiveNodes<T extends NodeData>(
nodeData: T,
composedDirectiveNames?: ReadonlySet<string>,
): ConstDirectiveNode[] {
const persistedDirectiveNodes: Array<ConstDirectiveNode> = [];
if (nodeData.persistedDirectivesData.isDeprecated) {
persistedDirectiveNodes.push(generateDeprecatedDirective(nodeData.persistedDirectivesData.deprecatedReason));
Expand All @@ -451,6 +459,11 @@ export function getClientPersistedDirectiveNodes<T extends NodeData>(nodeData: T
);
continue;
}
if (composedDirectiveNames?.has(directiveName)) {
// Composed directives may be repeatable; all validated nodes are safe to emit.
persistedDirectiveNodes.push(...directiveNodes);
continue;
}
// Only include @deprecated, @oneOf, and @semanticNonNull in the client schema.
if (!PERSISTED_CLIENT_DIRECTIVES.has(directiveName)) {
continue;
Expand All @@ -463,16 +476,19 @@ export function getClientPersistedDirectiveNodes<T extends NodeData>(nodeData: T
return persistedDirectiveNodes;
}

export function getClientSchemaFieldNodeByFieldData(fieldData: FieldData): MutableFieldNode {
const directives = getClientPersistedDirectiveNodes(fieldData);
export function getClientSchemaFieldNodeByFieldData(
fieldData: FieldData,
composedDirectiveNames?: ReadonlySet<string>,
): MutableFieldNode {
const directives = getClientPersistedDirectiveNodes(fieldData, composedDirectiveNames);
const argumentNodes: MutableInputValueNode[] = [];
for (const inputValueData of fieldData.argumentDataByName.values()) {
if (isNodeDataInaccessible(inputValueData)) {
continue;
}
argumentNodes.push({
...inputValueData.node,
directives: getClientPersistedDirectiveNodes(inputValueData),
directives: getClientPersistedDirectiveNodes(inputValueData, composedDirectiveNames),
});
}
return {
Expand Down Expand Up @@ -540,24 +556,35 @@ function addValidatedArgumentNodes(
return true;
}

export function addValidPersistedDirectiveDefinitionNodeByData(
definitions: (MutableDefinitionNode | DefinitionNode)[],
export function buildValidPersistedDirectiveDefinitionNode(
data: PersistedDirectiveDefinitionData,
persistedDirectiveDefinitionByDirectiveName: Map<DirectiveName, DirectiveDefinitionNode>,
errors: Error[],
) {
): MutableDirectiveDefinitionNode | undefined {
const argumentNodes: MutableInputValueNode[] = [];
if (!addValidatedArgumentNodes(argumentNodes, data, persistedDirectiveDefinitionByDirectiveName, errors)) {
return;
return undefined;
}
definitions.push({
return {
arguments: argumentNodes,
kind: Kind.DIRECTIVE_DEFINITION,
locations: setToNameNodeArray(data.executableLocations),
locations: setToNameNodeArray(data.locations ?? data.executableLocations),
name: stringToNameNode(data.name),
repeatable: data.repeatable,
description: data.description,
});
};
}

export function addValidPersistedDirectiveDefinitionNodeByData(
definitions: (MutableDefinitionNode | DefinitionNode)[],
data: PersistedDirectiveDefinitionData,
persistedDirectiveDefinitionByDirectiveName: Map<DirectiveName, DirectiveDefinitionNode>,
errors: Error[],
) {
const node = buildValidPersistedDirectiveDefinitionNode(data, persistedDirectiveDefinitionByDirectiveName, errors);
if (node) {
definitions.push(node);
}
}

type InvalidFieldNames = {
Expand Down
1 change: 1 addition & 0 deletions composition/src/subgraph/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export type SubgraphConfig = {
};

export type InternalSubgraph = {
composedDirectiveDefinitionDataByDirectiveName: Map<DirectiveName, PersistedDirectiveDefinitionData>;
conditionalFieldDataByCoordinates: Map<string, ConditionalFieldData>;
configurationDataByTypeName: Map<TypeName, ConfigurationData>;
definitions: DocumentNode;
Expand Down
3 changes: 1 addition & 2 deletions composition/src/v1/constants/directive-definitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,7 @@ export const AUTHENTICATED_DEFINITION: DirectiveDefinitionNode = {
repeatable: false,
};

// @composeDirective is currently unimplemented
/* directive @composeDirective(name: String!) repeatable on SCHEMA */
// directive @composeDirective(name: String!) repeatable on SCHEMA
export const COMPOSE_DIRECTIVE_DEFINITION: DirectiveDefinitionNode = {
arguments: [
{
Expand Down
Loading
Loading