error types in provider_client for http errors
diff --git a/errors.go b/errors.go
index e47aff1..3de5363 100644
--- a/errors.go
+++ b/errors.go
@@ -4,7 +4,7 @@
// BaseError is an error type that all other error types embed.
type BaseError struct {
- OriginalError string
+ OriginalError error
Function string
}
@@ -14,7 +14,7 @@
// ErrInvalidInput is an error type used for most non-HTTP Gophercloud errors.
type ErrInvalidInput struct {
- BaseError
+ *BaseError
Argument string
Value interface{}
}
@@ -26,7 +26,7 @@
// ErrUnexpectedResponseCode is returned by the Request method when a response code other than
// those listed in OkCodes is encountered.
type ErrUnexpectedResponseCode struct {
- BaseError
+ *BaseError
URL string
Method string
Expected []int
@@ -41,27 +41,42 @@
)
}
+// ErrDefault400 is the default error type returned on a 400 HTTP response code.
type ErrDefault400 struct {
*ErrUnexpectedResponseCode
}
+
+// ErrDefault401 is the default error type returned on a 401 HTTP response code.
type ErrDefault401 struct {
*ErrUnexpectedResponseCode
}
+
+// ErrDefault404 is the default error type returned on a 404 HTTP response code.
type ErrDefault404 struct {
*ErrUnexpectedResponseCode
}
+
+// ErrDefault405 is the default error type returned on a 405 HTTP response code.
type ErrDefault405 struct {
*ErrUnexpectedResponseCode
}
+
+// ErrDefault408 is the default error type returned on a 408 HTTP response code.
type ErrDefault408 struct {
*ErrUnexpectedResponseCode
}
+
+// ErrDefault429 is the default error type returned on a 429 HTTP response code.
type ErrDefault429 struct {
*ErrUnexpectedResponseCode
}
+
+// ErrDefault500 is the default error type returned on a 500 HTTP response code.
type ErrDefault500 struct {
*ErrUnexpectedResponseCode
}
+
+// ErrDefault503 is the default error type returned on a 503 HTTP response code.
type ErrDefault503 struct {
*ErrUnexpectedResponseCode
}
@@ -139,6 +154,7 @@
Error503(*ErrUnexpectedResponseCode) error
}
+// ErrTimeOut is the error type returned when an operations times out.
type ErrTimeOut struct {
*BaseError
}
@@ -147,6 +163,7 @@
return "A time out occurred"
}
+// ErrUnableToReauthenticate is the error type returned when reauthentication fails.
type ErrUnableToReauthenticate struct {
*BaseError
}
@@ -155,6 +172,8 @@
return fmt.Sprintf("Unable to re-authenticate: %s", e.OriginalError)
}
+// ErrErrorAfterReauthentication is the error type returned when reauthentication
+// succeeds, but an error occurs afterword (usually an HTTP error).
type ErrErrorAfterReauthentication struct {
*BaseError
}
diff --git a/provider_client.go b/provider_client.go
index 4f25589..1e4ab06 100644
--- a/provider_client.go
+++ b/provider_client.go
@@ -3,10 +3,12 @@
import (
"bytes"
"encoding/json"
+ "errors"
"fmt"
"io"
"io/ioutil"
"net/http"
+ "runtime"
"strings"
)
@@ -98,6 +100,9 @@
// provided with a blank value (""), that header will be *omitted* instead: use this to suppress
// the default Accept header or an inferred Content-Type, for example.
MoreHeaders map[string]string
+ // ErrorType specifies the resource error type to return if an error is encountered.
+ // This lets resources override default error messages based on the response status code.
+ ErrorContext error
}
var applicationJSON = "application/json"
@@ -202,13 +207,104 @@
if !ok {
body, _ := ioutil.ReadAll(resp.Body)
resp.Body.Close()
- return resp, &ErrUnexpectedResponseCode{
+ pc := make([]uintptr, 1) // at least 1 entry needed
+ runtime.Callers(2, pc)
+ f := runtime.FuncForPC(pc[0])
+ respErr := &ErrUnexpectedResponseCode{
+ BaseError: &BaseError{
+ Function: f.Name(),
+ },
URL: url,
Method: method,
Expected: options.OkCodes,
Actual: resp.StatusCode,
Body: body,
}
+
+ errType := options.ErrorContext
+ switch resp.StatusCode {
+ case http.StatusBadRequest:
+ err = ErrDefault400{respErr}
+ if error400er, ok := errType.(Err400er); ok {
+ err = error400er.Error400(respErr)
+ }
+ case http.StatusUnauthorized:
+ if client.ReauthFunc != nil {
+ err = client.ReauthFunc()
+ if err != nil {
+ return nil, &ErrUnableToReauthenticate{
+ &BaseError{
+ OriginalError: respErr,
+ },
+ }
+ }
+ if options.RawBody != nil {
+ seeker, ok := options.RawBody.(io.Seeker)
+ if !ok {
+ return nil, &ErrErrorAfterReauthentication{
+ &BaseError{
+ OriginalError: errors.New("Couldn't seek to beginning of content."),
+ },
+ }
+ }
+ seeker.Seek(0, 0)
+ }
+ resp, err = client.Request(method, url, options)
+ if err != nil {
+ switch err.(type) {
+ case *ErrUnexpectedResponseCode:
+ return nil, &ErrErrorAfterReauthentication{&BaseError{OriginalError: err.(*ErrUnexpectedResponseCode)}}
+ default:
+ return nil, &ErrErrorAfterReauthentication{
+ &BaseError{
+ OriginalError: err,
+ },
+ }
+ }
+ }
+ return resp, nil
+ }
+ err = ErrDefault401{respErr}
+ if error401er, ok := errType.(Err401er); ok {
+ err = error401er.Error401(respErr)
+ }
+ case http.StatusNotFound:
+ err = ErrDefault404{respErr}
+ if error404er, ok := errType.(Err404er); ok {
+ err = error404er.Error404(respErr)
+ }
+ case http.StatusMethodNotAllowed:
+ err = ErrDefault405{respErr}
+ if error405er, ok := errType.(Err405er); ok {
+ err = error405er.Error405(respErr)
+ }
+ case http.StatusRequestTimeout:
+ err = ErrDefault408{respErr}
+ if error408er, ok := errType.(Err408er); ok {
+ err = error408er.Error408(respErr)
+ }
+ case 429:
+ err = ErrDefault429{respErr}
+ if error429er, ok := errType.(Err429er); ok {
+ err = error429er.Error429(respErr)
+ }
+ case http.StatusInternalServerError:
+ err = ErrDefault500{respErr}
+ if error500er, ok := errType.(Err500er); ok {
+ err = error500er.Error500(respErr)
+ }
+ case http.StatusServiceUnavailable:
+ err = ErrDefault503{respErr}
+ if error503er, ok := errType.(Err503er); ok {
+ err = error503er.Error503(respErr)
+ }
+ }
+
+ if err == nil {
+ err = respErr
+ }
+
+ return resp, err
}
// Parse the response body as JSON, if requested to do so.