3408: Propagate API error messages to caller.
authorTom Clegg <tom@curoverse.com>
Thu, 8 Jan 2015 20:50:42 +0000 (15:50 -0500)
committerTom Clegg <tom@curoverse.com>
Thu, 8 Jan 2015 20:50:42 +0000 (15:50 -0500)
sdk/go/arvadosclient/arvadosclient.go
sdk/go/arvadosclient/arvadosclient_test.go

index 3d5aff7b2325b4d93e3a0afacd71c44329c42f6f..5ea2524aa63c730632c567edb10132224b919b3a 100644 (file)
@@ -12,15 +12,20 @@ import (
        "net/http"
        "net/url"
        "os"
+       "strings"
 )
 
 // Errors
 var MissingArvadosApiHost = errors.New("Missing required environment variable ARVADOS_API_HOST")
 var MissingArvadosApiToken = errors.New("Missing required environment variable ARVADOS_API_TOKEN")
-var ArvadosErrorForbidden = errors.New("Forbidden")
-var ArvadosErrorNotFound = errors.New("Not found")
-var ArvadosErrorBadRequest = errors.New("Bad request")
-var ArvadosErrorServerError = errors.New("Server error")
+
+type ArvadosApiError struct {
+       error
+       HttpStatusCode int
+       HttpStatus string
+}
+
+func (e ArvadosApiError) Error() string { return e.error.Error() }
 
 // Helper type so we don't have to write out 'map[string]interface{}' every time.
 type Dict map[string]interface{}
@@ -137,23 +142,35 @@ func (this ArvadosClient) CallRaw(method string, resource string, uuid string, a
                return nil, err
        }
 
-       switch resp.StatusCode {
-       case http.StatusOK:
+       if resp.StatusCode == http.StatusOK {
                return resp.Body, nil
-       case http.StatusForbidden:
-               resp.Body.Close()
-               return nil, ArvadosErrorForbidden
-       case http.StatusNotFound:
-               resp.Body.Close()
-               return nil, ArvadosErrorNotFound
-       default:
-               resp.Body.Close()
-               if resp.StatusCode >= 400 && resp.StatusCode <= 499 {
-                       return nil, ArvadosErrorBadRequest
-               } else {
-                       return nil, ArvadosErrorServerError
+       }
+
+       defer resp.Body.Close()
+       errorText := fmt.Sprintf("API response: %s", resp.Status)
+
+       // If the response body has {"errors":["reason1","reason2"]}
+       // then return those reasons.
+       var errInfo = Dict{}
+       if err := json.NewDecoder(resp.Body).Decode(&errInfo); err == nil {
+               if errorList, ok := errInfo["errors"]; ok {
+                       var errorStrings []string
+                       if errArray, ok := errorList.([]interface{}); ok {
+                               for _, errItem := range errArray {
+                                       // We expect an array of strings here.
+                                       // Non-strings will be passed along
+                                       // JSON-encoded.
+                                       if s, ok := errItem.(string); ok {
+                                               errorStrings = append(errorStrings, s)
+                                       } else if j, err := json.Marshal(errItem); err == nil {
+                                               errorStrings = append(errorStrings, string(j))
+                                       }
+                               }
+                               errorText = strings.Join(errorStrings, "; ")
+                       }
                }
        }
+       return nil, ArvadosApiError{errors.New(errorText), resp.StatusCode, resp.Status}
 }
 
 // Access to a resource.
index ca1d93319de77044e66ae49cd9549e5d4d01688f..bf9b4e31c41dbb5ba974e1da3b4d1dbb99cda7e3 100644 (file)
@@ -89,3 +89,31 @@ func (s *ServerRequiredSuite) TestCreatePipelineTemplate(c *C) {
        c.Assert(err, Equals, nil)
        c.Assert(getback["name"], Equals, "tmp2")
 }
+
+func (s *ServerRequiredSuite) TestErrorResponse(c *C) {
+       os.Setenv("ARVADOS_API_HOST", "localhost:3000")
+       os.Setenv("ARVADOS_API_TOKEN", "4axaw8zxe0qm22wa6urpp5nskcne8z88cvbupv653y1njyi05h")
+       os.Setenv("ARVADOS_API_HOST_INSECURE", "true")
+
+       arv, _ := MakeArvadosClient()
+
+       getback := make(Dict)
+
+       {
+               err := arv.Create("logs",
+                       Dict{"log": Dict{"bogus_attr": "foo"}},
+                       &getback)
+               c.Assert(err, ErrorMatches, ".*unknown attribute: bogus_attr.*")
+               c.Assert(err, FitsTypeOf, ArvadosApiError{})
+               c.Assert(err.(ArvadosApiError).HttpStatusCode, Equals, 422)
+       }
+
+       {
+               err := arv.Create("bogus",
+                       Dict{"bogus": Dict{}},
+                       &getback)
+               c.Assert(err, ErrorMatches, "Path not found")
+               c.Assert(err, FitsTypeOf, ArvadosApiError{})
+               c.Assert(err.(ArvadosApiError).HttpStatusCode, Equals, 404)
+       }
+}