cuelang.org/go@v0.10.1/cue/decode_test.go (about)

     1  // Copyright 2021 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 cue_test
    16  
    17  import (
    18  	"fmt"
    19  	"reflect"
    20  	"testing"
    21  	"time"
    22  
    23  	"cuelang.org/go/cue"
    24  	"cuelang.org/go/internal/cuetdtest"
    25  	"github.com/go-quicktest/qt"
    26  	"github.com/google/go-cmp/cmp"
    27  )
    28  
    29  func TestDecode(t *testing.T) {
    30  	type Nested struct {
    31  		P *int `json:"P"`
    32  	}
    33  	type fields struct {
    34  		A int `json:"A"`
    35  		B int `json:"B"`
    36  		C int `json:"C"`
    37  		M map[string]interface{}
    38  		*Nested
    39  	}
    40  	one := 1
    41  	intList := func(ints ...int) *[]int {
    42  		ints = append([]int{}, ints...)
    43  		return &ints
    44  	}
    45  	testCases := []struct {
    46  		value string
    47  		dst   interface{}
    48  		want  interface{}
    49  		err   string
    50  	}{{
    51  		// clear pointer
    52  		value: `null`,
    53  		dst:   &[]int{1},
    54  		want:  []int(nil),
    55  	}, {
    56  
    57  		value: `1`,
    58  		err:   "cannot decode into unsettable value",
    59  	}, {
    60  		dst:   new(interface{}),
    61  		value: `_|_`,
    62  		err:   "explicit error (_|_ literal) in source",
    63  	}, {
    64  		// clear pointer
    65  		value: `null`,
    66  		dst:   &[]int{1},
    67  		want:  []int(nil),
    68  	}, {
    69  		// clear pointer
    70  		value: `[null]`,
    71  		dst:   &[]*int{&one},
    72  		want:  []*int{nil},
    73  	}, {
    74  		value: `true`,
    75  		dst:   new(bool),
    76  		want:  true,
    77  	}, {
    78  		value: `false`,
    79  		dst:   new(bool),
    80  		want:  false,
    81  	}, {
    82  		value: `bool`,
    83  		dst:   new(bool),
    84  		err:   "cannot convert non-concrete value bool",
    85  	}, {
    86  		value: `_`,
    87  		dst:   new([]int),
    88  		want:  []int(nil),
    89  	}, {
    90  		value: `"str"`,
    91  		dst:   new(string),
    92  		want:  "str",
    93  	}, {
    94  		value: `"str"`,
    95  		dst:   new(int),
    96  		err:   "cannot use value \"str\" (type string) as int",
    97  	}, {
    98  		value: `'bytes'`,
    99  		dst:   new([]byte),
   100  		want:  []byte("bytes"),
   101  	}, {
   102  		value: `'bytes'`,
   103  		dst:   &[3]byte{},
   104  		want:  [3]byte{0x62, 0x79, 0x74},
   105  	}, {
   106  		value: `1`,
   107  		dst:   new(float32),
   108  		want:  float32(1),
   109  	}, {
   110  		value: `500`,
   111  		dst:   new(uint8),
   112  		err:   "integer 500 overflows uint8",
   113  	}, {
   114  		value: `501`,
   115  		dst:   new(int8),
   116  		err:   "integer 501 overflows int8",
   117  	}, {
   118  		value: `{}`,
   119  		dst:   &fields{},
   120  		want:  fields{},
   121  	}, {
   122  		value: `{A:1,b:2,c:3}`,
   123  		dst:   &fields{},
   124  		want:  fields{A: 1, B: 2, C: 3},
   125  	}, {
   126  		// allocate map
   127  		value: `{a:1,m:{a: 3}}`,
   128  		dst:   &fields{},
   129  		want: fields{A: 1,
   130  			M: map[string]interface{}{"a": int(3)}},
   131  	}, {
   132  		// indirect int
   133  		value: `{p: 1}`,
   134  		dst:   &fields{},
   135  		want:  fields{Nested: &Nested{P: &one}},
   136  	}, {
   137  		value: `{for k, v in y if v > 1 {"\(k)": v} }
   138  		y: {a:1,b:2,c:3}`,
   139  		dst:  &fields{},
   140  		want: fields{B: 2, C: 3},
   141  	}, {
   142  		value: `{a:1,b:2,c:int}`,
   143  		dst:   new(fields),
   144  		err:   "c: cannot convert non-concrete value int",
   145  	}, {
   146  		value: `[]`,
   147  		dst:   intList(),
   148  		want:  *intList(),
   149  	}, {
   150  		value: `[1,2,3]`,
   151  		dst:   intList(),
   152  		want:  *intList(1, 2, 3),
   153  	}, {
   154  		// shorten list
   155  		value: `[1,2,3]`,
   156  		dst:   intList(1, 2, 3, 4),
   157  		want:  *intList(1, 2, 3),
   158  	}, {
   159  		// shorter array
   160  		value: `[1,2,3]`,
   161  		dst:   &[2]int{},
   162  		want:  [2]int{1, 2},
   163  	}, {
   164  		// longer array
   165  		value: `[1,2,3]`,
   166  		dst:   &[4]int{},
   167  		want:  [4]int{1, 2, 3, 0},
   168  	}, {
   169  		value: `[for x in #y if x > 1 { x }]
   170  				#y: [1,2,3]`,
   171  		dst:  intList(),
   172  		want: *intList(2, 3),
   173  	}, {
   174  		value: `[int]`,
   175  		dst:   intList(),
   176  		err:   "0: cannot convert non-concrete value int",
   177  	}, {
   178  		value: `{a: 1, b: 2, c: 3}`,
   179  		dst:   &map[string]int{},
   180  		want:  map[string]int{"a": 1, "b": 2, "c": 3},
   181  	}, {
   182  		value: `{"1": 1, "-2": 2, "3": 3}`,
   183  		dst:   &map[int]int{},
   184  		want:  map[int]int{1: 1, -2: 2, 3: 3},
   185  	}, {
   186  		value: `{"1": 1, "2": 2, "3": 3}`,
   187  		dst:   &map[uint]int{},
   188  		want:  map[uint]int{1: 1, 2: 2, 3: 3},
   189  	}, {
   190  		value: `{a: 1, b: 2, c: true, d: e: 2}`,
   191  		dst:   &map[string]interface{}{},
   192  		want: map[string]interface{}{
   193  			"a": 1, "b": 2, "c": true,
   194  			"d": map[string]interface{}{"e": 2}},
   195  	}, {
   196  		value: `{a: b: *2 | int}`,
   197  		dst:   &map[string]interface{}{},
   198  		want:  map[string]interface{}{"a": map[string]interface{}{"b": int(2)}},
   199  	}, {
   200  		value: `{a: 1, b: 2, c: true}`,
   201  		dst:   &map[string]int{},
   202  		err:   "c: cannot use value true (type bool) as int",
   203  	}, {
   204  		value: `{"300": 3}`,
   205  		dst:   &map[int8]int{},
   206  		err:   "key integer 300 overflows int8",
   207  	}, {
   208  		value: `{"300": 3}`,
   209  		dst:   &map[uint8]int{},
   210  		err:   "key integer 300 overflows uint8",
   211  	}, {
   212  		// Issue #1401
   213  		value: `a: b: _ | *[0, ...]`,
   214  		dst:   &map[string]interface{}{},
   215  		want: map[string]interface{}{
   216  			"a": map[string]interface{}{
   217  				"b": []interface{}{int(0)},
   218  			},
   219  		},
   220  	}, {
   221  		// Issue #1466
   222  		value: `{"x": "1s"}
   223  		`,
   224  		dst:  &S{},
   225  		want: S{X: Duration{D: 1000000000}},
   226  	}, {
   227  		// Issue #1466
   228  		value: `{"x": '1s'}
   229  			`,
   230  		dst:  &S{},
   231  		want: S{X: Duration{D: 1000000000}},
   232  	}, {
   233  		// Issue #1466
   234  		value: `{"x": 1}
   235  				`,
   236  		dst: &S{},
   237  		err: "Decode: x: cannot use value 1 (type int) as (string|bytes)",
   238  	}, {
   239  		value: `[]`,
   240  		dst:   new(interface{}),
   241  		want:  []interface{}{},
   242  	}}
   243  	for _, tc := range testCases {
   244  		cuetdtest.FullMatrix.Run(t, tc.value, func(t *cuetdtest.M) {
   245  			err := getValue(t, tc.value).Decode(tc.dst)
   246  			checkFatal(t.T, err, tc.err, "init")
   247  
   248  			got := reflect.ValueOf(tc.dst).Elem().Interface()
   249  			if diff := cmp.Diff(got, tc.want); diff != "" {
   250  				t.Error(diff)
   251  				t.Errorf("\n%#v\n%#v", got, tc.want)
   252  			}
   253  		})
   254  	}
   255  }
   256  
   257  func TestDecodeIntoCUEValue(t *testing.T) {
   258  	cuetdtest.FullMatrix.Do(t, func(t *cuetdtest.M) {
   259  		// We should be able to decode into a CUE value so we can
   260  		// decode partially incomplete values into Go.
   261  		// This test doesn't fit within the table used by TestDecode
   262  		// because cue values aren't easily comparable with cmp.Diff.
   263  		var st struct {
   264  			X cue.Value `json:"x"`
   265  		}
   266  		err := getValue(t, `x: string`).Decode(&st)
   267  		qt.Assert(t, qt.IsNil(err))
   268  		qt.Assert(t, qt.Equals(fmt.Sprint(st.X), "string"))
   269  
   270  		// Check we can decode into a top level value.
   271  		var v cue.Value
   272  		err = getValue(t, `int`).Decode(&v)
   273  		qt.Assert(t, qt.IsNil(err))
   274  		qt.Assert(t, qt.Equals(fmt.Sprint(v), "int"))
   275  	})
   276  }
   277  
   278  type Duration struct {
   279  	D time.Duration
   280  }
   281  type S struct {
   282  	X Duration `json:"x"`
   283  }
   284  
   285  func (d *Duration) UnmarshalText(data []byte) error {
   286  	v, err := time.ParseDuration(string(data))
   287  	if err != nil {
   288  		return err
   289  	}
   290  	d.D = v
   291  	return nil
   292  }
   293  
   294  func (d *Duration) MarshalText() ([]byte, error) {
   295  	return []byte(d.D.String()), nil
   296  }