github.com/solo-io/cue@v0.4.7/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  	"github.com/solo-io/cue/cue"
    23  	"github.com/solo-io/cue/cue/cuecontext"
    24  	"github.com/solo-io/cue/cue/parser"
    25  	"github.com/solo-io/cue/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  }