Skip to content

Commit

Permalink
Another possible solution...
Browse files Browse the repository at this point in the history
  • Loading branch information
svanharmelen committed Dec 29, 2021
1 parent 979f1c9 commit 4a6c4b7
Show file tree
Hide file tree
Showing 5 changed files with 60 additions and 112 deletions.
81 changes: 1 addition & 80 deletions gitlab.go
Expand Up @@ -18,14 +18,12 @@
package gitlab

import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"math/rand"
"mime/multipart"
"net/http"
"net/url"
"sort"
Expand Down Expand Up @@ -570,96 +568,19 @@ func (c *Client) NewRequest(method, path string, opt interface{}, options []Requ
return nil, err
}

for _, fn := range options {
if fn == nil {
continue
}
if err := fn(req); err != nil {
return nil, err
}
}

// Set the request specific headers.
for k, v := range reqHeaders {
req.Header[k] = v
}

return req, nil
}

// UploadRequest creates an API request for uploading a file. The method
// expects a relative URL path that will be resolved relative to the base
// URL of the Client. Relative URL paths should always be specified without
// a preceding slash. If specified, the value pointed to by body is JSON
// encoded and included as the request body.
func (c *Client) UploadRequest(method, path string, content io.Reader, filename string, uploadType UploadType, opt interface{}, options []RequestOptionFunc) (*retryablehttp.Request, error) {
u := *c.baseURL
unescaped, err := url.PathUnescape(path)
if err != nil {
return nil, err
}

// Set the encoded path data
u.RawPath = c.baseURL.Path + path
u.Path = c.baseURL.Path + unescaped

// Create a request specific headers map.
reqHeaders := make(http.Header)
reqHeaders.Set("Accept", "application/json")

if c.UserAgent != "" {
reqHeaders.Set("User-Agent", c.UserAgent)
}

b := new(bytes.Buffer)
w := multipart.NewWriter(b)

fw, err := w.CreateFormFile(string(uploadType), filename)
if err != nil {
return nil, err
}

if _, err := io.Copy(fw, content); err != nil {
return nil, err
}

if opt != nil {
fields, err := query.Values(opt)
if err != nil {
return nil, err
}
for name := range fields {
if err = w.WriteField(name, fmt.Sprintf("%v", fields.Get(name))); err != nil {
return nil, err
}
}
}

if err = w.Close(); err != nil {
return nil, err
}

reqHeaders.Set("Content-Type", w.FormDataContentType())

req, err := retryablehttp.NewRequest(method, u.String(), b)
if err != nil {
return nil, err
}

for _, fn := range options {
if fn == nil {
continue
}
if err := fn(req); err != nil {
if err := fn(req, opt); err != nil {
return nil, err
}
}

// Set the request specific headers.
for k, v := range reqHeaders {
req.Header[k] = v
}

return req, nil
}

Expand Down
2 changes: 1 addition & 1 deletion gitlab_test.go
Expand Up @@ -101,7 +101,7 @@ func mustWriteHTTPResponse(t *testing.T, w io.Writer, fixturePath string) {
}
}

func errorOption(*retryablehttp.Request) error {
func errorOption(req *retryablehttp.Request, opt interface{}) error {
return errors.New("RequestOptionFunc returns an error")
}

Expand Down
11 changes: 2 additions & 9 deletions project_import_export.go
Expand Up @@ -174,15 +174,8 @@ type ImportFileOptions struct {
// GitLab API docs:
// https://docs.gitlab.com/ce/api/project_import_export.html#import-a-file
func (s *ProjectImportExportService) ImportFromFile(archive io.Reader, opt *ImportFileOptions, options ...RequestOptionFunc) (*ImportStatus, *Response, error) {
req, err := s.client.UploadRequest(
http.MethodPost,
"projects/import",
archive,
"archive.tar.gz",
UploadFile,
opt,
options,
)
options = append(options, WithFile(archive, "archive.tar.gz", UploadFile))
req, err := s.client.NewRequest(http.MethodPost, "projects/import", opt, options)
if err != nil {
return nil, nil, err
}
Expand Down
22 changes: 4 additions & 18 deletions projects.go
Expand Up @@ -1293,15 +1293,8 @@ func (s *ProjectsService) UploadFile(pid interface{}, content io.Reader, filenam
}
u := fmt.Sprintf("projects/%s/uploads", PathEscape(project))

req, err := s.client.UploadRequest(
http.MethodPost,
u,
content,
filename,
UploadFile,
nil,
options,
)
options = append(options, WithFile(content, filename, UploadFile))
req, err := s.client.NewRequest(http.MethodPost, u, nil, options)
if err != nil {
return nil, nil, err
}
Expand All @@ -1326,15 +1319,8 @@ func (s *ProjectsService) UploadAvatar(pid interface{}, avatar io.Reader, option
}
u := fmt.Sprintf("projects/%s", PathEscape(project))

req, err := s.client.UploadRequest(
http.MethodPut,
u,
avatar,
"avatar.png",
UploadAvatar,
nil,
options,
)
options = append(options, WithFile(avatar, "avatar.png", UploadAvatar))
req, err := s.client.NewRequest(http.MethodPut, u, nil, options)
if err != nil {
return nil, nil, err
}
Expand Down
56 changes: 52 additions & 4 deletions request_options.go
Expand Up @@ -17,25 +17,73 @@
package gitlab

import (
"bytes"
"context"
"fmt"
"io"
"mime/multipart"

"github.com/google/go-querystring/query"
retryablehttp "github.com/hashicorp/go-retryablehttp"
)

// RequestOptionFunc can be passed to all API requests to customize the API request.
type RequestOptionFunc func(*retryablehttp.Request) error
type RequestOptionFunc func(req *retryablehttp.Request, opt interface{}) error

// WithContext runs the request with the provided context
func WithContext(ctx context.Context) RequestOptionFunc {
return func(req *retryablehttp.Request) error {
return func(req *retryablehttp.Request, opt interface{}) error {
*req = *req.WithContext(ctx)
return nil
}
}

// WithFile transforms the request to a multipart request with a file.
func WithFile(content io.Reader, filename string, uploadType UploadType) RequestOptionFunc {
return func(req *retryablehttp.Request, opt interface{}) error {
b := new(bytes.Buffer)
w := multipart.NewWriter(b)

fw, err := w.CreateFormFile(string(uploadType), filename)
if err != nil {
return err
}

if _, err := io.Copy(fw, content); err != nil {
return err
}

if opt != nil {
fields, err := query.Values(opt)
if err != nil {
return err
}
for name := range fields {
if err = w.WriteField(name, fmt.Sprintf("%v", fields.Get(name))); err != nil {
return err
}
}
}

if err = w.Close(); err != nil {
return err
}

// Set the buffer as the request body.
if err = req.SetBody(b); err != nil {
return err
}

// Overwrite the default content type.
req.Header.Set("Content-Type", w.FormDataContentType())

return nil
}
}

// WithSudo takes either a username or user ID and sets the SUDO request header.
func WithSudo(uid interface{}) RequestOptionFunc {
return func(req *retryablehttp.Request) error {
return func(req *retryablehttp.Request, opt interface{}) error {
user, err := parseID(uid)
if err != nil {
return err
Expand All @@ -47,7 +95,7 @@ func WithSudo(uid interface{}) RequestOptionFunc {

// WithToken takes a token which is then used when making this one request.
func WithToken(authType AuthType, token string) RequestOptionFunc {
return func(req *retryablehttp.Request) error {
return func(req *retryablehttp.Request, opt interface{}) error {
switch authType {
case JobToken:
req.Header.Set("JOB-TOKEN", token)
Expand Down

0 comments on commit 4a6c4b7

Please sign in to comment.