Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
### Added
- Add support for HJSON. #131
- Add new parse.Config to adjust parsing of varibles returned by a Resolve. #139
- Add call to InitDefaults when map, primitives, or structs implement Initializer interface during Unpack. #104

### Changed
- Moved internal/parse to parse module. #139
Expand Down
59 changes: 59 additions & 0 deletions initializer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

package ucfg

import (
"reflect"
)

// Initializer interface provides initialization of default values support to Unpack.
// The InitDefaults method will be executed for any type passed directly or indirectly to
// Unpack.
type Initializer interface {
InitDefaults()
}

func tryInitDefaults(val reflect.Value) reflect.Value {
t := val.Type()

var initializer Initializer
if t.Implements(iInitializer) {
initializer = val.Interface().(Initializer)
initializer.InitDefaults()
return val
} else if reflect.PtrTo(t).Implements(iInitializer) {
tmp := pointerize(reflect.PtrTo(t), t, val)
initializer = tmp.Interface().(Initializer)
initializer.InitDefaults()

// Return the element in the pointer so the value is set into the
// field and not a pointer to the value.
return tmp.Elem()
Comment thread
blakerouse marked this conversation as resolved.
}
return val
}

func hasInitDefaults(t reflect.Type) bool {
if t.Implements(iInitializer) {
return true
}
if reflect.PtrTo(t).Implements(iInitializer) {
return true
}
return false
}
235 changes: 235 additions & 0 deletions initializer_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

package ucfg

import (
"testing"

"github.com/stretchr/testify/assert"
)

type myIntInitializer int
type myMapInitializer map[string]string

func (i *myIntInitializer) InitDefaults() {
*i = myIntInitializer(3)
}

func (m *myMapInitializer) InitDefaults() {
(*m)["init"] = "defaults"
}

type structInitializer struct {
I int
J int
}
Comment thread
blakerouse marked this conversation as resolved.

func (s *structInitializer) InitDefaults() {
s.J = 10
}

type structNoInitalizer struct {
I myIntInitializer
}

type nestedStructInitializer struct {
M myMapInitializer
N structInitializer
O int
P structNoInitalizer
}

func (n *nestedStructInitializer) InitDefaults() {
n.O = 20

// overridden by InitDefaults from structInitializer
n.N.J = 15
}

type ptrNestedStructInitializer struct {
M *myMapInitializer
N *structInitializer
O int
P *structNoInitalizer
}

func (n *ptrNestedStructInitializer) InitDefaults() {
n.O = 20
}

func TestInitDefaultsPrimitive(t *testing.T) {
c, _ := NewFrom(map[string]interface{}{})

// unpack S
r := &struct {
I myIntInitializer
}{}

err := c.Unpack(r)
assert.NoError(t, err)
assert.Equal(t, myIntInitializer(3), r.I)
}

func TestInitDefaultsPrimitiveSet(t *testing.T) {
c, _ := NewFrom(map[string]interface{}{
"i": 25,
})

// unpack S
r := &struct {
I myIntInitializer
}{}

err := c.Unpack(r)
assert.NoError(t, err)
assert.Equal(t, myIntInitializer(25), r.I)
}

func TestInitDefaultsMap(t *testing.T) {
c, _ := NewFrom(map[string]interface{}{})

// unpack S
r := &struct {
M myMapInitializer
}{}

err := c.Unpack(r)
assert.NoError(t, err)
assert.Equal(t, myMapInitializer{
"init": "defaults",
}, r.M)
}

func TestInitDefaultsMapUpdate(t *testing.T) {
c, _ := NewFrom(map[string]interface{}{
"m": map[string]interface{}{
"other": "config",
},
})

// unpack S
r := &struct {
M myMapInitializer
}{}

err := c.Unpack(r)
assert.NoError(t, err)
assert.Equal(t, myMapInitializer{
"init": "defaults",
"other": "config",
}, r.M)
}

func TestInitDefaultsMapReplace(t *testing.T) {
c, _ := NewFrom(map[string]interface{}{
"m": map[string]interface{}{
"init": "replace",
"other": "config",
},
})

// unpack S
r := &struct {
M myMapInitializer
}{}

err := c.Unpack(r)
assert.NoError(t, err)
assert.Equal(t, myMapInitializer{
"init": "replace",
"other": "config",
}, r.M)
}

func TestInitDefaultsSingle(t *testing.T) {
c, _ := NewFrom(map[string]interface{}{
"s": map[string]interface{}{
"i": 5,
},
})

// unpack S
r := &struct {
S structInitializer
}{}

err := c.Unpack(r)
assert.NoError(t, err)
assert.Equal(t, 5, r.S.I)
assert.Equal(t, 10, r.S.J)
}

func TestInitDefaultsNested(t *testing.T) {
c, _ := NewFrom(map[string]interface{}{
"s": map[string]interface{}{
"n": map[string]interface{}{
"i": 5,
},
},
})

// unpack S
r := &struct {
S nestedStructInitializer
}{}

err := c.Unpack(r)
assert.NoError(t, err)
assert.Equal(t, myMapInitializer{
"init": "defaults",
}, r.S.M)
assert.Equal(t, 5, r.S.N.I)
assert.Equal(t, 10, r.S.N.J)
assert.Equal(t, 20, r.S.O)
assert.Equal(t, myIntInitializer(3), r.S.P.I)
}

func TestInitDefaultsNestedEmpty(t *testing.T) {
c, _ := NewFrom(map[string]interface{}{})

// unpack S
r := &struct {
S nestedStructInitializer
}{}

err := c.Unpack(r)
assert.NoError(t, err)
assert.Equal(t, myMapInitializer{
"init": "defaults",
}, r.S.M)
assert.Equal(t, 0, r.S.N.I)
assert.Equal(t, 10, r.S.N.J)
assert.Equal(t, 20, r.S.O)
assert.Equal(t, myIntInitializer(3), r.S.P.I)
}

func TestInitDefaultsPtrNestedEmpty(t *testing.T) {
c, _ := NewFrom(map[string]interface{}{})

// unpack S
r := &struct {
S ptrNestedStructInitializer
}{}

err := c.Unpack(r)
assert.NoError(t, err)
assert.Nil(t, r.S.M)
assert.Nil(t, r.S.N)
assert.Nil(t, r.S.P)
assert.Equal(t, 20, r.S.O)
}
Loading