1
0
mirror of https://github.com/interviewstreet/go-jira.git synced 2025-02-01 13:07:50 +02:00

Wrap http.Response in Response struct

The Response struct has StartAt, MaxResults and Total fields, which are
returned from JIRA API in issue search responses. They are now
accessible in Response object.
This commit is contained in:
Maciej Kwiek 2016-06-15 17:09:13 +02:00
parent 6b49178e1f
commit facc86872b
5 changed files with 83 additions and 22 deletions

View File

@ -5,7 +5,6 @@ import (
"fmt"
"io"
"mime/multipart"
"net/http"
"net/url"
"strings"
"time"
@ -281,7 +280,10 @@ type CommentVisibility struct {
// searchResult is only a small wrapper arround the Search (with JQL) method
// to be able to parse the results
type searchResult struct {
Issues []Issue `json:"issues"`
Issues []Issue `json:"issues"`
StartAt int `json:"startAt"`
MaxResults int `json:"maxResults"`
Total int `json:"total"`
}
// CustomFields represents custom fields of JIRA
@ -294,7 +296,7 @@ type CustomFields map[string]string
// If the issue cannot be found via an exact match, JIRA will also look for the issue in a case-insensitive way, or by looking to see if the issue was moved.
//
// JIRA API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/issue-getIssue
func (s *IssueService) Get(issueID string) (*Issue, *http.Response, error) {
func (s *IssueService) Get(issueID string) (*Issue, *Response, error) {
apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s", issueID)
req, err := s.client.NewRequest("GET", apiEndpoint, nil)
if err != nil {
@ -310,11 +312,11 @@ func (s *IssueService) Get(issueID string) (*Issue, *http.Response, error) {
return issue, resp, nil
}
// DownloadAttachment returns a http.Response of an attachment for a given attachmentID.
// The attachment is in the http.Response.Body of the response.
// DownloadAttachment returns a Response of an attachment for a given attachmentID.
// The attachment is in the Response.Body of the response.
// This is an io.ReadCloser.
// The caller should close the resp.Body.
func (s *IssueService) DownloadAttachment(attachmentID string) (*http.Response, error) {
func (s *IssueService) DownloadAttachment(attachmentID string) (*Response, error) {
apiEndpoint := fmt.Sprintf("secure/attachment/%s/", attachmentID)
req, err := s.client.NewRequest("GET", apiEndpoint, nil)
if err != nil {
@ -330,7 +332,7 @@ func (s *IssueService) DownloadAttachment(attachmentID string) (*http.Response,
}
// PostAttachment uploads r (io.Reader) as an attachment to a given attachmentID
func (s *IssueService) PostAttachment(attachmentID string, r io.Reader, attachmentName string) (*[]Attachment, *http.Response, error) {
func (s *IssueService) PostAttachment(attachmentID string, r io.Reader, attachmentName string) (*[]Attachment, *Response, error) {
apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/attachments", attachmentID)
b := new(bytes.Buffer)
@ -371,7 +373,7 @@ func (s *IssueService) PostAttachment(attachmentID string, r io.Reader, attachme
// The issueType field must correspond to a sub-task issue type and you must provide a parent field in the issue create request containing the id or key of the parent issue.
//
// JIRA API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/issue-createIssues
func (s *IssueService) Create(issue *Issue) (*Issue, *http.Response, error) {
func (s *IssueService) Create(issue *Issue) (*Issue, *Response, error) {
apiEndpoint := "rest/api/2/issue/"
req, err := s.client.NewRequest("POST", apiEndpoint, issue)
if err != nil {
@ -390,7 +392,7 @@ func (s *IssueService) Create(issue *Issue) (*Issue, *http.Response, error) {
// AddComment adds a new comment to issueID.
//
// JIRA API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/issue-addComment
func (s *IssueService) AddComment(issueID string, comment *Comment) (*Comment, *http.Response, error) {
func (s *IssueService) AddComment(issueID string, comment *Comment) (*Comment, *Response, error) {
apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/comment", issueID)
req, err := s.client.NewRequest("POST", apiEndpoint, comment)
if err != nil {
@ -409,7 +411,7 @@ func (s *IssueService) AddComment(issueID string, comment *Comment) (*Comment, *
// AddLink adds a link between two issues.
//
// JIRA API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/issueLink
func (s *IssueService) AddLink(issueLink *IssueLink) (*http.Response, error) {
func (s *IssueService) AddLink(issueLink *IssueLink) (*Response, error) {
apiEndpoint := fmt.Sprintf("rest/api/2/issueLink")
req, err := s.client.NewRequest("POST", apiEndpoint, issueLink)
if err != nil {
@ -423,7 +425,7 @@ func (s *IssueService) AddLink(issueLink *IssueLink) (*http.Response, error) {
// Search will search for tickets according to the jql
//
// JIRA API docs: https://developer.atlassian.com/jiradev/jira-apis/jira-rest-apis/jira-rest-api-tutorials/jira-rest-api-example-query-issues
func (s *IssueService) Search(jql string) ([]Issue, *http.Response, error) {
func (s *IssueService) Search(jql string) ([]Issue, *Response, error) {
u := fmt.Sprintf("rest/api/2/search?jql=%s", url.QueryEscape(jql))
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
@ -436,7 +438,7 @@ func (s *IssueService) Search(jql string) ([]Issue, *http.Response, error) {
}
// GetCustomFields returns a map of customfield_* keys with string values
func (s *IssueService) GetCustomFields(issueID string) (CustomFields, *http.Response, error) {
func (s *IssueService) GetCustomFields(issueID string) (CustomFields, *Response, error) {
apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s", issueID)
req, err := s.client.NewRequest("GET", apiEndpoint, nil)
if err != nil {

View File

@ -318,7 +318,7 @@ func TestIssue_Search(t *testing.T) {
testMethod(t, r, "GET")
testRequestURL(t, r, "/rest/api/2/search?jql=something")
w.WriteHeader(http.StatusOK)
fmt.Fprint(w, `{"expand": "schema,names","startAt": 0,"maxResults": 50,"total": 6,"issues": [{"expand": "html","id": "10230","self": "http://kelpie9:8081/rest/api/2/issue/BULK-62","key": "BULK-62","fields": {"summary": "testing","timetracking": null,"issuetype": {"self": "http://kelpie9:8081/rest/api/2/issuetype/5","id": "5","description": "The sub-task of the issue","iconUrl": "http://kelpie9:8081/images/icons/issue_subtask.gif","name": "Sub-task","subtask": true},"customfield_10071": null}},{"expand": "html","id": "10004","self": "http://kelpie9:8081/rest/api/2/issue/BULK-47","key": "BULK-47","fields": {"summary": "Cheese v1 2.0 issue","timetracking": null,"issuetype": {"self": "http://kelpie9:8081/rest/api/2/issuetype/3","id": "3","description": "A task that needs to be done.","iconUrl": "http://kelpie9:8081/images/icons/task.gif","name": "Task","subtask": false}}}]}`)
fmt.Fprint(w, `{"expand": "schema,names","startAt": 1,"maxResults": 40,"total": 6,"issues": [{"expand": "html","id": "10230","self": "http://kelpie9:8081/rest/api/2/issue/BULK-62","key": "BULK-62","fields": {"summary": "testing","timetracking": null,"issuetype": {"self": "http://kelpie9:8081/rest/api/2/issuetype/5","id": "5","description": "The sub-task of the issue","iconUrl": "http://kelpie9:8081/images/icons/issue_subtask.gif","name": "Sub-task","subtask": true},"customfield_10071": null}},{"expand": "html","id": "10004","self": "http://kelpie9:8081/rest/api/2/issue/BULK-47","key": "BULK-47","fields": {"summary": "Cheese v1 2.0 issue","timetracking": null,"issuetype": {"self": "http://kelpie9:8081/rest/api/2/issuetype/3","id": "3","description": "A task that needs to be done.","iconUrl": "http://kelpie9:8081/images/icons/task.gif","name": "Task","subtask": false}}}]}`)
})
_, resp, err := testClient.Issue.Search("something")
@ -328,6 +328,16 @@ func TestIssue_Search(t *testing.T) {
if err != nil {
t.Errorf("Error given: %s", err)
}
if resp.StartAt != 1 {
t.Errorf("StartAt should populate with 1, %v given", resp.StartAt)
}
if resp.MaxResults != 40 {
t.Errorf("StartAt should populate with 40, %v given", resp.MaxResults)
}
if resp.Total != 6 {
t.Errorf("StartAt should populate with 6, %v given", resp.Total)
}
}
func Test_CustomFields(t *testing.T) {

41
jira.go
View File

@ -122,25 +122,26 @@ func (c *Client) NewMultiPartRequest(method, urlStr string, buf *bytes.Buffer) (
// Do sends an API request and returns the API response.
// The API response is JSON decoded and stored in the value pointed to by v, or returned as an error if an API error has occurred.
func (c *Client) Do(req *http.Request, v interface{}) (*http.Response, error) {
resp, err := c.client.Do(req)
func (c *Client) Do(req *http.Request, v interface{}) (*Response, error) {
httpResp, err := c.client.Do(req)
if err != nil {
return nil, err
}
err = CheckResponse(resp)
err = CheckResponse(httpResp)
if err != nil {
// Even though there was an error, we still return the response
// in case the caller wants to inspect it further
return resp, err
return newResponse(httpResp, nil), err
}
if v != nil {
// Open a NewDecoder and defer closing the reader only if there is a provided interface to decode to
defer resp.Body.Close()
err = json.NewDecoder(resp.Body).Decode(v)
defer httpResp.Body.Close()
err = json.NewDecoder(httpResp.Body).Decode(v)
}
resp := newResponse(httpResp, v)
return resp, err
}
@ -162,3 +163,31 @@ func CheckResponse(r *http.Response) error {
func (c *Client) GetBaseURL() url.URL {
return *c.baseURL
}
// Response represents JIRA API response. It wraps http.Response returned from
// API and provides information about paging.
type Response struct {
*http.Response
StartAt int
MaxResults int
Total int
}
func newResponse(r *http.Response, v interface{}) *Response {
resp := &Response{Response: r}
resp.populatePageValues(v)
return resp
}
// Sets paging values if response json was parsed to searchResult type
// (can be extended with other types if they also need paging info)
func (r *Response) populatePageValues(v interface{}) {
switch value := v.(type) {
case *searchResult:
r.StartAt = value.StartAt
r.MaxResults = value.MaxResults
r.Total = value.Total
}
return
}

View File

@ -296,3 +296,24 @@ func TestGetBaseURL_WithURL(t *testing.T) {
t.Errorf("Base URLs are not equal. Expected %+v, got %+v", *u, b)
}
}
func TestPagingInfoEmptyByDefault(t *testing.T) {
c, _ := NewClient(nil, testJIRAInstanceURL)
req, _ := c.NewRequest("GET", "/", nil)
type foo struct {
A string
}
body := new(foo)
resp, _ := c.Do(req, body)
if resp.StartAt != 0 {
t.Errorf("StartAt not equal to 0")
}
if resp.MaxResults != 0 {
t.Errorf("StartAt not equal to 0")
}
if resp.Total != 0 {
t.Errorf("StartAt not equal to 0")
}
}

View File

@ -2,7 +2,6 @@ package jira
import (
"fmt"
"net/http"
)
// ProjectService handles projects for the JIRA instance / API.
@ -73,7 +72,7 @@ type ProjectComponent struct {
// GetList gets all projects form JIRA
//
// JIRA API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/project-getAllProjects
func (s *ProjectService) GetList() (*ProjectList, *http.Response, error) {
func (s *ProjectService) GetList() (*ProjectList, *Response, error) {
apiEndpoint := "rest/api/2/project"
req, err := s.client.NewRequest("GET", apiEndpoint, nil)
if err != nil {
@ -93,7 +92,7 @@ func (s *ProjectService) GetList() (*ProjectList, *http.Response, error) {
// This can be an project id, or an project key.
//
// JIRA API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/project-getProject
func (s *ProjectService) Get(projectID string) (*Project, *http.Response, error) {
func (s *ProjectService) Get(projectID string) (*Project, *Response, error) {
apiEndpoint := fmt.Sprintf("/rest/api/2/project/%s", projectID)
req, err := s.client.NewRequest("GET", apiEndpoint, nil)
if err != nil {