Skip to content

Commit

Permalink
Support reply-after for 429 & 503 responses (#26)
Browse files Browse the repository at this point in the history
* Forward Reply-After header in Response

* Test retry-after and x-debug-id header parsing

* in-passing dependency update
  • Loading branch information
telegrapher authored Apr 2, 2024
1 parent af0993a commit bcbf7de
Show file tree
Hide file tree
Showing 6 changed files with 130 additions and 2 deletions.
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
}

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)
}

}

0 comments on commit bcbf7de

Please sign in to comment.