// Error returns the error executing the request, nil if no error occurred. // If the returned object is of type Status and has Status != StatusSuccess, the // additional information in Status will be used to enrich the error. // See the Request.Do() comment for what errors you might get. func (r Result) Error() error { // if we have received an unexpected server error, and we have a body and decoder, we can try to extract // a Status object. if r.err == nil || !errors.IsUnexpectedServerError(r.err) || len(r.body) == 0 || r.decoder == nil { return r.err } // attempt to convert the body into a Status object // to be backwards compatible with old servers that do not return a version, default to "v1" out, _, err := r.decoder.Decode(r.body, &unversioned.GroupVersionKind{Version: "v1"}, nil) if err != nil { glog.V(5).Infof("body was not decodable (unable to check for Status): %v", err) return r.err } switch t := out.(type) { case *unversioned.Status: // because we default the kind, we *must* check for StatusFailure if t.Status == unversioned.StatusFailure { return errors.FromObject(t) } } return r.err }
func TestTransformUnstructuredError(t *testing.T) { testCases := []struct { Req *http.Request Res *http.Response Resource string Name string ErrFn func(error) bool Transformed error }{ { Resource: "foo", Name: "bar", Req: &http.Request{ Method: "POST", }, Res: &http.Response{ StatusCode: http.StatusConflict, Body: ioutil.NopCloser(bytes.NewReader(nil)), }, ErrFn: apierrors.IsAlreadyExists, }, { Resource: "foo", Name: "bar", Req: &http.Request{ Method: "PUT", }, Res: &http.Response{ StatusCode: http.StatusConflict, Body: ioutil.NopCloser(bytes.NewReader(nil)), }, ErrFn: apierrors.IsConflict, }, { Resource: "foo", Name: "bar", Req: &http.Request{}, Res: &http.Response{ StatusCode: http.StatusNotFound, Body: ioutil.NopCloser(bytes.NewReader(nil)), }, ErrFn: apierrors.IsNotFound, }, { Req: &http.Request{}, Res: &http.Response{ StatusCode: http.StatusBadRequest, Body: ioutil.NopCloser(bytes.NewReader(nil)), }, ErrFn: apierrors.IsBadRequest, }, { // status in response overrides transformed result Req: &http.Request{}, Res: &http.Response{StatusCode: http.StatusBadRequest, Body: ioutil.NopCloser(bytes.NewReader([]byte(`{"kind":"Status","apiVersion":"v1","status":"Failure","code":404}`)))}, ErrFn: apierrors.IsBadRequest, Transformed: &apierrors.StatusError{ ErrStatus: unversioned.Status{Status: unversioned.StatusFailure, Code: http.StatusNotFound}, }, }, { // successful status is ignored Req: &http.Request{}, Res: &http.Response{StatusCode: http.StatusBadRequest, Body: ioutil.NopCloser(bytes.NewReader([]byte(`{"kind":"Status","apiVersion":"v1","status":"Success","code":404}`)))}, ErrFn: apierrors.IsBadRequest, }, { // empty object does not change result Req: &http.Request{}, Res: &http.Response{StatusCode: http.StatusBadRequest, Body: ioutil.NopCloser(bytes.NewReader([]byte(`{}`)))}, ErrFn: apierrors.IsBadRequest, }, { // we default apiVersion for backwards compatibility with old clients // TODO: potentially remove in 1.7 Req: &http.Request{}, Res: &http.Response{StatusCode: http.StatusBadRequest, Body: ioutil.NopCloser(bytes.NewReader([]byte(`{"kind":"Status","status":"Failure","code":404}`)))}, ErrFn: apierrors.IsBadRequest, Transformed: &apierrors.StatusError{ ErrStatus: unversioned.Status{Status: unversioned.StatusFailure, Code: http.StatusNotFound}, }, }, { // we do not default kind Req: &http.Request{}, Res: &http.Response{StatusCode: http.StatusBadRequest, Body: ioutil.NopCloser(bytes.NewReader([]byte(`{"status":"Failure","code":404}`)))}, ErrFn: apierrors.IsBadRequest, }, } for i, testCase := range testCases { r := &Request{ content: defaultContentConfig(), serializers: defaultSerializers(), resourceName: testCase.Name, resource: testCase.Resource, } result := r.transformResponse(testCase.Res, testCase.Req) err := result.err if !testCase.ErrFn(err) { t.Errorf("unexpected error: %v", err) continue } if !apierrors.IsUnexpectedServerError(err) { t.Errorf("%d: unexpected error type: %v", i, err) } if len(testCase.Name) != 0 && !strings.Contains(err.Error(), testCase.Name) { t.Errorf("unexpected error string: %s", err) } if len(testCase.Resource) != 0 && !strings.Contains(err.Error(), testCase.Resource) { t.Errorf("unexpected error string: %s", err) } // verify Error() properly transforms the error transformed := result.Error() expect := testCase.Transformed if expect == nil { expect = err } if !reflect.DeepEqual(expect, transformed) { t.Errorf("%d: unexpected Error(): %s", i, diff.ObjectReflectDiff(expect, transformed)) } // verify result.Get properly transforms the error if _, err := result.Get(); !reflect.DeepEqual(expect, err) { t.Errorf("%d: unexpected error on Get(): %s", i, diff.ObjectReflectDiff(expect, err)) } // verify result.Into properly handles the error if err := result.Into(&api.Pod{}); !reflect.DeepEqual(expect, err) { t.Errorf("%d: unexpected error on Into(): %s", i, diff.ObjectReflectDiff(expect, err)) } // verify result.Raw leaves the error in the untransformed state if _, err := result.Raw(); !reflect.DeepEqual(result.err, err) { t.Errorf("%d: unexpected error on Raw(): %s", i, diff.ObjectReflectDiff(expect, err)) } } }