Skip to content

Commit

Permalink
Return idiomatic generic error instead of awserr.Error interface.
Browse files Browse the repository at this point in the history
Instead of returning an awserr.Error interface the SDK now returns the generic error interface.
This addresses issues where using the SDK would complicate other non-SDK function calls which also
returned errors.

In order to retrieve the Code, Message, and Original Error the returned error will need to be cast to
an awserr inteface. To only retrieve the base generic information you can cast to awserr.Error which will
provide access to the Code, Message and OrigErr.  In order to retrieve more specific information such
as for request failures (awserr.RequestFailure) you can cast to more specific interface types. The
specific error interface types all supersets of the base awserr.Error.

Example Usage:
    resp, err := s3Svc.ListBuckets(&s3.ListBucketsInput{})

    if awsErr, ok := err.(awserr.Error); ok {
        // Generic AWS Error with Code, Message, and original error (if any)
        fmt.Println(awsErr.Code(), awsErr.Message(), awsErr.OrigErr())
        if reqErr, ok := err.(awserr.RequestFailure); ok {
            // A service error occurred
            fmt.Println(reqErr.StatusCode(), reqErr.RequestID())
        }
    } else {
        fmt.Println(err.Error())
    }

Fixes #238
  • Loading branch information
jasdel committed May 20, 2015
1 parent a1eab2b commit be94f7c
Show file tree
Hide file tree
Showing 158 changed files with 12,475 additions and 8,892 deletions.
3 changes: 1 addition & 2 deletions aws/credentials/chain_provider.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package credentials

import (
"github.com/awslabs/aws-sdk-go/aws/awserr"
"github.com/awslabs/aws-sdk-go/internal/apierr"
)

Expand Down Expand Up @@ -57,7 +56,7 @@ func NewChainCredentials(providers []Provider) *Credentials {
//
// If a provider is found it will be cached and any calls to IsExpired()
// will return the expired state of the cached provider.
func (c *ChainProvider) Retrieve() (Value, awserr.Error) {
func (c *ChainProvider) Retrieve() (Value, error) {
for _, p := range c.Providers {
if creds, err := p.Retrieve(); err == nil {
c.curr = p
Expand Down
5 changes: 2 additions & 3 deletions aws/credentials/credentials.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@
package credentials

import (
"github.com/awslabs/aws-sdk-go/aws/awserr"
"sync"
"time"
)
Expand Down Expand Up @@ -87,7 +86,7 @@ type Value struct {
type Provider interface {
// Refresh returns nil if it successfully retrieved the value.
// Error is returned if the value were not obtainable, or empty.
Retrieve() (Value, awserr.Error)
Retrieve() (Value, error)

// IsExpired returns if the credentials are no longer valid, and need
// to be retrieved.
Expand Down Expand Up @@ -130,7 +129,7 @@ func NewCredentials(provider Provider) *Credentials {
//
// If Credentials.Expire() was called the credentials Value will be force
// expired, and the next call to Get() will cause them to be refreshed.
func (c *Credentials) Get() (Value, awserr.Error) {
func (c *Credentials) Get() (Value, error) {
c.m.Lock()
defer c.m.Unlock()

Expand Down
6 changes: 3 additions & 3 deletions aws/credentials/credentials_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ import (
type stubProvider struct {
creds Value
expired bool
err awserr.Error
err error
}

func (s *stubProvider) Retrieve() (Value, awserr.Error) {
func (s *stubProvider) Retrieve() (Value, error) {
s.expired = false
return s.creds, s.err
}
Expand Down Expand Up @@ -42,7 +42,7 @@ func TestCredentialsGetWithError(t *testing.T) {
c := NewCredentials(&stubProvider{err: apierr.New("provider error", "", nil), expired: true})

_, err := c.Get()
assert.Equal(t, "provider error", err.Code(), "Expected provider error")
assert.Equal(t, "provider error", err.(awserr.Error).Code(), "Expected provider error")
}

func TestCredentialsExpire(t *testing.T) {
Expand Down
7 changes: 3 additions & 4 deletions aws/credentials/ec2_role_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"bufio"
"encoding/json"
"fmt"
"github.com/awslabs/aws-sdk-go/aws/awserr"
"github.com/awslabs/aws-sdk-go/internal/apierr"
"net/http"
"time"
Expand Down Expand Up @@ -77,7 +76,7 @@ func NewEC2RoleCredentials(client *http.Client, endpoint string, window time.Dur
// Retrieve retrieves credentials from the EC2 service.
// Error will be returned if the request fails, or unable to extract
// the desired credentials.
func (m *EC2RoleProvider) Retrieve() (Value, awserr.Error) {
func (m *EC2RoleProvider) Retrieve() (Value, error) {
if m.Client == nil {
m.Client = http.DefaultClient
}
Expand Down Expand Up @@ -129,7 +128,7 @@ type ec2RoleCredRespBody struct {

// requestCredList requests a list of credentials from the EC2 service.
// If there are no credentials, or there is an error making or receiving the request
func requestCredList(client *http.Client, endpoint string) ([]string, awserr.Error) {
func requestCredList(client *http.Client, endpoint string) ([]string, error) {
resp, err := client.Get(endpoint)
if err != nil {
return nil, apierr.New("ListEC2Role", "failed to list EC2 Roles", err)
Expand All @@ -153,7 +152,7 @@ func requestCredList(client *http.Client, endpoint string) ([]string, awserr.Err
//
// If the credentials cannot be found, or there is an error reading the response
// and error will be returned.
func requestCred(client *http.Client, endpoint, credsName string) (*ec2RoleCredRespBody, awserr.Error) {
func requestCred(client *http.Client, endpoint, credsName string) (*ec2RoleCredRespBody, error) {
resp, err := client.Get(endpoint + credsName)
if err != nil {
return nil, apierr.New("GetEC2RoleCredentials",
Expand Down
3 changes: 1 addition & 2 deletions aws/credentials/env_provider.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package credentials

import (
"github.com/awslabs/aws-sdk-go/aws/awserr"
"github.com/awslabs/aws-sdk-go/internal/apierr"
"os"
)
Expand Down Expand Up @@ -32,7 +31,7 @@ func NewEnvCredentials() *Credentials {
}

// Retrieve retrieves the keys from the environment.
func (e *EnvProvider) Retrieve() (Value, awserr.Error) {
func (e *EnvProvider) Retrieve() (Value, error) {
e.retrieved = false

id := os.Getenv("AWS_ACCESS_KEY_ID")
Expand Down
7 changes: 3 additions & 4 deletions aws/credentials/shared_credentials_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (

"github.com/vaughan0/go-ini"

"github.com/awslabs/aws-sdk-go/aws/awserr"
"github.com/awslabs/aws-sdk-go/internal/apierr"
)

Expand Down Expand Up @@ -45,7 +44,7 @@ func NewSharedCredentials(filename, profile string) *Credentials {

// Retrieve reads and extracts the shared credentials from the current
// users home directory.
func (p *SharedCredentialsProvider) Retrieve() (Value, awserr.Error) {
func (p *SharedCredentialsProvider) Retrieve() (Value, error) {
p.retrieved = false

filename, err := p.filename()
Expand All @@ -70,7 +69,7 @@ func (p *SharedCredentialsProvider) IsExpired() bool {
// loadProfiles loads from the file pointed to by shared credentials filename for profile.
// The credentials retrieved from the profile will be returned or error. Error will be
// returned if it fails to read from the file, or the data is invalid.
func loadProfile(filename, profile string) (Value, awserr.Error) {
func loadProfile(filename, profile string) (Value, error) {
config, err := ini.LoadFile(filename)
if err != nil {
return Value{}, apierr.New("SharedCredsLoad", "failed to load shared credentials file", err)
Expand Down Expand Up @@ -103,7 +102,7 @@ func loadProfile(filename, profile string) (Value, awserr.Error) {
// filename returns the filename to use to read AWS shared credentials.
//
// Will return an error if the user's home directory path cannot be found.
func (p *SharedCredentialsProvider) filename() (string, awserr.Error) {
func (p *SharedCredentialsProvider) filename() (string, error) {
if p.Filename == "" {
homeDir := os.Getenv("HOME") // *nix
if homeDir == "" { // Windows
Expand Down
3 changes: 1 addition & 2 deletions aws/credentials/static_provider.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package credentials

import (
"github.com/awslabs/aws-sdk-go/aws/awserr"
"github.com/awslabs/aws-sdk-go/internal/apierr"
)

Expand All @@ -27,7 +26,7 @@ func NewStaticCredentials(id, secret, token string) *Credentials {
}

// Retrieve returns the credentials or error if the credentials are invalid.
func (s *StaticProvider) Retrieve() (Value, awserr.Error) {
func (s *StaticProvider) Retrieve() (Value, error) {
if s.AccessKeyID == "" || s.SecretAccessKey == "" {
return Value{}, ErrStaticCredentialsEmpty
}
Expand Down
16 changes: 9 additions & 7 deletions aws/handler_functions.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package aws
import (
"bytes"
"fmt"
"github.com/awslabs/aws-sdk-go/aws/awserr"
"github.com/awslabs/aws-sdk-go/internal/apierr"
"io"
"io/ioutil"
Expand All @@ -12,6 +11,7 @@ import (
"regexp"
"strconv"
"time"
"github.com/awslabs/aws-sdk-go/aws/awserr"
)

var sleepDelay = func(delay time.Duration) {
Expand Down Expand Up @@ -108,10 +108,12 @@ func AfterRetryHandler(r *Request) {
// when the expired token exception occurs the credentials
// need to be expired locally so that the next request to
// get credentials will trigger a credentials refresh.
if r.Error != nil && r.Error.Code() == "ExpiredTokenException" {
r.Config.Credentials.Expire()
// The credentials will need to be resigned with new credentials
r.signed = false
if r.Error != nil {
if err, ok := r.Error.(awserr.Error); ok && err.Code() == "ExpiredTokenException" {
r.Config.Credentials.Expire()
// The credentials will need to be resigned with new credentials
r.signed = false
}
}

r.RetryCount++
Expand All @@ -122,11 +124,11 @@ func AfterRetryHandler(r *Request) {
var (
// ErrMissingRegion is an error that is returned if region configuration is
// not found.
ErrMissingRegion awserr.Error = apierr.New("MissingRegion", "could not find region configuration", nil)
ErrMissingRegion error = apierr.New("MissingRegion", "could not find region configuration", nil)

// ErrMissingEndpoint is an error that is returned if an endpoint cannot be
// resolved for a service.
ErrMissingEndpoint awserr.Error = apierr.New("MissingEndpoint", "'Endpoint' configuration is required for this service", nil)
ErrMissingEndpoint error = apierr.New("MissingEndpoint", "'Endpoint' configuration is required for this service", nil)
)

// ValidateEndpointHandler is a request handler to validate a request had the
Expand Down
3 changes: 1 addition & 2 deletions aws/handler_functions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"os"
"testing"

"github.com/awslabs/aws-sdk-go/aws/awserr"
"github.com/awslabs/aws-sdk-go/aws/credentials"
"github.com/awslabs/aws-sdk-go/internal/apierr"
"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -41,7 +40,7 @@ type mockCredsProvider struct {
retreiveCalled bool
}

func (m *mockCredsProvider) Retrieve() (credentials.Value, awserr.Error) {
func (m *mockCredsProvider) Retrieve() (credentials.Value, error) {
m.retreiveCalled = true
return credentials.Value{}, nil
}
Expand Down
9 changes: 5 additions & 4 deletions aws/param_validator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (

"github.com/awslabs/aws-sdk-go/aws"
"github.com/stretchr/testify/assert"
"github.com/awslabs/aws-sdk-go/aws/awserr"
)

var service = func() *aws.Service {
Expand Down Expand Up @@ -58,8 +59,8 @@ func TestMissingRequiredParameters(t *testing.T) {
aws.ValidateParameters(req)

assert.Error(t, req.Error)
assert.Equal(t, "InvalidParameter", req.Error.Code())
assert.Equal(t, "3 validation errors:\n- missing required parameter: RequiredList\n- missing required parameter: RequiredMap\n- missing required parameter: RequiredBool", req.Error.Message())
assert.Equal(t, "InvalidParameter", req.Error.(awserr.Error).Code())
assert.Equal(t, "3 validation errors:\n- missing required parameter: RequiredList\n- missing required parameter: RequiredMap\n- missing required parameter: RequiredBool", req.Error.(awserr.Error).Message())
}

func TestNestedMissingRequiredParameters(t *testing.T) {
Expand All @@ -77,7 +78,7 @@ func TestNestedMissingRequiredParameters(t *testing.T) {
aws.ValidateParameters(req)

assert.Error(t, req.Error)
assert.Equal(t, "InvalidParameter", req.Error.Code())
assert.Equal(t, "3 validation errors:\n- missing required parameter: RequiredList[0].Name\n- missing required parameter: RequiredMap[\"key2\"].Name\n- missing required parameter: OptionalStruct.Name", req.Error.Message())
assert.Equal(t, "InvalidParameter", req.Error.(awserr.Error).Code())
assert.Equal(t, "3 validation errors:\n- missing required parameter: RequiredList[0].Name\n- missing required parameter: RequiredMap[\"key2\"].Name\n- missing required parameter: OptionalStruct.Name", req.Error.(awserr.Error).Message())

}
11 changes: 5 additions & 6 deletions aws/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package aws

import (
"bytes"
"github.com/awslabs/aws-sdk-go/aws/awserr"
"io"
"io/ioutil"
"net/http"
Expand All @@ -24,7 +23,7 @@ type Request struct {
Body io.ReadSeeker
bodyStart int64 // offset from beginning of Body that the request body starts
Params interface{}
Error awserr.Error
Error error
Data interface{}
RequestID string
RetryCount uint
Expand Down Expand Up @@ -116,7 +115,7 @@ func (r *Request) SetReaderBody(reader io.ReadSeeker) {

// Presign returns the request's signed URL. Error will be returned
// if the signing fails.
func (r *Request) Presign(expireTime time.Duration) (string, awserr.Error) {
func (r *Request) Presign(expireTime time.Duration) (string, error) {
r.ExpireTime = expireTime
r.Sign()
if r.Error != nil {
Expand All @@ -135,7 +134,7 @@ func (r *Request) Presign(expireTime time.Duration) (string, awserr.Error) {
//
// If any Validate or Build errors occur the build will stop and the error
// which occurred will be returned.
func (r *Request) Build() awserr.Error {
func (r *Request) Build() error {
if !r.built {
r.Error = nil
r.Handlers.Validate.Run(r)
Expand All @@ -153,7 +152,7 @@ func (r *Request) Build() awserr.Error {
//
// Send will build the request prior to signing. All Sign Handlers will
// be executed in the order they were set.
func (r *Request) Sign() awserr.Error {
func (r *Request) Sign() error {
if r.signed {
return r.Error
}
Expand All @@ -172,7 +171,7 @@ func (r *Request) Sign() awserr.Error {
//
// Send will sign the request prior to sending. All Send Handlers will
// be executed in the order they were set.
func (r *Request) Send() awserr.Error {
func (r *Request) Send() error {
for {
r.Sign()
if r.Error != nil {
Expand Down
10 changes: 5 additions & 5 deletions aws/request_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,8 +135,8 @@ func TestRequest4xxUnretryable(t *testing.T) {
} else {
assert.Fail(t, "Expected error to be a service failure")
}
assert.Equal(t, "SignatureDoesNotMatch", err.Code())
assert.Equal(t, "Signature does not match.", err.Message())
assert.Equal(t, "SignatureDoesNotMatch", err.(awserr.Error).Code())
assert.Equal(t, "Signature does not match.", err.(awserr.Error).Message())
assert.Equal(t, 0, int(r.RetryCount))
}

Expand Down Expand Up @@ -171,8 +171,8 @@ func TestRequestExhaustRetries(t *testing.T) {
} else {
assert.Fail(t, "Expected error to be a service failure")
}
assert.Equal(t, "UnknownError", err.Code())
assert.Equal(t, "An error occurred.", err.Message())
assert.Equal(t, "UnknownError", err.(awserr.Error).Code())
assert.Equal(t, "An error occurred.", err.(awserr.Error).Message())
assert.Equal(t, 3, int(r.RetryCount))
assert.True(t, reflect.DeepEqual([]time.Duration{30 * time.Millisecond, 60 * time.Millisecond, 120 * time.Millisecond}, delays))
}
Expand All @@ -194,7 +194,7 @@ func TestRequestRecoverExpiredCreds(t *testing.T) {
credExpiredAfterRetry := false

s.Handlers.Retry.PushBack(func(r *Request) {
if r.Error != nil && r.Error.Code() == "ExpiredTokenException" {
if r.Error != nil && r.Error.(awserr.Error).Code() == "ExpiredTokenException" {
credExpiredBeforeRetry = r.Config.Credentials.IsExpired()
}
})
Expand Down
7 changes: 5 additions & 2 deletions aws/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"time"

"github.com/awslabs/aws-sdk-go/internal/endpoints"
"github.com/awslabs/aws-sdk-go/aws/awserr"
)

// A Service implements the base service request and response handling
Expand Down Expand Up @@ -146,8 +147,10 @@ func shouldRetry(r *Request) bool {
return true
}
if r.Error != nil {
if _, ok := retryableCodes[r.Error.Code()]; ok {
return true
if err, ok := r.Error.(awserr.Error); ok {
if _, ok := retryableCodes[err.Code()]; ok {
return true
}
}
}
return false
Expand Down
1 change: 0 additions & 1 deletion internal/fixtures/protocol/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@ var extraImports = []string{
"net/url",
"github.com/awslabs/aws-sdk-go/internal/protocol/xml/xmlutil",
"github.com/awslabs/aws-sdk-go/internal/util",
"github.com/awslabs/aws-sdk-go/aws/awserr",
"github.com/stretchr/testify/assert",
}

Expand Down
2 changes: 0 additions & 2 deletions internal/model/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,6 @@ var oprw sync.Mutex
func (a *API) APIGoCode() string {
a.resetImports()
a.imports["sync"] = true
a.imports["github.com/awslabs/aws-sdk-go/aws/awserr"] = true
var buf bytes.Buffer
err := tplAPI.Execute(&buf, a)
if err != nil {
Expand Down Expand Up @@ -325,7 +324,6 @@ func (a *API) InterfaceGoCode() string {
a.resetImports()
a.imports = map[string]bool{
"github.com/awslabs/aws-sdk-go/service/" + a.PackageName(): true,
"github.com/awslabs/aws-sdk-go/aws/awserr": true,
}

var buf bytes.Buffer
Expand Down
Loading

0 comments on commit be94f7c

Please sign in to comment.