Skip to content

Commit

Permalink
Merge pull request #265 from peczenyj/add-method-temporary-on-amqp-error
Browse files Browse the repository at this point in the history
add methods Temporary and Recoverable to amqp.Error
  • Loading branch information
Zerpet authored Jun 6, 2024
2 parents afb3ba3 + 3e232fa commit 2d7a4f4
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 10 deletions.
48 changes: 47 additions & 1 deletion types.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ var (
errInvalidTypeAssertion = &Error{Code: InternalError, Reason: "type assertion unsuccessful", Server: false, Recover: true}
)

var _ error = (*Error)(nil)

// Error captures the code and reason a channel or connection has been closed
// by the server.
type Error struct {
Expand All @@ -88,10 +90,54 @@ func newError(code uint16, text string) *Error {
}
}

func (e Error) Error() string {
func (e *Error) Error() string {
return fmt.Sprintf("Exception (%d) Reason: %q", e.Code, e.Reason)
}

// Recoverable returns true if the error can be recovered by retrying later or with different parameters.
// Returns the value of the Recover field.
func (e *Error) Recoverable() bool {
return e.Recover
}

// Temporary returns true if the error can be recovered by retrying later with the same parameters.
//
// The following are the codes which might be resolved by retry without external
// action, according to the AMQP 0.91 spec
// (https://www.rabbitmq.com/amqp-0-9-1-reference.html#constants). The quotations
// are from that page.
//
// ContentTooLarge (311)
// "The client attempted to transfer content larger than the server could
// accept at the present time. The client may retry at a later time."
//
// ConnectionForced (320)
// "An operator intervened to close the connection for some reason. The
// client may retry at some later date."
func (e *Error) Temporary() bool {
// amqp.Error has a Recover field which sounds like it should mean "retryable".
// But it actually means "can be recovered by retrying later or with different
// parameters," which is not what we want. The error codes for which Recover is
// true, defined in the isSoftExceptionCode function, including things
// like NotFound and AccessRefused, which require outside action.
switch e.Code {
case ContentTooLarge:
return true

case ConnectionForced:
return true

default:
return false
}
}

// GoString returns a longer description of the error than .Error() including all fields.
func (e *Error) GoString() string {
return fmt.Sprintf("Exception=%d, Reason=%q, Recover=%v, Server=%v",
e.Code, e.Reason, e.Recover, e.Server)
}

// Used by header frames to capture routing and header information
type properties struct {
ContentType string // MIME content type
Expand Down
49 changes: 40 additions & 9 deletions types_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package amqp091

import (
"fmt"
"testing"
"time"
)
Expand All @@ -10,24 +11,54 @@ func TestNewError(t *testing.T) {
code uint16
text string
expectedServer bool
recoverable bool
temporary bool
}{
// Just three basics samples
{404, "Not Found", true},
{500, "Internal Server Error", true},
{403, "Forbidden", true},
{404, "Not Found", true, true, false},
{500, "Internal Server Error", true, false, false},
{403, "Forbidden", true, true, false},
{311, "Content Too Large", true, true, true},
}

for _, tc := range testCases {
err := newError(tc.code, tc.text)
if err.Code != int(tc.code) {
t.Errorf("expected Code %d, got %d", tc.code, err.Code)
aerr := newError(tc.code, tc.text)
if aerr.Code != int(tc.code) {
t.Errorf("expected Code %d, got %d", tc.code, aerr.Code)
}
if err.Reason != tc.text {
t.Errorf("expected Reason %s, got %s", tc.text, err.Reason)
if aerr.Reason != tc.text {
t.Errorf("expected Reason %s, got %s", tc.text, aerr.Reason)
}
if err.Server != tc.expectedServer {
if aerr.Server != tc.expectedServer {
t.Errorf("expected Server to be %v", tc.expectedServer)
}
if aerr.Recover != tc.recoverable {
t.Errorf("expected Recover to be %v", tc.recoverable)
}

if ok := aerr.Recoverable(); ok != tc.recoverable {
t.Errorf("expected err to be temporary %v", tc.recoverable)
}

if ok := aerr.Temporary(); ok != tc.temporary {
t.Errorf("expected err to be retriable %v", tc.recoverable)
}
}
}

func TestErrorMessage(t *testing.T) {
var err error = newError(404, "Not Found")

expected := `Exception (404) Reason: "Not Found"`

if got := err.Error(); expected != got {
t.Errorf("expected Error %q, got %q", expected, got)
}

expected = `Exception=404, Reason="Not Found", Recover=true, Server=true`

if got := fmt.Sprintf("%#v", err); expected != got {
t.Errorf("expected go string %q, got %q", expected, got)
}
}

Expand Down

0 comments on commit 2d7a4f4

Please sign in to comment.