Skip to content

Commit

Permalink
feat: support mapstructure
Browse files Browse the repository at this point in the history
  • Loading branch information
tmzane committed Nov 26, 2022
1 parent 9b5b3b2 commit 4efad77
Show file tree
Hide file tree
Showing 7 changed files with 97 additions and 14 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ The rational from [Uber Style Guide][1]:
* `encoding/xml` support
* `gopkg.in/yaml.v3` support
* `github.com/BurntSushi/toml` support
* `github.com/mitchellh/mapstructure` support

## 📦 Install

Expand All @@ -59,7 +60,6 @@ go vet -vettool=$(which musttag) ./...

## 📅 Roadmap

* Support `mapstructure`
* Support custom tags via config

[1]: https://github.com/uber-go/guide/blob/master/style.md#use-field-tags-in-marshaled-structs
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ go 1.18

require (
github.com/BurntSushi/toml v1.2.1
github.com/mitchellh/mapstructure v1.5.0
golang.org/x/tools v0.3.0
gopkg.in/yaml.v3 v3.0.1
)
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak=
github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA=
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
Expand Down
16 changes: 12 additions & 4 deletions musttag.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
// we need these dependencies for the tests in testdata to compile.
// `go mod tidy` will remove them from go.mod if we don't import them here.
_ "github.com/BurntSushi/toml"
_ "github.com/mitchellh/mapstructure"
_ "gopkg.in/yaml.v3"
)

Expand Down Expand Up @@ -82,10 +83,11 @@ func run(pass *analysis.Pass) (any, error) {
// look for and the expression that likely contains the struct to check.
func tagAndExpr(pass *analysis.Pass, call *ast.CallExpr) (string, ast.Expr, bool) {
const (
jsonTag = "json"
xmlTag = "xml"
yamlTag = "yaml"
tomlTag = "toml"
jsonTag = "json"
xmlTag = "xml"
yamlTag = "yaml"
tomlTag = "toml"
mapstructureTag = "mapstructure"
)

fn := typeutil.StaticCallee(pass.TypesInfo, call)
Expand Down Expand Up @@ -129,6 +131,12 @@ func tagAndExpr(pass *analysis.Pass, call *ast.CallExpr) (string, ast.Expr, bool
case "github.com/BurntSushi/toml.DecodeFS":
return tomlTag, call.Args[2], true

case "github.com/mitchellh/mapstructure.Decode",
"github.com/mitchellh/mapstructure.DecodeMetadata",
"github.com/mitchellh/mapstructure.WeakDecode",
"github.com/mitchellh/mapstructure.WeakDecodeMetadata":
return mapstructureTag, call.Args[1], true

default:
return "", nil, false
}
Expand Down
10 changes: 10 additions & 0 deletions testdata/src/examples/examples.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"encoding/xml"

"github.com/BurntSushi/toml"
"github.com/mitchellh/mapstructure"
"gopkg.in/yaml.v3"
)

Expand Down Expand Up @@ -45,3 +46,12 @@ func exampleTOML() {
toml.Decode("", &user)
toml.Unmarshal(nil, &user)
}

func exampleMS() {
var user struct { // want `\Qexported fields should be annotated with the "mapstructure" tag`
Name string
Email string `mapstructure:"email"`
}
mapstructure.Decode(nil, &user)
mapstructure.WeakDecode(nil, &user)
}
12 changes: 12 additions & 0 deletions testdata/src/github.com/mitchellh/mapstructure/mapstructure.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Package mapstructure provides stubs for github.com/mitchellh/mapstructure.
package mapstructure

// mapstructure.NewDecoder wants the struct pointer to be passed via
// mapstructure.DecoderConfig, that's why we do not support it for now.

type Metadata struct{}

func Decode(_, _ any) error { return nil }
func DecodeMetadata(_, _ any, _ *Metadata) error { return nil }
func WeakDecode(_, _ any) error { return nil }
func WeakDecodeMetadata(_, _ any, _ *Metadata) error { return nil }
68 changes: 59 additions & 9 deletions testdata/src/tests/tests.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"encoding/xml"

"github.com/BurntSushi/toml"
"github.com/mitchellh/mapstructure"
"gopkg.in/yaml.v3"
)

Expand Down Expand Up @@ -36,7 +37,12 @@ func namedType() {
`\Qtoml.DecodeFS`
`\Qtoml.DecodeFile`
`\Qtoml.Encoder.Encode`
`\Qtoml.Decoder.Decode` */
`\Qtoml.Decoder.Decode`
`\Qmapstructure.Decode`
`\Qmapstructure.DecodeMetadata`
`\Qmapstructure.WeakDecode`
`\Qmapstructure.WeakDecodeMetadata` */
NoTag int
}
var x X
Expand Down Expand Up @@ -66,6 +72,11 @@ func namedType() {
toml.DecodeFile("", &x)
toml.NewEncoder(nil).Encode(X{})
toml.NewDecoder(nil).Decode(&X{})

mapstructure.Decode(nil, &x)
mapstructure.DecodeMetadata(nil, &x, nil)
mapstructure.WeakDecode(nil, &X{})
mapstructure.WeakDecodeMetadata(nil, &X{}, nil)
}

func nestedNamedType() {
Expand Down Expand Up @@ -94,11 +105,16 @@ func nestedNamedType() {
`\Qtoml.DecodeFS`
`\Qtoml.DecodeFile`
`\Qtoml.Encoder.Encode`
`\Qtoml.Decoder.Decode` */
`\Qtoml.Decoder.Decode`
`\Qmapstructure.Decode`
`\Qmapstructure.DecodeMetadata`
`\Qmapstructure.WeakDecode`
`\Qmapstructure.WeakDecodeMetadata` */
NoTag int
}
type X struct {
Y Y `json:"y" xml:"y" yaml:"y" toml:"y"`
Y Y `json:"y" xml:"y" yaml:"y" toml:"y" mapstructure:"y"`
}
var x X

Expand Down Expand Up @@ -127,6 +143,11 @@ func nestedNamedType() {
toml.DecodeFile("", &x)
toml.NewEncoder(nil).Encode(X{})
toml.NewDecoder(nil).Decode(&X{})

mapstructure.Decode(nil, &x)
mapstructure.DecodeMetadata(nil, &x, nil)
mapstructure.WeakDecode(nil, &X{})
mapstructure.WeakDecodeMetadata(nil, &X{}, nil)
}

func anonymousType() {
Expand All @@ -145,7 +166,10 @@ func anonymousType() {
`\Qtoml.Unmarshal`
`\Qtoml.Decode`
`\Qtoml.DecodeFS`
`\Qtoml.DecodeFile` */
`\Qtoml.DecodeFile`
`\Qmapstructure.Decode`
`\Qmapstructure.DecodeMetadata` */
NoTag int
}

Expand Down Expand Up @@ -174,6 +198,11 @@ func anonymousType() {
toml.DecodeFile("", &x)
toml.NewEncoder(nil).Encode(struct{ NoTag int }{}) // want `\Qtoml.Encoder.Encode`
toml.NewDecoder(nil).Decode(&struct{ NoTag int }{}) // want `\Qtoml.Decoder.Decode`

mapstructure.Decode(nil, &x)
mapstructure.DecodeMetadata(nil, &x, nil)
mapstructure.WeakDecode(nil, &struct{ NoTag int }{}) // want `\Qmapstructure.WeakDecode`
mapstructure.WeakDecodeMetadata(nil, &struct{ NoTag int }{}, nil) // want `\Qmapstructure.WeakDecodeMetadata`
}

func nestedAnonymousType() {
Expand All @@ -192,8 +221,11 @@ func nestedAnonymousType() {
`\Qtoml.Unmarshal`
`\Qtoml.Decode`
`\Qtoml.DecodeFS`
`\Qtoml.DecodeFile` */
Y *struct{ NoTag int } `json:"y" xml:"y" yaml:"y" toml:"y"`
`\Qtoml.DecodeFile`
`\Qmapstructure.Decode`
`\Qmapstructure.DecodeMetadata` */
Y *struct{ NoTag int } `json:"y" xml:"y" yaml:"y" toml:"y" mapstructure:"y"`
}

json.Marshal(x)
Expand Down Expand Up @@ -221,14 +253,19 @@ func nestedAnonymousType() {
toml.DecodeFile("", &x)
toml.NewEncoder(nil).Encode(struct{ Y struct{ NoTag int } }{}) // want `\Qtoml.Encoder.Encode`
toml.NewDecoder(nil).Decode(&struct{ Y struct{ NoTag int } }{}) // want `\Qtoml.Decoder.Decode`

mapstructure.Decode(nil, &x)
mapstructure.DecodeMetadata(nil, &x, nil)
mapstructure.WeakDecode(nil, &struct{ Y struct{ NoTag int } }{}) // want `\Qmapstructure.WeakDecode`
mapstructure.WeakDecodeMetadata(nil, &struct{ Y struct{ NoTag int } }{}, nil) // want `\Qmapstructure.WeakDecodeMetadata`
}

// all good, nothing to report.
func typeWithAllTags() {
var x struct {
Y int `json:"y" xml:"y" yaml:"y" toml:"y"`
Z int `json:"z" xml:"z" yaml:"z" toml:"z"`
Nested struct{} `json:"nested" xml:"nested" yaml:"nested" toml:"nested"`
Y int `json:"y" xml:"y" yaml:"y" toml:"y" mapstructure:"y"`
Z int `json:"z" xml:"z" yaml:"z" toml:"z" mapstructure:"z"`
Nested struct{} `json:"nested" xml:"nested" yaml:"nested" toml:"nested" mapstructure:"nested"`
private int
}

Expand Down Expand Up @@ -257,6 +294,11 @@ func typeWithAllTags() {
toml.DecodeFile("", &x)
toml.NewEncoder(nil).Encode(x)
toml.NewDecoder(nil).Decode(&x)

mapstructure.Decode(nil, &x)
mapstructure.DecodeMetadata(nil, &x, nil)
mapstructure.WeakDecode(nil, &x)
mapstructure.WeakDecodeMetadata(nil, &x, nil)
}

// non-static calls should be ignored.
Expand All @@ -276,6 +318,9 @@ func nonStaticCalls() {

unmarshalTOML := toml.Unmarshal
unmarshalTOML(nil, &x)

decodeMS := mapstructure.Decode
decodeMS(nil, &x)
}

// non-struct argument calls should be ignored.
Expand Down Expand Up @@ -305,4 +350,9 @@ func nonStructArgument() {
toml.DecodeFile("", &[]int{})
toml.NewEncoder(nil).Encode(map[int]int{})
toml.NewDecoder(nil).Decode(&map[int]int{})

mapstructure.Decode(nil, &[]int{})
mapstructure.DecodeMetadata(nil, &[]int{}, nil)
mapstructure.WeakDecode(nil, &map[int]int{})
mapstructure.WeakDecodeMetadata(nil, &map[int]int{}, nil)
}

0 comments on commit 4efad77

Please sign in to comment.