github.com/vishnusomank/figtree@v0.1.0/figtree_test.go (about)

     1  package figtree
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"fmt"
     7  	"os"
     8  	"path"
     9  	"reflect"
    10  	"runtime"
    11  	"strconv"
    12  	"strings"
    13  	"testing"
    14  	"unicode"
    15  
    16  	"emperror.dev/errors"
    17  	logging "gopkg.in/op/go-logging.v1"
    18  	yaml "gopkg.in/yaml.v3"
    19  
    20  	"github.com/coryb/walky"
    21  	"github.com/stretchr/testify/assert"
    22  	"github.com/stretchr/testify/require"
    23  )
    24  
    25  type info struct {
    26  	name string
    27  	line string
    28  }
    29  
    30  func line() string {
    31  	_, file, line, _ := runtime.Caller(1)
    32  	return fmt.Sprintf("%s:%d", path.Base(file), line)
    33  }
    34  
    35  func init() {
    36  	StringifyValue = false
    37  	logging.SetLevel(logging.NOTICE, "")
    38  }
    39  
    40  func newFigTreeFromEnv(opts ...CreateOption) *FigTree {
    41  	cwd, _ := os.Getwd()
    42  	opts = append([]CreateOption{
    43  		WithHome(os.Getenv("HOME")),
    44  		WithCwd(cwd),
    45  		WithEnvPrefix("FIGTREE"),
    46  	}, opts...)
    47  
    48  	return NewFigTree(opts...)
    49  }
    50  
    51  func tSrc(s string, l, c int) SourceLocation {
    52  	return NewSource(s, WithLocation(&FileCoordinate{Line: l, Column: c}))
    53  }
    54  
    55  type TestOptions struct {
    56  	String1    StringOption     `json:"str1,omitempty" yaml:"str1,omitempty"`
    57  	LeaveEmpty StringOption     `json:"leave-empty,omitempty" yaml:"leave-empty,omitempty"`
    58  	Array1     ListStringOption `json:"arr1,omitempty" yaml:"arr1,omitempty"`
    59  	Map1       MapStringOption  `json:"map1,omitempty" yaml:"map1,omitempty"`
    60  	Int1       IntOption        `json:"int1,omitempty" yaml:"int1,omitempty"`
    61  	Float1     Float32Option    `json:"float1,omitempty" yaml:"float1,omitempty"`
    62  	Bool1      BoolOption       `json:"bool1,omitempty" yaml:"bool1,omitempty"`
    63  }
    64  
    65  type TestBuiltin struct {
    66  	String1    string            `yaml:"str1,omitempty"`
    67  	LeaveEmpty string            `yaml:"leave-empty,omitempty"`
    68  	Array1     []string          `yaml:"arr1,omitempty"`
    69  	Map1       map[string]string `yaml:"map1,omitempty"`
    70  	Int1       int               `yaml:"int1,omitempty"`
    71  	Float1     float32           `yaml:"float1,omitempty"`
    72  	Bool1      bool              `yaml:"bool1,omitempty"`
    73  }
    74  
    75  func TestOptionsLoadConfigD3(t *testing.T) {
    76  	opts := TestOptions{}
    77  	require.NoError(t, os.Chdir("d1/d2/d3"))
    78  	t.Cleanup(func() {
    79  		_ = os.Chdir("../../..")
    80  	})
    81  
    82  	arr1 := []StringOption{}
    83  	arr1 = append(arr1, StringOption{tSrc("figtree.yml", 3, 5), true, "d3arr1val1"})
    84  	arr1 = append(arr1, StringOption{tSrc("figtree.yml", 4, 5), true, "d3arr1val2"})
    85  	arr1 = append(arr1, StringOption{tSrc("figtree.yml", 5, 5), true, "dupval"})
    86  	arr1 = append(arr1, StringOption{tSrc("../figtree.yml", 3, 5), true, "211"})
    87  	arr1 = append(arr1, StringOption{tSrc("../figtree.yml", 4, 5), true, "d2arr1val2"})
    88  	arr1 = append(arr1, StringOption{tSrc("../../figtree.yml", 3, 5), true, "d1arr1val1"})
    89  	arr1 = append(arr1, StringOption{tSrc("../../figtree.yml", 4, 5), true, "d1arr1val2"})
    90  
    91  	expected := TestOptions{
    92  		String1:    StringOption{tSrc("figtree.yml", 1, 7), true, "d3str1val1"},
    93  		LeaveEmpty: StringOption{},
    94  		Array1:     arr1,
    95  		Map1: map[string]StringOption{
    96  			"key0": {tSrc("../../figtree.yml", 7, 9), true, "d1map1val0"},
    97  			"key1": {tSrc("../figtree.yml", 7, 9), true, "211"},
    98  			"key2": {tSrc("figtree.yml", 7, 9), true, "d3map1val2"},
    99  			"key3": {tSrc("figtree.yml", 8, 9), true, "d3map1val3"},
   100  			"dup":  {tSrc("figtree.yml", 9, 9), true, "d3dupval"},
   101  		},
   102  		Int1:   IntOption{tSrc("figtree.yml", 10, 7), true, 333},
   103  		Float1: Float32Option{tSrc("figtree.yml", 11, 9), true, 3.33},
   104  		Bool1:  BoolOption{tSrc("figtree.yml", 12, 8), true, true},
   105  	}
   106  
   107  	fig := newFigTreeFromEnv()
   108  	err := fig.LoadAllConfigs("figtree.yml", &opts)
   109  	assert.NoError(t, err)
   110  	assert.Exactly(t, expected, opts)
   111  }
   112  
   113  func TestOptionsLoadConfigD2(t *testing.T) {
   114  	opts := TestOptions{}
   115  	require.NoError(t, os.Chdir("d1/d2"))
   116  	t.Cleanup(func() {
   117  		_ = os.Chdir("../..")
   118  	})
   119  
   120  	arr1 := []StringOption{}
   121  	arr1 = append(arr1, StringOption{tSrc("figtree.yml", 3, 5), true, "211"})
   122  	arr1 = append(arr1, StringOption{tSrc("figtree.yml", 4, 5), true, "d2arr1val2"})
   123  	arr1 = append(arr1, StringOption{tSrc("figtree.yml", 5, 5), true, "dupval"})
   124  	arr1 = append(arr1, StringOption{tSrc("../figtree.yml", 3, 5), true, "d1arr1val1"})
   125  	arr1 = append(arr1, StringOption{tSrc("../figtree.yml", 4, 5), true, "d1arr1val2"})
   126  
   127  	expected := TestOptions{
   128  		String1:    StringOption{tSrc("figtree.yml", 1, 7), true, "d2str1val1"},
   129  		LeaveEmpty: StringOption{},
   130  		Array1:     arr1,
   131  		Map1: map[string]StringOption{
   132  			"key0": {tSrc("../figtree.yml", 7, 9), true, "d1map1val0"},
   133  			"key1": {tSrc("figtree.yml", 7, 9), true, "211"},
   134  			"key2": {tSrc("figtree.yml", 8, 9), true, "d2map1val2"},
   135  			"dup":  {tSrc("figtree.yml", 9, 9), true, "d2dupval"},
   136  		},
   137  		Int1:   IntOption{tSrc("figtree.yml", 10, 7), true, 222},
   138  		Float1: Float32Option{tSrc("figtree.yml", 11, 9), true, 2.22},
   139  		Bool1:  BoolOption{tSrc("figtree.yml", 12, 8), true, false},
   140  	}
   141  
   142  	fig := newFigTreeFromEnv()
   143  	err := fig.LoadAllConfigs("figtree.yml", &opts)
   144  	assert.NoError(t, err)
   145  	assert.Exactly(t, expected, opts)
   146  }
   147  
   148  func TestOptionsLoadConfigD1(t *testing.T) {
   149  	opts := TestOptions{}
   150  	require.NoError(t, os.Chdir("d1"))
   151  	t.Cleanup(func() {
   152  		_ = os.Chdir("..")
   153  	})
   154  
   155  	arr1 := []StringOption{}
   156  	arr1 = append(arr1, StringOption{tSrc("figtree.yml", 3, 5), true, "d1arr1val1"})
   157  	arr1 = append(arr1, StringOption{tSrc("figtree.yml", 4, 5), true, "d1arr1val2"})
   158  	arr1 = append(arr1, StringOption{tSrc("figtree.yml", 5, 5), true, "dupval"})
   159  
   160  	expected := TestOptions{
   161  		String1:    StringOption{tSrc("figtree.yml", 1, 7), true, "d1str1val1"},
   162  		LeaveEmpty: StringOption{},
   163  		Array1:     arr1,
   164  		Map1: map[string]StringOption{
   165  			"key0": {tSrc("figtree.yml", 7, 9), true, "d1map1val0"},
   166  			"key1": {tSrc("figtree.yml", 8, 9), true, "d1map1val1"},
   167  			"dup":  {tSrc("figtree.yml", 9, 9), true, "d1dupval"},
   168  		},
   169  		Int1:   IntOption{tSrc("figtree.yml", 10, 7), true, 111},
   170  		Float1: Float32Option{tSrc("figtree.yml", 11, 9), true, 1.11},
   171  		Bool1:  BoolOption{tSrc("figtree.yml", 12, 8), true, true},
   172  	}
   173  
   174  	fig := newFigTreeFromEnv()
   175  	err := fig.LoadAllConfigs("figtree.yml", &opts)
   176  	assert.NoError(t, err)
   177  	assert.Exactly(t, expected, opts)
   178  }
   179  
   180  func TestOptionsCorrupt(t *testing.T) {
   181  	opts := TestOptions{}
   182  	require.NoError(t, os.Chdir("d1"))
   183  	t.Cleanup(func() {
   184  		_ = os.Chdir("..")
   185  	})
   186  
   187  	fig := newFigTreeFromEnv()
   188  	err := fig.LoadAllConfigs("corrupt.yml", &opts)
   189  	assert.NotNil(t, err)
   190  }
   191  
   192  func TestBuiltinLoadConfigD3(t *testing.T) {
   193  	opts := TestBuiltin{}
   194  	require.NoError(t, os.Chdir("d1/d2/d3"))
   195  	t.Cleanup(func() {
   196  		_ = os.Chdir("../../..")
   197  	})
   198  
   199  	arr1 := []string{}
   200  	arr1 = append(arr1, "d3arr1val1")
   201  	arr1 = append(arr1, "d3arr1val2")
   202  	arr1 = append(arr1, "dupval")
   203  	arr1 = append(arr1, "211")
   204  	arr1 = append(arr1, "d2arr1val2")
   205  	arr1 = append(arr1, "d1arr1val1")
   206  	arr1 = append(arr1, "d1arr1val2")
   207  
   208  	expected := TestBuiltin{
   209  		String1:    "d3str1val1",
   210  		LeaveEmpty: "",
   211  		Array1:     arr1,
   212  		Map1: map[string]string{
   213  			"key0": "d1map1val0",
   214  			"key1": "211",
   215  			"key2": "d3map1val2",
   216  			"key3": "d3map1val3",
   217  			"dup":  "d3dupval",
   218  		},
   219  		Int1:   333,
   220  		Float1: 3.33,
   221  		Bool1:  true,
   222  	}
   223  
   224  	fig := newFigTreeFromEnv()
   225  	err := fig.LoadAllConfigs("figtree.yml", &opts)
   226  	assert.NoError(t, err)
   227  	assert.Exactly(t, expected, opts)
   228  }
   229  
   230  func TestBuiltinLoadConfigD2(t *testing.T) {
   231  	opts := TestBuiltin{}
   232  	require.NoError(t, os.Chdir("d1/d2"))
   233  	t.Cleanup(func() {
   234  		_ = os.Chdir("../..")
   235  	})
   236  
   237  	arr1 := []string{}
   238  	arr1 = append(arr1, "211")
   239  	arr1 = append(arr1, "d2arr1val2")
   240  	arr1 = append(arr1, "dupval")
   241  	arr1 = append(arr1, "d1arr1val1")
   242  	arr1 = append(arr1, "d1arr1val2")
   243  
   244  	expected := TestBuiltin{
   245  		String1:    "d2str1val1",
   246  		LeaveEmpty: "",
   247  		Array1:     arr1,
   248  		Map1: map[string]string{
   249  			"key0": "d1map1val0",
   250  			"key1": "211",
   251  			"key2": "d2map1val2",
   252  			"dup":  "d2dupval",
   253  		},
   254  		Int1:   222,
   255  		Float1: 2.22,
   256  		// note this will be true from d1/figtree.yml since the
   257  		// d1/d2/figtree.yml set it to false which is a zero value
   258  		Bool1: true,
   259  	}
   260  
   261  	fig := newFigTreeFromEnv()
   262  	err := fig.LoadAllConfigs("figtree.yml", &opts)
   263  	assert.NoError(t, err)
   264  	assert.Exactly(t, expected, opts)
   265  }
   266  
   267  func TestBuiltinLoadConfigD1(t *testing.T) {
   268  	opts := TestBuiltin{}
   269  	require.NoError(t, os.Chdir("d1"))
   270  	t.Cleanup(func() {
   271  		_ = os.Chdir("..")
   272  	})
   273  
   274  	arr1 := []string{}
   275  	arr1 = append(arr1, "d1arr1val1")
   276  	arr1 = append(arr1, "d1arr1val2")
   277  	arr1 = append(arr1, "dupval")
   278  
   279  	expected := TestBuiltin{
   280  		String1:    "d1str1val1",
   281  		LeaveEmpty: "",
   282  		Array1:     arr1,
   283  		Map1: map[string]string{
   284  			"key0": "d1map1val0",
   285  			"key1": "d1map1val1",
   286  			"dup":  "d1dupval",
   287  		},
   288  		Int1:   111,
   289  		Float1: 1.11,
   290  		Bool1:  true,
   291  	}
   292  
   293  	fig := newFigTreeFromEnv()
   294  	err := fig.LoadAllConfigs("figtree.yml", &opts)
   295  	assert.NoError(t, err)
   296  	assert.Exactly(t, expected, opts)
   297  }
   298  
   299  func TestBuiltinCorrupt(t *testing.T) {
   300  	opts := TestBuiltin{}
   301  	require.NoError(t, os.Chdir("d1"))
   302  	t.Cleanup(func() {
   303  		_ = os.Chdir("..")
   304  	})
   305  
   306  	fig := newFigTreeFromEnv()
   307  	err := fig.LoadAllConfigs("corrupt.yml", &opts)
   308  	assert.NotNil(t, err)
   309  }
   310  
   311  func TestOptionsLoadConfigDefaults(t *testing.T) {
   312  	opts := TestOptions{
   313  		String1:    NewStringOption("defaultVal1"),
   314  		LeaveEmpty: NewStringOption("emptyVal1"),
   315  		Int1:       NewIntOption(999),
   316  		Float1:     NewFloat32Option(9.99),
   317  		Bool1:      NewBoolOption(true),
   318  	}
   319  	require.NoError(t, os.Chdir("d1/d2"))
   320  	t.Cleanup(func() {
   321  		_ = os.Chdir("../..")
   322  	})
   323  
   324  	arr1 := []StringOption{}
   325  	arr1 = append(arr1, StringOption{tSrc("figtree.yml", 3, 5), true, "211"})
   326  	arr1 = append(arr1, StringOption{tSrc("figtree.yml", 4, 5), true, "d2arr1val2"})
   327  	arr1 = append(arr1, StringOption{tSrc("figtree.yml", 5, 5), true, "dupval"})
   328  	arr1 = append(arr1, StringOption{tSrc("../figtree.yml", 3, 5), true, "d1arr1val1"})
   329  	arr1 = append(arr1, StringOption{tSrc("../figtree.yml", 4, 5), true, "d1arr1val2"})
   330  
   331  	expected := TestOptions{
   332  		String1:    StringOption{tSrc("figtree.yml", 1, 7), true, "d2str1val1"},
   333  		LeaveEmpty: StringOption{NewSource("default"), true, "emptyVal1"},
   334  		Array1:     arr1,
   335  		Map1: map[string]StringOption{
   336  			"key0": {tSrc("../figtree.yml", 7, 9), true, "d1map1val0"},
   337  			"key1": {tSrc("figtree.yml", 7, 9), true, "211"},
   338  			"key2": {tSrc("figtree.yml", 8, 9), true, "d2map1val2"},
   339  			"dup":  {tSrc("figtree.yml", 9, 9), true, "d2dupval"},
   340  		},
   341  		Int1:   IntOption{tSrc("figtree.yml", 10, 7), true, 222},
   342  		Float1: Float32Option{tSrc("figtree.yml", 11, 9), true, 2.22},
   343  		Bool1:  BoolOption{tSrc("figtree.yml", 12, 8), true, false},
   344  	}
   345  
   346  	fig := newFigTreeFromEnv()
   347  	err := fig.LoadAllConfigs("figtree.yml", &opts)
   348  	assert.NoError(t, err)
   349  	require.Exactly(t, expected, opts)
   350  }
   351  
   352  func TestMergeMapsWithNull(t *testing.T) {
   353  	dest := map[string]interface{}{
   354  		"requires": map[string]interface{}{
   355  			"pkgA": nil,
   356  			"pkgB": ">1.2.3",
   357  		},
   358  	}
   359  
   360  	src := map[string]interface{}{
   361  		"requires": map[string]interface{}{
   362  			"pkgC": "<1.2.3",
   363  			"pkgD": nil,
   364  		},
   365  	}
   366  
   367  	err := Merge(dest, src)
   368  	require.NoError(t, err)
   369  
   370  	expected := map[string]interface{}{
   371  		"requires": map[string]interface{}{
   372  			"pkgA": nil,
   373  			"pkgB": ">1.2.3",
   374  			"pkgC": "<1.2.3",
   375  			"pkgD": nil,
   376  		},
   377  	}
   378  	assert.Equal(t, expected, dest)
   379  }
   380  
   381  func TestMergeMapsIntoStructWithNull(t *testing.T) {
   382  	src1 := map[string]interface{}{
   383  		"requires": map[string]interface{}{
   384  			"pkgA": nil,
   385  			"pkgB": ">1.2.3",
   386  		},
   387  	}
   388  
   389  	src2 := map[string]interface{}{
   390  		"requires": map[string]interface{}{
   391  			"pkgC": "<1.2.3",
   392  			"pkgD": nil,
   393  		},
   394  	}
   395  
   396  	dest := MakeMergeStruct(src1, src2)
   397  	err := Merge(dest, src1)
   398  	require.NoError(t, err)
   399  	err = Merge(dest, src2)
   400  	require.NoError(t, err)
   401  
   402  	expected := &struct {
   403  		Requires struct {
   404  			PkgA interface{} `json:"pkgA" yaml:"pkgA"`
   405  			PkgB string      `json:"pkgB" yaml:"pkgB"`
   406  			PkgC string      `json:"pkgC" yaml:"pkgC"`
   407  			PkgD interface{} `json:"pkgD" yaml:"pkgD"`
   408  		} `json:"requires" yaml:"requires"`
   409  	}{
   410  		struct {
   411  			PkgA interface{} `json:"pkgA" yaml:"pkgA"`
   412  			PkgB string      `json:"pkgB" yaml:"pkgB"`
   413  			PkgC string      `json:"pkgC" yaml:"pkgC"`
   414  			PkgD interface{} `json:"pkgD" yaml:"pkgD"`
   415  		}{
   416  			PkgA: nil,
   417  			PkgB: ">1.2.3",
   418  			PkgC: "<1.2.3",
   419  			PkgD: nil,
   420  		},
   421  	}
   422  	assert.Equal(t, expected, dest)
   423  }
   424  
   425  func TestMergeStringIntoStringOption(t *testing.T) {
   426  	src1 := struct {
   427  		Value StringOption
   428  	}{}
   429  
   430  	src2 := struct {
   431  		Value string
   432  	}{"val1"}
   433  
   434  	dest := MakeMergeStruct(src1, src2)
   435  
   436  	err := Merge(dest, src1)
   437  	require.NoError(t, err)
   438  	err = Merge(dest, src2)
   439  	require.NoError(t, err)
   440  
   441  	expected := &struct {
   442  		Value StringOption
   443  	}{StringOption{NewSource("merge"), true, "val1"}}
   444  	assert.Equal(t, expected, dest)
   445  }
   446  
   447  func TestMergeStringOptions(t *testing.T) {
   448  	src1 := struct {
   449  		Value StringOption
   450  	}{}
   451  
   452  	src2 := struct {
   453  		Value StringOption
   454  	}{NewStringOption("val1")}
   455  
   456  	dest := MakeMergeStruct(src1, src2)
   457  
   458  	err := Merge(dest, src1)
   459  	require.NoError(t, err)
   460  	err = Merge(dest, src2)
   461  	require.NoError(t, err)
   462  
   463  	expected := &struct {
   464  		Value StringOption
   465  	}{StringOption{NewSource("default"), true, "val1"}}
   466  	assert.Equal(t, expected, dest)
   467  }
   468  
   469  func TestMergeMapStringIntoStringOption(t *testing.T) {
   470  	src1 := map[string]interface{}{
   471  		"map": MapStringOption{},
   472  	}
   473  
   474  	src2 := map[string]interface{}{
   475  		"map": MapStringOption{
   476  			"key": NewStringOption("val1"),
   477  		},
   478  	}
   479  	dest := MakeMergeStruct(src1, src2)
   480  
   481  	err := Merge(dest, src1)
   482  	require.NoError(t, err)
   483  	err = Merge(dest, src2)
   484  	require.NoError(t, err)
   485  
   486  	expected := &struct {
   487  		Map struct {
   488  			Key StringOption `json:"key" yaml:"key"`
   489  		} `json:"map" yaml:"map"`
   490  	}{
   491  		Map: struct {
   492  			Key StringOption `json:"key" yaml:"key"`
   493  		}{StringOption{NewSource("default"), true, "val1"}},
   494  	}
   495  	assert.Equal(t, expected, dest)
   496  }
   497  
   498  func TestMergeMapStringOptions(t *testing.T) {
   499  	src1 := struct {
   500  		Value StringOption
   501  	}{}
   502  
   503  	src2 := struct {
   504  		Value StringOption
   505  	}{NewStringOption("val1")}
   506  
   507  	dest := MakeMergeStruct(src1, src2)
   508  
   509  	err := Merge(dest, src1)
   510  	require.NoError(t, err)
   511  	err = Merge(dest, src2)
   512  	require.NoError(t, err)
   513  
   514  	expected := &struct {
   515  		Value StringOption
   516  	}{StringOption{NewSource("default"), true, "val1"}}
   517  	assert.Equal(t, expected, dest)
   518  }
   519  
   520  func TestMergeMapWithStruct(t *testing.T) {
   521  	dest := map[string]interface{}{
   522  		"mapkey": "mapval1",
   523  		"map": map[string]interface{}{
   524  			"mapkey":  "mapval2",
   525  			"nullkey": nil,
   526  		},
   527  	}
   528  
   529  	src := struct {
   530  		StructField string
   531  		Map         struct {
   532  			StructField string
   533  		}
   534  	}{
   535  		StructField: "field1",
   536  		Map: struct {
   537  			StructField string
   538  		}{
   539  			StructField: "field2",
   540  		},
   541  	}
   542  
   543  	m := NewMerger()
   544  	changed, err := m.mergeStructs(reflect.ValueOf(&dest), newMergeSource(reflect.ValueOf(&src)), false)
   545  	require.NoError(t, err)
   546  	require.True(t, changed)
   547  
   548  	expected := map[string]interface{}{
   549  		"mapkey":       "mapval1",
   550  		"struct-field": "field1",
   551  		"map": map[string]interface{}{
   552  			"mapkey":       "mapval2",
   553  			"nullkey":      nil,
   554  			"struct-field": "field2",
   555  		},
   556  	}
   557  	assert.Equal(t, expected, dest)
   558  }
   559  
   560  func TestMergeStructWithMap(t *testing.T) {
   561  	dest := struct {
   562  		StructField string
   563  		Mapkey      string
   564  		Map         struct {
   565  			StructField string
   566  			Mapkey      string
   567  		}
   568  	}{
   569  		StructField: "field1",
   570  		Map: struct {
   571  			StructField string
   572  			Mapkey      string
   573  		}{
   574  			StructField: "field2",
   575  		},
   576  	}
   577  
   578  	src := map[string]interface{}{
   579  		"mapkey": "mapval1",
   580  		"map": map[string]interface{}{
   581  			"mapkey":  "mapval2",
   582  			"nullkey": nil,
   583  		},
   584  	}
   585  
   586  	merged := MakeMergeStruct(&dest, &src)
   587  	err := Merge(merged, &dest)
   588  	require.NoError(t, err)
   589  	err = Merge(merged, &src)
   590  	require.NoError(t, err)
   591  
   592  	expected := struct {
   593  		Map struct {
   594  			Mapkey      string
   595  			Nullkey     interface{} `json:"nullkey" yaml:"nullkey"`
   596  			StructField string
   597  		}
   598  		Mapkey      string
   599  		StructField string
   600  	}{
   601  		Map: struct {
   602  			Mapkey      string
   603  			Nullkey     interface{} `json:"nullkey" yaml:"nullkey"`
   604  			StructField string
   605  		}{
   606  			Mapkey:      "mapval2",
   607  			StructField: "field2",
   608  		},
   609  		Mapkey:      "mapval1",
   610  		StructField: "field1",
   611  	}
   612  	assert.Equal(t, &expected, merged)
   613  }
   614  
   615  func TestMergeStructWithMapArbitraryNaming(t *testing.T) {
   616  	// Go struct field names should not matter
   617  	dest := struct {
   618  		MyStructField string `yaml:"struct-field"`
   619  		MyMapkey      string `yaml:"mapkey"`
   620  		MyMap         struct {
   621  			MyStructField string `yaml:"struct-field"`
   622  			MyMapkey      string `yaml:"mapkey"`
   623  		} `yaml:"map"`
   624  	}{
   625  		MyStructField: "field1",
   626  		MyMap: struct {
   627  			MyStructField string `yaml:"struct-field"`
   628  			MyMapkey      string `yaml:"mapkey"`
   629  		}{
   630  			MyStructField: "field2",
   631  		},
   632  	}
   633  
   634  	src := map[string]interface{}{
   635  		"mapkey": "mapval1",
   636  		"map": map[string]interface{}{
   637  			"mapkey":  "mapval2",
   638  			"nullkey": nil,
   639  		},
   640  	}
   641  
   642  	merged := MakeMergeStruct(&dest, &src)
   643  	err := Merge(merged, &dest)
   644  	require.NoError(t, err)
   645  	err = Merge(merged, &src)
   646  	require.NoError(t, err)
   647  
   648  	expected := struct {
   649  		Map struct {
   650  			Mapkey      string      `yaml:"mapkey"`
   651  			Nullkey     interface{} `json:"nullkey" yaml:"nullkey"`
   652  			StructField string      `yaml:"struct-field"`
   653  		} `yaml:"map"`
   654  		Mapkey      string `yaml:"mapkey"`
   655  		StructField string `yaml:"struct-field"`
   656  	}{
   657  		Map: struct {
   658  			Mapkey      string      `yaml:"mapkey"`
   659  			Nullkey     interface{} `json:"nullkey" yaml:"nullkey"`
   660  			StructField string      `yaml:"struct-field"`
   661  		}{
   662  			Mapkey:      "mapval2",
   663  			StructField: "field2",
   664  		},
   665  		Mapkey:      "mapval1",
   666  		StructField: "field1",
   667  	}
   668  	assert.Equal(t, &expected, merged)
   669  }
   670  
   671  func TestMergeStructUsingOptionsWithMap(t *testing.T) {
   672  	dest := struct {
   673  		Bool    BoolOption
   674  		Byte    ByteOption
   675  		Float32 Float32Option
   676  		Float64 Float64Option
   677  		Int16   Int16Option
   678  		Int32   Int32Option
   679  		Int64   Int64Option
   680  		Int8    Int8Option
   681  		Int     IntOption
   682  		Rune    RuneOption
   683  		String  StringOption
   684  		Uint16  Uint16Option
   685  		Uint32  Uint32Option
   686  		Uint64  Uint64Option
   687  		Uint8   Uint8Option
   688  		Uint    UintOption
   689  	}{}
   690  
   691  	src := map[string]interface{}{
   692  		"bool":     true,
   693  		"byte":     byte(10),
   694  		"float-32": float32(1.23),
   695  		"float-64": float64(2.34),
   696  		"int-16":   int16(123),
   697  		"int-32":   int32(234),
   698  		"int-64":   int64(345),
   699  		"int-8":    int8(127),
   700  		"int":      int(456),
   701  		"rune":     rune('a'),
   702  		"string":   "stringval",
   703  		"uint-16":  uint16(123),
   704  		"uint-32":  uint32(234),
   705  		"uint-64":  uint64(345),
   706  		"uint-8":   uint8(255),
   707  		"uint":     uint(456),
   708  	}
   709  
   710  	err := Merge(&dest, &src)
   711  	require.NoError(t, err)
   712  
   713  	expected := struct {
   714  		Bool    BoolOption
   715  		Byte    ByteOption
   716  		Float32 Float32Option
   717  		Float64 Float64Option
   718  		Int16   Int16Option
   719  		Int32   Int32Option
   720  		Int64   Int64Option
   721  		Int8    Int8Option
   722  		Int     IntOption
   723  		Rune    RuneOption
   724  		String  StringOption
   725  		Uint16  Uint16Option
   726  		Uint32  Uint32Option
   727  		Uint64  Uint64Option
   728  		Uint8   Uint8Option
   729  		Uint    UintOption
   730  	}{
   731  		Bool:    BoolOption{NewSource("merge"), true, true},
   732  		Byte:    ByteOption{NewSource("merge"), true, byte(10)},
   733  		Float32: Float32Option{NewSource("merge"), true, float32(1.23)},
   734  		Float64: Float64Option{NewSource("merge"), true, float64(2.34)},
   735  		Int16:   Int16Option{NewSource("merge"), true, int16(123)},
   736  		Int32:   Int32Option{NewSource("merge"), true, int32(234)},
   737  		Int64:   Int64Option{NewSource("merge"), true, int64(345)},
   738  		Int8:    Int8Option{NewSource("merge"), true, int8(127)},
   739  		Int:     IntOption{NewSource("merge"), true, int(456)},
   740  		Rune:    RuneOption{NewSource("merge"), true, rune('a')},
   741  		String:  StringOption{NewSource("merge"), true, "stringval"},
   742  		Uint16:  Uint16Option{NewSource("merge"), true, uint16(123)},
   743  		Uint32:  Uint32Option{NewSource("merge"), true, uint32(234)},
   744  		Uint64:  Uint64Option{NewSource("merge"), true, uint64(345)},
   745  		Uint8:   Uint8Option{NewSource("merge"), true, uint8(255)},
   746  		Uint:    UintOption{NewSource("merge"), true, uint(456)},
   747  	}
   748  	assert.Equal(t, expected, dest)
   749  }
   750  
   751  func TestMergeMapWithStructUsingOptions(t *testing.T) {
   752  	dest := map[string]interface{}{
   753  		"bool":    false,
   754  		"byte":    byte(0),
   755  		"float32": float32(0),
   756  		"float64": float64(0),
   757  		"int16":   int16(0),
   758  		"int32":   int32(0),
   759  		"int64":   int64(0),
   760  		"int8":    int8(0),
   761  		"int":     int(0),
   762  		"rune":    rune(0),
   763  		"string":  "",
   764  		"uint16":  uint16(0),
   765  		"uint32":  uint32(0),
   766  		"uint64":  uint64(0),
   767  		"uint8":   uint8(0),
   768  		"uint":    uint(0),
   769  	}
   770  
   771  	src := struct {
   772  		Bool    BoolOption
   773  		Byte    ByteOption
   774  		Float32 Float32Option `yaml:"float32"`
   775  		Float64 Float64Option `yaml:"float64"`
   776  		Int16   Int16Option   `yaml:"int16"`
   777  		Int32   Int32Option   `yaml:"int32"`
   778  		Int64   Int64Option   `yaml:"int64"`
   779  		Int8    Int8Option    `yaml:"int8"`
   780  		Int     IntOption
   781  		Rune    RuneOption
   782  		String  StringOption
   783  		Uint16  Uint16Option `yaml:"uint16"`
   784  		Uint32  Uint32Option `yaml:"uint32"`
   785  		Uint64  Uint64Option `yaml:"uint64"`
   786  		Uint8   Uint8Option  `yaml:"uint8"`
   787  		Uint    UintOption
   788  	}{
   789  		Bool:    NewBoolOption(true),
   790  		Byte:    NewByteOption(10),
   791  		Float32: NewFloat32Option(1.23),
   792  		Float64: NewFloat64Option(2.34),
   793  		Int16:   NewInt16Option(123),
   794  		Int32:   NewInt32Option(234),
   795  		Int64:   NewInt64Option(345),
   796  		Int8:    NewInt8Option(127),
   797  		Int:     NewIntOption(456),
   798  		Rune:    NewRuneOption('a'),
   799  		String:  NewStringOption("stringval"),
   800  		Uint16:  NewUint16Option(123),
   801  		Uint32:  NewUint32Option(234),
   802  		Uint64:  NewUint64Option(345),
   803  		Uint8:   NewUint8Option(255),
   804  		Uint:    NewUintOption(456),
   805  	}
   806  
   807  	err := Merge(&dest, &src)
   808  	require.NoError(t, err)
   809  
   810  	expected := map[string]interface{}{
   811  		"bool":    true,
   812  		"byte":    byte(10),
   813  		"float32": float32(1.23),
   814  		"float64": float64(2.34),
   815  		"int16":   int16(123),
   816  		"int32":   int32(234),
   817  		"int64":   int64(345),
   818  		"int8":    int8(127),
   819  		"int":     int(456),
   820  		"rune":    rune('a'),
   821  		"string":  "stringval",
   822  		"uint16":  uint16(123),
   823  		"uint32":  uint32(234),
   824  		"uint64":  uint64(345),
   825  		"uint8":   uint8(255),
   826  		"uint":    uint(456),
   827  	}
   828  	assert.Equal(t, expected, dest)
   829  }
   830  
   831  func TestMergeStructUsingListOptionsWithMap(t *testing.T) {
   832  	dest := struct {
   833  		Strings ListStringOption
   834  	}{
   835  		Strings: ListStringOption{
   836  			NewStringOption("abc"),
   837  		},
   838  	}
   839  
   840  	src := map[string]interface{}{
   841  		"strings": []string{
   842  			"abc",
   843  			"def",
   844  		},
   845  	}
   846  
   847  	err := Merge(&dest, &src)
   848  	require.NoError(t, err)
   849  
   850  	expected := struct {
   851  		Strings ListStringOption
   852  	}{
   853  		ListStringOption{
   854  			StringOption{NewSource("default"), true, "abc"},
   855  			StringOption{NewSource("merge"), true, "def"},
   856  		},
   857  	}
   858  	assert.Equal(t, expected, dest)
   859  }
   860  
   861  func TestMergeMapWithStructUsingListOptions(t *testing.T) {
   862  	dest := map[string]interface{}{
   863  		"strings": []string{"abc"},
   864  	}
   865  
   866  	src := struct {
   867  		Strings ListStringOption
   868  	}{
   869  		Strings: ListStringOption{
   870  			NewStringOption("abc"),
   871  			NewStringOption("def"),
   872  		},
   873  	}
   874  
   875  	err := Merge(&dest, &src)
   876  	require.NoError(t, err)
   877  
   878  	expected := map[string]interface{}{
   879  		"strings": []string{"abc", "def"},
   880  	}
   881  	assert.Equal(t, expected, dest)
   882  }
   883  
   884  func TestMergeStructWithListUsingListOptions(t *testing.T) {
   885  	dest := struct {
   886  		Property []interface{}
   887  	}{
   888  		Property: []interface{}{
   889  			"abc",
   890  		},
   891  	}
   892  
   893  	src := struct {
   894  		Property ListStringOption
   895  	}{
   896  		Property: ListStringOption{
   897  			NewStringOption("abc"),
   898  			NewStringOption("def"),
   899  		},
   900  	}
   901  
   902  	err := Merge(&dest, &src)
   903  	require.NoError(t, err)
   904  
   905  	expected := struct {
   906  		Property []interface{}
   907  	}{
   908  		Property: []interface{}{
   909  			"abc",
   910  			NewStringOption("def"),
   911  		},
   912  	}
   913  	assert.Equal(t, expected, dest)
   914  }
   915  
   916  func TestMergeStructUsingMapOptionsWithMap(t *testing.T) {
   917  	dest := struct {
   918  		Strings MapStringOption
   919  	}{}
   920  
   921  	src := map[string]interface{}{
   922  		"strings": map[string]interface{}{
   923  			"key1": "val1",
   924  			"key2": "val2",
   925  		},
   926  	}
   927  
   928  	err := Merge(&dest, &src)
   929  	require.NoError(t, err)
   930  
   931  	expected := struct {
   932  		Strings MapStringOption
   933  	}{
   934  		Strings: MapStringOption{
   935  			"key1": StringOption{NewSource("merge"), true, "val1"},
   936  			"key2": StringOption{NewSource("merge"), true, "val2"},
   937  		},
   938  	}
   939  	assert.Equal(t, expected, dest)
   940  }
   941  
   942  func TestMergeMapWithStructUsingMapOptions(t *testing.T) {
   943  	dest := map[string]interface{}{
   944  		"strings": map[string]string{},
   945  	}
   946  
   947  	src := struct {
   948  		Strings MapStringOption
   949  	}{
   950  		Strings: MapStringOption{
   951  			"key1": NewStringOption("val1"),
   952  			"key2": NewStringOption("val2"),
   953  		},
   954  	}
   955  
   956  	err := Merge(&dest, &src)
   957  	require.NoError(t, err)
   958  
   959  	expected := map[string]interface{}{
   960  		"strings": map[string]string{
   961  			"key1": "val1",
   962  			"key2": "val2",
   963  		},
   964  	}
   965  	assert.Equal(t, expected, dest)
   966  }
   967  
   968  func TestMergeStructsWithSrcEmbedded(t *testing.T) {
   969  	dest := struct {
   970  		FieldName string
   971  	}{}
   972  
   973  	type embedded struct {
   974  		FieldName string
   975  	}
   976  
   977  	src := struct {
   978  		embedded
   979  	}{
   980  		embedded: embedded{
   981  			FieldName: "field1",
   982  		},
   983  	}
   984  
   985  	m := NewMerger()
   986  	changed, err := m.mergeStructs(reflect.ValueOf(&dest), newMergeSource(reflect.ValueOf(&src)), false)
   987  	require.NoError(t, err)
   988  	require.True(t, changed)
   989  
   990  	expected := struct {
   991  		FieldName string
   992  	}{
   993  		FieldName: "field1",
   994  	}
   995  	assert.Equal(t, expected, dest)
   996  }
   997  
   998  func TestMergeStructsWithDestEmbedded(t *testing.T) {
   999  	type embedded struct {
  1000  		FieldName string
  1001  	}
  1002  
  1003  	dest := struct {
  1004  		embedded
  1005  	}{}
  1006  
  1007  	src := struct {
  1008  		FieldName string
  1009  	}{
  1010  		FieldName: "field1",
  1011  	}
  1012  
  1013  	m := NewMerger()
  1014  	changed, err := m.mergeStructs(reflect.ValueOf(&dest), newMergeSource(reflect.ValueOf(&src)), false)
  1015  	require.NoError(t, err)
  1016  	require.True(t, changed)
  1017  
  1018  	expected := struct {
  1019  		embedded
  1020  	}{
  1021  		embedded: embedded{
  1022  			FieldName: "field1",
  1023  		},
  1024  	}
  1025  	assert.Equal(t, expected, dest)
  1026  }
  1027  
  1028  func TestMakeMergeStruct(t *testing.T) {
  1029  	input := map[string]interface{}{
  1030  		"mapkey": "mapval1",
  1031  		"map": map[string]interface{}{
  1032  			"mapkey": "mapval2",
  1033  		},
  1034  		"nilmap": nil,
  1035  	}
  1036  
  1037  	got := MakeMergeStruct(input)
  1038  
  1039  	err := Merge(got, &input)
  1040  	require.NoError(t, err)
  1041  
  1042  	assert.Equal(t, input["mapkey"], reflect.ValueOf(got).Elem().FieldByName("Mapkey").Interface())
  1043  	assert.Equal(t, struct {
  1044  		Mapkey string `json:"mapkey" yaml:"mapkey"`
  1045  	}{"mapval2"}, reflect.ValueOf(got).Elem().FieldByName("Map").Interface())
  1046  	assert.Equal(t, input["map"].(map[string]interface{})["mapkey"], reflect.ValueOf(got).Elem().FieldByName("Map").FieldByName("Mapkey").Interface())
  1047  }
  1048  
  1049  func TestMakeMergeStructWithDups(t *testing.T) {
  1050  	input := map[string]interface{}{
  1051  		"mapkey": "mapval1",
  1052  	}
  1053  
  1054  	s := struct {
  1055  		Mapkey string
  1056  	}{
  1057  		Mapkey: "mapval2",
  1058  	}
  1059  
  1060  	got := MakeMergeStruct(input, s)
  1061  	err := Merge(got, &input)
  1062  	require.NoError(t, err)
  1063  
  1064  	assert.Equal(t, &struct {
  1065  		Mapkey string `json:"mapkey" yaml:"mapkey"`
  1066  	}{"mapval1"}, got)
  1067  
  1068  	got = MakeMergeStruct(s, input)
  1069  	err = Merge(got, &s)
  1070  	require.NoError(t, err)
  1071  
  1072  	assert.Equal(t, &struct{ Mapkey string }{"mapval2"}, got)
  1073  }
  1074  
  1075  func TestMakeMergeStructWithInline(t *testing.T) {
  1076  	type Inner struct {
  1077  		InnerString StringOption `json:"inner-string" yaml:"inner-string"`
  1078  	}
  1079  
  1080  	outer := struct {
  1081  		Inner       `figtree:",inline"`
  1082  		OuterString string
  1083  	}{}
  1084  
  1085  	other := struct {
  1086  		InnerString string
  1087  		OtherString string
  1088  	}{}
  1089  
  1090  	got := MakeMergeStruct(outer, other)
  1091  	assert.IsType(t, (*struct {
  1092  		InnerString StringOption `json:"inner-string" yaml:"inner-string"`
  1093  		OtherString string
  1094  		OuterString string
  1095  	})(nil), got)
  1096  
  1097  	otherMap := map[string]interface{}{
  1098  		"inner-string": "inner",
  1099  		"other-string": "other",
  1100  	}
  1101  
  1102  	got = MakeMergeStruct(outer, otherMap)
  1103  	assert.IsType(t, (*struct {
  1104  		InnerString StringOption `json:"inner-string" yaml:"inner-string"`
  1105  		OtherString string       `json:"other-string" yaml:"other-string"`
  1106  		OuterString string
  1107  	})(nil), got)
  1108  }
  1109  
  1110  func TestMakeMergeStructWithYaml(t *testing.T) {
  1111  	input := "foo-bar: foo-val\n"
  1112  	data := map[string]interface{}{}
  1113  	err := yaml.Unmarshal([]byte(input), &data)
  1114  	assert.NoError(t, err)
  1115  
  1116  	// turn map data into a struct
  1117  	got := MakeMergeStruct(data)
  1118  	// then assign the data back into that struct
  1119  	err = Merge(got, data)
  1120  	require.NoError(t, err)
  1121  
  1122  	expected := &struct {
  1123  		FooBar string `json:"foo-bar" yaml:"foo-bar"`
  1124  	}{
  1125  		"foo-val",
  1126  	}
  1127  	assert.Equal(t, expected, got)
  1128  
  1129  	// make sure the new structure serializes back to the original document
  1130  	output, err := yaml.Marshal(expected)
  1131  	assert.NoError(t, err)
  1132  	assert.Equal(t, input, string(output))
  1133  }
  1134  
  1135  func TestMakeMergeStructWithJson(t *testing.T) {
  1136  	input := `{"foo-bar":"foo-val"}`
  1137  	data := map[string]interface{}{}
  1138  	err := json.Unmarshal([]byte(input), &data)
  1139  	assert.NoError(t, err)
  1140  
  1141  	// turn map data into a struct
  1142  	got := MakeMergeStruct(data)
  1143  	// then assign the data back into that struct
  1144  	err = Merge(got, data)
  1145  	require.NoError(t, err)
  1146  
  1147  	expected := &struct {
  1148  		FooBar string `json:"foo-bar" yaml:"foo-bar"`
  1149  	}{
  1150  		"foo-val",
  1151  	}
  1152  	assert.Equal(t, expected, got)
  1153  
  1154  	// make sure the new structure serializes back to the original document
  1155  	output, err := json.Marshal(expected)
  1156  	assert.NoError(t, err)
  1157  	assert.Equal(t, input, string(output))
  1158  }
  1159  
  1160  func TestMergeWithZeros(t *testing.T) {
  1161  	var zero interface{}
  1162  	tests := []struct {
  1163  		info info
  1164  		dest map[string]interface{}
  1165  		src  map[string]interface{}
  1166  		want map[string]interface{}
  1167  	}{
  1168  		{
  1169  			info: info{"zero to nil", line()},
  1170  			dest: map[string]interface{}{},
  1171  			src: map[string]interface{}{
  1172  				"value": zero,
  1173  			},
  1174  			want: map[string]interface{}{
  1175  				"value": zero,
  1176  			},
  1177  		},
  1178  		{
  1179  			info: info{"zero to zero", line()},
  1180  			dest: map[string]interface{}{
  1181  				"value": zero,
  1182  			},
  1183  			src: map[string]interface{}{
  1184  				"value": zero,
  1185  			},
  1186  			want: map[string]interface{}{
  1187  				"value": zero,
  1188  			},
  1189  		},
  1190  		{
  1191  			info: info{"zero to StringOption", line()},
  1192  			dest: map[string]interface{}{
  1193  				"value": StringOption{},
  1194  			},
  1195  			src: map[string]interface{}{
  1196  				"value": zero,
  1197  			},
  1198  			want: map[string]interface{}{
  1199  				"value": StringOption{},
  1200  			},
  1201  		},
  1202  		{
  1203  			info: info{"StringOption to zero", line()},
  1204  			dest: map[string]interface{}{
  1205  				"value": zero,
  1206  			},
  1207  			src: map[string]interface{}{
  1208  				"value": StringOption{},
  1209  			},
  1210  			want: map[string]interface{}{
  1211  				"value": StringOption{},
  1212  			},
  1213  		},
  1214  		{
  1215  			info: info{"list zero to nil", line()},
  1216  			dest: map[string]interface{}{
  1217  				"value": nil,
  1218  			},
  1219  			src: map[string]interface{}{
  1220  				"value": []interface{}{zero},
  1221  			},
  1222  			want: map[string]interface{}{
  1223  				"value": []interface{}{zero},
  1224  			},
  1225  		},
  1226  		{
  1227  			info: info{"list zero to empty", line()},
  1228  			dest: map[string]interface{}{
  1229  				"value": []interface{}{},
  1230  			},
  1231  			src: map[string]interface{}{
  1232  				"value": []interface{}{zero},
  1233  			},
  1234  			want: map[string]interface{}{
  1235  				"value": []interface{}{},
  1236  			},
  1237  		},
  1238  		{
  1239  			info: info{"list zero to StringOption", line()},
  1240  			dest: map[string]interface{}{
  1241  				"value": []interface{}{StringOption{}},
  1242  			},
  1243  			src: map[string]interface{}{
  1244  				"value": []interface{}{zero},
  1245  			},
  1246  			want: map[string]interface{}{
  1247  				"value": []interface{}{StringOption{}},
  1248  			},
  1249  		},
  1250  		{
  1251  			info: info{"list StringOption to zero", line()},
  1252  			dest: map[string]interface{}{
  1253  				"value": []interface{}{zero},
  1254  			},
  1255  			src: map[string]interface{}{
  1256  				"value": []interface{}{StringOption{}},
  1257  			},
  1258  			want: map[string]interface{}{
  1259  				"value": []interface{}{zero},
  1260  			},
  1261  		},
  1262  		{
  1263  			info: info{"list StringOption to empty", line()},
  1264  			dest: map[string]interface{}{
  1265  				"value": []interface{}{},
  1266  			},
  1267  			src: map[string]interface{}{
  1268  				"value": []interface{}{StringOption{}},
  1269  			},
  1270  			want: map[string]interface{}{
  1271  				"value": []interface{}{},
  1272  			},
  1273  		},
  1274  		{
  1275  			info: info{"zero to ListStringOption", line()},
  1276  			dest: map[string]interface{}{
  1277  				"value": ListStringOption{StringOption{}},
  1278  			},
  1279  			src: map[string]interface{}{
  1280  				"value": []interface{}{zero},
  1281  			},
  1282  			want: map[string]interface{}{
  1283  				"value": ListStringOption{StringOption{}},
  1284  			},
  1285  		},
  1286  		{
  1287  			info: info{"ListStringOption to zero", line()},
  1288  			dest: map[string]interface{}{
  1289  				"value": []interface{}{zero},
  1290  			},
  1291  			src: map[string]interface{}{
  1292  				"value": ListStringOption{StringOption{}},
  1293  			},
  1294  			want: map[string]interface{}{
  1295  				"value": []interface{}{zero},
  1296  			},
  1297  		},
  1298  		{
  1299  			info: info{"map zero to nil", line()},
  1300  			dest: map[string]interface{}{
  1301  				"value": nil,
  1302  			},
  1303  			src: map[string]interface{}{
  1304  				"value": map[string]interface{}{
  1305  					"key": zero,
  1306  				},
  1307  			},
  1308  			want: map[string]interface{}{
  1309  				"value": map[string]interface{}{
  1310  					"key": zero,
  1311  				},
  1312  			},
  1313  		},
  1314  		{
  1315  			info: info{"map zero to empty", line()},
  1316  			dest: map[string]interface{}{
  1317  				"value": map[string]interface{}{},
  1318  			},
  1319  			src: map[string]interface{}{
  1320  				"value": map[string]interface{}{
  1321  					"key": zero,
  1322  				},
  1323  			},
  1324  			want: map[string]interface{}{
  1325  				"value": map[string]interface{}{
  1326  					"key": zero,
  1327  				},
  1328  			},
  1329  		},
  1330  		{
  1331  			info: info{"MapStringOption to zero", line()},
  1332  			dest: map[string]interface{}{
  1333  				"value": zero,
  1334  			},
  1335  			src: map[string]interface{}{
  1336  				"value": MapStringOption{
  1337  					"key": StringOption{},
  1338  				},
  1339  			},
  1340  			want: map[string]interface{}{
  1341  				"value": MapStringOption{
  1342  					"key": StringOption{},
  1343  				},
  1344  			},
  1345  		},
  1346  		{
  1347  			info: info{"map zero to StringOption", line()},
  1348  			dest: map[string]interface{}{
  1349  				"value": MapStringOption{
  1350  					"key": StringOption{},
  1351  				},
  1352  			},
  1353  			src: map[string]interface{}{
  1354  				"value": zero,
  1355  			},
  1356  			want: map[string]interface{}{
  1357  				"value": MapStringOption{
  1358  					"key": StringOption{},
  1359  				},
  1360  			},
  1361  		},
  1362  		{
  1363  			info: info{"map zero key to StringOption", line()},
  1364  			dest: map[string]interface{}{
  1365  				"value": MapStringOption{
  1366  					"key": StringOption{},
  1367  				},
  1368  			},
  1369  			src: map[string]interface{}{
  1370  				"value": map[string]interface{}{
  1371  					"key": zero,
  1372  				},
  1373  			},
  1374  			want: map[string]interface{}{
  1375  				"value": MapStringOption{
  1376  					"key": StringOption{},
  1377  				},
  1378  			},
  1379  		},
  1380  		{
  1381  			info: info{"map StringOption to zero key", line()},
  1382  			dest: map[string]interface{}{
  1383  				"value": map[string]interface{}{
  1384  					"key": zero,
  1385  				},
  1386  			},
  1387  			src: map[string]interface{}{
  1388  				"value": MapStringOption{
  1389  					"key": StringOption{},
  1390  				},
  1391  			},
  1392  			want: map[string]interface{}{
  1393  				"value": map[string]interface{}{
  1394  					"key": StringOption{},
  1395  				},
  1396  			},
  1397  		},
  1398  	}
  1399  
  1400  	for _, tt := range tests {
  1401  		require.True(t,
  1402  			t.Run(tt.info.name, func(t *testing.T) {
  1403  				// assert.NotPanics(t, func() {
  1404  				Log.Debugf("+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++")
  1405  				Log.Debugf("%s", tt.info.name)
  1406  				Log.Debugf("+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++")
  1407  				err := Merge(&tt.dest, &tt.src)
  1408  				require.NoError(t, err)
  1409  				// })
  1410  				assert.Equal(t, tt.want, tt.dest, tt.info.line)
  1411  
  1412  				got := MakeMergeStruct(tt.dest)
  1413  				err = Merge(got, tt.dest)
  1414  				require.NoError(t, err)
  1415  				err = Merge(got, tt.src)
  1416  				require.NoError(t, err)
  1417  
  1418  				expected := MakeMergeStruct(tt.want)
  1419  				err = Merge(expected, tt.want)
  1420  				require.NoError(t, err)
  1421  
  1422  				assert.Equal(t, expected, got, tt.info.line)
  1423  			}),
  1424  		)
  1425  	}
  1426  }
  1427  
  1428  func TestMergeStructsWithZeros(t *testing.T) {
  1429  	var zero interface{}
  1430  	tests := []struct {
  1431  		info info
  1432  		dest interface{}
  1433  		src  interface{}
  1434  		want interface{}
  1435  		line string
  1436  	}{
  1437  		{
  1438  			info: info{"bare nil", line()},
  1439  			dest: struct {
  1440  				Value interface{}
  1441  			}{},
  1442  			src: struct {
  1443  				Value interface{}
  1444  			}{zero},
  1445  			want: struct {
  1446  				Value interface{}
  1447  			}{zero},
  1448  		},
  1449  		{
  1450  			info: info{"bare zero", line()},
  1451  			dest: struct {
  1452  				Value interface{}
  1453  			}{zero},
  1454  			src: struct {
  1455  				Value interface{}
  1456  			}{zero},
  1457  			want: struct {
  1458  				Value interface{}
  1459  			}{zero},
  1460  		},
  1461  		{
  1462  			info: info{"bare StringOption", line()},
  1463  			dest: struct {
  1464  				Value interface{}
  1465  			}{StringOption{}},
  1466  			src: struct {
  1467  				Value interface{}
  1468  			}{StringOption{}},
  1469  			want: struct {
  1470  				Value interface{}
  1471  			}{StringOption{}},
  1472  		},
  1473  		{
  1474  			info: info{"bare StringOptions to zero", line()},
  1475  			dest: struct {
  1476  				Value interface{}
  1477  			}{zero},
  1478  			src: struct {
  1479  				Value StringOption
  1480  			}{StringOption{}},
  1481  			want: struct {
  1482  				Value interface{}
  1483  			}{zero},
  1484  		},
  1485  		{
  1486  			info: info{"list zero to nil", line()},
  1487  			dest: struct {
  1488  				Value interface{}
  1489  			}{},
  1490  			src: struct {
  1491  				Value interface{}
  1492  			}{[]interface{}{zero}},
  1493  			want: struct {
  1494  				Value interface{}
  1495  			}{[]interface{}{zero}},
  1496  		},
  1497  		{
  1498  			info: info{"list zero to empty", line()},
  1499  			dest: struct {
  1500  				Value interface{}
  1501  			}{[]interface{}{}},
  1502  			src: struct {
  1503  				Value interface{}
  1504  			}{[]interface{}{zero}},
  1505  			want: struct {
  1506  				Value interface{}
  1507  			}{[]interface{}{}},
  1508  		},
  1509  		{
  1510  			info: info{"list zero to StringOption", line()},
  1511  			dest: struct {
  1512  				Value interface{}
  1513  			}{[]interface{}{StringOption{}}},
  1514  			src: struct {
  1515  				Value interface{}
  1516  			}{[]interface{}{zero}},
  1517  			want: struct {
  1518  				Value interface{}
  1519  			}{[]interface{}{StringOption{}}},
  1520  		},
  1521  		{
  1522  			info: info{"list StringOption to zero", line()},
  1523  			dest: struct {
  1524  				Value interface{}
  1525  			}{[]interface{}{zero}},
  1526  			src: struct {
  1527  				Value interface{}
  1528  			}{[]interface{}{StringOption{}}},
  1529  			want: struct {
  1530  				Value interface{}
  1531  			}{[]interface{}{zero}},
  1532  		},
  1533  		{
  1534  			info: info{"list ListStringOption to empty list", line()},
  1535  			line: line(),
  1536  			dest: struct {
  1537  				Value interface{}
  1538  			}{[]interface{}{}},
  1539  			src: struct {
  1540  				Value ListStringOption
  1541  			}{ListStringOption{StringOption{}}},
  1542  			want: struct {
  1543  				Value interface{}
  1544  			}{[]interface{}{}},
  1545  		},
  1546  		{
  1547  			info: info{"list zero list to ListStringOption", line()},
  1548  			dest: struct {
  1549  				Value ListStringOption
  1550  			}{ListStringOption{StringOption{}}},
  1551  			src: struct {
  1552  				Value interface{}
  1553  			}{[]interface{}{zero}},
  1554  			want: struct {
  1555  				Value ListStringOption
  1556  			}{ListStringOption{StringOption{}}},
  1557  		},
  1558  		{
  1559  			info: info{"list ListStringOption to zero list", line()},
  1560  			dest: struct {
  1561  				Value interface{}
  1562  			}{[]interface{}{zero}},
  1563  			src: struct {
  1564  				Value ListStringOption
  1565  			}{ListStringOption{StringOption{}}},
  1566  			want: struct {
  1567  				Value interface{}
  1568  			}{[]interface{}{zero}},
  1569  		},
  1570  		{
  1571  			info: info{"map zero to nil", line()},
  1572  			dest: struct {
  1573  				Value interface{}
  1574  			}{},
  1575  			src: struct {
  1576  				Value interface{}
  1577  			}{map[string]interface{}{"key": zero}},
  1578  			want: struct {
  1579  				Value interface{}
  1580  			}{map[string]interface{}{"key": zero}},
  1581  		},
  1582  		{
  1583  			info: info{"map zero to empty", line()},
  1584  			dest: struct {
  1585  				Value interface{}
  1586  			}{map[string]interface{}{}},
  1587  			src: struct {
  1588  				Value interface{}
  1589  			}{map[string]interface{}{"key": zero}},
  1590  			want: struct {
  1591  				Value interface{}
  1592  			}{map[string]interface{}{"key": zero}},
  1593  		},
  1594  		{
  1595  			info: info{"map StringOption to zero", line()},
  1596  			dest: struct {
  1597  				Value interface{}
  1598  			}{zero},
  1599  			src: struct {
  1600  				Value interface{}
  1601  			}{map[string]interface{}{"key": zero}},
  1602  			want: struct {
  1603  				Value interface{}
  1604  			}{map[string]interface{}{"key": zero}},
  1605  		},
  1606  		{
  1607  			info: info{"MapStringOption StringOption to zero", line()},
  1608  			dest: struct {
  1609  				Value interface{}
  1610  			}{zero},
  1611  			src: struct {
  1612  				Value interface{}
  1613  			}{MapStringOption{
  1614  				"key": StringOption{},
  1615  			}},
  1616  			want: struct {
  1617  				Value interface{}
  1618  			}{MapStringOption{
  1619  				"key": StringOption{},
  1620  			}},
  1621  		},
  1622  		{
  1623  			info: info{"zero to MapStringOption StringOption", line()},
  1624  			dest: struct {
  1625  				Value interface{}
  1626  			}{MapStringOption{
  1627  				"key": StringOption{},
  1628  			}},
  1629  			src: struct {
  1630  				Value interface{}
  1631  			}{zero},
  1632  			want: struct {
  1633  				Value interface{}
  1634  			}{MapStringOption{
  1635  				"key": StringOption{},
  1636  			}},
  1637  		},
  1638  		{
  1639  			info: info{"map zero to MapStringOption StringOption", line()},
  1640  			dest: struct {
  1641  				Value interface{}
  1642  			}{MapStringOption{
  1643  				"key": StringOption{},
  1644  			}},
  1645  			src: struct {
  1646  				Value interface{}
  1647  			}{map[string]interface{}{
  1648  				"key": zero,
  1649  			}},
  1650  			want: struct {
  1651  				Value interface{}
  1652  			}{MapStringOption{
  1653  				"key": StringOption{},
  1654  			}},
  1655  		},
  1656  	}
  1657  	for _, tt := range tests {
  1658  		require.True(t,
  1659  			t.Run(tt.info.name, func(t *testing.T) {
  1660  				// assert.NotPanics(t, func() {
  1661  				Log.Debugf("+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++")
  1662  				Log.Debugf("%s", tt.info.name)
  1663  				Log.Debugf("+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++")
  1664  				err := Merge(&tt.dest, &tt.src)
  1665  				require.NoError(t, err)
  1666  				// })
  1667  				assert.Equal(t, tt.want, tt.dest, tt.info.line)
  1668  
  1669  				got := MakeMergeStruct(tt.dest)
  1670  				err = Merge(got, tt.dest)
  1671  				require.NoError(t, err)
  1672  				err = Merge(got, tt.src)
  1673  				require.NoError(t, err)
  1674  
  1675  				expected := MakeMergeStruct(tt.want)
  1676  				err = Merge(expected, tt.want)
  1677  				require.NoError(t, err)
  1678  
  1679  				assert.Equal(t, expected, got, tt.info.line)
  1680  			}),
  1681  		)
  1682  	}
  1683  }
  1684  
  1685  func TestMergeStructsWithPreservedMaps(t *testing.T) {
  1686  	tests := []struct {
  1687  		info   info
  1688  		src    interface{}
  1689  		want   interface{}
  1690  		merger *Merger
  1691  	}{
  1692  		{
  1693  			info: info{"convert map to struct by default", line()},
  1694  			src: map[string]interface{}{
  1695  				"map": map[string]string{"key": "value"},
  1696  			},
  1697  			want: &struct {
  1698  				Map struct {
  1699  					Key string `json:"key" yaml:"key"`
  1700  				} `json:"map" yaml:"map"`
  1701  			}{},
  1702  			merger: NewMerger(),
  1703  		}, {
  1704  			info: info{"preserve map when converting to struct", line()},
  1705  			src: map[string]interface{}{
  1706  				"map":   map[string]string{"key": "value"},
  1707  				"other": map[string]string{"key": "value"},
  1708  			},
  1709  			want: &struct {
  1710  				Map   map[string]string `json:"map" yaml:"map"`
  1711  				Other struct {
  1712  					Key string `json:"key" yaml:"key"`
  1713  				} `json:"other" yaml:"other"`
  1714  			}{},
  1715  			merger: NewMerger(PreserveMap("map")),
  1716  		},
  1717  	}
  1718  
  1719  	for _, tt := range tests {
  1720  		require.True(t,
  1721  			t.Run(tt.info.name, func(t *testing.T) {
  1722  				got := tt.merger.MakeMergeStruct(tt.src)
  1723  				assert.Equal(t, tt.want, got)
  1724  			}),
  1725  		)
  1726  	}
  1727  }
  1728  
  1729  func TestFigtreePreProcessor(t *testing.T) {
  1730  	var input yaml.Node
  1731  	err := yaml.Unmarshal([]byte(`
  1732  bad-name: good-value
  1733  good-name: bad-value
  1734  ok-name: want-array
  1735  `), &input)
  1736  	assert.NoError(t, err)
  1737  
  1738  	pp := func(node *yaml.Node) error {
  1739  		// rename "bad-name" key to "fixed-name"
  1740  		if keyNode, _ := walky.GetKeyValue(node, walky.NewStringNode("bad-name")); keyNode != nil {
  1741  			keyNode.Value = "fixed-name"
  1742  		}
  1743  
  1744  		// reset "bad-value" key to "fixed-value", under "good-name" key
  1745  		if valNode := walky.GetKey(node, "good-name"); valNode != nil {
  1746  			valNode.Value = "fixed-value"
  1747  		}
  1748  
  1749  		// migrate "ok-name" value from string to list of strings
  1750  		if keyNode, valNode := walky.GetKeyValue(node, walky.NewStringNode("ok-name")); keyNode != nil {
  1751  			if valNode.Kind == yaml.ScalarNode {
  1752  				seqNode := walky.NewSequenceNode()
  1753  				require.NoError(t,
  1754  					walky.AppendNode(seqNode, walky.ShallowCopyNode(valNode)),
  1755  				)
  1756  				require.NoError(t,
  1757  					walky.AssignMapNode(node, keyNode, seqNode),
  1758  				)
  1759  			}
  1760  		}
  1761  		return nil
  1762  	}
  1763  
  1764  	fig := newFigTreeFromEnv(WithPreProcessor(pp))
  1765  
  1766  	dest := struct {
  1767  		FixedName string   `yaml:"fixed-name"`
  1768  		GoodName  string   `yaml:"good-name"`
  1769  		OkName    []string `yaml:"ok-name"`
  1770  	}{}
  1771  
  1772  	want := struct {
  1773  		FixedName string   `yaml:"fixed-name"`
  1774  		GoodName  string   `yaml:"good-name"`
  1775  		OkName    []string `yaml:"ok-name"`
  1776  	}{"good-value", "fixed-value", []string{"want-array"}}
  1777  
  1778  	err = fig.LoadConfigSource(&input, "test", &dest)
  1779  	assert.NoError(t, err)
  1780  	assert.Equal(t, want, dest)
  1781  }
  1782  
  1783  func TestMergeMapWithCopy(t *testing.T) {
  1784  	type mss = map[string]string
  1785  
  1786  	dest := struct {
  1787  		Map mss
  1788  	}{}
  1789  
  1790  	src1 := struct {
  1791  		Map mss
  1792  	}{
  1793  		mss{
  1794  			"key": "value",
  1795  		},
  1796  	}
  1797  
  1798  	src2 := struct {
  1799  		Map mss
  1800  	}{
  1801  		mss{
  1802  			"otherkey": "othervalue",
  1803  		},
  1804  	}
  1805  
  1806  	err := Merge(&dest, &src1)
  1807  	require.NoError(t, err)
  1808  	assert.Equal(t, mss{"key": "value"}, dest.Map)
  1809  
  1810  	err = Merge(&dest, &src2)
  1811  	require.NoError(t, err)
  1812  	assert.Equal(t, mss{"key": "value", "otherkey": "othervalue"}, dest.Map)
  1813  
  1814  	// verify that src1 was unmodified
  1815  	assert.Equal(t, mss{"key": "value"}, src1.Map)
  1816  }
  1817  
  1818  func TestMergeBoolString(t *testing.T) {
  1819  	src1 := struct {
  1820  		EnableThing BoolOption
  1821  	}{NewBoolOption(true)}
  1822  
  1823  	src2 := map[string]interface{}{
  1824  		"enable-thing": "true",
  1825  	}
  1826  
  1827  	dest := MakeMergeStruct(src1, src2)
  1828  	err := Merge(dest, src1)
  1829  	require.NoError(t, err)
  1830  	err = Merge(dest, src2)
  1831  	require.NoError(t, err)
  1832  
  1833  	expected := &struct {
  1834  		EnableThing BoolOption
  1835  	}{BoolOption{Source: NewSource("merge"), Defined: true, Value: true}}
  1836  
  1837  	assert.Equal(t, expected, dest)
  1838  }
  1839  
  1840  func TestMergeStringBool(t *testing.T) {
  1841  	src1 := struct {
  1842  		EnableThing StringOption
  1843  	}{NewStringOption("true")}
  1844  
  1845  	src2 := map[string]interface{}{
  1846  		"enable-thing": true,
  1847  	}
  1848  
  1849  	dest := MakeMergeStruct(src1, src2)
  1850  	err := Merge(dest, src1)
  1851  	require.NoError(t, err)
  1852  	err = Merge(dest, src2)
  1853  	require.NoError(t, err)
  1854  
  1855  	expected := &struct {
  1856  		EnableThing StringOption
  1857  	}{StringOption{Source: NewSource("merge"), Defined: true, Value: "true"}}
  1858  
  1859  	assert.Equal(t, expected, dest)
  1860  }
  1861  
  1862  func TestMergeStringFloat64(t *testing.T) {
  1863  	src1 := struct {
  1864  		SomeThing StringOption
  1865  	}{NewStringOption("true")}
  1866  
  1867  	src2 := map[string]interface{}{
  1868  		"some-thing": 42.0,
  1869  	}
  1870  
  1871  	dest := MakeMergeStruct(src1, src2)
  1872  	err := Merge(dest, src1)
  1873  	require.NoError(t, err)
  1874  	err = Merge(dest, src2)
  1875  	require.NoError(t, err)
  1876  
  1877  	expected := &struct {
  1878  		SomeThing StringOption
  1879  	}{StringOption{Source: NewSource("merge"), Defined: true, Value: "42"}}
  1880  
  1881  	assert.Equal(t, expected, dest)
  1882  }
  1883  
  1884  func TestMergeDefaults(t *testing.T) {
  1885  	src1 := &struct {
  1886  		SomeThing StringOption
  1887  	}{NewStringOption("foo")}
  1888  
  1889  	src2 := &struct {
  1890  		SomeThing StringOption
  1891  	}{NewStringOption("bar")}
  1892  
  1893  	dest := MakeMergeStruct(src1, src2)
  1894  	err := Merge(dest, src1)
  1895  	require.NoError(t, err)
  1896  
  1897  	expected := &struct {
  1898  		SomeThing StringOption
  1899  	}{StringOption{Source: NewSource("default"), Defined: true, Value: "foo"}}
  1900  
  1901  	assert.Equal(t, expected, dest)
  1902  
  1903  	err = Merge(dest, src2)
  1904  	require.NoError(t, err)
  1905  
  1906  	assert.Equal(t, expected, dest)
  1907  }
  1908  
  1909  // TestMergeCopySlices verifies when we merge a non-nil slice onto a nil-slice
  1910  // that the result is a copy of the original rather than direct reference
  1911  // assignment.  Otherwise we will get into conditions where we have multiple
  1912  // merged objects using the exact same reference to a slice where if we change
  1913  // one slice it modified all merged structs.
  1914  func TestMergeCopySlice(t *testing.T) {
  1915  	type stuffer = struct {
  1916  		Stuff []string
  1917  	}
  1918  
  1919  	stuffers := []*stuffer{}
  1920  	common := &stuffer{Stuff: []string{"common"}}
  1921  
  1922  	stuff1 := &stuffer{Stuff: nil}
  1923  	stuff2 := &stuffer{Stuff: nil}
  1924  
  1925  	for _, stuff := range []*stuffer{stuff1, stuff2} {
  1926  		err := Merge(stuff, common)
  1927  		require.NoError(t, err)
  1928  		stuffers = append(stuffers, stuff)
  1929  	}
  1930  
  1931  	assert.Equal(t, []string{"common"}, stuffers[0].Stuff)
  1932  	assert.Equal(t, []string{"common"}, stuffers[1].Stuff)
  1933  
  1934  	stuffers[0].Stuff[0] = "updated"
  1935  	assert.Equal(t, []string{"updated"}, stuffers[0].Stuff)
  1936  	assert.Equal(t, []string{"common"}, stuffers[1].Stuff)
  1937  }
  1938  
  1939  func TestMergeCopyArray(t *testing.T) {
  1940  	type stuffer = struct {
  1941  		Stuff [2]string
  1942  	}
  1943  
  1944  	stuffers := []*stuffer{}
  1945  	common := &stuffer{Stuff: [2]string{"common"}}
  1946  
  1947  	stuff1 := &stuffer{Stuff: [2]string{}}
  1948  	stuff2 := &stuffer{Stuff: [2]string{}}
  1949  
  1950  	for _, stuff := range []*stuffer{stuff1, stuff2} {
  1951  		err := Merge(stuff, common)
  1952  		require.NoError(t, err)
  1953  		stuffers = append(stuffers, stuff)
  1954  	}
  1955  
  1956  	assert.Equal(t, [2]string{"common", ""}, stuffers[0].Stuff)
  1957  	assert.Equal(t, [2]string{"common", ""}, stuffers[1].Stuff)
  1958  
  1959  	stuffers[0].Stuff[0] = "updated"
  1960  	assert.Equal(t, [2]string{"updated", ""}, stuffers[0].Stuff)
  1961  	assert.Equal(t, [2]string{"common", ""}, stuffers[1].Stuff)
  1962  }
  1963  
  1964  func TestListOfStructs(t *testing.T) {
  1965  	type myStruct struct {
  1966  		ID   string `yaml:"id"`
  1967  		Name string `yaml:"name"`
  1968  	}
  1969  	type myStructs []myStruct
  1970  	type data struct {
  1971  		Structs myStructs `yaml:"list"`
  1972  	}
  1973  
  1974  	config := `
  1975  list:
  1976    - id: abc
  1977      name: def
  1978    - id: foo
  1979      name: bar
  1980  `
  1981  	expected := data{
  1982  		Structs: myStructs{
  1983  			{ID: "abc", Name: "def"},
  1984  			{ID: "foo", Name: "bar"},
  1985  		},
  1986  	}
  1987  	var node yaml.Node
  1988  	err := yaml.Unmarshal([]byte(config), &node)
  1989  	require.NoError(t, err)
  1990  	dest := data{}
  1991  	fig := newFigTreeFromEnv()
  1992  	err = fig.LoadConfigSource(&node, "test", &dest)
  1993  	require.NoError(t, err)
  1994  	require.Equal(t, expected, dest)
  1995  
  1996  	content, err := yaml.Marshal(dest)
  1997  	require.NoError(t, err)
  1998  	raw := map[string]any{}
  1999  	err = yaml.Unmarshal(content, &raw)
  2000  	require.NoError(t, err)
  2001  
  2002  	dest = data{}
  2003  	err = Merge(&dest, &raw)
  2004  	require.NoError(t, err)
  2005  	require.Equal(t, expected, dest)
  2006  }
  2007  
  2008  func TestLoadConfigToNode(t *testing.T) {
  2009  	type SubData struct {
  2010  		Field yaml.Node `yaml:"field"`
  2011  	}
  2012  	type data struct {
  2013  		SubData `yaml:",inline"`
  2014  		List    []yaml.Node          `yaml:"list"`
  2015  		Map     map[string]yaml.Node `yaml:"map"`
  2016  		Stuff   yaml.Node            `yaml:"stuff"`
  2017  		Sub     SubData              `yaml:"sub"`
  2018  	}
  2019  
  2020  	config := `
  2021  field: 123
  2022  list: [a, 99]
  2023  map:
  2024    key1: abc
  2025    key2: 123
  2026  stuff: {a: 1, b: 2}
  2027  sub:
  2028    field: ghi
  2029  `
  2030  	expected := data{
  2031  		SubData: SubData{
  2032  			Field: yaml.Node{Kind: yaml.ScalarNode, Tag: "!!int", Value: "123", Line: 2, Column: 8},
  2033  		},
  2034  		List: []yaml.Node{
  2035  			{Kind: yaml.ScalarNode, Tag: "!!str", Value: "a", Line: 3, Column: 8},
  2036  			{Kind: yaml.ScalarNode, Tag: "!!int", Value: "99", Line: 3, Column: 11},
  2037  		},
  2038  		Map: map[string]yaml.Node{
  2039  			"key1": {Kind: yaml.ScalarNode, Tag: "!!str", Value: "abc", Line: 5, Column: 9},
  2040  			"key2": {Kind: yaml.ScalarNode, Tag: "!!int", Value: "123", Line: 6, Column: 9},
  2041  		},
  2042  		Stuff: yaml.Node{
  2043  			Kind:  yaml.MappingNode,
  2044  			Tag:   "!!map",
  2045  			Style: yaml.FlowStyle,
  2046  			Content: []*yaml.Node{
  2047  				{Kind: yaml.ScalarNode, Tag: "!!str", Value: "a", Line: 7, Column: 9},
  2048  				{Kind: yaml.ScalarNode, Tag: "!!int", Value: "1", Line: 7, Column: 12},
  2049  				{Kind: yaml.ScalarNode, Tag: "!!str", Value: "b", Line: 7, Column: 15},
  2050  				{Kind: yaml.ScalarNode, Tag: "!!int", Value: "2", Line: 7, Column: 18},
  2051  			},
  2052  			Line:   7,
  2053  			Column: 8,
  2054  		},
  2055  		Sub: SubData{
  2056  			Field: yaml.Node{Kind: yaml.ScalarNode, Tag: "!!str", Value: "ghi", Line: 9, Column: 10},
  2057  		},
  2058  	}
  2059  	var node yaml.Node
  2060  	err := yaml.Unmarshal([]byte(config), &node)
  2061  	require.NoError(t, err)
  2062  	dest := data{}
  2063  	fig := newFigTreeFromEnv()
  2064  	err = fig.LoadConfigSource(&node, "test", &dest)
  2065  	require.NoError(t, err)
  2066  	require.Equal(t, expected, dest)
  2067  }
  2068  
  2069  type UnmarshalInt int
  2070  
  2071  func (t *UnmarshalInt) UnmarshalYAML(unmarshal func(any) error) error {
  2072  	var rawType string
  2073  	if err := unmarshal(&rawType); err != nil {
  2074  		return errors.WithStack(err)
  2075  	}
  2076  	switch strings.ToLower(rawType) {
  2077  	case "foo":
  2078  		*t = 1
  2079  	case "bar":
  2080  		*t = 2
  2081  	default:
  2082  		return errors.Errorf("Unknown unmarshal test value: %s", rawType)
  2083  	}
  2084  	return nil
  2085  }
  2086  
  2087  func TestLoadConfigWithUnmarshalInt(t *testing.T) {
  2088  	type Property struct {
  2089  		Type UnmarshalInt  `yaml:"type"`
  2090  		Ptr  *UnmarshalInt `yaml:"ptr"`
  2091  	}
  2092  	type data struct {
  2093  		Properties []Property `yaml:"properties"`
  2094  	}
  2095  
  2096  	config := `
  2097  properties:
  2098    - type: foo
  2099      ptr: foo
  2100    - type: bar
  2101      ptr: bar
  2102  `
  2103  	foo := UnmarshalInt(1)
  2104  	bar := UnmarshalInt(2)
  2105  
  2106  	expected := data{
  2107  		Properties: []Property{
  2108  			{Type: foo, Ptr: &foo},
  2109  			{Type: bar, Ptr: &bar},
  2110  		},
  2111  	}
  2112  
  2113  	var node yaml.Node
  2114  	err := yaml.Unmarshal([]byte(config), &node)
  2115  	require.NoError(t, err)
  2116  	dest := data{}
  2117  	fig := newFigTreeFromEnv()
  2118  	err = fig.LoadConfigSource(&node, "test", &dest)
  2119  	require.NoError(t, err)
  2120  	require.Equal(t, expected, dest)
  2121  
  2122  	content, err := yaml.Marshal(dest)
  2123  	require.NoError(t, err)
  2124  	raw := map[string]any{}
  2125  	err = yaml.Unmarshal(content, &raw)
  2126  	require.NoError(t, err)
  2127  
  2128  	dest = data{}
  2129  	err = Merge(&dest, &raw)
  2130  	require.NoError(t, err)
  2131  	require.Equal(t, expected, dest)
  2132  }
  2133  
  2134  type UnmarshalString string
  2135  
  2136  func (t *UnmarshalString) UnmarshalYAML(unmarshal func(any) error) error {
  2137  	var rawType any
  2138  	if err := unmarshal(&rawType); err != nil {
  2139  		return errors.WithStack(err)
  2140  	}
  2141  	switch c := rawType.(type) {
  2142  	case string:
  2143  		*t = UnmarshalString(strings.ToUpper(c))
  2144  	case int:
  2145  		*t = UnmarshalString(fmt.Sprint(c))
  2146  	default:
  2147  		panic(fmt.Sprintf("can't handle %T", c))
  2148  	}
  2149  	return nil
  2150  }
  2151  
  2152  type UnmarshalStringList []UnmarshalString
  2153  
  2154  // UnmarshalYAML will unmarshal a list of PortSpecs and return an error if any items
  2155  // could not be unmarshalled.
  2156  func (pl *UnmarshalStringList) UnmarshalYAML(unmarshal func(any) error) error {
  2157  	var ps []UnmarshalString
  2158  	if err := unmarshal(&ps); err != nil {
  2159  		return err
  2160  	}
  2161  
  2162  	*pl = ps
  2163  	return nil
  2164  }
  2165  
  2166  func TestLoadConfigWithUnmarshalString(t *testing.T) {
  2167  	type Property struct {
  2168  		Type UnmarshalString  `yaml:"type"`
  2169  		Ptr  *UnmarshalString `yaml:"ptr"`
  2170  	}
  2171  	type data struct {
  2172  		Properties []Property          `yaml:"properties"`
  2173  		Prop       Property            `yaml:"prop"`
  2174  		Str        UnmarshalString     `yaml:"str"`
  2175  		Ptr        *UnmarshalString    `yaml:"ptr"`
  2176  		Strs       []UnmarshalString   `yaml:"strs"`
  2177  		Ptrs       []*UnmarshalString  `yaml:"ptrs"`
  2178  		PtrStrs    *[]UnmarshalString  `yaml:"ptr-strs"`
  2179  		StrList    UnmarshalStringList `yaml:"str-list"`
  2180  	}
  2181  
  2182  	config := `
  2183  properties:
  2184    - type: foo
  2185      ptr: foo
  2186    - type: bar
  2187      ptr: bar
  2188  prop:
  2189    type: 123
  2190    ptr: 123
  2191  ptr: a
  2192  str: a
  2193  strs: [a, b]
  2194  ptrs: [a, b]
  2195  str-list: [a, b]
  2196  ptr-strs: [a, b]
  2197  `
  2198  	foo := UnmarshalString("FOO")
  2199  	bar := UnmarshalString("BAR")
  2200  	baz := UnmarshalString("123")
  2201  	a := UnmarshalString("A")
  2202  	b := UnmarshalString("B")
  2203  
  2204  	expected := data{
  2205  		Properties: []Property{
  2206  			{Type: foo, Ptr: &foo},
  2207  			{Type: bar, Ptr: &bar},
  2208  		},
  2209  		Prop:    Property{Type: baz, Ptr: &baz},
  2210  		Str:     a,
  2211  		Ptr:     &a,
  2212  		Strs:    []UnmarshalString{a, b},
  2213  		Ptrs:    []*UnmarshalString{&a, &b},
  2214  		StrList: UnmarshalStringList{a, b},
  2215  		PtrStrs: &[]UnmarshalString{a, b},
  2216  	}
  2217  
  2218  	var node yaml.Node
  2219  	err := yaml.Unmarshal([]byte(config), &node)
  2220  	require.NoError(t, err)
  2221  	dest := data{}
  2222  	fig := newFigTreeFromEnv()
  2223  	err = fig.LoadConfigSource(&node, "test", &dest)
  2224  	require.NoError(t, err)
  2225  	require.Equal(t, expected, dest)
  2226  
  2227  	content, err := yaml.Marshal(dest)
  2228  	require.NoError(t, err)
  2229  	raw := map[string]any{}
  2230  	err = yaml.Unmarshal(content, &raw)
  2231  	require.NoError(t, err)
  2232  
  2233  	dest = data{}
  2234  	err = Merge(&dest, &raw)
  2235  	require.NoError(t, err)
  2236  	require.Equal(t, expected, dest)
  2237  }
  2238  
  2239  func TestLoadConfigWithSliceDups(t *testing.T) {
  2240  	type data struct {
  2241  		Strs    []UnmarshalString `yaml:"strs"`
  2242  		Simple  []string          `yaml:"simple"`
  2243  		Options ListStringOption  `yaml:"options"`
  2244  	}
  2245  	configs := []struct {
  2246  		Name string
  2247  		Body string
  2248  	}{{
  2249  		Name: "test",
  2250  		Body: `
  2251  strs: [a, b]
  2252  simple: [a, b]
  2253  options: [a, b]
  2254  `,
  2255  	}, {
  2256  		Name: "../test",
  2257  		Body: `
  2258  strs: [b ,c]
  2259  simple: [b, c]
  2260  options: [b, c]
  2261  `,
  2262  	}}
  2263  	expected := data{
  2264  		Strs:   []UnmarshalString{"A", "B", "C"},
  2265  		Simple: []string{"a", "b", "c"},
  2266  		Options: []StringOption{
  2267  			{tSrc("test", 4, 11), true, "a"},
  2268  			{tSrc("test", 4, 14), true, "b"},
  2269  			{tSrc("../test", 4, 14), true, "c"},
  2270  		},
  2271  	}
  2272  	sources := []ConfigSource{}
  2273  	for _, c := range configs {
  2274  		var node yaml.Node
  2275  		err := yaml.Unmarshal([]byte(c.Body), &node)
  2276  		require.NoError(t, err)
  2277  		sources = append(sources, ConfigSource{
  2278  			Config:   &node,
  2279  			Filename: c.Name,
  2280  		})
  2281  	}
  2282  	fig := newFigTreeFromEnv()
  2283  	got := data{}
  2284  	err := fig.LoadAllConfigSources(sources, &got)
  2285  	require.NoError(t, err)
  2286  	require.Equal(t, expected, got)
  2287  }
  2288  
  2289  func TestMapOfOptionLists(t *testing.T) {
  2290  	type data struct {
  2291  		Stuff map[string]ListStringOption `yaml:"stuff"`
  2292  	}
  2293  
  2294  	config := `
  2295  stuff:
  2296    foo:
  2297      - abc
  2298      - def
  2299    bar:
  2300      - ghi
  2301      - jkl
  2302  `
  2303  	expected := data{
  2304  		Stuff: map[string]ListStringOption{
  2305  			"bar": {
  2306  				StringOption{tSrc("test", 7, 7), true, "ghi"},
  2307  				StringOption{tSrc("test", 8, 7), true, "jkl"},
  2308  			},
  2309  			"foo": {
  2310  				StringOption{tSrc("test", 4, 7), true, "abc"},
  2311  				StringOption{tSrc("test", 5, 7), true, "def"},
  2312  			},
  2313  		},
  2314  	}
  2315  	var node yaml.Node
  2316  	err := yaml.Unmarshal([]byte(config), &node)
  2317  	require.NoError(t, err)
  2318  	dest := data{}
  2319  	fig := newFigTreeFromEnv()
  2320  	err = fig.LoadConfigSource(&node, "test", &dest)
  2321  	require.NoError(t, err)
  2322  	require.Equal(t, expected, dest)
  2323  }
  2324  
  2325  func TestMapOfStructs(t *testing.T) {
  2326  	type myStruct struct {
  2327  		Name string
  2328  	}
  2329  	var dest map[string]myStruct
  2330  
  2331  	config := `
  2332  foo:
  2333    name: abc
  2334  bar:
  2335    name: def
  2336  `
  2337  	expected := map[string]myStruct{
  2338  		"bar": {Name: "def"},
  2339  		"foo": {Name: "abc"},
  2340  	}
  2341  	var node yaml.Node
  2342  	err := yaml.Unmarshal([]byte(config), &node)
  2343  	require.NoError(t, err)
  2344  	fig := newFigTreeFromEnv()
  2345  	err = fig.LoadConfigSource(&node, "test", &dest)
  2346  	require.NoError(t, err)
  2347  	require.Equal(t, expected, dest)
  2348  
  2349  	content, err := yaml.Marshal(dest)
  2350  	require.NoError(t, err)
  2351  	raw := map[string]any{}
  2352  	err = yaml.Unmarshal(content, &raw)
  2353  	require.NoError(t, err)
  2354  
  2355  	dest = map[string]myStruct{}
  2356  	err = Merge(&dest, &raw)
  2357  	require.NoError(t, err)
  2358  	require.Equal(t, expected, dest)
  2359  }
  2360  
  2361  func TestZeroYAML(t *testing.T) {
  2362  	type myStruct struct {
  2363  		Name string
  2364  	}
  2365  	var dest map[string]myStruct
  2366  
  2367  	config := `
  2368  foo:
  2369  bar:
  2370  `
  2371  	expected := map[string]myStruct{
  2372  		"bar": {},
  2373  		"foo": {},
  2374  	}
  2375  	var node yaml.Node
  2376  	err := yaml.Unmarshal([]byte(config), &node)
  2377  	require.NoError(t, err)
  2378  	fig := newFigTreeFromEnv()
  2379  	err = fig.LoadConfigSource(&node, "test", &dest)
  2380  	require.NoError(t, err)
  2381  	require.Equal(t, expected, dest)
  2382  
  2383  	content, err := yaml.Marshal(dest)
  2384  	require.NoError(t, err)
  2385  	raw := map[string]any{}
  2386  	err = yaml.Unmarshal(content, &raw)
  2387  	require.NoError(t, err)
  2388  
  2389  	dest = map[string]myStruct{}
  2390  	err = Merge(&dest, &raw)
  2391  	require.NoError(t, err)
  2392  	require.Equal(t, expected, dest)
  2393  }
  2394  
  2395  func TestBoolToStringOption(t *testing.T) {
  2396  	type myStruct struct {
  2397  		Name StringOption
  2398  	}
  2399  	var data map[string]myStruct
  2400  
  2401  	// true/false will be parsed as `!!bool`
  2402  	// but we want to assign it to a StringOption
  2403  	config := `
  2404  foo:
  2405    name: true
  2406  bar:
  2407    name: false
  2408  `
  2409  	expected := map[string]myStruct{
  2410  		"bar": {Name: StringOption{tSrc("test", 5, 9), true, "false"}},
  2411  		"foo": {Name: StringOption{tSrc("test", 3, 9), true, "true"}},
  2412  	}
  2413  	var node yaml.Node
  2414  	err := yaml.Unmarshal([]byte(config), &node)
  2415  	require.NoError(t, err)
  2416  	fig := newFigTreeFromEnv()
  2417  	err = fig.LoadConfigSource(&node, "test", &data)
  2418  	require.NoError(t, err)
  2419  	require.Equal(t, expected, data)
  2420  }
  2421  
  2422  func TestBoolToString(t *testing.T) {
  2423  	type myStruct struct {
  2424  		Name string
  2425  	}
  2426  	var dest map[string]myStruct
  2427  
  2428  	// true/false will be parsed as `!!bool`
  2429  	// but we want to assign it to a string
  2430  	config := `
  2431  foo:
  2432    name: true
  2433  bar:
  2434    name: false
  2435  `
  2436  	expected := map[string]myStruct{
  2437  		"bar": {Name: "false"},
  2438  		"foo": {Name: "true"},
  2439  	}
  2440  	var node yaml.Node
  2441  	err := yaml.Unmarshal([]byte(config), &node)
  2442  	require.NoError(t, err)
  2443  	fig := newFigTreeFromEnv()
  2444  	err = fig.LoadConfigSource(&node, "test", &dest)
  2445  	require.NoError(t, err)
  2446  	require.Equal(t, expected, dest)
  2447  
  2448  	content, err := yaml.Marshal(dest)
  2449  	require.NoError(t, err)
  2450  	raw := map[string]any{}
  2451  	err = yaml.Unmarshal(content, &raw)
  2452  	require.NoError(t, err)
  2453  
  2454  	dest = map[string]myStruct{}
  2455  	err = Merge(&dest, &raw)
  2456  	require.NoError(t, err)
  2457  	require.Equal(t, expected, dest)
  2458  }
  2459  
  2460  func TestIntToStringOption(t *testing.T) {
  2461  	var data map[string]StringOption
  2462  
  2463  	config := `
  2464  a: 11
  2465  b: 11.1
  2466  c: 11.1.1
  2467  `
  2468  	expected := map[string]StringOption{
  2469  		"a": {tSrc("test", 2, 4), true, "11"},
  2470  		"b": {tSrc("test", 3, 4), true, "11.1"},
  2471  		"c": {tSrc("test", 4, 4), true, "11.1.1"},
  2472  	}
  2473  	var node yaml.Node
  2474  	err := yaml.Unmarshal([]byte(config), &node)
  2475  	require.NoError(t, err)
  2476  	fig := newFigTreeFromEnv()
  2477  	err = fig.LoadConfigSource(&node, "test", &data)
  2478  	require.NoError(t, err)
  2479  	require.Equal(t, expected, data)
  2480  }
  2481  
  2482  func TestIntToString(t *testing.T) {
  2483  	var dest map[string]string
  2484  	config := `
  2485  a: 11
  2486  b: 11.1
  2487  c: 11.1.1
  2488  `
  2489  	expected := map[string]string{
  2490  		"a": "11",
  2491  		"b": "11.1",
  2492  		"c": "11.1.1",
  2493  	}
  2494  	var node yaml.Node
  2495  	err := yaml.Unmarshal([]byte(config), &node)
  2496  	require.NoError(t, err)
  2497  	fig := newFigTreeFromEnv()
  2498  	err = fig.LoadConfigSource(&node, "test", &dest)
  2499  	require.NoError(t, err)
  2500  	require.Equal(t, expected, dest)
  2501  
  2502  	content, err := yaml.Marshal(dest)
  2503  	require.NoError(t, err)
  2504  	raw := map[string]any{}
  2505  	err = yaml.Unmarshal(content, &raw)
  2506  	require.NoError(t, err)
  2507  
  2508  	dest = map[string]string{}
  2509  	err = Merge(&dest, &raw)
  2510  	require.NoError(t, err)
  2511  	require.Equal(t, expected, dest)
  2512  }
  2513  
  2514  func TestAssignToAnyOption(t *testing.T) {
  2515  	var data map[string]Option[any]
  2516  	config := `
  2517  a: foo
  2518  b: 12
  2519  c: 12.2
  2520  d: 12.2.2
  2521  `
  2522  	expected := map[string]Option[any]{
  2523  		"a": {tSrc("test", 2, 4), true, "foo"},
  2524  		"b": {tSrc("test", 3, 4), true, 12},
  2525  		"c": {tSrc("test", 4, 4), true, 12.2},
  2526  		"d": {tSrc("test", 5, 4), true, "12.2.2"},
  2527  	}
  2528  	var node yaml.Node
  2529  	err := yaml.Unmarshal([]byte(config), &node)
  2530  	require.NoError(t, err)
  2531  	fig := newFigTreeFromEnv()
  2532  	err = fig.LoadConfigSource(&node, "test", &data)
  2533  	require.NoError(t, err)
  2534  	require.Equal(t, expected, data)
  2535  }
  2536  
  2537  func TestAssignToAny(t *testing.T) {
  2538  	var dest map[string]any
  2539  	config := `
  2540  a: foo
  2541  b: 12
  2542  c: 12.2
  2543  d: 12.2.2
  2544  `
  2545  	expected := map[string]any{
  2546  		"a": "foo",
  2547  		"b": 12,
  2548  		"c": 12.2,
  2549  		"d": "12.2.2",
  2550  	}
  2551  	var node yaml.Node
  2552  	err := yaml.Unmarshal([]byte(config), &node)
  2553  	require.NoError(t, err)
  2554  	fig := newFigTreeFromEnv()
  2555  	err = fig.LoadConfigSource(&node, "test", &dest)
  2556  	require.NoError(t, err)
  2557  	require.Equal(t, expected, dest)
  2558  
  2559  	content, err := yaml.Marshal(dest)
  2560  	require.NoError(t, err)
  2561  	raw := map[string]any{}
  2562  	err = yaml.Unmarshal(content, &raw)
  2563  	require.NoError(t, err)
  2564  
  2565  	dest = map[string]any{}
  2566  	err = Merge(&dest, &raw)
  2567  	require.NoError(t, err)
  2568  	require.Equal(t, expected, dest)
  2569  }
  2570  
  2571  func TestAssignInterfaceListToListStringOption(t *testing.T) {
  2572  	type data struct {
  2573  		MyList ListStringOption `yaml:"mylist"`
  2574  	}
  2575  	config := `
  2576  mylist: []
  2577  `
  2578  	expected := data{}
  2579  	var node yaml.Node
  2580  	err := yaml.Unmarshal([]byte(config), &node)
  2581  	require.NoError(t, err)
  2582  	fig := newFigTreeFromEnv()
  2583  	dest := data{}
  2584  	err = fig.LoadConfigSource(&node, "test", &dest)
  2585  	require.NoError(t, err)
  2586  	require.Equal(t, expected, dest)
  2587  
  2588  	content, err := yaml.Marshal(dest)
  2589  	require.NoError(t, err)
  2590  	raw := map[string]any{}
  2591  	err = yaml.Unmarshal(content, &raw)
  2592  	require.NoError(t, err)
  2593  
  2594  	dest = data{}
  2595  	err = Merge(&dest, &raw)
  2596  	require.NoError(t, err)
  2597  	require.Equal(t, expected, dest)
  2598  }
  2599  
  2600  func TestAssignStringIntoList(t *testing.T) {
  2601  	type data struct {
  2602  		MyList ListStringOption `yaml:"mylist"`
  2603  	}
  2604  	config := `
  2605  mylist: foobar
  2606  `
  2607  	var node yaml.Node
  2608  	err := yaml.Unmarshal([]byte(config), &node)
  2609  	require.NoError(t, err)
  2610  	fig := newFigTreeFromEnv()
  2611  	dest := data{}
  2612  	err = fig.LoadConfigSource(&node, "test", &dest)
  2613  	require.Error(t, err)
  2614  }
  2615  
  2616  func TestAssignStringToOptionPointer(t *testing.T) {
  2617  	type data struct {
  2618  		MyStr *StringOption `yaml:"my-str"`
  2619  	}
  2620  	config := `
  2621  my-str: abc
  2622  `
  2623  	expected := data{
  2624  		MyStr: &StringOption{tSrc("test", 2, 9), true, "abc"},
  2625  	}
  2626  	var node yaml.Node
  2627  	err := yaml.Unmarshal([]byte(config), &node)
  2628  	require.NoError(t, err)
  2629  	fig := newFigTreeFromEnv()
  2630  
  2631  	got := data{MyStr: &StringOption{}}
  2632  	err = fig.LoadConfigSource(&node, "test", &got)
  2633  	require.NoError(t, err)
  2634  	require.Equal(t, expected, got)
  2635  
  2636  	got = data{}
  2637  	err = fig.LoadConfigSource(&node, "test", &got)
  2638  	require.NoError(t, err)
  2639  	require.Equal(t, expected, got)
  2640  }
  2641  
  2642  func TestAssignYAMLMergeMap(t *testing.T) {
  2643  	type data struct {
  2644  		MyMap    map[string]int `yaml:"my-map"`
  2645  		ExtraMap map[string]int `yaml:"extra-map"`
  2646  	}
  2647  	config := `
  2648  defs:
  2649    - &common
  2650      a: 1
  2651      b: 2
  2652    - &extra
  2653      d: 4
  2654      e: 5
  2655  my-map:
  2656    <<: *common
  2657    c: 3
  2658  extra-map:
  2659    <<: [*common, *extra]
  2660    c: 3
  2661  `
  2662  	expected := data{
  2663  		MyMap: map[string]int{
  2664  			"a": 1,
  2665  			"b": 2,
  2666  			"c": 3,
  2667  		},
  2668  		ExtraMap: map[string]int{
  2669  			"a": 1,
  2670  			"b": 2,
  2671  			"c": 3,
  2672  			"d": 4,
  2673  			"e": 5,
  2674  		},
  2675  	}
  2676  	var node yaml.Node
  2677  	err := yaml.Unmarshal([]byte(config), &node)
  2678  	require.NoError(t, err)
  2679  	fig := newFigTreeFromEnv()
  2680  
  2681  	got := data{}
  2682  	err = fig.LoadConfigSource(&node, "test", &got)
  2683  	require.NoError(t, err)
  2684  	require.Equal(t, expected, got)
  2685  }
  2686  
  2687  func TestDecodeWithSource(t *testing.T) {
  2688  	StringifyValue = false
  2689  	defer func() {
  2690  		StringifyValue = true
  2691  	}()
  2692  
  2693  	type data struct {
  2694  		MyMap    map[string]IntOption `yaml:"my-map"`
  2695  		ExtraMap map[string]IntOption `yaml:"extra-map"`
  2696  	}
  2697  	config := `
  2698  defs:
  2699    - &common
  2700      a: 1
  2701      b: 2
  2702      c:
  2703    - &extra
  2704      e: 4
  2705      f: 5
  2706      g:
  2707  my-map:
  2708    <<: *common
  2709    d: 3
  2710  extra-map:
  2711    <<: [*common, *extra]
  2712    d: 3
  2713  `
  2714  
  2715  	var node yaml.Node
  2716  	err := yaml.Unmarshal([]byte(config), &node)
  2717  	if err != nil {
  2718  		panic(err)
  2719  	}
  2720  	fig := newFigTreeFromEnv()
  2721  
  2722  	got := data{}
  2723  	err = fig.LoadConfigSource(&node, "test", &got)
  2724  	require.NoError(t, err)
  2725  
  2726  	var buf bytes.Buffer
  2727  	err = yaml.NewEncoder(&buf).Encode(&got)
  2728  	require.NoError(t, err)
  2729  
  2730  	expected := `
  2731  	my-map:
  2732  	    a:
  2733  	        value: 1
  2734  	        source: test:4:8
  2735  	        defined: true
  2736  	    b:
  2737  	        value: 2
  2738  	        source: test:5:8
  2739  	        defined: true
  2740  	    c:
  2741  	        value: 0
  2742  	        source: test:6:7
  2743  	        defined: false
  2744  	    d:
  2745  	        value: 3
  2746  	        source: test:13:6
  2747  	        defined: true
  2748  	extra-map:
  2749  	    a:
  2750  	        value: 1
  2751  	        source: test:4:8
  2752  	        defined: true
  2753  	    b:
  2754  	        value: 2
  2755  	        source: test:5:8
  2756  	        defined: true
  2757  	    c:
  2758  	        value: 0
  2759  	        source: test:6:7
  2760  	        defined: false
  2761  	    d:
  2762  	        value: 3
  2763  	        source: test:16:6
  2764  	        defined: true
  2765  	    e:
  2766  	        value: 4
  2767  	        source: test:8:8
  2768  	        defined: true
  2769  	    f:
  2770  	        value: 5
  2771  	        source: test:9:8
  2772  	        defined: true
  2773  	    g:
  2774  	        value: 0
  2775  	        source: test:10:7
  2776  	        defined: false
  2777  `
  2778  	expected = strings.TrimLeftFunc(expected, unicode.IsSpace)
  2779  	expected = strings.ReplaceAll(expected, "\t", "")
  2780  	require.Equal(t, expected, buf.String())
  2781  }
  2782  
  2783  func TestPreserveRawYAMLStrings(t *testing.T) {
  2784  	type data struct {
  2785  		MyMap map[string]string `yaml:"my-map"`
  2786  	}
  2787  	config := `
  2788  my-map:
  2789    Prop1: "False"
  2790    Prop2: False
  2791    Prop3: 12
  2792    Prop4: 12.3
  2793  `
  2794  	expected := data{
  2795  		MyMap: map[string]string{
  2796  			"Prop1": "False",
  2797  			"Prop2": "False",
  2798  			"Prop3": "12",
  2799  			"Prop4": "12.3",
  2800  		},
  2801  	}
  2802  	var node yaml.Node
  2803  	err := yaml.Unmarshal([]byte(config), &node)
  2804  	require.NoError(t, err)
  2805  	fig := newFigTreeFromEnv()
  2806  	got := data{}
  2807  	err = fig.LoadConfigSource(&node, "test", &got)
  2808  	require.NoError(t, err)
  2809  	require.Equal(t, expected, got)
  2810  
  2811  	content, err := yaml.Marshal(got)
  2812  	require.NoError(t, err)
  2813  	raw := map[string]any{}
  2814  	err = yaml.Unmarshal(content, &raw)
  2815  	require.NoError(t, err)
  2816  
  2817  	got = data{}
  2818  	err = Merge(&got, &raw)
  2819  	require.NoError(t, err)
  2820  	require.Equal(t, expected, got)
  2821  }
  2822  
  2823  func TestMergeZeroOption(t *testing.T) {
  2824  	type testData struct {
  2825  		Data StringOption
  2826  	}
  2827  	dest := testData{
  2828  		Data: NewStringOption("default"),
  2829  	}
  2830  	err := Merge(&dest, &testData{})
  2831  	require.NoError(t, err)
  2832  
  2833  	expected := testData{
  2834  		Data: NewStringOption("default"),
  2835  	}
  2836  
  2837  	require.Equal(t, expected, dest)
  2838  }
  2839  
  2840  func TestYAMLReferences(t *testing.T) {
  2841  	type stuffOptions struct {
  2842  		A StringOption
  2843  		B StringOption
  2844  		C StringOption
  2845  		D StringOption
  2846  	}
  2847  	type stuffStrings struct {
  2848  		A string
  2849  		B string
  2850  		C string
  2851  		D string
  2852  	}
  2853  	type data struct {
  2854  		Stuff1 stuffOptions      `yaml:"stuff1"`
  2855  		Stuff2 stuffStrings      `yaml:"stuff2"`
  2856  		Stuff3 map[string]string `yaml:"stuff3"`
  2857  	}
  2858  
  2859  	config := `
  2860  defs:
  2861   - &mystuff
  2862     a: 1
  2863     b: 2
  2864   - &num 42
  2865  stuff1:
  2866    <<: *mystuff
  2867    c: 3
  2868    d: *num
  2869  stuff2:
  2870    <<: *mystuff
  2871    c: 4
  2872    d: *num
  2873  stuff3:
  2874    <<: *mystuff
  2875    c: 5
  2876    d: *num
  2877  `
  2878  	expected := data{
  2879  		Stuff1: stuffOptions{
  2880  			A: StringOption{tSrc("test", 4, 7), true, "1"},
  2881  			B: StringOption{tSrc("test", 5, 7), true, "2"},
  2882  			C: StringOption{tSrc("test", 9, 6), true, "3"},
  2883  			D: StringOption{tSrc("test", 6, 4), true, "42"},
  2884  		},
  2885  		Stuff2: stuffStrings{
  2886  			A: "1",
  2887  			B: "2",
  2888  			C: "4",
  2889  			D: "42",
  2890  		},
  2891  		Stuff3: map[string]string{
  2892  			"a": "1",
  2893  			"b": "2",
  2894  			"c": "5",
  2895  			"d": "42",
  2896  		},
  2897  	}
  2898  
  2899  	var node yaml.Node
  2900  	err := yaml.Unmarshal([]byte(config), &node)
  2901  	require.NoError(t, err)
  2902  	fig := newFigTreeFromEnv()
  2903  
  2904  	got := data{}
  2905  	err = fig.LoadConfigSource(&node, "test", &got)
  2906  	require.NoError(t, err)
  2907  	require.Equal(t, expected, got)
  2908  }
  2909  
  2910  func TestYAMLReferencesMaps(t *testing.T) {
  2911  	type stuffOptions struct {
  2912  		Map    MapStringOption
  2913  		Merged StringOption
  2914  		Extra  StringOption
  2915  	}
  2916  	type stuffMap struct {
  2917  		Map    map[string]string
  2918  		Merged string
  2919  		Extra  string
  2920  	}
  2921  	type data struct {
  2922  		Stuff1 stuffOptions `yaml:"stuff1"`
  2923  		Stuff2 stuffOptions `yaml:"stuff2"`
  2924  		Stuff3 stuffMap     `yaml:"stuff3"`
  2925  		Stuff4 stuffMap     `yaml:"stuff4"`
  2926  	}
  2927  
  2928  	config := `
  2929  defs:
  2930   - &map1
  2931     map: {a: 1, b: 2} # this is ignored, dup key with merge site
  2932     merged: "map1"
  2933   - &map2
  2934     map: {b: 3, c: 4, d: 5} # this is ignored, dup key with merge site
  2935     merged: "map2"
  2936  stuff1:
  2937    <<: *map1
  2938    map: {b: 3, c: 4}
  2939    extra: stuff1
  2940  stuff2:
  2941    <<: [*map1, *map2]
  2942    map: {b: 5, c: 6, e: 7}
  2943    extra: stuff2
  2944  stuff3:
  2945    <<: *map1
  2946    map: {b: 3, c: 4}
  2947    extra: stuff3
  2948  stuff4:
  2949    <<: [*map1, *map2]
  2950    map: {b: 5, c: 6, e: 7}
  2951    extra: stuff4
  2952  `
  2953  	expected := data{
  2954  		Stuff1: stuffOptions{
  2955  			Map: MapStringOption{
  2956  				"b": {tSrc("test", 11, 12), true, "3"},
  2957  				"c": {tSrc("test", 11, 18), true, "4"},
  2958  			},
  2959  			Merged: StringOption{tSrc("test", 5, 12), true, "map1"},
  2960  			Extra:  StringOption{tSrc("test", 12, 10), true, "stuff1"},
  2961  		},
  2962  		Stuff2: stuffOptions{
  2963  			Map: MapStringOption{
  2964  				"b": {tSrc("test", 15, 12), true, "5"},
  2965  				"c": {tSrc("test", 15, 18), true, "6"},
  2966  				"e": {tSrc("test", 15, 24), true, "7"},
  2967  			},
  2968  			Merged: StringOption{tSrc("test", 5, 12), true, "map1"},
  2969  			Extra:  StringOption{tSrc("test", 16, 10), true, "stuff2"},
  2970  		},
  2971  		Stuff3: stuffMap{
  2972  			Map: map[string]string{
  2973  				"b": "3",
  2974  				"c": "4",
  2975  			},
  2976  			Merged: "map1",
  2977  			Extra:  "stuff3",
  2978  		},
  2979  		Stuff4: stuffMap{
  2980  			Map: map[string]string{
  2981  				"b": "5",
  2982  				"c": "6",
  2983  				"e": "7",
  2984  			},
  2985  			Merged: "map1",
  2986  			Extra:  "stuff4",
  2987  		},
  2988  	}
  2989  
  2990  	var node yaml.Node
  2991  	err := yaml.Unmarshal([]byte(config), &node)
  2992  	require.NoError(t, err)
  2993  	fig := newFigTreeFromEnv()
  2994  
  2995  	got := data{}
  2996  	err = fig.LoadConfigSource(&node, "test", &got)
  2997  	require.NoError(t, err)
  2998  	require.Equal(t, expected, got)
  2999  }
  3000  
  3001  func TestNullMerge(t *testing.T) {
  3002  	type data struct {
  3003  		Stuff MapStringOption
  3004  	}
  3005  	configs := []string{`
  3006  stuff:
  3007    a:
  3008  `, `
  3009  stuff:
  3010    a: 1
  3011  `}
  3012  
  3013  	sources := []ConfigSource{}
  3014  	for i, config := range configs {
  3015  		var node yaml.Node
  3016  		err := yaml.Unmarshal([]byte(config), &node)
  3017  		require.NoError(t, err)
  3018  		sources = append(sources, ConfigSource{
  3019  			Config:   &node,
  3020  			Filename: "config" + strconv.Itoa(i),
  3021  		})
  3022  	}
  3023  	got := data{}
  3024  	fig := newFigTreeFromEnv()
  3025  	err := fig.LoadAllConfigSources(sources, &got)
  3026  	require.NoError(t, err)
  3027  	expected := data{
  3028  		Stuff: MapStringOption{
  3029  			"a": {tSrc("config1", 3, 6), true, "1"},
  3030  		},
  3031  	}
  3032  	require.Equal(t, expected, got)
  3033  }
  3034  
  3035  func TestArrayAllowDupsOnPrimarySource(t *testing.T) {
  3036  	type data struct {
  3037  		Stuff1 []string         `yaml:"stuff1"`
  3038  		Stuff2 ListStringOption `yaml:"stuff2"`
  3039  	}
  3040  
  3041  	configs := []string{`
  3042  stuff1: [a, b, a, a]
  3043  stuff2: [a, b, a, a]
  3044  `, `
  3045  stuff1: [a, b, c]
  3046  stuff2: [a, b, c]
  3047  `}
  3048  
  3049  	sources := []ConfigSource{}
  3050  	for i, config := range configs {
  3051  		var node yaml.Node
  3052  		err := yaml.Unmarshal([]byte(config), &node)
  3053  		require.NoError(t, err)
  3054  		sources = append(sources, ConfigSource{
  3055  			Config:   &node,
  3056  			Filename: "config" + strconv.Itoa(i),
  3057  		})
  3058  	}
  3059  	got := data{}
  3060  	fig := newFigTreeFromEnv()
  3061  	err := fig.LoadAllConfigSources(sources, &got)
  3062  	require.NoError(t, err)
  3063  	expected := data{
  3064  		Stuff1: []string{"a", "b", "a", "a", "c"},
  3065  		Stuff2: ListStringOption{
  3066  			{tSrc("config0", 3, 10), true, "a"},
  3067  			{tSrc("config0", 3, 13), true, "b"},
  3068  			{tSrc("config0", 3, 16), true, "a"},
  3069  			{tSrc("config0", 3, 19), true, "a"},
  3070  			{tSrc("config1", 3, 16), true, "c"},
  3071  		},
  3072  	}
  3073  	require.Equal(t, expected, got)
  3074  }
  3075  
  3076  func TestOverwrite(t *testing.T) {
  3077  	type data struct {
  3078  		A1 []string          `yaml:"a1"`
  3079  		A2 ListStringOption  `yaml:"a2"`
  3080  		A3 []string          `yaml:"a3"`
  3081  		A4 ListStringOption  `yaml:"a4"`
  3082  		B1 map[string]string `yaml:"b1"`
  3083  		B2 MapStringOption   `yaml:"b2"`
  3084  		B3 map[string]string `yaml:"b3"`
  3085  		B4 MapStringOption   `yaml:"b4"`
  3086  		C1 string            `yaml:"c1"`
  3087  		C2 StringOption      `yaml:"c2"`
  3088  		C3 string            `yaml:"c3"`
  3089  		C4 StringOption      `yaml:"c4"`
  3090  	}
  3091  
  3092  	configs := []string{`
  3093  a1: [a, b, a, a]
  3094  a2: [a, b, a, a]
  3095  a3: [a, b, a, a]
  3096  a4: [a, b, a, a]
  3097  b1: {a: 1, b: 2}
  3098  b2: {a: 1, b: 2}
  3099  b3: {a: 1, b: 2}
  3100  b4: {a: 1, b: 2}
  3101  c1: a
  3102  c2: a
  3103  c3: a
  3104  c4: a
  3105  `, `
  3106  config:
  3107    overwrite: [a1, a2, a3, a4, b1, b2, b3, b4, c1, c2, c3, c4]
  3108  a1: [a]
  3109  a2: [a]
  3110  a3: []
  3111  a4: []
  3112  b1: {a: 1}
  3113  b2: {a: 1}
  3114  b3: {}
  3115  b4: {}
  3116  c1: b
  3117  c2: b
  3118  c3: ""
  3119  c4: ""
  3120  `, `
  3121  # these are ignored b/c first overwrite will "win"
  3122  config:
  3123    overwrite: [a1, a2, a3, a4, b1, b2, b3, b4, c1, c2, c3, c4]
  3124  a1: [c]
  3125  a2: [c]
  3126  a3: [c]
  3127  a4: [c]
  3128  b1: {c: 1}
  3129  b2: {c: 1}
  3130  b3: {c: 1}
  3131  b4: {c: 1}
  3132  c1: c
  3133  c2: c
  3134  c3: c
  3135  c4: c
  3136  `}
  3137  
  3138  	sources := []ConfigSource{}
  3139  	for i, config := range configs {
  3140  		var node yaml.Node
  3141  		err := yaml.Unmarshal([]byte(config), &node)
  3142  		require.NoError(t, err)
  3143  		sources = append(sources, ConfigSource{
  3144  			Config:   &node,
  3145  			Filename: "config" + strconv.Itoa(i),
  3146  		})
  3147  	}
  3148  	got := data{}
  3149  	fig := newFigTreeFromEnv()
  3150  	err := fig.LoadAllConfigSources(sources, &got)
  3151  	require.NoError(t, err)
  3152  	expected := data{
  3153  		A1: []string{"a"},
  3154  		A2: ListStringOption{
  3155  			{tSrc("config1", 5, 6), true, "a"},
  3156  		},
  3157  		A3: []string{},
  3158  		A4: ListStringOption{},
  3159  		B1: map[string]string{"a": "1"},
  3160  		B2: MapStringOption{
  3161  			"a": {tSrc("config1", 9, 9), true, "1"},
  3162  		},
  3163  		B3: map[string]string{},
  3164  		B4: MapStringOption{},
  3165  		C1: "b",
  3166  		C2: StringOption{tSrc("config1", 13, 5), true, "b"},
  3167  		C3: "",
  3168  		C4: StringOption{tSrc("config1", 15, 5), true, ""},
  3169  	}
  3170  	require.Equal(t, expected, got)
  3171  }
  3172  
  3173  func TestCustomOption(t *testing.T) {
  3174  	type MyString string
  3175  	type data struct {
  3176  		Stuff Option[MyString]
  3177  		Other Option[UnmarshalString]
  3178  		More  Option[*MyString]
  3179  		Extra Option[*UnmarshalString]
  3180  	}
  3181  	config := `
  3182  stuff: abc
  3183  other: foo
  3184  more: baz
  3185  extra: bin
  3186  `
  3187  	baz := MyString("baz")
  3188  	bin := UnmarshalString("BIN")
  3189  	expected := data{
  3190  		Stuff: Option[MyString]{
  3191  			Source:  tSrc("test", 2, 8),
  3192  			Defined: true,
  3193  			Value:   MyString("abc"),
  3194  		},
  3195  		Other: Option[UnmarshalString]{
  3196  			Source:  tSrc("test", 3, 8),
  3197  			Defined: true,
  3198  			Value:   UnmarshalString("FOO"),
  3199  		},
  3200  		More: Option[*MyString]{
  3201  			Source:  tSrc("test", 4, 7),
  3202  			Defined: true,
  3203  			Value:   &baz,
  3204  		},
  3205  		Extra: Option[*UnmarshalString]{
  3206  			Source:  tSrc("test", 5, 8),
  3207  			Defined: true,
  3208  			Value:   &bin,
  3209  		},
  3210  	}
  3211  	var node yaml.Node
  3212  	err := yaml.Unmarshal([]byte(config), &node)
  3213  	require.NoError(t, err)
  3214  	fig := newFigTreeFromEnv()
  3215  	got := data{}
  3216  	err = fig.LoadConfigSource(&node, "test", &got)
  3217  	require.NoError(t, err)
  3218  	require.Equal(t, expected, got)
  3219  }
  3220  
  3221  type ParsedThing struct {
  3222  	First  string
  3223  	Second string
  3224  }
  3225  
  3226  func (t *ParsedThing) UnmarshalYAML(unmarshal func(any) error) error {
  3227  	var rawType string
  3228  	if err := unmarshal(&rawType); err != nil {
  3229  		return errors.WithStack(err)
  3230  	}
  3231  	first, second, ok := strings.Cut(rawType, ":")
  3232  	if !ok {
  3233  		return errors.Errorf("invalid parsed thing, expected: 'first:second', got: %s", rawType)
  3234  	}
  3235  	t.First = first
  3236  	t.Second = second
  3237  	return nil
  3238  }
  3239  
  3240  func (t ParsedThing) MarshalYAML() (any, error) {
  3241  	return t.First + ":" + t.Second, nil
  3242  }
  3243  
  3244  func TestCustomOptionStruct(t *testing.T) {
  3245  	StringifyValue = true
  3246  	defer func() {
  3247  		StringifyValue = false
  3248  	}()
  3249  	type data struct {
  3250  		Stuff Option[ParsedThing]
  3251  		Other ParsedThing
  3252  		More  Option[*ParsedThing]
  3253  		Extra *ParsedThing
  3254  	}
  3255  	config := `stuff: abc:def
  3256  other: ghi:jkl
  3257  more: mno:pqr
  3258  extra: stu:vwx
  3259  `
  3260  	expected := data{
  3261  		Stuff: Option[ParsedThing]{
  3262  			Source:  tSrc("yaml", 1, 8),
  3263  			Defined: true,
  3264  			Value: ParsedThing{
  3265  				First:  "abc",
  3266  				Second: "def",
  3267  			},
  3268  		},
  3269  		Other: ParsedThing{
  3270  			First:  "ghi",
  3271  			Second: "jkl",
  3272  		},
  3273  		More: Option[*ParsedThing]{
  3274  			Source:  tSrc("yaml", 3, 7),
  3275  			Defined: true,
  3276  			Value: &ParsedThing{
  3277  				First:  "mno",
  3278  				Second: "pqr",
  3279  			},
  3280  		},
  3281  		Extra: &ParsedThing{
  3282  			First:  "stu",
  3283  			Second: "vwx",
  3284  		},
  3285  	}
  3286  	var node yaml.Node
  3287  	err := yaml.Unmarshal([]byte(config), &node)
  3288  	require.NoError(t, err)
  3289  	fig := newFigTreeFromEnv()
  3290  	got := data{}
  3291  	err = fig.LoadConfigSource(&node, "yaml", &got)
  3292  	require.NoError(t, err)
  3293  	require.Equal(t, expected, got)
  3294  
  3295  	content, err := yaml.Marshal(got)
  3296  	require.NoError(t, err)
  3297  	got = data{}
  3298  	err = yaml.Unmarshal(content, &got)
  3299  	require.NoError(t, err)
  3300  	require.Equal(t, expected, got)
  3301  }
  3302  
  3303  func TestMergeArrayWithNilElems(t *testing.T) {
  3304  	dest := map[string]interface{}{
  3305  		"stuff": []any{
  3306  			"abc",
  3307  			nil,
  3308  		},
  3309  	}
  3310  
  3311  	src := map[string]interface{}{
  3312  		"stuff": []any{
  3313  			"abc",
  3314  			"def",
  3315  		},
  3316  	}
  3317  
  3318  	err := Merge(dest, src)
  3319  	require.NoError(t, err)
  3320  
  3321  	expected := map[string]interface{}{
  3322  		"stuff": []any{
  3323  			"abc",
  3324  			nil,
  3325  			"def",
  3326  		},
  3327  	}
  3328  	assert.Equal(t, expected, dest)
  3329  }
  3330  
  3331  func TestMergeEmbedStructWithNilFields(t *testing.T) {
  3332  	type Embed struct {
  3333  		A string
  3334  		B string
  3335  	}
  3336  	type myStruct struct {
  3337  		Foo string
  3338  		Bar string
  3339  		*Embed
  3340  	}
  3341  
  3342  	dest := &myStruct{
  3343  		Foo:   "foo",
  3344  		Embed: &Embed{},
  3345  	}
  3346  
  3347  	src := &myStruct{
  3348  		Bar: "bar",
  3349  	}
  3350  
  3351  	err := Merge(dest, src)
  3352  	require.NoError(t, err)
  3353  
  3354  	// Empty Embed is already set in dest, so left empty
  3355  	expected := &myStruct{
  3356  		Foo:   "foo",
  3357  		Bar:   "bar",
  3358  		Embed: &Embed{},
  3359  	}
  3360  	assert.Equal(t, expected, dest)
  3361  
  3362  	dest = &myStruct{
  3363  		Foo: "foo",
  3364  	}
  3365  
  3366  	src = &myStruct{
  3367  		Bar:   "bar",
  3368  		Embed: &Embed{},
  3369  	}
  3370  
  3371  	err = Merge(dest, src)
  3372  	require.NoError(t, err)
  3373  
  3374  	// Embed is nil, b/c it is empty in src, and nil in dest
  3375  	// We do not create new pointer structs for empty sources.
  3376  	expected = &myStruct{
  3377  		Foo:   "foo",
  3378  		Bar:   "bar",
  3379  		Embed: (*Embed)(nil),
  3380  	}
  3381  	assert.Equal(t, expected, dest)
  3382  
  3383  	dest = &myStruct{
  3384  		Foo: "foo",
  3385  		Embed: &Embed{
  3386  			A: "a",
  3387  		},
  3388  	}
  3389  
  3390  	src = &myStruct{
  3391  		Bar: "bar",
  3392  	}
  3393  
  3394  	err = Merge(dest, src)
  3395  	require.NoError(t, err)
  3396  
  3397  	// Embed is defined from dest, no updates from nil Embed in src
  3398  	expected = &myStruct{
  3399  		Foo: "foo",
  3400  		Bar: "bar",
  3401  		Embed: &Embed{
  3402  			A: "a",
  3403  		},
  3404  	}
  3405  	assert.Equal(t, expected, dest)
  3406  
  3407  	dest = &myStruct{
  3408  		Foo: "foo",
  3409  		Embed: &Embed{
  3410  			A: "a",
  3411  		},
  3412  	}
  3413  
  3414  	src = &myStruct{
  3415  		Bar: "bar",
  3416  	}
  3417  
  3418  	err = Merge(dest, src)
  3419  	require.NoError(t, err)
  3420  
  3421  	// Embed is created from src, no errors on merge from nil Embed in dest.
  3422  	expected = &myStruct{
  3423  		Foo: "foo",
  3424  		Bar: "bar",
  3425  		Embed: &Embed{
  3426  			A: "a",
  3427  		},
  3428  	}
  3429  	assert.Equal(t, expected, dest)
  3430  }
  3431  
  3432  func TestMergeWithDstStructArg(t *testing.T) {
  3433  	dest := struct {
  3434  		MyStructField string `yaml:"struct-field"`
  3435  	}{
  3436  		MyStructField: "field1",
  3437  	}
  3438  
  3439  	src := map[string]interface{}{
  3440  		"struct-field": "mapval1",
  3441  	}
  3442  
  3443  	err := Merge(dest, src)
  3444  	require.Error(t, err)
  3445  }