Skip to content

Commit

Permalink
Adding custom JSON deserializer for UserInfo type (#851)
Browse files Browse the repository at this point in the history
* Adding custom JSON deserializer for UserInfo type

* Adding error test case

* Errorf instead of sprintf

* Removing error stutter

---------

Co-authored-by: Will Vedder <[email protected]>
  • Loading branch information
willvedd and willvedd authored Sep 20, 2023
1 parent 9cc4f17 commit 66252ab
Show file tree
Hide file tree
Showing 2 changed files with 63 additions and 1 deletion.
37 changes: 37 additions & 0 deletions internal/auth/authutil/user_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import (
"fmt"
"net/http"
"net/url"
"reflect"
"strconv"
"time"
)

Expand All @@ -31,6 +33,41 @@ type UserInfo struct {
UpdatedAt *time.Time `json:"updated_at,omitempty"`
}

// UnmarshalJSON is a custom deserializer for the UserInfo type.
// A custom solution is necessary due to possible inconsistencies in value types.
func (u *UserInfo) UnmarshalJSON(b []byte) error {
type userInfo UserInfo
type userAlias struct {
*userInfo
RawEmailVerified interface{} `json:"email_verified,omitempty"`
}

alias := &userAlias{(*userInfo)(u), nil}

err := json.Unmarshal(b, alias)
if err != nil {
return err
}

if alias.RawEmailVerified != nil {
var emailVerified bool
switch rawEmailVerified := alias.RawEmailVerified.(type) {
case bool:
emailVerified = rawEmailVerified
case string:
emailVerified, err = strconv.ParseBool(rawEmailVerified)
if err != nil {
return err
}
default:
return fmt.Errorf("email_verified field expected to be bool or string, got: %s", reflect.TypeOf(rawEmailVerified))
}
alias.EmailVerified = &emailVerified
}

return nil
}

// FetchUserInfo fetches and parses user information with the provided access token.
func FetchUserInfo(httpClient *http.Client, baseDomain, token string) (*UserInfo, error) {
endpoint := url.URL{Scheme: "https", Host: baseDomain, Path: "/userinfo"}
Expand Down
27 changes: 26 additions & 1 deletion internal/auth/authutil/user_info_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ func TestUserInfo(t *testing.T) {
assert.Equal(t, "Bearer token", r.Header.Get("authorization"))

w.Header().Set("Content-Type", "application/json")
io.WriteString(w, `{"name": "Joe Bloggs"}`)
io.WriteString(w, `{"name": "Joe Bloggs","email_verified":true}`)
}))

defer ts.Close()
Expand All @@ -27,6 +27,25 @@ func TestUserInfo(t *testing.T) {

assert.NoError(t, err)
assert.Equal(t, "Joe Bloggs", *user.Name)
assert.Equal(t, true, *user.EmailVerified)
})

t.Run("Successfully call user info endpoint with string encoded email verified field", func(t *testing.T) {
ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, "Bearer token", r.Header.Get("authorization"))

w.Header().Set("Content-Type", "application/json")
io.WriteString(w, `{"email_verified":"true"}`)
}))

defer ts.Close()
parsedURL, err := url.Parse(ts.URL)
assert.NoError(t, err)

user, err := FetchUserInfo(ts.Client(), parsedURL.Host, "token")

assert.NoError(t, err)
assert.Equal(t, true, *user.EmailVerified)
})

testCases := []struct {
Expand All @@ -46,6 +65,12 @@ func TestUserInfo(t *testing.T) {
httpStatus: http.StatusOK,
response: `{ "foo": "bar" `,
},
{
name: "Email verified field not string or bool",
expect: "cannot decode response: email_verified field expected to be bool or string, got: float64",
httpStatus: http.StatusOK,
response: `{ "email_verified": 0 }`,
},
}

for _, testCase := range testCases {
Expand Down

0 comments on commit 66252ab

Please sign in to comment.