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/cue/builtin_test.go b/cue/builtin_test.go
index b2a487f..3248f5d 100644
--- a/cue/builtin_test.go
+++ b/cue/builtin_test.go
@@ -255,6 +255,21 @@
test("strings", `strings.MinRunes(0) & "e"`),
`_|_(invalid value "e" (does not satisfy strings.MinRunes(0)))`,
}, {
+ test("struct", `struct.MinFields(0) & ""`),
+ `_|_(conflicting values MinFields (0) and "" (mismatched types struct and string))`,
+ }, {
+ test("struct", `struct.MinFields(0) & {a: 1}`),
+ `{a: 1}`,
+ }, {
+ test("struct", `struct.MinFields(2) & {a: 1}`),
+ `_|_(invalid value {a: 1} (does not satisfy struct.MinFields(2)))`,
+ }, {
+ test("struct", `struct.MaxFields(0) & {a: 1}`),
+ `_|_(invalid value {a: 1} (does not satisfy struct.MaxFields(0)))`,
+ }, {
+ test("struct", `struct.MaxFields(2) & {a: 1}`),
+ `{a: 1}`,
+ }, {
test("math/bits", `bits.And(0x10000000000000F0E, 0xF0F7)`), `6`,
}, {
test("math/bits", `bits.Or(0x100000000000000F0, 0x0F)`),
diff --git a/cue/builtins.go b/cue/builtins.go
index b75ddc0..db27401 100644
--- a/cue/builtins.go
+++ b/cue/builtins.go
@@ -2238,6 +2238,39 @@
},
}},
},
+ "struct": &builtinPkg{
+ native: []*builtin{{
+ Name: "MinFields",
+ Params: []kind{structKind, intKind},
+ Result: boolKind,
+ Func: func(c *callCtxt) {
+ object, n := c.structVal(0), c.int(1)
+ c.ret, c.err = func() (interface{}, error) {
+ iter := object.Fields(Hidden(false), Optional(false))
+ count := 0
+ for iter.Next() {
+ count++
+ }
+ return count >= n, nil
+ }()
+ },
+ }, {
+ Name: "MaxFields",
+ Params: []kind{structKind, intKind},
+ Result: boolKind,
+ Func: func(c *callCtxt) {
+ object, n := c.structVal(0), c.int(1)
+ c.ret, c.err = func() (interface{}, error) {
+ iter := object.Fields(Hidden(false), Optional(false))
+ count := 0
+ for iter.Next() {
+ count++
+ }
+ return count <= n, nil
+ }()
+ },
+ }},
+ },
"text/tabwriter": &builtinPkg{
native: []*builtin{{
Name: "Write",
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
diff --git a/pkg/struct/struct.go b/pkg/struct/struct.go
new file mode 100644
index 0000000..00094e1
--- /dev/null
+++ b/pkg/struct/struct.go
@@ -0,0 +1,46 @@
+// Copyright 2019 CUE Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package struct defines utilities for struct types.
+package structs
+
+import (
+ "cuelang.org/go/cue"
+)
+
+// MinFields validates the minimum number of fields that are part of a struct.
+//
+// Only fields that are part of the data model count. This excludes hidden
+// fields, optional fields, and definitions.
+func MinFields(object *cue.Struct, n int) (bool, error) {
+ iter := object.Fields(cue.Hidden(false), cue.Optional(false))
+ count := 0
+ for iter.Next() {
+ count++
+ }
+ return count >= n, nil
+}
+
+// MaxFields validates the maximum number of fields that are part of a struct.
+//
+// Only fields that are part of the data model count. This excludes hidden
+// fields, optional fields, and definitions.
+func MaxFields(object *cue.Struct, n int) (bool, error) {
+ iter := object.Fields(cue.Hidden(false), cue.Optional(false))
+ count := 0
+ for iter.Next() {
+ count++
+ }
+ return count <= n, nil
+}