Skip to content

Commit ce195d2

Browse files
committed
pkg/strings: add MinRunes and MaxRunes
Also adds respective conversions for the OpenAPI encoding. cue/builtins.go is generated using go generate Change-Id: I3b709cb56bd59fbe08a0376d4b4760b9b21a2e3a Reviewed-on: https://cue-review.googlesource.com/c/cue/+/2625 Reviewed-by: Marcel van Lohuizen <[email protected]>
1 parent 0be096f commit ce195d2

File tree

7 files changed

+132
-3
lines changed

7 files changed

+132
-3
lines changed

cue/builtin_test.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,18 @@ func TestBuiltins(t *testing.T) {
164164
}, {
165165
test("strings", `strings.ToTitle("alpha")`),
166166
`"Alpha"`,
167+
}, {
168+
test("strings", `strings.MaxRunes(3) & "foo"`),
169+
`"foo"`,
170+
}, {
171+
test("strings", `strings.MaxRunes(3) & "quux"`),
172+
`_|_(invalid value "quux" (does not satisfy strings.MaxRunes(3)))`,
173+
}, {
174+
test("strings", `strings.MinRunes(1) & "e"`),
175+
`"e"`,
176+
}, {
177+
test("strings", `strings.MinRunes(0) & "e"`),
178+
`_|_(invalid value "e" (does not satisfy strings.MinRunes(0)))`,
167179
}, {
168180
test("math/bits", `bits.And(0x10000000000000F0E, 0xF0F7)`), `6`,
169181
}, {

cue/builtins.go

Lines changed: 21 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

encoding/openapi/build.go

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
package openapi
1616

1717
import (
18+
"fmt"
1819
"math"
1920
"math/big"
2021
"path"
@@ -612,8 +613,8 @@ func (b *builder) number(v cue.Value) {
612613

613614
// string supports the following options:
614615
//
615-
// - maxLenght (Unicode codepoints)
616-
// - minLenght (Unicode codepoints)
616+
// - maxLength (Unicode codepoints)
617+
// - minLength (Unicode codepoints)
617618
// - pattern (a regexp)
618619
//
619620
// The regexp pattern is as follows, and is limited to be a strict subset of RE2:
@@ -658,7 +659,7 @@ func (b *builder) string(v cue.Value) {
658659
return
659660
}
660661
if op == cue.RegexMatchOp {
661-
b.setFilter("schema", "pattern", s)
662+
b.setFilter("Schema", "pattern", s)
662663
} else {
663664
b.setNot("pattern", s)
664665
}
@@ -670,6 +671,22 @@ func (b *builder) string(v cue.Value) {
670671
case cue.NoOp, cue.SelectorOp:
671672
// TODO: determine formats from specific types.
672673

674+
case cue.CallOp:
675+
name := fmt.Sprint(a[0])
676+
field := ""
677+
switch name {
678+
case "strings.MinRunes":
679+
field = "minLength"
680+
case "strings.MaxRunes":
681+
field = "maxLength"
682+
default:
683+
b.failf(v, "builtin %v not supported in OpenAPI", name)
684+
}
685+
if len(a) != 2 {
686+
b.failf(v, "builtin %v may only be used with single argument", name)
687+
}
688+
b.setFilter("Schema", field, b.int(a[1]))
689+
673690
default:
674691
b.failf(v, "unsupported op %v for string type", op)
675692
}

encoding/openapi/openapi_test.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@ func TestParseDefinitions(t *testing.T) {
5050
"array.cue",
5151
"array.json",
5252
defaultConfig,
53+
}, {
54+
"strings.cue",
55+
"strings.json",
56+
defaultConfig,
5357
}, {
5458
"oneof.cue",
5559
"oneof.json",
@@ -82,6 +86,9 @@ func TestParseDefinitions(t *testing.T) {
8286
inst := cue.Build(load.Instances([]string{filename}, nil))[0]
8387

8488
b, err := Gen(inst, tc.config)
89+
if err != nil {
90+
t.Fatal(err)
91+
}
8592
var out = &bytes.Buffer{}
8693
_ = json.Indent(out, b, "", " ")
8794

encoding/openapi/testdata/strings.cue

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import "strings"
2+
3+
MyType: {
4+
myString: strings.MinRunes(1) & strings.MaxRunes(5)
5+
6+
myPattern: =~"foo.*bar"
7+
8+
myAntiPattern: !~"foo.*bar"
9+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
{
2+
"openapi": "3.0.0",
3+
"info": {},
4+
"components": {
5+
"schemas": {
6+
"MyType": {
7+
"type": "object",
8+
"required": [
9+
"myString",
10+
"myPattern",
11+
"myAntiPattern"
12+
],
13+
"properties": {
14+
"myString": {
15+
"type": "string",
16+
"minLength": 1,
17+
"maxLength": 5
18+
},
19+
"myPattern": {
20+
"type": "string",
21+
"pattern": "foo.*bar"
22+
},
23+
"myAntiPattern": {
24+
"not": {
25+
"type": "string",
26+
"pattern": "foo.*bar"
27+
},
28+
"type": "string"
29+
}
30+
}
31+
}
32+
}
33+
}
34+
}

pkg/strings/manual.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,42 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15+
// Package strings implements simple functions to manipulate UTF-8 encoded
16+
// strings.package strings.
17+
//
18+
// Some of the functions in this package are specifically intended as field
19+
// constraints. For instance, MaxRunes as used in this CUE program
20+
//
21+
// import "strings"
22+
//
23+
// myString: strings.MaxRunes(5)
24+
//
25+
// specifies that the myString should be at most 5 code points.
1526
package strings
1627

1728
import (
1829
"strings"
1930
"unicode"
2031
)
2132

33+
// MinRunes reports whether the number of runes (Unicode codepoints) in a string
34+
// is at least a certain minimum. MinRunes can be used a a field constraint to
35+
// except all strings for which this property holds.
36+
func MinRunes(s string, max int) bool {
37+
// TODO: CUE strings cannot be invalid UTF-8. In case this changes, we need
38+
// to use the following conversion to count properly:
39+
// s, _ = unicodeenc.UTF8.NewDecoder().String(s)
40+
return len([]rune(s)) <= max
41+
}
42+
43+
// MaxRunes reports whether the number of runes (Unicode codepoints) in a string
44+
// exceeds a certain maximum. MaxRunes can be used a a field constraint to
45+
// except all strings for which this property holds
46+
func MaxRunes(s string, max int) bool {
47+
// See comment in MinRunes implementation.
48+
return len([]rune(s)) <= max
49+
}
50+
2251
// ToTitle returns a copy of the string s with all Unicode letters that begin
2352
// words mapped to their title case.
2453
func ToTitle(s string) string {

0 commit comments

Comments
 (0)