Browse Source

Improve UploadReleaseAsset usage

Inspired by the Ruby version [1], UploadReleaseAsset now accepts a
simple path to a file.

The Content-Type is now determined internally using the
mime.TypeByExtension() function.

Note this version is also more flexible. Indeed, the previous version
needed an io.Reader, but actually only 3 kinds of io.Reader were bore.
This is because of http.NewRequest needs to determine the Content-Length
of the body, and it can only determine it for bytes.Buffer, bytes.Reader
or strings.Reader [2]. Note there is an issue raised about this [3].

With a path to a file, we can easily determine the file size, and then
manually add it to the request.

[1]
https://github.com/octokit/octokit.rb/blob/master/lib/octokit/client/releases.rb#L85
[2] http://golang.org/src/pkg/net/http/request.go#L447
[3] https://code.google.com/p/go/issues/detail?id=6738
Thomas Bruyelle 12 years ago
committed by Will Norris
parent
commit
c248a08b00
5 changed files with 46 additions and 10 deletions
  1. +8
    -3
      github/github.go
  2. +12
    -0
      github/github_test.go
  3. +16
    -3
      github/repos_releases.go
  4. +9
    -4
      github/repos_releases_test.go
  5. +1
    -0
      github/testdata/upload.txt

+ 8
- 3
github/github.go View File

@ -32,7 +32,8 @@ const (
headerRateRemaining = "X-RateLimit-Remaining" headerRateRemaining = "X-RateLimit-Remaining"
headerRateReset = "X-RateLimit-Reset" headerRateReset = "X-RateLimit-Reset"
mediaTypeV3 = "application/vnd.github.v3+json"
mediaTypeV3 = "application/vnd.github.v3+json"
defaultMediaType = "application/octet-stream"
) )
// A Client manages communication with the GitHub API. // A Client manages communication with the GitHub API.
@ -166,7 +167,7 @@ func (c *Client) NewRequest(method, urlStr string, body interface{}) (*http.Requ
// NewUploadRequest creates an upload request. A relative URL can be provided in // 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. // urlStr, in which case it is resolved relative to the UploadURL of the Client.
// Relative URLs should always be specified without a preceding slash. // Relative URLs should always be specified without a preceding slash.
func (c *Client) NewUploadRequest(urlStr string, reader io.Reader, contentType string) (*http.Request, error) {
func (c *Client) NewUploadRequest(urlStr string, reader io.Reader, size int64, mediaType string) (*http.Request, error) {
rel, err := url.Parse(urlStr) rel, err := url.Parse(urlStr)
if err != nil { if err != nil {
return nil, err return nil, err
@ -177,9 +178,13 @@ func (c *Client) NewUploadRequest(urlStr string, reader io.Reader, contentType s
if err != nil { if err != nil {
return nil, err return nil, err
} }
req.ContentLength = size
if len(mediaType) == 0 {
mediaType = defaultMediaType
}
req.Header.Add("Content-Type", mediaType)
req.Header.Add("Accept", mediaTypeV3) req.Header.Add("Accept", mediaTypeV3)
req.Header.Add("Content-Type", contentType)
req.Header.Add("User-Agent", c.UserAgent) req.Header.Add("User-Agent", c.UserAgent)
return req, nil return req, nil
} }


+ 12
- 0
github/github_test.go View File

@ -85,6 +85,18 @@ func testURLParseError(t *testing.T, err error) {
} }
} }
func testBody(t *testing.T, r *http.Request, want string){
b, err := ioutil.ReadAll(r.Body)
if err != nil {
t.Errorf("Unable to read body")
}
str := string(b)
if want != str {
t.Errorf("Body = %s, want: %s", str, want)
}
}
// Helper function to test that a value is marshalled to JSON as expected. // Helper function to test that a value is marshalled to JSON as expected.
func testJSONMarshal(t *testing.T, v interface{}, want string) { func testJSONMarshal(t *testing.T, v interface{}, want string) {
j, err := json.Marshal(v) j, err := json.Marshal(v)


+ 16
- 3
github/repos_releases.go View File

@ -6,8 +6,11 @@
package github package github
import ( import (
"errors"
"fmt" "fmt"
"io"
"mime"
"os"
"path/filepath"
) )
// RepositoryRelease represents a GitHub release in a repository. // RepositoryRelease represents a GitHub release in a repository.
@ -209,16 +212,26 @@ func (s *RepositoriesService) DeleteReleaseAsset(owner, repo string, id int) (*R
} }
// UploadReleaseAsset creates an asset by uploading a file into a release repository. // UploadReleaseAsset creates an asset by uploading a file into a release repository.
// To upload assets that cannot be represented by an os.File, call NewUploadRequest directly.
// //
// GitHub API docs : http://developer.github.com/v3/repos/releases/#upload-a-release-asset // 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) {
func (s *RepositoriesService) UploadReleaseAsset(owner, repo string, id int, opt *UploadOptions, file *os.File) (*ReleaseAsset, *Response, error) {
u := fmt.Sprintf("repos/%s/%s/releases/%d/assets", owner, repo, id) u := fmt.Sprintf("repos/%s/%s/releases/%d/assets", owner, repo, id)
u, err := addOptions(u, opt) u, err := addOptions(u, opt)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
req, err := s.client.NewUploadRequest(u, reader, contentType)
stat, err := file.Stat()
if err != nil {
return nil, nil, err
}
if stat.IsDir() {
return nil, nil, errors.New("The asset to upload can't be a directory")
}
mediaType := mime.TypeByExtension(filepath.Ext(file.Name()))
req, err := s.client.NewUploadRequest(u, file, stat.Size(), mediaType)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }


+ 9
- 4
github/repos_releases_test.go View File

@ -9,8 +9,8 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"net/http" "net/http"
"os"
"reflect" "reflect"
"strings"
"testing" "testing"
) )
@ -207,15 +207,20 @@ func TestRepositoriesService_UploadReleaseAsset(t *testing.T) {
mux.HandleFunc("/repos/o/r/releases/1/assets", func(w http.ResponseWriter, r *http.Request) { mux.HandleFunc("/repos/o/r/releases/1/assets", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "POST") testMethod(t, r, "POST")
testHeader(t, r, "Content-Type", "application/zip")
testHeader(t, r, "Content-Type", "text/plain; charset=utf-8")
testHeader(t, r, "Content-Length", "12")
testFormValues(t, r, values{"name": "n"}) testFormValues(t, r, values{"name": "n"})
testBody(t, r, "Upload me !\n")
fmt.Fprintf(w, `{"id":1}`) fmt.Fprintf(w, `{"id":1}`)
}) })
opt := &UploadOptions{Name: "n"} opt := &UploadOptions{Name: "n"}
data := strings.NewReader("data")
asset, _, err := client.Repositories.UploadReleaseAsset("o", "r", 1, opt, data, "application/zip")
file, err := os.Open("testdata/upload.txt")
if err != nil {
t.Errorf("Unable to open file testdata/upload.txt")
}
asset, _, err := client.Repositories.UploadReleaseAsset("o", "r", 1, opt, file)
if err != nil { if err != nil {
t.Errorf("Repositories.UploadReleaseAssert returned error: %v", err) t.Errorf("Repositories.UploadReleaseAssert returned error: %v", err)
} }


+ 1
- 0
github/testdata/upload.txt View File

@ -0,0 +1 @@
Upload me !

Loading…
Cancel
Save