encoding/openapi: make API backwards compatible for Istio

This allows a smooth transition for Istio to move from
the old to new API.

Change-Id: If5f6a95a6a3c685a844757fd602974ec39a8969e
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/5320
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/encoding/openapi/openapi.go b/encoding/openapi/openapi.go
index 49f33fd..4f4a92f 100644
--- a/encoding/openapi/openapi.go
+++ b/encoding/openapi/openapi.go
@@ -107,7 +107,20 @@
 	return (*OrderedMap)(top), err
 }
 
-func (c *Config) compose(schemas *ast.StructLit) (*ast.StructLit, error) {
+func toCUE(name string, x interface{}) (v ast.Expr, err error) {
+	b, err := json.Marshal(x)
+	if err == nil {
+		v, err = cuejson.Extract(name, b)
+	}
+	if err != nil {
+		return nil, errors.Wrapf(err, token.NoPos,
+			"openapi: could not encode %s", name)
+	}
+	return v, nil
+
+}
+
+func (c *Config) compose(schemas *ast.StructLit) (x *ast.StructLit, err error) {
 	// Support of OrderedMap is mostly for backwards compatibility.
 	var info ast.Expr
 	switch x := c.Info.(type) {
@@ -120,13 +133,9 @@
 	case OrderedMap:
 		info = (*ast.StructLit)(&x)
 	default:
-		b, err := json.Marshal(x)
-		if err == nil {
-			info, err = cuejson.Extract("info", b)
-		}
+		info, err = toCUE("info section", x)
 		if err != nil {
-			return nil, errors.Wrapf(err, token.NoPos,
-				"openapi: could not encode info section")
+			return nil, err
 		}
 	}
 
diff --git a/encoding/openapi/orderedmap.go b/encoding/openapi/orderedmap.go
index a0313ac..9f8bdd0 100644
--- a/encoding/openapi/orderedmap.go
+++ b/encoding/openapi/orderedmap.go
@@ -15,7 +15,11 @@
 package openapi
 
 import (
+	"fmt"
+
 	"cuelang.org/go/cue/ast"
+	"cuelang.org/go/cue/literal"
+	"cuelang.org/go/cue/token"
 	"cuelang.org/go/internal/encoding/json"
 )
 
@@ -33,6 +37,54 @@
 	Value interface{}
 }
 
+// TODO: these functions are here to support backwards compatibility with Istio.
+// At some point, once this is removed from Istio, this can be removed.
+
+func fromLegacy(x interface{}) ast.Expr {
+	switch x := x.(type) {
+	case *OrderedMap:
+		return (*ast.StructLit)(x)
+	case []*OrderedMap:
+		a := make([]ast.Expr, len(x))
+		for i, v := range x {
+			a[i] = fromLegacy(v)
+		}
+		return ast.NewList(a...)
+	case string:
+		return ast.NewString(x)
+	case ast.Expr:
+		return x
+	default:
+		panic(fmt.Sprintf("unsupported type %T", x))
+	}
+}
+
+func toLegacy(x ast.Expr) interface{} {
+	switch x := x.(type) {
+	case *ast.StructLit:
+		return (*OrderedMap)(x)
+	case *ast.ListLit:
+		a := make([]*OrderedMap, len(x.Elts))
+		for i, v := range x.Elts {
+			e, ok := v.(*ast.StructLit)
+			if !ok {
+				return x
+			}
+			a[i] = (*OrderedMap)(e)
+		}
+		return a
+	case *ast.BasicLit:
+		if x.Kind == token.STRING {
+			str, err := literal.Unquote(x.Value)
+			if err != nil {
+				return x
+			}
+			return str
+		}
+	}
+	return x
+}
+
 func (m *OrderedMap) len() int {
 	return len(m.Elts)
 }
@@ -42,7 +94,7 @@
 	kvs := make([]KeyValue, len(m.Elts))
 	for i, e := range m.Elts {
 		kvs[i].Key = label(e)
-		kvs[i].Value = e.(*ast.Field).Value
+		kvs[i].Value = toLegacy(e.(*ast.Field).Value)
 	}
 	return kvs
 }
@@ -63,8 +115,27 @@
 
 // Set sets a key value pair. If a pair with the same key already existed, it
 // will be replaced with the new value. Otherwise, the new value is added to
-// the end.
-func (m *OrderedMap) Set(key string, expr ast.Expr) {
+// the end. The value must be of type string, ast.Expr, or *OrderedMap.
+//
+// Deprecated: use cuelang.org/go/cue/ast to manipulate ASTs.
+func (m *OrderedMap) Set(key string, x interface{}) {
+	switch x := x.(type) {
+	case *OrderedMap:
+		m.setExpr(key, (*ast.StructLit)(x))
+	case string:
+		m.setExpr(key, ast.NewString(x))
+	case ast.Expr:
+		m.setExpr(key, x)
+	default:
+		v, err := toCUE("Set", x)
+		if err != nil {
+			panic(err)
+		}
+		m.setExpr(key, v)
+	}
+}
+
+func (m *OrderedMap) setExpr(key string, expr ast.Expr) {
 	if f := m.find(key); f != nil {
 		f.Value = expr
 		return
@@ -75,6 +146,19 @@
 	})
 }
 
+// SetAll replaces existing key-value pairs with the given ones. The keys must
+// be unique.
+func (m *OrderedMap) SetAll(kvs []KeyValue) {
+	var a []ast.Decl
+	for _, kv := range kvs {
+		a = append(a, &ast.Field{
+			Label: ast.NewString(kv.Key),
+			Value: fromLegacy(kv.Value),
+		})
+	}
+	m.Elts = a
+}
+
 // exists reports whether a key-value pair exists for the given key.
 func (m *OrderedMap) exists(key string) bool {
 	return m.find(key) != nil