Skip to content

Commit

Permalink
Wrap error (#65)
Browse files Browse the repository at this point in the history
  • Loading branch information
at-wat authored Dec 30, 2019
1 parent 52f1c59 commit 7975b1a
Show file tree
Hide file tree
Showing 25 changed files with 368 additions and 36 deletions.
4 changes: 3 additions & 1 deletion client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import (
"net"
"testing"
"time"

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

func TestProtocolViolation(t *testing.T) {
Expand Down Expand Up @@ -79,7 +81,7 @@ func TestProtocolViolation(t *testing.T) {

select {
case err := <-errCh:
if err != ErrInvalidPacket {
if !errs.Is(err, ErrInvalidPacket) {
t.Errorf("Expected error against invalid packet: '%v', got: '%v'", ErrInvalidPacket, err)
}
case <-ctx.Done():
Expand Down
2 changes: 1 addition & 1 deletion conn.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ func (d *DialOptions) dial(urlStr string) (*BaseClient, error) {
ws.PayloadType = websocket.BinaryFrame
c.Transport = ws
default:
return nil, ErrUnsupportedProtocol
return nil, wrapErrorf(ErrUnsupportedProtocol, "protocol %s", u.Scheme)
}
return c, nil
}
Expand Down
4 changes: 2 additions & 2 deletions connack.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,10 @@ type pktConnAck struct {

func (p *pktConnAck) parse(flag byte, contents []byte) (*pktConnAck, error) {
if flag != 0 {
return nil, ErrInvalidPacket
return nil, wrapError(ErrInvalidPacket, "parsing CONNACK")
}
if len(contents) != 2 {
return nil, ErrInvalidPacketLength
return nil, wrapError(ErrInvalidPacketLength, "parsing CONNACK")
}
return &pktConnAck{
SessionPresent: (contents[0]&0x01 != 0),
Expand Down
2 changes: 1 addition & 1 deletion connect.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ func (c *BaseClient) Connect(ctx context.Context, clientID string, opts ...Conne
}).pack()

if err := c.write(pkt); err != nil {
return false, err
return false, wrapError(err, "sending CONNECT")
}
select {
case <-c.connClosed:
Expand Down
2 changes: 1 addition & 1 deletion disconnect.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ func (c *BaseClient) Disconnect(ctx context.Context) error {
pkt := pack(packetDisconnect.b())
c.connStateUpdate(StateDisconnected)
if err := c.write(pkt); err != nil {
return err
return wrapError(err, "sending DISCONNECT")
}
c.Transport.Close()
return nil
Expand Down
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 mqtt-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 mqtt

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...))
}
87 changes: 87 additions & 0 deletions error_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// Copyright 2019 The mqtt-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 mqtt

import (
"errors"
"testing"

"github.com/at-wat/mqtt-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")
errDoubleChained := wrapErrorf(errChained, "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 !errDoubleChained.(*Error).Is(errBase) {
t.Errorf("Wrapped error '%v' doesn't match '%v'", errDoubleChained, 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())
}
}
6 changes: 3 additions & 3 deletions filter.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,20 @@ type topicFilter []string

func newTopicFilter(filter string) (topicFilter, error) {
if len(filter) == 0 {
return nil, ErrInvalidTopicFilter
return nil, wrapError(ErrInvalidTopicFilter, "empty filter")
}
tf := strings.Split(filter, "/")

// Validate according to MQTT 3.1.1 spec. 4.7.1
for i, f := range tf {
if strings.Contains(f, "+") {
if len(f) != 1 {
return nil, ErrInvalidTopicFilter
return nil, wrapError(ErrInvalidTopicFilter, "invalid + usage")
}
}
if strings.Contains(f, "#") {
if len(f) != 1 || i != len(tf)-1 {
return nil, ErrInvalidTopicFilter
return nil, wrapError(ErrInvalidTopicFilter, "invalid # usage")
}
}
}
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 mqtt-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 mqtt-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 7975b1a

Please sign in to comment.