//nolint:gomnd package field import ( "fmt" "strconv" "time" "unicode" "unicode/utf8" ) type Encoder interface { AppendField(dst []byte, field Field) []byte AppendValue(dst []byte, val Value) []byte } func WithAppendString(fn func(dst []byte, in string) []byte) func(*BaseEncoder) { return func(be *BaseEncoder) { be.AppendString = fn } } func WithNullValue(in string) func(*BaseEncoder) { return func(be *BaseEncoder) { be.nullValue = []byte(in) } } func WithDelimeter(in byte) func(*BaseEncoder) { return func(be *BaseEncoder) { be.delimeter = in } } func WithGropuConfig(start, end, deli byte) func(*BaseEncoder) { return func(be *BaseEncoder) { be.group = groupConfig{ start: start, end: end, deli: deli, } } } func WithDefaultValue(fn func(dst []byte, e Encoder, val Value) []byte) func(*BaseEncoder) { return func(be *BaseEncoder) { be.DefaultValue = fn } } func NewEncoder(opts ...func(*BaseEncoder)) BaseEncoder { be := BaseEncoder{ nullValue: []byte("null"), group: groupConfig{ start: '{', end: '}', deli: ',', }, array: groupConfig{ start: '[', end: ']', deli: ',', }, timeFormat: time.RFC3339, AppendString: AppendString, delimeter: '=', DefaultValue: func(dst []byte, e Encoder, val Value) []byte { return e.AppendValue(dst, StringValue(fmt.Sprintf("%+v", val.Any()))) }, } for _, opt := range opts { opt(&be) } return be } type groupConfig struct { start byte end byte deli byte } type BaseEncoder struct { nullValue []byte group groupConfig array groupConfig timeFormat string AppendString func(dst []byte, in string) []byte delimeter byte DefaultValue func(dst []byte, e Encoder, val Value) []byte } func (b BaseEncoder) AppendValue(dst []byte, val Value) []byte { return b.appendValue(dst, val, "", 0) } func (b BaseEncoder) AppendDelimiter(dst []byte, deli byte) []byte { if deli == 0 { return dst } return append(dst, deli) } //nolint:gocyclo,cyclop func (b BaseEncoder) appendValue(dst []byte, val Value, prefix string, deli byte) []byte { switch val.Kind { case KindGroup: return b.appendGroup(dst, val.AsGroup(), prefix) case KindClosure: return b.appendValue(dst, AnyValue(val.Resolve()), prefix, deli) case KindArray: return b.AppendArray(b.AppendDelimiter(dst, deli), val.AsArray()) case KindNil: return b.AppendNull(b.AppendDelimiter(dst, deli)) case KindBool: return b.AppendBool(b.AppendDelimiter(dst, deli), val.AsBool()) case KindBinary: return b.AppendBytes(b.AppendDelimiter(dst, deli), val.AsBinary()) case KindComplex128: return b.AppendComplex(b.AppendDelimiter(dst, deli), val.AsComplex128()) case KindInt64: return b.AppendInt(b.AppendDelimiter(dst, deli), val.AsInt64()) case KindFloat32: return b.AppendFloat(b.AppendDelimiter(dst, deli), float64(val.AsFloat32()), 32) case KindFloat64: return b.AppendFloat(b.AppendDelimiter(dst, deli), val.AsFloat64(), 64) case KindUint64: return b.AppendUint(b.AppendDelimiter(dst, deli), val.AsUint64()) case KindError: return b.AppendString(b.AppendDelimiter(dst, deli), val.AsError().Error()) case KindString: return b.AppendString(b.AppendDelimiter(dst, deli), val.AsString()) case KindDuration: return b.AppendDuration(b.AppendDelimiter(dst, deli), val.AsDuration()) case KindTime: return b.AppendTime(b.AppendDelimiter(dst, deli), val.AsTime()) case KindAny: return b.DefaultValue(b.AppendDelimiter(dst, deli), b, val) } return b.DefaultValue(b.AppendDelimiter(dst, deli), b, val) } func (b BaseEncoder) AppendDuration(dst []byte, d time.Duration) []byte { return b.AppendString(dst, d.String()) } func (b BaseEncoder) AppendTime(dst []byte, t time.Time) []byte { return b.AppendString(dst, t.Format(b.timeFormat)) } func AppendString(dst []byte, in string) []byte { if needsQuoting(in) { return strconv.AppendQuote(dst, in) } return append(dst, in...) } //nolint:cyclop func needsQuoting(in string) bool { if len(in) == 0 { return true } for i := 0; i < len(in); { char := in[i] if char < utf8.RuneSelf { // Quote anything except a backslash that would need quoting in a // JSON string, as well as space and '=' if char != '\\' && (char == ' ' || char == '=' || !safeSet[char]) { return true } i++ continue } decodeRune, size := utf8.DecodeRuneInString(in[i:]) if decodeRune == utf8.RuneError || unicode.IsSpace(decodeRune) || !unicode.IsPrint(decodeRune) { return true } i += size } return false } func (b BaseEncoder) AppendField(dst []byte, field Field) []byte { prefix := "" if len(dst) != 0 { prew := dst[len(dst)-1] if prew != '{' && prew != '.' { prefix = string(b.group.deli) } } return b.appendField(dst, field, prefix, b.delimeter) } func (b BaseEncoder) appendField(dst []byte, field Field, prefix string, deli byte) []byte { dst = b.AppendKey(dst, field.Key, prefix) return b.appendValue(dst, field.Value, field.Key+".", deli) } func (b BaseEncoder) AppendKey(dst []byte, key string, prefix string) []byte { if prefix != "" { dst = append(dst, prefix...) } return b.AppendString(dst, key) } func (b BaseEncoder) AppendComplex(dst []byte, c complex128) []byte { cmplx := strconv.FormatComplex(c, 'g', -1, 128) return b.AppendString(dst, cmplx) } func (b BaseEncoder) AppendFloat(dst []byte, f float64, bitSize int) []byte { return strconv.AppendFloat(dst, f, 'g', -1, bitSize) } func (b BaseEncoder) AppendUint(dst []byte, u uint64) []byte { return strconv.AppendUint(dst, u, 10) } func (b BaseEncoder) AppendNull(dst []byte) []byte { return append(dst, b.nullValue...) } func (b BaseEncoder) AppendInt(dst []byte, val int64) []byte { return strconv.AppendInt(dst, val, 10) } func (b BaseEncoder) AppendBool(dst []byte, val bool) []byte { return strconv.AppendBool(dst, val) } func (b BaseEncoder) AppendGroup(dst []byte, fields []Field) []byte { dst = append(dst, b.group.start) dst = b.appendGroup(dst, fields, "") return append(dst, b.group.end) } func (b BaseEncoder) appendGroup(dst []byte, fields []Field, prefix string) []byte { if len(fields) > 0 { dst = b.appendField(dst, fields[0], ".", b.delimeter) for _, field := range fields[1:] { dst = b.appendField(append(dst, b.group.deli), field, prefix, b.delimeter) } } return dst } func (b BaseEncoder) AppendArray(dst []byte, in []Value) []byte { dst = append(dst, b.array.start) if len(in) > 0 { dst = b.appendValue(dst, in[0], "", 0) for _, value := range in[1:] { dst = b.appendValue(append(dst, b.array.deli), value, "", 0) } } return append(dst, b.array.end) } func (b BaseEncoder) AppendBytes(dst, in []byte) []byte { dst = append(dst, '"') dst = append(dst, in...) return append(dst, '"') }