Skip to content

Commit

Permalink
Wrap error (#115)
Browse files Browse the repository at this point in the history
Support Go1.13 error unwrapping.
  • Loading branch information
at-wat authored Dec 21, 2019
1 parent d2269ab commit c273fdc
Show file tree
Hide file tree
Showing 26 changed files with 525 additions and 193 deletions.
22 changes: 12 additions & 10 deletions block_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import (
"io"
"reflect"
"testing"

"github.com/at-wat/ebml-go/internal/errs"
)

func TestUnmarshalBlock(t *testing.T) {
Expand Down Expand Up @@ -64,10 +66,10 @@ func TestUnmarshalBlock(t *testing.T) {
t.Run(n, func(t *testing.T) {
block, err := UnmarshalBlock(bytes.NewBuffer(c.input), int64(len(c.input)))
if err != nil {
t.Fatalf("Failed to unmarshal block: %v", err)
t.Fatalf("Failed to unmarshal block: '%v'", err)
}
if !reflect.DeepEqual(c.expected, *block) {
t.Errorf("Unexpected unmarshal result, expected: %v, got: %v", c.expected, *block)
t.Errorf("Expected unmarshal result: '%v', got: '%v'", c.expected, *block)
}
})
}
Expand All @@ -77,9 +79,9 @@ func TestUnmarshalBlock_Error(t *testing.T) {
t.Run("EOF", func(t *testing.T) {
input := []byte{0x21, 0x23, 0x45, 0x00, 0x02, 0x00}
for l := 0; l < len(input); l++ {
if _, err := UnmarshalBlock(bytes.NewBuffer(input[:l]), int64(len(input))); err != io.ErrUnexpectedEOF {
t.Errorf("UnmarshalBlock should return %v against short data (%d bytes), but got %v",
io.ErrUnexpectedEOF, l, err)
if _, err := UnmarshalBlock(bytes.NewBuffer(input[:l]), int64(len(input))); !errs.Is(err, io.ErrUnexpectedEOF) {
t.Errorf("Short data (%d bytes) expected error: '%v', got: '%v'",
l, io.ErrUnexpectedEOF, err)
}
}
})
Expand All @@ -95,8 +97,8 @@ func TestUnmarshalBlock_Error(t *testing.T) {
for n, c := range testCases {
t.Run(n, func(t *testing.T) {
_, err := UnmarshalBlock(bytes.NewBuffer(c.input), int64(len(c.input)))
if err != c.err {
t.Errorf("Unexpected error, expected: %v, got: %v", c.err, err)
if !errs.Is(err, c.err) {
t.Errorf("Expected error: '%v', got: '%v'", c.err, err)
}
})
}
Expand Down Expand Up @@ -125,10 +127,10 @@ func TestMarshalBlock(t *testing.T) {
var b bytes.Buffer
err := MarshalBlock(&c.input, &b)
if err != nil {
t.Fatalf("Failed to marshal block: %v", err)
t.Fatalf("Failed to marshal block: '%v'", err)
}
if !reflect.DeepEqual(c.expected, b.Bytes()) {
t.Errorf("Unexpected marshal result, expected: %v, got: %v", c.expected, b.Bytes())
t.Errorf("Expected marshal result: '%v', got: '%v'", c.expected, b.Bytes())
}
})
}
Expand All @@ -142,7 +144,7 @@ func TestMarshalBlock_Error(t *testing.T) {
for l := 0; l < 7; l++ {
err := MarshalBlock(input, &limitedDummyWriter{limit: l})
if err != bytes.ErrTooLarge {
t.Errorf("UnmarshalBlock should bytes.ErrTooLarge against too large data (Writer size limit: %d), but got %v", l, err)
t.Errorf("Expected error against too large data (Writer size limit: %d): '%v', got: '%v'", l, bytes.ErrTooLarge, err)
}
}
},
Expand Down
2 changes: 1 addition & 1 deletion elementtype.go
Original file line number Diff line number Diff line change
Expand Up @@ -549,6 +549,6 @@ func ElementTypeFromString(s string) (ElementType, error) {
return ElementTimecode, nil

default:
return 0, ErrUnknownElementName
return 0, wrapErrorf(ErrUnknownElementName, "parsing \"%s\"", s)
}
}
6 changes: 3 additions & 3 deletions elementtype_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ func TestElementType_Roundtrip(t *testing.T) {
for e := ElementInvalid + 1; e < elementMax; e++ {
s := e.String()
if el, err := ElementTypeFromString(s); err != nil {
t.Errorf("Failed to get ElementType from string: %v", err)
t.Errorf("Failed to get ElementType from string: '%v'", err)
} else if e != el {
t.Errorf("Failed to roundtrip ElementType %d and string", e)
}
Expand All @@ -20,9 +20,9 @@ func TestElementType_Bytes(t *testing.T) {
expected := []byte{0x18, 0x53, 0x80, 0x67}

if !bytes.Equal(ElementSegment.Bytes(), expected) {
t.Errorf("Unexpected bytes, expected: %v, got: %v", expected, ElementSegment.Bytes())
t.Errorf("Expected bytes: '%v', got: '%v'", expected, ElementSegment.Bytes())
}
if ElementSegment.DataType() != DataTypeMaster {
t.Errorf("Unexpected DataType, expected: %s, got: %s", DataTypeMaster, ElementSegment.DataType())
t.Errorf("Expected DataType: %s, got: %s", DataTypeMaster, ElementSegment.DataType())
}
}
87 changes: 87 additions & 0 deletions error.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// Copyright 2019 The ebml-go authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package ebml

import (
"fmt"
"reflect"
)

// Error records a failed parsing.
type Error struct {
Err error
Failure string
}

func (e *Error) Error() string {
// TODO: migrate to fmt.Sprintf %w once Go1.12 reaches EOL.
return e.Failure + ": " + e.Err.Error()
}

// Unwrap returns the reason of the failure.
// This is for Go1.13 error unwrapping.
func (e *Error) Unwrap() error {
return e.Err
}

// Is reports whether chained error contains target.
// This is for Go1.13 error unwrapping.
func (e *Error) Is(target error) bool {
err := e.Err

switch target {
case e:
return true
case nil:
return err == nil
}
for {
switch err {
case nil:
return false
case target:
return true
}
x, ok := err.(interface{ Unwrap() error })
if !ok {
// Some stdlibs haven't have error unwrapper yet.
// Check err.Err field if exposed.
if reflect.TypeOf(err).Kind() == reflect.Ptr {
e := reflect.ValueOf(err).Elem().FieldByName("Err")
if e.IsValid() {
e2, ok := e.Interface().(error)
if !ok {
return false
}
err = e2
continue
}
}
return false
}
err = x.Unwrap()
}
}

func wrapError(err error, failure string) error {
return &Error{
Failure: failure,
Err: err,
}
}

func wrapErrorf(err error, failureFmt string, v ...interface{}) error {
return wrapError(err, fmt.Sprintf(failureFmt, v...))
}
83 changes: 83 additions & 0 deletions error_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// Copyright 2019 The ebml-go authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package ebml

import (
"errors"
"testing"

"github.com/at-wat/ebml-go/internal/errs"
)

type dummyError struct {
Err error
}

func (e *dummyError) Error() string {
return e.Err.Error()
}

func TestError(t *testing.T) {
errBase := errors.New("an error")
errOther := errors.New("an another error")
errChained := wrapErrorf(errBase, "info")
errChainedNil := wrapErrorf(nil, "info")
errChainedOther := wrapErrorf(errOther, "info")
err112Chained := wrapErrorf(&dummyError{errBase}, "info")
err112Nil := wrapErrorf(&dummyError{nil}, "info")
errStr := "info: an error"

t.Run("ErrorsIs", func(t *testing.T) {
if !errs.Is(errChained, errBase) {
t.Errorf("Wrapped error '%v' doesn't chain '%v'", errChained, errBase)
}
})

t.Run("Is", func(t *testing.T) {
if !errChained.(*Error).Is(errChained) {
t.Errorf("Wrapped error '%v' doesn't match its-self", errChained)
}
if !errChained.(*Error).Is(errBase) {
t.Errorf("Wrapped error '%v' doesn't match '%v'", errChained, errBase)
}
if !err112Chained.(*Error).Is(errBase) {
t.Errorf("Wrapped error '%v' doesn't match '%v'",
err112Chained, errBase)
}
if !errChainedNil.(*Error).Is(nil) {
t.Errorf("Nil chained error '%v' doesn't match 'nil'", errChainedNil)
}

if errChainedNil.(*Error).Is(errBase) {
t.Errorf("Wrapped error '%v' unexpectedly matched '%v'",
errChainedNil, errBase)
}
if errChainedOther.(*Error).Is(errBase) {
t.Errorf("Wrapped error '%v' unexpectedly matched '%v'",
errChainedOther, errBase)
}
if err112Nil.(*Error).Is(errBase) {
t.Errorf("Wrapped error '%v' unexpectedly matched '%v'",
errChainedOther, errBase)
}
})

if errChained.Error() != errStr {
t.Errorf("Error string expected: %s, got: %s", errStr, errChained.Error())
}
if errChained.(*Error).Unwrap() != errBase {
t.Errorf("Unwrapped error expected: %s, got: %s", errBase, errChained.(*Error).Unwrap())
}
}
2 changes: 1 addition & 1 deletion internal/buffercloser/buffercloser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ func TestBufferCloser(t *testing.T) {
}

if !bytes.Equal(data, buf.Bytes()) {
t.Errorf("Unexpected bytes in the buffer, expected: %d, got: %d", data, buf.Bytes())
t.Errorf("Expected bytes in the buffer: %d, got: %d", data, buf.Bytes())
}

if err := buf.Close(); err != nil {
Expand Down
40 changes: 40 additions & 0 deletions internal/errs/errors_112.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// +build !go1.13

// Copyright 2019 The ebml-go authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package errs

// Is compares error type. Works like Go1.13 errors.Is().
func Is(err, target error) bool {
if target == nil {
return err == nil
}
for {
if err == target {
return true
}
if err == nil {
return false
}
if x, ok := err.(interface{ Is(error) bool }); ok {
return x.Is(target)
}
x, ok := err.(interface{ Unwrap() error })
if !ok {
return false
}
err = x.Unwrap()
}
}
27 changes: 27 additions & 0 deletions internal/errs/errors_113.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// +build go1.13

// Copyright 2019 The ebml-go authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package errs is for compatibility with Go1.13 error wrapping.
package errs

import (
"errors"
)

// Is compares error type. Wrapping Go1.13 errors.Is().
func Is(err, target error) bool {
return errors.Is(err, target)
}
Loading

0 comments on commit c273fdc

Please sign in to comment.