github.com/solo-io/cue@v0.4.7/encoding/gocode/gocodec/codec.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 codec converts Go to and from CUE and validates Go values based on
    16  // CUE constraints.
    17  //
    18  // CUE constraints can be used to validate Go types as well as fill out
    19  // missing struct fields that are implied from the constraints and the values
    20  // already defined by the struct value.
    21  package gocodec
    22  
    23  import (
    24  	"sync"
    25  
    26  	"github.com/solo-io/cue/cue"
    27  	"github.com/solo-io/cue/cue/cuecontext"
    28  	"github.com/solo-io/cue/internal/value"
    29  )
    30  
    31  // Config has no options yet, but is defined for future extensibility.
    32  type Config struct {
    33  }
    34  
    35  // A Codec decodes and encodes CUE from and to Go values and validates and
    36  // completes Go values based on CUE templates.
    37  type Codec struct {
    38  	runtime *cue.Context
    39  	mutex   sync.RWMutex
    40  }
    41  
    42  // New creates a new Codec for the given instance.
    43  //
    44  // It is safe to use the methods of Codec concurrently as long as the given
    45  // Runtime is not used elsewhere while using Codec. However, only the concurrent
    46  // use of Decode, Validate, and Complete is efficient.
    47  func New(r *cue.Runtime, c *Config) *Codec {
    48  	return &Codec{runtime: value.ConvertToContext(r)}
    49  }
    50  
    51  // ExtractType extracts a CUE value from a Go type.
    52  //
    53  // The type represented by x is converted as the underlying type. Specific
    54  // values, such as map or slice elements or field values of structs are ignored.
    55  // If x is of type reflect.Type, the type represented by x is extracted.
    56  //
    57  // Fields of structs can be annoted using additional constrains using the 'cue'
    58  // field tag. The value of the tag is a CUE expression, which may contain
    59  // references to the JSON name of other fields in a struct.
    60  //
    61  //     type Sum struct {
    62  //         A int `cue:"c-b" json:"a,omitempty"`
    63  //         B int `cue:"c-a" json:"b,omitempty"`
    64  //         C int `cue:"a+b" json:"c,omitempty"`
    65  //     }
    66  //
    67  func (c *Codec) ExtractType(x interface{}) (cue.Value, error) {
    68  	// ExtractType cannot introduce new fields on repeated calls. We could
    69  	// consider optimizing the lock usage based on this property.
    70  	c.mutex.Lock()
    71  	defer c.mutex.Unlock()
    72  
    73  	return fromGoType(c.runtime, x)
    74  }
    75  
    76  // TODO: allow extracting constraints and type info separately?
    77  
    78  // Decode converts x to a CUE value.
    79  //
    80  // If x is of type reflect.Value it will convert the value represented by x.
    81  func (c *Codec) Decode(x interface{}) (cue.Value, error) {
    82  	c.mutex.Lock()
    83  	defer c.mutex.Unlock()
    84  
    85  	// Depending on the type, can introduce new labels on repeated calls.
    86  	return fromGoValue(c.runtime, x, false)
    87  }
    88  
    89  // Encode converts v to a Go value.
    90  func (c *Codec) Encode(v cue.Value, x interface{}) error {
    91  	c.mutex.RLock()
    92  	defer c.mutex.RUnlock()
    93  
    94  	return v.Decode(x)
    95  }
    96  
    97  var defaultCodec = New(value.ConvertToRuntime(cuecontext.New()), nil)
    98  
    99  // Validate calls Validate on a default Codec for the type of x.
   100  func Validate(x interface{}) error {
   101  	c := defaultCodec
   102  	c.mutex.RLock()
   103  	defer c.mutex.RUnlock()
   104  
   105  	r := defaultCodec.runtime
   106  	v, err := fromGoType(r, x)
   107  	if err != nil {
   108  		return err
   109  	}
   110  	w, err := fromGoValue(r, x, false)
   111  	if err != nil {
   112  		return err
   113  	}
   114  	v = v.Unify(w)
   115  	if err := v.Validate(); err != nil {
   116  		return err
   117  	}
   118  	return nil
   119  }
   120  
   121  // Validate checks whether x satisfies the constraints defined by v.
   122  //
   123  // The given value must be created using the same Runtime with which c was
   124  // initialized.
   125  func (c *Codec) Validate(v cue.Value, x interface{}) error {
   126  	c.mutex.RLock()
   127  	defer c.mutex.RUnlock()
   128  
   129  	r := checkAndForkContext(c.runtime, v)
   130  	w, err := fromGoValue(r, x, false)
   131  	if err != nil {
   132  		return err
   133  	}
   134  	return w.Unify(v).Err()
   135  }
   136  
   137  // Complete sets previously undefined values in x that can be uniquely
   138  // determined form the constraints defined by v if validation passes, or returns
   139  // an error, without modifying anything, otherwise.
   140  //
   141  // Only undefined values are modified. A value is considered undefined if it is
   142  // pointer type and is nil or if it is a field with a zero value that has a json
   143  // tag with the omitempty flag.
   144  //
   145  // The given value must be created using the same Runtime with which c was
   146  // initialized.
   147  //
   148  // Complete does a JSON round trip. This means that data not preserved in such a
   149  // round trip, such as the location name of a time.Time, is lost after a
   150  // successful update.
   151  func (c *Codec) Complete(v cue.Value, x interface{}) error {
   152  	c.mutex.RLock()
   153  	defer c.mutex.RUnlock()
   154  
   155  	r := checkAndForkContext(c.runtime, v)
   156  	w, err := fromGoValue(r, x, true)
   157  	if err != nil {
   158  		return err
   159  	}
   160  
   161  	w = w.Unify(v)
   162  	if err := w.Validate(cue.Concrete(true)); err != nil {
   163  		return err
   164  	}
   165  	return w.Decode(x)
   166  }
   167  
   168  func fromGoValue(r *cue.Context, x interface{}, allowDefault bool) (cue.Value, error) {
   169  	v := value.FromGoValue(r, x, allowDefault)
   170  	if err := v.Err(); err != nil {
   171  		return v, err
   172  	}
   173  	return v, nil
   174  }
   175  
   176  func fromGoType(r *cue.Context, x interface{}) (cue.Value, error) {
   177  	v := value.FromGoType(r, x)
   178  	if err := v.Err(); err != nil {
   179  		return v, err
   180  	}
   181  	return v, nil
   182  }
   183  
   184  func checkAndForkContext(r *cue.Context, v cue.Value) *cue.Context {
   185  	rr := v.Context()
   186  	if r != rr {
   187  		panic("value not from same runtime")
   188  	}
   189  	return rr
   190  }