diff --git a/common/viperutil/config_util.go b/common/viperutil/config_util.go index 192a506006f..55a1fbad5a2 100644 --- a/common/viperutil/config_util.go +++ b/common/viperutil/config_util.go @@ -18,9 +18,9 @@ import ( "strconv" "strings" + "github.com/go-viper/mapstructure/v2" "github.com/hyperledger/fabric-lib-go/bccsp/factory" "github.com/hyperledger/fabric-lib-go/common/flogging" - "github.com/mitchellh/mapstructure" "github.com/pkg/errors" "gopkg.in/yaml.v2" ) diff --git a/core/handlers/library/config.go b/core/handlers/library/config.go index 06b00a3c1be..2bd910b7a10 100644 --- a/core/handlers/library/config.go +++ b/core/handlers/library/config.go @@ -9,7 +9,7 @@ package library import ( "time" - "github.com/mitchellh/mapstructure" + "github.com/go-viper/mapstructure/v2" "github.com/spf13/viper" ) diff --git a/go.mod b/go.mod index b7c14fb7a4c..54085cb558c 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,7 @@ require ( github.com/davecgh/go-spew v1.1.1 github.com/fsouza/go-dockerclient v1.12.0 github.com/go-kit/kit v0.13.0 + github.com/go-viper/mapstructure/v2 v2.2.1 github.com/gorilla/handlers v1.5.2 github.com/gorilla/mux v1.8.1 github.com/hyperledger-labs/SmartBFT v0.0.0-20240916013553-852e5be5889b @@ -21,7 +22,6 @@ require ( github.com/hyperledger/fabric-protos-go-apiv2 v0.3.4 github.com/kr/pretty v0.3.1 github.com/miekg/pkcs11 v1.1.1 - github.com/mitchellh/mapstructure v1.5.0 github.com/onsi/ginkgo/v2 v2.20.2 github.com/onsi/gomega v1.34.2 github.com/pkg/errors v0.9.1 @@ -69,7 +69,6 @@ require ( github.com/go-logfmt/logfmt v0.5.1 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect - github.com/go-viper/mapstructure/v2 v2.0.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/golang/snappy v0.0.4 // indirect @@ -85,6 +84,7 @@ require ( github.com/mattn/go-isatty v0.0.19 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mmcloughlin/addchain v0.4.0 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect github.com/moby/patternmatcher v0.6.0 // indirect diff --git a/go.sum b/go.sum index 2b5b50cae56..74f44de537d 100644 --- a/go.sum +++ b/go.sum @@ -770,8 +770,8 @@ github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg78 github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= -github.com/go-viper/mapstructure/v2 v2.0.0 h1:dhn8MZ1gZ0mzeodTG3jt5Vj/o87xZKuNAprG2mQfMfc= -github.com/go-viper/mapstructure/v2 v2.0.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= +github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= diff --git a/internal/peer/common/common.go b/internal/peer/common/common.go index 7fe84d567ee..793af23c4a7 100644 --- a/internal/peer/common/common.go +++ b/internal/peer/common/common.go @@ -14,6 +14,7 @@ import ( "strings" "time" + "github.com/go-viper/mapstructure/v2" "github.com/hyperledger/fabric-lib-go/bccsp" "github.com/hyperledger/fabric-lib-go/bccsp/factory" "github.com/hyperledger/fabric-lib-go/common/flogging" @@ -26,7 +27,6 @@ import ( "github.com/hyperledger/fabric/msp" mspmgmt "github.com/hyperledger/fabric/msp/mgmt" "github.com/hyperledger/fabric/protoutil" - "github.com/mitchellh/mapstructure" "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/spf13/viper" diff --git a/internal/peer/node/start_test.go b/internal/peer/node/start_test.go index 6ed0459728f..bac1ffc243f 100644 --- a/internal/peer/node/start_test.go +++ b/internal/peer/node/start_test.go @@ -12,12 +12,12 @@ import ( "testing" "time" + "github.com/go-viper/mapstructure/v2" "github.com/hyperledger/fabric-protos-go-apiv2/common" "github.com/hyperledger/fabric/core/handlers/library" "github.com/hyperledger/fabric/core/testutil" "github.com/hyperledger/fabric/internal/peer/node/mock" msptesttools "github.com/hyperledger/fabric/msp/mgmt/testtools" - "github.com/mitchellh/mapstructure" . "github.com/onsi/gomega" "github.com/spf13/viper" "github.com/stretchr/testify/require" diff --git a/orderer/common/localconfig/config_test.go b/orderer/common/localconfig/config_test.go index 1efb226743f..e9854062c75 100644 --- a/orderer/common/localconfig/config_test.go +++ b/orderer/common/localconfig/config_test.go @@ -10,8 +10,8 @@ import ( "testing" "time" + "github.com/go-viper/mapstructure/v2" "github.com/hyperledger/fabric/core/config/configtest" - "github.com/mitchellh/mapstructure" "github.com/stretchr/testify/require" ) diff --git a/orderer/consensus/etcdraft/consenter.go b/orderer/consensus/etcdraft/consenter.go index 1141948d1bc..60c286831b5 100644 --- a/orderer/consensus/etcdraft/consenter.go +++ b/orderer/consensus/etcdraft/consenter.go @@ -12,6 +12,7 @@ import ( "time" "code.cloudfoundry.org/clock" + "github.com/go-viper/mapstructure/v2" "github.com/hyperledger/fabric-lib-go/bccsp" "github.com/hyperledger/fabric-lib-go/common/flogging" "github.com/hyperledger/fabric-lib-go/common/metrics" @@ -26,7 +27,6 @@ import ( "github.com/hyperledger/fabric/orderer/common/types" "github.com/hyperledger/fabric/orderer/consensus" "github.com/hyperledger/fabric/protoutil" - "github.com/mitchellh/mapstructure" "github.com/pkg/errors" "go.etcd.io/etcd/raft/v3" "google.golang.org/protobuf/proto" diff --git a/orderer/consensus/smartbft/consenter.go b/orderer/consensus/smartbft/consenter.go index d12e038beb9..d7c2dae5340 100644 --- a/orderer/consensus/smartbft/consenter.go +++ b/orderer/consensus/smartbft/consenter.go @@ -17,6 +17,7 @@ import ( "sync/atomic" "time" + "github.com/go-viper/mapstructure/v2" "github.com/hyperledger-labs/SmartBFT/pkg/api" "github.com/hyperledger-labs/SmartBFT/pkg/wal" "github.com/hyperledger/fabric-lib-go/bccsp" @@ -35,7 +36,6 @@ import ( "github.com/hyperledger/fabric/orderer/consensus" "github.com/hyperledger/fabric/orderer/consensus/smartbft/util" "github.com/hyperledger/fabric/protoutil" - "github.com/mitchellh/mapstructure" "github.com/pkg/errors" "go.uber.org/zap" "google.golang.org/protobuf/proto" diff --git a/vendor/github.com/go-viper/mapstructure/v2/decode_hooks.go b/vendor/github.com/go-viper/mapstructure/v2/decode_hooks.go index 24d82f07c36..1f3c69d4b8c 100644 --- a/vendor/github.com/go-viper/mapstructure/v2/decode_hooks.go +++ b/vendor/github.com/go-viper/mapstructure/v2/decode_hooks.go @@ -6,6 +6,7 @@ import ( "fmt" "net" "net/netip" + "net/url" "reflect" "strconv" "strings" @@ -36,6 +37,30 @@ func typedDecodeHook(h DecodeHookFunc) DecodeHookFunc { return nil } +// cachedDecodeHook takes a raw DecodeHookFunc (an interface{}) and turns +// it into a closure to be used directly +// if the type fails to convert we return a closure always erroring to keep the previous behaviour +func cachedDecodeHook(raw DecodeHookFunc) func(from reflect.Value, to reflect.Value) (interface{}, error) { + switch f := typedDecodeHook(raw).(type) { + case DecodeHookFuncType: + return func(from reflect.Value, to reflect.Value) (interface{}, error) { + return f(from.Type(), to.Type(), from.Interface()) + } + case DecodeHookFuncKind: + return func(from reflect.Value, to reflect.Value) (interface{}, error) { + return f(from.Kind(), to.Kind(), from.Interface()) + } + case DecodeHookFuncValue: + return func(from reflect.Value, to reflect.Value) (interface{}, error) { + return f(from, to) + } + default: + return func(from reflect.Value, to reflect.Value) (interface{}, error) { + return nil, errors.New("invalid decode hook signature") + } + } +} + // 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. @@ -61,13 +86,17 @@ func DecodeHookExec( // The composed funcs are called in order, with the result of the // previous transformation. func ComposeDecodeHookFunc(fs ...DecodeHookFunc) DecodeHookFunc { + cached := make([]func(from reflect.Value, to reflect.Value) (interface{}, error), 0, len(fs)) + for _, f := range fs { + cached = append(cached, cachedDecodeHook(f)) + } return func(f reflect.Value, t reflect.Value) (interface{}, error) { var err error data := f.Interface() newFrom := f - for _, f1 := range fs { - data, err = DecodeHookExec(f1, newFrom, t) + for _, c := range cached { + data, err = c(newFrom, t) if err != nil { return nil, err } @@ -81,13 +110,17 @@ func ComposeDecodeHookFunc(fs ...DecodeHookFunc) DecodeHookFunc { // OrComposeDecodeHookFunc executes all input hook functions until one of them returns no error. In that case its value is returned. // If all hooks return an error, OrComposeDecodeHookFunc returns an error concatenating all error messages. func OrComposeDecodeHookFunc(ff ...DecodeHookFunc) DecodeHookFunc { + cached := make([]func(from reflect.Value, to reflect.Value) (interface{}, error), 0, len(ff)) + for _, f := range ff { + cached = append(cached, cachedDecodeHook(f)) + } return func(a, b reflect.Value) (interface{}, error) { var allErrs string var out interface{} var err error - for _, f := range ff { - out, err = DecodeHookExec(f, a, b) + for _, c := range cached { + out, err = c(a, b) if err != nil { allErrs += err.Error() + "\n" continue @@ -144,6 +177,26 @@ func StringToTimeDurationHookFunc() DecodeHookFunc { } } +// StringToURLHookFunc returns a DecodeHookFunc that converts +// strings to *url.URL. +func StringToURLHookFunc() 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(&url.URL{}) { + return data, nil + } + + // Convert it by parsing + return url.Parse(data.(string)) + } +} + // StringToIPHookFunc returns a DecodeHookFunc that converts // strings to net.IP func StringToIPHookFunc() DecodeHookFunc { diff --git a/vendor/github.com/go-viper/mapstructure/v2/mapstructure.go b/vendor/github.com/go-viper/mapstructure/v2/mapstructure.go index 4b54fae0874..e77e63ba383 100644 --- a/vendor/github.com/go-viper/mapstructure/v2/mapstructure.go +++ b/vendor/github.com/go-viper/mapstructure/v2/mapstructure.go @@ -266,6 +266,10 @@ type DecoderConfig struct { // defaults to "mapstructure" TagName string + // The option of the value in the tag that indicates a field should + // be squashed. This defaults to "squash". + SquashTagOption string + // IgnoreUntaggedFields ignores all struct fields without explicit // TagName, comparable to `mapstructure:"-"` as default behaviour. IgnoreUntaggedFields bool @@ -274,6 +278,10 @@ type DecoderConfig struct { // field name or tag. Defaults to `strings.EqualFold`. This can be used // to implement case-sensitive tag values, support snake casing, etc. MatchName func(mapKey, fieldName string) bool + + // DecodeNil, if set to true, will cause the DecodeHook (if present) to run + // even if the input is nil. This can be used to provide default values. + DecodeNil bool } // A Decoder takes a raw interface value and turns it into structured @@ -283,7 +291,8 @@ type DecoderConfig struct { // structure. The top-level Decode method is just a convenience that sets // up the most basic Decoder. type Decoder struct { - config *DecoderConfig + config *DecoderConfig + cachedDecodeHook func(from reflect.Value, to reflect.Value) (interface{}, error) } // Metadata contains information about decoding a structure that @@ -401,6 +410,10 @@ func NewDecoder(config *DecoderConfig) (*Decoder, error) { config.TagName = "mapstructure" } + if config.SquashTagOption == "" { + config.SquashTagOption = "squash" + } + if config.MatchName == nil { config.MatchName = strings.EqualFold } @@ -408,6 +421,9 @@ func NewDecoder(config *DecoderConfig) (*Decoder, error) { result := &Decoder{ config: config, } + if config.DecodeHook != nil { + result.cachedDecodeHook = cachedDecodeHook(config.DecodeHook) + } return result, nil } @@ -426,19 +442,26 @@ func (d *Decoder) Decode(input interface{}) error { return err } +// isNil returns true if the input is nil or a typed nil pointer. +func isNil(input interface{}) bool { + if input == nil { + return true + } + val := reflect.ValueOf(input) + return val.Kind() == reflect.Ptr && val.IsNil() +} + // 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 - } + var ( + inputVal = reflect.ValueOf(input) + outputKind = getKind(outVal) + decodeNil = d.config.DecodeNil && d.cachedDecodeHook != nil + ) + if isNil(input) { + // Typed nils won't match the "input == nil" below, so reset input. + input = nil } - if input == nil { // If the data is nil, then we don't set anything, unless ZeroFields is set // to true. @@ -449,30 +472,46 @@ func (d *Decoder) decode(name string, input interface{}, outVal reflect.Value) e d.config.Metadata.Keys = append(d.config.Metadata.Keys, name) } } - return nil + if !decodeNil { + 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) + if !decodeNil { + // 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 + } + // Hooks need a valid inputVal, so reset it to zero value of outVal type. + switch outputKind { + case reflect.Struct, reflect.Map: + var mapVal map[string]interface{} + inputVal = reflect.ValueOf(mapVal) // create nil map pointer + case reflect.Slice, reflect.Array: + var sliceVal []interface{} + inputVal = reflect.ValueOf(sliceVal) // create nil slice pointer + default: + inputVal = reflect.Zero(outVal.Type()) } - return nil } - if d.config.DecodeHook != nil { + if d.cachedDecodeHook != nil { // We have a DecodeHook, so let's pre-process the input. var err error - input, err = DecodeHookExec(d.config.DecodeHook, inputVal, outVal) + input, err = d.cachedDecodeHook(inputVal, outVal) if err != nil { return fmt.Errorf("error decoding '%s': %w", name, err) } } + if isNil(input) { + return nil + } var err error - outputKind := getKind(outVal) addMetaKey := true switch outputKind { case reflect.Bool: @@ -753,8 +792,8 @@ func (d *Decoder) decodeBool(name string, data interface{}, val reflect.Value) e } default: return fmt.Errorf( - "'%s' expected type '%s', got unconvertible type '%s', value: '%v'", - name, val.Type(), dataVal.Type(), data) + "'%s' expected type '%s', got unconvertible type '%#v', value: '%#v'", + name, val, dataVal, data) } return nil @@ -973,7 +1012,7 @@ func (d *Decoder) decodeMapFromStruct(name string, dataVal reflect.Value, val re } // If "squash" is specified in the tag, we squash the field down. - squash = squash || strings.Index(tagValue[index+1:], "squash") != -1 + squash = squash || strings.Contains(tagValue[index+1:], d.config.SquashTagOption) if squash { // When squashing, the embedded type can be a pointer to a struct. if v.Kind() == reflect.Ptr && v.Elem().Kind() == reflect.Struct { @@ -1351,7 +1390,7 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e // We always parse the tags cause we're looking for other tags too tagParts := strings.Split(fieldType.Tag.Get(d.config.TagName), ",") for _, tag := range tagParts[1:] { - if tag == "squash" { + if tag == d.config.SquashTagOption { squash = true break } @@ -1363,10 +1402,15 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e } if squash { - if fieldVal.Kind() != reflect.Struct { - errs = append(errs, fmt.Errorf("%s: unsupported type for squash: %s", fieldType.Name, fieldVal.Kind())) - } else { + switch fieldVal.Kind() { + case reflect.Struct: structs = append(structs, fieldVal) + case reflect.Interface: + if !fieldVal.IsNil() { + structs = append(structs, fieldVal.Elem().Elem()) + } + default: + errs = append(errs, fmt.Errorf("%s: unsupported type for squash: %s", fieldType.Name, fieldVal.Kind())) } continue } diff --git a/vendor/modules.txt b/vendor/modules.txt index 6dfa5f99037..0172a8a6b3b 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -166,7 +166,7 @@ github.com/go-logr/logr/funcr # github.com/go-task/slim-sprig/v3 v3.0.0 ## explicit; go 1.20 github.com/go-task/slim-sprig/v3 -# github.com/go-viper/mapstructure/v2 v2.0.0 +# github.com/go-viper/mapstructure/v2 v2.2.1 ## explicit; go 1.18 github.com/go-viper/mapstructure/v2 github.com/go-viper/mapstructure/v2/internal/errors