Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor errors and add support for Go 1.13 errors #124

Merged
merged 7 commits into from Oct 9, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion .travis.yml
Expand Up @@ -6,9 +6,9 @@ addons:
chrome: stable

go:
- 1.12.x
- 1.13.x
- 1.14.x
- 1.15.x
- master

before_install:
Expand Down
2 changes: 2 additions & 0 deletions error.go
Expand Up @@ -6,4 +6,6 @@ import (

// ErrorCause returns the underlying cause for this error, if possible.
// If err does not implement causer.Cause(), then err is returned.
//
// Deprecated: Use errors.Unwrap, errors.Is or errors.As instead.
func ErrorCause(err error) error { return errors.Cause(err) }
74 changes: 48 additions & 26 deletions internal/errors/errors.go
@@ -1,21 +1,23 @@
package errors

import (
"errors"
"fmt"
"strings"
)

// Causer is an interface to access to its direct cause of the error.
type Causer interface {
Cause() error
}
// Interfaces for common error unwrapping.
type causer interface{ Cause() error }
type wrapper interface{ Unwrap() error }

// Cause returns the underlying cause for this error, if possible.
// If err does not implement Causer.Cause(), then err is returned.
// If err does not implement causer.Cause(), then err is returned.
//
// Deprecated: Use errors.Unwrap, errors.Is or errors.As instead.
func Cause(err error) error {
for err != nil {
if c, ok := err.(Causer); ok {
if c, ok := err.(wrapper); ok {
err = c.Unwrap()
} else if c, ok := err.(causer); ok {
err = c.Cause()
} else {
return err
Expand All @@ -24,16 +26,6 @@ func Cause(err error) error {
return err
}

// New returns an error that formats as the given text.
func New(text string) error {
return errors.New(text)
}

// Errorf wraps New and fmt.Sprintf.
func Errorf(format string, a ...interface{}) error {
return New(fmt.Sprintf(format, a...))
}

// Wrapf wraps an error with a message. Wrapf returns nil if error is nil.
func Wrapf(err error, format string, a ...interface{}) error {
if err == nil {
Expand All @@ -51,15 +43,12 @@ type wrapped struct {
}

var _ error = (*wrapped)(nil)
var _ Causer = (*wrapped)(nil)
var _ causer = (*wrapped)(nil)
var _ wrapper = (*wrapped)(nil)

func (e *wrapped) Error() string {
return fmt.Sprintf("%s: %s", e.msg, e.err)
}

func (e *wrapped) Cause() error {
return e.err
}
func (e *wrapped) Error() string { return fmt.Sprintf("%s: %s", e.msg, e.err.Error()) }
func (e *wrapped) Cause() error { return e.err }
func (e *wrapped) Unwrap() error { return e.err }

// Merge merges multiple errors into one.
// Merge returns nil if all errors are nil.
Expand All @@ -73,14 +62,16 @@ func Merge(err ...error) error {
if len(errs) == 0 {
return nil
}
return &merged{s: err}
return &merged{s: errs}
}

type merged struct {
s []error
}

var _ error = (*merged)(nil)
var _ causer = (*merged)(nil)
var _ wrapper = (*merged)(nil)

func (e *merged) Error() string {
var m []string
Expand All @@ -89,3 +80,34 @@ func (e *merged) Error() string {
}
return strings.Join(m, ": ")
}

// Unwrap returns only the first error, there is
// no way to create a queue of errors.
func (e *merged) Unwrap() error { return e.s[0] }

// Cause returns only the first error, there is
// no way to create a queue of errors.
func (e *merged) Cause() error { return e.s[0] }

// Is runs errors.Is on all merged errors.
func (e *merged) Is(target error) bool {
if target == nil {
return nil == e.s
}
for _, err := range e.s {
if Is(err, target) {
return true
}
}
return false
}

// As runs errors.As on all merged errors.
func (e *merged) As(target interface{}) bool {
for _, err := range e.s {
if As(err, target) {
return true
}
}
return false
}
41 changes: 41 additions & 0 deletions internal/errors/errors_test.go
Expand Up @@ -50,6 +50,47 @@ func TestMergeError(t *testing.T) {
}
}

func TestMergeErrorIs(t *testing.T) {
err1 := errors.New("first")
err2 := errors.New("second")
err3 := errors.New("third")

got := Merge(err1, err2)

if !errors.Is(got, err1) {
t.Errorf("merged error is not err1, want true, got false")
}
if !errors.Is(got, err2) {
t.Errorf("merged error is not err2, want true, got false")
}
if errors.Is(got, err3) {
t.Errorf("merged error is err3, want false, got true")
}
}

type testErrorAs struct{ msg string }

func (e testErrorAs) Error() string { return e.msg }

func TestMergeErrorAs(t *testing.T) {
err1 := &wrapped{msg: "err1"}
err2 := testErrorAs{msg: "err2"}

err := Merge(err1, err2)
got1 := &wrapped{}
if !errors.As(err, &got1) {
t.Errorf("merged error as wrapped failed, want true, got false")
} else if got1.msg != "err1" {
t.Errorf("merged error as wrapped did not assign the error, want msg=err1, got msg=%s", got1.msg)
}
got2 := testErrorAs{}
if !errors.As(err, &got2) {
t.Errorf("merged error as testErrorAs failed, want true, got false")
} else if got2.Error() != "err2" {
t.Errorf("merged error as testErrorAs did not assign the error, want msg=err2, got msg=%s", got2.Error())
}
}

func TestMergeNoError(t *testing.T) {
got := Merge(nil, nil)
if got != nil {
Expand Down
64 changes: 64 additions & 0 deletions internal/errors/stdlib.go
@@ -0,0 +1,64 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package errors

import (
stderrors "errors"
"fmt"
)

// New returns an error that formats as the given text.
// Each call to New returns a distinct error value even if the text is identical.
func New(text string) error { return stderrors.New(text) }

// Unwrap returns the result of calling the Unwrap method on err, if err's
// type contains an Unwrap method returning error.
// Otherwise, Unwrap returns nil.
func Unwrap(err error) error { return stderrors.Unwrap(err) }

// Is reports whether any error in err's chain matches target.
//
// The chain consists of err itself followed by the sequence of errors obtained by
// repeatedly calling Unwrap.
//
// An error is considered to match a target if it is equal to that target or if
// it implements a method Is(error) bool such that Is(target) returns true.
//
// An error type might provide an Is method so it can be treated as equivalent
// to an existing error. For example, if MyError defines
//
// func (m MyError) Is(target error) bool { return target == os.ErrExist }
//
// then Is(MyError{}, os.ErrExist) returns true. See syscall.Errno.Is for
// an example in the standard library.
func Is(err, target error) bool { return stderrors.Is(err, target) }

// As finds the first error in err's chain that matches target, and if so, sets
// target to that error value and returns true. Otherwise, it returns false.
//
// The chain consists of err itself followed by the sequence of errors obtained by
// repeatedly calling Unwrap.
//
// An error matches target if the error's concrete value is assignable to the value
// pointed to by target, or if the error has a method As(interface{}) bool such that
// As(target) returns true. In the latter case, the As method is responsible for
// setting target.
//
// An error type might provide an As method so it can be treated as if it were a
// different error type.
//
// As panics if target is not a non-nil pointer to either a type that implements
// error, or to any interface type.
func As(err error, target interface{}) bool { return stderrors.As(err, target) }

// Errorf formats according to a format specifier and returns the string as a
// value that satisfies error.
//
// If the format specifier includes a %w verb with an error operand,
// the returned error will implement an Unwrap method returning the operand. It is
// invalid to include more than one %w verb or to supply it with an operand
// that does not implement the error interface. The %w verb is otherwise
// a synonym for %v.
func Errorf(format string, a ...interface{}) error { return fmt.Errorf(format, a...) }
19 changes: 12 additions & 7 deletions protocol/internal/error.go
Expand Up @@ -11,20 +11,25 @@ type OpError struct {
Err error
}

func (e OpError) Error() string {
return fmt.Sprintf("cdp.%s: %s: %s", e.Domain, e.Op, e.Err.Error())
}

// Cause implements error causer.
func (e *OpError) Cause() error {
return e.Err
}

func (e OpError) Error() string {
return fmt.Sprintf("cdp.%s: %s: %s", e.Domain, e.Op, e.Err.Error())
// Unwrap implements Wrapper.
func (e *OpError) Unwrap() error {
return e.Err
}

type causer interface {
Cause() error
}
type causer interface{ Cause() error }
type wrapper interface{ Unwrap() error }

var (
_ error = (*OpError)(nil)
_ causer = (*OpError)(nil)
_ error = (*OpError)(nil)
_ causer = (*OpError)(nil)
_ wrapper = (*OpError)(nil)
)
5 changes: 3 additions & 2 deletions rpcc/conn.go
Expand Up @@ -25,8 +25,9 @@ func (e *closeError) Error() string {
}
return e.msg
}
func (e *closeError) Closed() bool { return true }
func (e *closeError) Cause() error { return e.err }
func (e *closeError) Closed() bool { return true }
func (e *closeError) Cause() error { return e.err }
func (e *closeError) Unwrap() error { return e.err }

var (
// ErrConnClosing indicates that the operation is illegal because
Expand Down
25 changes: 10 additions & 15 deletions session/manager.go
Expand Up @@ -70,22 +70,17 @@ func (m *Manager) watch(ev *sessionEvents, created <-chan *session, done, errC c
defer ev.Close()

isClosing := func(err error) bool {
for e := err;; {
// Test if this is an rpcc.closeError.
if v, ok := e.(interface{ Closed() bool }); ok && v.Closed() {
// Cleanup, the underlying connection was closed
// before the Manager and its context does not
// inherit from rpcc.Conn.
m.cancel()
return true
}
if v, ok := e.(errors.Causer); ok {
e = v.Cause()
} else {
break
}
// Test if this is an rpcc.closeError.
var e interface{ Closed() bool }
if ok := errors.As(err, &e); ok && e.Closed() {
// Cleanup, the underlying connection was closed
// before the Manager and its context does not
// inherit from rpcc.Conn.
m.cancel()
return true
}
if cdp.ErrorCause(err) == context.Canceled {

if errors.Is(err, context.Canceled) {
// Manager was closed.
return true
}
Expand Down
4 changes: 2 additions & 2 deletions session/manager_test.go
Expand Up @@ -64,7 +64,7 @@ func TestManager_ErrorsAreSentOnErrChan(t *testing.T) {
detached.err = errors.New("detach nope")
detached.markReady()
err := <-m.Err()
if err := errors.Cause(err); err != detached.err {
if !errors.Is(err, detached.err) {
t.Errorf("got error: %v; want: %v", err, detached.err)
}
detached.next()
Expand All @@ -73,7 +73,7 @@ func TestManager_ErrorsAreSentOnErrChan(t *testing.T) {
message.err = errors.New("message nope")
message.markReady()
err = <-m.Err()
if err := errors.Cause(err); err != message.err {
if !errors.Is(err, message.err) {
t.Errorf("got error: %v; want: %v", err, message.err)
}
message.next()
Expand Down
2 changes: 1 addition & 1 deletion session/session.go
Expand Up @@ -118,7 +118,7 @@ func dial(ctx context.Context, id target.ID, tc *cdp.Client, detachTimeout time.

err := tc.Target.DetachFromTarget(ctx,
target.NewDetachFromTargetArgs().SetSessionID(s.ID))
if err := cdp.ErrorCause(err); err == context.DeadlineExceeded {
if errors.Is(err, context.DeadlineExceeded) {
return fmt.Errorf("session: detach timed out for session %s", s.ID)
}
return errors.Wrapf(err, "session: detach failed for session %s", s.ID)
Expand Down