github.com/wI2L/jettison@v0.7.5-0.20230106001914-c70014c6417a/options.go (about) 1 package jettison 2 3 import ( 4 "context" 5 "fmt" 6 "time" 7 ) 8 9 // defaultTimeLayout is the default layout used 10 // to format time.Time values. This is compliant 11 // with the ECMA specification and the JavaScript 12 // Date's toJSON method implementation. 13 const defaultTimeLayout = time.RFC3339Nano 14 15 // defaultDurationFmt is the default format used 16 // to encode time.Duration values. 17 const defaultDurationFmt = DurationNanoseconds 18 19 // An Option overrides the default encoding 20 // behavior of the MarshalOpts function. 21 type Option func(*encOpts) 22 23 type bitmask uint64 24 25 func (b *bitmask) set(f bitmask) { *b |= f } 26 func (b bitmask) has(f bitmask) bool { return b&f != 0 } 27 28 const ( 29 unixTime bitmask = 1 << iota 30 unsortedMap 31 rawByteSlice 32 byteArrayAsString 33 nilMapEmpty 34 nilSliceEmpty 35 noStringEscaping 36 noHTMLEscaping 37 noUTF8Coercion 38 noCompact 39 noNumberValidation 40 ) 41 42 type encOpts struct { 43 ctx context.Context 44 timeLayout string 45 durationFmt DurationFmt 46 flags bitmask 47 allowList stringSet 48 denyList stringSet 49 } 50 51 func defaultEncOpts() encOpts { 52 return encOpts{ 53 ctx: context.TODO(), 54 timeLayout: defaultTimeLayout, 55 durationFmt: defaultDurationFmt, 56 } 57 } 58 59 func (eo *encOpts) apply(opts ...Option) { 60 for _, opt := range opts { 61 if opt != nil { 62 opt(eo) 63 } 64 } 65 } 66 67 func (eo encOpts) validate() error { 68 switch { 69 case eo.ctx == nil: 70 return fmt.Errorf("nil context") 71 case eo.timeLayout == "": 72 return fmt.Errorf("empty time layout") 73 case !eo.durationFmt.valid(): 74 return fmt.Errorf("unknown duration format") 75 default: 76 return nil 77 } 78 } 79 80 // isDeniedField returns whether a struct field 81 // identified by its name must be skipped during 82 // the encoding of a struct. 83 func (eo encOpts) isDeniedField(name string) bool { 84 // The deny-list has precedence and must 85 // be checked first if it has entries. 86 if eo.denyList != nil { 87 if _, ok := eo.denyList[name]; ok { 88 return true 89 } 90 } 91 if eo.allowList != nil { 92 if _, ok := eo.allowList[name]; !ok { 93 return true 94 } 95 } 96 return false 97 } 98 99 type stringSet map[string]struct{} 100 101 func fieldListToSet(list []string) stringSet { 102 m := make(stringSet) 103 for _, f := range list { 104 m[f] = struct{}{} 105 } 106 return m 107 } 108 109 // UnixTime configures an encoder to encode 110 // time.Time values as Unix timestamps. This 111 // option, when used, has precedence over any 112 // time layout confiured. 113 func UnixTime() Option { 114 return func(o *encOpts) { o.flags.set(unixTime) } 115 } 116 117 // UnsortedMap configures an encoder to skip 118 // the sort of map keys. 119 func UnsortedMap() Option { 120 return func(o *encOpts) { o.flags.set(unsortedMap) } 121 } 122 123 // RawByteSlice configures an encoder to 124 // encode byte slices as raw JSON strings, 125 // rather than bas64-encoded strings. 126 func RawByteSlice() Option { 127 return func(o *encOpts) { o.flags.set(rawByteSlice) } 128 } 129 130 // ByteArrayAsString configures an encoder 131 // to encode byte arrays as raw JSON strings. 132 func ByteArrayAsString() Option { 133 return func(o *encOpts) { o.flags.set(byteArrayAsString) } 134 } 135 136 // NilMapEmpty configures an encoder to 137 // encode nil Go maps as empty JSON objects, 138 // rather than null. 139 func NilMapEmpty() Option { 140 return func(o *encOpts) { o.flags.set(nilMapEmpty) } 141 } 142 143 // NilSliceEmpty configures an encoder to 144 // encode nil Go slices as empty JSON arrays, 145 // rather than null. 146 func NilSliceEmpty() Option { 147 return func(o *encOpts) { o.flags.set(nilSliceEmpty) } 148 } 149 150 // NoStringEscaping configures an encoder to 151 // disable string escaping. 152 func NoStringEscaping() Option { 153 return func(o *encOpts) { o.flags.set(noStringEscaping) } 154 } 155 156 // NoHTMLEscaping configures an encoder to 157 // disable the escaping of problematic HTML 158 // characters in JSON strings. 159 func NoHTMLEscaping() Option { 160 return func(o *encOpts) { o.flags.set(noHTMLEscaping) } 161 } 162 163 // NoUTF8Coercion configures an encoder to 164 // disable UTF8 coercion that replace invalid 165 // bytes with the Unicode replacement rune. 166 func NoUTF8Coercion() Option { 167 return func(o *encOpts) { o.flags.set(noUTF8Coercion) } 168 } 169 170 // NoNumberValidation configures an encoder to 171 // disable the validation of json.Number values. 172 func NoNumberValidation() Option { 173 return func(o *encOpts) { o.flags.set(noNumberValidation) } 174 } 175 176 // NoCompact configures an encoder to disable 177 // the compaction of the JSON output produced 178 // by a call to MarshalJSON, or the content of 179 // a json.RawMessage. 180 // see https://golang.org/pkg/encoding/json/#Compact 181 func NoCompact() Option { 182 return func(o *encOpts) { o.flags.set(noCompact) } 183 } 184 185 // TimeLayout sets the time layout used to encode 186 // time.Time values. The layout must be compatible 187 // with the Golang time package specification. 188 func TimeLayout(layout string) Option { 189 return func(o *encOpts) { 190 o.timeLayout = layout 191 } 192 } 193 194 // DurationFormat sets the format used to encode 195 // time.Duration values. 196 func DurationFormat(format DurationFmt) Option { 197 return func(o *encOpts) { 198 o.durationFmt = format 199 } 200 } 201 202 // WithContext sets the context to use during 203 // encoding. The context will be passed in to 204 // the AppendJSONContext method of types that 205 // implement the AppendMarshalerCtx interface. 206 func WithContext(ctx context.Context) Option { 207 return func(o *encOpts) { 208 o.ctx = ctx 209 } 210 } 211 212 // AllowList sets the list of fields which are to be 213 // considered when encoding a struct. 214 // The fields are identified by the name that is 215 // used in the final JSON payload. 216 // See DenyFields documentation for more information 217 // regarding joint use with this option. 218 func AllowList(fields []string) Option { 219 m := fieldListToSet(fields) 220 return func(o *encOpts) { 221 o.allowList = m 222 } 223 } 224 225 // DenyList is similar to AllowList, but conversely 226 // sets the list of fields to omit during encoding. 227 // When used in conjunction with AllowList, denied 228 // fields have precedence over the allowed fields. 229 func DenyList(fields []string) Option { 230 m := fieldListToSet(fields) 231 return func(o *encOpts) { 232 o.denyList = m 233 } 234 }