From 3b87a64f0da58ac9b83c3f40780f52763cc69cc3 Mon Sep 17 00:00:00 2001 From: Thomas Bruyelle Date: Tue, 15 Oct 2013 10:36:00 +0200 Subject: [PATCH] add support for Release assets Includes Repositories.ListReleaseAssets, Repositories.GetReleaseAsset, Repositories.EditReleaseAsset, Repositories.DeleteReleaseAsset, Repositories.UploadReleaseAsset. --- github/github.go | 33 ++++++++- github/github_test.go | 4 +- github/repos_releases.go | 125 +++++++++++++++++++++++++++++++- github/repos_releases_test.go | 129 +++++++++++++++++++++++++++++++--- github/strings_test.go | 2 + 5 files changed, 278 insertions(+), 15 deletions(-) diff --git a/github/github.go b/github/github.go index 2df7917..1748461 100644 --- a/github/github.go +++ b/github/github.go @@ -10,6 +10,7 @@ import ( "encoding/json" "errors" "fmt" + "io" "io/ioutil" "net/http" "net/url" @@ -24,6 +25,7 @@ import ( const ( libraryVersion = "0.1" defaultBaseURL = "https://api.github.com/" + uploadBaseURL = "https://uploads.github.com/" userAgent = "go-github/" + libraryVersion headerRateLimit = "X-RateLimit-Limit" @@ -44,6 +46,9 @@ type Client struct { // always be specified with a trailing slash. BaseURL *url.URL + // Base URL for uploading files. + UploadURL *url.URL + // User agent used when communicating with the GitHub API. UserAgent string @@ -75,6 +80,11 @@ type ListOptions struct { PerPage int `url:"per_page,omitempty"` } +// UploadOptions specifies the parameters to methods that support uploads. +type UploadOptions struct { + Name string `url:"name,omitempty"` +} + // addOptions adds the parameters in opt as URL query parameters to s. opt // must be a struct whose fields may contain "url" tags. func addOptions(s string, opt interface{}) (string, error) { @@ -106,8 +116,9 @@ func NewClient(httpClient *http.Client) *Client { httpClient = http.DefaultClient } baseURL, _ := url.Parse(defaultBaseURL) + uploadURL, _ := url.Parse(uploadBaseURL) - c := &Client{client: httpClient, BaseURL: baseURL, UserAgent: userAgent} + c := &Client{client: httpClient, BaseURL: baseURL, UserAgent: userAgent, UploadURL: uploadURL} c.Activity = &ActivityService{client: c} c.Gists = &GistsService{client: c} c.Git = &GitService{client: c} @@ -150,6 +161,26 @@ func (c *Client) NewRequest(method, urlStr string, body interface{}) (*http.Requ return req, nil } +// NewUploadRequest creates an upload request. A relative URL can be provided in +// urlStr, in which case it is resolved relative to the UploadURL of the Client. +// Relative URLs should always be specified without a preceding slash. +func (c *Client) NewUploadRequest(urlStr string, reader io.Reader, contentType string) (*http.Request, error) { + rel, err := url.Parse(urlStr) + if err != nil { + return nil, err + } + + u := c.UploadURL.ResolveReference(rel) + req, err := http.NewRequest("POST", u.String(), reader) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + req.Header.Add("User-Agent", c.UserAgent) + return req, nil +} + // Response is a GitHub API response. This wraps the standard http.Response // returned from GitHub and provides convenient access to things like // pagination links. diff --git a/github/github_test.go b/github/github_test.go index 0bde882..76d943e 100644 --- a/github/github_test.go +++ b/github/github_test.go @@ -40,7 +40,9 @@ func setup() { // github client configured to use test server client = NewClient(nil) - client.BaseURL, _ = url.Parse(server.URL) + url, _ := url.Parse(server.URL) + client.BaseURL = url + client.UploadURL = url } // teardown closes the test HTTP server. diff --git a/github/repos_releases.go b/github/repos_releases.go index aea8ed9..f56d593 100644 --- a/github/repos_releases.go +++ b/github/repos_releases.go @@ -5,7 +5,10 @@ package github -import "fmt" +import ( + "fmt" + "io" +) // RepositoryRelease represents a GitHub release in a repository. type RepositoryRelease struct { @@ -14,8 +17,8 @@ type RepositoryRelease struct { TargetCommitish *string `json:"target_commitish,omitempty"` Name *string `json:"name,omitempty"` Body *string `json:"body,omitempty"` - Draft *bool `json:"draft"` - Prerelease *bool `json:"prerelease"` + Draft *bool `json:"draft,omitempty"` + Prerelease *bool `json:"prerelease,omitempty"` CreatedAt *Timestamp `json:"created_at,omitempty"` PublishedAt *Timestamp `json:"published_at,omitempty"` URL *string `json:"url,omitempty"` @@ -28,6 +31,24 @@ func (r RepositoryRelease) String() string { return Stringify(r) } +// ReleaseAsset represents a Github release asset in a repository. +type ReleaseAsset struct { + ID *int `json:"id,omitempty"` + URL *string `json:"url,omitempty"` + Name *string `json:"name,omitempty"` + Label *string `json:"label,omitempty"` + State *string `json:"state,omitempty"` + ContentType *string `json:"content_type,omitempty"` + Size *int `json:"size,omitempty"` + DownloadCount *int `json:"download_count,omitempty"` + CreatedAt *Timestamp `json:"created_at,omitempty"` + UpdatedAt *Timestamp `json:"updated_at,omitempty"` +} + +func (r ReleaseAsset) String() string { + return Stringify(r) +} + // ListReleases lists the releases for a repository. // // GitHub API docs: http://developer.github.com/v3/repos/releases/#list-releases-for-a-repository @@ -121,3 +142,101 @@ func (s *RepositoriesService) DeleteRelease(owner, repo string, id int) (*Respon req.Header.Add("Accept", mimeReleasePreview) return s.client.Do(req, nil) } + +// ListReleaseAssets lists the release's assets. +// +// GitHub API docs : http://developer.github.com/v3/repos/releases/#list-assets-for-a-release +func (s *RepositoriesService) ListReleaseAssets(owner, repo string, id int) ([]ReleaseAsset, *Response, error) { + u := fmt.Sprintf("repos/%s/%s/releases/%d/assets", owner, repo, id) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + req.Header.Add("Accept", mimeReleasePreview) + + assets := new([]ReleaseAsset) + resp, err := s.client.Do(req, assets) + if err != nil { + return nil, resp, nil + } + return *assets, resp, err +} + +// GetReleaseAsset fetches a single release asset. +// +// GitHub API docs : http://developer.github.com/v3/repos/releases/#get-a-single-release-asset +func (s *RepositoriesService) GetReleaseAsset(owner, repo string, id int) (*ReleaseAsset, *Response, error) { + u := fmt.Sprintf("repos/%s/%s/releases/assets/%d", owner, repo, id) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + req.Header.Add("Accept", mimeReleasePreview) + + asset := new(ReleaseAsset) + resp, err := s.client.Do(req, asset) + if err != nil { + return nil, resp, nil + } + return asset, resp, err +} + +// EditReleaseAsset edits a repository release asset. +// +// GitHub API docs : http://developer.github.com/v3/repos/releases/#edit-a-release-asset +func (s *RepositoriesService) EditReleaseAsset(owner, repo string, id int, release *ReleaseAsset) (*ReleaseAsset, *Response, error) { + u := fmt.Sprintf("repos/%s/%s/releases/assets/%d", owner, repo, id) + + req, err := s.client.NewRequest("PATCH", u, release) + if err != nil { + return nil, nil, err + } + req.Header.Add("Accept", mimeReleasePreview) + + asset := new(ReleaseAsset) + resp, err := s.client.Do(req, asset) + if err != nil { + return nil, resp, err + } + return asset, resp, err +} + +// DeleteReleaseAsset delete a single release asset from a repository. +// +// GitHub API docs : http://developer.github.com/v3/repos/releases/#delete-a-release-asset +func (s *RepositoriesService) DeleteReleaseAsset(owner, repo string, id int) (*Response, error) { + u := fmt.Sprintf("repos/%s/%s/releases/assets/%d", owner, repo, id) + + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + req.Header.Add("Accept", mimeReleasePreview) + return s.client.Do(req, nil) +} + +// UploadReleaseAsset creates an asset by uploading a file into a release repository. +// +// GitHub API docs : http://developer.github.com/v3/repos/releases/#upload-a-release-asset +func (s *RepositoriesService) UploadReleaseAsset(owner, repo string, id int, opt *UploadOptions, reader io.Reader, contentType string) (*ReleaseAsset, *Response, error) { + u := fmt.Sprintf("repos/%s/%s/releases/%d/assets", owner, repo, id) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewUploadRequest(u, reader, contentType) + if err != nil { + return nil, nil, err + } + req.Header.Add("Accept", mimeReleasePreview) + + asset := new(ReleaseAsset) + resp, err := s.client.Do(req, asset) + if err != nil { + return nil, resp, err + } + return asset, resp, err +} diff --git a/github/repos_releases_test.go b/github/repos_releases_test.go index e22741c..88ac442 100644 --- a/github/repos_releases_test.go +++ b/github/repos_releases_test.go @@ -10,6 +10,7 @@ import ( "fmt" "net/http" "reflect" + "strings" "testing" ) @@ -17,13 +18,13 @@ func TestRepositoriesService_ListReleases(t *testing.T) { setup() defer teardown() - mux.HandleFunc("/repos/o/u/releases", func(w http.ResponseWriter, r *http.Request) { + mux.HandleFunc("/repos/o/r/releases", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, "GET") testHeader(t, r, "Accept", mimeReleasePreview) fmt.Fprint(w, `[{"id":1}]`) }) - releases, _, err := client.Repositories.ListReleases("o", "u") + releases, _, err := client.Repositories.ListReleases("o", "r") if err != nil { t.Errorf("Repositories.ListReleases returned error: %v", err) } @@ -37,13 +38,13 @@ func TestRepositoriesService_GetRelease(t *testing.T) { setup() defer teardown() - mux.HandleFunc("/repos/o/u/releases/1", func(w http.ResponseWriter, r *http.Request) { + mux.HandleFunc("/repos/o/r/releases/1", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, "GET") testHeader(t, r, "Accept", mimeReleasePreview) fmt.Fprint(w, `{"id":1}`) }) - release, resp, err := client.Repositories.GetRelease("o", "u", 1) + release, resp, err := client.Repositories.GetRelease("o", "r", 1) if err != nil { t.Errorf("Repositories.GetRelease returned error: %v\n%v", err, resp.Body) } @@ -60,7 +61,7 @@ func TestRepositoriesService_CreateRelease(t *testing.T) { input := &RepositoryRelease{Name: String("v1.0")} - mux.HandleFunc("/repos/o/u/releases", func(w http.ResponseWriter, r *http.Request) { + mux.HandleFunc("/repos/o/r/releases", func(w http.ResponseWriter, r *http.Request) { v := new(RepositoryRelease) json.NewDecoder(r.Body).Decode(v) @@ -72,7 +73,7 @@ func TestRepositoriesService_CreateRelease(t *testing.T) { fmt.Fprint(w, `{"id":1}`) }) - release, _, err := client.Repositories.CreateRelease("o", "u", input) + release, _, err := client.Repositories.CreateRelease("o", "r", input) if err != nil { t.Errorf("Repositories.CreateRelease returned error: %v", err) } @@ -89,7 +90,7 @@ func TestRepositoriesService_EditRelease(t *testing.T) { input := &RepositoryRelease{Name: String("n")} - mux.HandleFunc("/repos/o/u/releases/1", func(w http.ResponseWriter, r *http.Request) { + mux.HandleFunc("/repos/o/r/releases/1", func(w http.ResponseWriter, r *http.Request) { v := new(RepositoryRelease) json.NewDecoder(r.Body).Decode(v) @@ -101,7 +102,7 @@ func TestRepositoriesService_EditRelease(t *testing.T) { fmt.Fprint(w, `{"id":1}`) }) - release, _, err := client.Repositories.EditRelease("o", "u", 1, input) + release, _, err := client.Repositories.EditRelease("o", "r", 1, input) if err != nil { t.Errorf("Repositories.EditRelease returned error: %v", err) } @@ -115,13 +116,121 @@ func TestRepositoriesService_DeleteRelease(t *testing.T) { setup() defer teardown() - mux.HandleFunc("/repos/o/u/releases/1", func(w http.ResponseWriter, r *http.Request) { + mux.HandleFunc("/repos/o/r/releases/1", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, "DELETE") testHeader(t, r, "Accept", mimeReleasePreview) }) - _, err := client.Repositories.DeleteRelease("o", "u", 1) + _, err := client.Repositories.DeleteRelease("o", "r", 1) if err != nil { t.Errorf("Repositories.DeleteRelease returned error: %v", err) } } + +func TestRepositoriesService_ListReleaseAssets(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/releases/1/assets", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mimeReleasePreview) + fmt.Fprint(w, `[{"id":1}]`) + }) + + assets, _, err := client.Repositories.ListReleaseAssets("o", "r", 1) + if err != nil { + t.Errorf("Repositories.ListReleaseAssets returned error: %v", err) + } + want := []ReleaseAsset{{ID: Int(1)}} + if !reflect.DeepEqual(assets, want) { + t.Errorf("Repositories.ListReleaseAssets returned %+v, want %+v", assets, want) + } +} + +func TestRepositoriesService_GetReleaseAsset(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/releases/assets/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mimeReleasePreview) + fmt.Fprint(w, `{"id":1}`) + }) + + asset, _, err := client.Repositories.GetReleaseAsset("o", "r", 1) + if err != nil { + t.Errorf("Repositories.GetReleaseAsset returned error: %v", err) + } + want := &ReleaseAsset{ID: Int(1)} + if !reflect.DeepEqual(asset, want) { + t.Errorf("Repositories.GetReleaseAsset returned %+v, want %+v", asset, want) + } +} + +func TestRepositoriesService_EditReleaseAsset(t *testing.T) { + setup() + defer teardown() + + input := &ReleaseAsset{Name: String("n")} + + mux.HandleFunc("/repos/o/r/releases/assets/1", func(w http.ResponseWriter, r *http.Request) { + v := new(ReleaseAsset) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "PATCH") + testHeader(t, r, "Accept", mimeReleasePreview) + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + fmt.Fprint(w, `{"id":1}`) + }) + + asset, _, err := client.Repositories.EditReleaseAsset("o", "r", 1, input) + if err != nil { + t.Errorf("Repositories.EditReleaseAsset returned error: %v", err) + } + want := &ReleaseAsset{ID: Int(1)} + if !reflect.DeepEqual(asset, want) { + t.Errorf("Repositories.EditReleaseAsset returned = %+v, want %+v", asset, want) + } +} + +func TestRepositoriesService_DeleteReleaseAsset(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/releases/assets/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + testHeader(t, r, "Accept", mimeReleasePreview) + }) + + _, err := client.Repositories.DeleteReleaseAsset("o", "r", 1) + if err != nil { + t.Errorf("Repositories.DeleteReleaseAsset returned error: %v", err) + } +} + +func TestRepositoriesService_UploadReleaseAsset(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/releases/1/assets", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + testHeader(t, r, "Accept", mimeReleasePreview) + testHeader(t, r, "Content-Type", "application/zip") + testFormValues(t, r, values{"name": "n"}) + + fmt.Fprintf(w, `{"id":1}`) + }) + + opt := &UploadOptions{Name: "n"} + data := strings.NewReader("data") + asset, _, err := client.Repositories.UploadReleaseAsset("o", "r", 1, opt, data, "application/zip") + if err != nil { + t.Errorf("Repositories.UploadReleaseAssert returned error: %v", err) + } + want := &ReleaseAsset{ID: Int(1)} + if !reflect.DeepEqual(asset, want) { + t.Errorf("Repositories.UploadReleaseAssert returned %+v, want %+v", asset, want) + } +} diff --git a/github/strings_test.go b/github/strings_test.go index 7367e0c..df39fb3 100644 --- a/github/strings_test.go +++ b/github/strings_test.go @@ -120,6 +120,8 @@ func TestString(t *testing.T) { {WebHookAuthor{Name: String("n")}, `github.WebHookAuthor{Name:"n"}`}, {WebHookCommit{ID: String("1")}, `github.WebHookCommit{ID:"1"}`}, {WebHookPayload{Ref: String("r")}, `github.WebHookPayload{Ref:"r"}`}, + {RepositoryRelease{ID: Int(1)}, `github.RepositoryRelease{ID:1}`}, + {ReleaseAsset{ID: Int(1)},`github.ReleaseAsset{ID:1}`}, } for i, tt := range tests {