cue icon indicating copy to clipboard operation
cue copied to clipboard

CUE export openapi+yaml format not resolving references correctly

Open bojandjukic1 opened this issue 6 months ago • 6 comments

What version of CUE are you using (cue version)?

0.11.0

Does this issue reproduce with the latest stable release?

Yes

What did you do?

Tried to export the following minimal CUE file as an example

// test.cue
#TestBaseComponent : {
    propABase: string
}
#TestParentComponent: {
    propAParent: string
    testChild: #TestBaseComponent & {
        propABase: propAParent
    }
}

using command cue export .\test.cue --out openapi+yaml

What did you expect to see?

I expected that the propABase in the definition would be a string not a ref type

What did you see instead?

See the invalid syntax $ref: '#/components/schemas/TestParentComponent.propAParent' below

openapi: 3.0.0
info:
  title: Generated by cue.
  version: no version
paths: {}
components:
  schemas:
    TestBaseComponent:
      type: object
      required:
        - propABase
      properties:
        propABase:
          type: string
    TestParentComponent:
      type: object
      required:
        - propAParent
        - testChild
      properties:
        propAParent:
          type: string
        testChild:
          type: object
          properties:
            propABase:
              $ref: '#/components/schemas/TestParentComponent.propAParent'
          allOf:
            - $ref: '#/components/schemas/TestBaseComponent'
            - required:
                - propABase

bojandjukic1 avatar May 27 '25 05:05 bojandjukic1

Could you please explain why you expect propABase to be a string type rather than a reference? In the original CUE it does use a reference to propAParent, so the current output seems fine to me.

mvdan avatar May 27 '25 08:05 mvdan

What I mean is that the generated output is an invalid openAPI spec (which seems like it shouldn't be allowed?). As far as I understand, $ref: '#/components/schemas/TestParentComponent.propAParent is invalid because you need to point to a schema object and not a property. At least it was invalid for the tooling that was consuming the generated output (redocly).

bojandjukic1 avatar May 27 '25 09:05 bojandjukic1

Ah I see, thanks for the clarification. I'll let @rogpeppe chime in.

mvdan avatar May 27 '25 09:05 mvdan

There are a couple of issues I'd highlight here. Firstly, the generated reference is just incorrect: a period is not a valid separator in JSON Pointer, and the actual position of the properties is inside the properties object, so the reference would be more correctly written as #/components/schemas/TestParentComponent/properties/propAParent. Secondly, the original schema implies that propABase is the same as propAParent, but it's not actually possible for OpenAPI to express that constraint.

In general, I don't think it's a good idea to produce schemas that refer to non-definition objects, so I'd say that the most appropriate fix here is either to generate an independent definition and refer to that within both TestParentComponent and from TestBaseComponent, or to generate independent definitions for both.

The first approach might look like:

openapi: 3.0.0
info:
  title: Generated by cue.
  version: no version
paths: {}
components:
  schemas:
    TestBaseComponent:
      type: object
      required:
        - propABase
      properties:
        propABase:
          type: string
    TestParentComponent:
      type: object
      required:
        - propAParent
        - testChild
      properties:
        propAParent:
          $ref: "#/components/schemas/TestParentComponent.propAParent"
        testChild:
          type: object
          properties:
            propABase:
              $ref: '#/components/schemas/TestParentComponent.propAParent'
          allOf:
            - $ref: '#/components/schemas/TestBaseComponent'
            - required:
                - propABase
    TestParentComponent.propAParent:
      type: string

The second might look like this:

openapi: 3.0.0
info:
  title: Generated by cue.
  version: no version
paths: {}
components:
  schemas:
    TestBaseComponent:
      type: object
      required:
        - propABase
      properties:
        propABase:
          type: string
    TestParentComponent:
      type: object
      required:
        - propAParent
        - testChild
      properties:
        propAParent:
          type: string
        testChild:
          type: object
          properties:
            propABase:
              type: string
          allOf:
            - $ref: '#/components/schemas/TestBaseComponent'
            - required:
                - propABase

In this particular case, the second might seem more attractive, but that might not seem so if there ends up with significant duplication between the two sites.

rogpeppe avatar May 27 '25 14:05 rogpeppe

For the record, this is a contrived definition to demonstrate the point that an invalid spec was produced. In the real definition that it was inspired from, I was able to get around the issue after taking a step back and redefining things to split out the constraints from the implementation similar to what you've suggested. However I thought I'd at least highlight that it seems possible to produce valid CUE that results in invalid OpenAPI spec.

bojandjukic1 avatar May 27 '25 23:05 bojandjukic1

Indeed, and thank you for filing the issue!

mvdan avatar May 28 '25 05:05 mvdan