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  }