From 7bc2a3a3ca0d249d203aeadc0928d69168e1bba9 Mon Sep 17 00:00:00 2001 From: Nathan Youngman Date: Tue, 12 Jan 2016 12:32:18 -0700 Subject: [PATCH] parse and return timestamp in error struct #12 --- push/service.go | 67 ++++++++++++++++++++++++++------------------ push/service_test.go | 43 ++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+), 27 deletions(-) diff --git a/push/service.go b/push/service.go index 0087b1f..207ebf0 100644 --- a/push/service.go +++ b/push/service.go @@ -96,6 +96,17 @@ var ( ErrUnknown = errors.New("unknown error") ) +// Error with a timestamp +type Error struct { + Err error + Timestamp time.Time + DeviceToken string +} + +func (e *Error) Error() string { + return fmt.Sprintf("%v (device token %v last invalid at %v)", e.Err.Error(), e.DeviceToken, e.Timestamp) +} + var errorReason = map[string]error{ "PayloadEmpty": ErrPayloadEmpty, "PayloadTooLarge": ErrPayloadTooLarge, @@ -122,11 +133,22 @@ var errorReason = map[string]error{ "MissingTopic": ErrMissingTopic, } +var errorStatus = map[int]error{ + http.StatusBadRequest: ErrBadRequest, + http.StatusForbidden: ErrForbidden, + http.StatusMethodNotAllowed: ErrMethodNotAllowed, + http.StatusGone: ErrGone, + http.StatusRequestEntityTooLarge: ErrPayloadTooLarge, + statusTooManyRequests: ErrTooManyRequests, + http.StatusInternalServerError: ErrInternalServerError, + http.StatusServiceUnavailable: ErrServiceUnavailable, +} + type response struct { // Reason for failure Reason string `json:"reason"` - // Timestamp for 410 errors (maybe this is an int) - Timestamp string `json:"timestamp"` + // Timestamp for 410 StatusGone (ErrUnregistered) + Timestamp int64 `json:"timestamp"` } const statusTooManyRequests = 429 @@ -171,35 +193,26 @@ func (s *Service) PushBytes(deviceToken string, headers *Headers, payload []byte var response response json.Unmarshal(body, &response) - if e, ok := errorReason[response.Reason]; ok { - return "", e + e, ok := errorReason[response.Reason] + if !ok { + // fallback to HTTP status codes if reason not found in JSON + e, ok = errorStatus[resp.StatusCode] + if !ok { + e = ErrUnknown + } } - // fallback to HTTP status codes if reason not found in JSON - - switch resp.StatusCode { - case http.StatusBadRequest: - return "", ErrBadRequest - case http.StatusForbidden: - return "", ErrForbidden - case http.StatusMethodNotAllowed: - return "", ErrMethodNotAllowed - case http.StatusGone: - // TODO: this should return an error structure with timestamp - // but I don't know the format of timestamp (Unix time?) - // and there may be a JSON response handled above (ErrUnregistered?) - return "", ErrGone - case http.StatusRequestEntityTooLarge: - return "", ErrPayloadTooLarge - case statusTooManyRequests: - return "", ErrTooManyRequests - case http.StatusInternalServerError: - return "", ErrInternalServerError - case http.StatusServiceUnavailable: - return "", ErrServiceUnavailable + // error constant if there was no timestamp + if response.Timestamp == 0 { + return "", e } - return "", ErrUnknown + // error struct with a timestamp + return "", &Error{ + Err: e, + Timestamp: time.Unix(response.Timestamp, 0), + DeviceToken: deviceToken, + } } // set headers for an HTTP request diff --git a/push/service_test.go b/push/service_test.go index 4b5d6c2..6dcc9f7 100644 --- a/push/service_test.go +++ b/push/service_test.go @@ -7,6 +7,7 @@ import ( "net/http/httptest" "reflect" "testing" + "time" "github.com/RobotsAndPencils/buford/push" ) @@ -72,3 +73,45 @@ func TestBadPriorityPush(t *testing.T) { t.Errorf("Expected error %v, got %v.", push.ErrBadPriority, err) } } + +func TestTimestampError(t *testing.T) { + deviceToken := "c2732227a1d8021cfaf781d71fb2f908c61f5861079a00954a5453f1d0281433" + payload := []byte(`{ "aps" : { "alert" : "Hello HTTP/2" } }`) + + handler := http.NewServeMux() + server := httptest.NewServer(handler) + + handler.HandleFunc("/3/device/", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusGone) + w.Write([]byte(`{"reason":"Unregistered","timestamp":12622780800}`)) + }) + + service := push.Service{ + Client: http.DefaultClient, + Host: server.URL, + } + + _, err := service.PushBytes(deviceToken, nil, payload) + + if err == push.ErrUnregistered { + t.Error("Expected error structure, got constant.") + } + + e, ok := err.(*push.Error) + if !ok { + t.Fatalf("Expected push error, got %v.", err) + } + + if e.Err != push.ErrUnregistered { + t.Errorf("Expected error %v, got %v.", push.ErrUnregistered, err) + } + + expected := time.Unix(12622780800, 0) + if e.Timestamp != expected { + t.Errorf("Expected timestamp %v, got %v.", expected, e.Timestamp) + } + + if e.DeviceToken != deviceToken { + t.Errorf("Expected device token %v, got %v.", deviceToken, e.DeviceToken) + } +}