Introduce APIError type and make ErrorEvent.Err public
This makes it a little bit easier to act on API errors that happen while streaming.pull/177/head
parent
9faaa4f0dc
commit
972ffb4771
25
helper.go
25
helper.go
|
@ -3,12 +3,26 @@ package mastodon
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"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.
|
// Base64EncodeFileName returns the base64 data URI format string of the file with the file name.
|
||||||
func Base64EncodeFileName(filename string) (string, error) {
|
func Base64EncodeFileName(filename string) (string, error) {
|
||||||
file, err := os.Open(filename)
|
file, err := os.Open(filename)
|
||||||
|
@ -41,15 +55,18 @@ func Base64Encode(file *os.File) (string, error) {
|
||||||
func String(v string) *string { return &v }
|
func String(v string) *string { return &v }
|
||||||
|
|
||||||
func parseAPIError(prefix string, resp *http.Response) error {
|
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 {
|
var e struct {
|
||||||
Error string `json:"error"`
|
Error string `json:"error"`
|
||||||
}
|
}
|
||||||
|
|
||||||
json.NewDecoder(resp.Body).Decode(&e)
|
json.NewDecoder(resp.Body).Decode(&e)
|
||||||
if e.Error != "" {
|
if e.Error != "" {
|
||||||
errMsg = fmt.Sprintf("%s: %s", errMsg, e.Error)
|
res.Message = e.Error
|
||||||
}
|
}
|
||||||
|
|
||||||
return errors.New(errMsg)
|
return &res
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,7 +73,11 @@ func TestString(t *testing.T) {
|
||||||
func TestParseAPIError(t *testing.T) {
|
func TestParseAPIError(t *testing.T) {
|
||||||
// No api error.
|
// No api error.
|
||||||
r := ioutil.NopCloser(strings.NewReader(`<html><head><title>404</title></head></html>`))
|
r := ioutil.NopCloser(strings.NewReader(`<html><head><title>404</title></head></html>`))
|
||||||
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"
|
want := "bad request: 404 Not Found"
|
||||||
if err.Error() != want {
|
if err.Error() != want {
|
||||||
t.Fatalf("want %q but %q", want, err.Error())
|
t.Fatalf("want %q but %q", want, err.Error())
|
||||||
|
@ -81,7 +85,11 @@ func TestParseAPIError(t *testing.T) {
|
||||||
|
|
||||||
// With api error.
|
// With api error.
|
||||||
r = ioutil.NopCloser(strings.NewReader(`{"error":"Record not found"}`))
|
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"
|
want = "bad request: 404 Not Found: Record not found"
|
||||||
if err.Error() != want {
|
if err.Error() != want {
|
||||||
t.Fatalf("want %q but %q", want, err.Error())
|
t.Fatalf("want %q but %q", want, err.Error())
|
||||||
|
|
|
@ -40,10 +40,10 @@ type DeleteEvent struct{ ID ID }
|
||||||
func (e *DeleteEvent) event() {}
|
func (e *DeleteEvent) event() {}
|
||||||
|
|
||||||
// ErrorEvent is a struct for passing errors to app.
|
// 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) 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.
|
// Event is an interface passing events to app.
|
||||||
type Event interface {
|
type Event interface {
|
||||||
|
|
|
@ -73,8 +73,8 @@ data: {"content": "foo"}
|
||||||
}
|
}
|
||||||
case *ErrorEvent:
|
case *ErrorEvent:
|
||||||
passError = true
|
passError = true
|
||||||
if event.err == nil {
|
if event.Err == nil {
|
||||||
t.Fatalf("should be fail: %v", event.err)
|
t.Fatalf("should be fail: %v", event.Err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -126,8 +126,8 @@ data: {"content": "foo"}
|
||||||
switch event := e.(type) {
|
switch event := e.(type) {
|
||||||
case *ErrorEvent:
|
case *ErrorEvent:
|
||||||
passError = true
|
passError = true
|
||||||
if event.err == nil {
|
if event.Err == nil {
|
||||||
t.Fatalf("should be fail: %v", event.err)
|
t.Fatalf("should be fail: %v", event.Err)
|
||||||
}
|
}
|
||||||
case *UpdateEvent:
|
case *UpdateEvent:
|
||||||
cnt++
|
cnt++
|
||||||
|
@ -183,8 +183,8 @@ func TestDoStreaming(t *testing.T) {
|
||||||
for e := range q {
|
for e := range q {
|
||||||
if event, ok := e.(*ErrorEvent); ok {
|
if event, ok := e.(*ErrorEvent); ok {
|
||||||
passError = true
|
passError = true
|
||||||
if event.err == nil {
|
if event.Err == nil {
|
||||||
t.Fatalf("should be fail: %v", event.err)
|
t.Fatalf("should be fail: %v", event.Err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
func (c *WSClient) handleWS(ctx context.Context, rawurl string, q chan Event) error {
|
||||||
conn, err := c.dialRedirect(rawurl)
|
conn, err := c.dialRedirect(rawurl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
q <- &ErrorEvent{err: err}
|
q <- &ErrorEvent{Err: err}
|
||||||
|
|
||||||
// End.
|
// End.
|
||||||
return err
|
return err
|
||||||
|
@ -103,7 +103,7 @@ func (c *WSClient) handleWS(ctx context.Context, rawurl string, q chan Event) er
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
q <- &ErrorEvent{err: ctx.Err()}
|
q <- &ErrorEvent{Err: ctx.Err()}
|
||||||
|
|
||||||
// End.
|
// End.
|
||||||
return ctx.Err()
|
return ctx.Err()
|
||||||
|
@ -113,7 +113,7 @@ func (c *WSClient) handleWS(ctx context.Context, rawurl string, q chan Event) er
|
||||||
var s Stream
|
var s Stream
|
||||||
err := conn.ReadJSON(&s)
|
err := conn.ReadJSON(&s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
q <- &ErrorEvent{err: err}
|
q <- &ErrorEvent{Err: err}
|
||||||
|
|
||||||
// Reconnect.
|
// Reconnect.
|
||||||
break
|
break
|
||||||
|
|
|
@ -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)
|
t.Fatalf("want %q but %q", "1234567", events[3].(*DeleteEvent).ID)
|
||||||
}
|
}
|
||||||
if errorEvent, ok := events[4].(*ErrorEvent); !ok {
|
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 {
|
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 {
|
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()
|
defer wg.Done()
|
||||||
e := <-q
|
e := <-q
|
||||||
if errorEvent, ok := e.(*ErrorEvent); !ok {
|
if errorEvent, ok := e.(*ErrorEvent); !ok {
|
||||||
t.Errorf("should be fail: %v", errorEvent.err)
|
t.Errorf("should be fail: %v", errorEvent.Err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
|
@ -204,7 +204,7 @@ func TestHandleWS(t *testing.T) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
e := <-q
|
e := <-q
|
||||||
if errorEvent, ok := e.(*ErrorEvent); !ok {
|
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)
|
err := client.handleWS(context.Background(), ":", q)
|
||||||
|
@ -219,7 +219,7 @@ func TestHandleWS(t *testing.T) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
e := <-q
|
e := <-q
|
||||||
if errorEvent, ok := e.(*ErrorEvent); !ok {
|
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)
|
err = client.handleWS(ctx, "ws://"+ts.Listener.Addr().String(), q)
|
||||||
|
@ -232,7 +232,7 @@ func TestHandleWS(t *testing.T) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
e := <-q
|
e := <-q
|
||||||
if errorEvent, ok := e.(*ErrorEvent); !ok {
|
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)
|
client.handleWS(context.Background(), "ws://"+ts.Listener.Addr().String(), q)
|
||||||
|
|
Loading…
Reference in New Issue