github.com/bingoohuang/gg@v0.0.0-20240325092523-45da7dee9335/pkg/yaml/path_test.go (about) 1 package yaml_test 2 3 import ( 4 "fmt" 5 "log" 6 "reflect" 7 "strings" 8 "testing" 9 10 "github.com/bingoohuang/gg/pkg/yaml" 11 "github.com/bingoohuang/gg/pkg/yaml/parser" 12 ) 13 14 func builder() *yaml.PathBuilder { return &yaml.PathBuilder{} } 15 16 func TestPath(t *testing.T) { 17 yml := ` 18 store: 19 book: 20 - author: john 21 price: 10 22 - author: ken 23 price: 12 24 bicycle: 25 color: red 26 price: 19.95 27 ` 28 tests := []struct { 29 name string 30 path *yaml.Path 31 expected interface{} 32 }{ 33 { 34 name: "$.store.book[0].author", 35 path: builder().Root().Child("store").Child("book").Index(0).Child("author").Build(), 36 expected: "john", 37 }, 38 { 39 name: "$.store.book[1].price", 40 path: builder().Root().Child("store").Child("book").Index(1).Child("price").Build(), 41 expected: uint64(12), 42 }, 43 { 44 name: "$.store.book[*].author", 45 path: builder().Root().Child("store").Child("book").IndexAll().Child("author").Build(), 46 expected: []interface{}{"john", "ken"}, 47 }, 48 { 49 name: "$.store.book[0]", 50 path: builder().Root().Child("store").Child("book").Index(0).Build(), 51 expected: map[string]interface{}{"author": "john", "price": uint64(10)}, 52 }, 53 { 54 name: "$..author", 55 path: builder().Root().Recursive("author").Build(), 56 expected: []interface{}{"john", "ken"}, 57 }, 58 { 59 name: "$.store.bicycle.price", 60 path: builder().Root().Child("store").Child("bicycle").Child("price").Build(), 61 expected: float64(19.95), 62 }, 63 } 64 t.Run("PathString", func(t *testing.T) { 65 for _, test := range tests { 66 t.Run(test.name, func(t *testing.T) { 67 path, err := yaml.PathString(test.name) 68 if err != nil { 69 t.Fatalf("%+v", err) 70 } 71 if test.name != path.String() { 72 t.Fatalf("expected %s but actual %s", test.name, path.String()) 73 } 74 }) 75 } 76 }) 77 t.Run("string", func(t *testing.T) { 78 for _, test := range tests { 79 t.Run(test.name, func(t *testing.T) { 80 if test.name != test.path.String() { 81 t.Fatalf("expected %s but actual %s", test.name, test.path.String()) 82 } 83 }) 84 } 85 }) 86 t.Run("read", func(t *testing.T) { 87 for _, test := range tests { 88 t.Run(test.name, func(t *testing.T) { 89 var v interface{} 90 if err := test.path.Read(strings.NewReader(yml), &v); err != nil { 91 t.Fatalf("%+v", err) 92 } 93 if !reflect.DeepEqual(test.expected, v) { 94 t.Fatalf("expected %v(%T). but actual %v(%T)", test.expected, test.expected, v, v) 95 } 96 }) 97 } 98 }) 99 t.Run("filter", func(t *testing.T) { 100 var target interface{} 101 if err := yaml.Unmarshal([]byte(yml), &target); err != nil { 102 t.Fatalf("failed to unmarshal: %+v", err) 103 } 104 for _, test := range tests { 105 t.Run(test.name, func(t *testing.T) { 106 var v interface{} 107 if err := test.path.Filter(target, &v); err != nil { 108 t.Fatalf("%+v", err) 109 } 110 if !reflect.DeepEqual(test.expected, v) { 111 t.Fatalf("expected %v(%T). but actual %v(%T)", test.expected, test.expected, v, v) 112 } 113 }) 114 } 115 }) 116 } 117 118 func TestPath_Invalid(t *testing.T) { 119 tests := []struct { 120 path string 121 src string 122 }{ 123 { 124 path: "$.wrong", 125 src: "foo: bar", 126 }, 127 } 128 for _, test := range tests { 129 path, err := yaml.PathString(test.path) 130 if err != nil { 131 t.Fatal(err) 132 } 133 t.Run("path.Read", func(t *testing.T) { 134 file, err := parser.ParseBytes([]byte(test.src), 0) 135 if err != nil { 136 t.Fatal(err) 137 } 138 var v interface{} 139 err = path.Read(file, &v) 140 if err == nil { 141 t.Fatal("expected error") 142 } 143 if !yaml.IsNotFoundNodeError(err) { 144 t.Fatalf("unexpected error %s", err) 145 } 146 }) 147 t.Run("path.ReadNode", func(t *testing.T) { 148 file, err := parser.ParseBytes([]byte(test.src), 0) 149 if err != nil { 150 t.Fatal(err) 151 } 152 _, err = path.ReadNode(file) 153 if err == nil { 154 t.Fatal("expected error") 155 } 156 if !yaml.IsNotFoundNodeError(err) { 157 t.Fatalf("unexpected error %s", err) 158 } 159 }) 160 } 161 } 162 163 func TestPath_Merge(t *testing.T) { 164 tests := []struct { 165 path string 166 dst string 167 src string 168 expected string 169 }{ 170 { 171 "$.c", 172 ` 173 a: 1 174 b: 2 175 c: 176 d: 3 177 e: 4 178 `, 179 ` 180 f: 5 181 g: 6 182 `, 183 ` 184 a: 1 185 b: 2 186 c: 187 d: 3 188 e: 4 189 f: 5 190 g: 6 191 `, 192 }, 193 { 194 "$.a.b", 195 ` 196 a: 197 b: 198 - 1 199 - 2 200 `, 201 ` 202 - 3 203 - map: 204 - 4 205 - 5 206 `, 207 ` 208 a: 209 b: 210 - 1 211 - 2 212 - 3 213 - map: 214 - 4 215 - 5 216 `, 217 }, 218 } 219 for _, test := range tests { 220 t.Run(test.path, func(t *testing.T) { 221 path, err := yaml.PathString(test.path) 222 if err != nil { 223 t.Fatalf("%+v", err) 224 } 225 t.Run("FromReader", func(t *testing.T) { 226 file, err := parser.ParseBytes([]byte(test.dst), 0) 227 if err != nil { 228 t.Fatalf("%+v", err) 229 } 230 if err := path.MergeFromReader(file, strings.NewReader(test.src)); err != nil { 231 t.Fatalf("%+v", err) 232 } 233 actual := "\n" + file.String() + "\n" 234 if test.expected != actual { 235 t.Fatalf("expected: %q. but got %q", test.expected, actual) 236 } 237 }) 238 t.Run("FromFile", func(t *testing.T) { 239 file, err := parser.ParseBytes([]byte(test.dst), 0) 240 if err != nil { 241 t.Fatalf("%+v", err) 242 } 243 src, err := parser.ParseBytes([]byte(test.src), 0) 244 if err != nil { 245 t.Fatalf("%+v", err) 246 } 247 if err := path.MergeFromFile(file, src); err != nil { 248 t.Fatalf("%+v", err) 249 } 250 actual := "\n" + file.String() + "\n" 251 if test.expected != actual { 252 t.Fatalf("expected: %q. but got %q", test.expected, actual) 253 } 254 }) 255 t.Run("FromNode", func(t *testing.T) { 256 file, err := parser.ParseBytes([]byte(test.dst), 0) 257 if err != nil { 258 t.Fatalf("%+v", err) 259 } 260 src, err := parser.ParseBytes([]byte(test.src), 0) 261 if err != nil { 262 t.Fatalf("%+v", err) 263 } 264 if len(src.Docs) == 0 { 265 t.Fatalf("failed to parse") 266 } 267 if err := path.MergeFromNode(file, src.Docs[0]); err != nil { 268 t.Fatalf("%+v", err) 269 } 270 actual := "\n" + file.String() + "\n" 271 if test.expected != actual { 272 t.Fatalf("expected: %q. but got %q", test.expected, actual) 273 } 274 }) 275 }) 276 } 277 } 278 279 func TestPath_Replace(t *testing.T) { 280 tests := []struct { 281 path string 282 dst string 283 src string 284 expected string 285 }{ 286 { 287 "$.a", 288 ` 289 a: 1 290 b: 2 291 `, 292 `3`, 293 ` 294 a: 3 295 b: 2 296 `, 297 }, 298 { 299 "$.b", 300 ` 301 b: 1 302 c: 2 303 `, 304 ` 305 d: e 306 f: 307 g: h 308 i: j 309 `, 310 ` 311 b: 312 d: e 313 f: 314 g: h 315 i: j 316 c: 2 317 `, 318 }, 319 { 320 "$.a.b[0]", 321 ` 322 a: 323 b: 324 - hello 325 c: 2 326 `, 327 `world`, 328 ` 329 a: 330 b: 331 - world 332 c: 2 333 `, 334 }, 335 336 { 337 "$.books[*].author", 338 ` 339 books: 340 - name: book_a 341 author: none 342 - name: book_b 343 author: none 344 pictures: 345 - name: picture_a 346 author: none 347 - name: picture_b 348 author: none 349 building: 350 author: none 351 `, 352 `ken`, 353 ` 354 books: 355 - name: book_a 356 author: ken 357 - name: book_b 358 author: ken 359 pictures: 360 - name: picture_a 361 author: none 362 - name: picture_b 363 author: none 364 building: 365 author: none 366 `, 367 }, 368 { 369 "$..author", 370 ` 371 books: 372 - name: book_a 373 author: none 374 - name: book_b 375 author: none 376 pictures: 377 - name: picture_a 378 author: none 379 - name: picture_b 380 author: none 381 building: 382 author: none 383 `, 384 `ken`, 385 ` 386 books: 387 - name: book_a 388 author: ken 389 - name: book_b 390 author: ken 391 pictures: 392 - name: picture_a 393 author: ken 394 - name: picture_b 395 author: ken 396 building: 397 author: ken 398 `, 399 }, 400 } 401 for _, test := range tests { 402 t.Run(test.path, func(t *testing.T) { 403 path, err := yaml.PathString(test.path) 404 if err != nil { 405 t.Fatalf("%+v", err) 406 } 407 t.Run("WithReader", func(t *testing.T) { 408 file, err := parser.ParseBytes([]byte(test.dst), 0) 409 if err != nil { 410 t.Fatalf("%+v", err) 411 } 412 if err := path.ReplaceWithReader(file, strings.NewReader(test.src)); err != nil { 413 t.Fatalf("%+v", err) 414 } 415 actual := "\n" + file.String() + "\n" 416 if test.expected != actual { 417 t.Fatalf("expected: %q. but got %q", test.expected, actual) 418 } 419 }) 420 t.Run("WithFile", func(t *testing.T) { 421 file, err := parser.ParseBytes([]byte(test.dst), 0) 422 if err != nil { 423 t.Fatalf("%+v", err) 424 } 425 src, err := parser.ParseBytes([]byte(test.src), 0) 426 if err != nil { 427 t.Fatalf("%+v", err) 428 } 429 if err := path.ReplaceWithFile(file, src); err != nil { 430 t.Fatalf("%+v", err) 431 } 432 actual := "\n" + file.String() + "\n" 433 if test.expected != actual { 434 t.Fatalf("expected: %q. but got %q", test.expected, actual) 435 } 436 }) 437 t.Run("WithNode", func(t *testing.T) { 438 file, err := parser.ParseBytes([]byte(test.dst), 0) 439 if err != nil { 440 t.Fatalf("%+v", err) 441 } 442 src, err := parser.ParseBytes([]byte(test.src), 0) 443 if err != nil { 444 t.Fatalf("%+v", err) 445 } 446 if len(src.Docs) == 0 { 447 t.Fatalf("failed to parse") 448 } 449 if err := path.ReplaceWithNode(file, src.Docs[0]); err != nil { 450 t.Fatalf("%+v", err) 451 } 452 actual := "\n" + file.String() + "\n" 453 if test.expected != actual { 454 t.Fatalf("expected: %q. but got %q", test.expected, actual) 455 } 456 }) 457 }) 458 } 459 } 460 461 func ExamplePath_AnnotateSource() { 462 yml := ` 463 a: 1 464 b: "hello" 465 ` 466 var v struct { 467 A int 468 B string 469 } 470 if err := yaml.Unmarshal([]byte(yml), &v); err != nil { 471 panic(err) 472 } 473 if v.A != 2 { 474 // output error with YAML source 475 path, err := yaml.PathString("$.a") 476 if err != nil { 477 log.Fatal(err) 478 } 479 source, err := path.AnnotateSource([]byte(yml), false) 480 if err != nil { 481 log.Fatal(err) 482 } 483 fmt.Printf("a value expected 2 but actual %d:\n%s\n", v.A, string(source)) 484 } 485 // OUTPUT: 486 // a value expected 2 but actual 1: 487 // > 2 | a: 1 488 // ^ 489 // 3 | b: "hello" 490 } 491 492 func ExamplePath_PathString() { 493 yml := ` 494 store: 495 book: 496 - author: john 497 price: 10 498 - author: ken 499 price: 12 500 bicycle: 501 color: red 502 price: 19.95 503 ` 504 path, err := yaml.PathString("$.store.book[*].author") 505 if err != nil { 506 log.Fatal(err) 507 } 508 var authors []string 509 if err := path.Read(strings.NewReader(yml), &authors); err != nil { 510 log.Fatal(err) 511 } 512 fmt.Println(authors) 513 // OUTPUT: 514 // [john ken] 515 }