Skip to content

fix(openapi3): anyOf with $ref + inline object incorrectly imported as model instead of union#10041

Merged
baywet merged 2 commits intomainfrom
copilot/fix-import-tool-composition-handling
Mar 16, 2026
Merged

fix(openapi3): anyOf with $ref + inline object incorrectly imported as model instead of union#10041
baywet merged 2 commits intomainfrom
copilot/fix-import-tool-composition-handling

Conversation

Copy link
Contributor

Copilot AI commented Mar 16, 2026

When anyOf contains a $ref alongside an inline object schema, the importer was discarding the $ref variant and emitting only a model from the object — losing the union semantics entirely.

Root cause: getTypeSpecKind and unwrapSingleAnyOfOneOf filtered out $ref members when counting "meaningful inline members". With exactly one inline object remaining, they concluded the schema was a plain model, never checking whether $ref members were also present as union variants.

Fix:

  • Both functions now check hasRefMembers before applying the single-inline-object → model shortcut
  • If any $ref members exist alongside the inline object, the schema is correctly classified as a union
# Input — was producing `model VoiceIdsOrCustomVoice { id: string }`
VoiceIdsOrCustomVoice:
  anyOf:
    - $ref: '#/components/schemas/VoiceIdsShared'
    - type: object
      properties:
        id: { type: string }
// Now correctly produces:
union VoiceIdsOrCustomVoice {
  VoiceIdsShared,
  { id: string },
}

The single-inline-object → model unwrap still applies when no $ref members are present (e.g. anyOf: [{type: object, ...}, {type: null}]), preserving existing behavior.

Original prompt

This section details on the original issue you should resolve

<issue_title>import tool - composition of object + enum not handled correctly</issue_title>
<issue_description>The following OpenAPI description

components:
  schemas:
    VoiceIdsOrCustomVoice:
      title: Voice
      description: |
        A built-in voice name or a custom voice reference.
      anyOf:
        - $ref: '#/components/schemas/VoiceIdsShared'
        - type: object
          description: Custom voice reference.
          additionalProperties: false
          required:
            - id
          properties:
            id:
              type: string
    VoiceIdsShared:
      example: ash
      anyOf:
        - type: string
        - type: string
          enum:
            - alloy
            - ash
            - ballad
            - coral
            - echo
            - sage
            - shimmer
            - verse
            - marin
            - cedar

Results in the following TypeSpec definition after import

/** Custom voice reference. */
@summary("Voice")
model VoiceIdsOrCustomVoice {
  /** The custom voice ID, e.g. `voice_1234`. */
  id: string;
}

union VoiceIdsShared {
  string,

    | "alloy"
    | "ash"
    | "ballad"
    | "coral"
    | "echo"
    | "sage"
    | "shimmer"
    | "verse"
    | "marin"
    | "cedar",
}

But should really be

/** Custom voice reference. */
@summary("Voice")
union VoiceIdsOrCustomVoice {
  VoiceIdsShared,
  {
    id: string,
  },
}

union VoiceIdsShared {
  string,

    | "alloy"
    | "ash"
    | "ballad"
    | "coral"
    | "echo"
    | "sage"
    | "shimmer"
    | "verse"
    | "marin"
    | "cedar",
}

Interestingly enough, the following input OpenAPI description works as expected even though it's more complex.

components:
  schemas:
    VoiceIdsOrCustomVoice:
      title: Voice
      description: |
        A built-in voice name or a custom voice reference.
      anyOf:
        - allOf:
            - $ref: '#/components/schemas/VoiceIdsShared'
          type: string
        - type: object
          description: Custom voice reference.
          additionalProperties: false
          required:
            - id
          properties:
            id:
              type: string
    VoiceIdsShared:
      example: ash
      anyOf:
        - type: string
        - type: string
          enum:
            - alloy
            - ash
            - ballad
            - coral
            - echo
            - sage
            - shimmer
            - verse
            - marin
            - cedar
```</issue_description>

## Comments on the Issue (you are @copilot in this section)

<comments>
</comments>

📍 Connect Copilot coding agent with Jira, Azure Boards or Linear to delegate work to Copilot in one click without leaving your project management tool.

When importing an OpenAPI schema with anyOf containing a $ref alongside
an inline object, the importer was incorrectly treating it as a model.
The fix checks for $ref members before deciding to unwrap to a model.

Co-authored-by: baywet <7905502+baywet@users.noreply.github.com>
@microsoft-github-policy-service microsoft-github-policy-service bot added emitter:openapi3 Issues for @typespec/openapi3 emitter openapi3:converter Issues for @typespec/openapi3 openapi to typespec converter labels Mar 16, 2026
Copilot AI changed the title [WIP] Fix import tool handling of object and enum composition fix(openapi3): anyOf with $ref + inline object incorrectly imported as model instead of union Mar 16, 2026
Copilot AI requested a review from baywet March 16, 2026 17:06
@baywet baywet marked this pull request as ready for review March 16, 2026 17:33
@baywet baywet enabled auto-merge March 16, 2026 17:33
@pkg-pr-new
Copy link

pkg-pr-new bot commented Mar 16, 2026

Open in StackBlitz

npm i https://pkg.pr.new/@typespec/openapi3@10041

commit: 0c565a2

@azure-sdk
Copy link
Collaborator

You can try these changes here

🛝 Playground 🌐 Website 🛝 VSCode Extension

@baywet baywet added this pull request to the merge queue Mar 16, 2026
Merged via the queue into main with commit 941f190 Mar 16, 2026
30 checks passed
@baywet baywet deleted the copilot/fix-import-tool-composition-handling branch March 16, 2026 18:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

emitter:openapi3 Issues for @typespec/openapi3 emitter openapi3:converter Issues for @typespec/openapi3 openapi to typespec converter

Projects

None yet

Development

Successfully merging this pull request may close these issues.

import tool - composition of object + enum not handled correctly

4 participants