github.com/solo-io/cue@v0.4.7/internal/encoding/json/encode_test.go (about)

     1  // Copyright 2020 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 json
    16  
    17  import (
    18  	"encoding/json"
    19  	"strings"
    20  	"testing"
    21  
    22  	"github.com/google/go-cmp/cmp"
    23  	"github.com/solo-io/cue/cue/ast"
    24  	"github.com/solo-io/cue/cue/parser"
    25  	"github.com/solo-io/cue/cue/token"
    26  )
    27  
    28  func TestEncodeFile(t *testing.T) {
    29  	testCases := []struct {
    30  		name string
    31  		in   string
    32  		out  string
    33  	}{{
    34  		name: "foo",
    35  		in: `
    36  		package test
    37  
    38  		seq: [
    39  			1, 2, 3, {
    40  				a: 1
    41  				b: 2
    42  			}
    43  		]
    44  		a: b: c: 3
    45  		b: {
    46  			x: 0
    47  			y: 1
    48  			z: 2
    49  		}
    50  		`,
    51  		out: `{
    52      "seq": [
    53          1, 2, 3, {
    54              "a": 1,
    55              "b": 2
    56          }
    57      ],
    58      "a": {"b": {"c": 3}},
    59      "b": {
    60          "x": 0,
    61          "y": 1,
    62          "z": 2
    63      }
    64  }`,
    65  	}, {
    66  		name: "oneLineFields",
    67  		in: `
    68  		seq: [1, 2, 3]
    69  		esq: []
    70  		emp: {}
    71  		map: {a: 3}
    72  		str: "str"
    73  		int: 1K
    74  		bin: 0b11
    75  		hex: 0x11
    76  		dec: .3
    77  		dat: '\x80'
    78  		nil: null
    79  		yes: true
    80  		non: false
    81  		`,
    82  		out: `{
    83      "seq": [1, 2, 3],
    84      "esq": [],
    85      "emp": {},
    86      "map": {"a": 3},
    87      "str": "str",
    88      "int": 1000,
    89      "bin": 3,
    90      "hex": 17,
    91      "dec": 0.3,
    92      "dat": "\ufffd",
    93      "nil": null,
    94      "yes": true,
    95      "non": false
    96  }`,
    97  	}, {
    98  		name: "comments",
    99  		in: `
   100  // Document
   101  
   102  // head 1
   103  f1: 1
   104  // foot 1
   105  
   106  // head 2
   107  f2: 2 // line 2
   108  
   109  // intermezzo f2
   110  //
   111  // with multiline
   112  
   113  // head 3
   114  f3:
   115  	// struct doc
   116  	{
   117  		a: 1
   118  	}
   119  
   120  f4: {
   121  } // line 4
   122  
   123  // Trailing
   124  `,
   125  		out: `{
   126      "f1": 1,
   127      "f2": 2,
   128      "f3": {
   129          "a": 1
   130      },
   131  
   132      "f4": {}
   133  }`,
   134  	}, {
   135  		// TODO: support this at some point
   136  		name: "embed",
   137  		in: `
   138  	// hex
   139  	0xabc // line
   140  	// trail
   141  	`,
   142  		out: `2748`,
   143  	}, {
   144  		name: "anchors",
   145  		in: `
   146  		a: b
   147  		b: 3
   148  		`,
   149  		out: "json: unsupported node b (*ast.Ident)",
   150  	}, {
   151  		name: "errors",
   152  		in: `
   153  			m: {
   154  				a: 1
   155  				b: 3
   156  			}
   157  			c: [1, [ for x in m { x } ]]
   158  			`,
   159  		out: "json: unsupported node for x in m {x} (*ast.Comprehension)",
   160  	}, {
   161  		name: "disallowMultipleEmbeddings",
   162  		in: `
   163  		1
   164  		1
   165  		`,
   166  		out: "json: multiple embedded values",
   167  	}, {
   168  		name: "disallowDefinitions",
   169  		in:   `a :: 2 `,
   170  		out:  "json: definition not allowed",
   171  	}, {
   172  		name: "disallowOptionals",
   173  		in:   `a?: 2`,
   174  		out:  "json: optional fields not allowed",
   175  	}, {
   176  		name: "disallowBulkOptionals",
   177  		in:   `[string]: 2`,
   178  		out:  "json: only literal labels allowed",
   179  	}, {
   180  		name: "noImports",
   181  		in: `
   182  		import "foo"
   183  
   184  		a: 1
   185  		`,
   186  		out: `json: unsupported node import "foo" (*ast.ImportDecl)`,
   187  	}, {
   188  		name: "disallowMultipleEmbeddings",
   189  		in: `
   190  		1
   191  		a: 2
   192  		`,
   193  		out: "json: embedding mixed with fields",
   194  	}, {
   195  		name: "prometheus",
   196  		in: `
   197  		{
   198  			receivers: [{
   199  				name: "pager"
   200  				slack_configs: [{
   201  					text: """
   202  						{{ range .Alerts }}{{ .Annotations.description }}
   203  						{{ end }}
   204  						"""
   205  					channel:       "#cloudmon"
   206  					send_resolved: true
   207  				}]
   208  			}]
   209  			route: {
   210  				receiver: "pager"
   211  				group_by: ["alertname", "cluster"]
   212  			}
   213  		}`,
   214  		out: `{
   215      "receivers": [{
   216          "name": "pager",
   217          "slack_configs": [{
   218              "text": "{{ range .Alerts }}{{ .Annotations.description }}\n{{ end }}",
   219              "channel": "#cloudmon",
   220              "send_resolved": true
   221          }]
   222      }],
   223      "route": {
   224          "receiver": "pager",
   225          "group_by": ["alertname", "cluster"]
   226      }
   227  }`,
   228  	}}
   229  	for _, tc := range testCases {
   230  		t.Run(tc.name, func(t *testing.T) {
   231  			f, err := parser.ParseFile(tc.name, tc.in, parser.ParseComments)
   232  			if err != nil {
   233  				t.Fatal(err)
   234  			}
   235  			b, err := Encode(f)
   236  			var got string
   237  			if err != nil {
   238  				got = err.Error()
   239  			} else {
   240  				if !json.Valid(b) {
   241  					t.Fatal("invalid JSON")
   242  				}
   243  				got = strings.TrimSpace(string(b))
   244  			}
   245  			want := strings.TrimSpace(tc.out)
   246  			if got != want {
   247  				t.Log("\n" + got)
   248  				t.Error(cmp.Diff(got, want))
   249  			}
   250  		})
   251  	}
   252  }
   253  
   254  func TestEncodeAST(t *testing.T) {
   255  	comment := func(s string) *ast.CommentGroup {
   256  		return &ast.CommentGroup{List: []*ast.Comment{
   257  			&ast.Comment{Text: "// " + s},
   258  		}}
   259  	}
   260  	testCases := []struct {
   261  		name string
   262  		in   ast.Expr
   263  		out  string
   264  	}{{
   265  		in: ast.NewStruct(
   266  			comment("foo"),
   267  			comment("bar"),
   268  			"field", ast.NewString("value"),
   269  			"field2", ast.NewString("value"),
   270  			comment("trail1"),
   271  			comment("trail2"),
   272  		),
   273  		out: `{"field":"value","field2":"value"}`,
   274  	}, {
   275  		in: &ast.StructLit{Elts: []ast.Decl{
   276  			comment("bar"),
   277  			&ast.EmbedDecl{Expr: ast.NewBool(true)},
   278  		}},
   279  		out: `true`,
   280  	}, {
   281  		in: &ast.UnaryExpr{
   282  			Op: token.SUB,
   283  			X:  &ast.BasicLit{Kind: token.INT, Value: "-2"},
   284  		},
   285  		out: `double minus not allowed`,
   286  	}, {
   287  		in:  &ast.BasicLit{Kind: token.INT, Value: "-2.0.0"},
   288  		out: `invalid number "-2.0.0"`,
   289  	}, {
   290  		in: &ast.StructLit{Elts: []ast.Decl{
   291  			&ast.EmbedDecl{Expr: ast.NewBool(true)},
   292  			&ast.Package{},
   293  		}},
   294  		out: `invalid package clause`,
   295  	}}
   296  	for _, tc := range testCases {
   297  		t.Run(tc.name, func(t *testing.T) {
   298  			b, err := Encode(tc.in)
   299  			var got string
   300  			if err != nil {
   301  				got = err.Error()
   302  			} else {
   303  				got = strings.TrimSpace(string(b))
   304  			}
   305  			want := strings.TrimSpace(tc.out)
   306  			if got != want {
   307  				t.Error(cmp.Diff(got, want))
   308  			}
   309  		})
   310  	}
   311  }