Skip to content

Bug: External $ref in components.parameters renders incorrectly during bundling #501

@rriski

Description

@rriski

Note

AI disclosure: issue found and reported by Claude. I have verified it independently.

Root Cause Analysis

Summary

When components.parameters contains an external $ref (e.g., $ref: "./params.yaml#/FilterParam"), the bundled output is malformed because NewParameter() copies empty field values from the low-level struct without resolving the reference.

Versions Affected

Version Status
libopenapi v0.28.1 / vacuum v0.20.0 ✅ Works
libopenapi v0.30.4+ / vacuum v0.21.0+ ❌ Broken

Minimal Reproduction

Directory structure:

test/
├── openapi.yaml
└── params.yaml

openapi.yaml:

openapi: 3.0.4
info:
  title: Test
  version: 1.0.0
paths:
  /test:
    get:
      parameters:
        - $ref: "#/components/parameters/FilterParam"
      responses:
        "200":
          description: OK
components:
  parameters:
    FilterParam:
      $ref: "./params.yaml#/FilterParam"

params.yaml:

FilterParam:
  name: filter
  in: query
  required: false
  description: Filter expression
  schema:
    type: string

Command:

cd test && vacuum bundle openapi.yaml -o

Expected output:

components:
  parameters:
    FilterParam:
      name: filter
      in: query
      required: false
      description: Filter expression
      schema:
        type: string

Actual output:

components:
  parameters:
    FilterParam:
      schema:
        description: Filter expression
      examples:
        name: {}
        in: {}
        required: {}
        description: {}
        schema: {}
      content:
        name: {}
        in: {}
        required: {}
        description: {}
        schema: {}

Code Flow

  1. Low-level parsing - When components.parameters.FilterParam is parsed, the low-level Parameter struct correctly identifies this as a reference:

    • param.IsReference()true
    • param.GetReference()"./params.yaml#/FilterParam"
    • param.Name.Value"" (empty - actual value is in the referenced file)
  2. High-level construction - NewParameter() copies the empty values without resolving the reference:

    func NewParameter(param *low.Parameter) *Parameter {
        p := new(Parameter)
        p.low = param
        p.Name = param.Name.Value        // "" (empty)
        p.In = param.In.Value            // "" (empty)
        p.Description = param.Description.Value  // "" (empty)
        // ...
        return p
    }

    Note: The high-level Parameter has a Reference field but NewParameter() never sets it from param.GetReference().

  3. Bundling - When NodeBuilder.Render() runs with Resolve=true:

    • It sees n.Low.IsReference() = true and n.Resolve = true
    • It correctly skips rendering as a $ref node (line 296-298)
    • But then it renders the high-level struct fields which are all empty
    • The empty Name, In, Required, Description fields get incorrectly placed under schema, examples, and content keys

Bisects to 5620619

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions