-
Notifications
You must be signed in to change notification settings - Fork 58
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Enhance reliability of how we make requests and parse errors #108
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,7 +3,6 @@ package management | |
import ( | ||
"encoding/json" | ||
"fmt" | ||
"net/http" | ||
) | ||
|
||
// Branding is used to customize the look and feel of Auth0 to align | ||
|
@@ -168,21 +167,7 @@ func (m *BrandingManager) UniversalLogin(opts ...RequestOption) (ul *BrandingUni | |
// | ||
// See: https://auth0.com/docs/api/management/v2#!/Branding/put_universal_login | ||
func (m *BrandingManager) SetUniversalLogin(ul *BrandingUniversalLogin, opts ...RequestOption) (err error) { | ||
req, err := m.NewRequest("PUT", m.URI("branding", "templates", "universal-login"), ul.Body, opts...) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
res, err := m.Do(req) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if res.StatusCode >= http.StatusBadRequest { | ||
return newError(res.Body) | ||
} | ||
|
||
return nil | ||
Comment on lines
-171
to
-185
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. All of this part was just replicating what the |
||
return m.Request("PUT", m.URI("branding", "templates", "universal-login"), ul.Body, opts...) | ||
} | ||
|
||
// DeleteUniversalLogin deletes the template for the New Universal Login Experience. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -146,23 +146,23 @@ type EnrollmentTicket struct { | |
// | ||
// See: https://auth0.com/docs/api/management/v2#!/Guardian/post_ticket | ||
func (m *EnrollmentManager) CreateTicket(t *CreateEnrollmentTicket, opts ...RequestOption) (EnrollmentTicket, error) { | ||
req, err := m.NewRequest("POST", m.URI("guardian", "enrollments", "ticket"), t, opts...) | ||
request, err := m.NewRequest("POST", m.URI("guardian", "enrollments", "ticket"), t, opts...) | ||
if err != nil { | ||
return EnrollmentTicket{}, err | ||
} | ||
|
||
res, err := m.Do(req) | ||
response, err := m.Do(request) | ||
if err != nil { | ||
return EnrollmentTicket{}, err | ||
} | ||
defer res.Body.Close() | ||
defer response.Body.Close() | ||
|
||
if res.StatusCode != http.StatusOK { | ||
return EnrollmentTicket{}, newError(res.Body) | ||
if response.StatusCode != http.StatusOK { | ||
return EnrollmentTicket{}, newError(response) | ||
} | ||
|
||
var out EnrollmentTicket | ||
err = json.NewDecoder(res.Body).Decode(&out) | ||
err = json.NewDecoder(response.Body).Decode(&out) | ||
Comment on lines
+149
to
+165
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unfortunately we couldn't do the same thing as we did in the branding.go file and simply replace all the logic with a call to |
||
return out, err | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,7 +4,6 @@ import ( | |
"bytes" | ||
"encoding/json" | ||
"mime/multipart" | ||
"net/http" | ||
"net/textproto" | ||
"strconv" | ||
"time" | ||
|
@@ -126,29 +125,7 @@ func (m *JobManager) ImportUsers(j *Job, opts ...RequestOption) error { | |
} | ||
mp.Close() | ||
|
||
req, err := http.NewRequest("POST", m.URI("jobs", "users-imports"), &payload) | ||
if err != nil { | ||
return err | ||
} | ||
req.Header.Add("Content-Type", mp.FormDataContentType()) | ||
|
||
for _, option := range opts { | ||
option.apply(req) | ||
} | ||
|
||
res, err := m.Do(req) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if res.StatusCode < http.StatusOK || res.StatusCode >= http.StatusBadRequest { | ||
return newError(res.Body) | ||
} | ||
|
||
if res.StatusCode != http.StatusNoContent { | ||
defer res.Body.Close() | ||
return json.NewDecoder(res.Body).Decode(j) | ||
} | ||
opts = append(opts, Header("Content-Type", mp.FormDataContentType())) | ||
|
||
return nil | ||
Comment on lines
-129
to
-153
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Again, this is doing exactly what the |
||
return m.Request("POST", m.URI("jobs", "users-imports"), &payload, opts...) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,7 +3,7 @@ package management | |
import ( | ||
"encoding/json" | ||
"fmt" | ||
"io" | ||
"net/http" | ||
) | ||
|
||
// Error is an interface describing any error which | ||
|
@@ -21,13 +21,25 @@ type managementError struct { | |
Message string `json:"message"` | ||
} | ||
|
||
func newError(r io.Reader) error { | ||
m := &managementError{} | ||
if err := json.NewDecoder(r).Decode(m); err != nil { | ||
return err | ||
func newError(response *http.Response) error { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Quite a few improvements to this func:
|
||
apiError := &managementError{} | ||
|
||
if err := json.NewDecoder(response.Body).Decode(apiError); err != nil { | ||
return &managementError{ | ||
StatusCode: response.StatusCode, | ||
Err: http.StatusText(response.StatusCode), | ||
Message: fmt.Errorf("failed to decode json error response payload: %w", err).Error(), | ||
} | ||
} | ||
|
||
// This can happen in case the error message structure changes. | ||
// If that happens we still want to display the correct code. | ||
if apiError.Status() == 0 { | ||
apiError.StatusCode = response.StatusCode | ||
apiError.Err = http.StatusText(response.StatusCode) | ||
} | ||
|
||
return m | ||
return apiError | ||
} | ||
|
||
// Error formats the error into a string representation. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
package management | ||
|
||
import ( | ||
"io" | ||
"net/http" | ||
"strings" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestNewError(t *testing.T) { | ||
var testCases = []struct { | ||
name string | ||
givenResponse http.Response | ||
expectedError managementError | ||
}{ | ||
{ | ||
name: "it fails to decode if body is not a json", | ||
givenResponse: http.Response{ | ||
StatusCode: http.StatusForbidden, | ||
Body: io.NopCloser(strings.NewReader("Hello, I'm not a JSON.")), | ||
}, | ||
expectedError: managementError{ | ||
StatusCode: 403, | ||
Err: "Forbidden", | ||
Message: "failed to decode json error response payload: invalid character 'H' looking for beginning of value", | ||
}, | ||
}, | ||
{ | ||
name: "it correctly decodes the error response payload", | ||
givenResponse: http.Response{ | ||
StatusCode: http.StatusBadRequest, | ||
Body: io.NopCloser(strings.NewReader(`{"statusCode":400,"error":"Bad Request","message":"One of 'client_id' or 'name' is required."}`)), | ||
}, | ||
expectedError: managementError{ | ||
StatusCode: 400, | ||
Err: "Bad Request", | ||
Message: "One of 'client_id' or 'name' is required.", | ||
}, | ||
}, | ||
{ | ||
name: "it will still post the correct status code if the body doesn't have the correct structure", | ||
givenResponse: http.Response{ | ||
StatusCode: http.StatusInternalServerError, | ||
Body: io.NopCloser(strings.NewReader(`{"errorMessage":"wrongStruct"}`)), | ||
}, | ||
expectedError: managementError{ | ||
StatusCode: 500, | ||
Err: "Internal Server Error", | ||
Message: "", | ||
}, | ||
}, | ||
} | ||
|
||
for _, testCase := range testCases { | ||
t.Run(testCase.name, func(t *testing.T) { | ||
actualError := newError(&testCase.givenResponse) | ||
assert.Equal(t, &testCase.expectedError, actualError) | ||
}) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -84,27 +84,32 @@ func (m *Management) Do(req *http.Request) (*http.Response, error) { | |
} | ||
|
||
// Request combines NewRequest and Do, while also handling decoding of response payload. | ||
func (m *Management) Request(method, uri string, v interface{}, options ...RequestOption) error { | ||
request, err := m.NewRequest(method, uri, v, options...) | ||
func (m *Management) Request(method, uri string, payload interface{}, options ...RequestOption) error { | ||
request, err := m.NewRequest(method, uri, payload, options...) | ||
if err != nil { | ||
return err | ||
return fmt.Errorf("failed to create a new request: %w", err) | ||
} | ||
|
||
response, err := m.Do(request) | ||
if err != nil { | ||
return fmt.Errorf("request failed: %w", err) | ||
return fmt.Errorf("failed to send the request: %w", err) | ||
} | ||
defer response.Body.Close() | ||
|
||
if response.StatusCode < http.StatusOK || response.StatusCode >= http.StatusBadRequest { | ||
return newError(response.Body) | ||
// If the response contains a client or a server error then return the error. | ||
if response.StatusCode >= http.StatusBadRequest { | ||
return newError(response) | ||
} | ||
|
||
if response.StatusCode != http.StatusNoContent && response.StatusCode != http.StatusAccepted { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This check was replaced by |
||
if err := json.NewDecoder(response.Body).Decode(v); err != nil { | ||
return fmt.Errorf("decoding response payload failed: %w", err) | ||
} | ||
responseBody, err := io.ReadAll(response.Body) | ||
if err != nil { | ||
return fmt.Errorf("failed to read the response body: %w", err) | ||
} | ||
|
||
return response.Body.Close() | ||
if len(responseBody) > 0 && string(responseBody) != "{}" { | ||
if err = json.Unmarshal(responseBody, &payload); err != nil { | ||
return fmt.Errorf("failed to unmarshal response payload: %w", err) | ||
} | ||
} | ||
|
||
return nil | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We've changed the signature of the
newError
func to accept the entire response so we can have access to the status code as well. Further clarifications as to why this is needed are provided in the comments below.