encoding/openapi: wrap $ref in allOf when needed.

It is unclear to me what the JSON schema spec says (and it
seems the current behavior is correct), but at least OpenAPI
expects $ref to be by itself.

Change-Id: Ic91f21eb16b1de342b99722ee45a91fb8844ed1a
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/6344
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/encoding/openapi/build.go b/encoding/openapi/build.go
index 05efb62..e3f5d17 100644
--- a/encoding/openapi/build.go
+++ b/encoding/openapi/build.go
@@ -1155,10 +1155,24 @@
 		t = &OrderedMap{}
 
 	case 1:
-		t = (*OrderedMap)(b.allOf[0])
+		hasRef := false
+		for _, e := range b.allOf[0].Elts {
+			if f, ok := e.(*ast.Field); ok {
+				name, _, _ := ast.LabelName(f.Label)
+				hasRef = hasRef || name == "$ref"
+			}
+		}
+		if !hasRef || b.singleFields == nil {
+			t = (*OrderedMap)(b.allOf[0])
+			break
+		}
+		fallthrough
 
 	default:
 		exprs := []ast.Expr{}
+		if t != nil {
+			exprs = append(exprs, (*ast.StructLit)(t))
+		}
 		for _, s := range b.allOf {
 			exprs = append(exprs, s)
 		}
@@ -1190,7 +1204,8 @@
 func (b *builder) addRef(v cue.Value, inst *cue.Instance, ref []string) {
 	name := b.ctx.makeRef(inst, ref)
 	b.addConjunct(func(b *builder) {
-		b.set("$ref", ast.NewString(path.Join("#", b.ctx.refPrefix, name)))
+		b.allOf = append(b.allOf, ast.NewStruct(
+			"$ref", ast.NewString(path.Join("#", b.ctx.refPrefix, name))))
 	})
 
 	if b.ctx.inst != inst {
diff --git a/encoding/openapi/testdata/refs.cue b/encoding/openapi/testdata/refs.cue
index 970fd6d..76d5499 100644
--- a/encoding/openapi/testdata/refs.cue
+++ b/encoding/openapi/testdata/refs.cue
@@ -11,3 +11,11 @@
 
 // ExcludedInt is not included in the output.
 #ExcludedInt: int
+
+#Type: {
+	a?: string
+	#BaseType
+}
+#BaseType: {
+	b: string
+}
diff --git a/encoding/openapi/testdata/refs.json b/encoding/openapi/testdata/refs.json
index e73e4ee..8c507ed 100644
--- a/encoding/openapi/testdata/refs.json
+++ b/encoding/openapi/testdata/refs.json
@@ -7,6 +7,18 @@
    "paths": {},
    "components": {
       "schemas": {
+         "BaseType": {
+            "type": "object",
+            "required": [
+               "b"
+            ],
+            "properties": {
+               "b": {
+                  "type": "string",
+                  "format": "string"
+               }
+            }
+         },
          "Keep": {
             "type": "object",
             "required": [
@@ -30,6 +42,20 @@
                   "type": "integer"
                }
             }
+         },
+         "Type": {
+            "type": "object",
+            "properties": {
+               "a": {
+                  "type": "string",
+                  "format": "string"
+               }
+            },
+            "allOf": [
+               {
+                  "$ref": "#/components/schemas/BaseType"
+               }
+            ]
          }
       }
    }