Skip to content

Commit b5471de

Browse files
authored
Add binary marshal (#426)
Allow specifying directives for (external) types that support [BinaryMarshaler](https://pkg.go.dev/encoding#BinaryMarshaler) + [BinaryUnmarshaler](https://pkg.go.dev/encoding#BinaryUnmarshaler) and optionally [BinaryAppender](https://pkg.go.dev/encoding#BinaryAppender). * `//msgp:binmarshal pkg.Type pkg.Type2` will use BinaryMarshaler/BinaryUnmarshaler. * `//msgp:binappend pkg.Type pkg.Type2` will use BinaryAppender/BinaryUnmarshaler. Serialized data will be added an `bin` data, no special header. I guess we could also add the text interfaces. But the user would probably need to specify the storage type... * `//msgp:textmarshal pkg.Type pkg.Type2` will use TextMarshaler/TextUnmarshaler; data saved as bin (default) * `//msgp:textappend pkg.Type pkg.Type2` will use TextAppender/TextUnmarshaler; data saved as bin (default) * `//msgp:textmarshal as:string pkg.Type pkg.Type2` will use TextMarshaler/TextUnmarshaler; data saved as string. * `//msgp:textappend as:string pkg.Type pkg.Type2` will use TextAppender/TextUnmarshaler; data saved as string. # Example ``` // BinaryTestType implements encoding.BinaryMarshaler and encoding.BinaryUnmarshaler type BinaryTestType struct { Value string } //msgp:binmarshal BinaryTestType func (t *BinaryTestType) MarshalBinary() ([]byte, error) { return []byte(t.Value), nil } func (t *BinaryTestType) UnmarshalBinary(data []byte) error { t.Value = string(data) return nil } ``` # Implementation notes Sizes are not accurate. We assume header + 32 bytes.
1 parent 0146c03 commit b5471de

File tree

14 files changed

+1383
-3
lines changed

14 files changed

+1383
-3
lines changed

.github/workflows/test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ jobs:
2121
test:
2222
strategy:
2323
matrix:
24-
go-version: [1.23.x, 1.24.x, 1.25.x]
24+
go-version: [1.24.x, 1.25.x]
2525
os: [ubuntu-latest]
2626
runs-on: ${{ matrix.os }}
2727
timeout-minutes: 10

_generated/binary_marshaler.go

Lines changed: 289 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,289 @@
1+
package _generated
2+
3+
import (
4+
"encoding"
5+
"fmt"
6+
)
7+
8+
//go:generate msgp -v
9+
10+
// BinaryTestType implements encoding.BinaryMarshaler and encoding.BinaryUnmarshaler
11+
type BinaryTestType struct {
12+
Value string
13+
}
14+
15+
func (t *BinaryTestType) MarshalBinary() ([]byte, error) {
16+
return []byte(t.Value), nil
17+
}
18+
19+
func (t *BinaryTestType) UnmarshalBinary(data []byte) error {
20+
t.Value = string(data)
21+
return nil
22+
}
23+
24+
// Verify it implements the interfaces
25+
var _ encoding.BinaryMarshaler = (*BinaryTestType)(nil)
26+
var _ encoding.BinaryUnmarshaler = (*BinaryTestType)(nil)
27+
28+
//msgp:binmarshal BinaryTestType
29+
30+
// TextBinTestType implements encoding.TextMarshaler and encoding.TextUnmarshaler
31+
type TextBinTestType struct {
32+
Value string
33+
}
34+
35+
func (t *TextBinTestType) MarshalText() ([]byte, error) {
36+
return []byte(fmt.Sprintf("text:%s", t.Value)), nil
37+
}
38+
39+
func (t *TextBinTestType) UnmarshalText(data []byte) error {
40+
t.Value = string(data[5:]) // Remove "text:" prefix
41+
return nil
42+
}
43+
44+
var _ encoding.TextMarshaler = (*TextBinTestType)(nil)
45+
var _ encoding.TextUnmarshaler = (*TextBinTestType)(nil)
46+
47+
//msgp:textmarshal TextBinTestType
48+
49+
// TextStringTestType for testing as:string option
50+
type TextStringTestType struct {
51+
Value string
52+
}
53+
54+
func (t *TextStringTestType) MarshalText() ([]byte, error) {
55+
return []byte(fmt.Sprintf("stringtext:%s", t.Value)), nil
56+
}
57+
58+
func (t *TextStringTestType) UnmarshalText(data []byte) error {
59+
t.Value = string(data[11:]) // Remove "stringtext:" prefix
60+
return nil
61+
}
62+
63+
var _ encoding.TextMarshaler = (*TextStringTestType)(nil)
64+
var _ encoding.TextUnmarshaler = (*TextStringTestType)(nil)
65+
66+
//msgp:textmarshal as:string TextStringTestType
67+
68+
// TestStruct contains various combinations of marshaler types
69+
type TestStruct struct {
70+
// Direct values
71+
BinaryValue BinaryTestType `msg:"bin_val"`
72+
TextBinValue TextBinTestType `msg:"text_bin_val"`
73+
TextStringValue TextStringTestType `msg:"text_str_val"`
74+
75+
// Pointers
76+
BinaryPtr *BinaryTestType `msg:"bin_ptr"`
77+
TextBinPtr *TextBinTestType `msg:"text_bin_ptr,omitempty"`
78+
TextStringPtr *TextStringTestType `msg:"text_str_ptr,omitempty"`
79+
80+
// Slices
81+
BinarySlice []BinaryTestType `msg:"bin_slice"`
82+
TextBinSlice []TextBinTestType `msg:"text_bin_slice"`
83+
TextStringSlice []TextStringTestType `msg:"text_str_slice"`
84+
85+
// Arrays
86+
BinaryArray [3]BinaryTestType `msg:"bin_array"`
87+
TextBinArray [2]TextBinTestType `msg:"text_bin_array"`
88+
TextStringArray [4]TextStringTestType `msg:"text_str_array"`
89+
90+
// Maps with marshaler types as values
91+
BinaryMap map[string]BinaryTestType `msg:"bin_map"`
92+
TextBinMap map[string]TextBinTestType `msg:"text_bin_map"`
93+
TextStringMap map[string]TextStringTestType `msg:"text_str_map"`
94+
95+
// Nested pointers and slices
96+
NestedPtrSlice []*BinaryTestType `msg:"nested_ptr_slice"`
97+
SliceOfArrays [][2]TextBinTestType `msg:"slice_of_arrays"`
98+
MapOfSlices map[string][]BinaryTestType `msg:"map_of_slices"`
99+
}
100+
101+
//msgp:binmarshal ErrorTestType
102+
103+
// ErrorTestType for testing error conditions
104+
type ErrorTestType struct {
105+
ShouldError bool
106+
}
107+
108+
func (e *ErrorTestType) MarshalBinary() ([]byte, error) {
109+
if e.ShouldError {
110+
return nil, fmt.Errorf("intentional marshal error")
111+
}
112+
return []byte("ok"), nil
113+
}
114+
115+
func (e *ErrorTestType) UnmarshalBinary(data []byte) error {
116+
if string(data) == "error" {
117+
return fmt.Errorf("intentional unmarshal error")
118+
}
119+
e.ShouldError = false
120+
return nil
121+
}
122+
123+
// Test types for as:string positioning flexibility
124+
125+
// TestTextMarshalerStringMiddle for testing as:string in middle of type list
126+
type TestTextMarshalerStringMiddle struct {
127+
Value string
128+
}
129+
130+
func (t *TestTextMarshalerStringMiddle) MarshalText() ([]byte, error) {
131+
return []byte("middle:" + t.Value), nil
132+
}
133+
134+
func (t *TestTextMarshalerStringMiddle) UnmarshalText(text []byte) error {
135+
t.Value = string(text)
136+
return nil
137+
}
138+
139+
// TestTextMarshalerStringEnd for testing as:string at end of type list
140+
type TestTextMarshalerStringEnd struct {
141+
Value string
142+
}
143+
144+
func (t *TestTextMarshalerStringEnd) MarshalText() ([]byte, error) {
145+
return []byte("end:" + t.Value), nil
146+
}
147+
148+
func (t *TestTextMarshalerStringEnd) UnmarshalText(text []byte) error {
149+
t.Value = string(text)
150+
return nil
151+
}
152+
153+
// TestTextAppenderStringPos for testing textappend with as:string positioning
154+
type TestTextAppenderStringPos struct {
155+
Value string
156+
}
157+
158+
func (t *TestTextAppenderStringPos) AppendText(dst []byte) ([]byte, error) {
159+
return append(dst, []byte("append:"+t.Value)...), nil
160+
}
161+
162+
func (t *TestTextAppenderStringPos) UnmarshalText(text []byte) error {
163+
t.Value = string(text)
164+
return nil
165+
}
166+
167+
// BinaryAppenderType implements encoding.BinaryAppender (Go 1.22+)
168+
type BinaryAppenderType struct {
169+
Value string
170+
}
171+
172+
func (t *BinaryAppenderType) AppendBinary(dst []byte) ([]byte, error) {
173+
return append(dst, []byte("binappend:"+t.Value)...), nil
174+
}
175+
176+
func (t *BinaryAppenderType) UnmarshalBinary(data []byte) error {
177+
t.Value = string(data)
178+
return nil
179+
}
180+
181+
// TextAppenderBinType implements encoding.TextAppender (stored as binary)
182+
type TextAppenderBinType struct {
183+
Value string
184+
}
185+
186+
func (t *TextAppenderBinType) AppendText(dst []byte) ([]byte, error) {
187+
return append(dst, []byte("textbin:"+t.Value)...), nil
188+
}
189+
190+
func (t *TextAppenderBinType) UnmarshalText(text []byte) error {
191+
t.Value = string(text)
192+
return nil
193+
}
194+
195+
// ErrorBinaryAppenderType for testing error conditions with BinaryAppender
196+
type ErrorBinaryAppenderType struct {
197+
ShouldError bool
198+
Value string
199+
}
200+
201+
func (e *ErrorBinaryAppenderType) AppendBinary(dst []byte) ([]byte, error) {
202+
if e.ShouldError {
203+
return nil, fmt.Errorf("intentional append binary error")
204+
}
205+
return append(dst, []byte("ok")...), nil
206+
}
207+
208+
func (e *ErrorBinaryAppenderType) UnmarshalBinary(data []byte) error {
209+
if string(data) == "error" {
210+
return fmt.Errorf("intentional unmarshal binary error")
211+
}
212+
e.ShouldError = false
213+
e.Value = string(data)
214+
return nil
215+
}
216+
217+
// ErrorTextAppenderType for testing error conditions with TextAppender
218+
type ErrorTextAppenderType struct {
219+
ShouldError bool
220+
Value string
221+
}
222+
223+
func (e *ErrorTextAppenderType) AppendText(dst []byte) ([]byte, error) {
224+
if e.ShouldError {
225+
return nil, fmt.Errorf("intentional append text error")
226+
}
227+
return append(dst, []byte("ok")...), nil
228+
}
229+
230+
func (e *ErrorTextAppenderType) UnmarshalText(text []byte) error {
231+
if string(text) == "error" {
232+
return fmt.Errorf("intentional unmarshal text error")
233+
}
234+
e.ShouldError = false
235+
e.Value = string(text)
236+
return nil
237+
}
238+
239+
//msgp:binappend BinaryAppenderType ErrorBinaryAppenderType
240+
//msgp:textappend TextAppenderBinType ErrorTextAppenderType
241+
//msgp:textmarshal TestTextMarshalerStringMiddle as:string TestTextMarshalerStringEnd
242+
//msgp:textappend TestTextAppenderStringPos as:string
243+
244+
//msgp:binappend BinaryAppenderValue
245+
246+
// BinaryAppenderValue implements encoding.BinaryAppender (Go 1.22+)
247+
type BinaryAppenderValue struct {
248+
Value string `msg:"-"`
249+
}
250+
251+
func (t BinaryAppenderValue) AppendBinary(dst []byte) ([]byte, error) {
252+
return append(dst, []byte("binappend:"+t.Value)...), nil
253+
}
254+
255+
func (t *BinaryAppenderValue) UnmarshalBinary(data []byte) error {
256+
t.Value = string(data)
257+
return nil
258+
}
259+
260+
//msgp:textappend TestAppendTextString as:string
261+
262+
type TestAppendTextString struct {
263+
Value string `msg:"-"`
264+
}
265+
266+
func (t TestAppendTextString) AppendText(dst []byte) ([]byte, error) {
267+
return append(dst, []byte("append:"+t.Value)...), nil
268+
}
269+
270+
func (t *TestAppendTextString) UnmarshalText(text []byte) error {
271+
t.Value = string(text)
272+
return nil
273+
}
274+
275+
//msgp:textappend TextAppenderBinValue
276+
277+
// TextAppenderBinValue implements encoding.TextAppender (stored as binary)
278+
type TextAppenderBinValue struct {
279+
Value string `msg:"-"`
280+
}
281+
282+
func (t TextAppenderBinValue) AppendText(dst []byte) ([]byte, error) {
283+
return append(dst, []byte("textbin:"+t.Value)...), nil
284+
}
285+
286+
func (t *TextAppenderBinValue) UnmarshalText(text []byte) error {
287+
t.Value = string(text)
288+
return nil
289+
}

0 commit comments

Comments
 (0)