Skip to content
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

Support reply-after for 429 & 503 responses #26

Merged
merged 3 commits into from
Apr 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@ module github.com/adobe/ims-go

go 1.18

require github.com/golang-jwt/jwt/v5 v5.0.0
require github.com/golang-jwt/jwt/v5 v5.2.1
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
github.com/golang-jwt/jwt/v5 v5.0.0 h1:1n1XNM9hk7O9mnQoNBGolZvzebBQ7p93ULHRc28XJUE=
github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
5 changes: 4 additions & 1 deletion ims/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,8 @@ type Response struct {
// The raw body of the HTTP response.
Body []byte
// The value of the X-Debug-Id header.
XDebugID string
XDebugID string
RetryAfter string
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

food for though, you looked more into the usage of retry after.
from my view it makes sense extract the returned time and parse it into a date-time type directly in the client, instead of delegating this responsibility to the caller.

}

func (c *Client) do(req *http.Request) (*Response, error) {
Expand All @@ -93,10 +94,12 @@ func (c *Client) do(req *http.Request) (*Response, error) {

// X-Debug-Id is the header used by IMS to track requests.
xdebugid := res.Header.Get("x-debug-id")
retryAfter := res.Header.Get("Retry-After")

return &Response{
StatusCode: res.StatusCode,
Body: data,
XDebugID: xdebugid,
RetryAfter: retryAfter,
}, nil
}
60 changes: 60 additions & 0 deletions ims/cluster_exchange_token_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,66 @@ func TestClusterExchangeError(t *testing.T) {
}
}

func TestClusterExchangeTooManyRequests(t *testing.T) {
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Retry-After", "77")
w.Header().Set("x-debug-id", "banana")
w.WriteHeader(http.StatusTooManyRequests)

body := struct {
ErrorCode string `json:"error"`
ErrorMessage string `json:"error_description"`
}{
ErrorCode: "error-code",
ErrorMessage: "error-message",
}

if err := json.NewEncoder(w).Encode(&body); err != nil {
t.Fatalf("encode response: %v", err)
}
}))
defer s.Close()

c, err := ims.NewClient(&ims.ClientConfig{
URL: s.URL,
})
if err != nil {
t.Fatalf("create client: %v", err)
}

res, err := c.ClusterExchange(&ims.ClusterExchangeRequest{
ClientID: "irrelevant",
ClientSecret: "irrelevant",
Scopes: []string{"yolo", "test"},
UserToken: "old-token",
OrgID: "orgid",
})

if res != nil {
t.Fatalf("expected nil response because of error")
}

imsErr, ok := ims.IsError(err)
if !ok {
t.Fatalf("expected IMS error")
}
if imsErr.StatusCode != http.StatusTooManyRequests {
t.Fatalf("invalid status code: %v", imsErr.StatusCode)
}
if imsErr.ErrorCode != "error-code" {
t.Fatalf("invalid error code: %v", imsErr.ErrorCode)
}
if imsErr.ErrorMessage != "error-message" {
t.Fatalf("invalid error message: %v", imsErr.ErrorMessage)
}
if imsErr.RetryAfter != "77" {
t.Fatalf("invalid retry-after header: %v", imsErr.RetryAfter)
}
if imsErr.XDebugID != "banana" {
t.Fatalf("invalid x-debug-id header: %v", imsErr.RetryAfter)
}
}

func TestClusterExchangeInvalidRequest(t *testing.T) {
c, err := ims.NewClient(&ims.ClientConfig{
URL: "http://ims.endpoint",
Expand Down
10 changes: 10 additions & 0 deletions ims/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,16 @@ type Error struct {
}

func (e *Error) Error() string {
if e.RetryAfter != "" {
return fmt.Sprintf(
"error response: statusCode=%d, errorCode='%s', errorMessage='%s', x-debug-id='%s', retry-after='%s'",
e.StatusCode,
e.ErrorCode,
e.ErrorMessage,
e.XDebugID,
e.RetryAfter,
)
}
return fmt.Sprintf(
"error response: statusCode=%d, errorCode='%s', errorMessage='%s', x-debug-id='%s'",
e.StatusCode,
Expand Down
53 changes: 53 additions & 0 deletions ims/get_profile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,56 @@ func TestGetProfileEmptyErrorResponse(t *testing.T) {
t.Fatalf("invalid error type: %v", err)
}
}

func TestGetProfileTooManyRequests(t *testing.T) {
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
t.Fatalf("invalid method: %v", r.Method)
}
if v := r.Header.Get("authorization"); v != "Bearer accessToken" {
t.Fatalf("invalid authorization header: %v", v)
}

w.Header().Set("Retry-After", "77")
w.Header().Set("x-debug-id", "banana")
w.WriteHeader(http.StatusTooManyRequests)
}))
defer s.Close()

c, err := ims.NewClient(&ims.ClientConfig{
URL: s.URL,
})
if err != nil {
t.Fatalf("create client: %v", err)
}

res, err := c.GetProfile(&ims.GetProfileRequest{
AccessToken: "accessToken",
})

if err == nil {
t.Fatalf("expected error in get profile")
}

if res != nil {
t.Fatalf("expected nil response because of error")
}

imsErr, ok := ims.IsError(err)
if !ok {
t.Fatalf("expected IMS error")
}

if imsErr.StatusCode != http.StatusTooManyRequests {
t.Fatalf("invalid status code: %v", imsErr.StatusCode)
}

if imsErr.RetryAfter != "77" {
t.Fatalf("invalid retry-after header: %v", imsErr.RetryAfter)
}

if imsErr.XDebugID != "banana" {
t.Fatalf("invalid x-debug-id header: %v", imsErr.XDebugID)
}

}
Loading