Skip to content

Conversation

@Aenimus
Copy link
Member

@Aenimus Aenimus commented Sep 17, 2025

Summary by CodeRabbit

  • New Features

    • Full support for @OneOf on input objects in both normalization and federation, with validation enforcing no required fields and warnings for single optional fields.
    • @OneOf exposed as a built-in directive and included in persisted/non-repeatable client directives.
    • Restored directive definitions for @semanticNonNull, @requireFetchReasons, and Redis publish/subscribe.
  • Bug Fixes

    • Minor capitalization tweak in an input-field error message.
  • Tests

    • Added comprehensive tests covering @OneOf and semantic-non-null behaviors.

Checklist

  • I have discussed my proposed changes in an issue and have received approval to proceed.
  • I have followed the coding standards of the project.
  • Tests or benchmarks have been added or updated.
  • Documentation has been updated on https://github.com/wundergraph/cosmo-docs.
  • I have read the Contributors Guide.

@coderabbitai
Copy link

coderabbitai bot commented Sep 17, 2025

Walkthrough

Adds support for the @OneOf directive across normalization and federation: new constants/definitions, validation that input-object oneOf fields are all optional, error and warning factories, directive registration/emission, restored directive-definition datas, and accompanying tests and test helpers.

Changes

Cohort / File(s) Summary
Errors & Types
composition/src/errors/errors.ts, composition/src/errors/types.ts
Add oneOfRequiredFieldsError({ requiredFieldNames, typeName }) and exported OneOfRequiredFieldsErrorParams type to report non-nullable fields on @oneOf input objects.
String Constants & Built-in Directives
composition/src/utils/string-constants.ts, composition/src/v1/utils/constants.ts, composition/src/v1/utils/constants.ts
Add ONE_OF = 'oneOf', export ONE_OF_DEFINITION AST node, and include ONE_OF in built-in directive sets (ALL_IN_BUILT_DIRECTIVE_NAMES, persisted/non-repeatable directive lists).
Directive-definition Data & Initialization
composition/src/v1/normalization/directive-definition-data.ts, composition/src/v1/normalization/utils.ts
Add/export ONE_OF_DEFINITION_DATA and reintroduce several directive-definition data entries (REDIS_PUBLISH/SUBSCRIBE, REQUIRE_FETCH_REASONS, SEMANTIC_NON_NULL); include [ONE_OF, ONE_OF_DEFINITION_DATA] in initialization mapping.
Normalization: ONE_OF handling
composition/src/v1/normalization/normalization-factory.ts, composition/src/v1/normalization/params.ts, composition/src/v1/utils/utils.ts
Import/emit ONE_OF_DEFINITION and SEMANTIC_NON_NULL_DEFINITION; add ValidateOneOfDirectiveParams type; validate @oneOf on input objects (error on required fields; warning when only one optional field exists); export getFirstEntry.
Federation: ONE_OF handling
composition/src/v1/federation/federation-factory.ts, composition/src/v1/federation/params.ts, composition/src/v1/federation/utils/constants.ts
Register ONE_OF in persistedDirectiveDefinitionByDirectiveName, add validateOneOfDirective path, collect requiredFieldNames, emit oneOfRequiredFieldsError or singleFederatedInputFieldOneOfWarning; add ValidateOneOfDirectiveParams type.
Warnings & Params
composition/src/v1/warnings/params.ts, composition/src/v1/warnings/warnings.ts
Add SingleSubgraphInputFieldOneOfWarningParams and SingleFederatedInputFieldOneOfWarningParams types and warning builders singleSubgraphInputFieldOneOfWarning and singleFederatedInputFieldOneOfWarning.
Tests & Test Utilities
composition/tests/v1/directives/one-of.test.ts, composition/tests/v1/directives/semantic-non-null.test.ts, composition/tests/v1/directives/external.test.ts, composition/tests/v1/router-configuration.test.ts, composition/tests/v1/utils/utils.ts, composition/utils/utils.ts
Add comprehensive @oneOf tests (normalization + federation), a @semanticNonNull success test, remove some result.success assertions, and add baseDirectiveDefinitionsWithSemanticNonNull and normalizeSubgraphSuccess test helpers/exports.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title concisely and accurately describes the primary change — adding @OneOf support to composition — and is short, clear, and directly related to the changeset; it avoids vague terms and does not misrepresent the scope. It uses the conventional-commit prefix "feat:" which aligns with the introduced feature and appropriately omits implementation details.
✨ Finishing touches
  • 📝 Generate Docstrings

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 611c2c8 and bcea092.

📒 Files selected for processing (7)
  • composition/src/v1/federation/federation-factory.ts (18 hunks)
  • composition/src/v1/federation/params.ts (1 hunks)
  • composition/src/v1/normalization/normalization-factory.ts (11 hunks)
  • composition/src/v1/normalization/params.ts (1 hunks)
  • composition/src/v1/utils/constants.ts (3 hunks)
  • composition/src/v1/warnings/warnings.ts (2 hunks)
  • composition/tests/v1/directives/one-of.test.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • composition/tests/v1/directives/one-of.test.ts
  • composition/src/v1/warnings/warnings.ts
  • composition/src/v1/utils/constants.ts
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-08T20:57:07.946Z
Learnt from: JivusAyrus
PR: wundergraph/cosmo#2156
File: controlplane/src/core/repositories/SubgraphRepository.ts:1746-1763
Timestamp: 2025-09-08T20:57:07.946Z
Learning: The checkSubgraphSchema.ts file already correctly implements linked subgraph functionality, using byName(linkedSubgraph.name, linkedSubgraph.namespace) to fetch target subgraphs and properly handles parse(newSchemaSDL) for schema building. The implementation doesn't need fixes for byId usage or schema parsing as it's already correct.

Applied to files:

  • composition/src/v1/normalization/normalization-factory.ts
🧬 Code graph analysis (4)
composition/src/v1/normalization/params.ts (3)
composition/src/v1/federation/params.ts (1)
  • ValidateOneOfDirectiveParams (5-9)
composition/src/schema-building/types.ts (1)
  • InputObjectDefinitionData (120-132)
composition/src/types/types.ts (1)
  • FieldName (5-5)
composition/src/v1/federation/params.ts (3)
composition/src/v1/normalization/params.ts (1)
  • ValidateOneOfDirectiveParams (4-7)
composition/src/schema-building/types.ts (1)
  • InputObjectDefinitionData (120-132)
composition/src/types/types.ts (1)
  • FieldName (5-5)
composition/src/v1/federation/federation-factory.ts (8)
composition/src/utils/string-constants.ts (1)
  • ONE_OF (102-102)
composition/src/v1/utils/constants.ts (1)
  • ONE_OF_DEFINITION (650-655)
composition/src/v1/federation/params.ts (1)
  • ValidateOneOfDirectiveParams (5-9)
composition/src/v1/warnings/warnings.ts (1)
  • singleFederatedInputFieldOneOfWarning (190-204)
composition/src/utils/types.ts (1)
  • InvalidRequiredInputValueData (33-37)
composition/src/schema-building/ast.ts (1)
  • MutableInputValueNode (108-115)
composition/src/types/types.ts (1)
  • FieldName (5-5)
composition/src/schema-building/utils.ts (1)
  • isTypeRequired (124-126)
composition/src/v1/normalization/normalization-factory.ts (7)
composition/src/v1/normalization/params.ts (1)
  • ValidateOneOfDirectiveParams (4-7)
composition/src/utils/string-constants.ts (2)
  • ONE_OF (102-102)
  • SEMANTIC_NON_NULL (132-132)
composition/src/errors/errors.ts (1)
  • oneOfRequiredFieldsError (1666-1675)
composition/src/v1/warnings/warnings.ts (1)
  • singleSubgraphInputFieldOneOfWarning (174-188)
composition/src/utils/utils.ts (1)
  • getFirstEntry (251-257)
composition/src/v1/utils/constants.ts (1)
  • ONE_OF_DEFINITION (650-655)
composition/src/schema-building/utils.ts (1)
  • isTypeRequired (124-126)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: integration_test (./events)
  • GitHub Check: integration_test (./. ./fuzzquery ./lifecycle ./modules)
  • GitHub Check: build_test
  • GitHub Check: Analyze (go)
🔇 Additional comments (9)
composition/src/v1/normalization/params.ts (1)

1-7: LGTM!

The new ValidateOneOfDirectiveParams type for the normalization flow is well-structured with appropriate imports and fields.

composition/src/v1/federation/params.ts (1)

1-9: LGTM!

The ValidateOneOfDirectiveParams type for the federation flow correctly includes the additional inputValueNodes field needed for federation-time validation.

composition/src/v1/normalization/normalization-factory.ts (3)

67-68: Good addition of ONE_OF_DEFINITION import.

The import is properly placed alongside other directive definitions.


3389-3413: Robust @oneOf validation logic.

The method correctly validates that all fields in an Input Object with @oneOf are optional and emits appropriate errors/warnings. The single-field warning is particularly helpful for schema design guidance.


3506-3541: Well-integrated @oneOf validation in the type definition processing.

The validation is properly positioned before processing the Input Object fields, and the early return on validation failure prevents invalid schemas from being further processed.

composition/src/v1/federation/federation-factory.ts (4)

274-274: Proper persistence of @oneOf directive definition.

The @oneOf directive is correctly added to the persistent directive definitions alongside other schema directives.


1879-1901: Comprehensive federation-time @oneOf validation.

The method properly validates that all fields are optional and provides helpful warnings for single-field Input Objects. The error and warning messages are clear and actionable.


1975-2018: Well-integrated @oneOf validation in federation processing.

The validation correctly occurs after collecting all input fields but before adding them to the parent definition. The early return on validation failure prevents invalid schemas from being included in the federated graph.


1486-1486: Fix interface existence check.

Good catch on fixing the comparison operator from < 0 to < 1 for checking if the interface data map is empty.

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

Please see the documentation for more information.

Example:

reviews:
  pre_merge_checks:
    custom_checks:
      - name: "Undocumented Breaking Changes"
        mode: "warning"
        instructions: |
          Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).

Please share your feedback with us on this Discord post.


Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link

github-actions bot commented Sep 17, 2025

Router-nonroot image scan passed

✅ No security vulnerabilities found in image:

ghcr.io/wundergraph/cosmo/router:sha-989e5e43fcb2ca1c7800d17a772d06e8ab26a3c1-nonroot

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
composition/src/v1/normalization/normalization-factory.ts (1)

3479-3531: Address switch declaration scope issues and improve code organization.

The static analysis correctly identifies that variables declared in switch cases can be accessed by other cases. Additionally, the @OneOf validation logic would benefit from extraction into a dedicated method.

Apply this diff to fix the scope issues and improve maintainability:

case Kind.INPUT_OBJECT_TYPE_DEFINITION:
+  {
   const fieldCount = parentData.inputValueDataByName.size;
   if (fieldCount < 1) {
     this.errors.push(noInputValueDefinitionsError(parentTypeName));
     break;
   }
   const definesOneOf = parentData.directivesByDirectiveName.has(ONE_OF);
   const requiredFieldNames = new Set<FieldName>();
   for (const valueData of parentData.inputValueDataByName.values()) {
     if (isTypeRequired(valueData.type)) {
       requiredFieldNames.add(valueData.name);
     }
     // Base Scalars have already been set
     if (valueData.namedTypeKind !== Kind.NULL) {
       continue;
     }
     const namedTypeData = this.parentDefinitionDataByTypeName.get(valueData.namedTypeName);
     if (!namedTypeData) {
       // undefined types are handled elsewhere
       continue;
     }
     if (!isInputNodeKind(namedTypeData.kind)) {
       this.errors.push(
         invalidNamedTypeError({
           data: valueData,
           namedTypeData,
           nodeType: `${kindToNodeType(parentData.kind)} field`,
         }),
       );
       continue;
     }
     valueData.namedTypeKind = namedTypeData.kind;
   }
   if (definesOneOf) {
     if (requiredFieldNames.size > 0) {
       this.errors.push(
         oneOfRequiredFieldsError({
           requiredFieldNames: Array.from(requiredFieldNames),
           typeName: parentTypeName,
         }),
       );
       break;
     }
     if (fieldCount === 1) {
       this.warnings.push(
         singleSubgraphInputFieldOneOfWarning({
           fieldName: getFirstEntry(parentData.inputValueDataByName)?.name ?? 'unknown',
           subgraphName: this.subgraphName,
           typeName: parentTypeName,
         }),
       );
     }
   }
   definitions.push(this.getInputObjectNodeByData(parentData));
   break;
+  }

Consider extracting the @OneOf validation logic to a dedicated method for better organization:

private validateOneOfDirective(
  parentData: InputObjectDefinitionData,
  parentTypeName: string,
  fieldCount: number,
  requiredFieldNames: Set<FieldName>
): void {
  if (requiredFieldNames.size > 0) {
    this.errors.push(
      oneOfRequiredFieldsError({
        requiredFieldNames: Array.from(requiredFieldNames),
        typeName: parentTypeName,
      }),
    );
    return;
  }
  if (fieldCount === 1) {
    this.warnings.push(
      singleSubgraphInputFieldOneOfWarning({
        fieldName: getFirstEntry(parentData.inputValueDataByName)?.name ?? 'unknown',
        subgraphName: this.subgraphName,
        typeName: parentTypeName,
      }),
    );
  }
}
composition/src/v1/federation/federation-factory.ts (3)

1485-1489: Bug: impossible condition (size < 0) prevents error path.

interfaceDataByTypeName.size < 0 can never be true; it should be < 1. This can mask incompatible named type errors.

Apply:

-      if (interfaceDataByTypeName.size < 0 && !unionTypeName) {
+      if (interfaceDataByTypeName.size < 1 && !unionTypeName) {
         this.errors.push(incompatibleFederatedFieldNamedTypeError(fieldCoordinates, subgraphNamesByNamedTypeName));
         continue;
       }

1949-2026: Fix switch-case declarations scope (Biome noSwitchDeclarations) and add missing @OneOf default-value validation.

  • Declarations inside case Kind.INPUT_OBJECT_TYPE_DEFINITION: should be wrapped in a block to avoid leaking across cases.
  • Spec requires oneOf input fields to be nullable and have no default value; code enforces nullability but not “no default value”. Add a check and error out when any field has a default.

Apply:

-      case Kind.INPUT_OBJECT_TYPE_DEFINITION:
-        const invalidRequiredInputs = new Array<InvalidRequiredInputValueData>();
-        const inputValueNodes = new Array<MutableInputValueNode>();
-        const clientInputValueNodes = new Array<MutableInputValueNode>();
-        const definesOneOf = parentDefinitionData.directivesByDirectiveName.has(ONE_OF);
-        const requiredFieldNames = new Set<FieldName>();
+      case Kind.INPUT_OBJECT_TYPE_DEFINITION: {
+        const invalidRequiredInputs = new Array<InvalidRequiredInputValueData>();
+        const inputValueNodes = new Array<MutableInputValueNode>();
+        const clientInputValueNodes = new Array<MutableInputValueNode>();
+        const definesOneOf = parentDefinitionData.directivesByDirectiveName.has(ONE_OF);
+        const requiredFieldNames = new Set<FieldName>();
+        const defaultedFieldNames = new Set<FieldName>();

         for (const [inputValueName, inputValueData] of parentDefinitionData.inputValueDataByName) {
           if (isTypeRequired(inputValueData.type)) {
             requiredFieldNames.add(inputValueName);
           }
+          if (inputValueData.defaultValue !== undefined) {
+            defaultedFieldNames.add(inputValueName);
+          }
           if (parentDefinitionData.subgraphNames.size === inputValueData.subgraphNames.size) {
             inputValueNodes.push(this.getNodeWithPersistedDirectivesByInputValueData(inputValueData));
             if (isNodeDataInaccessible(inputValueData)) {
               continue;
             }
             clientInputValueNodes.push({
               ...inputValueData.node,
               directives: getClientPersistedDirectiveNodes(inputValueData),
             });
           } else if (isTypeRequired(inputValueData.type)) {
             invalidRequiredInputs.push({
               inputValueName,
               missingSubgraphs: getEntriesNotInHashSet(
                 parentDefinitionData.subgraphNames,
                 inputValueData.subgraphNames,
               ),
               requiredSubgraphs: [...inputValueData.requiredSubgraphNames],
             });
           }
         }
         if (invalidRequiredInputs.length > 0) {
           this.errors.push(
             invalidRequiredInputValueError(INPUT_OBJECT, parentTypeName, invalidRequiredInputs, false),
           );
-          break;
+          break;
         }
         if (definesOneOf) {
+          if (defaultedFieldNames.size > 0) {
+            this.errors.push(
+              new Error(
+                `The "@oneOf" directive defined on Input Object "${parentTypeName}" is invalid because all Input fields must not define default values; however, the following field(s) define defaults: "` +
+                  [...defaultedFieldNames].join('", "') +
+                  `".`,
+              ),
+            );
+            break;
+          }
           if (requiredFieldNames.size > 0) {
             this.errors.push(
               oneOfRequiredFieldsError({
                 requiredFieldNames: Array.from(requiredFieldNames),
                 typeName: parentTypeName,
               }),
             );
             break;
           }
           if (inputValueNodes.length === 1) {
             this.warnings.push(
               singleFederatedInputFieldOneOfWarning({
                 fieldName: inputValueNodes[0]!.name.value,
                 typeName: parentTypeName,
               }),
             );
           }
         }
         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(
               kindToNodeType(parentDefinitionData.kind),
               parentTypeName,
               'Input field',
             ),
           );
           break;
         }
         this.clientDefinitions.push({
           ...parentDefinitionData.node,
           directives: getClientPersistedDirectiveNodes(parentDefinitionData),
           fields: clientInputValueNodes,
         });
-        break;
+        break;
+      }

Notes:

  • Spec excerpt: “If the Input Object is a OneOf Input Object then: The type of the input field must be nullable. The input field must not have a default value.”

2957-2974: Logic bug: variable shadowing causes every argument to be marked @inaccessible.

Inside handleChildTagExclusions you shadow the tagNames parameter with for (const [argumentName, tagNames] ...), then compare the set to itself: !tagNames.isDisjointFrom(tagNames) is always true. This hides valid arguments.

Apply:

-              for (const [argumentName, tagNames] of childTagData.tagNamesByArgumentName) {
+              for (const [argumentName, argumentTagNames] of childTagData.tagNamesByArgumentName) {
                 const inputValueData = getOrThrowError(
                   fieldData.argumentDataByName,
                   argumentName,
                   `${fieldName}.argumentDataByArgumentName`,
                 );
                 if (isNodeDataInaccessible(inputValueData)) {
                   continue;
                 }
-                if (!tagNames.isDisjointFrom(tagNames)) {
+                if (!tagNames.isDisjointFrom(argumentTagNames)) {
                   getValueOrDefault(
                     inputValueData.persistedDirectivesData.directivesByDirectiveName,
                     INACCESSIBLE,
                     () => [generateSimpleDirective(INACCESSIBLE)],
                   );
                   this.inaccessibleCoords.add(inputValueData.federatedCoords);
                 }
               }
🧹 Nitpick comments (8)
composition/tests/v1/utils/utils.ts (1)

37-45: Test helper for semanticNonNull — OK; consider dedup

Looks good. Consider composing this from baseDirectiveDefinitions plus only the delta to reduce duplication across test helpers.

composition/tests/v1/directives/semantic-non-null.test.ts (1)

471-480: naf Subgraph fixture — OK; minor naming nit

Readable as-is. Optionally rename to a more descriptive id (e.g., nn_success) for future maintainers.

composition/src/errors/types.ts (1)

33-36: Type for oneOf required fields — OK; prefer readonly

If these params are not mutated, prefer ReadonlyArray for stronger typing.

-export type OneOfRequiredFieldsErrorParams = {
-  requiredFieldNames: Array<FieldName>;
+export type OneOfRequiredFieldsErrorParams = {
+  requiredFieldNames: ReadonlyArray<FieldName>;
   typeName: TypeName;
 };
composition/src/errors/errors.ts (1)

1666-1675: oneOfRequiredFieldsError helper — OK; edge‑case guard (optional)

Message format/pluralization are consistent. If there’s any chance of an empty requiredFieldNames array, consider guarding to avoid "".

-export function oneOfRequiredFieldsError({ requiredFieldNames, typeName }: OneOfRequiredFieldsErrorParams): Error {
+export function oneOfRequiredFieldsError({ requiredFieldNames, typeName }: OneOfRequiredFieldsErrorParams): Error {
+  const names = requiredFieldNames.length ? requiredFieldNames.join(QUOTATION_JOIN) : '<none>';
   return new Error(
-    `The "@oneOf" directive defined on Input Object "${typeName}" is invalid because all Input fields must be` +
+    `The "@oneOf" directive defined on Input Object "${typeName}" is invalid because all Input fields must be` +
       ` optional (nullable); however, the following Input field` +
       (requiredFieldNames.length > 1 ? `s are` : ` is`) +
-      ` required (non-nullable): "` +
-      requiredFieldNames.join(QUOTATION_JOIN) +
+      ` required (non-nullable): "` +
+      names +
       `".`,
   );
 }
composition/src/v1/warnings/params.ts (1)

1-12: Warning param types — OK; prefer readonly fields

If immutable, mark fields as readonly to better express intent.

-export type SingleSubgraphInputFieldOneOfWarningParams = {
-  fieldName: FieldName;
-  subgraphName: SubgraphName;
-  typeName: TypeName;
-};
+export type SingleSubgraphInputFieldOneOfWarningParams = {
+  readonly fieldName: FieldName;
+  readonly subgraphName: SubgraphName;
+  readonly typeName: TypeName;
+};
 
-export type SingleFederatedInputFieldOneOfWarningParams = {
-  fieldName: FieldName;
-  typeName: TypeName;
-};
+export type SingleFederatedInputFieldOneOfWarningParams = {
+  readonly fieldName: FieldName;
+  readonly typeName: TypeName;
+};
composition/src/v1/warnings/warnings.ts (1)

190-203: Minor: Missing return type annotation.

Consider adding an explicit return type for consistency with the other warning functions in this file.

export function singleFederatedInputFieldOneOfWarning({
  fieldName,
  typeName,
-}: SingleFederatedInputFieldOneOfWarningParams) {
+}: SingleFederatedInputFieldOneOfWarningParams): Warning {
composition/tests/v1/directives/one-of.test.ts (1)

550-555: Fix inconsistent indentation in test fixture.

The indentation in the fbac fixture is inconsistent with the rest of the test file.

  definitions: parse(`
-  input Input {
-    a: ID!
-    b: Boolean
-  }
-`),
+    input Input {
+      a: ID!
+      b: Boolean
+    }
+  `),
composition/src/v1/normalization/normalization-factory.ts (1)

3525-3525: Potential null handling improvement for field name extraction.

Using the nullish coalescing operator with a fallback to 'unknown' is good defensive programming. However, consider logging when this fallback occurs for debugging purposes.

-          fieldName: getFirstEntry(parentData.inputValueDataByName)?.name ?? 'unknown',
+          fieldName: (() => {
+            const firstEntry = getFirstEntry(parentData.inputValueDataByName);
+            if (!firstEntry?.name) {
+              console.warn(`Unable to retrieve field name for @oneOf on ${parentTypeName}`);
+              return 'unknown';
+            }
+            return firstEntry.name;
+          })(),
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 29c7b00 and 611c2c8.

📒 Files selected for processing (15)
  • composition/src/errors/errors.ts (2 hunks)
  • composition/src/errors/types.ts (2 hunks)
  • composition/src/utils/string-constants.ts (3 hunks)
  • composition/src/v1/federation/federation-factory.ts (8 hunks)
  • composition/src/v1/normalization/directive-definition-data.ts (5 hunks)
  • composition/src/v1/normalization/normalization-factory.ts (8 hunks)
  • composition/src/v1/normalization/utils.ts (3 hunks)
  • composition/src/v1/utils/constants.ts (3 hunks)
  • composition/src/v1/warnings/params.ts (1 hunks)
  • composition/src/v1/warnings/warnings.ts (2 hunks)
  • composition/tests/v1/directives/external.test.ts (0 hunks)
  • composition/tests/v1/directives/one-of.test.ts (1 hunks)
  • composition/tests/v1/directives/semantic-non-null.test.ts (3 hunks)
  • composition/tests/v1/router-configuration.test.ts (0 hunks)
  • composition/tests/v1/utils/utils.ts (1 hunks)
💤 Files with no reviewable changes (2)
  • composition/tests/v1/router-configuration.test.ts
  • composition/tests/v1/directives/external.test.ts
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-08T20:57:07.946Z
Learnt from: JivusAyrus
PR: wundergraph/cosmo#2156
File: controlplane/src/core/repositories/SubgraphRepository.ts:1746-1763
Timestamp: 2025-09-08T20:57:07.946Z
Learning: The checkSubgraphSchema.ts file already correctly implements linked subgraph functionality, using byName(linkedSubgraph.name, linkedSubgraph.namespace) to fetch target subgraphs and properly handles parse(newSchemaSDL) for schema building. The implementation doesn't need fixes for byId usage or schema parsing as it's already correct.

Applied to files:

  • composition/tests/v1/directives/semantic-non-null.test.ts
🧬 Code graph analysis (12)
composition/src/v1/warnings/params.ts (1)
composition/src/types/types.ts (3)
  • FieldName (5-5)
  • SubgraphName (9-9)
  • TypeName (11-11)
composition/src/errors/types.ts (1)
composition/src/types/types.ts (2)
  • FieldName (5-5)
  • TypeName (11-11)
composition/tests/v1/directives/one-of.test.ts (7)
composition/tests/utils/utils.ts (6)
  • normalizeSubgraphFailure (31-38)
  • normalizeSubgraphSuccess (40-47)
  • schemaToSortedNormalizedString (27-29)
  • normalizeString (19-21)
  • federateSubgraphsSuccess (59-72)
  • federateSubgraphsFailure (49-57)
composition/src/router-compatibility-version/router-compatibility-version.ts (1)
  • ROUTER_COMPATIBILITY_VERSION_ONE (3-3)
composition/src/errors/errors.ts (1)
  • oneOfRequiredFieldsError (1666-1675)
composition/src/utils/string-constants.ts (1)
  • INPUT (68-68)
composition/tests/v1/utils/utils.ts (3)
  • baseDirectiveDefinitions (18-25)
  • versionOneRouterDefinitions (118-118)
  • schemaQueryDefinition (94-97)
composition/src/v1/warnings/warnings.ts (2)
  • singleSubgraphInputFieldOneOfWarning (174-188)
  • singleFederatedInputFieldOneOfWarning (190-204)
composition/src/ast/utils.ts (1)
  • parse (272-274)
composition/src/utils/string-constants.ts (1)
composition/src/types/types.ts (1)
  • DirectiveName (3-3)
composition/src/errors/errors.ts (2)
composition/src/errors/types.ts (1)
  • OneOfRequiredFieldsErrorParams (33-36)
composition/src/utils/string-constants.ts (1)
  • QUOTATION_JOIN (117-117)
composition/src/v1/warnings/warnings.ts (2)
composition/src/v1/warnings/params.ts (2)
  • SingleSubgraphInputFieldOneOfWarningParams (3-7)
  • SingleFederatedInputFieldOneOfWarningParams (9-12)
composition/src/warnings/types.ts (1)
  • Warning (9-17)
composition/src/v1/normalization/utils.ts (2)
composition/src/utils/string-constants.ts (1)
  • ONE_OF (102-102)
composition/src/v1/normalization/directive-definition-data.ts (1)
  • ONE_OF_DEFINITION_DATA (450-458)
composition/src/v1/utils/constants.ts (2)
composition/src/utils/string-constants.ts (2)
  • ONE_OF (102-102)
  • INPUT_OBJECT_UPPER (72-72)
composition/src/ast/utils.ts (2)
  • stringArrayToNameNodeArray (84-90)
  • stringToNameNode (77-82)
composition/tests/v1/directives/semantic-non-null.test.ts (4)
composition/tests/utils/utils.ts (3)
  • normalizeSubgraphSuccess (40-47)
  • schemaToSortedNormalizedString (27-29)
  • normalizeString (19-21)
composition/src/router-compatibility-version/router-compatibility-version.ts (1)
  • ROUTER_COMPATIBILITY_VERSION_ONE (3-3)
composition/tests/v1/utils/utils.ts (2)
  • schemaQueryDefinition (94-97)
  • baseDirectiveDefinitionsWithSemanticNonNull (37-45)
composition/src/ast/utils.ts (1)
  • parse (272-274)
composition/src/v1/normalization/directive-definition-data.ts (4)
composition/src/schema-building/types.ts (2)
  • DirectiveDefinitionData (41-50)
  • ArgumentData (30-34)
composition/src/utils/string-constants.ts (14)
  • INPUT_OBJECT_UPPER (72-72)
  • ONE_OF (102-102)
  • CHANNEL (12-12)
  • PROVIDER_ID (112-112)
  • DEFAULT_EDFS_PROVIDER_ID (21-21)
  • FIELD_DEFINITION_UPPER (54-54)
  • EDFS_REDIS_PUBLISH (36-36)
  • CHANNELS (13-13)
  • EDFS_REDIS_SUBSCRIBE (37-37)
  • OBJECT_UPPER (105-105)
  • REQUIRE_FETCH_REASONS (120-120)
  • LEVELS (80-80)
  • INT_SCALAR (74-74)
  • SEMANTIC_NON_NULL (132-132)
composition/src/v1/utils/constants.ts (6)
  • ONE_OF_DEFINITION (650-655)
  • REQUIRED_STRING_TYPE_NODE (107-110)
  • EDFS_REDIS_PUBLISH_DEFINITION (427-448)
  • EDFS_REDIS_SUBSCRIBE_DEFINITION (451-478)
  • REQUIRE_FETCH_REASONS_DEFINITION (676-681)
  • SEMANTIC_NON_NULL_DEFINITION (721-752)
composition/src/ast/utils.ts (1)
  • stringToNamedTypeNode (100-105)
composition/src/v1/federation/federation-factory.ts (8)
composition/src/utils/string-constants.ts (1)
  • ONE_OF (102-102)
composition/src/v1/utils/constants.ts (1)
  • ONE_OF_DEFINITION (650-655)
composition/src/utils/types.ts (1)
  • InvalidRequiredInputValueData (33-37)
composition/src/schema-building/ast.ts (1)
  • MutableInputValueNode (108-115)
composition/src/types/types.ts (1)
  • FieldName (5-5)
composition/src/schema-building/utils.ts (1)
  • isTypeRequired (124-126)
composition/src/errors/errors.ts (1)
  • oneOfRequiredFieldsError (1666-1675)
composition/src/v1/warnings/warnings.ts (1)
  • singleFederatedInputFieldOneOfWarning (190-204)
composition/src/v1/normalization/normalization-factory.ts (7)
composition/src/utils/string-constants.ts (2)
  • ONE_OF (102-102)
  • SEMANTIC_NON_NULL (132-132)
composition/src/v1/utils/constants.ts (2)
  • ONE_OF_DEFINITION (650-655)
  • SEMANTIC_NON_NULL_DEFINITION (721-752)
composition/src/errors/errors.ts (1)
  • oneOfRequiredFieldsError (1666-1675)
composition/src/types/types.ts (1)
  • FieldName (5-5)
composition/src/schema-building/utils.ts (1)
  • isTypeRequired (124-126)
composition/src/v1/warnings/warnings.ts (1)
  • singleSubgraphInputFieldOneOfWarning (174-188)
composition/src/utils/utils.ts (1)
  • getFirstEntry (251-257)
🪛 Biome (2.1.2)
composition/src/v1/federation/federation-factory.ts

[error] 1950-1950: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1951-1951: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1952-1952: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1953-1953: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1954-1954: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)

composition/src/v1/normalization/normalization-factory.ts

[error] 3480-3480: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 3485-3485: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 3486-3486: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: integration_test (./events)
  • GitHub Check: integration_test (./. ./fuzzquery ./lifecycle ./modules)
  • GitHub Check: build_test
🔇 Additional comments (25)
composition/src/utils/string-constants.ts (3)

102-102: Add ONE_OF constant — OK

Name matches spec and usage elsewhere.


190-190: Mark @OneOf as non‑repeatable — OK

This aligns with its spec semantics and repo’s expectations.


172-172: Persist @OneOf to clients — verified

ONE_OF is in PERSISTED_CLIENT_DIRECTIVES and is registered where persisted directive definitions are assembled (composition/src/utils/string-constants.ts:172; composition/src/schema-building/utils.ts — getClientPersistedDirectiveNodes; composition/src/v1/federation/federation-factory.ts — persistedDirectiveDefinitionByDirectiveName includes ONE_OF_DEFINITION).

composition/tests/v1/directives/semantic-non-null.test.ts (3)

21-21: Import normalizeSubgraphSuccess — OK

Import is used; no issues.


25-25: Import test directive block — OK

Matches usage in expectations.


104-119: Happy‑path normalization assertion — OK

Stable, uses normalized/lexicographic helpers; assertion is deterministic.

composition/src/errors/types.ts (1)

2-2: New imports — OK

composition/src/errors/errors.ts (1)

12-12: Import OneOfRequiredFieldsErrorParams — OK

composition/src/v1/normalization/utils.ts (3)

40-41: Import ONE_OF_DEFINITION_DATA — OK


74-75: Import ONE_OF — OK


411-411: Register @OneOf in directive definition datas — definition not found; verify location/repeatability

rg returned no matches for ONE_OF_DEFINITION_DATA / name: ONE_OF / locations: INPUT_OBJECT / isRepeatable: false. Either point to the file/path where ONE_OF_DEFINITION_DATA is defined or confirm the directive is declared with locations: [INPUT_OBJECT] and isRepeatable: false.

composition/src/v1/utils/constants.ts (2)

71-71: Import ONE_OF — OK


517-517: Expose ONE_OF in built‑ins — OK

Keeps name discoverable across flows.

composition/src/v1/warnings/warnings.ts (1)

173-187: LGTM! Clear and actionable warning messages.

The warning function correctly guides users to remove @OneOf when only a single optional field is present, suggesting to make the field required instead.

composition/tests/v1/directives/one-of.test.ts (2)

21-102: Comprehensive test coverage for @OneOf normalization.

The tests effectively validate error scenarios for required fields and warning conditions for single-field @OneOf definitions.


346-364: Good test coverage for cross-subgraph @OneOf validation.

The tests correctly verify that @OneOf constraints are enforced across federated subgraphs.

composition/src/v1/normalization/normalization-factory.ts (2)

68-71: Proper integration of new directive definitions.

The @OneOf and @semanticNonNull directive definitions are correctly imported and will be added to the schema when referenced.


3438-3452: Good conditional loading of directive definitions.

The code correctly adds directive definitions only when they are actually referenced in the schema, avoiding unnecessary bloat.

composition/src/v1/federation/federation-factory.ts (4)

273-274: Registering ONE_OF in persisted directives looks good.

Adding [ONE_OF, ONE_OF_DEFINITION] ensures @OneOf instances are carried through during federation. No issues spotted here.


2013-2016: Message text tweak LGTM.

“Input field” capitalization aligns with surrounding messages.


1738-1761: Repeatable directive validation flows are correct.

Flattening persisted directives and guarding non‑repeatables via the stored definition is the right approach; it will honor @OneOf’s non‑repeatability once the constant is fixed.


269-279: ONE_OF omission is safe — graphql v16.9.0 provides @OneOf

package.json shows graphql overridden to 16.9.0, and graphql-js v16.9.0 implemented the built‑in @OneOf directive, so leaving ONE_OF out of persistedDirectiveDefinitions will not remove @OneOf at runtime/router SDL. (github.com)

If you need to support older graphql versions or want the router SDL to always embed the directive definition, add ONE_OF to persistedDirectiveDefinitions in composition/src/v1/federation/federation-factory.ts (lines ~269–279).

composition/src/v1/normalization/directive-definition-data.ts (3)

692-726: SEMANTIC_NON_NULL data restoration LGTM.

Levels type/default align with the corresponding definition; non‑repeatable on FIELD_DEFINITION is consistent.


585-647: Restored EDFS Redis and RequireFetchReasons directive datas look consistent.

Argument shapes, defaults, locations, and repeatability match the imported SDL definitions.

Also applies to: 649-657


21-21: Imports updated correctly for ONE_OF.

No issues with the added imports.

Also applies to: 81-81

@Aenimus Aenimus merged commit 9a55be8 into main Sep 17, 2025
42 of 44 checks passed
@Aenimus Aenimus deleted the david/eng-8173-add-one-of-composition-support branch September 17, 2025 15:36
@Noroth Noroth mentioned this pull request Sep 30, 2025
5 tasks
@coderabbitai coderabbitai bot mentioned this pull request Sep 30, 2025
5 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants