cuelang.org/go@v0.13.0/encoding/jsonschema/constraints_array.go (about)

     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  
    15  package jsonschema
    16  
    17  import (
    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  
    27  func 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 isBottom(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  
    61  func 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  
    70  func 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  
    79  func 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  
   103  func 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  
   122  func 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  
   137  func 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  
   143  func 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  
   159  func 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  
   170  func clearPos(e ast.Expr) ast.Expr {
   171  	ast.SetRelPos(e, token.NoRelPos)
   172  	return e
   173  }