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  }