encoding/gocode: add Go code generator
Also:
- add gocodec used by generator
- added ability to create temporary Runtime (for concurrency)
- removed index freezing
The latter was only for precondition checking of
internal code. At the same time, freezing could
lead to spurious races if Runtime is used
concurrently.
Note:
- gocodec is a separate package as it is useful
on its own and that way it can be used without
pulling all the dependencies of gocode.
Change-Id: Ib59b65084038b616c9fb725cbe152a56b8869416
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/2742
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/encoding/gocode/gocodec/codec.go b/encoding/gocode/gocodec/codec.go
new file mode 100644
index 0000000..0037d2a
--- /dev/null
+++ b/encoding/gocode/gocodec/codec.go
@@ -0,0 +1,157 @@
+// 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 codec converts Go to and from CUE and validates Go values based on
+// CUE constraints.
+//
+// CUE constraints can be used to validate Go types as well as fill out
+// missing struct fields that are implied from the constraints and the values
+// already defined by the struct value.
+package gocodec
+
+import (
+ "sync"
+
+ "cuelang.org/go/cue"
+ "cuelang.org/go/internal"
+)
+
+// Config has no options yet, but is defined for future extensibility.
+type Config struct {
+}
+
+// A Codec decodes and encodes CUE from and to Go values and validates and
+// completes Go values based on CUE templates.
+type Codec struct {
+ runtime *cue.Runtime
+ mutex sync.RWMutex
+}
+
+// New creates a new Codec for the given instance.
+//
+// It is safe to use the methods of Codec concurrently as long as the given
+// Runtime is not used elsewhere while using Codec. However, only the concurrent
+// use of Decode, Validate, and Complete is efficient.
+func New(r *cue.Runtime, c *Config) *Codec {
+ return &Codec{runtime: r}
+}
+
+// ExtractType extracts a CUE value from a Go type.
+//
+// The type represented by x is converted as the underlying type. Specific
+// values, such as map or slice elements or field values of structs are ignored.
+// If x is of type reflect.Type, the type represented by x is extracted.
+//
+// Fields of structs can be annoted using additional constrains using the 'cue'
+// field tag. The value of the tag is a CUE expression, which may contain
+// references to the JSON name of other fields in a struct.
+//
+// type Sum struct {
+// A int `cue:"c-b" json:"a,omitempty"`
+// B int `cue:"c-a" json:"b,omitempty"`
+// C int `cue:"a+b" json:"c,omitempty"`
+// }
+//
+func (c *Codec) ExtractType(x interface{}) (cue.Value, error) {
+ // ExtractType cannot introduce new fields on repeated calls. We could
+ // consider optimizing the lock usage based on this property.
+ c.mutex.Lock()
+ defer c.mutex.Unlock()
+
+ return fromGoType(c.runtime, x)
+}
+
+// TODO: allow extracting constraints and type info separately?
+
+// Decode converts x to a CUE value.
+//
+// If x is of type reflect.Value it will convert the value represented by x.
+func (c *Codec) Decode(x interface{}) (cue.Value, error) {
+ c.mutex.Lock()
+ defer c.mutex.Unlock()
+
+ // Depending on the type, can introduce new labels on repeated calls.
+ return fromGoValue(c.runtime, x, false)
+}
+
+// Encode converts v to a Go value.
+func (c *Codec) Encode(v cue.Value, x interface{}) error {
+ c.mutex.RLock()
+ defer c.mutex.RUnlock()
+
+ return v.Decode(x)
+}
+
+// Validate checks whether x satisfies the constraints defined by v.
+//
+// The given value must be created using the same Runtime with which c was
+// initialized.
+func (c *Codec) Validate(v cue.Value, x interface{}) error {
+ c.mutex.RLock()
+ defer c.mutex.RUnlock()
+
+ r := checkAndForkRuntime(c.runtime, v)
+ w, err := fromGoValue(r, x, false)
+ if err != nil {
+ return err
+ }
+ return w.Unify(v).Err()
+}
+
+// Complete sets previously undefined values in x that can be uniquely
+// determined form the constraints defined by v if validation passes, or returns
+// an error, without modifying anything, otherwise.
+//
+// Only undefined values are modified. A value is considered undefined if it is
+// pointer type and is nil or if it is a field with a zero value that has a json
+// tag with the omitempty flag.
+//
+// The given value must be created using the same Runtime with which c was
+// initialized.
+//
+// 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 *Codec) Complete(v cue.Value, x interface{}) error {
+ c.mutex.RLock()
+ defer c.mutex.RUnlock()
+
+ r := checkAndForkRuntime(c.runtime, v)
+ w, err := fromGoValue(r, x, true)
+ if err != nil {
+ return err
+ }
+
+ return w.Unify(v).Decode(x)
+}
+
+func fromGoValue(r *cue.Runtime, x interface{}, allowDefault bool) (cue.Value, error) {
+ v := internal.FromGoValue(r, x, allowDefault).(cue.Value)
+ if err := v.Err(); err != nil {
+ return v, err
+ }
+ return v, nil
+}
+
+func fromGoType(r *cue.Runtime, x interface{}) (cue.Value, error) {
+ v := internal.FromGoType(r, x).(cue.Value)
+ if err := v.Err(); err != nil {
+ return v, err
+ }
+ return v, nil
+}
+
+func checkAndForkRuntime(r *cue.Runtime, v cue.Value) *cue.Runtime {
+ return internal.CheckAndForkRuntime(r, v).(*cue.Runtime)
+}