Decode errors returned to clients by the Watch interface

This commit is contained in:
Clayton Coleman 2015-03-23 23:15:35 -04:00
parent 30358e8b04
commit 524f8731b5
2 changed files with 101 additions and 46 deletions

View File

@ -449,11 +449,10 @@ func (r *Request) Watch() (watch.Interface, error) {
return nil, err
}
if resp.StatusCode != http.StatusOK {
var body []byte
if resp.Body != nil {
body, _ = ioutil.ReadAll(resp.Body)
if _, _, err := r.transformResponse(resp, req, nil); err != nil {
return nil, err
}
return nil, fmt.Errorf("for request '%+v', got status: %v\nbody: %v", req.URL, resp.StatusCode, string(body))
return nil, fmt.Errorf("for request '%+v', got status: %v", req.URL, resp.StatusCode)
}
return watch.NewStreamWatcher(watchjson.NewDecoder(resp.Body, r.codec)), nil
}
@ -604,12 +603,18 @@ func (r *Request) Do() Result {
if err != nil {
return Result{err: err}
}
respBody, created, err := r.transformResponse(body, r.resp, r.req)
respBody, created, err := r.transformResponse(r.resp, r.req, body)
return Result{respBody, created, err, r.codec}
}
// transformResponse converts an API response into a structured API object.
func (r *Request) transformResponse(body []byte, resp *http.Response, req *http.Request) ([]byte, bool, error) {
// transformResponse converts an API response into a structured API object. If body is nil, the response
// body will be read to try and gather more response data.
func (r *Request) transformResponse(resp *http.Response, req *http.Request, body []byte) ([]byte, bool, error) {
if body == nil && resp.Body != nil {
if data, err := ioutil.ReadAll(resp.Body); err == nil {
body = data
}
}
// Did the server give us a status response?
isStatusResponse := false
var status api.Status
@ -622,6 +627,30 @@ func (r *Request) transformResponse(body []byte, resp *http.Response, req *http.
// no-op, we've been upgraded
case resp.StatusCode < http.StatusOK || resp.StatusCode > http.StatusPartialContent:
if !isStatusResponse {
return nil, false, r.transformUnstructuredResponseError(resp, req, body)
}
return nil, false, errors.FromObject(&status)
}
// If the server gave us a status back, look at what it was.
success := resp.StatusCode >= http.StatusOK && resp.StatusCode <= http.StatusPartialContent
if isStatusResponse && (status.Status != api.StatusSuccess && !success) {
// "Working" requests need to be handled specially.
// "Failed" requests are clearly just an error and it makes sense to return them as such.
return nil, false, errors.FromObject(&status)
}
created := resp.StatusCode == http.StatusCreated
return body, created, nil
}
// transformUnstructuredResponseError handles an error from the server that is not in a structured form.
func (r *Request) transformUnstructuredResponseError(resp *http.Response, req *http.Request, body []byte) error {
if body == nil && resp.Body != nil {
if data, err := ioutil.ReadAll(resp.Body); err == nil {
body = data
}
}
var err error = &UnexpectedStatusError{
Request: req,
Response: resp,
@ -658,21 +687,7 @@ func (r *Request) transformResponse(body []byte, resp *http.Response, req *http.
case http.StatusInternalServerError:
err = errors.NewInternalError(fmt.Errorf(message))
}
return nil, false, err
}
return nil, false, errors.FromObject(&status)
}
// If the server gave us a status back, look at what it was.
success := resp.StatusCode >= http.StatusOK && resp.StatusCode <= http.StatusPartialContent
if isStatusResponse && (status.Status != api.StatusSuccess && !success) {
// "Working" requests need to be handled specially.
// "Failed" requests are clearly just an error and it makes sense to return them as such.
return nil, false, errors.FromObject(&status)
}
created := resp.StatusCode == http.StatusCreated
return body, created, nil
return err
}
// isTextResponse returns true if the response appears to be a textual media type.

View File

@ -267,7 +267,7 @@ func TestTransformResponse(t *testing.T) {
if err != nil {
t.Errorf("failed to read body of response: %v", err)
}
response, created, err := r.transformResponse(body, test.Response, &http.Request{})
response, created, err := r.transformResponse(test.Response, &http.Request{}, body)
hasErr := err != nil
if hasErr != test.Error {
t.Errorf("%d: unexpected error: %t %v", i, test.Error, err)
@ -357,7 +357,7 @@ func TestTransformUnstructuredError(t *testing.T) {
if err != nil {
t.Errorf("failed to read body: %v", err)
}
_, _, err = r.transformResponse(body, testCase.Res, testCase.Req)
_, _, err = r.transformResponse(testCase.Res, testCase.Req, body)
if !testCase.ErrFn(err) {
t.Errorf("unexpected error: %v", err)
continue
@ -381,6 +381,7 @@ func TestRequestWatch(t *testing.T) {
testCases := []struct {
Request *Request
Err bool
ErrFn func(error) bool
Empty bool
}{
{
@ -402,12 +403,48 @@ func TestRequestWatch(t *testing.T) {
},
{
Request: &Request{
codec: testapi.Codec(),
client: clientFunc(func(req *http.Request) (*http.Response, error) {
return &http.Response{StatusCode: http.StatusForbidden}, nil
}),
baseURL: &url.URL{},
},
Err: true,
ErrFn: func(err error) bool {
return apierrors.IsForbidden(err)
},
},
{
Request: &Request{
codec: testapi.Codec(),
client: clientFunc(func(req *http.Request) (*http.Response, error) {
return &http.Response{StatusCode: http.StatusUnauthorized}, nil
}),
baseURL: &url.URL{},
},
Err: true,
ErrFn: func(err error) bool {
return apierrors.IsUnauthorized(err)
},
},
{
Request: &Request{
codec: testapi.Codec(),
client: clientFunc(func(req *http.Request) (*http.Response, error) {
return &http.Response{
StatusCode: http.StatusUnauthorized,
Body: ioutil.NopCloser(bytes.NewReader([]byte(runtime.EncodeOrDie(testapi.Codec(), &api.Status{
Status: api.StatusFailure,
Reason: api.StatusReasonUnauthorized,
})))),
}, nil
}),
baseURL: &url.URL{},
},
Err: true,
ErrFn: func(err error) bool {
return apierrors.IsUnauthorized(err)
},
},
{
Request: &Request{
@ -453,6 +490,9 @@ func TestRequestWatch(t *testing.T) {
t.Errorf("%d: expected %t, got %t: %v", i, testCase.Err, hasErr, err)
continue
}
if testCase.ErrFn != nil && !testCase.ErrFn(err) {
t.Errorf("%d: error not valid: %v", i, err)
}
if hasErr && watch != nil {
t.Errorf("%d: watch should be nil when error is returned", i)
continue