in composition/src/v1/federation/federation-factory.ts [1883:2308]
missingSubgraphs: getEntriesNotInHashSet(
parentDefinitionData.subgraphNames,
inputValueData.subgraphNames,
),
requiredSubgraphs: [...inputValueData.requiredSubgraphNames],
});
}
}
if (invalidRequiredInputs.length > 0) {
this.errors.push(
invalidRequiredInputValueError(INPUT_OBJECT, parentTypeName, invalidRequiredInputs, false),
);
break;
}
parentDefinitionData.node.fields = inputValueNodes;
this.routerDefinitions.push(this.getNodeForRouterSchemaByData(parentDefinitionData));
if (isNodeDataInaccessible(parentDefinitionData)) {
this.validateReferencesOfInaccessibleType(parentDefinitionData);
break;
}
if (clientInputValueNodes.length < 1) {
this.errors.push(
allChildDefinitionsAreInaccessibleError(
kindToTypeString(parentDefinitionData.kind),
parentTypeName,
'input field',
),
);
break;
}
this.clientDefinitions.push({
...parentDefinitionData.node,
directives: getClientPersistedDirectiveNodes(parentDefinitionData),
fields: clientInputValueNodes,
});
break;
case Kind.INTERFACE_TYPE_DEFINITION:
// intentional fallthrough
case Kind.OBJECT_TYPE_DEFINITION:
const fieldNodes: Array<MutableFieldNode> = [];
const clientSchemaFieldNodes: Array<MutableFieldNode> = [];
const graphFieldDataByFieldName = new Map<string, GraphFieldData>();
const invalidFieldNames = newInvalidFieldNames();
const isObject = parentDefinitionData.kind === Kind.OBJECT_TYPE_DEFINITION;
for (const [fieldName, fieldData] of parentDefinitionData.fieldDataByFieldName) {
pushAuthorizationDirectives(fieldData, this.authorizationDataByParentTypeName.get(parentTypeName));
const argumentNodes = this.getValidFieldArgumentNodes(fieldData);
if (isObject) {
validateExternalAndShareable(fieldData, invalidFieldNames);
}
fieldNodes.push(this.getNodeWithPersistedDirectivesByFieldData(fieldData, argumentNodes));
if (isNodeDataInaccessible(fieldData)) {
continue;
}
clientSchemaFieldNodes.push(getClientSchemaFieldNodeByFieldData(fieldData));
graphFieldDataByFieldName.set(fieldName, this.fieldDataToGraphFieldData(fieldData));
}
if (isObject) {
if (invalidFieldNames.byShareable.size > 0) {
this.errors.push(invalidFieldShareabilityError(parentDefinitionData, invalidFieldNames.byShareable));
}
if (invalidFieldNames.subgraphNamesByExternalFieldName.size > 0) {
this.errors.push(
allExternalFieldInstancesError(parentTypeName, invalidFieldNames.subgraphNamesByExternalFieldName),
);
}
}
parentDefinitionData.node.fields = fieldNodes;
this.internalGraph.initializeNode(parentTypeName, graphFieldDataByFieldName);
// Implemented interfaces can only be validated after all fields are merged
if (parentDefinitionData.implementedInterfaceTypeNames.size > 0) {
interfaceImplementations.push({ data: parentDefinitionData, clientSchemaFieldNodes });
break;
}
this.routerDefinitions.push(this.getNodeForRouterSchemaByData(parentDefinitionData));
const isQuery = isNodeQuery(parentTypeName);
if (isNodeDataInaccessible(parentDefinitionData)) {
if (isQuery) {
this.errors.push(inaccessibleQueryRootTypeError);
break;
}
this.validateReferencesOfInaccessibleType(parentDefinitionData);
this.internalGraph.setNodeInaccessible(parentDefinitionData.name);
break;
}
if (clientSchemaFieldNodes.length < 1) {
const error = isQuery
? noQueryRootTypeError(false)
: allChildDefinitionsAreInaccessibleError(
kindToTypeString(parentDefinitionData.kind),
parentTypeName,
FIELD,
);
this.errors.push(error);
break;
}
this.clientDefinitions.push({
...parentDefinitionData.node,
directives: getClientPersistedDirectiveNodes(parentDefinitionData),
fields: clientSchemaFieldNodes,
});
break;
case Kind.SCALAR_TYPE_DEFINITION:
if (BASE_SCALARS.has(parentTypeName)) {
break;
}
this.routerDefinitions.push(this.getNodeForRouterSchemaByData(parentDefinitionData));
if (isNodeDataInaccessible(parentDefinitionData)) {
this.validateReferencesOfInaccessibleType(parentDefinitionData);
this.internalGraph.setNodeInaccessible(parentDefinitionData.name);
break;
}
this.clientDefinitions.push({
...parentDefinitionData.node,
directives: getClientPersistedDirectiveNodes(parentDefinitionData),
});
break;
case Kind.UNION_TYPE_DEFINITION:
parentDefinitionData.node.types = mapToArrayOfValues(parentDefinitionData.memberByMemberTypeName);
this.routerDefinitions.push(this.getNodeForRouterSchemaByData(parentDefinitionData));
if (isNodeDataInaccessible(parentDefinitionData)) {
this.validateReferencesOfInaccessibleType(parentDefinitionData);
this.internalGraph.setNodeInaccessible(parentDefinitionData.name);
break;
}
const clientMembers = this.getClientSchemaUnionMembers(parentDefinitionData);
if (clientMembers.length < 1) {
this.errors.push(allChildDefinitionsAreInaccessibleError(UNION, parentTypeName, 'union member type'));
break;
}
this.clientDefinitions.push({
...parentDefinitionData.node,
directives: getClientPersistedDirectiveNodes(parentDefinitionData),
types: clientMembers,
});
break;
}
}
}
federateSubgraphData() {
this.federateInternalSubgraphData();
this.handleEntityInterfaces();
// generate the map of tag data that is used by contracts
this.generateTagData();
this.pushVersionTwoDirectiveDefinitionsToDocumentDefinitions();
}
validateInterfaceImplementationsAndPushToDocumentDefinitions(
interfaceImplementations: InterfaceImplementationData[],
) {
for (const { data, clientSchemaFieldNodes } of interfaceImplementations) {
data.node.interfaces = this.getValidImplementedInterfaces(data);
this.routerDefinitions.push(
getNodeForRouterSchemaByData(data, this.persistedDirectiveDefinitionByDirectiveName, this.errors),
);
if (isNodeDataInaccessible(data)) {
this.validateReferencesOfInaccessibleType(data);
this.internalGraph.setNodeInaccessible(data.name);
continue;
}
const clientInterfaces: NamedTypeNode[] = [];
for (const interfaceTypeName of data.implementedInterfaceTypeNames) {
if (!this.inaccessibleCoords.has(interfaceTypeName)) {
clientInterfaces.push(stringToNamedTypeNode(interfaceTypeName));
}
}
/* It is not possible for clientSchemaFieldNodes to be empty.
* If all interface fields were declared @inaccessible, the error would be caught above.
* */
this.clientDefinitions.push({
...data.node,
directives: getClientPersistedDirectiveNodes(data),
fields: clientSchemaFieldNodes,
interfaces: clientInterfaces,
});
}
}
pushVersionTwoDirectiveDefinitionsToDocumentDefinitions() {
if (!this.isVersionTwo) {
return;
}
this.routerDefinitions = [
AUTHENTICATED_DEFINITION,
DEPRECATED_DEFINITION,
INACCESSIBLE_DEFINITION,
REQUIRES_SCOPES_DEFINITION,
TAG_DEFINITION,
SCOPE_SCALAR_DEFINITION,
];
this.clientDefinitions = [
AUTHENTICATED_DEFINITION,
DEPRECATED_DEFINITION,
REQUIRES_SCOPES_DEFINITION,
SCOPE_SCALAR_DEFINITION,
];
}
validatePathSegmentInaccessibility(path: string): boolean {
if (!path) {
return false;
}
const coordinates = path.split(LEFT_PARENTHESIS)[0];
const segments = coordinates.split(PERIOD);
let segment = segments[0];
for (let i = 0; i < segments.length; i++) {
if (this.inaccessibleCoords.has(segment)) {
return true;
}
segment += `.${segments[i + 1]}`;
}
return false;
}
validateReferencesOfInaccessibleType(data: ParentDefinitionData) {
const allCoords = this.coordsByNamedTypeName.get(data.name);
if (!allCoords || allCoords.size < 1) {
return;
}
const invalidCoords: Array<string> = [];
for (const coords of allCoords) {
if (this.inaccessibleCoords.has(coords)) {
continue;
}
if (!this.validatePathSegmentInaccessibility(coords)) {
invalidCoords.push(coords);
}
}
if (invalidCoords.length > 0) {
this.errors.push(invalidReferencesOfInaccessibleTypeError(kindToTypeString(data.kind), data.name, invalidCoords));
}
}
validateQueryRootType() {
const query = this.parentDefinitionDataByTypeName.get(QUERY);
if (!query || query.kind !== Kind.OBJECT_TYPE_DEFINITION || query.fieldDataByFieldName.size < 1) {
this.errors.push(noQueryRootTypeError());
return;
}
for (const fieldData of query.fieldDataByFieldName.values()) {
if (!isNodeDataInaccessible(fieldData)) {
return;
}
}
this.errors.push(noQueryRootTypeError());
}
validateSubscriptionFieldConditionFieldPath(
conditionFieldPath: string,
objectData: ObjectDefinitionData,
inputFieldPath: string,
directiveSubgraphName: string,
fieldErrorMessages: Array<string>,
): string[] {
const paths = conditionFieldPath.split(PERIOD);
if (paths.length < 1) {
fieldErrorMessages.push(
invalidSubscriptionFieldConditionFieldPathErrorMessage(inputFieldPath, conditionFieldPath),
);
return [];
}
let lastData: ParentDefinitionData = objectData;
if (this.inaccessibleCoords.has(lastData.renamedTypeName)) {
fieldErrorMessages.push(
inaccessibleSubscriptionFieldConditionFieldPathFieldErrorMessage(
inputFieldPath,
conditionFieldPath,
paths[0],
lastData.renamedTypeName,
),
);
return [];
}
let partialConditionFieldPath = '';
for (let i = 0; i < paths.length; i++) {
const fieldName = paths[i];
partialConditionFieldPath += partialConditionFieldPath.length > 0 ? `.${fieldName}` : fieldName;
if (lastData.kind !== Kind.OBJECT_TYPE_DEFINITION) {
fieldErrorMessages.push(
invalidSubscriptionFieldConditionFieldPathParentErrorMessage(
inputFieldPath,
conditionFieldPath,
partialConditionFieldPath,
),
);
return [];
}
const fieldData: FieldData | undefined = lastData.fieldDataByFieldName.get(fieldName);
if (!fieldData) {
fieldErrorMessages.push(
undefinedSubscriptionFieldConditionFieldPathFieldErrorMessage(
inputFieldPath,
conditionFieldPath,
partialConditionFieldPath,
fieldName,
lastData.renamedTypeName,
),
);
return [];
}
const fieldPath = `${lastData.renamedTypeName}.${fieldName}`;
if (!fieldData.subgraphNames.has(directiveSubgraphName)) {
fieldErrorMessages.push(
invalidSubscriptionFieldConditionFieldPathFieldErrorMessage(
inputFieldPath,
conditionFieldPath,
partialConditionFieldPath,
fieldPath,
directiveSubgraphName,
),
);
return [];
}
if (this.inaccessibleCoords.has(fieldPath)) {
fieldErrorMessages.push(
inaccessibleSubscriptionFieldConditionFieldPathFieldErrorMessage(
inputFieldPath,
conditionFieldPath,
partialConditionFieldPath,
fieldPath,
),
);
return [];
}
if (BASE_SCALARS.has(fieldData.namedTypeName)) {
lastData = { kind: Kind.SCALAR_TYPE_DEFINITION, name: fieldData.namedTypeName } as ScalarDefinitionData;
continue;
}
lastData = getOrThrowError(this.parentDefinitionDataByTypeName, fieldData.namedTypeName, PARENT_DEFINITION_DATA);
}
if (!isLeafKind(lastData.kind)) {
fieldErrorMessages.push(
nonLeafSubscriptionFieldConditionFieldPathFinalFieldErrorMessage(
inputFieldPath,
conditionFieldPath,
paths[paths.length - 1],
kindToTypeString(lastData.kind),
lastData.name,
),
);
return [];
}
return paths;
}
validateSubscriptionFieldCondition(
objectValueNode: ConstObjectValueNode,
condition: SubscriptionFieldCondition,
objectData: ObjectDefinitionData,
depth: number,
inputPath: string,
directiveSubgraphName: string,
errorMessages: string[],
): boolean {
if (depth > MAX_SUBSCRIPTION_FILTER_DEPTH || this.isMaxDepth) {
errorMessages.push(subscriptionFilterConditionDepthExceededErrorMessage(inputPath));
this.isMaxDepth = true;
return false;
}
let hasErrors = false;
const validFieldNames = new Set<string>([FIELD_PATH, VALUES]);
const duplicatedFieldNames = new Set<string>();
const invalidFieldNames = new Set<string>();
const fieldErrorMessages: string[] = [];
for (const objectFieldNode of objectValueNode.fields) {
const inputFieldName = objectFieldNode.name.value;
const inputFieldPath = inputPath + `.${inputFieldName}`;
switch (inputFieldName) {
case FIELD_PATH: {
if (validFieldNames.has(FIELD_PATH)) {
validFieldNames.delete(FIELD_PATH);
} else {
hasErrors = true;
duplicatedFieldNames.add(FIELD_PATH);
break;
}
if (objectFieldNode.value.kind !== Kind.STRING) {
fieldErrorMessages.push(
invalidInputFieldTypeErrorMessage(inputFieldPath, STRING, kindToTypeString(objectFieldNode.value.kind)),
);
hasErrors = true;
break;
}
const fieldPath = this.validateSubscriptionFieldConditionFieldPath(
objectFieldNode.value.value,
objectData,
inputFieldPath,
directiveSubgraphName,
fieldErrorMessages,
);
if (fieldPath.length < 1) {
hasErrors = true;
break;
}
condition.fieldPath = fieldPath;
break;
}
case VALUES: {
if (validFieldNames.has(VALUES)) {
validFieldNames.delete(VALUES);
} else {
hasErrors = true;
duplicatedFieldNames.add(VALUES);
break;
}
const objectFieldValueKind = objectFieldNode.value.kind;
if (objectFieldValueKind == Kind.NULL || objectFieldValueKind == Kind.OBJECT) {
fieldErrorMessages.push(
invalidInputFieldTypeErrorMessage(inputFieldPath, LIST, kindToTypeString(objectFieldNode.value.kind)),
);
hasErrors = true;
break;
}
// Coerce scalars into a list
if (objectFieldValueKind !== Kind.LIST) {
condition.values = [getSubscriptionFilterValue(objectFieldNode.value)];
break;
}
// Prevent duplicate values
const values = new Set<SubscriptionFilterValue>();
const invalidIndices: number[] = [];
for (let i = 0; i < objectFieldNode.value.values.length; i++) {
const valueNode = objectFieldNode.value.values[i];
if (valueNode.kind === Kind.OBJECT || valueNode.kind === Kind.LIST) {