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