cuelang.org/go@v0.13.0/internal/core/compile/validator.go (about) 1 // Copyright 2024 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 // https://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 compile 16 17 // This file contains validator and other non-monotonic builtins. 18 19 import ( 20 "cuelang.org/go/internal/core/adt" 21 "cuelang.org/go/internal/core/validate" 22 ) 23 24 // matchN is a validator that checks that the number of schemas in the given 25 // list that unify with "self" matches the number passed as the first argument 26 // of the validator. Note that this number may itself be a number constraint 27 // and does not have to be a concrete number. 28 var matchNBuiltin = &adt.Builtin{ 29 Name: "matchN", 30 Params: []adt.Param{topParam, intParam, listParam}, // varargs 31 Result: adt.BoolKind, 32 NonConcrete: true, 33 Func: func(call *adt.CallContext) adt.Expr { 34 c := call.OpContext() 35 args := call.Args() 36 37 if !c.IsValidator { 38 return c.NewErrf("matchN is a validator and should not be used as a function") 39 } 40 41 self := finalizeSelf(c, args[0]) 42 if err := bottom(c, self); err != nil { 43 return &adt.Bool{B: false} 44 } 45 46 var errs []*adt.Bottom 47 48 constraints := c.Elems(args[2]) 49 50 var count, possibleCount int64 51 for _, check := range constraints { 52 v := adt.Unify(c, self, check) 53 if err := validate.Validate(c, v, finalCfg); err == nil { 54 // TODO: is it always true that the lack of an error signifies 55 // success? 56 count++ 57 } else { 58 errs = append(errs, err) 59 if err.IsIncomplete() { 60 possibleCount++ 61 } 62 } 63 } 64 65 bound := args[1] 66 // TODO: consider a mode to require "all" to pass, for instance by 67 // supporting the value null or "all". 68 69 b := checkNum(c, bound, count, count+possibleCount) 70 if b != nil { 71 // Only show errors related to incomplete schema if there is still 72 // a possibility that we can resolve it. 73 isIncomplete := b.IsIncomplete() 74 for _, err := range errs { 75 if !isIncomplete || err.IsIncomplete() { 76 c.AddBottom(err) 77 } 78 } 79 return b 80 } 81 return &adt.Bool{B: true} 82 }, 83 } 84 85 // matchIf is a validator that checks that if the first argument unifies with 86 // self, the second argument also unifies with self, otherwise the third 87 // argument unifies with self. 88 // The same finalization heuristics are applied to self as are applied 89 // in matchN. 90 var matchIfBuiltin = &adt.Builtin{ 91 Name: "matchIf", 92 Params: []adt.Param{topParam, topParam, topParam, topParam}, 93 Result: adt.BoolKind, 94 NonConcrete: true, 95 Func: func(call *adt.CallContext) adt.Expr { 96 c := call.OpContext() 97 args := call.Args() 98 99 if !c.IsValidator { 100 return c.NewErrf("matchIf is a validator and should not be used as a function") 101 } 102 103 self := finalizeSelf(c, args[0]) 104 if err := bottom(c, self); err != nil { 105 return &adt.Bool{B: false} 106 } 107 ifSchema, thenSchema, elseSchema := args[1], args[2], args[3] 108 v := adt.Unify(c, self, ifSchema) 109 var chosenSchema adt.Value 110 if err := validate.Validate(c, v, finalCfg); err == nil { 111 chosenSchema = thenSchema 112 } else { 113 chosenSchema = elseSchema 114 } 115 v = adt.Unify(c, self, chosenSchema) 116 err := validate.Validate(c, v, finalCfg) 117 if err == nil { 118 return &adt.Bool{B: true} 119 } 120 // TODO should we also include in the error something about the fact that 121 // the if condition passed or failed? 122 return err 123 }, 124 } 125 126 var finalCfg = &validate.Config{Final: true} 127 128 // finalizeSelf ensures a value is fully evaluated and then strips it of any 129 // of its validators or default values. 130 func finalizeSelf(c *adt.OpContext, self adt.Value) adt.Value { 131 if x, ok := self.(*adt.Vertex); ok { 132 self = x.ToDataAll(c) 133 } 134 return self 135 } 136 137 // TODO: use adt.Unify instead. 138 func unifyScalar(c *adt.OpContext, self, check adt.Value) *adt.Vertex { 139 v := &adt.Vertex{} 140 closeInfo := c.CloseInfo() 141 v.AddConjunct(adt.MakeConjunct(nil, self, closeInfo)) 142 v.AddConjunct(adt.MakeConjunct(nil, check, closeInfo)) 143 v.Finalize(c) 144 return v 145 } 146 147 func checkNum(ctx *adt.OpContext, bound adt.Value, count, maxCount int64) *adt.Bottom { 148 cnt := ctx.NewInt64(count) 149 n := unifyScalar(ctx, bound, cnt) 150 b, _ := n.BaseValue.(*adt.Bottom) 151 if b != nil { 152 b := ctx.NewErrf("%d matched, expected %v", count, bound) 153 154 // By default we should mark the error as incomplete, but check if we 155 // know for sure it will fail. 156 switch bound := bound.(type) { 157 case *adt.Num: 158 if i, err := bound.X.Int64(); err == nil && i > count && i <= maxCount { 159 b.Code = adt.IncompleteError 160 } 161 162 case *adt.BoundValue: 163 v := &adt.Vertex{} 164 v.AddConjunct(ctx.MakeConjunct(bound)) 165 v.AddConjunct(ctx.MakeConjunct(&adt.BoundValue{ 166 Op: adt.GreaterEqualOp, 167 Value: cnt, 168 })) 169 v.AddConjunct(ctx.MakeConjunct(&adt.BoundValue{ 170 Op: adt.LessEqualOp, 171 Value: ctx.NewInt64(maxCount), 172 })) 173 v.Finalize(ctx) 174 if _, ok := v.BaseValue.(*adt.Bottom); !ok { 175 b.Code = adt.IncompleteError 176 } 177 178 default: 179 b.Code = adt.IncompleteError 180 } 181 182 return b 183 } 184 return nil 185 } 186 187 func bottom(c *adt.OpContext, v adt.Value) *adt.Bottom { 188 switch x := v.(type) { 189 case *adt.Vertex: 190 return x.Err(c) 191 case *adt.Bottom: 192 return x 193 } 194 return nil 195 }