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 := instance.Eval(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 instance *cue.Instance 163 runtime = cuecontext.New() 164 ) 165 166 func init() { 167 var err error 168 instance, err = value.ConvertToRuntime(runtime).Compile("<cuego>", "{}") 169 if err != nil { 170 panic(err) 171 } 172 } 173 174 // fromGoValue converts a Go value to CUE 175 func fromGoValue(x interface{}, nilIsNull bool) (v cue.Value, err error) { 176 // TODO: remove the need to have a lock here. We could use a new index (new 177 // Instance) here as any previously unrecognized field can never match an 178 // existing one and can only be merged. 179 mutex.Lock() 180 v = value.FromGoValue(runtime, x, nilIsNull) 181 mutex.Unlock() 182 if err := v.Err(); err != nil { 183 return v, err 184 } 185 return v, nil 186 187 // // This should be equivalent to the following: 188 // b, err := json.Marshal(x) 189 // if err != nil { 190 // return v, err 191 // } 192 // expr, err := parser.ParseExpr(fset, "", b) 193 // if err != nil { 194 // return v, err 195 // } 196 // mutex.Lock() 197 // v = instance.Eval(expr) 198 // mutex.Unlock() 199 // return v, nil 200 201 } 202 203 func fromGoType(x interface{}) cue.Value { 204 // TODO: remove the need to have a lock here. We could use a new index (new 205 // Instance) here as any previously unrecognized field can never match an 206 // existing one and can only be merged. 207 mutex.Lock() 208 v := value.FromGoType(runtime, x) 209 mutex.Unlock() 210 return v 211 } 212