-
Notifications
You must be signed in to change notification settings - Fork 5
/
marshal.go
253 lines (223 loc) · 9.37 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
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
package qs
import (
"errors"
"fmt"
"net/url"
"reflect"
)
// MarshalPresence is an enum that controls the marshaling of empty fields.
// A field is empty if it has its zero value or it is an empty container.
type MarshalPresence int
const (
// MPUnspecified is the zero value of MarshalPresence. In most cases
// you will use this implicitly by simply leaving the
// MarshalOptions.DefaultMarshalPresence field uninitialised which results
// in using the default MarshalPresence which is KeepEmpty.
MPUnspecified MarshalPresence = iota
// KeepEmpty marshals the values of empty fields into the marshal output.
KeepEmpty
// OmitEmpty doesn't marshal the values of empty fields into the marshal output.
OmitEmpty
)
func (v MarshalPresence) String() string {
switch v {
case MPUnspecified:
return "MPUnspecified"
case KeepEmpty:
// using lowercase to match the format used in struct tags
return "keepempty"
case OmitEmpty:
// using lowercase to match the format used in struct tags
return "omitempty"
default:
return fmt.Sprintf("MarshalPresence(%v)", int(v))
}
}
// MarshalOptions is used as a parameter by the NewMarshaler function.
type MarshalOptions struct {
// NameTransformer is used to transform struct field names into a query
// string names when they aren't set explicitly in the struct field tag.
// If this field is nil then NewMarshaler uses a default function that
// converts the CamelCase field names to snake_case which is popular
// with query strings.
NameTransformer NameTransformFunc
// ValuesMarshalerFactory is used by QSMarshaler to create ValuesMarshaler
// objects for specific types. If this field is nil then NewMarshaler uses
// a default builtin factory.
ValuesMarshalerFactory ValuesMarshalerFactory
// MarshalerFactory is used by QSMarshaler to create Marshaler
// objects for specific types. If this field is nil then NewMarshaler uses
// a default builtin factory.
MarshalerFactory MarshalerFactory
// DefaultMarshalPresence is used for the marshaling of struct fields that
// don't have an explicit MarshalPresence option set in their tags.
// This option is used for every item when you marshal a map[string]WhateverType
// instead of a struct because map items can't have a tag to override this.
DefaultMarshalPresence MarshalPresence
}
// DefaultMarshaler is the marshaler used by the Marshal, MarshalValues,
// CanMarshal and CanMarshalType functions.
var DefaultMarshaler = NewMarshaler(&MarshalOptions{})
// Marshal marshals an object into a query string. The type of the object must
// be supported by the ValuesMarshalerFactory of the marshaler. By default only
// structs and maps satisfy this condition without using a custom
// ValuesMarshalerFactory.
//
// If you use a map then the key type has to be string or a type with string as
// its underlying type and the map value type can be anything that can be used
// as a struct field for marshaling.
//
// A struct value is marshaled by adding its fields one-by-one to the query
// string. Only exported struct fields are marshaled. The struct field tag can
// contain qs package specific options in the following format:
//
// FieldName bool `qs:"[name][,option1[,option2[...]]]"`
//
// - If name is "-" then this field is skipped just like unexported fields.
// - If name is omitted then it defaults to the snake_case of the FieldName.
// The snake_case transformation can be replaced with a field name to query
// string name converter function by creating a custom marshaler.
// - For marshaling you can specify one of the keepempty and omitempty options.
// If none of them is specified then the keepempty option is the default but
// this default can be changed by using a custom marshaler object.
//
// Examples:
// FieldName bool `qs:"-"
// FieldName bool `qs:"name_in_query_str"
// FieldName bool `qs:"name_in_query_str,keepempty"
// FieldName bool `qs:",omitempty"
//
// Anonymous struct fields are marshaled as if their inner exported fields were
// fields in the outer struct.
//
// Pointer fields are omitted when they are nil otherwise they are marshaled as
// the value pointed to.
//
// Items of array and slice fields are encoded by adding multiple items with the
// same key to the query string. E.g.: arr=[]byte{1, 2} is encoded as "arr=1&arr=2".
// You can change this behavior by creating a custom marshaler with its custom
// MarshalerFactory that provides your custom marshal logic for the given slice
// and/or array types.
//
// When a field is marshaled with the omitempty option then the field is skipped
// if it has the zero value of its type.
// A field is marshaled with the omitempty option when its tag explicitly
// specifies omitempty or when the tag contains neither omitempty nor keepempty
// but the marshaler's default marshal option is omitempty.
func Marshal(i interface{}) (string, error) {
return DefaultMarshaler.Marshal(i)
}
// MarshalValues is the same as Marshal but returns a url.Values instead of a
// query string.
func MarshalValues(i interface{}) (url.Values, error) {
return DefaultMarshaler.MarshalValues(i)
}
// CheckMarshal returns an error if the type of the given object can't be
// marshaled into a url.Values or query string. By default only maps and structs
// can be marshaled into query strings given that all of their fields or values
// can be marshaled to []string (which is the value type of the url.Values map).
//
// It performs the check on the type of the object without traversing or
// marshaling the object.
func CheckMarshal(i interface{}) error {
return DefaultMarshaler.CheckMarshal(i)
}
// CheckMarshalType returns an error if the given type can't be marshaled
// into a url.Values or query string. By default only maps and structs
// can be marshaled int query strings given that all of their fields or values
// can be marshaled to []string (which is the value type of the url.Values map).
func CheckMarshalType(t reflect.Type) error {
return DefaultMarshaler.CheckMarshalType(t)
}
// QSMarshaler objects can be created by calling NewMarshaler and they can be
// used to marshal structs or maps into query strings or url.Values.
type QSMarshaler struct {
opts *MarshalOptions
}
// NewMarshaler returns a new QSMarshaler object.
func NewMarshaler(opts *MarshalOptions) *QSMarshaler {
return &QSMarshaler{
opts: prepareMarshalOptions(*opts),
}
}
// Marshal marshals a given object into a query string.
// See the documentation of the global Marshal func.
func (p *QSMarshaler) Marshal(i interface{}) (string, error) {
values, err := p.MarshalValues(i)
if err != nil {
return "", err
}
return values.Encode(), nil
}
// MarshalValues marshals a given object into a url.Values.
// See the documentation of the global MarshalValues func.
func (p *QSMarshaler) MarshalValues(i interface{}) (url.Values, error) {
v := reflect.ValueOf(i)
if !v.IsValid() {
return nil, errors.New("received an empty interface")
}
if v.Kind() == reflect.Ptr {
if v.IsNil() {
return nil, fmt.Errorf("nil pointer of type %T", i)
}
v = v.Elem()
}
vum, err := p.opts.ValuesMarshalerFactory.ValuesMarshaler(v.Type(), p.opts)
if err != nil {
return nil, err
}
return vum.MarshalValues(v, p.opts)
}
// CheckMarshal check whether the type of the given object supports
// marshaling into query strings.
// See the documentation of the global CheckMarshal func.
func (p *QSMarshaler) CheckMarshal(i interface{}) error {
return p.CheckMarshalType(reflect.TypeOf(i))
}
// CheckMarshalType check whether the given type supports marshaling into
// query strings. See the documentation of the global CheckMarshalType func.
func (p *QSMarshaler) CheckMarshalType(t reflect.Type) error {
if t == nil {
return errors.New("nil type")
}
if t.Kind() == reflect.Ptr {
t = t.Elem()
}
_, err := p.opts.ValuesMarshalerFactory.ValuesMarshaler(t, p.opts)
return err
}
// NewDefaultMarshalOptions creates a new MarshalOptions in which every field
// is set to its default value.
func NewDefaultMarshalOptions() *MarshalOptions {
return prepareMarshalOptions(MarshalOptions{})
}
// defaultValuesMarshalerFactory is used by the NewMarshaler function when its
// MarshalOptions.ValuesMarshalerFactory parameter is nil.
var defaultValuesMarshalerFactory = newValuesMarshalerFactory()
// defaultMarshalerFactory is used by the NewMarshaler function when its
// MarshalOptions.MarshalerFactory parameter is nil. This variable is set
// to a factory object that handles most builtin types (arrays, pointers,
// bool, int, etc...). If a type implements the MarshalQS interface then this
// factory returns an marshaler object that allows instances of the given type
// to marshal themselves.
var defaultMarshalerFactory = newMarshalerFactory()
// defaultMarshalPresence is used by the NewMarshaler function when its
// MarshalOptions.DefaultMarshalPresence parameter is MPUnspecified.
const defaultMarshalPresence = KeepEmpty
func prepareMarshalOptions(opts MarshalOptions) *MarshalOptions {
if opts.NameTransformer == nil {
opts.NameTransformer = snakeCase
}
if opts.ValuesMarshalerFactory == nil {
opts.ValuesMarshalerFactory = defaultValuesMarshalerFactory
}
opts.ValuesMarshalerFactory = newValuesMarshalerCache(opts.ValuesMarshalerFactory)
if opts.MarshalerFactory == nil {
opts.MarshalerFactory = defaultMarshalerFactory
}
opts.MarshalerFactory = newMarshalerCache(opts.MarshalerFactory)
if opts.DefaultMarshalPresence == MPUnspecified {
opts.DefaultMarshalPresence = defaultMarshalPresence
}
return &opts
}