cuelang.org/go@v0.10.1/cuego/cuego.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 cuego 16 17 import ( 18 "fmt" 19 "reflect" 20 "sync" 21 22 "cuelang.org/go/cue" 23 "cuelang.org/go/cue/cuecontext" 24 "cuelang.org/go/cue/parser" 25 "cuelang.org/go/internal/value" 26 ) 27 28 // DefaultContext is the shared context used with top-level functions. 29 var DefaultContext = &Context{} 30 31 // MustConstrain is like Constrain, but panics if there is an error. 32 func MustConstrain(x interface{}, constraints string) { 33 if err := Constrain(x, constraints); err != nil { 34 panic(err) 35 } 36 } 37 38 // Constrain associates the given CUE constraints with the type of x or 39 // reports an error if the constraints are invalid or not compatible with x. 40 // 41 // Constrain works across package boundaries and is typically called in the 42 // package defining the type. Use a Context to apply constraints locally. 43 func Constrain(x interface{}, constraints string) error { 44 return DefaultContext.Constrain(x, constraints) 45 } 46 47 // Validate is a wrapper for Validate called on the global context. 48 func Validate(x interface{}) error { 49 return DefaultContext.Validate(x) 50 } 51 52 // Complete sets previously undefined values in x that can be uniquely 53 // determined form the constraints defined on the type of x such that validation 54 // passes, or returns an error, without modifying anything, if this is not 55 // possible. 56 // 57 // Complete does a JSON round trip. This means that data not preserved in such a 58 // round trip, such as the location name of a time.Time, is lost after a 59 // successful update. 60 func Complete(x interface{}) error { 61 return DefaultContext.Complete(x) 62 } 63 64 // A Context holds type constraints that are only applied within a given 65 // context. 66 // Global constraints that are defined at the time a constraint is 67 // created are applied as well. 68 type Context struct { 69 typeCache sync.Map // map[reflect.Type]cue.Value 70 } 71 72 // Validate checks whether x validates against the registered constraints for 73 // the type of x. 74 // 75 // Constraints for x can be defined as field tags or through the Register 76 // function. 77 func (c *Context) Validate(x interface{}) error { 78 a := c.load(x) 79 v, err := fromGoValue(x, false) 80 if err != nil { 81 return err 82 } 83 v = a.Unify(v) 84 if err := v.Validate(); err != nil { 85 return err 86 } 87 // TODO: validate all values are concrete. (original value subsumes result?) 88 return nil 89 } 90 91 // Complete sets previously undefined values in x that can be uniquely 92 // determined form the constraints defined on the type of x such that validation 93 // passes, or returns an error, without modifying anything, if this is not 94 // possible. 95 // 96 // A value is considered undefined if it is pointer type and is nil or if it 97 // is a field with a zero value and a json tag with the omitempty tag. 98 // Complete does a JSON round trip. This means that data not preserved in such a 99 // round trip, such as the location name of a time.Time, is lost after a 100 // successful update. 101 func (c *Context) Complete(x interface{}) error { 102 a := c.load(x) 103 v, err := fromGoValue(x, true) 104 if err != nil { 105 return err 106 } 107 v = a.Unify(v) 108 if err := v.Validate(cue.Concrete(true)); err != nil { 109 return err 110 } 111 return v.Decode(x) 112 } 113 114 func (c *Context) load(x interface{}) cue.Value { 115 t := reflect.TypeOf(x) 116 if value, ok := c.typeCache.Load(t); ok { 117 return value.(cue.Value) 118 } 119 120 // fromGoType should prevent the work is done no more than once, but even 121 // if it is, there is no harm done. 122 v := fromGoType(x) 123 c.typeCache.Store(t, v) 124 return v 125 } 126 127 // TODO: should we require that Constrain be defined on exported, 128 // named types types only? 129 130 // Constrain associates the given CUE constraints with the type of x or reports 131 // an error if the constraints are invalid or not compatible with x. 132 func (c *Context) Constrain(x interface{}, constraints string) error { 133 c.load(x) // Ensure fromGoType is called outside of lock. 134 135 mutex.Lock() 136 defer mutex.Unlock() 137 138 expr, err := parser.ParseExpr(fmt.Sprintf("<%T>", x), constraints) 139 if err != nil { 140 return err 141 } 142 143 v := runtime.BuildExpr(expr) 144 if v.Err() != nil { 145 return err 146 } 147 148 typ := c.load(x) 149 v = typ.Unify(v) 150 151 if err := v.Validate(); err != nil { 152 return err 153 } 154 155 t := reflect.TypeOf(x) 156 c.typeCache.Store(t, v) 157 return nil 158 } 159 160 var ( 161 mutex sync.Mutex 162 runtime = cuecontext.New() 163 ) 164 165 // fromGoValue converts a Go value to CUE 166 func fromGoValue(x interface{}, nilIsNull bool) (v cue.Value, err error) { 167 // TODO: remove the need to have a lock here. We could use a new index (new 168 // Instance) here as any previously unrecognized field can never match an 169 // existing one and can only be merged. 170 mutex.Lock() 171 v = value.FromGoValue(runtime, x, nilIsNull) 172 mutex.Unlock() 173 if err := v.Err(); err != nil { 174 return v, err 175 } 176 return v, nil 177 178 // // This should be equivalent to the following: 179 // b, err := json.Marshal(x) 180 // if err != nil { 181 // return v, err 182 // } 183 // expr, err := parser.ParseExpr(fset, "", b) 184 // if err != nil { 185 // return v, err 186 // } 187 // mutex.Lock() 188 // v = instance.Eval(expr) 189 // mutex.Unlock() 190 // return v, nil 191 192 } 193 194 func fromGoType(x interface{}) cue.Value { 195 // TODO: remove the need to have a lock here. We could use a new index (new 196 // Instance) here as any previously unrecognized field can never match an 197 // existing one and can only be merged. 198 mutex.Lock() 199 v := value.FromGoType(runtime, x) 200 mutex.Unlock() 201 return v 202 }