-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmarshal.go
152 lines (144 loc) · 5.2 KB
/
marshal.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
package netstring
import (
"fmt"
"reflect"
)
// Marshal takes "message" as a struct or a pointer to a struct and encodes all exported
// fields with a "netstring" tag as a series of "keyed" netstrings. The complexity of
// "message" struct is significantly constrained to such an extent that it is henceforth
// called a "basic-struct".
//
// If there is no "netstring" tag the field is ignored. The reason the "netstring" tag is
// required is to supply a netstring key value which assists Unmarshal in locating the
// appropriate field on the receiving side. Marshal cannot be used to encode standard
// netstrings.
//
// The "eom" parameter is used to create an end-of-message sentinel "keyed" netstring. It
// can be any valid Key excepting netstring.NoKey. The sentinel follows the "basic-struct"
// netstrings with Encoder.EncodeBytes(eom).
//
// There are significant constraints as to what constitutes a valid "basic-struct". In
// large part this is because netstrings are ill-suited to support complex messages - use
// encoding/json or protobufs for those. Candidate fields (i.e. exported with a
// "netstring" tag) can only be one of the following basic go types: all ints and uints,
// all floats, strings and byte slices. That's it! Put another way, fields cannot be
// complex types such as maps, arrays, structs, pointers, etc. Any unsupported field type
// which has a "netstring" tag returns an error.
//
// The "netstring" tag value must be a valid netstring.Key and each "netstring" tag value
// must be unique otherwise an error is returned.
//
// Though fields are encoded in the order found in the struct via the "reflect" package,
// this sequence should not be relied on. Always use the "keyed" values to associate
// netstrings to fields.
//
// To assist go applications wishing to Unmarshal, it is good practice to use the first
// netstring as a message type so that the receiving side can select the corresponding
// struct to Unmarshal in to. Having to know the type before seeing the payload is a
// fundamental issue for all go Unmarshal functions such as json.Unmarshal in that they
// have to know ahead of time what type of struct the message contains; thus the message
// type has to effectively precede the message. At least with netstrings that's easy to
// arrange.
//
// Type and tag checking is performed while encoding so any error return probably leaves
// the output stream in an indeterminate state.
//
// An example:
//
// type record struct {
// Age int `netstring:"a"`
// Country string `netstring:"c"`
// TLD []byte `netstring:"t"`
// CountryCode []byte `netstring:"C"`
// Name string `netstring:"n"`
// Height uint16 // Ignored - no netstring tag
// dbKey int64 // Ignored - not exported
// }
// ...
// var bbuf bytes.Buffer
// enc := netstring.NewEncoder(&bbuf)
// enc.EncodeString('M', "r0") // Message type 'r', version zero
//
// r := record{22, "New Zealand", []byte{'n', 'z'}, []byte("64"), "Bob", 173, 42}
// enc.Marshal('Z', &r)
//
// fmt.Println(bbuf.String()) // "3:Mr0,3:a22,12:cNew Zealand,3:tnz,3:C64,4:nBob,1:Z,"
//
// Particularly note the preceding message type "r0" and the trailing end-of-message
// sentinel 'Z'.
func (enc *Encoder) Marshal(eom Key, message any) error {
k, e := eom.Assess()
if e != nil {
return e
}
if !k {
return ErrBadMarshalEOM
}
vo := reflect.ValueOf(message) // vo is a reflect.Value
if !vo.IsValid() {
return ErrBadMarshalValue
}
to := vo.Type()
kind := vo.Kind()
if kind == reflect.Pointer { // If it's a pointer, step into the struct
vo = vo.Elem()
to = vo.Type()
kind = vo.Kind()
}
if kind != reflect.Struct { // Only go one-level deep, so no **struct{}
return ErrBadMarshalValue
}
dupes := make(map[Key]string)
for ix := 0; ix < to.NumField(); ix++ {
sf := to.Field(ix) // Get StructField
if !sf.IsExported() {
continue
}
tag := sf.Tag.Get("netstring")
if len(tag) == 0 {
continue
}
if len(tag) != 1 {
return fmt.Errorf(errorPrefix+"%s tag '%s' (0x%X) is not a valid netstring.Key",
sf.Name, tag, tag)
}
key := Key(tag[0])
keyed, err := key.Assess()
if err != nil {
return err
}
if !keyed {
return fmt.Errorf(errorPrefix+"%s tag '%s' (0x%X) is not a valid netstring.Key",
sf.Name, tag, tag)
}
if n, ok := dupes[key]; ok {
return fmt.Errorf(errorPrefix+"Duplicate tag '%s' for '%s' and '%s'",
tag, sf.Name, n)
}
dupes[key] = sf.Name
kind := sf.Type.Kind()
vf := vo.Field(ix)
switch kind {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
enc.EncodeInt64(key, vf.Int())
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
enc.EncodeUint64(key, vf.Uint())
case reflect.Float32, reflect.Float64:
enc.EncodeFloat64(key, vf.Float())
case reflect.String:
enc.EncodeString(key, vf.String())
case reflect.Slice: // Is it a byte slice?
eKind := sf.Type.Elem().Kind()
if eKind == reflect.Uint8 {
enc.EncodeBytes(key, vf.Bytes())
} else {
return fmt.Errorf(errorPrefix+"%s type unsupported (%s of %s)",
sf.Name, kind, eKind)
}
default:
return fmt.Errorf(errorPrefix+"%s type unsupported (%s)", sf.Name, kind)
}
}
enc.EncodeBytes(eom)
return nil
}