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 }