pkg/list: add list builtins for OpenAPI features
MaxItems, MinItems, UniqueItems, and Contains
Also added conversion to OpenAPI (except for
Contains, which is no supported by the latter).
Change-Id: I2edf4e8572acc08e8ced4a61918dff091325f3a0
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/2642
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cue/builtin.go b/cue/builtin.go
index 08fdcfc..939c3d1 100644
--- a/cue/builtin.go
+++ b/cue/builtin.go
@@ -143,7 +143,7 @@
Params: []kind{listKind},
Result: intKind,
Func: func(c *callCtxt) {
- iter := c.list(0)
+ iter := c.iter(0)
if !iter.Next() {
c.ret = &top{baseValue{c.src}}
return
@@ -161,7 +161,7 @@
Params: []kind{stringKind | bytesKind | listKind | structKind},
Result: intKind,
Func: func(c *callCtxt) {
- iter := c.list(0)
+ iter := c.iter(0)
d := []dValue{}
for iter.Next() {
d = append(d, dValue{iter.Value().path.v, false})
@@ -482,7 +482,21 @@
return b
}
-func (c *callCtxt) list(i int) (a Iterator) {
+func (c *callCtxt) list(i int) (a []Value) {
+ arg := c.args[i]
+ x := newValueRoot(c.ctx, arg)
+ v, err := x.List()
+ if err != nil {
+ c.invalidArgType(c.args[i], i, "list", err)
+ return a
+ }
+ for v.Next() {
+ a = append(a, v.Value())
+ }
+ return a
+}
+
+func (c *callCtxt) iter(i int) (a Iterator) {
arg := c.args[i]
x := newValueRoot(c.ctx, arg)
v, err := x.List()
diff --git a/cue/builtins.go b/cue/builtins.go
index 1a73490..ca1e556 100644
--- a/cue/builtins.go
+++ b/cue/builtins.go
@@ -19,6 +19,7 @@
"math/bits"
"path"
"regexp"
+ "sort"
"strconv"
"strings"
"text/tabwriter"
@@ -466,6 +467,64 @@
},
}},
},
+ "list": &builtinPkg{
+ native: []*builtin{{
+ Name: "MinItems",
+ Params: []kind{listKind, intKind},
+ Result: boolKind,
+ Func: func(c *callCtxt) {
+ a, n := c.list(0), c.int(1)
+ c.ret = func() interface{} {
+ return len(a) <= n
+ }()
+ },
+ }, {
+ Name: "MaxItems",
+ Params: []kind{listKind, intKind},
+ Result: boolKind,
+ Func: func(c *callCtxt) {
+ a, n := c.list(0), c.int(1)
+ c.ret = func() interface{} {
+ return len(a) <= n
+ }()
+ },
+ }, {
+ Name: "UniqueItems",
+ Params: []kind{listKind},
+ Result: boolKind,
+ Func: func(c *callCtxt) {
+ a := c.list(0)
+ c.ret = func() interface{} {
+ b := []string{}
+ for _, v := range a {
+ b = append(b, fmt.Sprint(v))
+ }
+ sort.Strings(b)
+ for i := 1; i < len(b); i++ {
+ if b[i-1] == b[i] {
+ return false
+ }
+ }
+ return true
+ }()
+ },
+ }, {
+ Name: "Contains",
+ Params: []kind{listKind, topKind},
+ Result: boolKind,
+ Func: func(c *callCtxt) {
+ a, v := c.list(0), c.value(1)
+ c.ret = func() interface{} {
+ for _, w := range a {
+ if v.Equals(w) {
+ return true
+ }
+ }
+ return false
+ }()
+ },
+ }},
+ },
"math": &builtinPkg{
native: []*builtin{{
Name: "MaxExp",
diff --git a/cue/gen.go b/cue/gen.go
index 3fd9e80..3d5a1c8 100644
--- a/cue/gen.go
+++ b/cue/gen.go
@@ -398,6 +398,8 @@
return "strList"
case "[]byte":
return "bytes"
+ case "[]cue.Value":
+ return "list"
case "io.Reader":
return "reader"
case "time.Time":
diff --git a/doc/tutorial/basics/bottom.md b/doc/tutorial/basics/bottom.md
index 2a36afa..4cdf814 100644
--- a/doc/tutorial/basics/bottom.md
+++ b/doc/tutorial/basics/bottom.md
@@ -31,8 +31,8 @@
<!-- result -->
`$ cue eval -i bottom.cue`
```
+list: [0, 1, 2]
a: _|_ /* conflicting values 4 and 5 */
l: [1, _|_ /* conflicting values 2 and 3 */]
-list: [0, 1, 2]
val: _|_ /* index 3 out of bounds */
```
diff --git a/encoding/openapi/build.go b/encoding/openapi/build.go
index 568d643..bf50916 100644
--- a/encoding/openapi/build.go
+++ b/encoding/openapi/build.go
@@ -499,6 +499,44 @@
// schema: an array instance is valid if at least one element matches
// this schema.
func (b *builder) array(v cue.Value) {
+
+ switch op, a := v.Expr(); op {
+ case cue.CallOp:
+ name := fmt.Sprint(a[0])
+ switch name {
+ case "list.UniqueItems":
+ if len(a) != 1 {
+ b.failf(v, "builtin %v may only be used without arguments", name)
+ }
+ b.setFilter("Schema", "uniqueItems", true)
+ return
+
+ case "list.MinItems":
+ if len(a) != 2 {
+ b.failf(v, "builtin %v must be called with one argument", name)
+ }
+ b.setFilter("Schema", "minItems", b.int(a[1]))
+ return
+
+ case "list.MaxItems":
+ if len(a) != 2 {
+ b.failf(v, "builtin %v must be called with one argument", name)
+ }
+ b.setFilter("Schema", "maxItems", 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
+ }
+
// Possible conjuncts:
// - one list (CUE guarantees merging all conjuncts)
// - no cap: is unified with list
diff --git a/encoding/openapi/testdata/array.cue b/encoding/openapi/testdata/array.cue
index 3261f81..9f28a52 100644
--- a/encoding/openapi/testdata/array.cue
+++ b/encoding/openapi/testdata/array.cue
@@ -1,6 +1,12 @@
+import "list"
+
Arrays: {
bar?: [...MyEnum]
foo?: [...MyStruct]
+
+ baz?: list.UniqueItems()
+
+ qux?: list.MinItems(1) & list.MaxItems(3)
}
Arrays: {
diff --git a/encoding/openapi/testdata/array.json b/encoding/openapi/testdata/array.json
index 3508df8..976a6be 100644
--- a/encoding/openapi/testdata/array.json
+++ b/encoding/openapi/testdata/array.json
@@ -38,6 +38,15 @@
}
}
}
+ },
+ "baz": {
+ "type": "array",
+ "uniqueItems": true
+ },
+ "qux": {
+ "type": "array",
+ "minItems": 1,
+ "maxItems": 3
}
}
},
diff --git a/pkg/list/list.go b/pkg/list/list.go
new file mode 100644
index 0000000..8955103
--- /dev/null
+++ b/pkg/list/list.go
@@ -0,0 +1,59 @@
+// 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 list contains functions for manipulating and examining lists.
+package list
+
+import (
+ "fmt"
+ "sort"
+
+ "cuelang.org/go/cue"
+)
+
+// MinItems reports whether a has at least n items.
+func MinItems(a []cue.Value, n int) bool {
+ return len(a) <= n
+}
+
+// MaxItems reports whether a has at most n items.
+func MaxItems(a []cue.Value, n int) bool {
+ return len(a) <= n
+}
+
+// UniqueItems reports whether all elements in the list are unique.
+func UniqueItems(a []cue.Value) bool {
+ b := []string{}
+ for _, v := range a {
+ b = append(b, fmt.Sprint(v))
+ }
+ sort.Strings(b)
+ for i := 1; i < len(b); i++ {
+ if b[i-1] == b[i] {
+ return false
+ }
+ }
+ return true
+}
+
+// Contains reports whether v is contained in a. The value must be a
+// comparable value.
+func Contains(a []cue.Value, v cue.Value) bool {
+ for _, w := range a {
+ if v.Equals(w) {
+ return true
+ }
+ }
+ return false
+}