Skip to content

Commit

Permalink
Export an Authentication Error to allow type assertions (#330)
Browse files Browse the repository at this point in the history
Co-authored-by: Rita Zerrizuela <[email protected]>
  • Loading branch information
ewanharris and Widcket authored Dec 13, 2023
1 parent 1f73dd6 commit 8c3c25e
Show file tree
Hide file tree
Showing 4 changed files with 33 additions and 13 deletions.
16 changes: 9 additions & 7 deletions authentication/authentication_error.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,19 @@ import (
"net/http"
)

type authenticationError struct {
// Error represents errors returned from the Authentication API. The `Err` property can
// be used to check the error code returned from the API.
type Error struct {
StatusCode int `json:"statusCode"`
Err string `json:"error"`
Message string `json:"error_description"`
}

func newError(response *http.Response) error {
apiError := &authenticationError{}
apiError := &Error{}

if err := json.NewDecoder(response.Body).Decode(apiError); err != nil {
return &authenticationError{
return &Error{
StatusCode: response.StatusCode,
Err: http.StatusText(response.StatusCode),
Message: fmt.Errorf("failed to decode json error response payload: %w", err).Error(),
Expand All @@ -34,20 +36,20 @@ func newError(response *http.Response) error {
}

// Error formats the error into a string representation.
func (a *authenticationError) Error() string {
func (a *Error) Error() string {
return fmt.Sprintf("%d %s: %s", a.StatusCode, a.Err, a.Message)
}

// Status returns the status code of the error.
func (a *authenticationError) Status() int {
func (a *Error) Status() int {
return a.StatusCode
}

// UnmarshalJSON implements the json.Unmarshaler interface.
//
// It is required to handle the differences between error responses between the APIs.
func (a *authenticationError) UnmarshalJSON(b []byte) error {
type authError authenticationError
func (a *Error) UnmarshalJSON(b []byte) error {
type authError Error
type authErrorWrapper struct {
*authError
Code string `json:"code"`
Expand Down
10 changes: 5 additions & 5 deletions authentication/authentication_error_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@ func Test_newError(t *testing.T) {
var testCases = []struct {
name string
givenResponse http.Response
expectedError authenticationError
expectedError Error
}{
{
name: "it fails to decode if body is not json",
givenResponse: http.Response{
StatusCode: http.StatusForbidden,
Body: io.NopCloser(strings.NewReader("Hello, I'm not JSON.")),
},
expectedError: authenticationError{
expectedError: Error{
StatusCode: 403,
Err: "Forbidden",
Message: "failed to decode json error response payload: invalid character 'H' looking for beginning of value",
Expand All @@ -33,7 +33,7 @@ func Test_newError(t *testing.T) {
StatusCode: http.StatusBadRequest,
Body: io.NopCloser(strings.NewReader(`{"statusCode":400,"error":"invalid_scope","error_description":"Scope must be an array or a string"}`)),
},
expectedError: authenticationError{
expectedError: Error{
StatusCode: 400,
Err: "invalid_scope",
Message: "Scope must be an array or a string",
Expand All @@ -45,7 +45,7 @@ func Test_newError(t *testing.T) {
StatusCode: http.StatusInternalServerError,
Body: io.NopCloser(strings.NewReader(`{"errorMessage":"wrongStruct"}`)),
},
expectedError: authenticationError{
expectedError: Error{
StatusCode: 500,
Err: "Internal Server Error",
Message: "",
Expand All @@ -57,7 +57,7 @@ func Test_newError(t *testing.T) {
StatusCode: http.StatusBadRequest,
Body: io.NopCloser(strings.NewReader(`{"name":"BadRequestError","code":"invalid_signup","description":"Invalid sign up","statusCode":400}`)),
},
expectedError: authenticationError{
expectedError: Error{
StatusCode: 400,
Err: "invalid_signup",
Message: "Invalid sign up",
Expand Down
2 changes: 1 addition & 1 deletion authentication/authentication_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -411,7 +411,7 @@ func TestRetries(t *testing.T) {
assert.NoError(t, err)

_, err = a.UserInfo(context.Background(), "123")
assert.Equal(t, http.StatusBadGateway, err.(*authenticationError).StatusCode)
assert.Equal(t, http.StatusBadGateway, err.(*Error).StatusCode)
assert.Equal(t, 1, i)
})

Expand Down
18 changes: 18 additions & 0 deletions authentication/doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,22 @@
// authentication.WithClientSecret(secret), // Optional depending on the grants used
// authentication.WithClockTolerance(10 * time.Second),
// )
//
// # Handling Errors
//
// This package exports an [authentication.Error] type that can be used to check errors
// returned from the Authentication API and handle them as necessary, for example
//
// tokens, err := auth.OAuth.LoginWithPassword(context.Background(), oauth.LoginWithPasswordRequest{
// Username: "[email protected]",
// Password: "hunter2",
// }, oauth.IDTokenValidationOptions{})
//
// if err != nil {
// if aerr, ok := err.(*authentication.Error); ok {
// if aerr.Err == "mfa_required" {
// // Handle prompting for MFA usage
// }
// }
// }
package authentication

0 comments on commit 8c3c25e

Please sign in to comment.