Skip to content

Commit

Permalink
support for parsing error messages from xml responses (#465)
Browse files Browse the repository at this point in the history
* support for parsing error messages from xml responses

* fixing the linting

* removed some duplicate code

* fix bug introduced in refactoring

* added XML test and fixed bug it uncovered
  • Loading branch information
tombuildsstuff authored and jhendrixMSFT committed Oct 21, 2019
1 parent 0b055be commit 87a0e43
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 9 deletions.
19 changes: 14 additions & 5 deletions autorest/azure/azure.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package azure
// limitations under the License.

import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
Expand Down Expand Up @@ -143,7 +144,7 @@ type RequestError struct {
autorest.DetailedError

// The error returned by the Azure service.
ServiceError *ServiceError `json:"error"`
ServiceError *ServiceError `json:"error" xml:"Error"`

// The request id (from the x-ms-request-id-header) of the request.
RequestID string
Expand Down Expand Up @@ -285,26 +286,34 @@ func WithErrorUnlessStatusCode(codes ...int) autorest.RespondDecorator {
var e RequestError
defer resp.Body.Close()

encodedAs := autorest.EncodedAsJSON
if strings.Contains(resp.Header.Get("Content-Type"), "xml") {
encodedAs = autorest.EncodedAsXML
}

// Copy and replace the Body in case it does not contain an error object.
// This will leave the Body available to the caller.
b, decodeErr := autorest.CopyAndDecode(autorest.EncodedAsJSON, resp.Body, &e)
b, decodeErr := autorest.CopyAndDecode(encodedAs, resp.Body, &e)
resp.Body = ioutil.NopCloser(&b)
if decodeErr != nil {
return fmt.Errorf("autorest/azure: error response cannot be parsed: %q error: %v", b.String(), decodeErr)
}
if e.ServiceError == nil {
// Check if error is unwrapped ServiceError
if err := json.Unmarshal(b.Bytes(), &e.ServiceError); err != nil {
decoder := autorest.NewDecoder(encodedAs, bytes.NewReader(b.Bytes()))
if err := decoder.Decode(&e.ServiceError); err != nil {
return err
}
}
if e.ServiceError.Message == "" {
// if we're here it means the returned error wasn't OData v4 compliant.
// try to unmarshal the body as raw JSON in hopes of getting something.
// try to unmarshal the body in hopes of getting something.
rawBody := map[string]interface{}{}
if err := json.Unmarshal(b.Bytes(), &rawBody); err != nil {
decoder := autorest.NewDecoder(encodedAs, bytes.NewReader(b.Bytes()))
if err := decoder.Decode(&rawBody); err != nil {
return err
}

e.ServiceError = &ServiceError{
Code: "Unknown",
Message: "Unknown service error",
Expand Down
29 changes: 29 additions & 0 deletions autorest/azure/azure_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -599,6 +599,35 @@ func TestParseResourceID_WithMalformedResourceID(t *testing.T) {
}
}

func TestRequestErrorString_WithXMLError(t *testing.T) {
j := `<?xml version="1.0" encoding="utf-8"?>
<Error>
<Code>InternalError</Code>
<Message>Internal service error.</Message>
</Error> `
uuid := "71FDB9F4-5E49-4C12-B266-DE7B4FD999A6"
r := mocks.NewResponseWithContent(j)
mocks.SetResponseHeader(r, HeaderRequestID, uuid)
r.Request = mocks.NewRequest()
r.StatusCode = http.StatusInternalServerError
r.Status = http.StatusText(r.StatusCode)
r.Header.Add("Content-Type", "text/xml")

err := autorest.Respond(r,
WithErrorUnlessStatusCode(http.StatusOK),
autorest.ByClosing())

if err == nil {
t.Fatalf("azure: returned nil error for proper error response")
}
azErr, _ := err.(*RequestError)
const expected = `autorest/azure: Service returned an error. Status=500 Code="InternalError" Message="Internal service error."`
if got := azErr.Error(); expected != got {
fmt.Println(got)
t.Fatalf("azure: send wrong RequestError.\nexpected=%v\ngot=%v", expected, got)
}
}

func withErrorPrepareDecorator(e *error) autorest.PrepareDecorator {
return func(p autorest.Preparer) autorest.Preparer {
return autorest.PreparerFunc(func(r *http.Request) (*http.Request, error) {
Expand Down
12 changes: 8 additions & 4 deletions autorest/azure/rp.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,15 @@ func DoRetryWithRegistration(client autorest.Client) autorest.SendDecorator {
if resp.StatusCode != http.StatusConflict || client.SkipResourceProviderRegistration {
return resp, err
}

var re RequestError
err = autorest.Respond(
resp,
autorest.ByUnmarshallingJSON(&re),
)
if strings.Contains(r.Header.Get("Content-Type"), "xml") {
// XML errors (e.g. Storage Data Plane) only return the inner object
err = autorest.Respond(resp, autorest.ByUnmarshallingXML(&re.ServiceError))
} else {
err = autorest.Respond(resp, autorest.ByUnmarshallingJSON(&re))
}

if err != nil {
return resp, err
}
Expand Down

0 comments on commit 87a0e43

Please sign in to comment.