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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
test:
strategy:
matrix:
go-version: [1.24.x, 1.25.x]
go-version: [1.24.x, 1.25.x, 1.26.x]
os: [ubuntu-latest]
runs-on: ${{ matrix.os }}
timeout-minutes: 10
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/validate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
linters:
strategy:
matrix:
go-version: [1.25.x]
go-version: [1.26.x]
os: [ubuntu-latest]
runs-on: ${{ matrix.os }}
timeout-minutes: 10
Expand Down
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ ci: prepare
if [ `arch` == 'x86_64' ]; then \
sudo apt-get -y -q update; \
sudo apt-get -y -q install build-essential; \
wget -q https://github.com/tinygo-org/tinygo/releases/download/v0.39.0/tinygo_0.39.0_amd64.deb; \
sudo dpkg -i tinygo_0.39.0_amd64.deb; \
wget -q https://github.com/tinygo-org/tinygo/releases/download/v0.41.1/tinygo_0.41.1_amd64.deb; \
sudo dpkg -i tinygo_0.41.1_amd64.deb; \
export PATH=$$PATH:/usr/local/tinygo/bin; \
fi
go test -v ./... ./_generated
14 changes: 14 additions & 0 deletions _generated/custom_tags.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package _generated

//go:generate msgp -d "tags primary,fallback"

type CustomTags struct {
Field1 string `primary:"f1_primary"`
Field2 string `fallback:"f2_fallback"`
Field3 string `primary:"f3_primary" fallback:"f3_fallback_ignored"`
Field4 string `msg:"f4_msg"`
Field5 string `msgpack:"f5_msgpack"`
Field6 string `fallback:"f6_fallback" msg:"f6_msg_ignored"`
Field7 string `fallback:"f7_fallback" msgpack:"f7_msgpack_ignored"`
Field8 string `primary:"f8_primary" msg:"f8_msg_ignored" msgpack:"f8_msgpack_ignored"`
}
93 changes: 93 additions & 0 deletions _generated/custom_tags_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package _generated

import (
"bytes"
"encoding/json"
"reflect"
"testing"

"github.com/tinylib/msgp/msgp"
)

func TestCustomTags(t *testing.T) {
ts := CustomTags{
Field1: "v1",
Field2: "v2",
Field3: "v3",
Field4: "v4",
Field5: "v5",
Field6: "v6",
Field7: "v7",
Field8: "v8",
}
wantKeys := map[string]any{
"f1_primary": "v1",
"f2_fallback": "v2",
"f3_primary": "v3",
"f4_msg": "v4",
"f5_msgpack": "v5",
"f6_fallback": "v6",
"f7_fallback": "v7",
"f8_primary": "v8",
}

t.Run("EncodeDecode", func(t *testing.T) {
var b bytes.Buffer
if err := msgp.Encode(&b, &ts); err != nil {
t.Fatal(err)
}
var got CustomTags
if err := msgp.Decode(&b, &got); err != nil {
t.Fatal(err)
}
if got != ts {
t.Errorf("got %+v, want %+v", got, ts)
}
})

t.Run("MarshalUnmarshal", func(t *testing.T) {
buf, err := ts.MarshalMsg(nil)
if err != nil {
t.Fatal(err)
}
var got CustomTags
left, err := got.UnmarshalMsg(buf)
if err != nil {
t.Fatal(err)
}
if len(left) != 0 {
t.Errorf("%d bytes left after unmarshal", len(left))
}
if got != ts {
t.Errorf("got %+v, want %+v", got, ts)
}
})

t.Run("WireKeys", func(t *testing.T) {
buf, err := ts.MarshalMsg(nil)
if err != nil {
t.Fatal(err)
}
var jsonBuf bytes.Buffer
if _, err := msgp.UnmarshalAsJSON(&jsonBuf, buf); err != nil {
t.Fatal(err)
}
got := map[string]any{}
if err := json.Unmarshal(jsonBuf.Bytes(), &got); err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(got, wantKeys) {
t.Errorf("got %v, want %v", got, wantKeys)
}
})

t.Run("Msgsize", func(t *testing.T) {
buf, err := ts.MarshalMsg(nil)
if err != nil {
t.Fatal(err)
}
if est := ts.Msgsize(); est < len(buf) {
t.Errorf("Msgsize %d underestimates actual %d", est, len(buf))
}
})
}
23 changes: 20 additions & 3 deletions parse/directives.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ var directives = map[string]directive{
// and then add it to this list.
var earlyDirectives = map[string]directive{
"tag": tag,
"tags": tag,
"pointer": pointer,
"maps": maps,
}
Expand Down Expand Up @@ -237,12 +238,28 @@ func asvartuple(text []string, f *FileSet) error {
}

//msgp:tag {tagname}
//msgp:tags {tag1},{tag2},...
//
// The tag/tags directive accepts a comma-separated priority list; fields are
// read from the first tag that has a non-empty value, falling back to msg
// then msgpack if none of the listed tags match.
func tag(text []string, f *FileSet) error {
if len(text) != 2 {
if len(text) < 2 {
return nil
}
f.tagName = strings.TrimSpace(text[1])
infof("using field tag %q\n", f.tagName)
var names []string
for _, t := range text[1:] {
for n := range strings.SplitSeq(t, ",") {
if n = strings.TrimSpace(n); n != "" {
names = append(names, n)
}
}
}

f.tagNames = append(names, "msg", "msgpack") // Add defaults
if len(names) > 0 {
infof("using field tags %q\n", strings.Join(names, ","))
}
return nil
}

Expand Down
18 changes: 8 additions & 10 deletions parse/getast.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ type FileSet struct {
NoDuplicates bool // Reject duplicate keys for all types
NoDupTypes map[string]struct{} // Reject duplicate keys for specific types only

tagName string // tag to read field names from
pointerRcv bool // generate with pointer receivers.
tagNames []string // tags to read field names from, in priority order
pointerRcv bool // generate with pointer receivers.
}

// File parses a file at the relative path
Expand All @@ -65,6 +65,7 @@ func File(name string, unexported bool, directives []string) (*FileSet, error) {
Directives: append([]string{}, directives...),
ArrayLimit: math.MaxUint32,
MapLimit: math.MaxUint32,
tagNames: []string{"msg", "msgpack"},
}

fset := token.NewFileSet()
Expand Down Expand Up @@ -527,15 +528,12 @@ func (fs *FileSet) getField(f *ast.Field) []gen.StructField {
var extension, flatten bool
// parse tag; otherwise field name is field tag
if f.Tag != nil {
st := reflect.StructTag(strings.Trim(f.Tag.Value, "`"))
var body string
if fs.tagName != "" {
body = reflect.StructTag(strings.Trim(f.Tag.Value, "`")).Get(fs.tagName)
}
if body == "" {
body = reflect.StructTag(strings.Trim(f.Tag.Value, "`")).Get("msg")
}
if body == "" {
body = reflect.StructTag(strings.Trim(f.Tag.Value, "`")).Get("msgpack")
for _, name := range fs.tagNames {
if body = st.Get(name); body != "" {
break
}
}
tags := strings.Split(body, ",")
if len(tags) >= 2 {
Expand Down
Loading