Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
338 changes: 170 additions & 168 deletions composition-go/index.global.js

Large diffs are not rendered by default.

45 changes: 44 additions & 1 deletion composition/src/errors/errors.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
import { Kind, OperationTypeNode } from 'graphql';
import { EntityInterfaceFederationData, InputValueData, ObjectDefinitionData } from '../schema-building/types';
import {
EntityInterfaceFederationData,
FieldData,
InputValueData,
ObjectDefinitionData,
} from '../schema-building/types';
import {
IncompatibleMergedTypesErrorParams,
InvalidNamedTypeErrorParams,
InvalidRootTypeFieldEventsDirectiveData,
SemanticNonNullLevelsIndexOutOfBoundsErrorParams,
SemanticNonNullLevelsNonNullErrorParams,
} from './types';
import { UnresolvableFieldData } from '../resolvability-graph/utils';
import {
Expand Down Expand Up @@ -1615,3 +1622,39 @@ export function invalidNamedTypeError({ data, namedTypeData, nodeType }: Invalid
' type.',
);
}

export function semanticNonNullLevelsNaNIndexErrorMessage(value: string) {
return `Index "${value}" is not a valid integer.`;
}

export function semanticNonNullLevelsIndexOutOfBoundsErrorMessage({
maxIndex,
typeString,
value,
}: SemanticNonNullLevelsIndexOutOfBoundsErrorParams) {
return (
`Index "${value}" is out of bounds for type ${typeString}; ` +
(maxIndex > 0 ? `valid indices are 0-${maxIndex} inclusive.` : `the only valid index is 0.`)
);
}

export function semanticNonNullLevelsNonNullErrorMessage({
typeString,
value,
}: SemanticNonNullLevelsNonNullErrorParams) {
return `Index "${value}" of type ${typeString} is non-null but must be nullable.`;
}

export function semanticNonNullInconsistentLevelsError(data: FieldData): Error {
const coords = `${data.renamedParentTypeName}.${data.name}`;
let message =
`The "@semanticNonNull" directive defined on field "${coords}"` +
` is invalid due to inconsistent values provided to the "levels" argument across the following subgraphs:\n`;
for (const [subgraphName, levels] of data.nullLevelsBySubgraphName) {
message += ` Subgraph "${subgraphName}" defines levels ${Array.from(levels).sort((a, b) => a - b)}.\n`;
}
message +=
`The list value provided to the "levels" argument must be consistently defined across all subgraphs that` +
` define "@semanticNonNull" on field "${coords}".`;
return new Error(message);
}
11 changes: 11 additions & 0 deletions composition/src/errors/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,14 @@ export type InvalidNamedTypeErrorParams = {
namedTypeData: ParentDefinitionData;
nodeType: string;
};

export type SemanticNonNullLevelsIndexOutOfBoundsErrorParams = {
maxIndex: number;
typeString: string;
value: string;
};

export type SemanticNonNullLevelsNonNullErrorParams = {
typeString: string;
value: string;
};
1 change: 1 addition & 0 deletions composition/src/schema-building/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ export type FieldData = {
namedTypeKind: OutputNodeKind | Kind.NULL;
namedTypeName: string;
node: MutableFieldNode;
nullLevelsBySubgraphName: Map<SubgraphName, Set<number>>;
originalParentTypeName: string;
persistedDirectivesData: PersistedDirectivesData;
renamedParentTypeName: string;
Expand Down
27 changes: 21 additions & 6 deletions composition/src/schema-building/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {
ParentDefinitionData,
PersistedDirectiveDefinitionData,
PersistedDirectivesData,
SchemaData,
} from './types';
import { MutableFieldNode, MutableInputValueNode, MutableTypeDefinitionNode } from './ast';
import { ObjectTypeNode, setToNameNodeArray, stringToNameNode } from '../ast/utils';
Expand All @@ -57,18 +58,26 @@ import {
INPUT_NODE_KINDS,
INT_SCALAR,
MUTATION,
NON_REPEATABLE_PERSISTED_DIRECTIVES,
OUTPUT_NODE_KINDS,
PERSISTED_CLIENT_DIRECTIVES,
QUERY,
REASON,
REQUIRES_SCOPES,
ROOT_TYPE_NAMES,
SEMANTIC_NON_NULL,
SHAREABLE,
STRING_SCALAR,
SUBSCRIPTION,
TAG,
} from '../utils/string-constants';
import { generateRequiresScopesDirective, generateSimpleDirective, getEntriesNotInHashSet } from '../utils/utils';
import {
generateRequiresScopesDirective,
generateSemanticNonNullDirective,
generateSimpleDirective,
getEntriesNotInHashSet,
getFirstEntry,
} from '../utils/utils';
import { InputNodeKind, InvalidRequiredInputValueData, OutputNodeKind } from '../utils/types';
import { getDescriptionFromString } from '../v1/federation/utils';
import { SubgraphName } from '../types/types';
Expand Down Expand Up @@ -325,8 +334,8 @@ export function extractPersistedDirectives(
persistedDirectivesData.directivesByDirectiveName.set(directiveName, [...directiveNodes]);
continue;
}
// Only add one instance of the @inaccessible directive
if (directiveName === INACCESSIBLE) {
// Only add one instance of certain directives.
if (NON_REPEATABLE_PERSISTED_DIRECTIVES.has(directiveName)) {
continue;
}
existingDirectives.push(...directiveNodes);
Expand Down Expand Up @@ -438,11 +447,17 @@ export function getClientPersistedDirectiveNodes<T extends NodeData>(nodeData: T
persistedDirectiveNodes.push(generateDeprecatedDirective(nodeData.persistedDirectivesData.deprecatedReason));
}
for (const [directiveName, directiveNodes] of nodeData.persistedDirectivesData.directivesByDirectiveName) {
// Only include @deprecated, @authenticated, and @requiresScopes in the client schema
if (directiveName === SEMANTIC_NON_NULL && isFieldData(nodeData)) {
persistedDirectiveNodes.push(
generateSemanticNonNullDirective(getFirstEntry(nodeData.nullLevelsBySubgraphName) ?? new Set<number>([0])),
);
continue;
}
// Only include @deprecated and @semanticNonNull in the client schema.
Comment thread
Aenimus marked this conversation as resolved.
if (!PERSISTED_CLIENT_DIRECTIVES.has(directiveName)) {
continue;
}
/* Persisted client-facing directives or all non-repeatable.
/* Persisted client-facing directives are all non-repeatable.
** The directive is validated against the definition when creating the router schema node, so it is not necessary
** to validate again. */
persistedDirectiveNodes.push(directiveNodes[0]);
Expand Down Expand Up @@ -761,7 +776,7 @@ export function areKindsEqual<T extends ParentDefinitionData>(a: T, b: ParentDef
return a.kind === b.kind;
}

export function isFieldData(data: ChildData): data is FieldData {
export function isFieldData(data: ChildData | NodeData | SchemaData): data is FieldData {
return data.kind === Kind.FIELD_DEFINITION;
}

Expand Down
2 changes: 2 additions & 0 deletions composition/src/types/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
export type ContractName = string;

export type DirectiveName = string;

export type FieldName = string;

export type FieldCoords = string;
Expand Down
15 changes: 10 additions & 5 deletions composition/src/utils/string-constants.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Kind } from 'graphql';
import { DirectiveName } from '../types/types';

export const AS = 'as';
export const AND_UPPER = 'AND';
Expand All @@ -8,10 +9,13 @@ export const AUTHENTICATED = 'authenticated';
export const ARGUMENT_DEFINITION_UPPER = 'ARGUMENT_DEFINITION';
export const BOOLEAN = 'boolean';
export const BOOLEAN_SCALAR = 'Boolean';
export const CHANNEL = 'channel';
export const CHANNELS = 'channels';
export const COMPOSE_DIRECTIVE = 'composeDirective';
export const CONDITION = 'condition';
export const CONFIGURE_DESCRIPTION = 'openfed__configureDescription';
export const CONFIGURE_CHILD_DESCRIPTIONS = 'openfed__configureChildDescriptions';
export const CONSUMER_INACTIVE_THRESHOLD = 'consumerInactiveThreshold';
export const CONSUMER_NAME = 'consumerName';
export const DEFAULT = 'default';
export const DEFAULT_EDFS_PROVIDER_ID = 'default';
Expand Down Expand Up @@ -73,6 +77,7 @@ export const INTERFACE_UPPER = 'INTERFACE';
export const INTERFACE_OBJECT = 'interfaceObject';
export const KEY = 'key';
export const LEFT_PARENTHESIS = '(';
export const LEVELS = 'levels';
export const LINK = 'link';
export const LINK_IMPORT = 'link__Import';
export const LINK_PURPOSE = 'link__Purpose';
Expand Down Expand Up @@ -103,7 +108,6 @@ export const PARENT_DEFINITION_DATA = 'parentDefinitionDataByTypeName';
export const PARENT_DEFINITION_DATA_MAP = 'parentDefinitionDataByParentTypeName';
export const PARENT_EXTENSION_DATA_MAP = 'parentExtensionDataByParentTypeName';
export const PERIOD = '.';
export const REQUIRE_FETCH_REASONS = 'openfed__requireFetchReasons';
export const PROVIDER_ID = 'providerId';
export const PROVIDES = 'provides';
export const PUBLISH = 'publish';
Expand All @@ -112,6 +116,7 @@ export const QUERY_UPPER = 'QUERY';
export const QUOTATION_JOIN = `", "`;
export const REASON = 'reason';
export const REQUEST = 'request';
export const REQUIRE_FETCH_REASONS = 'openfed__requireFetchReasons';
export const REQUIRES = 'requires';
export const REQUIRES_SCOPES = 'requiresScopes';
export const RESOLVABLE = 'resolvable';
Expand All @@ -123,12 +128,12 @@ export const SCOPES = 'scopes';
export const SCOPE_SCALAR = 'openfed__Scope';
export const SECURITY = 'SECURITY';
export const SELECTION_REPRESENTATION = ' { ... }';
export const SEMANTIC_NON_NULL = 'semanticNonNull';
export const SERVICE_OBJECT = '_Service';
export const SERVICE_FIELD = '_service';
export const SHAREABLE = 'shareable';
export const SPECIFIED_BY = 'specifiedBy';
export const STREAM_CONFIGURATION = 'streamConfiguration';
export const CONSUMER_INACTIVE_THRESHOLD = 'consumerInactiveThreshold';
export const STREAM_NAME = 'streamName';
export const STRING = 'string';
export const STRING_SCALAR = 'String';
Expand All @@ -150,8 +155,6 @@ export const UNION_UPPER = 'UNION';
export const URL_LOWER = 'url';
export const VALUES = 'values';
export const VARIABLE_DEFINITION_UPPER = 'VARIABLE_DEFINITION';
export const CHANNEL = 'channel';
export const CHANNELS = 'channels';

export const EXECUTABLE_DIRECTIVE_LOCATIONS = new Set<string>([
FIELD_UPPER,
Expand All @@ -165,7 +168,7 @@ export const EXECUTABLE_DIRECTIVE_LOCATIONS = new Set<string>([

export const ROOT_TYPE_NAMES = new Set<string>([MUTATION, QUERY, SUBSCRIPTION]);
export const AUTHORIZATION_DIRECTIVES = new Set<string>([AUTHENTICATED, REQUIRES_SCOPES]);
export const PERSISTED_CLIENT_DIRECTIVES = new Set<string>([DEPRECATED]);
export const PERSISTED_CLIENT_DIRECTIVES = new Set<string>([DEPRECATED, SEMANTIC_NON_NULL]);
export const INHERITABLE_DIRECTIVE_NAMES = new Set<string>([EXTERNAL, REQUIRE_FETCH_REASONS, SHAREABLE]);
export const IGNORED_FIELDS = new Set<string>([ENTITIES_FIELD, SERVICE_FIELD]);

Expand All @@ -182,3 +185,5 @@ export const OUTPUT_NODE_KINDS = new Set<Kind>([
Kind.SCALAR_TYPE_DEFINITION,
Kind.UNION_TYPE_DEFINITION,
]);

export const NON_REPEATABLE_PERSISTED_DIRECTIVES = new Set<DirectiveName>([INACCESSIBLE, SEMANTIC_NON_NULL]);
31 changes: 29 additions & 2 deletions composition/src/utils/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@ import {
INPUT_VALUE,
INT_SCALAR,
INTERFACE,
LEVELS,
NULL,
OBJECT,
REQUIRES_SCOPES,
SCALAR,
SCOPES,
SEMANTIC_NON_NULL,
STRING_SCALAR,
UNION,
} from './string-constants';
Expand Down Expand Up @@ -191,6 +193,31 @@ export function generateRequiresScopesDirective(orScopes: Set<string>[]): ConstD
};
}

export function generateSemanticNonNullDirective(levels: Set<number>): ConstDirectiveNode {
const sortedLevels = Array.from(levels).sort((a, b) => a - b);
const values = new Array<ConstValueNode>();
for (const level of sortedLevels) {
values.push({
kind: Kind.INT,
value: level.toString(),
});
}
return {
kind: Kind.DIRECTIVE,
name: stringToNameNode(SEMANTIC_NON_NULL),
arguments: [
{
kind: Kind.ARGUMENT,
name: stringToNameNode(LEVELS),
value: {
kind: Kind.LIST,
values,
},
},
],
};
}

// shallow copy
export function copyObjectValueMap<K, V>(source: Map<K, V>): Map<K, V> {
const output = new Map<K, V>();
Expand Down Expand Up @@ -221,8 +248,8 @@ export function addMapEntries<K, V>(source: Map<K, V>, target: Map<K, V>) {
}
}

export function getSingleSetEntry<T>(set: Set<T>): T | undefined {
const { value, done } = set.values().next();
export function getFirstEntry<K, V>(hashSet: Set<V> | Map<K, V>): V | undefined {
const { value, done } = hashSet.values().next();
if (done) {
return;
}
Expand Down
Loading
Loading