Implement verify failure handling
This commit is contained in:
parent
a4ab24dca6
commit
e1b4563815
79
lfsapi/errors.go
Normal file
79
lfsapi/errors.go
Normal file
@ -0,0 +1,79 @@
|
||||
package lfsapi
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/git-lfs/git-lfs/errors"
|
||||
)
|
||||
|
||||
type ClientError struct {
|
||||
Message string `json:"message"`
|
||||
DocumentationUrl string `json:"documentation_url,omitempty"`
|
||||
RequestId string `json:"request_id,omitempty"`
|
||||
}
|
||||
|
||||
func (e *ClientError) Error() string {
|
||||
msg := e.Message
|
||||
if len(e.DocumentationUrl) > 0 {
|
||||
msg += "\nDocs: " + e.DocumentationUrl
|
||||
}
|
||||
if len(e.RequestId) > 0 {
|
||||
msg += "\nRequest ID: " + e.RequestId
|
||||
}
|
||||
return msg
|
||||
}
|
||||
|
||||
func (c *Client) handleResponse(res *http.Response) error {
|
||||
if res.StatusCode < 400 {
|
||||
return nil
|
||||
}
|
||||
|
||||
cliErr := &ClientError{}
|
||||
err := decodeResponse(res, cliErr)
|
||||
if err == nil {
|
||||
if len(cliErr.Message) == 0 {
|
||||
err = defaultError(res)
|
||||
} else {
|
||||
err = errors.Wrap(cliErr, "http")
|
||||
}
|
||||
}
|
||||
|
||||
if res.StatusCode == 401 {
|
||||
return errors.NewAuthError(err)
|
||||
}
|
||||
|
||||
if res.StatusCode > 499 && res.StatusCode != 501 && res.StatusCode != 507 && res.StatusCode != 509 {
|
||||
return errors.NewFatalError(err)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
var (
|
||||
defaultErrors = map[int]string{
|
||||
400: "Client error: %s",
|
||||
401: "Authorization error: %s\nCheck that you have proper access to the repository",
|
||||
403: "Authorization error: %s\nCheck that you have proper access to the repository",
|
||||
404: "Repository or object not found: %s\nCheck that it exists and that you have proper access to it",
|
||||
429: "Rate limit exceeded: %s",
|
||||
500: "Server error: %s",
|
||||
501: "Not Implemented: %s",
|
||||
507: "Insufficient server storage: %s",
|
||||
509: "Bandwidth limit exceeded: %s",
|
||||
}
|
||||
)
|
||||
|
||||
func defaultError(res *http.Response) error {
|
||||
var msgFmt string
|
||||
|
||||
if f, ok := defaultErrors[res.StatusCode]; ok {
|
||||
msgFmt = f
|
||||
} else if res.StatusCode < 500 {
|
||||
msgFmt = defaultErrors[400] + fmt.Sprintf(" from HTTP %d", res.StatusCode)
|
||||
} else {
|
||||
msgFmt = defaultErrors[500] + fmt.Sprintf(" from HTTP %d", res.StatusCode)
|
||||
}
|
||||
|
||||
return errors.Errorf(msgFmt, res.Request.URL)
|
||||
}
|
@ -1,10 +1,42 @@
|
||||
package lfsapi
|
||||
|
||||
import "net/http"
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"regexp"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var (
|
||||
lfsMediaTypeRE = regexp.MustCompile(`\Aapplication/vnd\.git\-lfs\+json(;|\z)`)
|
||||
jsonMediaTypeRE = regexp.MustCompile(`\Aapplication/json(;|\z)`)
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
}
|
||||
|
||||
func (c *Client) Do(req *http.Request) (*http.Response, error) {
|
||||
return http.DefaultClient.Do(req)
|
||||
res, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
|
||||
return res, c.handleResponse(res)
|
||||
}
|
||||
|
||||
func decodeResponse(res *http.Response, obj interface{}) error {
|
||||
ctype := res.Header.Get("Content-Type")
|
||||
if !(lfsMediaTypeRE.MatchString(ctype) || jsonMediaTypeRE.MatchString(ctype)) {
|
||||
return nil
|
||||
}
|
||||
|
||||
err := json.NewDecoder(res.Body).Decode(obj)
|
||||
res.Body.Close()
|
||||
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "Unable to parse HTTP response for %s %s", res.Request.Method, res.Request.URL)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -4,9 +4,11 @@ import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
|
||||
"github.com/git-lfs/git-lfs/errors"
|
||||
"github.com/git-lfs/git-lfs/lfsapi"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
@ -60,3 +62,213 @@ func TestVerifySuccess(t *testing.T) {
|
||||
assert.Nil(t, verifyUpload(c, tr))
|
||||
assert.EqualValues(t, 1, called)
|
||||
}
|
||||
|
||||
func TestVerifyAuthErrWithBody(t *testing.T) {
|
||||
var called uint32
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.String() != "/verify" {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
atomic.AddUint32(&called, 1)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(401)
|
||||
w.Write([]byte(`{"message":"custom auth error"}`))
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
c := &lfsapi.Client{}
|
||||
tr := &Transfer{
|
||||
Oid: "abc",
|
||||
Size: 123,
|
||||
Actions: map[string]*Action{
|
||||
"verify": &Action{
|
||||
Href: srv.URL + "/verify",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
err := verifyUpload(c, tr)
|
||||
assert.NotNil(t, err)
|
||||
assert.True(t, errors.IsAuthError(err))
|
||||
assert.Equal(t, "Authentication required: http: custom auth error", err.Error())
|
||||
assert.EqualValues(t, 1, called)
|
||||
}
|
||||
|
||||
func TestVerifyFatalWithBody(t *testing.T) {
|
||||
var called uint32
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.String() != "/verify" {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
atomic.AddUint32(&called, 1)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(500)
|
||||
w.Write([]byte(`{"message":"custom fatal error"}`))
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
c := &lfsapi.Client{}
|
||||
tr := &Transfer{
|
||||
Oid: "abc",
|
||||
Size: 123,
|
||||
Actions: map[string]*Action{
|
||||
"verify": &Action{
|
||||
Href: srv.URL + "/verify",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
err := verifyUpload(c, tr)
|
||||
assert.NotNil(t, err)
|
||||
assert.True(t, errors.IsFatalError(err))
|
||||
assert.Equal(t, "Fatal error: http: custom fatal error", err.Error())
|
||||
assert.EqualValues(t, 1, called)
|
||||
}
|
||||
|
||||
func TestVerifyWithNonFatal500WithBody(t *testing.T) {
|
||||
c := &lfsapi.Client{}
|
||||
tr := &Transfer{
|
||||
Oid: "abc",
|
||||
Size: 123,
|
||||
Actions: map[string]*Action{
|
||||
"verify": &Action{},
|
||||
},
|
||||
}
|
||||
|
||||
var called uint32
|
||||
|
||||
nonFatalCodes := map[int]string{
|
||||
501: "custom 501 error",
|
||||
507: "custom 507 error",
|
||||
509: "custom 509 error",
|
||||
}
|
||||
|
||||
for nonFatalCode, expectedErr := range nonFatalCodes {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.String() != "/verify" {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
atomic.AddUint32(&called, 1)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(nonFatalCode)
|
||||
w.Write([]byte(`{"message":"` + expectedErr + `"}`))
|
||||
}))
|
||||
|
||||
tr.Actions["verify"].Href = srv.URL + "/verify"
|
||||
err := verifyUpload(c, tr)
|
||||
t.Logf("non fatal code %d", nonFatalCode)
|
||||
assert.NotNil(t, err)
|
||||
assert.Equal(t, "http: "+expectedErr, err.Error())
|
||||
srv.Close()
|
||||
}
|
||||
|
||||
assert.EqualValues(t, 3, called)
|
||||
}
|
||||
|
||||
func TestVerifyAuthErrWithoutBody(t *testing.T) {
|
||||
var called uint32
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.String() != "/verify" {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
atomic.AddUint32(&called, 1)
|
||||
w.WriteHeader(401)
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
c := &lfsapi.Client{}
|
||||
tr := &Transfer{
|
||||
Oid: "abc",
|
||||
Size: 123,
|
||||
Actions: map[string]*Action{
|
||||
"verify": &Action{
|
||||
Href: srv.URL + "/verify",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
err := verifyUpload(c, tr)
|
||||
assert.NotNil(t, err)
|
||||
assert.True(t, errors.IsAuthError(err))
|
||||
assert.True(t, strings.HasPrefix(err.Error(), "Authentication required: Authorization error:"), err.Error())
|
||||
assert.EqualValues(t, 1, called)
|
||||
}
|
||||
|
||||
func TestVerifyFatalWithoutBody(t *testing.T) {
|
||||
var called uint32
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.String() != "/verify" {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
atomic.AddUint32(&called, 1)
|
||||
w.WriteHeader(500)
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
c := &lfsapi.Client{}
|
||||
tr := &Transfer{
|
||||
Oid: "abc",
|
||||
Size: 123,
|
||||
Actions: map[string]*Action{
|
||||
"verify": &Action{
|
||||
Href: srv.URL + "/verify",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
err := verifyUpload(c, tr)
|
||||
assert.NotNil(t, err)
|
||||
assert.True(t, errors.IsFatalError(err))
|
||||
assert.True(t, strings.HasPrefix(err.Error(), "Fatal error: Server error:"), err.Error())
|
||||
assert.EqualValues(t, 1, called)
|
||||
}
|
||||
|
||||
func TestVerifyWithNonFatal500WithoutBody(t *testing.T) {
|
||||
c := &lfsapi.Client{}
|
||||
tr := &Transfer{
|
||||
Oid: "abc",
|
||||
Size: 123,
|
||||
Actions: map[string]*Action{
|
||||
"verify": &Action{},
|
||||
},
|
||||
}
|
||||
|
||||
var called uint32
|
||||
|
||||
nonFatalCodes := map[int]string{
|
||||
501: "Not Implemented:",
|
||||
507: "Insufficient server storage:",
|
||||
509: "Bandwidth limit exceeded:",
|
||||
}
|
||||
|
||||
for nonFatalCode, errPrefix := range nonFatalCodes {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.String() != "/verify" {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
atomic.AddUint32(&called, 1)
|
||||
w.WriteHeader(nonFatalCode)
|
||||
}))
|
||||
|
||||
tr.Actions["verify"].Href = srv.URL + "/verify"
|
||||
err := verifyUpload(c, tr)
|
||||
t.Logf("non fatal code %d", nonFatalCode)
|
||||
assert.NotNil(t, err)
|
||||
assert.True(t, strings.HasPrefix(err.Error(), errPrefix))
|
||||
srv.Close()
|
||||
}
|
||||
|
||||
assert.EqualValues(t, 3, called)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user