...

Source file src/cuelang.org/go/encoding/gocode/gocodec/codec.go

Documentation: cuelang.org/go/encoding/gocode/gocodec

     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  	"cuelang.org/go/cue"
    27  	"cuelang.org/go/cue/cuecontext"
    28  	"cuelang.org/go/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  //
    48  // Note: calling this with a *cue.Runtime value is deprecated.
    49  func New[Ctx *cue.Runtime | *cue.Context](ctx Ctx, c *Config) *Codec {
    50  	return &Codec{runtime: value.ConvertToContext(ctx)}
    51  }
    52  
    53  // ExtractType extracts a CUE value from a Go type.
    54  //
    55  // The type represented by x is converted as the underlying type. Specific
    56  // values, such as map or slice elements or field values of structs are ignored.
    57  // If x is of type reflect.Type, the type represented by x is extracted.
    58  //
    59  // Fields of structs can be annoted using additional constrains using the 'cue'
    60  // field tag. The value of the tag is a CUE expression, which may contain
    61  // references to the JSON name of other fields in a struct.
    62  //
    63  //	type Sum struct {
    64  //	    A int `cue:"c-b" json:"a,omitempty"`
    65  //	    B int `cue:"c-a" json:"b,omitempty"`
    66  //	    C int `cue:"a+b" json:"c,omitempty"`
    67  //	}
    68  func (c *Codec) ExtractType(x interface{}) (cue.Value, error) {
    69  	// ExtractType cannot introduce new fields on repeated calls. We could
    70  	// consider optimizing the lock usage based on this property.
    71  	c.mutex.Lock()
    72  	defer c.mutex.Unlock()
    73  
    74  	return fromGoType(c.runtime, x)
    75  }
    76  
    77  // TODO: allow extracting constraints and type info separately?
    78  
    79  // Decode converts x to a CUE value.
    80  //
    81  // If x is of type reflect.Value it will convert the value represented by x.
    82  func (c *Codec) Decode(x interface{}) (cue.Value, error) {
    83  	c.mutex.Lock()
    84  	defer c.mutex.Unlock()
    85  
    86  	// Depending on the type, can introduce new labels on repeated calls.
    87  	return fromGoValue(c.runtime, x, false)
    88  }
    89  
    90  // Encode converts v to a Go value.
    91  func (c *Codec) Encode(v cue.Value, x interface{}) error {
    92  	c.mutex.RLock()
    93  	defer c.mutex.RUnlock()
    94  
    95  	return v.Decode(x)
    96  }
    97  
    98  var defaultCodec = New(value.ConvertToRuntime(cuecontext.New()), nil)
    99  
   100  // Validate calls Validate on a default Codec for the type of x.
   101  func Validate(x interface{}) error {
   102  	c := defaultCodec
   103  	c.mutex.RLock()
   104  	defer c.mutex.RUnlock()
   105  
   106  	r := defaultCodec.runtime
   107  	v, err := fromGoType(r, x)
   108  	if err != nil {
   109  		return err
   110  	}
   111  	w, err := fromGoValue(r, x, false)
   112  	if err != nil {
   113  		return err
   114  	}
   115  	v = v.Unify(w)
   116  	if err := v.Validate(); err != nil {
   117  		return err
   118  	}
   119  	return nil
   120  }
   121  
   122  // Validate checks whether x satisfies the constraints defined by v.
   123  //
   124  // The given value must be created using the same Runtime with which c was
   125  // initialized.
   126  func (c *Codec) Validate(v cue.Value, x interface{}) error {
   127  	c.mutex.RLock()
   128  	defer c.mutex.RUnlock()
   129  
   130  	r := checkAndForkContext(c.runtime, v)
   131  	w, err := fromGoValue(r, x, false)
   132  	if err != nil {
   133  		return err
   134  	}
   135  	return w.Unify(v).Err()
   136  }
   137  
   138  // Complete sets previously undefined values in x that can be uniquely
   139  // determined form the constraints defined by v if validation passes, or returns
   140  // an error, without modifying anything, otherwise.
   141  //
   142  // Only undefined values are modified. A value is considered undefined if it is
   143  // pointer type and is nil or if it is a field with a zero value that has a json
   144  // tag with the omitempty flag.
   145  //
   146  // The given value must be created using the same Runtime with which c was
   147  // initialized.
   148  //
   149  // Complete does a JSON round trip. This means that data not preserved in such a
   150  // round trip, such as the location name of a time.Time, is lost after a
   151  // successful update.
   152  func (c *Codec) Complete(v cue.Value, x interface{}) error {
   153  	c.mutex.RLock()
   154  	defer c.mutex.RUnlock()
   155  
   156  	r := checkAndForkContext(c.runtime, v)
   157  	w, err := fromGoValue(r, x, true)
   158  	if err != nil {
   159  		return err
   160  	}
   161  
   162  	w = w.Unify(v)
   163  	if err := w.Validate(cue.Concrete(true)); err != nil {
   164  		return err
   165  	}
   166  	return w.Decode(x)
   167  }
   168  
   169  func fromGoValue(r *cue.Context, x interface{}, allowDefault bool) (cue.Value, error) {
   170  	v := value.FromGoValue(r, x, allowDefault)
   171  	if err := v.Err(); err != nil {
   172  		return v, err
   173  	}
   174  	return v, nil
   175  }
   176  
   177  func fromGoType(r *cue.Context, x interface{}) (cue.Value, error) {
   178  	v := value.FromGoType(r, x)
   179  	if err := v.Err(); err != nil {
   180  		return v, err
   181  	}
   182  	return v, nil
   183  }
   184  
   185  func checkAndForkContext(r *cue.Context, v cue.Value) *cue.Context {
   186  	rr := v.Context()
   187  	if r != rr {
   188  		panic("value not from same runtime")
   189  	}
   190  	return rr
   191  }
   192  

View as plain text