...

Source file src/gopkg.in/ini.v1/struct_test.go

Documentation: gopkg.in/ini.v1

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

View as plain text