diff --git a/composition/src/v1/federation/federation-factory.ts b/composition/src/v1/federation/federation-factory.ts index 31693277f5..a9e03f5ddc 100644 --- a/composition/src/v1/federation/federation-factory.ts +++ b/composition/src/v1/federation/federation-factory.ts @@ -29,7 +29,6 @@ import { configureDescriptionPropagationError, inaccessibleQueryRootTypeError, inaccessibleRequiredInputValueError, - inaccessibleSubscriptionFieldConditionFieldPathFieldErrorMessage, incompatibleFederatedFieldNamedTypeError, incompatibleMergedTypesError, incompatibleParentKindFatalError, @@ -2536,17 +2535,6 @@ export class FederationFactory { 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]; @@ -2587,17 +2575,6 @@ export class FederationFactory { ); 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; diff --git a/composition/tests/v1/directives/subscription-filter.test.ts b/composition/tests/v1/directives/subscription-filter.test.ts index e67b0e6cfd..92fef47168 100644 --- a/composition/tests/v1/directives/subscription-filter.test.ts +++ b/composition/tests/v1/directives/subscription-filter.test.ts @@ -2,7 +2,6 @@ import { describe, expect, test } from 'vitest'; import { CONDITION, FIRST_ORDINAL, - inaccessibleSubscriptionFieldConditionFieldPathFieldErrorMessage, invalidArgumentValueErrorMessage, invalidDirectiveError, invalidEventDrivenGraphError, @@ -35,6 +34,7 @@ import { undefinedSubscriptionFieldConditionFieldPathFieldErrorMessage, } from '../../../src'; import { + createSubgraph, federateSubgraphsFailure, federateSubgraphsSuccess, normalizeString, @@ -404,27 +404,214 @@ describe('@openfed__subscriptionFilter tests', () => { ]); }); - test('that an error is returned if fieldPath references an inaccessible field', () => { - const result = federateSubgraphsFailure([subgraphB, subgraphN], ROUTER_COMPATIBILITY_VERSION_ONE); - expect(result.errors).toHaveLength(1); - expect(result.errors).toStrictEqual([ - invalidSubscriptionFilterDirectiveError('Subscription.one', [ - subscriptionFieldConditionInvalidInputFieldError( - 'condition.IN', - [], - [], - [], - [ - inaccessibleSubscriptionFieldConditionFieldPathFieldErrorMessage( - 'condition.IN.fieldPath', - 'object.id', - 'object.id', - 'Object.id', - ), - ], - ), - ]), - ]); + test('that inaccessible EDG fields can be used within a subscription filter fieldPath', () => { + const subgraphA = createSubgraph( + 'subgraph-a', + ` + type Query{ + entity: Entity! + } + + type Entity @key(fields: "id") { + id: ID! + name: String! + } + `, + ); + + const subgraphB = createSubgraph( + 'subgraph-b', + ` + type Entity @key(fields: "id", resolvable: false) @key(fields: "name", resolvable: false) { + id: ID! @external + name: String! @external @inaccessible + } + + type Subscription { + one: Entity! @edfs__kafkaSubscribe(topics: ["employeeUpdated"]) @openfed__subscriptionFilter(condition: { IN: { fieldPath: "name", values: ["test"], } }) + }`, + ); + + const result = federateSubgraphsSuccess([subgraphA, subgraphB], ROUTER_COMPATIBILITY_VERSION_ONE); + expect(result.success).toBe(true); + const subscriptionFields = result.fieldConfigurations.filter( + (fc) => fc.typeName === SUBSCRIPTION && fc.fieldName === 'one', + ); + expect(subscriptionFields).toHaveLength(1); + expect(subscriptionFields[0]).toStrictEqual({ + argumentNames: [], + fieldName: 'one', + typeName: SUBSCRIPTION, + subscriptionFilterCondition: { + in: { + fieldPath: ['name'], + values: ['test'], + }, + }, + }); + + expect(schemaToSortedNormalizedString(result.federatedGraphClientSchema)).toBe( + normalizeString( + `schema { + query: Query + subscription: Subscription + } + + type Entity { + id: ID! + } + + type Query { + entity: Entity! + } + + type Subscription { + one: Entity! + } + `, + ), + ); + + expect(schemaToSortedNormalizedString(result.federatedGraphSchema)).toBe( + normalizeString( + `schema { + query: Query + subscription: Subscription + } + + directive @inaccessible on ARGUMENT_DEFINITION | ENUM | ENUM_VALUE | FIELD_DEFINITION | INPUT_FIELD_DEFINITION | INPUT_OBJECT | INTERFACE | OBJECT | SCALAR | UNION + + type Entity { + id: ID! + name: String! @inaccessible + } + + type Query { + entity: Entity! + } + + type Subscription { + one: Entity! + } + `, + ), + ); + }); + + test('that EDG fields that are defined inaccessible in another subgraph can be used within a subscription filter fieldPath', () => { + const subgraphA = createSubgraph( + 'subgraph-a', + ` + type Query{ + entity: Entity! + } + + type Entity @key(fields: "id") { + id: ID! + name: String! @inaccessible + } + `, + ); + + const subgraphB = createSubgraph( + 'subgraph-b', + `directive @edfs__natsRequest(subject: String!, providerId: String! = "default") on FIELD_DEFINITION + directive @edfs__natsPublish(subject: String!, providerId: String! = "default") on FIELD_DEFINITION + directive @edfs__natsSubscribe(subjects: [String!]!, providerId: String! = "default", streamConfiguration: edfs__NatsStreamConfiguration) on FIELD_DEFINITION + + type edfs__PublishResult { + success: Boolean! + } + + input edfs__NatsStreamConfiguration { + consumerInactiveThreshold: Int! = 30 + consumerName: String! + streamName: String! + } + + type Entity @key(fields: "id", resolvable: false) @key(fields: "name", resolvable: false) { + id: ID! @external + name: String! @external + } + + type Subscription { + one: Entity! @edfs__natsSubscribe(subjects: ["employeeUpdated"]) @openfed__subscriptionFilter(condition: { IN: { fieldPath: "name", values: ["test"], } }) + }`, + ); + + const result = federateSubgraphsSuccess([subgraphA, subgraphB], ROUTER_COMPATIBILITY_VERSION_ONE); + expect(result.success).toBe(true); + const subscriptionFields = result.fieldConfigurations.filter( + (fc) => fc.typeName === SUBSCRIPTION && fc.fieldName === 'one', + ); + expect(result.warnings).toHaveLength(0); + expect(subscriptionFields).toHaveLength(1); + expect(subscriptionFields[0]).toStrictEqual({ + argumentNames: [], + fieldName: 'one', + typeName: SUBSCRIPTION, + subscriptionFilterCondition: { + in: { + fieldPath: ['name'], + values: ['test'], + }, + }, + }); + + expect(schemaToSortedNormalizedString(result.federatedGraphClientSchema)).toBe( + normalizeString( + `schema { + query: Query + subscription: Subscription + } + + type Entity { + id: ID! + } + + type Query { + entity: Entity! + } + + type Subscription { + one: Entity! + } + + type edfs__PublishResult { + success: Boolean! + } + `, + ), + ); + + expect(schemaToSortedNormalizedString(result.federatedGraphSchema)).toBe( + normalizeString( + `schema { + query: Query + subscription: Subscription + } + + directive @inaccessible on ARGUMENT_DEFINITION | ENUM | ENUM_VALUE | FIELD_DEFINITION | INPUT_FIELD_DEFINITION | INPUT_OBJECT | INTERFACE | OBJECT | SCALAR | UNION + + type Entity { + id: ID! + name: String! @inaccessible + } + + type Query { + entity: Entity! + } + + type Subscription { + one: Entity! + } + + type edfs__PublishResult { + success: Boolean! + } + `, + ), + ); }); test('that an error is if condition.AND or condition.OR contain no elements or more than 5 elements', () => { @@ -923,25 +1110,6 @@ const subgraphM: Subgraph = { `), }; -const subgraphN: Subgraph = { - name: 'subgraph-n', - url: '', - definitions: parse(` - type Entity @key(fields: "id object { id }", resolvable: false) { - id: ID! @external - object: Object! @external - } - - type Object { - id: ID! @external @inaccessible - } - - type Subscription { - one: Entity! @edfs__kafkaSubscribe(topics: ["employeeUpdated"]) @openfed__subscriptionFilter(condition: { IN: { fieldPath: "object.id", values: [1], } }) - } - `), -}; - const subgraphO: Subgraph = { name: 'subgraph-o', url: '',