pkg/struct: add Min/MaxFields builtins

- also define their mappings to OpenAPI

Issue #56

Change-Id: I3db075ea25d5e5df8bf3a003fd881862e73be683
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/2685
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/encoding/openapi/build.go b/encoding/openapi/build.go
index 70ab78c..f6ddbbc 100644
--- a/encoding/openapi/build.go
+++ b/encoding/openapi/build.go
@@ -466,6 +466,36 @@
 	// object composed of the same type, if a property is required and set to a
 	// constant value for each type, it is a discriminator.
 
+	switch op, a := v.Expr(); op {
+	case cue.CallOp:
+		name := fmt.Sprint(a[0])
+		switch name {
+		case "struct.MinFields":
+			if len(a) != 2 {
+				b.failf(v, "builtin %v must be called with one argument", name)
+			}
+			b.setFilter("Schema", "minProperties", b.int(a[1]))
+			return
+
+		case "struct.MaxFields":
+			if len(a) != 2 {
+				b.failf(v, "builtin %v must be called with one argument", name)
+			}
+			b.setFilter("Schema", "maxProperties", b.int(a[1]))
+			return
+
+		default:
+			b.failf(v, "builtin %v not supported in OpenAPI", name)
+		}
+
+	case cue.NoOp:
+		// TODO: extract format from specific type.
+
+	default:
+		b.failf(v, "unsupported op %v for number type", op)
+		return
+	}
+
 	required := []string{}
 	for i, _ := v.Fields(cue.Optional(false), cue.Hidden(false)); i.Next(); {
 		required = append(required, i.Label())
diff --git a/encoding/openapi/openapi_test.go b/encoding/openapi/openapi_test.go
index 75e9fcc..9fbe314 100644
--- a/encoding/openapi/openapi_test.go
+++ b/encoding/openapi/openapi_test.go
@@ -51,6 +51,10 @@
 		"array.json",
 		defaultConfig,
 	}, {
+		"struct.cue",
+		"struct.json",
+		defaultConfig,
+	}, {
 		"strings.cue",
 		"strings.json",
 		defaultConfig,
diff --git a/encoding/openapi/testdata/struct.cue b/encoding/openapi/testdata/struct.cue
new file mode 100644
index 0000000..875c714
--- /dev/null
+++ b/encoding/openapi/testdata/struct.cue
@@ -0,0 +1,8 @@
+import "struct"
+
+MyMap: struct.MinFields(4)
+MyMap: struct.MaxFields(9)
+
+MyType: {
+	map: MyMap
+}
diff --git a/encoding/openapi/testdata/struct.json b/encoding/openapi/testdata/struct.json
new file mode 100644
index 0000000..a4c2b13
--- /dev/null
+++ b/encoding/openapi/testdata/struct.json
@@ -0,0 +1,24 @@
+{
+   "openapi": "3.0.0",
+   "info": {},
+   "components": {
+      "schemas": {
+         "MyMap": {
+            "type": "object",
+            "minProperties": 4,
+            "maxProperties": 9
+         },
+         "MyType": {
+            "type": "object",
+            "required": [
+               "map"
+            ],
+            "properties": {
+               "map": {
+                  "$ref": "#/components/schemas/MyMap"
+               }
+            }
+         }
+      }
+   }
+}
\ No newline at end of file