diff --git a/error.go b/error.go new file mode 100644 index 0000000..2b4cdcc --- /dev/null +++ b/error.go @@ -0,0 +1,78 @@ +package jira + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + + "github.com/pkg/errors" +) + +// Error message from JIRA +// See https://docs.atlassian.com/jira/REST/cloud/#error-responses +type Error struct { + HTTPError error + ErrorMessages []string `json:"errorMessages"` + Errors map[string]string `json:"errors"` +} + +// NewJiraError creates a new jira Error +func NewJiraError(resp *http.Response, httpError error) error { + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return errors.Wrap(err, httpError.Error()) + } + + jerr := Error{HTTPError: httpError} + err = json.Unmarshal(body, &jerr) + if err != nil { + return errors.Wrap(err, err.Error()) + } + + return &jerr +} + +// Error is a short string representing the error +func (e *Error) Error() string { + if len(e.ErrorMessages) > 0 { + // return fmt.Sprintf("%v", e.HTTPError) + return fmt.Sprintf("%s: %v", e.ErrorMessages[0], e.HTTPError) + } + if len(e.Errors) > 0 { + for key, value := range e.Errors { + return fmt.Sprintf("%s - %s: %v", key, value, e.HTTPError) + } + } + return e.HTTPError.Error() +} + +// LongError is a full representation of the error as a string +func (e *Error) LongError() string { + var msg bytes.Buffer + if e.HTTPError != nil { + msg.WriteString("Original:\n") + msg.WriteString(e.HTTPError.Error()) + msg.WriteString("\n") + } + if len(e.ErrorMessages) > 0 { + msg.WriteString("Messages:\n") + for _, v := range e.ErrorMessages { + msg.WriteString(" - ") + msg.WriteString(v) + msg.WriteString("\n") + } + } + if len(e.Errors) > 0 { + for key, value := range e.Errors { + msg.WriteString(" - ") + msg.WriteString(key) + msg.WriteString(" - ") + msg.WriteString(value) + msg.WriteString("\n") + } + } + return msg.String() +} diff --git a/error_test.go b/error_test.go new file mode 100644 index 0000000..3b76a2c --- /dev/null +++ b/error_test.go @@ -0,0 +1,131 @@ +package jira + +import ( + "errors" + "fmt" + "io" + "net/http" + "net/http/httptest" + "strings" + "testing" +) + +func TestError_NewJiraError(t *testing.T) { + handler := func(w http.ResponseWriter, r *http.Request) { + io.WriteString(w, `{"errorMessages":["Issue does not exist or you do not have permission to see it."],"errors":{}}`) + } + + req := httptest.NewRequest("GET", "http://example.com/foo", nil) + w := httptest.NewRecorder() + handler(w, req) + resp := w.Result() + + err := NewJiraError(resp, errors.New("Original http error")) + if err, ok := err.(*Error); ok { + t.Errorf("Expected jira Error. Got %s", err.Error()) + } + fmt.Println(err.Error()) +} + +func TestError_NilOriginalMessage(t *testing.T) { + defer func() { + if r := recover(); r != nil { + t.Errorf("Expected an error message. Got a panic (%v)", r) + } + }() + + msgErr := &Error{ + HTTPError: nil, + ErrorMessages: []string{"Issue does not exist"}, + Errors: map[string]string{ + "issuetype": "issue type is required", + "title": "title is required", + }, + } + + _ = msgErr.Error() +} + +func TestError_NilOriginalMessageLongError(t *testing.T) { + defer func() { + if r := recover(); r != nil { + t.Errorf("Expected an error message. Got a panic (%v)", r) + } + }() + + msgErr := &Error{ + HTTPError: nil, + ErrorMessages: []string{"Issue does not exist"}, + Errors: map[string]string{ + "issuetype": "issue type is required", + "title": "title is required", + }, + } + + _ = msgErr.LongError() +} + +func TestError_ShortMessage(t *testing.T) { + msgErr := &Error{ + HTTPError: errors.New("Original http error"), + ErrorMessages: []string{"Issue does not exist"}, + Errors: map[string]string{ + "issuetype": "issue type is required", + "title": "title is required", + }, + } + + mapErr := &Error{ + HTTPError: errors.New("Original http error"), + ErrorMessages: nil, + Errors: map[string]string{ + "issuetype": "issue type is required", + "title": "title is required", + }, + } + + noErr := &Error{ + HTTPError: errors.New("Original http error"), + ErrorMessages: nil, + Errors: nil, + } + + err := msgErr.Error() + if err != "Issue does not exist: Original http error" { + t.Errorf("Expected short message. Got %s", err) + } + + err = mapErr.Error() + if err != "issuetype - issue type is required: Original http error" { + t.Errorf("Expected short message. Got %s", err) + } + + err = noErr.Error() + if err != "Original http error" { + t.Errorf("Expected original error message. Got %s", err) + } +} + +func TestError_LongMessage(t *testing.T) { + longError := &Error{ + HTTPError: errors.New("Original http error"), + ErrorMessages: []string{"Issue does not exist."}, + Errors: map[string]string{ + "issuetype": "issue type is required", + "title": "title is required", + }, + } + + msg := longError.LongError() + if !strings.Contains(msg, "Original http error") { + t.Errorf("Expected the error message: Got\n%s\n", msg) + } + + if !strings.Contains(msg, "Issue does not exist") { + t.Errorf("Expected the error message: Got\n%s\n", msg) + } + + if !strings.Contains(msg, "title - title is required") { + t.Errorf("Expected the error map: Got\n%s\n", msg) + } +} diff --git a/issue.go b/issue.go index b9c9610..6372e4a 100644 --- a/issue.go +++ b/issue.go @@ -363,7 +363,8 @@ func (s *IssueService) Get(issueID string) (*Issue, *Response, error) { issue := new(Issue) resp, err := s.client.Do(req, issue) if err != nil { - return nil, resp, err + jerr := NewJiraError(resp.Response, err) + return nil, resp, jerr } return issue, resp, nil