Skip to content
This repository has been archived by the owner on Aug 31, 2019. It is now read-only.

Commit

Permalink
Uses package kv to serialize key/value pairs.
Browse files Browse the repository at this point in the history
This package already depended on package kv for kv.Flatten, so it seems OK to use this new functionality from package kv.
  • Loading branch information
jjeffery committed Jan 22, 2017
1 parent f34e4d7 commit da633fb
Show file tree
Hide file tree
Showing 2 changed files with 7 additions and 149 deletions.
152 changes: 5 additions & 147 deletions context.go
Expand Up @@ -2,9 +2,6 @@ package errors

import (
"bytes"
"encoding"
"fmt"
"reflect"
"strings"

"github.com/jjeffery/kv"
Expand Down Expand Up @@ -104,152 +101,13 @@ func (ctx context) appendKeyvals(keyvals []interface{}) []interface{} {

// writeToBuf writes the context's key/value pairs to a buffer.
func (ctx context) writeToBuf(buf *bytes.Buffer) {
keyvals := kv.Flatten(ctx.keyvals)
for i := 0; i < len(keyvals); i += 2 {
// kv.Flatten guarantees that every even-numbered index
// will contain a string, and that it will be followed by
// an odd-numbered index
key := keyvals[i].(string)
value := keyvals[i+1]

if buf.Len() > 0 {
buf.WriteRune(' ')
}
buf.WriteString(key)
buf.WriteRune('=')
writeValue(buf, value)
}
}

// constant byte values
var (
bytesNull = []byte("null")
bytesPanic = []byte(`"<PANIC>"`)
bytesError = []byte(`"<ERROR>"`)
)

func writeValue(buf *bytes.Buffer, value interface{}) {
defer func() {
if r := recover(); r != nil {
if buf != nil {
buf.Write(bytesPanic)
}
}
}()
switch v := value.(type) {
case nil:
writeBytesValue(buf, bytesNull)
return
case []byte:
writeBytesValue(buf, v)
return
case string:
writeStringValue(buf, v)
return
case bool, byte, int8, int16, uint16, int32, uint32, int64, uint64, int, uint, uintptr, float32, float64, complex64, complex128:
fmt.Fprint(buf, v)
return
case encoding.TextMarshaler:
writeTextMarshalerValue(buf, v)
return
case error:
writeStringValue(buf, v.Error())
return
case fmt.Stringer:
writeStringValue(buf, v.String())
return
default:
// handle pointer to any of the above
rv := reflect.ValueOf(value)
if rv.Kind() == reflect.Ptr {
if rv.IsNil() {
buf.Write(bytesNull)
return
}
writeValue(buf, rv.Elem().Interface())
return
}
writeStringValue(buf, fmt.Sprint(value))
}
}

func writeBytesValue(buf *bytes.Buffer, b []byte) {
if b == nil {
buf.Write(bytesNull)
if len(ctx.keyvals) == 0 {
return
}
index := bytes.IndexFunc(b, needsQuote)
if index < 0 {
buf.Write(b)
return
}
buf.WriteRune('"')
if index > 0 {
buf.Write(b[0:index])
b = b[index:]
}
for {
index = bytes.IndexFunc(b, needsBackslash)
if index < 0 {
break
}
if index > 0 {
buf.Write(b[:index])
b = b[index:]
}
buf.WriteRune('\\')
// we know that the rune will be a single byte
buf.WriteByte(b[0])
b = b[1:]
kvlist := kv.List(ctx.keyvals)
b, _ := kvlist.MarshalText()
if buf.Len() > 0 {
buf.WriteRune(' ')
}
buf.Write(b)
buf.WriteRune('"')
}

func writeStringValue(buf *bytes.Buffer, s string) {
index := strings.IndexFunc(s, needsQuote)
if index < 0 {
buf.WriteString(s)
return
}
buf.WriteRune('"')
if index > 0 {
buf.WriteString(s[0:index])
s = s[index:]
}
for {
index = strings.IndexFunc(s, needsBackslash)
if index < 0 {
break
}
if index > 0 {
buf.WriteString(s[0:index])
s = s[index:]
}
buf.WriteRune('\\')
// we know that the rune will be a single byte
buf.WriteByte(s[0])
s = s[1:]
}
buf.WriteString(s)
buf.WriteRune('"')
}

func writeTextMarshalerValue(buf *bytes.Buffer, t encoding.TextMarshaler) {
b, err := t.MarshalText()
if err != nil {
buf.Write(bytesError)
return
}
writeBytesValue(buf, b)
}

func needsQuote(c rune) bool {
// the single quote '\'' is not strictly necessary, but
// is more human readable if quoted
return c <= ' ' || c == '"' || c == '\\' || c == '\''
}

func needsBackslash(c rune) bool {
return c == '\\' || c == '"'
}
4 changes: 2 additions & 2 deletions errors_test.go
Expand Up @@ -71,7 +71,7 @@ func TestNew(t *testing.T) {
opts: []interface{}{
"f1", failingTextMarshaler(0),
},
expect: `msg f1="<ERROR>"`,
expect: `msg f1=<ERROR>`,
},
{
msg: "msg",
Expand Down Expand Up @@ -108,7 +108,7 @@ func TestNew(t *testing.T) {
opts: []interface{}{
"p1", panicingStringer("I can't do this"),
},
expect: `msg p1="<PANIC>"`,
expect: `msg p1=<PANIC>`,
},
}

Expand Down

0 comments on commit da633fb

Please sign in to comment.