Skip to content

Commit

Permalink
Merge pull request #23 from arp242/errcode
Browse files Browse the repository at this point in the history
Add error codes
  • Loading branch information
emilgpa committed Jul 29, 2019
2 parents c6968d8 + 40e64ba commit 672dd3f
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 27 deletions.
10 changes: 3 additions & 7 deletions .travis.yml
@@ -1,10 +1,6 @@
language: go

go:
- 1.2
- 1.3
- 1.4
- 1.5
- 1.6
- 1.7
- 1.8
- 1.10.x
- 1.11.x
- 1.12.x
38 changes: 33 additions & 5 deletions errors.go
Expand Up @@ -3,25 +3,53 @@ package formam
import (
"encoding/json"
"fmt"
"strings"
)

// Error codes.
const (
ErrCodeNotAPointer uint8 = iota // Didn't pass a pointer to Decode().
ErrCodeArrayIndex // Error attempting to use an array index (e.g. foo[2]).
ErrCodeConversion // Error converting field to the type.
ErrCodeUnknownType // Unknown type.
ErrCodeUnknownField // No struct field for passed parameter (will never be used if IgnoreUnknownKeys is set).
)

type Error struct {
err error
code uint8
field, path string
err error
}

func (s *Error) Error() string {
return "formam: " + s.err.Error()
var b strings.Builder
b.WriteString("formam: ")
if s.field != "" {
b.WriteString("field=")
b.WriteString(s.field)
b.WriteString("; path=")
b.WriteString(s.path)
b.WriteString(": ")
}

b.WriteString(s.err.Error())
return b.String()
}

func (s Error) MarshalJSON() ([]byte, error) {
return json.Marshal(s.err.Error())
return json.Marshal(s.Error())
}

// Code for this error. See the ErrCode* constants.
func (s Error) Code() uint8 {
return s.code
}

// Cause implements the causer interface from github.com/pkg/errors.
func (s *Error) Cause() error {
return s.err
}

func newError(format string, a ...interface{}) error {
return &Error{fmt.Errorf(format, a...)}
func newError(code uint8, field, path, format string, a ...interface{}) error {
return &Error{code: code, field: field, path: path, err: fmt.Errorf(format, a...)}
}
41 changes: 41 additions & 0 deletions errors_test.go
@@ -0,0 +1,41 @@
package formam

import (
"encoding/json"
"fmt"
"testing"
)

func TestError(t *testing.T) {
tests := []struct {
in error
wantString string
wantJSON string
}{
{newError(0, "", "", "oh noes"),
"formam: oh noes",
`"formam: oh noes"`},
{newError(0, "foo", "foo.bar", "oh noes"),
"formam: field=foo; path=foo.bar: oh noes",
`"formam: field=foo; path=foo.bar: oh noes"`},
}

for i, tt := range tests {
t.Run(fmt.Sprintf("%v", i), func(t *testing.T) {
outString := tt.in.Error()
if outString != tt.wantString {
t.Errorf("\nout: %#v\nwant: %#v\n", outString, tt.wantString)
}

j, err := json.Marshal(tt.in)
if err != nil {
t.Fatal(err)
}
outJSON := string(j)

if outJSON != tt.wantJSON {
t.Errorf("\nout: %#v\nwant: %#v\n", outJSON, tt.wantJSON)
}
})
}
}
31 changes: 16 additions & 15 deletions formam.go
Expand Up @@ -120,7 +120,7 @@ func NewDecoder(opts *DecoderOptions) *Decoder {
func (dec Decoder) Decode(vs url.Values, dst interface{}) error {
main := reflect.ValueOf(dst)
if main.Kind() != reflect.Ptr {
return newError("Decode: dst %q is not a pointer", main.Kind())
return newError(ErrCodeNotAPointer, "", "", "dst %q is not a pointer", main.Kind())
}
dec.main = main.Elem()
dec.formValues = vs
Expand All @@ -132,7 +132,7 @@ func (dec Decoder) Decode(vs url.Values, dst interface{}) error {
func Decode(vs url.Values, dst interface{}) error {
main := reflect.ValueOf(dst)
if main.Kind() != reflect.Ptr {
return newError("Decode: dst %q is not a pointer", main.Kind())
return newError(ErrCodeNotAPointer, "", "", "dst %q is not a pointer", main.Kind())
}
dec := &Decoder{
main: main.Elem(),
Expand Down Expand Up @@ -287,13 +287,13 @@ func (dec *Decoder) traverse() error {
case reflect.Array:
index, err := strconv.Atoi(dec.bracket)
if err != nil {
return newError("array index is not a numberf for field %q in path %q: %v", dec.field, dec.path, err)
return newError(ErrCodeArrayIndex, dec.field, dec.path, "array index is not a number: %s", err)
}
dec.curr = dec.curr.Index(index)
case reflect.Slice:
index, err := strconv.Atoi(dec.bracket)
if err != nil {
return newError("slice index is not a number for field %q in path %q: %v", dec.field, dec.path, err)
return newError(ErrCodeArrayIndex, dec.field, dec.path, "slice index is not a number: %s", err)
}
if dec.curr.Len() <= index {
dec.expandSlice(index + 1)
Expand All @@ -302,7 +302,7 @@ func (dec *Decoder) traverse() error {
case reflect.Map:
dec.traverseInMap(false)
default:
return newError("%q in path %q has an array index but it is a %v", dec.field, dec.path, dec.curr.Kind())
return newError(ErrCodeArrayIndex, dec.field, dec.path, "has an array index but it is a %v", dec.curr.Kind())
}
dec.bracket = ""
}
Expand Down Expand Up @@ -389,7 +389,7 @@ func (dec *Decoder) decode() error {
// has index, so to decode value by index indicated
index, err := strconv.Atoi(dec.bracket)
if err != nil {
return newError("array index is not a numberf for field %q in path %q: %v", dec.field, dec.path, err)
return newError(ErrCodeArrayIndex, dec.field, dec.path, "array index is not a number: %s", err)
}
dec.curr = dec.curr.Index(index)
return dec.decode()
Expand All @@ -406,7 +406,7 @@ func (dec *Decoder) decode() error {
// has index, so to decode value by index indicated
index, err := strconv.Atoi(dec.bracket)
if err != nil {
return newError("slice index is not a number for field %q in path %q: %v", dec.field, dec.path, err)
return newError(ErrCodeArrayIndex, dec.field, dec.path, "slice index is not a number: %s", err)
}
// only for slices
if dec.curr.Len() <= index {
Expand All @@ -419,19 +419,19 @@ func (dec *Decoder) decode() error {
dec.curr.SetString(dec.values[0])
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
if num, err := strconv.ParseInt(dec.values[0], 10, 64); err != nil {
return newError("could not parse number for field %q in path %q: %v", dec.field, dec.path, err)
return newError(ErrCodeConversion, dec.field, dec.path, "could not parse number: %s", err)
} else {
dec.curr.SetInt(num)
}
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
if num, err := strconv.ParseUint(dec.values[0], 10, 64); err != nil {
return newError("could not parse number for field %q in path %q: %v", dec.field, dec.path, err)
return newError(ErrCodeConversion, dec.field, dec.path, "could not parse number: %s", err)
} else {
dec.curr.SetUint(num)
}
case reflect.Float32, reflect.Float64:
if num, err := strconv.ParseFloat(dec.values[0], dec.curr.Type().Bits()); err != nil {
return newError("could not parse float for field %q in path %q: %v", dec.field, dec.path, err)
return newError(ErrCodeConversion, dec.field, dec.path, "could not parse float: %s", err)
} else {
dec.curr.SetFloat(num)
}
Expand Down Expand Up @@ -463,14 +463,14 @@ func (dec *Decoder) decode() error {
var err error
t, err = time.Parse("2006-01-02", dec.values[0])
if err != nil {
return newError("could not parse field %q in path %q: %v", dec.field, dec.path, err)
return newError(ErrCodeConversion, dec.field, dec.path, "could not parse field: %s", err)
}
}
dec.curr.Set(reflect.ValueOf(t))
case url.URL:
u, err := url.Parse(dec.values[0])
if err != nil {
return newError("could not parse field %q in path %q: %v", dec.field, dec.path, err)
return newError(ErrCodeConversion, dec.field, dec.path, "could not parse field: %s", err)
}
dec.curr.Set(reflect.ValueOf(*u))
default:
Expand All @@ -486,14 +486,15 @@ func (dec *Decoder) decode() error {
return nil
}
}
return newError("unsupported type for field %q in path %q. Maybe you should to include it the UnmarshalText interface or register it using custom type?", dec.field, dec.path)
return newError(ErrCodeUnknownType, dec.field, dec.path,
"unsupported type; maybe include it the UnmarshalText interface or register it using custom type?")
}
default:
if dec.opts.IgnoreUnknownKeys {
return nil
}

return newError("unsupported type for field %q in path %q", dec.field, dec.path)
return newError(ErrCodeUnknownType, dec.field, dec.path, "unsupported type")
}

return nil
Expand Down Expand Up @@ -550,7 +551,7 @@ func (dec *Decoder) findStructField() error {
if dec.opts.IgnoreUnknownKeys {
return nil
}
return newError("could not find the field %q in the path %q", dec.field, dec.path)
return newError(ErrCodeUnknownField, dec.field, dec.path, "unknown field")
}

// expandSlice expands the length and capacity of the current slice
Expand Down

0 comments on commit 672dd3f

Please sign in to comment.