...

Source file src/edge-infra.dev/pkg/lib/ini/struct_test.go

Documentation: edge-infra.dev/pkg/lib/ini

     1  package ini
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"strings"
     7  	"testing"
     8  	"time"
     9  
    10  	"github.com/stretchr/testify/assert"
    11  	"github.com/stretchr/testify/require"
    12  )
    13  
    14  type testNested struct {
    15  	Cities      []string `delim:"|"`
    16  	Visits      []time.Time
    17  	Years       []int
    18  	Numbers     []int64
    19  	Ages        []uint
    20  	Populations []uint64
    21  	Coordinates []float64
    22  	Flags       []bool
    23  	Note        string
    24  	Unused      int `ini:"-"`
    25  }
    26  
    27  type TestEmbeded struct {
    28  	GPA float64
    29  }
    30  
    31  type testStruct struct {
    32  	Name           string `ini:"NAME"`
    33  	Age            int
    34  	Male           bool
    35  	Money          float64
    36  	Born           time.Time
    37  	Time           time.Duration `ini:"Duration"`
    38  	OldVersionTime time.Duration
    39  	Others         testNested
    40  	OthersPtr      *testNested
    41  	NilPtr         *testNested
    42  	*TestEmbeded   `ini:"grade"`
    43  	Unused         int `ini:"-"`
    44  	Unsigned       uint
    45  	Omitted        bool     `ini:"omitthis,omitempty"`
    46  	Shadows        []string `ini:",allowshadow"`
    47  	ShadowInts     []int    `ini:"Shadows,allowshadow"`
    48  	BoolPtr        *bool
    49  	BoolPtrNil     *bool
    50  	FloatPtr       *float64
    51  	FloatPtrNil    *float64
    52  	IntPtr         *int
    53  	IntPtrNil      *int
    54  	UintPtr        *uint
    55  	UintPtrNil     *uint
    56  	StringPtr      *string
    57  	StringPtrNil   *string
    58  	TimePtr        *time.Time
    59  	TimePtrNil     *time.Time
    60  	DurationPtr    *time.Duration
    61  	DurationPtrNil *time.Duration
    62  }
    63  
    64  type testInterface struct {
    65  	Address    string
    66  	ListenPort int
    67  	PrivateKey string
    68  }
    69  
    70  type testPeer struct {
    71  	PublicKey    string
    72  	PresharedKey string
    73  	AllowedIPs   []string `delim:","`
    74  }
    75  
    76  type testNonUniqueSectionsStruct struct {
    77  	Interface testInterface
    78  	Peer      []testPeer `ini:",nonunique"`
    79  }
    80  
    81  type BaseStruct struct {
    82  	Base bool
    83  }
    84  
    85  type testExtend struct {
    86  	BaseStruct `ini:",extends"`
    87  	Extend     bool
    88  }
    89  
    90  const confDataStruct = `
    91  NAME = Unknwon
    92  Age = 21
    93  Male = true
    94  Money = 1.25
    95  Born = 1993-10-07T20:17:05Z
    96  Duration = 2h45m
    97  OldVersionTime = 30
    98  Unsigned = 3
    99  omitthis = true
   100  Shadows = 1, 2
   101  Shadows = 3, 4
   102  BoolPtr = false
   103  FloatPtr = 0
   104  IntPtr = 0
   105  UintPtr = 0
   106  StringPtr = ""
   107  TimePtr = 0001-01-01T00:00:00Z
   108  DurationPtr = 0s
   109  
   110  [Others]
   111  Cities = HangZhou|Boston
   112  Visits = 1993-10-07T20:17:05Z, 1993-10-07T20:17:05Z
   113  Years = 1993,1994
   114  Numbers = 10010,10086
   115  Ages = 18,19
   116  Populations = 12345678,98765432
   117  Coordinates = 192.168,10.11
   118  Flags       = true,false
   119  Note = Hello world!
   120  
   121  [OthersPtr]
   122  Cities = HangZhou|Boston
   123  Visits = 1993-10-07T20:17:05Z, 1993-10-07T20:17:05Z
   124  Years = 1993,1994
   125  Numbers = 10010,10086
   126  Ages = 18,19
   127  Populations = 12345678,98765432
   128  Coordinates = 192.168,10.11
   129  Flags       = true,false
   130  Note = Hello world!
   131  
   132  [grade]
   133  GPA = 2.8
   134  
   135  [foo.bar]
   136  Here = there
   137  When = then
   138  
   139  [extended]
   140  Base = true
   141  Extend = true
   142  `
   143  
   144  const confNonUniqueSectionDataStruct = `[Interface]
   145  Address    = 10.2.0.1/24
   146  ListenPort = 34777
   147  PrivateKey = privServerKey
   148  
   149  [Peer]
   150  PublicKey    = pubClientKey
   151  PresharedKey = psKey
   152  AllowedIPs   = 10.2.0.2/32,fd00:2::2/128
   153  
   154  [Peer]
   155  PublicKey    = pubClientKey2
   156  PresharedKey = psKey2
   157  AllowedIPs   = 10.2.0.3/32,fd00:2::3/128
   158  `
   159  
   160  type unsupport struct {
   161  	Byte byte
   162  }
   163  
   164  type unsupport2 struct {
   165  	Others struct {
   166  		Cities byte
   167  	}
   168  }
   169  
   170  type Unsupport3 struct {
   171  	Cities byte
   172  }
   173  
   174  type unsupport4 struct {
   175  	*Unsupport3 `ini:"Others"`
   176  }
   177  
   178  type defaultValue struct {
   179  	Name     string
   180  	Age      int
   181  	Male     bool
   182  	Optional *bool
   183  	Money    float64
   184  	Born     time.Time
   185  	Cities   []string
   186  }
   187  
   188  type fooBar struct {
   189  	Here, When string
   190  }
   191  
   192  const invalidDataConfStruct = `
   193  Name =
   194  Age = age
   195  Male = 123
   196  Money = money
   197  Born = nil
   198  Cities =
   199  `
   200  
   201  func Test_MapToStruct(t *testing.T) {
   202  	t.Run("map to struct", func(t *testing.T) {
   203  		t.Run("map file to struct", func(t *testing.T) {
   204  			ts := new(testStruct)
   205  			assert.NoError(t, MapTo(ts, []byte(confDataStruct)))
   206  
   207  			assert.Equal(t, "Unknwon", ts.Name)
   208  			assert.Equal(t, 21, ts.Age)
   209  			assert.True(t, ts.Male)
   210  			assert.Equal(t, 1.25, ts.Money)
   211  			assert.Equal(t, uint(3), ts.Unsigned)
   212  
   213  			ti, err := time.Parse(time.RFC3339, "1993-10-07T20:17:05Z")
   214  			require.NoError(t, err)
   215  			assert.Equal(t, ti.String(), ts.Born.String())
   216  
   217  			dur, err := time.ParseDuration("2h45m")
   218  			require.NoError(t, err)
   219  			assert.Equal(t, dur.Seconds(), ts.Time.Seconds())
   220  
   221  			assert.Equal(t, 30*time.Second, ts.OldVersionTime*time.Second)
   222  
   223  			assert.Equal(t, "HangZhou,Boston", strings.Join(ts.Others.Cities, ","))
   224  			assert.Equal(t, ti.String(), ts.Others.Visits[0].String())
   225  			assert.Equal(t, "[1993 1994]", fmt.Sprint(ts.Others.Years))
   226  			assert.Equal(t, "[10010 10086]", fmt.Sprint(ts.Others.Numbers))
   227  			assert.Equal(t, "[18 19]", fmt.Sprint(ts.Others.Ages))
   228  			assert.Equal(t, "[12345678 98765432]", fmt.Sprint(ts.Others.Populations))
   229  			assert.Equal(t, "[192.168 10.11]", fmt.Sprint(ts.Others.Coordinates))
   230  			assert.Equal(t, "[true false]", fmt.Sprint(ts.Others.Flags))
   231  			assert.Equal(t, "Hello world!", ts.Others.Note)
   232  			assert.Equal(t, 2.8, ts.TestEmbeded.GPA)
   233  
   234  			assert.Equal(t, "HangZhou,Boston", strings.Join(ts.OthersPtr.Cities, ","))
   235  			assert.Equal(t, ti.String(), ts.OthersPtr.Visits[0].String())
   236  			assert.Equal(t, "[1993 1994]", fmt.Sprint(ts.OthersPtr.Years))
   237  			assert.Equal(t, "[10010 10086]", fmt.Sprint(ts.OthersPtr.Numbers))
   238  			assert.Equal(t, "[18 19]", fmt.Sprint(ts.OthersPtr.Ages))
   239  			assert.Equal(t, "[12345678 98765432]", fmt.Sprint(ts.OthersPtr.Populations))
   240  			assert.Equal(t, "[192.168 10.11]", fmt.Sprint(ts.OthersPtr.Coordinates))
   241  			assert.Equal(t, "[true false]", fmt.Sprint(ts.OthersPtr.Flags))
   242  			assert.Equal(t, "Hello world!", ts.OthersPtr.Note)
   243  
   244  			assert.Nil(t, ts.NilPtr)
   245  
   246  			assert.Equal(t, false, *ts.BoolPtr)
   247  			assert.Nil(t, ts.BoolPtrNil)
   248  			assert.Equal(t, float64(0), *ts.FloatPtr)
   249  			assert.Nil(t, ts.FloatPtrNil)
   250  			assert.Equal(t, 0, *ts.IntPtr)
   251  			assert.Nil(t, ts.IntPtrNil)
   252  			assert.Equal(t, uint(0), *ts.UintPtr)
   253  			assert.Nil(t, ts.UintPtrNil)
   254  			assert.Equal(t, "", *ts.StringPtr)
   255  			assert.Nil(t, ts.StringPtrNil)
   256  			assert.NotNil(t, *ts.TimePtr)
   257  			assert.Nil(t, ts.TimePtrNil)
   258  			assert.Equal(t, time.Duration(0), *ts.DurationPtr)
   259  			assert.Nil(t, ts.DurationPtrNil)
   260  		})
   261  
   262  		t.Run("map section to struct", func(t *testing.T) {
   263  			foobar := new(fooBar)
   264  			f, err := Load([]byte(confDataStruct))
   265  			require.NoError(t, err)
   266  
   267  			assert.NoError(t, f.Section("foo.bar").MapTo(foobar))
   268  			assert.Equal(t, "there", foobar.Here)
   269  			assert.Equal(t, "then", foobar.When)
   270  		})
   271  
   272  		t.Run("map to non-pointer struct", func(t *testing.T) {
   273  			f, err := Load([]byte(confDataStruct))
   274  			require.NoError(t, err)
   275  			require.NotNil(t, f)
   276  
   277  			assert.Error(t, f.MapTo(testStruct{}))
   278  		})
   279  
   280  		t.Run("map to unsupported type", func(t *testing.T) {
   281  			f, err := Load([]byte(confDataStruct))
   282  			require.NoError(t, err)
   283  			require.NotNil(t, f)
   284  
   285  			f.NameMapper = func(raw string) string {
   286  				if raw == "Byte" {
   287  					return "NAME"
   288  				}
   289  				return raw
   290  			}
   291  			assert.Error(t, f.MapTo(&unsupport{}))
   292  			assert.Error(t, f.MapTo(&unsupport2{}))
   293  			assert.Error(t, f.MapTo(&unsupport4{}))
   294  		})
   295  
   296  		t.Run("map to omitempty field", func(t *testing.T) {
   297  			ts := new(testStruct)
   298  			assert.NoError(t, MapTo(ts, []byte(confDataStruct)))
   299  
   300  			assert.Equal(t, true, ts.Omitted)
   301  		})
   302  
   303  		t.Run("map with shadows", func(t *testing.T) {
   304  			f, err := LoadSources(LoadOptions{AllowShadows: true}, []byte(confDataStruct))
   305  			require.NoError(t, err)
   306  			ts := new(testStruct)
   307  			assert.NoError(t, f.MapTo(ts))
   308  
   309  			assert.Equal(t, "1 2 3 4", strings.Join(ts.Shadows, " "))
   310  			assert.Equal(t, "[1 2 3 4]", fmt.Sprintf("%v", ts.ShadowInts))
   311  		})
   312  
   313  		t.Run("map from invalid data source", func(t *testing.T) {
   314  			assert.Error(t, MapTo(&testStruct{}, "hi"))
   315  		})
   316  
   317  		t.Run("map to wrong types and gain default values", func(t *testing.T) {
   318  			f, err := Load([]byte(invalidDataConfStruct))
   319  			require.NoError(t, err)
   320  
   321  			ti, err := time.Parse(time.RFC3339, "1993-10-07T20:17:05Z")
   322  			require.NoError(t, err)
   323  			dv := &defaultValue{"Joe", 10, true, nil, 1.25, ti, []string{"HangZhou", "Boston"}}
   324  			assert.NoError(t, f.MapTo(dv))
   325  			assert.Equal(t, "Joe", dv.Name)
   326  			assert.Equal(t, 10, dv.Age)
   327  			assert.True(t, dv.Male)
   328  			assert.Equal(t, 1.25, dv.Money)
   329  			assert.Equal(t, ti.String(), dv.Born.String())
   330  			assert.Equal(t, "HangZhou,Boston", strings.Join(dv.Cities, ","))
   331  		})
   332  
   333  		t.Run("map to extended base", func(t *testing.T) {
   334  			f, err := Load([]byte(confDataStruct))
   335  			require.NoError(t, err)
   336  			require.NotNil(t, f)
   337  			te := testExtend{}
   338  			assert.NoError(t, f.Section("extended").MapTo(&te))
   339  			assert.True(t, te.Base)
   340  			assert.True(t, te.Extend)
   341  		})
   342  	})
   343  
   344  	t.Run("map to struct in strict mode", func(t *testing.T) {
   345  		f, err := Load([]byte(`
   346  name=bruce
   347  age=a30`))
   348  		require.NoError(t, err)
   349  
   350  		type Strict struct {
   351  			Name string `ini:"name"`
   352  			Age  int    `ini:"age"`
   353  		}
   354  		s := new(Strict)
   355  
   356  		assert.Error(t, f.Section("").StrictMapTo(s))
   357  	})
   358  
   359  	t.Run("map slice in strict mode", func(t *testing.T) {
   360  		f, err := Load([]byte(`
   361  names=alice, bruce`))
   362  		require.NoError(t, err)
   363  
   364  		type Strict struct {
   365  			Names []string `ini:"names"`
   366  		}
   367  		s := new(Strict)
   368  
   369  		assert.NoError(t, f.Section("").StrictMapTo(s))
   370  		assert.Equal(t, "[alice bruce]", fmt.Sprint(s.Names))
   371  	})
   372  }
   373  
   374  func Test_MapToStructNonUniqueSections(t *testing.T) {
   375  	t.Run("map to struct non unique", func(t *testing.T) {
   376  		t.Run("map file to struct non unique", func(t *testing.T) {
   377  			f, err := LoadSources(LoadOptions{AllowNonUniqueSections: true}, []byte(confNonUniqueSectionDataStruct))
   378  			require.NoError(t, err)
   379  			ts := new(testNonUniqueSectionsStruct)
   380  
   381  			assert.NoError(t, f.MapTo(ts))
   382  
   383  			assert.Equal(t, "10.2.0.1/24", ts.Interface.Address)
   384  			assert.Equal(t, 34777, ts.Interface.ListenPort)
   385  			assert.Equal(t, "privServerKey", ts.Interface.PrivateKey)
   386  
   387  			assert.Equal(t, "pubClientKey", ts.Peer[0].PublicKey)
   388  			assert.Equal(t, "psKey", ts.Peer[0].PresharedKey)
   389  			assert.Equal(t, "10.2.0.2/32", ts.Peer[0].AllowedIPs[0])
   390  			assert.Equal(t, "fd00:2::2/128", ts.Peer[0].AllowedIPs[1])
   391  
   392  			assert.Equal(t, "pubClientKey2", ts.Peer[1].PublicKey)
   393  			assert.Equal(t, "psKey2", ts.Peer[1].PresharedKey)
   394  			assert.Equal(t, "10.2.0.3/32", ts.Peer[1].AllowedIPs[0])
   395  			assert.Equal(t, "fd00:2::3/128", ts.Peer[1].AllowedIPs[1])
   396  		})
   397  
   398  		t.Run("map non unique section to struct", func(t *testing.T) {
   399  			newPeer := new(testPeer)
   400  			newPeerSlice := make([]testPeer, 0)
   401  
   402  			f, err := LoadSources(LoadOptions{AllowNonUniqueSections: true}, []byte(confNonUniqueSectionDataStruct))
   403  			require.NoError(t, err)
   404  
   405  			// try only first one
   406  			assert.NoError(t, f.Section("Peer").MapTo(newPeer))
   407  			assert.Equal(t, "pubClientKey", newPeer.PublicKey)
   408  			assert.Equal(t, "psKey", newPeer.PresharedKey)
   409  			assert.Equal(t, "10.2.0.2/32", newPeer.AllowedIPs[0])
   410  			assert.Equal(t, "fd00:2::2/128", newPeer.AllowedIPs[1])
   411  
   412  			// try all
   413  			assert.NoError(t, f.Section("Peer").MapTo(&newPeerSlice))
   414  			assert.Equal(t, "pubClientKey", newPeerSlice[0].PublicKey)
   415  			assert.Equal(t, "psKey", newPeerSlice[0].PresharedKey)
   416  			assert.Equal(t, "10.2.0.2/32", newPeerSlice[0].AllowedIPs[0])
   417  			assert.Equal(t, "fd00:2::2/128", newPeerSlice[0].AllowedIPs[1])
   418  
   419  			assert.Equal(t, "pubClientKey2", newPeerSlice[1].PublicKey)
   420  			assert.Equal(t, "psKey2", newPeerSlice[1].PresharedKey)
   421  			assert.Equal(t, "10.2.0.3/32", newPeerSlice[1].AllowedIPs[0])
   422  			assert.Equal(t, "fd00:2::3/128", newPeerSlice[1].AllowedIPs[1])
   423  		})
   424  
   425  		t.Run("map non unique sections with subsections to struct", func(t *testing.T) {
   426  			iniFile, err := LoadSources(LoadOptions{AllowNonUniqueSections: true}, strings.NewReader(`
   427  [Section]
   428  FieldInSubSection = 1
   429  FieldInSubSection2 = 2
   430  FieldInSection = 3
   431  
   432  [Section]
   433  FieldInSubSection = 4
   434  FieldInSubSection2 = 5
   435  FieldInSection = 6
   436  `))
   437  			require.NoError(t, err)
   438  
   439  			type SubSection struct {
   440  				FieldInSubSection string `ini:"FieldInSubSection"`
   441  			}
   442  			type SubSection2 struct {
   443  				FieldInSubSection2 string `ini:"FieldInSubSection2"`
   444  			}
   445  
   446  			type Section struct {
   447  				SubSection     `ini:"Section"`
   448  				SubSection2    `ini:"Section"`
   449  				FieldInSection string `ini:"FieldInSection"`
   450  			}
   451  
   452  			type File struct {
   453  				Sections []Section `ini:"Section,nonunique"`
   454  			}
   455  
   456  			f := new(File)
   457  			err = iniFile.MapTo(f)
   458  			require.NoError(t, err)
   459  
   460  			assert.Equal(t, "1", f.Sections[0].FieldInSubSection)
   461  			assert.Equal(t, "2", f.Sections[0].FieldInSubSection2)
   462  			assert.Equal(t, "3", f.Sections[0].FieldInSection)
   463  
   464  			assert.Equal(t, "4", f.Sections[1].FieldInSubSection)
   465  			assert.Equal(t, "5", f.Sections[1].FieldInSubSection2)
   466  			assert.Equal(t, "6", f.Sections[1].FieldInSection)
   467  		})
   468  	})
   469  }
   470  
   471  func Test_ReflectFromStruct(t *testing.T) {
   472  	t.Run("reflect from struct", func(t *testing.T) {
   473  		type Embeded struct {
   474  			Dates       []time.Time `delim:"|" comment:"Time data"`
   475  			Places      []string
   476  			Years       []int
   477  			Numbers     []int64
   478  			Ages        []uint
   479  			Populations []uint64
   480  			Coordinates []float64
   481  			Flags       []bool
   482  			None        []int
   483  		}
   484  		type Author struct {
   485  			Name      string `ini:"NAME"`
   486  			Male      bool
   487  			Optional  *bool
   488  			Age       int `comment:"Author's age"`
   489  			Height    uint
   490  			GPA       float64
   491  			Date      time.Time
   492  			NeverMind string `ini:"-"`
   493  			ignored   string
   494  			*Embeded  `ini:"infos" comment:"Embeded section"`
   495  		}
   496  
   497  		ti, err := time.Parse(time.RFC3339, "1993-10-07T20:17:05Z")
   498  		require.NoError(t, err)
   499  		a := &Author{"Unknwon", true, nil, 21, 100, 2.8, ti, "", "ignored",
   500  			&Embeded{
   501  				[]time.Time{ti, ti},
   502  				[]string{"HangZhou", "Boston"},
   503  				[]int{1993, 1994},
   504  				[]int64{10010, 10086},
   505  				[]uint{18, 19},
   506  				[]uint64{12345678, 98765432},
   507  				[]float64{192.168, 10.11},
   508  				[]bool{true, false},
   509  				[]int{},
   510  			}}
   511  		cfg := Empty()
   512  		assert.NoError(t, ReflectFrom(cfg, a))
   513  
   514  		var buf bytes.Buffer
   515  		_, err = cfg.WriteTo(&buf)
   516  		require.NoError(t, err)
   517  		assert.Equal(t, `NAME     = Unknwon
   518  Male     = true
   519  Optional =
   520  ; Author's age
   521  Age      = 21
   522  Height   = 100
   523  GPA      = 2.8
   524  Date     = 1993-10-07T20:17:05Z
   525  
   526  ; Embeded section
   527  [infos]
   528  ; Time data
   529  Dates       = 1993-10-07T20:17:05Z|1993-10-07T20:17:05Z
   530  Places      = HangZhou,Boston
   531  Years       = 1993,1994
   532  Numbers     = 10010,10086
   533  Ages        = 18,19
   534  Populations = 12345678,98765432
   535  Coordinates = 192.168,10.11
   536  Flags       = true,false
   537  None        =
   538  `,
   539  			buf.String(),
   540  		)
   541  
   542  		t.Run("reflect from non-point struct", func(t *testing.T) {
   543  			assert.Error(t, ReflectFrom(cfg, Author{}))
   544  		})
   545  
   546  		t.Run("reflect from struct with omitempty", func(t *testing.T) {
   547  			cfg := Empty()
   548  			type SpecialStruct struct {
   549  				FirstName  string    `ini:"first_name"`
   550  				LastName   string    `ini:"last_name,omitempty"`
   551  				JustOmitMe string    `ini:"omitempty"`
   552  				LastLogin  time.Time `ini:"last_login,omitempty"`
   553  				LastLogin2 time.Time `ini:",omitempty"`
   554  				NotEmpty   int       `ini:"omitempty"`
   555  				Number     int64     `ini:",omitempty"`
   556  				Ages       uint      `ini:",omitempty"`
   557  				Population uint64    `ini:",omitempty"`
   558  				Coordinate float64   `ini:",omitempty"`
   559  				Flag       bool      `ini:",omitempty"`
   560  				Note       *string   `ini:",omitempty"`
   561  			}
   562  			special := &SpecialStruct{
   563  				FirstName: "John",
   564  				LastName:  "Doe",
   565  				NotEmpty:  9,
   566  			}
   567  
   568  			assert.NoError(t, ReflectFrom(cfg, special))
   569  
   570  			var buf bytes.Buffer
   571  			_, err = cfg.WriteTo(&buf)
   572  			assert.Equal(t, `first_name = John
   573  last_name  = Doe
   574  omitempty  = 9
   575  `,
   576  				buf.String(),
   577  			)
   578  		})
   579  
   580  		t.Run("reflect from struct with non-anonymous structure pointer", func(t *testing.T) {
   581  			cfg := Empty()
   582  			type Rpc struct {
   583  				Enable  bool   `ini:"enable"`
   584  				Type    string `ini:"type"`
   585  				Address string `ini:"addr"`
   586  				Name    string `ini:"name"`
   587  			}
   588  			type Cfg struct {
   589  				Rpc *Rpc `ini:"rpc"`
   590  			}
   591  
   592  			config := &Cfg{
   593  				Rpc: &Rpc{
   594  					Enable:  true,
   595  					Type:    "type",
   596  					Address: "address",
   597  					Name:    "name",
   598  				},
   599  			}
   600  			assert.NoError(t, cfg.ReflectFrom(config))
   601  
   602  			var buf bytes.Buffer
   603  			_, err = cfg.WriteTo(&buf)
   604  			assert.Equal(t, `[rpc]
   605  enable = true
   606  type   = type
   607  addr   = address
   608  name   = name
   609  `,
   610  				buf.String(),
   611  			)
   612  		})
   613  	})
   614  }
   615  
   616  func Test_ReflectFromStructNonUniqueSections(t *testing.T) {
   617  	t.Run("reflect from struct with non unique sections", func(t *testing.T) {
   618  		nonUnique := &testNonUniqueSectionsStruct{
   619  			Interface: testInterface{
   620  				Address:    "10.2.0.1/24",
   621  				ListenPort: 34777,
   622  				PrivateKey: "privServerKey",
   623  			},
   624  			Peer: []testPeer{
   625  				{
   626  					PublicKey:    "pubClientKey",
   627  					PresharedKey: "psKey",
   628  					AllowedIPs:   []string{"10.2.0.2/32,fd00:2::2/128"},
   629  				},
   630  				{
   631  					PublicKey:    "pubClientKey2",
   632  					PresharedKey: "psKey2",
   633  					AllowedIPs:   []string{"10.2.0.3/32,fd00:2::3/128"},
   634  				},
   635  			},
   636  		}
   637  
   638  		cfg := Empty(LoadOptions{
   639  			AllowNonUniqueSections: true,
   640  		})
   641  
   642  		assert.NoError(t, ReflectFrom(cfg, nonUnique))
   643  
   644  		var buf bytes.Buffer
   645  		_, err := cfg.WriteTo(&buf)
   646  		require.NoError(t, err)
   647  		assert.Equal(t, confNonUniqueSectionDataStruct, buf.String())
   648  
   649  		// note: using ReflectFrom from should overwrite the existing sections
   650  		err = cfg.Section("Peer").ReflectFrom([]*testPeer{
   651  			{
   652  				PublicKey:    "pubClientKey3",
   653  				PresharedKey: "psKey3",
   654  				AllowedIPs:   []string{"10.2.0.4/32,fd00:2::4/128"},
   655  			},
   656  			{
   657  				PublicKey:    "pubClientKey4",
   658  				PresharedKey: "psKey4",
   659  				AllowedIPs:   []string{"10.2.0.5/32,fd00:2::5/128"},
   660  			},
   661  		})
   662  
   663  		require.NoError(t, err)
   664  
   665  		buf = bytes.Buffer{}
   666  		_, err = cfg.WriteTo(&buf)
   667  		require.NoError(t, err)
   668  		assert.Equal(t, `[Interface]
   669  Address    = 10.2.0.1/24
   670  ListenPort = 34777
   671  PrivateKey = privServerKey
   672  
   673  [Peer]
   674  PublicKey    = pubClientKey3
   675  PresharedKey = psKey3
   676  AllowedIPs   = 10.2.0.4/32,fd00:2::4/128
   677  
   678  [Peer]
   679  PublicKey    = pubClientKey4
   680  PresharedKey = psKey4
   681  AllowedIPs   = 10.2.0.5/32,fd00:2::5/128
   682  `,
   683  			buf.String(),
   684  		)
   685  
   686  		// note: using ReflectFrom from should overwrite the existing sections
   687  		err = cfg.Section("Peer").ReflectFrom(&testPeer{
   688  			PublicKey:    "pubClientKey5",
   689  			PresharedKey: "psKey5",
   690  			AllowedIPs:   []string{"10.2.0.6/32,fd00:2::6/128"},
   691  		})
   692  
   693  		require.NoError(t, err)
   694  
   695  		buf = bytes.Buffer{}
   696  		_, err = cfg.WriteTo(&buf)
   697  		require.NoError(t, err)
   698  		assert.Equal(t, `[Interface]
   699  Address    = 10.2.0.1/24
   700  ListenPort = 34777
   701  PrivateKey = privServerKey
   702  
   703  [Peer]
   704  PublicKey    = pubClientKey5
   705  PresharedKey = psKey5
   706  AllowedIPs   = 10.2.0.6/32,fd00:2::6/128
   707  `,
   708  			buf.String(),
   709  		)
   710  	})
   711  }
   712  
   713  // Inspired by https://github.com/go-ini/ini/issues/196
   714  func TestMapToAndReflectFromStructWithShadows(t *testing.T) {
   715  	t.Run("map to struct and then reflect with shadows should generate original config content", func(t *testing.T) {
   716  		type include struct {
   717  			Paths []string `ini:"path,omitempty,allowshadow"`
   718  		}
   719  
   720  		cfg, err := LoadSources(LoadOptions{
   721  			AllowShadows: true,
   722  		}, []byte(`
   723  [include]
   724  path = /tmp/gpm-profiles/test5.profile
   725  path = /tmp/gpm-profiles/test1.profile`))
   726  		require.NoError(t, err)
   727  
   728  		sec := cfg.Section("include")
   729  		inc := new(include)
   730  		err = sec.MapTo(inc)
   731  		require.NoError(t, err)
   732  
   733  		err = sec.ReflectFrom(inc)
   734  		require.NoError(t, err)
   735  
   736  		var buf bytes.Buffer
   737  		_, err = cfg.WriteTo(&buf)
   738  		require.NoError(t, err)
   739  		assert.Equal(t, `[include]
   740  path = /tmp/gpm-profiles/test5.profile
   741  path = /tmp/gpm-profiles/test1.profile
   742  `,
   743  			buf.String(),
   744  		)
   745  
   746  		t.Run("reflect from struct with shadows", func(t *testing.T) {
   747  			cfg := Empty(LoadOptions{
   748  				AllowShadows: true,
   749  			})
   750  			type ShadowStruct struct {
   751  				StringArray      []string    `ini:"sa,allowshadow"`
   752  				EmptyStringArrat []string    `ini:"empty,omitempty,allowshadow"`
   753  				Allowshadow      []string    `ini:"allowshadow,allowshadow"`
   754  				Dates            []time.Time `ini:",allowshadow"`
   755  				Places           []string    `ini:",allowshadow"`
   756  				Years            []int       `ini:",allowshadow"`
   757  				Numbers          []int64     `ini:",allowshadow"`
   758  				Ages             []uint      `ini:",allowshadow"`
   759  				Populations      []uint64    `ini:",allowshadow"`
   760  				Coordinates      []float64   `ini:",allowshadow"`
   761  				Flags            []bool      `ini:",allowshadow"`
   762  				None             []int       `ini:",allowshadow"`
   763  			}
   764  
   765  			shadow := &ShadowStruct{
   766  				StringArray: []string{"s1", "s2"},
   767  				Allowshadow: []string{"s3", "s4"},
   768  				Dates: []time.Time{time.Date(2020, 9, 12, 00, 00, 00, 651387237, time.UTC),
   769  					time.Date(2020, 9, 12, 00, 00, 00, 651387237, time.UTC)},
   770  				Places:      []string{"HangZhou", "Boston"},
   771  				Years:       []int{1993, 1994},
   772  				Numbers:     []int64{10010, 10086},
   773  				Ages:        []uint{18, 19},
   774  				Populations: []uint64{12345678, 98765432},
   775  				Coordinates: []float64{192.168, 10.11},
   776  				Flags:       []bool{true, false},
   777  				None:        []int{},
   778  			}
   779  
   780  			assert.NoError(t, ReflectFrom(cfg, shadow))
   781  
   782  			var buf bytes.Buffer
   783  			_, err := cfg.WriteTo(&buf)
   784  			require.NoError(t, err)
   785  			assert.Equal(t, `sa          = s1
   786  sa          = s2
   787  allowshadow = s3
   788  allowshadow = s4
   789  Dates       = 2020-09-12T00:00:00Z
   790  Places      = HangZhou
   791  Places      = Boston
   792  Years       = 1993
   793  Years       = 1994
   794  Numbers     = 10010
   795  Numbers     = 10086
   796  Ages        = 18
   797  Ages        = 19
   798  Populations = 12345678
   799  Populations = 98765432
   800  Coordinates = 192.168
   801  Coordinates = 10.11
   802  Flags       = true
   803  Flags       = false
   804  None        =
   805  `,
   806  				buf.String(),
   807  			)
   808  		})
   809  	})
   810  }
   811  
   812  type testMapper struct {
   813  	PackageName string
   814  }
   815  
   816  func Test_NameGetter(t *testing.T) {
   817  	t.Run("test name mappers", func(t *testing.T) {
   818  		assert.NoError(t, MapToWithMapper(&testMapper{}, TitleUnderscore, []byte("packag_name=ini")))
   819  
   820  		cfg, err := Load([]byte("PACKAGE_NAME=ini"))
   821  		require.NoError(t, err)
   822  		require.NotNil(t, cfg)
   823  
   824  		cfg.NameMapper = SnackCase
   825  		tg := new(testMapper)
   826  		assert.NoError(t, cfg.MapTo(tg))
   827  		assert.Equal(t, "ini", tg.PackageName)
   828  	})
   829  }
   830  
   831  type testDurationStruct struct {
   832  	Duration time.Duration `ini:"Duration"`
   833  }
   834  
   835  func Test_Duration(t *testing.T) {
   836  	t.Run("duration less than 16m50s", func(t *testing.T) {
   837  		ds := new(testDurationStruct)
   838  		assert.NoError(t, MapTo(ds, []byte("Duration=16m49s")))
   839  
   840  		dur, err := time.ParseDuration("16m49s")
   841  		require.NoError(t, err)
   842  		assert.Equal(t, dur.Seconds(), ds.Duration.Seconds())
   843  	})
   844  }
   845  
   846  type Employer struct {
   847  	Name  string
   848  	Title string
   849  }
   850  
   851  type Employers []*Employer
   852  
   853  func (es Employers) ReflectINIStruct(f *File) error {
   854  	for _, e := range es {
   855  		f.Section(e.Name).Key("Title").SetValue(e.Title)
   856  	}
   857  	return nil
   858  }
   859  
   860  // Inspired by https://github.com/go-ini/ini/issues/199
   861  func Test_StructReflector(t *testing.T) {
   862  	t.Run("reflect with StructReflector interface", func(t *testing.T) {
   863  		p := &struct {
   864  			FirstName string
   865  			Employer  Employers
   866  		}{
   867  			FirstName: "Andrew",
   868  			Employer: []*Employer{
   869  				{
   870  					Name:  `Employer "VMware"`,
   871  					Title: "Staff II Engineer",
   872  				},
   873  				{
   874  					Name:  `Employer "EMC"`,
   875  					Title: "Consultant Engineer",
   876  				},
   877  			},
   878  		}
   879  
   880  		f := Empty()
   881  		assert.NoError(t, f.ReflectFrom(p))
   882  
   883  		var buf bytes.Buffer
   884  		_, err := f.WriteTo(&buf)
   885  		require.NoError(t, err)
   886  
   887  		assert.Equal(t, `FirstName = Andrew
   888  
   889  [Employer "VMware"]
   890  Title = Staff II Engineer
   891  
   892  [Employer "EMC"]
   893  Title = Consultant Engineer
   894  `,
   895  			buf.String(),
   896  		)
   897  	})
   898  }
   899  

View as plain text