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