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 gocodec 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/cue/cuecontext"
28 "cuelang.org/go/internal/value"
29)
30
31// Config has no options yet, but is defined for future extensibility.
32type 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.
37type 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.
49func New[Ctx *cue.Runtime | *cue.Context](ctx Ctx, c *Config) *Codec {
50 return &Codec{runtime: value.Context(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// }
68func (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.
82func (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.
91func (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
98var defaultCodec = New(cuecontext.New(), nil)
99
100// Validate calls Validate on a default Codec for the type of x.
101func 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.
126func (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.
152func (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
169func 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
177func 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
185func 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}