encoding/openapi: define strict order on fields

This gives nicer results, but also makes the diffs
when supporting structural OpenAPI (for CRDs)
more pallatable.

The prepend option is then no longer necessary.

This also exposed a bug where the wrong fields
were uniqued in the simplify stage.

Change-Id: Ib73df14800447cf2d63adbd77da7fb01f3e73498
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/2802
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/encoding/openapi/build.go b/encoding/openapi/build.go
index ba64a67..c491a1c 100644
--- a/encoding/openapi/build.go
+++ b/encoding/openapi/build.go
@@ -191,15 +191,47 @@
 		}
 		if len(doc) > 0 {
 			str := strings.TrimSpace(strings.Join(doc, "\n\n"))
-			schema.prepend("description", str)
+			schema.Set("description", str)
 		}
 	}
 
 	simplify(c, schema)
 
+	sortSchema(schema)
+
 	return schema
 }
 
+func sortSchema(s *oaSchema) {
+	sort.Slice(s.kvs, func(i, j int) bool {
+		pi := fieldOrder[s.kvs[i].Key]
+		pj := fieldOrder[s.kvs[j].Key]
+		if pi != pj {
+			return pi > pj
+		}
+		return s.kvs[i].Key < s.kvs[j].Key
+	})
+}
+
+var fieldOrder = map[string]int{
+	"description":      31,
+	"type":             30,
+	"format":           29,
+	"required":         28,
+	"properties":       27,
+	"minProperties":    26,
+	"maxProperties":    25,
+	"minimum":          24,
+	"exclusiveMinimum": 23,
+	"maximum":          22,
+	"exclusiveMaximum": 21,
+	"minItems":         18,
+	"maxItems":         17,
+	"minLength":        16,
+	"maxLength":        15,
+	"items":            14,
+}
+
 func (b *builder) resolve(v cue.Value) cue.Value {
 	// Cycles are not allowed when expanding references. Right now we just
 	// cap the depth of evaluation at 30.
diff --git a/encoding/openapi/openapi.go b/encoding/openapi/openapi.go
index 2622600..87d4067 100644
--- a/encoding/openapi/openapi.go
+++ b/encoding/openapi/openapi.go
@@ -28,7 +28,7 @@
 
 	// ReferenceFunc allows users to specify an alternative representation
 	// for references. An empty string tells the generator to expand the type
-	// in place and not generate a schema for that entity, if applicable.
+	// in place and, if applicable, not generate a schema for that entity.
 	ReferenceFunc func(inst *cue.Instance, path []string) string
 
 	// DescriptionFunc allows rewriting a description associated with a certain
diff --git a/encoding/openapi/orderedmap.go b/encoding/openapi/orderedmap.go
index 91a2ec4..9e5113a 100644
--- a/encoding/openapi/orderedmap.go
+++ b/encoding/openapi/orderedmap.go
@@ -33,10 +33,6 @@
 	return m.kvs
 }
 
-func (m *OrderedMap) prepend(key string, value interface{}) {
-	m.kvs = append([]KeyValue{{key, value}}, m.kvs...)
-}
-
 // 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.
@@ -66,6 +62,16 @@
 	return false
 }
 
+// exists reports whether a key-value pair exists for the given key.
+func (m *OrderedMap) getMap(key string) *OrderedMap {
+	for _, v := range m.kvs {
+		if v.Key == key {
+			return v.Value.(*OrderedMap)
+		}
+	}
+	return nil
+}
+
 // MarshalJSON implements json.Marshaler.
 func (m *OrderedMap) MarshalJSON() (b []byte, err error) {
 	// This is a pointer receiever to enforce that we only store pointers to
@@ -76,15 +82,15 @@
 		if i > 0 {
 			b = append(b, ",\n"...)
 		}
-		key, err := json.Marshal(v.Key)
-		if je, ok := err.(*json.MarshalerError); ok {
+		key, ferr := json.Marshal(v.Key)
+		if je, ok := ferr.(*json.MarshalerError); ok {
 			return nil, je.Err
 		}
 		b = append(b, key...)
 		b = append(b, ": "...)
 
 		value, jerr := json.Marshal(v.Value)
-		if je, ok := err.(*json.MarshalerError); ok {
+		if je, ok := jerr.(*json.MarshalerError); ok {
 			err = jerr
 			value, _ = json.Marshal(je.Err.Error())
 		}
diff --git a/encoding/openapi/testdata/array.json b/encoding/openapi/testdata/array.json
index 976a6be..570d659 100644
--- a/encoding/openapi/testdata/array.json
+++ b/encoding/openapi/testdata/array.json
@@ -9,12 +9,12 @@
                "bar": {
                   "type": "array",
                   "items": {
+                     "default": "1",
                      "enum": [
                         "1",
                         "2",
                         "3"
-                     ],
-                     "default": "1"
+                     ]
                   }
                },
                "foo": {
@@ -28,12 +28,12 @@
                         "e": {
                            "type": "array",
                            "items": {
+                              "default": "1",
                               "enum": [
                                  "1",
                                  "2",
                                  "3"
-                              ],
-                              "default": "1"
+                              ]
                            }
                         }
                      }
@@ -52,12 +52,12 @@
          },
          "MyEnum": {
             "description": "MyEnum",
+            "default": "1",
             "enum": [
                "1",
                "2",
                "3"
-            ],
-            "default": "1"
+            ]
          },
          "MyStruct": {
             "description": "MyStruct",
@@ -69,12 +69,12 @@
                "e": {
                   "type": "array",
                   "items": {
+                     "default": "1",
                      "enum": [
                         "1",
                         "2",
                         "3"
-                     ],
-                     "default": "1"
+                     ]
                   }
                }
             }
diff --git a/encoding/openapi/testdata/nums.json b/encoding/openapi/testdata/nums.json
index 65c5564..fb6efba 100644
--- a/encoding/openapi/testdata/nums.json
+++ b/encoding/openapi/testdata/nums.json
@@ -8,6 +8,7 @@
             "multipleOf": 5
          },
          "neq": {
+            "type": "number",
             "not": {
                "type": "number",
                "allOff": [
@@ -20,8 +21,7 @@
                      "maximum": 4
                   }
                ]
-            },
-            "type": "number"
+            }
          }
       }
    }
diff --git a/encoding/openapi/testdata/openapi.json b/encoding/openapi/testdata/openapi.json
index 27c1e36..c60e19e 100644
--- a/encoding/openapi/testdata/openapi.json
+++ b/encoding/openapi/testdata/openapi.json
@@ -14,8 +14,8 @@
                   ],
                   "properties": {
                      "port": {
-                        "$ref": "#/components/schemas/Port",
-                        "type": "object"
+                        "type": "object",
+                        "$ref": "#/components/schemas/Port"
                      },
                      "foo": {
                         "allOf": [
diff --git a/encoding/openapi/testdata/strings.json b/encoding/openapi/testdata/strings.json
index cff15f4..d62bfff 100644
--- a/encoding/openapi/testdata/strings.json
+++ b/encoding/openapi/testdata/strings.json
@@ -21,11 +21,11 @@
                   "pattern": "foo.*bar"
                },
                "myAntiPattern": {
+                  "type": "string",
                   "not": {
                      "type": "string",
                      "pattern": "foo.*bar"
-                  },
-                  "type": "string"
+                  }
                }
             }
          }
diff --git a/encoding/openapi/types.go b/encoding/openapi/types.go
index e30a4ba..d46fd2b 100644
--- a/encoding/openapi/types.go
+++ b/encoding/openapi/types.go
@@ -86,7 +86,7 @@
 				continue
 			}
 		}
-		pairs[i] = pairs[k]
+		pairs[k] = pairs[i]
 		k++
 	}
 	t.kvs = pairs[:k]