From 972ffb47712932ba563fe1d9b6f02b1075f774da Mon Sep 17 00:00:00 2001 From: Alexander Bakker Date: Sat, 11 Mar 2023 12:10:46 +0100 Subject: [PATCH] Introduce APIError type and make ErrorEvent.Err public This makes it a little bit easier to act on API errors that happen while streaming. --- helper.go | 25 +++++++++++++++++++++---- helper_test.go | 12 ++++++++++-- streaming.go | 4 ++-- streaming_test.go | 12 ++++++------ streaming_ws.go | 6 +++--- streaming_ws_test.go | 14 +++++++------- 6 files changed, 49 insertions(+), 24 deletions(-) diff --git a/helper.go b/helper.go index 05af20f..2dc5921 100644 --- a/helper.go +++ b/helper.go @@ -3,12 +3,26 @@ package mastodon import ( "encoding/base64" "encoding/json" - "errors" "fmt" "net/http" "os" ) +type APIError struct { + prefix string + Message string + StatusCode int +} + +func (e *APIError) Error() string { + errMsg := fmt.Sprintf("%s: %d %s", e.prefix, e.StatusCode, http.StatusText(e.StatusCode)) + if e.Message == "" { + return errMsg + } + + return fmt.Sprintf("%s: %s", errMsg, e.Message) +} + // Base64EncodeFileName returns the base64 data URI format string of the file with the file name. func Base64EncodeFileName(filename string) (string, error) { file, err := os.Open(filename) @@ -41,15 +55,18 @@ func Base64Encode(file *os.File) (string, error) { func String(v string) *string { return &v } func parseAPIError(prefix string, resp *http.Response) error { - errMsg := fmt.Sprintf("%s: %s", prefix, resp.Status) + res := APIError{ + prefix: prefix, + StatusCode: resp.StatusCode, + } var e struct { Error string `json:"error"` } json.NewDecoder(resp.Body).Decode(&e) if e.Error != "" { - errMsg = fmt.Sprintf("%s: %s", errMsg, e.Error) + res.Message = e.Error } - return errors.New(errMsg) + return &res } diff --git a/helper_test.go b/helper_test.go index 0d4b2ce..e839b17 100644 --- a/helper_test.go +++ b/helper_test.go @@ -73,7 +73,11 @@ func TestString(t *testing.T) { func TestParseAPIError(t *testing.T) { // No api error. r := ioutil.NopCloser(strings.NewReader(`404`)) - err := parseAPIError("bad request", &http.Response{Status: "404 Not Found", Body: r}) + err := parseAPIError("bad request", &http.Response{ + Status: "404 Not Found", + StatusCode: http.StatusNotFound, + Body: r, + }) want := "bad request: 404 Not Found" if err.Error() != want { t.Fatalf("want %q but %q", want, err.Error()) @@ -81,7 +85,11 @@ func TestParseAPIError(t *testing.T) { // With api error. r = ioutil.NopCloser(strings.NewReader(`{"error":"Record not found"}`)) - err = parseAPIError("bad request", &http.Response{Status: "404 Not Found", Body: r}) + err = parseAPIError("bad request", &http.Response{ + Status: "404 Not Found", + StatusCode: http.StatusNotFound, + Body: r, + }) want = "bad request: 404 Not Found: Record not found" if err.Error() != want { t.Fatalf("want %q but %q", want, err.Error()) diff --git a/streaming.go b/streaming.go index b109f6c..20ca77f 100644 --- a/streaming.go +++ b/streaming.go @@ -40,10 +40,10 @@ type DeleteEvent struct{ ID ID } func (e *DeleteEvent) event() {} // ErrorEvent is a struct for passing errors to app. -type ErrorEvent struct{ err error } +type ErrorEvent struct{ Err error } func (e *ErrorEvent) event() {} -func (e *ErrorEvent) Error() string { return e.err.Error() } +func (e *ErrorEvent) Error() string { return e.Err.Error() } // Event is an interface passing events to app. type Event interface { diff --git a/streaming_test.go b/streaming_test.go index a9e90d9..759e0d0 100644 --- a/streaming_test.go +++ b/streaming_test.go @@ -73,8 +73,8 @@ data: {"content": "foo"} } case *ErrorEvent: passError = true - if event.err == nil { - t.Fatalf("should be fail: %v", event.err) + if event.Err == nil { + t.Fatalf("should be fail: %v", event.Err) } } } @@ -126,8 +126,8 @@ data: {"content": "foo"} switch event := e.(type) { case *ErrorEvent: passError = true - if event.err == nil { - t.Fatalf("should be fail: %v", event.err) + if event.Err == nil { + t.Fatalf("should be fail: %v", event.Err) } case *UpdateEvent: cnt++ @@ -183,8 +183,8 @@ func TestDoStreaming(t *testing.T) { for e := range q { if event, ok := e.(*ErrorEvent); ok { passError = true - if event.err == nil { - t.Fatalf("should be fail: %v", event.err) + if event.Err == nil { + t.Fatalf("should be fail: %v", event.Err) } } } diff --git a/streaming_ws.go b/streaming_ws.go index 5658bbf..ad9d7c2 100644 --- a/streaming_ws.go +++ b/streaming_ws.go @@ -88,7 +88,7 @@ func (c *WSClient) streamingWS(ctx context.Context, stream, tag string) (chan Ev func (c *WSClient) handleWS(ctx context.Context, rawurl string, q chan Event) error { conn, err := c.dialRedirect(rawurl) if err != nil { - q <- &ErrorEvent{err: err} + q <- &ErrorEvent{Err: err} // End. return err @@ -103,7 +103,7 @@ func (c *WSClient) handleWS(ctx context.Context, rawurl string, q chan Event) er for { select { case <-ctx.Done(): - q <- &ErrorEvent{err: ctx.Err()} + q <- &ErrorEvent{Err: ctx.Err()} // End. return ctx.Err() @@ -113,7 +113,7 @@ func (c *WSClient) handleWS(ctx context.Context, rawurl string, q chan Event) er var s Stream err := conn.ReadJSON(&s) if err != nil { - q <- &ErrorEvent{err: err} + q <- &ErrorEvent{Err: err} // Reconnect. break diff --git a/streaming_ws_test.go b/streaming_ws_test.go index 7e78890..8f4b108 100644 --- a/streaming_ws_test.go +++ b/streaming_ws_test.go @@ -135,13 +135,13 @@ func wsTest(t *testing.T, q chan Event, cancel func()) { t.Fatalf("want %q but %q", "1234567", events[3].(*DeleteEvent).ID) } if errorEvent, ok := events[4].(*ErrorEvent); !ok { - t.Fatalf("should be fail: %v", errorEvent.err) + t.Fatalf("should be fail: %v", errorEvent.Err) } if errorEvent, ok := events[5].(*ErrorEvent); !ok { - t.Fatalf("should be fail: %v", errorEvent.err) + t.Fatalf("should be fail: %v", errorEvent.Err) } if errorEvent, ok := events[6].(*ErrorEvent); !ok { - t.Fatalf("should be fail: %v", errorEvent.err) + t.Fatalf("should be fail: %v", errorEvent.Err) } } @@ -168,7 +168,7 @@ func TestStreamingWS(t *testing.T) { defer wg.Done() e := <-q if errorEvent, ok := e.(*ErrorEvent); !ok { - t.Errorf("should be fail: %v", errorEvent.err) + t.Errorf("should be fail: %v", errorEvent.Err) } }() wg.Wait() @@ -204,7 +204,7 @@ func TestHandleWS(t *testing.T) { defer wg.Done() e := <-q if errorEvent, ok := e.(*ErrorEvent); !ok { - t.Errorf("should be fail: %v", errorEvent.err) + t.Errorf("should be fail: %v", errorEvent.Err) } }() err := client.handleWS(context.Background(), ":", q) @@ -219,7 +219,7 @@ func TestHandleWS(t *testing.T) { defer wg.Done() e := <-q if errorEvent, ok := e.(*ErrorEvent); !ok { - t.Errorf("should be fail: %v", errorEvent.err) + t.Errorf("should be fail: %v", errorEvent.Err) } }() err = client.handleWS(ctx, "ws://"+ts.Listener.Addr().String(), q) @@ -232,7 +232,7 @@ func TestHandleWS(t *testing.T) { defer wg.Done() e := <-q if errorEvent, ok := e.(*ErrorEvent); !ok { - t.Errorf("should be fail: %v", errorEvent.err) + t.Errorf("should be fail: %v", errorEvent.Err) } }() client.handleWS(context.Background(), "ws://"+ts.Listener.Addr().String(), q)