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
15package gocodec
16
17import (
18 "fmt"
19 "reflect"
20 "testing"
21
22 "github.com/google/go-cmp/cmp"
23
24 "cuelang.org/go/cue"
25 "cuelang.org/go/cue/cuecontext"
26)
27
28type Sum struct {
29 A int `cue:"C-B" json:",omitempty"`
30 B int `cue:"C-A" json:",omitempty"`
31 C int `cue:"A+B & >=5" json:",omitempty"`
32}
33
34func checkErr(t *testing.T, got error, want string) {
35 t.Helper()
36 if (got == nil) != (want == "") {
37 t.Errorf("error: got %v; want %v", got, want)
38 }
39}
40func TestValidate(t *testing.T) {
41 fail := "some error"
42 testCases := []struct {
43 name string
44 value interface{}
45 constraints string
46 err string
47 }{{
48 name: "*Sum: nil disallowed by constraint",
49 value: (*Sum)(nil),
50 constraints: "!=null",
51 err: fail,
52 }, {
53 name: "Sum",
54 value: Sum{A: 1, B: 4, C: 5},
55 }, {
56 name: "*Sum",
57 value: &Sum{A: 1, B: 4, C: 5},
58 }, {
59 name: "*Sum: incorrect sum",
60 value: &Sum{A: 1, B: 4, C: 6},
61 err: fail,
62 }, {
63 name: "*Sum: field C is too low",
64 value: &Sum{A: 1, B: 3, C: 4},
65 err: fail,
66 }, {
67 name: "*Sum: nil value",
68 value: (*Sum)(nil),
69 }, {
70 // Not a typical constraint, but it is possible.
71 name: "string list",
72 value: []string{"a", "b", "c"},
73 constraints: `[_, "b", ...]`,
74 }}
75
76 for _, tc := range testCases {
77 t.Run(tc.name, func(t *testing.T) {
78 ctx := cuecontext.New()
79 codec := New(ctx, nil)
80
81 v, err := codec.ExtractType(tc.value)
82 if err != nil {
83 t.Fatal(err)
84 }
85
86 if tc.constraints != "" {
87 v1 := ctx.CompileString(tc.constraints, cue.Filename(tc.name))
88 if err := v1.Err(); err != nil {
89 t.Fatal(err)
90 }
91 v = v.Unify(v1)
92 }
93
94 err = codec.Validate(v, tc.value)
95 checkErr(t, err, tc.err)
96
97 // Smoke test that it seems to work OK with deprecated *cue.Runtime argument
98 r := &cue.Runtime{}
99 codec = New(r, nil)
100 if _, err := codec.ExtractType(tc.value); err != nil {
101 t.Fatal(err)
102 }
103 })
104 }
105}
106
107func TestComplete(t *testing.T) {
108 type sump struct {
109 A *int `cue:"C-B"`
110 B *int `cue:"C-A"`
111 C *int `cue:"A+B"`
112 }
113 one := 1
114 two := 2
115 fail := "some error"
116 _ = fail
117 _ = one
118 testCases := []struct {
119 name string
120 value interface{}
121 result interface{}
122 constraints string
123 err string
124 }{{
125 name: "*Sum",
126 value: &Sum{A: 1, B: 4, C: 5},
127 result: &Sum{A: 1, B: 4, C: 5},
128 }, {
129 name: "*Sum",
130 value: &Sum{A: 1, B: 4},
131 result: &Sum{A: 1, B: 4, C: 5},
132 }, {
133 name: "*sump",
134 value: &sump{A: &one, B: &one},
135 result: &sump{A: &one, B: &one, C: &two},
136 }, {
137 name: "*Sum: backwards",
138 value: &Sum{B: 4, C: 8},
139 result: &Sum{A: 4, B: 4, C: 8},
140 }, {
141 name: "*Sum: sum too low",
142 value: &Sum{A: 1, B: 3},
143 result: &Sum{A: 1, B: 3}, // Value should not be updated
144 err: fail,
145 }, {
146 name: "*Sum: sum underspecified",
147 value: &Sum{A: 1},
148 result: &Sum{A: 1}, // Value should not be updated
149 err: fail,
150 }, {
151 name: "Sum: cannot modify",
152 value: Sum{A: 3, B: 4, C: 7},
153 result: Sum{A: 3, B: 4, C: 7},
154 err: fail,
155 }, {
156 name: "*Sum: cannot update nil value",
157 value: (*Sum)(nil),
158 result: (*Sum)(nil),
159 err: fail,
160 }, {
161 name: "cannot modify slice",
162 value: []string{"a", "b", "c"},
163 result: []string{"a", "b", "c"},
164 err: fail,
165 }}
166 for _, tc := range testCases {
167 t.Run(tc.name, func(t *testing.T) {
168 ctx := cuecontext.New()
169 codec := New(ctx, nil)
170
171 v, err := codec.ExtractType(tc.value)
172 if err != nil {
173 t.Fatal(err)
174 }
175
176 if tc.constraints != "" {
177 c := ctx.CompileString(tc.constraints, cue.Filename(tc.name))
178 if err := c.Err(); err != nil {
179 t.Fatal(err)
180 }
181 v = v.Unify(c)
182 }
183
184 err = codec.Complete(v, tc.value)
185 checkErr(t, err, tc.err)
186 if diff := cmp.Diff(tc.value, tc.result); diff != "" {
187 t.Error(diff)
188 }
189 })
190 }
191}
192
193func TestEncode(t *testing.T) {
194 testCases := []struct {
195 in string
196 dst interface{}
197 want interface{}
198 }{{
199 in: "4",
200 dst: new(int),
201 want: 4,
202 }}
203 ctx := cuecontext.New()
204 c := New(ctx, nil)
205
206 for _, tc := range testCases {
207 t.Run("", func(t *testing.T) {
208 in := ctx.CompileString(tc.in, cue.Filename("test"))
209 if err := in.Err(); err != nil {
210 t.Fatal(err)
211 }
212
213 err := c.Encode(in, tc.dst)
214 if err != nil {
215 t.Fatal(err)
216 }
217
218 got := reflect.ValueOf(tc.dst).Elem().Interface()
219 if diff := cmp.Diff(got, tc.want); diff != "" {
220 t.Error(diff)
221 }
222 })
223 }
224}
225
226func TestDecode(t *testing.T) {
227 testCases := []struct {
228 in interface{}
229 want string
230 }{{
231 in: "str",
232 want: `"str"`,
233 }, {
234 in: func() interface{} {
235 type T struct {
236 B int
237 }
238 type S struct {
239 A string
240 T
241 }
242 return S{}
243 }(),
244 want: `{
245 A: ""
246 B: 0
247}`,
248 }, {
249 in: func() interface{} {
250 type T struct {
251 B int
252 }
253 type S struct {
254 A string
255 T `json:"t"`
256 }
257 return S{}
258 }(),
259 want: `{
260 A: ""
261 t: {
262 B: 0
263 }
264}`,
265 }}
266 c := New(cuecontext.New(), nil)
267
268 for _, tc := range testCases {
269 t.Run("", func(t *testing.T) {
270 v, err := c.Decode(tc.in)
271 if err != nil {
272 t.Fatal(err)
273 }
274
275 got := fmt.Sprint(v)
276 if got != tc.want {
277 t.Errorf("got %v; want %v", got, tc.want)
278 }
279 })
280 }
281}
282
283// For debugging purposes, do not remove.
284func TestX(t *testing.T) {
285 t.Skip()
286
287 fail := "some error"
288 // Not a typical constraint, but it is possible.
289 var (
290 name = "string list incompatible lengths"
291 value = []string{"a", "b", "c"}
292 constraints = `4*[string]`
293 wantErr = fail
294 )
295
296 ctx := cuecontext.New()
297 codec := New(ctx, nil)
298
299 v, err := codec.ExtractType(value)
300 if err != nil {
301 t.Fatal(err)
302 }
303
304 if constraints != "" {
305 c := ctx.CompileString(constraints, cue.Filename(name))
306 if err := c.Err(); err != nil {
307 t.Fatal(err)
308 }
309 v = v.Unify(c)
310 }
311
312 err = codec.Validate(v, value)
313 checkErr(t, err, wantErr)
314}