1// Copyright 2019 CUE Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package jsonschema
16
17import (
18 "strconv"
19
20 "cuelang.org/go/cue"
21 "cuelang.org/go/cue/ast"
22 "cuelang.org/go/cue/token"
23)
24
25// Array constraints
26
27func constraintAdditionalItems(key string, n cue.Value, s *state) {
28 var elem ast.Expr
29 switch n.Kind() {
30 case cue.BoolKind:
31 // Boolean values are supported even in earlier
32 // versions that did not support boolean schemas otherwise.
33 elem = boolSchema(s.boolValue(n))
34 case cue.StructKind:
35 elem = s.schema(n)
36 default:
37 s.errf(n, `value of "additionalItems" must be an object or boolean`)
38 }
39 if s.list == nil || !s.listItemsIsArray {
40 // If there's no "items" keyword or its value is not an array "additionalItems" doesn't apply.
41 return
42 }
43 if len(s.list.Elts) == 0 {
44 // Should never happen because "items" always adds an ellipsis
45 panic("no elements in list")
46 }
47 last := s.list.Elts[len(s.list.Elts)-1].(*ast.Ellipsis)
48 if isErrorCall(elem) {
49 // No additional elements allowed. Remove the ellipsis.
50 s.list.Elts = s.list.Elts[:len(s.list.Elts)-1]
51 return
52 }
53 if isTop(elem) {
54 // Nothing to do: there's already an ellipsis in place that
55 // allows anything.
56 return
57 }
58 last.Type = elem
59}
60
61func constraintMinContains(key string, n cue.Value, s *state) {
62 p, err := uint64Value(n)
63 if err != nil {
64 s.errf(n, `value of "minContains" must be a non-negative integer value`)
65 return
66 }
67 s.minContains = &p
68}
69
70func constraintMaxContains(key string, n cue.Value, s *state) {
71 p, err := uint64Value(n)
72 if err != nil {
73 s.errf(n, `value of "maxContains" must be a non-negative integer value`)
74 return
75 }
76 s.maxContains = &p
77}
78
79func constraintContains(key string, n cue.Value, s *state) {
80 list := s.addImport(n, "list")
81 x := s.schema(n)
82
83 var min uint64 = 1
84 if s.minContains != nil {
85 min = *s.minContains
86 }
87 var c ast.Expr = &ast.UnaryExpr{
88 Op: token.GEQ,
89 X: ast.NewLit(token.INT, strconv.FormatUint(min, 10)),
90 }
91
92 if s.maxContains != nil {
93 c = ast.NewBinExpr(token.AND, c, &ast.UnaryExpr{
94 Op: token.LEQ,
95 X: ast.NewLit(token.INT, strconv.FormatUint(*s.maxContains, 10)),
96 })
97 }
98
99 x = ast.NewCall(ast.NewSel(list, "MatchN"), c, clearPos(x))
100 s.add(n, arrayType, x)
101}
102
103func constraintItems(key string, n cue.Value, s *state) {
104 switch n.Kind() {
105 case cue.StructKind, cue.BoolKind:
106 elem := s.schema(n)
107 ast.SetRelPos(elem, token.NoRelPos)
108 s.add(n, arrayType, ast.NewList(&ast.Ellipsis{Type: elem}))
109 s.hasItems = true
110
111 case cue.ListKind:
112 if !s.schemaVersion.is(vto(VersionDraft2019_09)) {
113 // The list form is only supported up to 2019-09
114 s.errf(n, `from version %v onwards, the value of "items" must be an object or a boolean`, VersionDraft2020_12)
115 return
116 }
117 s.listItemsIsArray = true
118 constraintPrefixItems(key, n, s)
119 }
120}
121
122func constraintPrefixItems(key string, n cue.Value, s *state) {
123 if n.Kind() != cue.ListKind {
124 s.errf(n, `value of "prefixItems" must be an array`)
125 }
126 var a []ast.Expr
127 for _, n := range s.listItems(key, n, true) {
128 v := s.schema(n) // TODO: label with number literal.
129 ast.SetRelPos(v, token.NoRelPos)
130 a = append(a, v)
131 }
132 s.list = ast.NewList(a...)
133 s.list.Elts = append(s.list.Elts, &ast.Ellipsis{})
134 s.add(n, arrayType, s.list)
135}
136
137func constraintMaxItems(key string, n cue.Value, s *state) {
138 list := s.addImport(n, "list")
139 x := ast.NewCall(ast.NewSel(list, "MaxItems"), clearPos(s.uint(n)))
140 s.add(n, arrayType, x)
141}
142
143func constraintMinItems(key string, n cue.Value, s *state) {
144 a := []ast.Expr{}
145 p, err := uint64Value(n)
146 if err != nil {
147 s.errf(n, "invalid uint")
148 }
149 for ; p > 0; p-- {
150 a = append(a, top())
151 }
152 s.add(n, arrayType, ast.NewList(append(a, &ast.Ellipsis{})...))
153
154 // TODO: use this once constraint resolution is properly implemented.
155 // list := s.addImport(n, "list")
156 // s.addConjunct(n, ast.NewCall(ast.NewSel(list, "MinItems"), clearPos(s.uint(n))))
157}
158
159func constraintUniqueItems(key string, n cue.Value, s *state) {
160 if s.boolValue(n) {
161 if s.schemaVersion.is(k8s) {
162 s.errf(n, "cannot set uniqueItems to true in a Kubernetes schema")
163 return
164 }
165 list := s.addImport(n, "list")
166 s.add(n, arrayType, ast.NewCall(ast.NewSel(list, "UniqueItems")))
167 }
168}
169
170func clearPos(e ast.Expr) ast.Expr {
171 ast.SetRelPos(e, token.NoRelPos)
172 return e
173}