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