You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@camel.apache.org by GitBox <gi...@apache.org> on 2018/10/12 14:43:09 UTC

[GitHub] oscerd closed pull request #176: operator: swich to type safe trait definition

oscerd closed pull request #176: operator: swich to type safe trait definition
URL: https://github.com/apache/camel-k/pull/176
 
 
   

This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:

As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):

diff --git a/Gopkg.lock b/Gopkg.lock
index 3a9fc99..0c22817 100644
--- a/Gopkg.lock
+++ b/Gopkg.lock
@@ -52,6 +52,14 @@
   revision = "3eb9738c1697594ea6e71a7156a9bb32ed216cf0"
   version = "v2.8.0"
 
+[[projects]]
+  digest = "1:aa3ed0a71c4e66e4ae6486bf97a3f4cab28edc78df2e50c5ad01dc7d91604b88"
+  name = "github.com/fatih/structs"
+  packages = ["."]
+  pruneopts = "NUT"
+  revision = "4966fc68f5b7593aafa6cbbba2d65ec6e1416047"
+  version = "v1.1.0"
+
 [[projects]]
   digest = "1:81466b4218bf6adddac2572a30ac733a9255919bc2f470b4827a317bd4ee1756"
   name = "github.com/ghodss/yaml"
@@ -216,6 +224,14 @@
   revision = "c12348ce28de40eed0136aa2b644d0ee0650e56c"
   version = "v1.0.1"
 
+[[projects]]
+  digest = "1:a45ae66dea4c899d79fceb116accfa1892105c251f0dcd9a217ddc276b42ec68"
+  name = "github.com/mitchellh/mapstructure"
+  packages = ["."]
+  pruneopts = "NUT"
+  revision = "3536a929edddb9a5b34bd6861dc4a9647cb459fe"
+  version = "v1.1.2"
+
 [[projects]]
   digest = "1:2f42fa12d6911c7b7659738758631bec870b7e9b4c6be5444f963cdcfccc191f"
   name = "github.com/modern-go/concurrent"
@@ -723,6 +739,8 @@
   analyzer-name = "dep"
   analyzer-version = 1
   input-imports = [
+    "github.com/fatih/structs",
+    "github.com/mitchellh/mapstructure",
     "github.com/openshift/api/apps/v1",
     "github.com/openshift/api/authorization/v1",
     "github.com/openshift/api/build/v1",
diff --git a/build/Makefile b/build/Makefile
index 7357b71..f087869 100644
--- a/build/Makefile
+++ b/build/Makefile
@@ -1,5 +1,7 @@
 build: build-runtime build-operator build-kamel build-compile-integration-tests test
 
+build-go: build-embed-resources build-operator build-kamel
+
 build-operator: build-embed-resources
 	go build -o camel-k-operator ./cmd/camel-k-operator/*.go
 
diff --git a/pkg/apis/camel/v1alpha1/types.go b/pkg/apis/camel/v1alpha1/types.go
index 61141af..06cccce 100644
--- a/pkg/apis/camel/v1alpha1/types.go
+++ b/pkg/apis/camel/v1alpha1/types.go
@@ -18,6 +18,7 @@ limitations under the License.
 package v1alpha1
 
 import (
+	"github.com/mitchellh/mapstructure"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 )
 
@@ -84,10 +85,29 @@ const (
 
 // A IntegrationTraitSpec contains the configuration of a trait
 type IntegrationTraitSpec struct {
-	Enabled       *bool             `json:"enabled,omitempty"`
 	Configuration map[string]string `json:"configuration,omitempty"`
 }
 
+// Decode the trait configuration to a type safe struct
+func (in *IntegrationTraitSpec) Decode(target interface{}) error {
+	md := mapstructure.Metadata{}
+
+	decoder, err := mapstructure.NewDecoder(
+		&mapstructure.DecoderConfig{
+			Metadata:         &md,
+			WeaklyTypedInput: true,
+			TagName:          "property",
+			Result:           &target,
+		},
+	)
+
+	if err != nil {
+		return err
+	}
+
+	return decoder.Decode(in.Configuration)
+}
+
 // IntegrationStatus --
 type IntegrationStatus struct {
 	Phase  IntegrationPhase `json:"phase,omitempty"`
diff --git a/pkg/apis/camel/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/camel/v1alpha1/zz_generated.deepcopy.go
index d7ea010..1c13379 100644
--- a/pkg/apis/camel/v1alpha1/zz_generated.deepcopy.go
+++ b/pkg/apis/camel/v1alpha1/zz_generated.deepcopy.go
@@ -383,11 +383,6 @@ func (in *IntegrationStatus) DeepCopy() *IntegrationStatus {
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *IntegrationTraitSpec) DeepCopyInto(out *IntegrationTraitSpec) {
 	*out = *in
-	if in.Enabled != nil {
-		in, out := &in.Enabled, &out.Enabled
-		*out = new(bool)
-		**out = **in
-	}
 	if in.Configuration != nil {
 		in, out := &in.Configuration, &out.Configuration
 		*out = make(map[string]string, len(*in))
diff --git a/pkg/client/cmd/completion_bash.go b/pkg/client/cmd/completion_bash.go
index 513a821..d6f093a 100644
--- a/pkg/client/cmd/completion_bash.go
+++ b/pkg/client/cmd/completion_bash.go
@@ -19,9 +19,11 @@ package cmd
 
 import (
 	"os"
+	"strings"
 
-	"github.com/apache/camel-k/pkg/util/camel"
+	"github.com/apache/camel-k/pkg/trait"
 
+	"github.com/apache/camel-k/pkg/util/camel"
 	"github.com/spf13/cobra"
 )
 
@@ -66,6 +68,12 @@ __kamel_dependency_type() {
     esac
 }
 
+__kamel_traits() {
+    local type_list="` + strings.Join(trait.ComputeTraitsProperties(), " ") + `"
+    COMPREPLY=( $( compgen -W "${type_list}" -- "$cur") )
+    compopt -o nospace
+}
+
 __kamel_languages() {
     local type_list="js groovy kotlin java xml"
     COMPREPLY=( $( compgen -W "${type_list}" -- "$cur") )
@@ -207,6 +215,13 @@ func configureKnownBashCompletions(command *cobra.Command) {
 			cobra.BashCompCustom: {"__kamel_runtimes"},
 		},
 	)
+	configureBashAnnotationForFlag(
+		command,
+		"trait",
+		map[string][]string{
+			cobra.BashCompCustom: {"__kamel_traits"},
+		},
+	)
 }
 
 func configureBashAnnotationForFlag(command *cobra.Command, flagName string, annotations map[string][]string) {
@@ -218,15 +233,11 @@ func configureBashAnnotationForFlag(command *cobra.Command, flagName string, ann
 }
 
 func computeCamelDependencies() string {
-	result := ""
+	results := make([]string, 0, len(camel.Runtime.Artifacts))
 
 	for k := range camel.Runtime.Artifacts {
-		if result != "" {
-			result = result + " " + k
-		} else {
-			result = k
-		}
+		results = append(results, k)
 	}
 
-	return result
+	return strings.Join(results, " ")
 }
diff --git a/pkg/client/cmd/run.go b/pkg/client/cmd/run.go
index 30f6fa8..5b6664d 100644
--- a/pkg/client/cmd/run.go
+++ b/pkg/client/cmd/run.go
@@ -26,6 +26,8 @@ import (
 	"strconv"
 	"strings"
 
+	"github.com/apache/camel-k/pkg/trait"
+
 	"github.com/apache/camel-k/pkg/util"
 
 	"github.com/apache/camel-k/pkg/util/sync"
@@ -100,7 +102,7 @@ type runCmdOptions struct {
 	Traits                    []string
 }
 
-func (*runCmdOptions) validateArgs(cmd *cobra.Command, args []string) error {
+func (o *runCmdOptions) validateArgs(cmd *cobra.Command, args []string) error {
 	if len(args) != 1 {
 		return errors.New("accepts 1 arg, received " + strconv.Itoa(len(args)))
 	}
@@ -119,10 +121,19 @@ func (*runCmdOptions) validateArgs(cmd *cobra.Command, args []string) error {
 			return errors.New("The URL provided is not reachable " + fileName + " The error code returned is " + strconv.Itoa(resp.StatusCode))
 		}
 	}
+
 	return nil
 }
 
 func (o *runCmdOptions) run(cmd *cobra.Command, args []string) error {
+	tp := trait.ComputeTraitsProperties()
+	for _, t := range o.Traits {
+		if !util.StringSliceExists(tp, t) {
+			fmt.Printf("Error: %s is not a valid trait property\n", t)
+			return nil
+		}
+	}
+
 	integration, err := o.createIntegration(cmd, args)
 	if err != nil {
 		return err
@@ -387,22 +398,15 @@ func (*runCmdOptions) configureTrait(integration *v1alpha1.Integration, config s
 	traitID := parts[1]
 	prop := parts[2][1:]
 	val := parts[3]
-	var spec v1alpha1.IntegrationTraitSpec
-	var ok bool
-	if spec, ok = integration.Spec.Traits[traitID]; !ok {
+
+	spec, ok := integration.Spec.Traits[traitID]
+	if !ok {
 		spec = v1alpha1.IntegrationTraitSpec{
 			Configuration: make(map[string]string),
 		}
 	}
-	if prop == "enabled" {
-		boolVal, err := strconv.ParseBool(val)
-		if err != nil {
-			return errors.Wrap(err, "cannot parse bool value "+val)
-		}
-		spec.Enabled = &boolVal
-	} else {
-		spec.Configuration[prop] = val
-	}
+
+	spec.Configuration[prop] = val
 	integration.Spec.Traits[traitID] = spec
 	return nil
 }
diff --git a/pkg/trait/base.go b/pkg/trait/base.go
index 3432b93..f146ce0 100644
--- a/pkg/trait/base.go
+++ b/pkg/trait/base.go
@@ -19,19 +19,23 @@ package trait
 
 import (
 	"fmt"
-	"github.com/apache/camel-k/pkg/util/kubernetes"
 	"strings"
 
+	"github.com/apache/camel-k/pkg/util/kubernetes"
+
 	appsv1 "k8s.io/api/apps/v1"
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 )
 
 type baseTrait struct {
+	Trait
 }
 
-func (*baseTrait) id() id {
-	return id("base")
+func newBaseTrait() baseTrait {
+	return baseTrait{
+		Trait: NewTraitWithID("base"),
+	}
 }
 
 func (d *baseTrait) customize(environment *environment, resources *kubernetes.Collection) (bool, error) {
diff --git a/pkg/trait/catalog.go b/pkg/trait/catalog.go
index abc6415..4d94477 100644
--- a/pkg/trait/catalog.go
+++ b/pkg/trait/catalog.go
@@ -23,10 +23,10 @@ import (
 )
 
 var (
-	tBase    = &baseTrait{}
-	tService = &serviceTrait{}
-	tRoute   = &routeTrait{}
-	tOwner   = &ownerTrait{}
+	tBase    = newBaseTrait()
+	tService = newServiceTrait()
+	tRoute   = newRouteTrait()
+	tOwner   = newOwnerTrait()
 )
 
 // customizersFor returns a Catalog for the given integration details
@@ -34,16 +34,16 @@ func customizersFor(environment *environment) customizer {
 	switch environment.Platform.Spec.Cluster {
 	case v1alpha1.IntegrationPlatformClusterOpenShift:
 		return compose(
-			tBase,
-			tService,
-			tRoute,
-			tOwner,
+			&tBase,
+			&tService,
+			&tRoute,
+			&tOwner,
 		)
 	case v1alpha1.IntegrationPlatformClusterKubernetes:
 		return compose(
-			tBase,
-			tService,
-			tOwner,
+			&tBase,
+			&tService,
+			&tOwner,
 		)
 		// case Knative: ...
 	}
@@ -62,18 +62,18 @@ type chainedCustomizer struct {
 	customizers []customizer
 }
 
-func (c *chainedCustomizer) id() id {
-	return id("")
+func (c *chainedCustomizer) ID() ID {
+	return ID("")
 }
 
 func (c *chainedCustomizer) customize(environment *environment, resources *kubernetes.Collection) (bool, error) {
 	atLeastOne := false
 	for _, custom := range c.customizers {
-		if environment.isExplicitlyEnabled(custom.id()) || environment.isAutoDetectionMode(custom.id()) {
+		if environment.isEnabled(custom.ID()) || environment.isAutoDetectionMode(custom.ID()) {
 			if done, err := custom.customize(environment, resources); err != nil {
 				return false, err
-			} else if done && custom.id() != "" {
-				environment.ExecutedCustomizers = append(environment.ExecutedCustomizers, custom.id())
+			} else if done && custom.ID() != "" {
+				environment.ExecutedCustomizers = append(environment.ExecutedCustomizers, custom.ID())
 				atLeastOne = atLeastOne || done
 			}
 		}
diff --git a/pkg/trait/owner.go b/pkg/trait/owner.go
index 8603bd5..a47f3c9 100644
--- a/pkg/trait/owner.go
+++ b/pkg/trait/owner.go
@@ -17,15 +17,20 @@ limitations under the License.
 
 package trait
 
-import "github.com/apache/camel-k/pkg/util/kubernetes"
-import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+import (
+	"github.com/apache/camel-k/pkg/util/kubernetes"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
 
 // ownerTrait ensures that all created resources belong to the integration being created
 type ownerTrait struct {
+	Trait
 }
 
-func (*ownerTrait) id() id {
-	return id("owner")
+func newOwnerTrait() ownerTrait {
+	return ownerTrait{
+		Trait: NewTraitWithID("owner"),
+	}
 }
 
 func (*ownerTrait) customize(e *environment, resources *kubernetes.Collection) (bool, error) {
diff --git a/pkg/trait/route.go b/pkg/trait/route.go
index acf6984..725b1a9 100644
--- a/pkg/trait/route.go
+++ b/pkg/trait/route.go
@@ -26,10 +26,13 @@ import (
 )
 
 type routeTrait struct {
+	Trait
 }
 
-func (*routeTrait) id() id {
-	return id("route")
+func newRouteTrait() routeTrait {
+	return routeTrait{
+		Trait: NewTraitWithID("route"),
+	}
 }
 
 func (e *routeTrait) customize(environment *environment, resources *kubernetes.Collection) (bool, error) {
diff --git a/pkg/trait/service.go b/pkg/trait/service.go
index 9907466..2afe06c 100644
--- a/pkg/trait/service.go
+++ b/pkg/trait/service.go
@@ -35,18 +35,20 @@ var webComponents = map[string]bool{
 }
 
 type serviceTrait struct {
-}
+	Trait
 
-const (
-	serviceTraitPortKey = "port"
-)
+	Port int `property:"port"`
+}
 
-func (*serviceTrait) id() id {
-	return id("service")
+func newServiceTrait() serviceTrait {
+	return serviceTrait{
+		Trait: NewTraitWithID("service"),
+		Port:  8080,
+	}
 }
 
 func (s *serviceTrait) customize(environment *environment, resources *kubernetes.Collection) (bool, error) {
-	if environment.isAutoDetectionMode(s.id()) && !s.requiresService(environment) {
+	if environment.isAutoDetectionMode(s.ID()) && !s.requiresService(environment) {
 		return false, nil
 	}
 	svc, err := s.getServiceFor(environment)
@@ -58,8 +60,8 @@ func (s *serviceTrait) customize(environment *environment, resources *kubernetes
 }
 
 func (s *serviceTrait) getServiceFor(e *environment) (*corev1.Service, error) {
-	port, err := e.getIntConfigOr(s.id(), serviceTraitPortKey, 8080)
-	if err != nil {
+	t := newServiceTrait()
+	if _, err := e.getTrait(s.ID(), &t); err != nil {
 		return nil, err
 	}
 
@@ -81,7 +83,7 @@ func (s *serviceTrait) getServiceFor(e *environment) (*corev1.Service, error) {
 					Name:       "http",
 					Port:       80,
 					Protocol:   corev1.ProtocolTCP,
-					TargetPort: intstr.FromInt(port),
+					TargetPort: intstr.FromInt(t.Port),
 				},
 			},
 			Selector: map[string]string{
diff --git a/pkg/trait/trait.go b/pkg/trait/trait.go
index db3c56f..8b317ab 100644
--- a/pkg/trait/trait.go
+++ b/pkg/trait/trait.go
@@ -55,6 +55,6 @@ func newEnvironment(integration *v1alpha1.Integration) (*environment, error) {
 		Platform:            pl,
 		Context:             ctx,
 		Integration:         integration,
-		ExecutedCustomizers: make([]id, 0),
+		ExecutedCustomizers: make([]ID, 0),
 	}, nil
 }
diff --git a/pkg/trait/trait_test.go b/pkg/trait/trait_test.go
index 8427068..f447f84 100644
--- a/pkg/trait/trait_test.go
+++ b/pkg/trait/trait_test.go
@@ -18,6 +18,8 @@ limitations under the License.
 package trait
 
 import (
+	"testing"
+
 	"github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
 	"github.com/apache/camel-k/pkg/util/kubernetes"
 	routev1 "github.com/openshift/api/route/v1"
@@ -25,16 +27,15 @@ import (
 	appsv1 "k8s.io/api/apps/v1"
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"testing"
 )
 
-func TestOpenshiftTraits(t *testing.T) {
+func TestOpenShiftTraits(t *testing.T) {
 	env := createTestEnv(v1alpha1.IntegrationPlatformClusterOpenShift, "camel:core")
 	res := processTestEnv(t, env)
-	assert.Contains(t, env.ExecutedCustomizers, id("base"))
-	assert.NotContains(t, env.ExecutedCustomizers, id("service"))
-	assert.NotContains(t, env.ExecutedCustomizers, id("route"))
-	assert.Contains(t, env.ExecutedCustomizers, id("owner"))
+	assert.Contains(t, env.ExecutedCustomizers, ID("base"))
+	assert.NotContains(t, env.ExecutedCustomizers, ID("service"))
+	assert.NotContains(t, env.ExecutedCustomizers, ID("route"))
+	assert.Contains(t, env.ExecutedCustomizers, ID("owner"))
 	assert.NotNil(t, res.GetConfigMap(func(cm *corev1.ConfigMap) bool {
 		return cm.Name == "test"
 	}))
@@ -43,13 +44,13 @@ func TestOpenshiftTraits(t *testing.T) {
 	}))
 }
 
-func TestOpenshiftTraitsWithWeb(t *testing.T) {
+func TestOpenShiftTraitsWithWeb(t *testing.T) {
 	env := createTestEnv(v1alpha1.IntegrationPlatformClusterOpenShift, "camel:core", "camel:undertow")
 	res := processTestEnv(t, env)
-	assert.Contains(t, env.ExecutedCustomizers, id("base"))
-	assert.Contains(t, env.ExecutedCustomizers, id("service"))
-	assert.Contains(t, env.ExecutedCustomizers, id("route"))
-	assert.Contains(t, env.ExecutedCustomizers, id("owner"))
+	assert.Contains(t, env.ExecutedCustomizers, ID("base"))
+	assert.Contains(t, env.ExecutedCustomizers, ID("service"))
+	assert.Contains(t, env.ExecutedCustomizers, ID("route"))
+	assert.Contains(t, env.ExecutedCustomizers, ID("owner"))
 	assert.NotNil(t, res.GetConfigMap(func(cm *corev1.ConfigMap) bool {
 		return cm.Name == "test"
 	}))
@@ -64,7 +65,7 @@ func TestOpenshiftTraitsWithWeb(t *testing.T) {
 	}))
 }
 
-func TestOpenshiftTraitsWithWebAndConfig(t *testing.T) {
+func TestOpenShiftTraitsWithWebAndConfig(t *testing.T) {
 	env := createTestEnv(v1alpha1.IntegrationPlatformClusterOpenShift, "camel:core", "camel:undertow")
 	env.Integration.Spec.Traits = make(map[string]v1alpha1.IntegrationTraitSpec)
 	env.Integration.Spec.Traits["service"] = v1alpha1.IntegrationTraitSpec{
@@ -73,26 +74,25 @@ func TestOpenshiftTraitsWithWebAndConfig(t *testing.T) {
 		},
 	}
 	res := processTestEnv(t, env)
-	assert.Contains(t, env.ExecutedCustomizers, id("service"))
-	assert.Contains(t, env.ExecutedCustomizers, id("route"))
+	assert.Contains(t, env.ExecutedCustomizers, ID("service"))
+	assert.Contains(t, env.ExecutedCustomizers, ID("route"))
 	assert.NotNil(t, res.GetService(func(svc *corev1.Service) bool {
 		return svc.Name == "test" && svc.Spec.Ports[0].TargetPort.IntVal == int32(7071)
 	}))
 }
 
-func TestOpenshiftTraitsWithWebAndDisabledTrait(t *testing.T) {
-	falseBoolean := false
+func TestOpenShiftTraitsWithWebAndDisabledTrait(t *testing.T) {
 	env := createTestEnv(v1alpha1.IntegrationPlatformClusterOpenShift, "camel:core", "camel:undertow")
 	env.Integration.Spec.Traits = make(map[string]v1alpha1.IntegrationTraitSpec)
 	env.Integration.Spec.Traits["service"] = v1alpha1.IntegrationTraitSpec{
 		Configuration: map[string]string{
-			"port": "7071",
+			"enabled": "false",
+			"port":    "7071",
 		},
-		Enabled: &falseBoolean,
 	}
 	res := processTestEnv(t, env)
-	assert.NotContains(t, env.ExecutedCustomizers, id("service"))
-	assert.NotContains(t, env.ExecutedCustomizers, id("route")) // No route without service
+	assert.NotContains(t, env.ExecutedCustomizers, ID("service"))
+	assert.NotContains(t, env.ExecutedCustomizers, ID("route")) // No route without service
 	assert.Nil(t, res.GetService(func(svc *corev1.Service) bool {
 		return true
 	}))
@@ -101,10 +101,10 @@ func TestOpenshiftTraitsWithWebAndDisabledTrait(t *testing.T) {
 func TestKubernetesTraits(t *testing.T) {
 	env := createTestEnv(v1alpha1.IntegrationPlatformClusterKubernetes, "camel:core")
 	res := processTestEnv(t, env)
-	assert.Contains(t, env.ExecutedCustomizers, id("base"))
-	assert.NotContains(t, env.ExecutedCustomizers, id("service"))
-	assert.NotContains(t, env.ExecutedCustomizers, id("route"))
-	assert.Contains(t, env.ExecutedCustomizers, id("owner"))
+	assert.Contains(t, env.ExecutedCustomizers, ID("base"))
+	assert.NotContains(t, env.ExecutedCustomizers, ID("service"))
+	assert.NotContains(t, env.ExecutedCustomizers, ID("route"))
+	assert.Contains(t, env.ExecutedCustomizers, ID("owner"))
 	assert.NotNil(t, res.GetConfigMap(func(cm *corev1.ConfigMap) bool {
 		return cm.Name == "test"
 	}))
@@ -116,10 +116,10 @@ func TestKubernetesTraits(t *testing.T) {
 func TestKubernetesTraitsWithWeb(t *testing.T) {
 	env := createTestEnv(v1alpha1.IntegrationPlatformClusterKubernetes, "camel:core", "camel:servlet")
 	res := processTestEnv(t, env)
-	assert.Contains(t, env.ExecutedCustomizers, id("base"))
-	assert.Contains(t, env.ExecutedCustomizers, id("service"))
-	assert.NotContains(t, env.ExecutedCustomizers, id("route"))
-	assert.Contains(t, env.ExecutedCustomizers, id("owner"))
+	assert.Contains(t, env.ExecutedCustomizers, ID("base"))
+	assert.Contains(t, env.ExecutedCustomizers, ID("service"))
+	assert.NotContains(t, env.ExecutedCustomizers, ID("route"))
+	assert.Contains(t, env.ExecutedCustomizers, ID("owner"))
 	assert.NotNil(t, res.GetConfigMap(func(cm *corev1.ConfigMap) bool {
 		return cm.Name == "test"
 	}))
@@ -131,6 +131,25 @@ func TestKubernetesTraitsWithWeb(t *testing.T) {
 	}))
 }
 
+func TestTraitDecode(t *testing.T) {
+	env := createTestEnv(v1alpha1.IntegrationPlatformClusterOpenShift)
+	env.Integration.Spec.Traits = make(map[string]v1alpha1.IntegrationTraitSpec)
+	env.Integration.Spec.Traits["service"] = v1alpha1.IntegrationTraitSpec{
+		Configuration: map[string]string{
+			"enabled": "false",
+			"port":    "7071",
+		},
+	}
+
+	svc := newServiceTrait()
+	ok, err := env.getTrait(ID("service"), &svc)
+
+	assert.Nil(t, err)
+	assert.True(t, ok)
+	assert.Equal(t, 7071, svc.Port)
+	assert.Equal(t, true, svc.Enabled)
+}
+
 func processTestEnv(t *testing.T, env *environment) *kubernetes.Collection {
 	resources := kubernetes.NewCollection()
 	customizers := customizersFor(env)
@@ -156,6 +175,6 @@ func createTestEnv(cluster v1alpha1.IntegrationPlatformCluster, dependencies ...
 				Cluster: cluster,
 			},
 		},
-		ExecutedCustomizers: make([]id, 0),
+		ExecutedCustomizers: make([]ID, 0),
 	}
 }
diff --git a/pkg/trait/types.go b/pkg/trait/types.go
index 306f0b4..a825c6f 100644
--- a/pkg/trait/types.go
+++ b/pkg/trait/types.go
@@ -18,19 +18,55 @@ limitations under the License.
 package trait
 
 import (
+	"fmt"
+	"reflect"
+
+	"github.com/sirupsen/logrus"
+
 	"github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
 	"github.com/apache/camel-k/pkg/util/kubernetes"
 	"github.com/pkg/errors"
-	"strconv"
 )
 
+// Identifiable represent an identifiable type
+type Identifiable interface {
+	ID() ID
+}
+
 // ID uniquely identifies a trait
-type id string
+type ID string
+
+// Trait --
+type Trait struct {
+	Identifiable
+
+	id      ID
+	Enabled bool `property:"enabled"`
+}
+
+// ID returns the trait ID
+func (trait *Trait) ID() ID {
+	return trait.id
+}
+
+// NewTrait creates a new trait with defaults
+func NewTrait() Trait {
+	return Trait{
+		Enabled: true,
+	}
+}
+
+// NewTraitWithID creates a new trait with defaults and given ID
+func NewTraitWithID(traitID ID) Trait {
+	return Trait{
+		id:      traitID,
+		Enabled: true,
+	}
+}
 
 // A Customizer performs customization of the deployed objects
 type customizer interface {
-	// The Name of the customizer
-	id() id
+	Identifiable
 	// Customize executes the trait customization on the resources and return true if the resources have been changed
 	customize(environment *environment, resources *kubernetes.Collection) (bool, error)
 }
@@ -40,72 +76,50 @@ type environment struct {
 	Platform            *v1alpha1.IntegrationPlatform
 	Context             *v1alpha1.IntegrationContext
 	Integration         *v1alpha1.Integration
-	ExecutedCustomizers []id
+	ExecutedCustomizers []ID
 }
 
-func (e *environment) getTraitSpec(traitID id) *v1alpha1.IntegrationTraitSpec {
-	if e.Integration.Spec.Traits == nil {
-		return nil
-	}
-	if conf, ok := e.Integration.Spec.Traits[string(traitID)]; ok {
-		return &conf
-	}
-	return nil
-}
+func (e *environment) getTrait(traitID ID, target interface{}) (bool, error) {
+	if spec := e.getTraitSpec(traitID); spec != nil {
+		err := spec.Decode(&target)
+		if err != nil {
+			return false, errors.Wrap(err, fmt.Sprintf("unable to convert trait %s to the target struct %s", traitID, reflect.TypeOf(target).Name()))
+		}
 
-func (e *environment) isExplicitlyEnabled(traitID id) bool {
-	conf := e.getTraitSpec(traitID)
-	return conf != nil && conf.Enabled != nil && *conf.Enabled
-}
-
-func (e *environment) isExplicitlyDisabled(traitID id) bool {
-	conf := e.getTraitSpec(traitID)
-	return conf != nil && conf.Enabled != nil && !*conf.Enabled
-}
+		return true, nil
+	}
 
-func (e *environment) isAutoDetectionMode(traitID id) bool {
-	conf := e.getTraitSpec(traitID)
-	return conf == nil || conf.Enabled == nil
+	return false, nil
 }
 
-func (e *environment) getConfig(traitID id, key string) *string {
-	conf := e.getTraitSpec(traitID)
-	if conf == nil || conf.Configuration == nil {
+func (e *environment) getTraitSpec(traitID ID) *v1alpha1.IntegrationTraitSpec {
+	if e.Integration.Spec.Traits == nil {
 		return nil
 	}
-	if v, ok := conf.Configuration[key]; ok {
-		return &v
+	if conf, ok := e.Integration.Spec.Traits[string(traitID)]; ok {
+		return &conf
 	}
 	return nil
 }
 
-func (e *environment) getConfigOr(traitID id, key string, defaultValue string) string {
-	val := e.getConfig(traitID, key)
-	if val != nil {
-		return *val
+func (e *environment) isEnabled(traitID ID) bool {
+	t := NewTrait()
+	if _, err := e.getTrait(traitID, &t); err != nil {
+		logrus.Panic(err)
 	}
-	return defaultValue
-}
 
-func (e *environment) getIntConfig(traitID id, key string) (*int, error) {
-	val := e.getConfig(traitID, key)
-	if val == nil {
-		return nil, nil
-	}
-	intVal, err := strconv.Atoi(*val)
-	if err != nil {
-		return nil, errors.Wrap(err, "cannot extract a integer from property "+key+" with value "+*val)
-	}
-	return &intVal, nil
+	return t.Enabled
 }
 
-func (e *environment) getIntConfigOr(traitID id, key string, defaultValue int) (int, error) {
-	val, err := e.getIntConfig(traitID, key)
-	if err != nil {
-		return 0, err
+func (e *environment) isAutoDetectionMode(traitID ID) bool {
+	spec := e.getTraitSpec(traitID)
+	if spec == nil {
+		return true
 	}
-	if val != nil {
-		return *val, nil
+
+	if spec.Configuration == nil {
+		return true
 	}
-	return defaultValue, nil
+
+	return spec.Configuration["enabled"] == ""
 }
diff --git a/pkg/trait/util.go b/pkg/trait/util.go
index 0e5da55..577bdb7 100644
--- a/pkg/trait/util.go
+++ b/pkg/trait/util.go
@@ -19,11 +19,14 @@ package trait
 
 import (
 	"fmt"
+	"reflect"
+	"strings"
+
 	"github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
+	"github.com/fatih/structs"
 	"github.com/operator-framework/operator-sdk/pkg/sdk"
 	"github.com/pkg/errors"
 	"k8s.io/api/core/v1"
-	"strings"
 )
 
 // GetIntegrationContext retrieves the context set on the integration
@@ -115,3 +118,33 @@ func CombineConfigurationAsSlice(configurationType string, context *v1alpha1.Int
 
 	return keys
 }
+
+// ComputeTraitsProperties --
+func ComputeTraitsProperties() []string {
+	results := make([]string, 0)
+
+	processFields(structs.Fields(tService), func(name string) {
+		results = append(results, string(tService.ID())+"."+name)
+	})
+
+	return results
+}
+
+func processFields(fields []*structs.Field, processor func(string)) {
+	for _, f := range fields {
+		if f.IsEmbedded() && f.IsExported() && f.Kind() == reflect.Struct {
+			processFields(f.Fields(), processor)
+		}
+
+		if f.IsEmbedded() {
+			continue
+		}
+
+		property := f.Tag("property")
+
+		if property != "" {
+			items := strings.Split(property, ",")
+			processor(items[0])
+		}
+	}
+}
diff --git a/vendor/github.com/fatih/structs/LICENSE b/vendor/github.com/fatih/structs/LICENSE
new file mode 100644
index 0000000..34504e4
--- /dev/null
+++ b/vendor/github.com/fatih/structs/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2014 Fatih Arslan
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
\ No newline at end of file
diff --git a/vendor/github.com/fatih/structs/field.go b/vendor/github.com/fatih/structs/field.go
new file mode 100644
index 0000000..e697832
--- /dev/null
+++ b/vendor/github.com/fatih/structs/field.go
@@ -0,0 +1,141 @@
+package structs
+
+import (
+	"errors"
+	"fmt"
+	"reflect"
+)
+
+var (
+	errNotExported = errors.New("field is not exported")
+	errNotSettable = errors.New("field is not settable")
+)
+
+// Field represents a single struct field that encapsulates high level
+// functions around the field.
+type Field struct {
+	value      reflect.Value
+	field      reflect.StructField
+	defaultTag string
+}
+
+// Tag returns the value associated with key in the tag string. If there is no
+// such key in the tag, Tag returns the empty string.
+func (f *Field) Tag(key string) string {
+	return f.field.Tag.Get(key)
+}
+
+// Value returns the underlying value of the field. It panics if the field
+// is not exported.
+func (f *Field) Value() interface{} {
+	return f.value.Interface()
+}
+
+// IsEmbedded returns true if the given field is an anonymous field (embedded)
+func (f *Field) IsEmbedded() bool {
+	return f.field.Anonymous
+}
+
+// IsExported returns true if the given field is exported.
+func (f *Field) IsExported() bool {
+	return f.field.PkgPath == ""
+}
+
+// IsZero returns true if the given field is not initialized (has a zero value).
+// It panics if the field is not exported.
+func (f *Field) IsZero() bool {
+	zero := reflect.Zero(f.value.Type()).Interface()
+	current := f.Value()
+
+	return reflect.DeepEqual(current, zero)
+}
+
+// Name returns the name of the given field
+func (f *Field) Name() string {
+	return f.field.Name
+}
+
+// Kind returns the fields kind, such as "string", "map", "bool", etc ..
+func (f *Field) Kind() reflect.Kind {
+	return f.value.Kind()
+}
+
+// Set sets the field to given value v. It returns an error if the field is not
+// settable (not addressable or not exported) or if the given value's type
+// doesn't match the fields type.
+func (f *Field) Set(val interface{}) error {
+	// we can't set unexported fields, so be sure this field is exported
+	if !f.IsExported() {
+		return errNotExported
+	}
+
+	// do we get here? not sure...
+	if !f.value.CanSet() {
+		return errNotSettable
+	}
+
+	given := reflect.ValueOf(val)
+
+	if f.value.Kind() != given.Kind() {
+		return fmt.Errorf("wrong kind. got: %s want: %s", given.Kind(), f.value.Kind())
+	}
+
+	f.value.Set(given)
+	return nil
+}
+
+// Zero sets the field to its zero value. It returns an error if the field is not
+// settable (not addressable or not exported).
+func (f *Field) Zero() error {
+	zero := reflect.Zero(f.value.Type()).Interface()
+	return f.Set(zero)
+}
+
+// Fields returns a slice of Fields. This is particular handy to get the fields
+// of a nested struct . A struct tag with the content of "-" ignores the
+// checking of that particular field. Example:
+//
+//   // Field is ignored by this package.
+//   Field *http.Request `structs:"-"`
+//
+// It panics if field is not exported or if field's kind is not struct
+func (f *Field) Fields() []*Field {
+	return getFields(f.value, f.defaultTag)
+}
+
+// Field returns the field from a nested struct. It panics if the nested struct
+// is not exported or if the field was not found.
+func (f *Field) Field(name string) *Field {
+	field, ok := f.FieldOk(name)
+	if !ok {
+		panic("field not found")
+	}
+
+	return field
+}
+
+// FieldOk returns the field from a nested struct. The boolean returns whether
+// the field was found (true) or not (false).
+func (f *Field) FieldOk(name string) (*Field, bool) {
+	value := &f.value
+	// value must be settable so we need to make sure it holds the address of the
+	// variable and not a copy, so we can pass the pointer to strctVal instead of a
+	// copy (which is not assigned to any variable, hence not settable).
+	// see "https://blog.golang.org/laws-of-reflection#TOC_8."
+	if f.value.Kind() != reflect.Ptr {
+		a := f.value.Addr()
+		value = &a
+	}
+	v := strctVal(value.Interface())
+	t := v.Type()
+
+	field, ok := t.FieldByName(name)
+	if !ok {
+		return nil, false
+	}
+
+	return &Field{
+		field: field,
+		value: v.FieldByName(name),
+	}, true
+}
diff --git a/vendor/github.com/fatih/structs/structs.go b/vendor/github.com/fatih/structs/structs.go
new file mode 100644
index 0000000..3a87706
--- /dev/null
+++ b/vendor/github.com/fatih/structs/structs.go
@@ -0,0 +1,584 @@
+// Package structs contains various utilities functions to work with structs.
+package structs
+
+import (
+	"fmt"
+
+	"reflect"
+)
+
+var (
+	// DefaultTagName is the default tag name for struct fields which provides
+	// a more granular to tweak certain structs. Lookup the necessary functions
+	// for more info.
+	DefaultTagName = "structs" // struct's field default tag name
+)
+
+// Struct encapsulates a struct type to provide several high level functions
+// around the struct.
+type Struct struct {
+	raw     interface{}
+	value   reflect.Value
+	TagName string
+}
+
+// New returns a new *Struct with the struct s. It panics if the s's kind is
+// not struct.
+func New(s interface{}) *Struct {
+	return &Struct{
+		raw:     s,
+		value:   strctVal(s),
+		TagName: DefaultTagName,
+	}
+}
+
+// Map converts the given struct to a map[string]interface{}, where the keys
+// of the map are the field names and the values of the map the associated
+// values of the fields. The default key string is the struct field name but
+// can be changed in the struct field's tag value. The "structs" key in the
+// struct's field tag value is the key name. Example:
+//
+//   // Field appears in map as key "myName".
+//   Name string `structs:"myName"`
+//
+// A tag value with the content of "-" ignores that particular field. Example:
+//
+//   // Field is ignored by this package.
+//   Field bool `structs:"-"`
+//
+// A tag value with the content of "string" uses the stringer to get the value. Example:
+//
+//   // The value will be output of Animal's String() func.
+//   // Map will panic if Animal does not implement String().
+//   Field *Animal `structs:"field,string"`
+//
+// A tag value with the option of "flatten" used in a struct field is to flatten its fields
+// in the output map. Example:
+//
+//   // The FieldStruct's fields will be flattened into the output map.
+//   FieldStruct time.Time `structs:",flatten"`
+//
+// A tag value with the option of "omitnested" stops iterating further if the type
+// is a struct. Example:
+//
+//   // Field is not processed further by this package.
+//   Field time.Time     `structs:"myName,omitnested"`
+//   Field *http.Request `structs:",omitnested"`
+//
+// A tag value with the option of "omitempty" ignores that particular field if
+// the field value is empty. Example:
+//
+//   // Field appears in map as key "myName", but the field is
+//   // skipped if empty.
+//   Field string `structs:"myName,omitempty"`
+//
+//   // Field appears in map as key "Field" (the default), but
+//   // the field is skipped if empty.
+//   Field string `structs:",omitempty"`
+//
+// Note that only exported fields of a struct can be accessed, non exported
+// fields will be neglected.
+func (s *Struct) Map() map[string]interface{} {
+	out := make(map[string]interface{})
+	s.FillMap(out)
+	return out
+}
+
+// FillMap is the same as Map. Instead of returning the output, it fills the
+// given map.
+func (s *Struct) FillMap(out map[string]interface{}) {
+	if out == nil {
+		return
+	}
+
+	fields := s.structFields()
+
+	for _, field := range fields {
+		name := field.Name
+		val := s.value.FieldByName(name)
+		isSubStruct := false
+		var finalVal interface{}
+
+		tagName, tagOpts := parseTag(field.Tag.Get(s.TagName))
+		if tagName != "" {
+			name = tagName
+		}
+
+		// if the value is a zero value and the field is marked as omitempty do
+		// not include
+		if tagOpts.Has("omitempty") {
+			zero := reflect.Zero(val.Type()).Interface()
+			current := val.Interface()
+
+			if reflect.DeepEqual(current, zero) {
+				continue
+			}
+		}
+
+		if !tagOpts.Has("omitnested") {
+			finalVal = s.nested(val)
+
+			v := reflect.ValueOf(val.Interface())
+			if v.Kind() == reflect.Ptr {
+				v = v.Elem()
+			}
+
+			switch v.Kind() {
+			case reflect.Map, reflect.Struct:
+				isSubStruct = true
+			}
+		} else {
+			finalVal = val.Interface()
+		}
+
+		if tagOpts.Has("string") {
+			s, ok := val.Interface().(fmt.Stringer)
+			if ok {
+				out[name] = s.String()
+			}
+			continue
+		}
+
+		if isSubStruct && (tagOpts.Has("flatten")) {
+			for k := range finalVal.(map[string]interface{}) {
+				out[k] = finalVal.(map[string]interface{})[k]
+			}
+		} else {
+			out[name] = finalVal
+		}
+	}
+}
+
+// Values converts the given s struct's field values to a []interface{}.  A
+// struct tag with the content of "-" ignores the that particular field.
+// Example:
+//
+//   // Field is ignored by this package.
+//   Field int `structs:"-"`
+//
+// A value with the option of "omitnested" stops iterating further if the type
+// is a struct. Example:
+//
+//   // Fields is not processed further by this package.
+//   Field time.Time     `structs:",omitnested"`
+//   Field *http.Request `structs:",omitnested"`
+//
+// A tag value with the option of "omitempty" ignores that particular field and
+// is not added to the values if the field value is empty. Example:
+//
+//   // Field is skipped if empty
+//   Field string `structs:",omitempty"`
+//
+// Note that only exported fields of a struct can be accessed, non exported
+// fields  will be neglected.
+func (s *Struct) Values() []interface{} {
+	fields := s.structFields()
+
+	var t []interface{}
+
+	for _, field := range fields {
+		val := s.value.FieldByName(field.Name)
+
+		_, tagOpts := parseTag(field.Tag.Get(s.TagName))
+
+		// if the value is a zero value and the field is marked as omitempty do
+		// not include
+		if tagOpts.Has("omitempty") {
+			zero := reflect.Zero(val.Type()).Interface()
+			current := val.Interface()
+
+			if reflect.DeepEqual(current, zero) {
+				continue
+			}
+		}
+
+		if tagOpts.Has("string") {
+			s, ok := val.Interface().(fmt.Stringer)
+			if ok {
+				t = append(t, s.String())
+			}
+			continue
+		}
+
+		if IsStruct(val.Interface()) && !tagOpts.Has("omitnested") {
+			// look out for embedded structs, and convert them to a
+			// []interface{} to be added to the final values slice
+			t = append(t, Values(val.Interface())...)
+		} else {
+			t = append(t, val.Interface())
+		}
+	}
+
+	return t
+}
+
+// Fields returns a slice of Fields. A struct tag with the content of "-"
+// ignores the checking of that particular field. Example:
+//
+//   // Field is ignored by this package.
+//   Field bool `structs:"-"`
+//
+// It panics if s's kind is not struct.
+func (s *Struct) Fields() []*Field {
+	return getFields(s.value, s.TagName)
+}
+
+// Names returns a slice of field names. A struct tag with the content of "-"
+// ignores the checking of that particular field. Example:
+//
+//   // Field is ignored by this package.
+//   Field bool `structs:"-"`
+//
+// It panics if s's kind is not struct.
+func (s *Struct) Names() []string {
+	fields := getFields(s.value, s.TagName)
+
+	names := make([]string, len(fields))
+
+	for i, field := range fields {
+		names[i] = field.Name()
+	}
+
+	return names
+}
+
+func getFields(v reflect.Value, tagName string) []*Field {
+	if v.Kind() == reflect.Ptr {
+		v = v.Elem()
+	}
+
+	t := v.Type()
+
+	var fields []*Field
+
+	for i := 0; i < t.NumField(); i++ {
+		field := t.Field(i)
+
+		if tag := field.Tag.Get(tagName); tag == "-" {
+			continue
+		}
+
+		f := &Field{
+			field: field,
+			value: v.FieldByName(field.Name),
+		}
+
+		fields = append(fields, f)
+
+	}
+
+	return fields
+}
+
+// Field returns a new Field struct that provides several high level functions
+// around a single struct field entity. It panics if the field is not found.
+func (s *Struct) Field(name string) *Field {
+	f, ok := s.FieldOk(name)
+	if !ok {
+		panic("field not found")
+	}
+
+	return f
+}
+
+// FieldOk returns a new Field struct that provides several high level functions
+// around a single struct field entity. The boolean returns true if the field
+// was found.
+func (s *Struct) FieldOk(name string) (*Field, bool) {
+	t := s.value.Type()
+
+	field, ok := t.FieldByName(name)
+	if !ok {
+		return nil, false
+	}
+
+	return &Field{
+		field:      field,
+		value:      s.value.FieldByName(name),
+		defaultTag: s.TagName,
+	}, true
+}
+
+// IsZero returns true if all fields in a struct is a zero value (not
+// initialized) A struct tag with the content of "-" ignores the checking of
+// that particular field. Example:
+//
+//   // Field is ignored by this package.
+//   Field bool `structs:"-"`
+//
+// A value with the option of "omitnested" stops iterating further if the type
+// is a struct. Example:
+//
+//   // Field is not processed further by this package.
+//   Field time.Time     `structs:"myName,omitnested"`
+//   Field *http.Request `structs:",omitnested"`
+//
+// Note that only exported fields of a struct can be accessed, non exported
+// fields  will be neglected. It panics if s's kind is not struct.
+func (s *Struct) IsZero() bool {
+	fields := s.structFields()
+
+	for _, field := range fields {
+		val := s.value.FieldByName(field.Name)
+
+		_, tagOpts := parseTag(field.Tag.Get(s.TagName))
+
+		if IsStruct(val.Interface()) && !tagOpts.Has("omitnested") {
+			ok := IsZero(val.Interface())
+			if !ok {
+				return false
+			}
+
+			continue
+		}
+
+		// zero value of the given field, such as "" for string, 0 for int
+		zero := reflect.Zero(val.Type()).Interface()
+
+		//  current value of the given field
+		current := val.Interface()
+
+		if !reflect.DeepEqual(current, zero) {
+			return false
+		}
+	}
+
+	return true
+}
+
+// HasZero returns true if a field in a struct is not initialized (zero value).
+// A struct tag with the content of "-" ignores the checking of that particular
+// field. Example:
+//
+//   // Field is ignored by this package.
+//   Field bool `structs:"-"`
+//
+// A value with the option of "omitnested" stops iterating further if the type
+// is a struct. Example:
+//
+//   // Field is not processed further by this package.
+//   Field time.Time     `structs:"myName,omitnested"`
+//   Field *http.Request `structs:",omitnested"`
+//
+// Note that only exported fields of a struct can be accessed, non exported
+// fields  will be neglected. It panics if s's kind is not struct.
+func (s *Struct) HasZero() bool {
+	fields := s.structFields()
+
+	for _, field := range fields {
+		val := s.value.FieldByName(field.Name)
+
+		_, tagOpts := parseTag(field.Tag.Get(s.TagName))
+
+		if IsStruct(val.Interface()) && !tagOpts.Has("omitnested") {
+			ok := HasZero(val.Interface())
+			if ok {
+				return true
+			}
+
+			continue
+		}
+
+		// zero value of the given field, such as "" for string, 0 for int
+		zero := reflect.Zero(val.Type()).Interface()
+
+		//  current value of the given field
+		current := val.Interface()
+
+		if reflect.DeepEqual(current, zero) {
+			return true
+		}
+	}
+
+	return false
+}
+
+// Name returns the structs's type name within its package. For more info refer
+// to Name() function.
+func (s *Struct) Name() string {
+	return s.value.Type().Name()
+}
+
+// structFields returns the exported struct fields for a given s struct. This
+// is a convenient helper method to avoid duplicate code in some of the
+// functions.
+func (s *Struct) structFields() []reflect.StructField {
+	t := s.value.Type()
+
+	var f []reflect.StructField
+
+	for i := 0; i < t.NumField(); i++ {
+		field := t.Field(i)
+		// we can't access the value of unexported fields
+		if field.PkgPath != "" {
+			continue
+		}
+
+		// don't check if it's omitted
+		if tag := field.Tag.Get(s.TagName); tag == "-" {
+			continue
+		}
+
+		f = append(f, field)
+	}
+
+	return f
+}
+
+func strctVal(s interface{}) reflect.Value {
+	v := reflect.ValueOf(s)
+
+	// if pointer get the underlying element≤
+	for v.Kind() == reflect.Ptr {
+		v = v.Elem()
+	}
+
+	if v.Kind() != reflect.Struct {
+		panic("not struct")
+	}
+
+	return v
+}
+
+// Map converts the given struct to a map[string]interface{}. For more info
+// refer to Struct types Map() method. It panics if s's kind is not struct.
+func Map(s interface{}) map[string]interface{} {
+	return New(s).Map()
+}
+
+// FillMap is the same as Map. Instead of returning the output, it fills the
+// given map.
+func FillMap(s interface{}, out map[string]interface{}) {
+	New(s).FillMap(out)
+}
+
+// Values converts the given struct to a []interface{}. For more info refer to
+// Struct types Values() method.  It panics if s's kind is not struct.
+func Values(s interface{}) []interface{} {
+	return New(s).Values()
+}
+
+// Fields returns a slice of *Field. For more info refer to Struct types
+// Fields() method.  It panics if s's kind is not struct.
+func Fields(s interface{}) []*Field {
+	return New(s).Fields()
+}
+
+// Names returns a slice of field names. For more info refer to Struct types
+// Names() method.  It panics if s's kind is not struct.
+func Names(s interface{}) []string {
+	return New(s).Names()
+}
+
+// IsZero returns true if all fields is equal to a zero value. For more info
+// refer to Struct types IsZero() method.  It panics if s's kind is not struct.
+func IsZero(s interface{}) bool {
+	return New(s).IsZero()
+}
+
+// HasZero returns true if any field is equal to a zero value. For more info
+// refer to Struct types HasZero() method.  It panics if s's kind is not struct.
+func HasZero(s interface{}) bool {
+	return New(s).HasZero()
+}
+
+// IsStruct returns true if the given variable is a struct or a pointer to
+// struct.
+func IsStruct(s interface{}) bool {
+	v := reflect.ValueOf(s)
+	if v.Kind() == reflect.Ptr {
+		v = v.Elem()
+	}
+
+	// uninitialized zero value of a struct
+	if v.Kind() == reflect.Invalid {
+		return false
+	}
+
+	return v.Kind() == reflect.Struct
+}
+
+// Name returns the structs's type name within its package. It returns an
+// empty string for unnamed types. It panics if s's kind is not struct.
+func Name(s interface{}) string {
+	return New(s).Name()
+}
+
+// nested retrieves recursively all types for the given value and returns the
+// nested value.
+func (s *Struct) nested(val reflect.Value) interface{} {
+	var finalVal interface{}
+
+	v := reflect.ValueOf(val.Interface())
+	if v.Kind() == reflect.Ptr {
+		v = v.Elem()
+	}
+
+	switch v.Kind() {
+	case reflect.Struct:
+		n := New(val.Interface())
+		n.TagName = s.TagName
+		m := n.Map()
+
+		// do not add the converted value if there are no exported fields, ie:
+		// time.Time
+		if len(m) == 0 {
+			finalVal = val.Interface()
+		} else {
+			finalVal = m
+		}
+	case reflect.Map:
+		// get the element type of the map
+		mapElem := val.Type()
+		switch val.Type().Kind() {
+		case reflect.Ptr, reflect.Array, reflect.Map,
+			reflect.Slice, reflect.Chan:
+			mapElem = val.Type().Elem()
+			if mapElem.Kind() == reflect.Ptr {
+				mapElem = mapElem.Elem()
+			}
+		}
+
+		// only iterate over struct types, ie: map[string]StructType,
+		// map[string][]StructType,
+		if mapElem.Kind() == reflect.Struct ||
+			(mapElem.Kind() == reflect.Slice &&
+				mapElem.Elem().Kind() == reflect.Struct) {
+			m := make(map[string]interface{}, val.Len())
+			for _, k := range val.MapKeys() {
+				m[k.String()] = s.nested(val.MapIndex(k))
+			}
+			finalVal = m
+			break
+		}
+
+		// TODO(arslan): should this be optional?
+		finalVal = val.Interface()
+	case reflect.Slice, reflect.Array:
+		if val.Type().Kind() == reflect.Interface {
+			finalVal = val.Interface()
+			break
+		}
+
+		// TODO(arslan): should this be optional?
+		// do not iterate of non struct types, just pass the value. Ie: []int,
+		// []string, co... We only iterate further if it's a struct.
+		// i.e []foo or []*foo
+		if val.Type().Elem().Kind() != reflect.Struct &&
+			!(val.Type().Elem().Kind() == reflect.Ptr &&
+				val.Type().Elem().Elem().Kind() == reflect.Struct) {
+			finalVal = val.Interface()
+			break
+		}
+
+		slices := make([]interface{}, val.Len())
+		for x := 0; x < val.Len(); x++ {
+			slices[x] = s.nested(val.Index(x))
+		}
+		finalVal = slices
+	default:
+		finalVal = val.Interface()
+	}
+
+	return finalVal
+}
diff --git a/vendor/github.com/fatih/structs/tags.go b/vendor/github.com/fatih/structs/tags.go
new file mode 100644
index 0000000..136a31e
--- /dev/null
+++ b/vendor/github.com/fatih/structs/tags.go
@@ -0,0 +1,32 @@
+package structs
+
+import "strings"
+
+// tagOptions contains a slice of tag options
+type tagOptions []string
+
+// Has returns true if the given option is available in tagOptions
+func (t tagOptions) Has(opt string) bool {
+	for _, tagOpt := range t {
+		if tagOpt == opt {
+			return true
+		}
+	}
+
+	return false
+}
+
+// parseTag splits a struct field's tag into its name and a list of options
+// which comes after a name. A tag is in the form of: "name,option1,option2".
+// The name can be neglectected.
+func parseTag(tag string) (string, tagOptions) {
+	// tag is one of followings:
+	// ""
+	// "name"
+	// "name,opt"
+	// "name,opt,opt2"
+	// ",opt"
+
+	res := strings.Split(tag, ",")
+	return res[0], res[1:]
+}
diff --git a/vendor/github.com/mitchellh/mapstructure/LICENSE b/vendor/github.com/mitchellh/mapstructure/LICENSE
new file mode 100644
index 0000000..f9c841a
--- /dev/null
+++ b/vendor/github.com/mitchellh/mapstructure/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2013 Mitchell Hashimoto
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/github.com/mitchellh/mapstructure/decode_hooks.go b/vendor/github.com/mitchellh/mapstructure/decode_hooks.go
new file mode 100644
index 0000000..1f0abc6
--- /dev/null
+++ b/vendor/github.com/mitchellh/mapstructure/decode_hooks.go
@@ -0,0 +1,217 @@
+package mapstructure
+
+import (
+	"errors"
+	"fmt"
+	"net"
+	"reflect"
+	"strconv"
+	"strings"
+	"time"
+)
+
+// typedDecodeHook takes a raw DecodeHookFunc (an interface{}) and turns
+// it into the proper DecodeHookFunc type, such as DecodeHookFuncType.
+func typedDecodeHook(h DecodeHookFunc) DecodeHookFunc {
+	// Create variables here so we can reference them with the reflect pkg
+	var f1 DecodeHookFuncType
+	var f2 DecodeHookFuncKind
+
+	// Fill in the variables into this interface and the rest is done
+	// automatically using the reflect package.
+	potential := []interface{}{f1, f2}
+
+	v := reflect.ValueOf(h)
+	vt := v.Type()
+	for _, raw := range potential {
+		pt := reflect.ValueOf(raw).Type()
+		if vt.ConvertibleTo(pt) {
+			return v.Convert(pt).Interface()
+		}
+	}
+
+	return nil
+}
+
+// DecodeHookExec executes the given decode hook. This should be used
+// since it'll naturally degrade to the older backwards compatible DecodeHookFunc
+// that took reflect.Kind instead of reflect.Type.
+func DecodeHookExec(
+	raw DecodeHookFunc,
+	from reflect.Type, to reflect.Type,
+	data interface{}) (interface{}, error) {
+	switch f := typedDecodeHook(raw).(type) {
+	case DecodeHookFuncType:
+		return f(from, to, data)
+	case DecodeHookFuncKind:
+		return f(from.Kind(), to.Kind(), data)
+	default:
+		return nil, errors.New("invalid decode hook signature")
+	}
+}
+
+// ComposeDecodeHookFunc creates a single DecodeHookFunc that
+// automatically composes multiple DecodeHookFuncs.
+//
+// The composed funcs are called in order, with the result of the
+// previous transformation.
+func ComposeDecodeHookFunc(fs ...DecodeHookFunc) DecodeHookFunc {
+	return func(
+		f reflect.Type,
+		t reflect.Type,
+		data interface{}) (interface{}, error) {
+		var err error
+		for _, f1 := range fs {
+			data, err = DecodeHookExec(f1, f, t, data)
+			if err != nil {
+				return nil, err
+			}
+
+			// Modify the from kind to be correct with the new data
+			f = nil
+			if val := reflect.ValueOf(data); val.IsValid() {
+				f = val.Type()
+			}
+		}
+
+		return data, nil
+	}
+}
+
+// StringToSliceHookFunc returns a DecodeHookFunc that converts
+// string to []string by splitting on the given sep.
+func StringToSliceHookFunc(sep string) DecodeHookFunc {
+	return func(
+		f reflect.Kind,
+		t reflect.Kind,
+		data interface{}) (interface{}, error) {
+		if f != reflect.String || t != reflect.Slice {
+			return data, nil
+		}
+
+		raw := data.(string)
+		if raw == "" {
+			return []string{}, nil
+		}
+
+		return strings.Split(raw, sep), nil
+	}
+}
+
+// StringToTimeDurationHookFunc returns a DecodeHookFunc that converts
+// strings to time.Duration.
+func StringToTimeDurationHookFunc() DecodeHookFunc {
+	return func(
+		f reflect.Type,
+		t reflect.Type,
+		data interface{}) (interface{}, error) {
+		if f.Kind() != reflect.String {
+			return data, nil
+		}
+		if t != reflect.TypeOf(time.Duration(5)) {
+			return data, nil
+		}
+
+		// Convert it by parsing
+		return time.ParseDuration(data.(string))
+	}
+}
+
+// StringToIPHookFunc returns a DecodeHookFunc that converts
+// strings to net.IP
+func StringToIPHookFunc() DecodeHookFunc {
+	return func(
+		f reflect.Type,
+		t reflect.Type,
+		data interface{}) (interface{}, error) {
+		if f.Kind() != reflect.String {
+			return data, nil
+		}
+		if t != reflect.TypeOf(net.IP{}) {
+			return data, nil
+		}
+
+		// Convert it by parsing
+		ip := net.ParseIP(data.(string))
+		if ip == nil {
+			return net.IP{}, fmt.Errorf("failed parsing ip %v", data)
+		}
+
+		return ip, nil
+	}
+}
+
+// StringToIPNetHookFunc returns a DecodeHookFunc that converts
+// strings to net.IPNet
+func StringToIPNetHookFunc() DecodeHookFunc {
+	return func(
+		f reflect.Type,
+		t reflect.Type,
+		data interface{}) (interface{}, error) {
+		if f.Kind() != reflect.String {
+			return data, nil
+		}
+		if t != reflect.TypeOf(net.IPNet{}) {
+			return data, nil
+		}
+
+		// Convert it by parsing
+		_, net, err := net.ParseCIDR(data.(string))
+		return net, err
+	}
+}
+
+// StringToTimeHookFunc returns a DecodeHookFunc that converts
+// strings to time.Time.
+func StringToTimeHookFunc(layout string) DecodeHookFunc {
+	return func(
+		f reflect.Type,
+		t reflect.Type,
+		data interface{}) (interface{}, error) {
+		if f.Kind() != reflect.String {
+			return data, nil
+		}
+		if t != reflect.TypeOf(time.Time{}) {
+			return data, nil
+		}
+
+		// Convert it by parsing
+		return time.Parse(layout, data.(string))
+	}
+}
+
+// WeaklyTypedHook is a DecodeHookFunc which adds support for weak typing to
+// the decoder.
+//
+// Note that this is significantly different from the WeaklyTypedInput option
+// of the DecoderConfig.
+func WeaklyTypedHook(
+	f reflect.Kind,
+	t reflect.Kind,
+	data interface{}) (interface{}, error) {
+	dataVal := reflect.ValueOf(data)
+	switch t {
+	case reflect.String:
+		switch f {
+		case reflect.Bool:
+			if dataVal.Bool() {
+				return "1", nil
+			}
+			return "0", nil
+		case reflect.Float32:
+			return strconv.FormatFloat(dataVal.Float(), 'f', -1, 64), nil
+		case reflect.Int:
+			return strconv.FormatInt(dataVal.Int(), 10), nil
+		case reflect.Slice:
+			dataType := dataVal.Type()
+			elemKind := dataType.Elem().Kind()
+			if elemKind == reflect.Uint8 {
+				return string(dataVal.Interface().([]uint8)), nil
+			}
+		case reflect.Uint:
+			return strconv.FormatUint(dataVal.Uint(), 10), nil
+		}
+	}
+
+	return data, nil
+}
diff --git a/vendor/github.com/mitchellh/mapstructure/error.go b/vendor/github.com/mitchellh/mapstructure/error.go
new file mode 100644
index 0000000..47a99e5
--- /dev/null
+++ b/vendor/github.com/mitchellh/mapstructure/error.go
@@ -0,0 +1,50 @@
+package mapstructure
+
+import (
+	"errors"
+	"fmt"
+	"sort"
+	"strings"
+)
+
+// Error implements the error interface and can represents multiple
+// errors that occur in the course of a single decode.
+type Error struct {
+	Errors []string
+}
+
+func (e *Error) Error() string {
+	points := make([]string, len(e.Errors))
+	for i, err := range e.Errors {
+		points[i] = fmt.Sprintf("* %s", err)
+	}
+
+	sort.Strings(points)
+	return fmt.Sprintf(
+		"%d error(s) decoding:\n\n%s",
+		len(e.Errors), strings.Join(points, "\n"))
+}
+
+// WrappedErrors implements the errwrap.Wrapper interface to make this
+// return value more useful with the errwrap and go-multierror libraries.
+func (e *Error) WrappedErrors() []error {
+	if e == nil {
+		return nil
+	}
+
+	result := make([]error, len(e.Errors))
+	for i, e := range e.Errors {
+		result[i] = errors.New(e)
+	}
+
+	return result
+}
+
+func appendErrors(errors []string, err error) []string {
+	switch e := err.(type) {
+	case *Error:
+		return append(errors, e.Errors...)
+	default:
+		return append(errors, e.Error())
+	}
+}
diff --git a/vendor/github.com/mitchellh/mapstructure/mapstructure.go b/vendor/github.com/mitchellh/mapstructure/mapstructure.go
new file mode 100644
index 0000000..256ee63
--- /dev/null
+++ b/vendor/github.com/mitchellh/mapstructure/mapstructure.go
@@ -0,0 +1,1149 @@
+// Package mapstructure exposes functionality to convert an arbitrary
+// map[string]interface{} into a native Go structure.
+//
+// The Go structure can be arbitrarily complex, containing slices,
+// other structs, etc. and the decoder will properly decode nested
+// maps and so on into the proper structures in the native Go struct.
+// See the examples to see what the decoder is capable of.
+package mapstructure
+
+import (
+	"encoding/json"
+	"errors"
+	"fmt"
+	"reflect"
+	"sort"
+	"strconv"
+	"strings"
+)
+
+// DecodeHookFunc is the callback function that can be used for
+// data transformations. See "DecodeHook" in the DecoderConfig
+// struct.
+//
+// The type should be DecodeHookFuncType or DecodeHookFuncKind.
+// Either is accepted. Types are a superset of Kinds (Types can return
+// Kinds) and are generally a richer thing to use, but Kinds are simpler
+// if you only need those.
+//
+// The reason DecodeHookFunc is multi-typed is for backwards compatibility:
+// we started with Kinds and then realized Types were the better solution,
+// but have a promise to not break backwards compat so we now support
+// both.
+type DecodeHookFunc interface{}
+
+// DecodeHookFuncType is a DecodeHookFunc which has complete information about
+// the source and target types.
+type DecodeHookFuncType func(reflect.Type, reflect.Type, interface{}) (interface{}, error)
+
+// DecodeHookFuncKind is a DecodeHookFunc which knows only the Kinds of the
+// source and target types.
+type DecodeHookFuncKind func(reflect.Kind, reflect.Kind, interface{}) (interface{}, error)
+
+// DecoderConfig is the configuration that is used to create a new decoder
+// and allows customization of various aspects of decoding.
+type DecoderConfig struct {
+	// DecodeHook, if set, will be called before any decoding and any
+	// type conversion (if WeaklyTypedInput is on). This lets you modify
+	// the values before they're set down onto the resulting struct.
+	//
+	// If an error is returned, the entire decode will fail with that
+	// error.
+	DecodeHook DecodeHookFunc
+
+	// If ErrorUnused is true, then it is an error for there to exist
+	// keys in the original map that were unused in the decoding process
+	// (extra keys).
+	ErrorUnused bool
+
+	// ZeroFields, if set to true, will zero fields before writing them.
+	// For example, a map will be emptied before decoded values are put in
+	// it. If this is false, a map will be merged.
+	ZeroFields bool
+
+	// If WeaklyTypedInput is true, the decoder will make the following
+	// "weak" conversions:
+	//
+	//   - bools to string (true = "1", false = "0")
+	//   - numbers to string (base 10)
+	//   - bools to int/uint (true = 1, false = 0)
+	//   - strings to int/uint (base implied by prefix)
+	//   - int to bool (true if value != 0)
+	//   - string to bool (accepts: 1, t, T, TRUE, true, True, 0, f, F,
+	//     FALSE, false, False. Anything else is an error)
+	//   - empty array = empty map and vice versa
+	//   - negative numbers to overflowed uint values (base 10)
+	//   - slice of maps to a merged map
+	//   - single values are converted to slices if required. Each
+	//     element is weakly decoded. For example: "4" can become []int{4}
+	//     if the target type is an int slice.
+	//
+	WeaklyTypedInput bool
+
+	// Metadata is the struct that will contain extra metadata about
+	// the decoding. If this is nil, then no metadata will be tracked.
+	Metadata *Metadata
+
+	// Result is a pointer to the struct that will contain the decoded
+	// value.
+	Result interface{}
+
+	// The tag name that mapstructure reads for field names. This
+	// defaults to "mapstructure"
+	TagName string
+}
+
+// A Decoder takes a raw interface value and turns it into structured
+// data, keeping track of rich error information along the way in case
+// anything goes wrong. Unlike the basic top-level Decode method, you can
+// more finely control how the Decoder behaves using the DecoderConfig
+// structure. The top-level Decode method is just a convenience that sets
+// up the most basic Decoder.
+type Decoder struct {
+	config *DecoderConfig
+}
+
+// Metadata contains information about decoding a structure that
+// is tedious or difficult to get otherwise.
+type Metadata struct {
+	// Keys are the keys of the structure which were successfully decoded
+	Keys []string
+
+	// Unused is a slice of keys that were found in the raw value but
+	// weren't decoded since there was no matching field in the result interface
+	Unused []string
+}
+
+// Decode takes an input structure and uses reflection to translate it to
+// the output structure. output must be a pointer to a map or struct.
+func Decode(input interface{}, output interface{}) error {
+	config := &DecoderConfig{
+		Metadata: nil,
+		Result:   output,
+	}
+
+	decoder, err := NewDecoder(config)
+	if err != nil {
+		return err
+	}
+
+	return decoder.Decode(input)
+}
+
+// WeakDecode is the same as Decode but is shorthand to enable
+// WeaklyTypedInput. See DecoderConfig for more info.
+func WeakDecode(input, output interface{}) error {
+	config := &DecoderConfig{
+		Metadata:         nil,
+		Result:           output,
+		WeaklyTypedInput: true,
+	}
+
+	decoder, err := NewDecoder(config)
+	if err != nil {
+		return err
+	}
+
+	return decoder.Decode(input)
+}
+
+// DecodeMetadata is the same as Decode, but is shorthand to
+// enable metadata collection. See DecoderConfig for more info.
+func DecodeMetadata(input interface{}, output interface{}, metadata *Metadata) error {
+	config := &DecoderConfig{
+		Metadata: metadata,
+		Result:   output,
+	}
+
+	decoder, err := NewDecoder(config)
+	if err != nil {
+		return err
+	}
+
+	return decoder.Decode(input)
+}
+
+// WeakDecodeMetadata is the same as Decode, but is shorthand to
+// enable both WeaklyTypedInput and metadata collection. See
+// DecoderConfig for more info.
+func WeakDecodeMetadata(input interface{}, output interface{}, metadata *Metadata) error {
+	config := &DecoderConfig{
+		Metadata:         metadata,
+		Result:           output,
+		WeaklyTypedInput: true,
+	}
+
+	decoder, err := NewDecoder(config)
+	if err != nil {
+		return err
+	}
+
+	return decoder.Decode(input)
+}
+
+// NewDecoder returns a new decoder for the given configuration. Once
+// a decoder has been returned, the same configuration must not be used
+// again.
+func NewDecoder(config *DecoderConfig) (*Decoder, error) {
+	val := reflect.ValueOf(config.Result)
+	if val.Kind() != reflect.Ptr {
+		return nil, errors.New("result must be a pointer")
+	}
+
+	val = val.Elem()
+	if !val.CanAddr() {
+		return nil, errors.New("result must be addressable (a pointer)")
+	}
+
+	if config.Metadata != nil {
+		if config.Metadata.Keys == nil {
+			config.Metadata.Keys = make([]string, 0)
+		}
+
+		if config.Metadata.Unused == nil {
+			config.Metadata.Unused = make([]string, 0)
+		}
+	}
+
+	if config.TagName == "" {
+		config.TagName = "mapstructure"
+	}
+
+	result := &Decoder{
+		config: config,
+	}
+
+	return result, nil
+}
+
+// Decode decodes the given raw interface to the target pointer specified
+// by the configuration.
+func (d *Decoder) Decode(input interface{}) error {
+	return d.decode("", input, reflect.ValueOf(d.config.Result).Elem())
+}
+
+// Decodes an unknown data type into a specific reflection value.
+func (d *Decoder) decode(name string, input interface{}, outVal reflect.Value) error {
+	var inputVal reflect.Value
+	if input != nil {
+		inputVal = reflect.ValueOf(input)
+
+		// We need to check here if input is a typed nil. Typed nils won't
+		// match the "input == nil" below so we check that here.
+		if inputVal.Kind() == reflect.Ptr && inputVal.IsNil() {
+			input = nil
+		}
+	}
+
+	if input == nil {
+		// If the data is nil, then we don't set anything, unless ZeroFields is set
+		// to true.
+		if d.config.ZeroFields {
+			outVal.Set(reflect.Zero(outVal.Type()))
+
+			if d.config.Metadata != nil && name != "" {
+				d.config.Metadata.Keys = append(d.config.Metadata.Keys, name)
+			}
+		}
+		return nil
+	}
+
+	if !inputVal.IsValid() {
+		// If the input value is invalid, then we just set the value
+		// to be the zero value.
+		outVal.Set(reflect.Zero(outVal.Type()))
+		if d.config.Metadata != nil && name != "" {
+			d.config.Metadata.Keys = append(d.config.Metadata.Keys, name)
+		}
+		return nil
+	}
+
+	if d.config.DecodeHook != nil {
+		// We have a DecodeHook, so let's pre-process the input.
+		var err error
+		input, err = DecodeHookExec(
+			d.config.DecodeHook,
+			inputVal.Type(), outVal.Type(), input)
+		if err != nil {
+			return fmt.Errorf("error decoding '%s': %s", name, err)
+		}
+	}
+
+	var err error
+	outputKind := getKind(outVal)
+	switch outputKind {
+	case reflect.Bool:
+		err = d.decodeBool(name, input, outVal)
+	case reflect.Interface:
+		err = d.decodeBasic(name, input, outVal)
+	case reflect.String:
+		err = d.decodeString(name, input, outVal)
+	case reflect.Int:
+		err = d.decodeInt(name, input, outVal)
+	case reflect.Uint:
+		err = d.decodeUint(name, input, outVal)
+	case reflect.Float32:
+		err = d.decodeFloat(name, input, outVal)
+	case reflect.Struct:
+		err = d.decodeStruct(name, input, outVal)
+	case reflect.Map:
+		err = d.decodeMap(name, input, outVal)
+	case reflect.Ptr:
+		err = d.decodePtr(name, input, outVal)
+	case reflect.Slice:
+		err = d.decodeSlice(name, input, outVal)
+	case reflect.Array:
+		err = d.decodeArray(name, input, outVal)
+	case reflect.Func:
+		err = d.decodeFunc(name, input, outVal)
+	default:
+		// If we reached this point then we weren't able to decode it
+		return fmt.Errorf("%s: unsupported type: %s", name, outputKind)
+	}
+
+	// If we reached here, then we successfully decoded SOMETHING, so
+	// mark the key as used if we're tracking metainput.
+	if d.config.Metadata != nil && name != "" {
+		d.config.Metadata.Keys = append(d.config.Metadata.Keys, name)
+	}
+
+	return err
+}
+
+// This decodes a basic type (bool, int, string, etc.) and sets the
+// value to "data" of that type.
+func (d *Decoder) decodeBasic(name string, data interface{}, val reflect.Value) error {
+	if val.IsValid() && val.Elem().IsValid() {
+		return d.decode(name, data, val.Elem())
+	}
+
+	dataVal := reflect.ValueOf(data)
+
+	// If the input data is a pointer, and the assigned type is the dereference
+	// of that exact pointer, then indirect it so that we can assign it.
+	// Example: *string to string
+	if dataVal.Kind() == reflect.Ptr && dataVal.Type().Elem() == val.Type() {
+		dataVal = reflect.Indirect(dataVal)
+	}
+
+	if !dataVal.IsValid() {
+		dataVal = reflect.Zero(val.Type())
+	}
+
+	dataValType := dataVal.Type()
+	if !dataValType.AssignableTo(val.Type()) {
+		return fmt.Errorf(
+			"'%s' expected type '%s', got '%s'",
+			name, val.Type(), dataValType)
+	}
+
+	val.Set(dataVal)
+	return nil
+}
+
+func (d *Decoder) decodeString(name string, data interface{}, val reflect.Value) error {
+	dataVal := reflect.Indirect(reflect.ValueOf(data))
+	dataKind := getKind(dataVal)
+
+	converted := true
+	switch {
+	case dataKind == reflect.String:
+		val.SetString(dataVal.String())
+	case dataKind == reflect.Bool && d.config.WeaklyTypedInput:
+		if dataVal.Bool() {
+			val.SetString("1")
+		} else {
+			val.SetString("0")
+		}
+	case dataKind == reflect.Int && d.config.WeaklyTypedInput:
+		val.SetString(strconv.FormatInt(dataVal.Int(), 10))
+	case dataKind == reflect.Uint && d.config.WeaklyTypedInput:
+		val.SetString(strconv.FormatUint(dataVal.Uint(), 10))
+	case dataKind == reflect.Float32 && d.config.WeaklyTypedInput:
+		val.SetString(strconv.FormatFloat(dataVal.Float(), 'f', -1, 64))
+	case dataKind == reflect.Slice && d.config.WeaklyTypedInput,
+		dataKind == reflect.Array && d.config.WeaklyTypedInput:
+		dataType := dataVal.Type()
+		elemKind := dataType.Elem().Kind()
+		switch elemKind {
+		case reflect.Uint8:
+			var uints []uint8
+			if dataKind == reflect.Array {
+				uints = make([]uint8, dataVal.Len(), dataVal.Len())
+				for i := range uints {
+					uints[i] = dataVal.Index(i).Interface().(uint8)
+				}
+			} else {
+				uints = dataVal.Interface().([]uint8)
+			}
+			val.SetString(string(uints))
+		default:
+			converted = false
+		}
+	default:
+		converted = false
+	}
+
+	if !converted {
+		return fmt.Errorf(
+			"'%s' expected type '%s', got unconvertible type '%s'",
+			name, val.Type(), dataVal.Type())
+	}
+
+	return nil
+}
+
+func (d *Decoder) decodeInt(name string, data interface{}, val reflect.Value) error {
+	dataVal := reflect.Indirect(reflect.ValueOf(data))
+	dataKind := getKind(dataVal)
+	dataType := dataVal.Type()
+
+	switch {
+	case dataKind == reflect.Int:
+		val.SetInt(dataVal.Int())
+	case dataKind == reflect.Uint:
+		val.SetInt(int64(dataVal.Uint()))
+	case dataKind == reflect.Float32:
+		val.SetInt(int64(dataVal.Float()))
+	case dataKind == reflect.Bool && d.config.WeaklyTypedInput:
+		if dataVal.Bool() {
+			val.SetInt(1)
+		} else {
+			val.SetInt(0)
+		}
+	case dataKind == reflect.String && d.config.WeaklyTypedInput:
+		i, err := strconv.ParseInt(dataVal.String(), 0, val.Type().Bits())
+		if err == nil {
+			val.SetInt(i)
+		} else {
+			return fmt.Errorf("cannot parse '%s' as int: %s", name, err)
+		}
+	case dataType.PkgPath() == "encoding/json" && dataType.Name() == "Number":
+		jn := data.(json.Number)
+		i, err := jn.Int64()
+		if err != nil {
+			return fmt.Errorf(
+				"error decoding json.Number into %s: %s", name, err)
+		}
+		val.SetInt(i)
+	default:
+		return fmt.Errorf(
+			"'%s' expected type '%s', got unconvertible type '%s'",
+			name, val.Type(), dataVal.Type())
+	}
+
+	return nil
+}
+
+func (d *Decoder) decodeUint(name string, data interface{}, val reflect.Value) error {
+	dataVal := reflect.Indirect(reflect.ValueOf(data))
+	dataKind := getKind(dataVal)
+
+	switch {
+	case dataKind == reflect.Int:
+		i := dataVal.Int()
+		if i < 0 && !d.config.WeaklyTypedInput {
+			return fmt.Errorf("cannot parse '%s', %d overflows uint",
+				name, i)
+		}
+		val.SetUint(uint64(i))
+	case dataKind == reflect.Uint:
+		val.SetUint(dataVal.Uint())
+	case dataKind == reflect.Float32:
+		f := dataVal.Float()
+		if f < 0 && !d.config.WeaklyTypedInput {
+			return fmt.Errorf("cannot parse '%s', %f overflows uint",
+				name, f)
+		}
+		val.SetUint(uint64(f))
+	case dataKind == reflect.Bool && d.config.WeaklyTypedInput:
+		if dataVal.Bool() {
+			val.SetUint(1)
+		} else {
+			val.SetUint(0)
+		}
+	case dataKind == reflect.String && d.config.WeaklyTypedInput:
+		i, err := strconv.ParseUint(dataVal.String(), 0, val.Type().Bits())
+		if err == nil {
+			val.SetUint(i)
+		} else {
+			return fmt.Errorf("cannot parse '%s' as uint: %s", name, err)
+		}
+	default:
+		return fmt.Errorf(
+			"'%s' expected type '%s', got unconvertible type '%s'",
+			name, val.Type(), dataVal.Type())
+	}
+
+	return nil
+}
+
+func (d *Decoder) decodeBool(name string, data interface{}, val reflect.Value) error {
+	dataVal := reflect.Indirect(reflect.ValueOf(data))
+	dataKind := getKind(dataVal)
+
+	switch {
+	case dataKind == reflect.Bool:
+		val.SetBool(dataVal.Bool())
+	case dataKind == reflect.Int && d.config.WeaklyTypedInput:
+		val.SetBool(dataVal.Int() != 0)
+	case dataKind == reflect.Uint && d.config.WeaklyTypedInput:
+		val.SetBool(dataVal.Uint() != 0)
+	case dataKind == reflect.Float32 && d.config.WeaklyTypedInput:
+		val.SetBool(dataVal.Float() != 0)
+	case dataKind == reflect.String && d.config.WeaklyTypedInput:
+		b, err := strconv.ParseBool(dataVal.String())
+		if err == nil {
+			val.SetBool(b)
+		} else if dataVal.String() == "" {
+			val.SetBool(false)
+		} else {
+			return fmt.Errorf("cannot parse '%s' as bool: %s", name, err)
+		}
+	default:
+		return fmt.Errorf(
+			"'%s' expected type '%s', got unconvertible type '%s'",
+			name, val.Type(), dataVal.Type())
+	}
+
+	return nil
+}
+
+func (d *Decoder) decodeFloat(name string, data interface{}, val reflect.Value) error {
+	dataVal := reflect.Indirect(reflect.ValueOf(data))
+	dataKind := getKind(dataVal)
+	dataType := dataVal.Type()
+
+	switch {
+	case dataKind == reflect.Int:
+		val.SetFloat(float64(dataVal.Int()))
+	case dataKind == reflect.Uint:
+		val.SetFloat(float64(dataVal.Uint()))
+	case dataKind == reflect.Float32:
+		val.SetFloat(dataVal.Float())
+	case dataKind == reflect.Bool && d.config.WeaklyTypedInput:
+		if dataVal.Bool() {
+			val.SetFloat(1)
+		} else {
+			val.SetFloat(0)
+		}
+	case dataKind == reflect.String && d.config.WeaklyTypedInput:
+		f, err := strconv.ParseFloat(dataVal.String(), val.Type().Bits())
+		if err == nil {
+			val.SetFloat(f)
+		} else {
+			return fmt.Errorf("cannot parse '%s' as float: %s", name, err)
+		}
+	case dataType.PkgPath() == "encoding/json" && dataType.Name() == "Number":
+		jn := data.(json.Number)
+		i, err := jn.Float64()
+		if err != nil {
+			return fmt.Errorf(
+				"error decoding json.Number into %s: %s", name, err)
+		}
+		val.SetFloat(i)
+	default:
+		return fmt.Errorf(
+			"'%s' expected type '%s', got unconvertible type '%s'",
+			name, val.Type(), dataVal.Type())
+	}
+
+	return nil
+}
+
+func (d *Decoder) decodeMap(name string, data interface{}, val reflect.Value) error {
+	valType := val.Type()
+	valKeyType := valType.Key()
+	valElemType := valType.Elem()
+
+	// By default we overwrite keys in the current map
+	valMap := val
+
+	// If the map is nil or we're purposely zeroing fields, make a new map
+	if valMap.IsNil() || d.config.ZeroFields {
+		// Make a new map to hold our result
+		mapType := reflect.MapOf(valKeyType, valElemType)
+		valMap = reflect.MakeMap(mapType)
+	}
+
+	// Check input type and based on the input type jump to the proper func
+	dataVal := reflect.Indirect(reflect.ValueOf(data))
+	switch dataVal.Kind() {
+	case reflect.Map:
+		return d.decodeMapFromMap(name, dataVal, val, valMap)
+
+	case reflect.Struct:
+		return d.decodeMapFromStruct(name, dataVal, val, valMap)
+
+	case reflect.Array, reflect.Slice:
+		if d.config.WeaklyTypedInput {
+			return d.decodeMapFromSlice(name, dataVal, val, valMap)
+		}
+
+		fallthrough
+
+	default:
+		return fmt.Errorf("'%s' expected a map, got '%s'", name, dataVal.Kind())
+	}
+}
+
+func (d *Decoder) decodeMapFromSlice(name string, dataVal reflect.Value, val reflect.Value, valMap reflect.Value) error {
+	// Special case for BC reasons (covered by tests)
+	if dataVal.Len() == 0 {
+		val.Set(valMap)
+		return nil
+	}
+
+	for i := 0; i < dataVal.Len(); i++ {
+		err := d.decode(
+			fmt.Sprintf("%s[%d]", name, i),
+			dataVal.Index(i).Interface(), val)
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
+func (d *Decoder) decodeMapFromMap(name string, dataVal reflect.Value, val reflect.Value, valMap reflect.Value) error {
+	valType := val.Type()
+	valKeyType := valType.Key()
+	valElemType := valType.Elem()
+
+	// Accumulate errors
+	errors := make([]string, 0)
+
+	// If the input data is empty, then we just match what the input data is.
+	if dataVal.Len() == 0 {
+		if dataVal.IsNil() {
+			if !val.IsNil() {
+				val.Set(dataVal)
+			}
+		} else {
+			// Set to empty allocated value
+			val.Set(valMap)
+		}
+
+		return nil
+	}
+
+	for _, k := range dataVal.MapKeys() {
+		fieldName := fmt.Sprintf("%s[%s]", name, k)
+
+		// First decode the key into the proper type
+		currentKey := reflect.Indirect(reflect.New(valKeyType))
+		if err := d.decode(fieldName, k.Interface(), currentKey); err != nil {
+			errors = appendErrors(errors, err)
+			continue
+		}
+
+		// Next decode the data into the proper type
+		v := dataVal.MapIndex(k).Interface()
+		currentVal := reflect.Indirect(reflect.New(valElemType))
+		if err := d.decode(fieldName, v, currentVal); err != nil {
+			errors = appendErrors(errors, err)
+			continue
+		}
+
+		valMap.SetMapIndex(currentKey, currentVal)
+	}
+
+	// Set the built up map to the value
+	val.Set(valMap)
+
+	// If we had errors, return those
+	if len(errors) > 0 {
+		return &Error{errors}
+	}
+
+	return nil
+}
+
+func (d *Decoder) decodeMapFromStruct(name string, dataVal reflect.Value, val reflect.Value, valMap reflect.Value) error {
+	typ := dataVal.Type()
+	for i := 0; i < typ.NumField(); i++ {
+		// Get the StructField first since this is a cheap operation. If the
+		// field is unexported, then ignore it.
+		f := typ.Field(i)
+		if f.PkgPath != "" {
+			continue
+		}
+
+		// Next get the actual value of this field and verify it is assignable
+		// to the map value.
+		v := dataVal.Field(i)
+		if !v.Type().AssignableTo(valMap.Type().Elem()) {
+			return fmt.Errorf("cannot assign type '%s' to map value field of type '%s'", v.Type(), valMap.Type().Elem())
+		}
+
+		tagValue := f.Tag.Get(d.config.TagName)
+		tagParts := strings.Split(tagValue, ",")
+
+		// Determine the name of the key in the map
+		keyName := f.Name
+		if tagParts[0] != "" {
+			if tagParts[0] == "-" {
+				continue
+			}
+			keyName = tagParts[0]
+		}
+
+		// If "squash" is specified in the tag, we squash the field down.
+		squash := false
+		for _, tag := range tagParts[1:] {
+			if tag == "squash" {
+				squash = true
+				break
+			}
+		}
+		if squash && v.Kind() != reflect.Struct {
+			return fmt.Errorf("cannot squash non-struct type '%s'", v.Type())
+		}
+
+		switch v.Kind() {
+		// this is an embedded struct, so handle it differently
+		case reflect.Struct:
+			x := reflect.New(v.Type())
+			x.Elem().Set(v)
+
+			vType := valMap.Type()
+			vKeyType := vType.Key()
+			vElemType := vType.Elem()
+			mType := reflect.MapOf(vKeyType, vElemType)
+			vMap := reflect.MakeMap(mType)
+
+			err := d.decode(keyName, x.Interface(), vMap)
+			if err != nil {
+				return err
+			}
+
+			if squash {
+				for _, k := range vMap.MapKeys() {
+					valMap.SetMapIndex(k, vMap.MapIndex(k))
+				}
+			} else {
+				valMap.SetMapIndex(reflect.ValueOf(keyName), vMap)
+			}
+
+		default:
+			valMap.SetMapIndex(reflect.ValueOf(keyName), v)
+		}
+	}
+
+	if val.CanAddr() {
+		val.Set(valMap)
+	}
+
+	return nil
+}
+
+func (d *Decoder) decodePtr(name string, data interface{}, val reflect.Value) error {
+	// If the input data is nil, then we want to just set the output
+	// pointer to be nil as well.
+	isNil := data == nil
+	if !isNil {
+		switch v := reflect.Indirect(reflect.ValueOf(data)); v.Kind() {
+		case reflect.Chan,
+			reflect.Func,
+			reflect.Interface,
+			reflect.Map,
+			reflect.Ptr,
+			reflect.Slice:
+			isNil = v.IsNil()
+		}
+	}
+	if isNil {
+		if !val.IsNil() && val.CanSet() {
+			nilValue := reflect.New(val.Type()).Elem()
+			val.Set(nilValue)
+		}
+
+		return nil
+	}
+
+	// Create an element of the concrete (non pointer) type and decode
+	// into that. Then set the value of the pointer to this type.
+	valType := val.Type()
+	valElemType := valType.Elem()
+	if val.CanSet() {
+		realVal := val
+		if realVal.IsNil() || d.config.ZeroFields {
+			realVal = reflect.New(valElemType)
+		}
+
+		if err := d.decode(name, data, reflect.Indirect(realVal)); err != nil {
+			return err
+		}
+
+		val.Set(realVal)
+	} else {
+		if err := d.decode(name, data, reflect.Indirect(val)); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func (d *Decoder) decodeFunc(name string, data interface{}, val reflect.Value) error {
+	// Create an element of the concrete (non pointer) type and decode
+	// into that. Then set the value of the pointer to this type.
+	dataVal := reflect.Indirect(reflect.ValueOf(data))
+	if val.Type() != dataVal.Type() {
+		return fmt.Errorf(
+			"'%s' expected type '%s', got unconvertible type '%s'",
+			name, val.Type(), dataVal.Type())
+	}
+	val.Set(dataVal)
+	return nil
+}
+
+func (d *Decoder) decodeSlice(name string, data interface{}, val reflect.Value) error {
+	dataVal := reflect.Indirect(reflect.ValueOf(data))
+	dataValKind := dataVal.Kind()
+	valType := val.Type()
+	valElemType := valType.Elem()
+	sliceType := reflect.SliceOf(valElemType)
+
+	valSlice := val
+	if valSlice.IsNil() || d.config.ZeroFields {
+		if d.config.WeaklyTypedInput {
+			switch {
+			// Slice and array we use the normal logic
+			case dataValKind == reflect.Slice, dataValKind == reflect.Array:
+				break
+
+			// Empty maps turn into empty slices
+			case dataValKind == reflect.Map:
+				if dataVal.Len() == 0 {
+					val.Set(reflect.MakeSlice(sliceType, 0, 0))
+					return nil
+				}
+				// Create slice of maps of other sizes
+				return d.decodeSlice(name, []interface{}{data}, val)
+
+			case dataValKind == reflect.String && valElemType.Kind() == reflect.Uint8:
+				return d.decodeSlice(name, []byte(dataVal.String()), val)
+
+			// All other types we try to convert to the slice type
+			// and "lift" it into it. i.e. a string becomes a string slice.
+			default:
+				// Just re-try this function with data as a slice.
+				return d.decodeSlice(name, []interface{}{data}, val)
+			}
+		}
+
+		// Check input type
+		if dataValKind != reflect.Array && dataValKind != reflect.Slice {
+			return fmt.Errorf(
+				"'%s': source data must be an array or slice, got %s", name, dataValKind)
+
+		}
+
+		// If the input value is empty, then don't allocate since non-nil != nil
+		if dataVal.Len() == 0 {
+			return nil
+		}
+
+		// Make a new slice to hold our result, same size as the original data.
+		valSlice = reflect.MakeSlice(sliceType, dataVal.Len(), dataVal.Len())
+	}
+
+	// Accumulate any errors
+	errors := make([]string, 0)
+
+	for i := 0; i < dataVal.Len(); i++ {
+		currentData := dataVal.Index(i).Interface()
+		for valSlice.Len() <= i {
+			valSlice = reflect.Append(valSlice, reflect.Zero(valElemType))
+		}
+		currentField := valSlice.Index(i)
+
+		fieldName := fmt.Sprintf("%s[%d]", name, i)
+		if err := d.decode(fieldName, currentData, currentField); err != nil {
+			errors = appendErrors(errors, err)
+		}
+	}
+
+	// Finally, set the value to the slice we built up
+	val.Set(valSlice)
+
+	// If there were errors, we return those
+	if len(errors) > 0 {
+		return &Error{errors}
+	}
+
+	return nil
+}
+
+func (d *Decoder) decodeArray(name string, data interface{}, val reflect.Value) error {
+	dataVal := reflect.Indirect(reflect.ValueOf(data))
+	dataValKind := dataVal.Kind()
+	valType := val.Type()
+	valElemType := valType.Elem()
+	arrayType := reflect.ArrayOf(valType.Len(), valElemType)
+
+	valArray := val
+
+	if valArray.Interface() == reflect.Zero(valArray.Type()).Interface() || d.config.ZeroFields {
+		// Check input type
+		if dataValKind != reflect.Array && dataValKind != reflect.Slice {
+			if d.config.WeaklyTypedInput {
+				switch {
+				// Empty maps turn into empty arrays
+				case dataValKind == reflect.Map:
+					if dataVal.Len() == 0 {
+						val.Set(reflect.Zero(arrayType))
+						return nil
+					}
+
+				// All other types we try to convert to the array type
+				// and "lift" it into it. i.e. a string becomes a string array.
+				default:
+					// Just re-try this function with data as a slice.
+					return d.decodeArray(name, []interface{}{data}, val)
+				}
+			}
+
+			return fmt.Errorf(
+				"'%s': source data must be an array or slice, got %s", name, dataValKind)
+
+		}
+		if dataVal.Len() > arrayType.Len() {
+			return fmt.Errorf(
+				"'%s': expected source data to have length less or equal to %d, got %d", name, arrayType.Len(), dataVal.Len())
+
+		}
+
+		// Make a new array to hold our result, same size as the original data.
+		valArray = reflect.New(arrayType).Elem()
+	}
+
+	// Accumulate any errors
+	errors := make([]string, 0)
+
+	for i := 0; i < dataVal.Len(); i++ {
+		currentData := dataVal.Index(i).Interface()
+		currentField := valArray.Index(i)
+
+		fieldName := fmt.Sprintf("%s[%d]", name, i)
+		if err := d.decode(fieldName, currentData, currentField); err != nil {
+			errors = appendErrors(errors, err)
+		}
+	}
+
+	// Finally, set the value to the array we built up
+	val.Set(valArray)
+
+	// If there were errors, we return those
+	if len(errors) > 0 {
+		return &Error{errors}
+	}
+
+	return nil
+}
+
+func (d *Decoder) decodeStruct(name string, data interface{}, val reflect.Value) error {
+	dataVal := reflect.Indirect(reflect.ValueOf(data))
+
+	// If the type of the value to write to and the data match directly,
+	// then we just set it directly instead of recursing into the structure.
+	if dataVal.Type() == val.Type() {
+		val.Set(dataVal)
+		return nil
+	}
+
+	dataValKind := dataVal.Kind()
+	switch dataValKind {
+	case reflect.Map:
+		return d.decodeStructFromMap(name, dataVal, val)
+
+	case reflect.Struct:
+		// Not the most efficient way to do this but we can optimize later if
+		// we want to. To convert from struct to struct we go to map first
+		// as an intermediary.
+		m := make(map[string]interface{})
+		mval := reflect.Indirect(reflect.ValueOf(&m))
+		if err := d.decodeMapFromStruct(name, dataVal, mval, mval); err != nil {
+			return err
+		}
+
+		result := d.decodeStructFromMap(name, mval, val)
+		return result
+
+	default:
+		return fmt.Errorf("'%s' expected a map, got '%s'", name, dataVal.Kind())
+	}
+}
+
+func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) error {
+	dataValType := dataVal.Type()
+	if kind := dataValType.Key().Kind(); kind != reflect.String && kind != reflect.Interface {
+		return fmt.Errorf(
+			"'%s' needs a map with string keys, has '%s' keys",
+			name, dataValType.Key().Kind())
+	}
+
+	dataValKeys := make(map[reflect.Value]struct{})
+	dataValKeysUnused := make(map[interface{}]struct{})
+	for _, dataValKey := range dataVal.MapKeys() {
+		dataValKeys[dataValKey] = struct{}{}
+		dataValKeysUnused[dataValKey.Interface()] = struct{}{}
+	}
+
+	errors := make([]string, 0)
+
+	// This slice will keep track of all the structs we'll be decoding.
+	// There can be more than one struct if there are embedded structs
+	// that are squashed.
+	structs := make([]reflect.Value, 1, 5)
+	structs[0] = val
+
+	// Compile the list of all the fields that we're going to be decoding
+	// from all the structs.
+	type field struct {
+		field reflect.StructField
+		val   reflect.Value
+	}
+	fields := []field{}
+	for len(structs) > 0 {
+		structVal := structs[0]
+		structs = structs[1:]
+
+		structType := structVal.Type()
+
+		for i := 0; i < structType.NumField(); i++ {
+			fieldType := structType.Field(i)
+			fieldKind := fieldType.Type.Kind()
+
+			// If "squash" is specified in the tag, we squash the field down.
+			squash := false
+			tagParts := strings.Split(fieldType.Tag.Get(d.config.TagName), ",")
+			for _, tag := range tagParts[1:] {
+				if tag == "squash" {
+					squash = true
+					break
+				}
+			}
+
+			if squash {
+				if fieldKind != reflect.Struct {
+					errors = appendErrors(errors,
+						fmt.Errorf("%s: unsupported type for squash: %s", fieldType.Name, fieldKind))
+				} else {
+					structs = append(structs, structVal.FieldByName(fieldType.Name))
+				}
+				continue
+			}
+
+			// Normal struct field, store it away
+			fields = append(fields, field{fieldType, structVal.Field(i)})
+		}
+	}
+
+	// for fieldType, field := range fields {
+	for _, f := range fields {
+		field, fieldValue := f.field, f.val
+		fieldName := field.Name
+
+		tagValue := field.Tag.Get(d.config.TagName)
+		tagValue = strings.SplitN(tagValue, ",", 2)[0]
+		if tagValue != "" {
+			fieldName = tagValue
+		}
+
+		rawMapKey := reflect.ValueOf(fieldName)
+		rawMapVal := dataVal.MapIndex(rawMapKey)
+		if !rawMapVal.IsValid() {
+			// Do a slower search by iterating over each key and
+			// doing case-insensitive search.
+			for dataValKey := range dataValKeys {
+				mK, ok := dataValKey.Interface().(string)
+				if !ok {
+					// Not a string key
+					continue
+				}
+
+				if strings.EqualFold(mK, fieldName) {
+					rawMapKey = dataValKey
+					rawMapVal = dataVal.MapIndex(dataValKey)
+					break
+				}
+			}
+
+			if !rawMapVal.IsValid() {
+				// There was no matching key in the map for the value in
+				// the struct. Just ignore.
+				continue
+			}
+		}
+
+		// Delete the key we're using from the unused map so we stop tracking
+		delete(dataValKeysUnused, rawMapKey.Interface())
+
+		if !fieldValue.IsValid() {
+			// This should never happen
+			panic("field is not valid")
+		}
+
+		// If we can't set the field, then it is unexported or something,
+		// and we just continue onwards.
+		if !fieldValue.CanSet() {
+			continue
+		}
+
+		// If the name is empty string, then we're at the root, and we
+		// don't dot-join the fields.
+		if name != "" {
+			fieldName = fmt.Sprintf("%s.%s", name, fieldName)
+		}
+
+		if err := d.decode(fieldName, rawMapVal.Interface(), fieldValue); err != nil {
+			errors = appendErrors(errors, err)
+		}
+	}
+
+	if d.config.ErrorUnused && len(dataValKeysUnused) > 0 {
+		keys := make([]string, 0, len(dataValKeysUnused))
+		for rawKey := range dataValKeysUnused {
+			keys = append(keys, rawKey.(string))
+		}
+		sort.Strings(keys)
+
+		err := fmt.Errorf("'%s' has invalid keys: %s", name, strings.Join(keys, ", "))
+		errors = appendErrors(errors, err)
+	}
+
+	if len(errors) > 0 {
+		return &Error{errors}
+	}
+
+	// Add the unused keys to the list of unused keys if we're tracking metadata
+	if d.config.Metadata != nil {
+		for rawKey := range dataValKeysUnused {
+			key := rawKey.(string)
+			if name != "" {
+				key = fmt.Sprintf("%s.%s", name, key)
+			}
+
+			d.config.Metadata.Unused = append(d.config.Metadata.Unused, key)
+		}
+	}
+
+	return nil
+}
+
+func getKind(val reflect.Value) reflect.Kind {
+	kind := val.Kind()
+
+	switch {
+	case kind >= reflect.Int && kind <= reflect.Int64:
+		return reflect.Int
+	case kind >= reflect.Uint && kind <= reflect.Uint64:
+		return reflect.Uint
+	case kind >= reflect.Float32 && kind <= reflect.Float64:
+		return reflect.Float32
+	default:
+		return kind
+	}
+}


 

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
users@infra.apache.org


With regards,
Apache Git Services