013c02d010
To indicate that we have encountered an HTTP 422, let's introduce a new sentinel type to do just that. It will be instantiated and returned in a subsequent commit from tq/basic_upload.go, and then caught down in tq/transfer_queue.go.
379 lines
8.2 KiB
Go
379 lines
8.2 KiB
Go
package errors
|
|
|
|
import (
|
|
"fmt"
|
|
"net/url"
|
|
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
// IsFatalError indicates that the error is fatal and the process should exit
|
|
// immediately after handling the error.
|
|
func IsFatalError(err error) bool {
|
|
if e, ok := err.(interface {
|
|
Fatal() bool
|
|
}); ok {
|
|
return e.Fatal()
|
|
}
|
|
if parent := parentOf(err); parent != nil {
|
|
return IsFatalError(parent)
|
|
}
|
|
return false
|
|
}
|
|
|
|
// IsNotImplementedError indicates the client attempted to use a feature the
|
|
// server has not implemented (e.g. the batch endpoint).
|
|
func IsNotImplementedError(err error) bool {
|
|
if e, ok := err.(interface {
|
|
NotImplemented() bool
|
|
}); ok {
|
|
return e.NotImplemented()
|
|
}
|
|
if parent := parentOf(err); parent != nil {
|
|
return IsNotImplementedError(parent)
|
|
}
|
|
return false
|
|
}
|
|
|
|
// IsAuthError indicates the client provided a request with invalid or no
|
|
// authentication credentials when credentials are required (e.g. HTTP 401).
|
|
func IsAuthError(err error) bool {
|
|
if e, ok := err.(interface {
|
|
AuthError() bool
|
|
}); ok {
|
|
return e.AuthError()
|
|
}
|
|
if parent := parentOf(err); parent != nil {
|
|
return IsAuthError(parent)
|
|
}
|
|
return false
|
|
}
|
|
|
|
// IsSmudgeError indicates an error while smudging a files.
|
|
func IsSmudgeError(err error) bool {
|
|
if e, ok := err.(interface {
|
|
SmudgeError() bool
|
|
}); ok {
|
|
return e.SmudgeError()
|
|
}
|
|
if parent := parentOf(err); parent != nil {
|
|
return IsSmudgeError(parent)
|
|
}
|
|
return false
|
|
}
|
|
|
|
// IsCleanPointerError indicates an error while cleaning a file.
|
|
func IsCleanPointerError(err error) bool {
|
|
if e, ok := err.(interface {
|
|
CleanPointerError() bool
|
|
}); ok {
|
|
return e.CleanPointerError()
|
|
}
|
|
if parent := parentOf(err); parent != nil {
|
|
return IsCleanPointerError(parent)
|
|
}
|
|
return false
|
|
}
|
|
|
|
// IsNotAPointerError indicates the parsed data is not an LFS pointer.
|
|
func IsNotAPointerError(err error) bool {
|
|
if e, ok := err.(interface {
|
|
NotAPointerError() bool
|
|
}); ok {
|
|
return e.NotAPointerError()
|
|
}
|
|
if parent := parentOf(err); parent != nil {
|
|
return IsNotAPointerError(parent)
|
|
}
|
|
return false
|
|
}
|
|
|
|
// IsBadPointerKeyError indicates that the parsed data has an invalid key.
|
|
func IsBadPointerKeyError(err error) bool {
|
|
if e, ok := err.(interface {
|
|
BadPointerKeyError() bool
|
|
}); ok {
|
|
return e.BadPointerKeyError()
|
|
}
|
|
|
|
if parent := parentOf(err); parent != nil {
|
|
return IsBadPointerKeyError(parent)
|
|
}
|
|
return false
|
|
}
|
|
|
|
// If an error is abad pointer error of any type, returns NotAPointerError
|
|
func StandardizeBadPointerError(err error) error {
|
|
if IsBadPointerKeyError(err) {
|
|
badErr := err.(badPointerKeyError)
|
|
if badErr.Expected == "version" {
|
|
return NewNotAPointerError(err)
|
|
}
|
|
}
|
|
return err
|
|
}
|
|
|
|
// IsDownloadDeclinedError indicates that the smudge operation should not download.
|
|
// TODO: I don't really like using errors to control that flow, it should be refactored.
|
|
func IsDownloadDeclinedError(err error) bool {
|
|
if e, ok := err.(interface {
|
|
DownloadDeclinedError() bool
|
|
}); ok {
|
|
return e.DownloadDeclinedError()
|
|
}
|
|
if parent := parentOf(err); parent != nil {
|
|
return IsDownloadDeclinedError(parent)
|
|
}
|
|
return false
|
|
}
|
|
|
|
// IsDownloadDeclinedError indicates that the upload operation failed because of
|
|
// an HTTP 422 response code.
|
|
func IsUnprocessableEntityError(err error) bool {
|
|
if e, ok := err.(interface {
|
|
UnprocessableEntityError() bool
|
|
}); ok {
|
|
return e.UnprocessableEntityError()
|
|
}
|
|
if parent := parentOf(err); parent != nil {
|
|
return IsUnprocessableEntityError(parent)
|
|
}
|
|
return false
|
|
}
|
|
|
|
// IsRetriableError indicates the low level transfer had an error but the
|
|
// caller may retry the operation.
|
|
func IsRetriableError(err error) bool {
|
|
if e, ok := err.(interface {
|
|
RetriableError() bool
|
|
}); ok {
|
|
return e.RetriableError()
|
|
}
|
|
if cause, ok := Cause(err).(*url.Error); ok {
|
|
return cause.Temporary() || cause.Timeout()
|
|
}
|
|
if parent := parentOf(err); parent != nil {
|
|
return IsRetriableError(parent)
|
|
}
|
|
return false
|
|
}
|
|
|
|
type errorWithCause interface {
|
|
Cause() error
|
|
StackTrace() errors.StackTrace
|
|
error
|
|
fmt.Formatter
|
|
}
|
|
|
|
// wrappedError is the base error wrapper. It provides a Message string, a
|
|
// stack, and a context map around a regular Go error.
|
|
type wrappedError struct {
|
|
errorWithCause
|
|
context map[string]interface{}
|
|
}
|
|
|
|
// newWrappedError creates a wrappedError.
|
|
func newWrappedError(err error, message string) *wrappedError {
|
|
if err == nil {
|
|
err = errors.New("Error")
|
|
}
|
|
|
|
var errWithCause errorWithCause
|
|
|
|
if len(message) > 0 {
|
|
errWithCause = errors.Wrap(err, message).(errorWithCause)
|
|
} else if ewc, ok := err.(errorWithCause); ok {
|
|
errWithCause = ewc
|
|
} else {
|
|
errWithCause = errors.Wrap(err, "LFS").(errorWithCause)
|
|
}
|
|
|
|
return &wrappedError{
|
|
context: make(map[string]interface{}),
|
|
errorWithCause: errWithCause,
|
|
}
|
|
}
|
|
|
|
// Set sets the value for the key in the context.
|
|
func (e wrappedError) Set(key string, val interface{}) {
|
|
e.context[key] = val
|
|
}
|
|
|
|
// Get gets the value for a key in the context.
|
|
func (e wrappedError) Get(key string) interface{} {
|
|
return e.context[key]
|
|
}
|
|
|
|
// Del removes a key from the context.
|
|
func (e wrappedError) Del(key string) {
|
|
delete(e.context, key)
|
|
}
|
|
|
|
// Context returns the underlying context.
|
|
func (e wrappedError) Context() map[string]interface{} {
|
|
return e.context
|
|
}
|
|
|
|
// Definitions for IsFatalError()
|
|
|
|
type fatalError struct {
|
|
*wrappedError
|
|
}
|
|
|
|
func (e fatalError) Fatal() bool {
|
|
return true
|
|
}
|
|
|
|
func NewFatalError(err error) error {
|
|
return fatalError{newWrappedError(err, "Fatal error")}
|
|
}
|
|
|
|
// Definitions for IsNotImplementedError()
|
|
|
|
type notImplementedError struct {
|
|
*wrappedError
|
|
}
|
|
|
|
func (e notImplementedError) NotImplemented() bool {
|
|
return true
|
|
}
|
|
|
|
func NewNotImplementedError(err error) error {
|
|
return notImplementedError{newWrappedError(err, "Not implemented")}
|
|
}
|
|
|
|
// Definitions for IsAuthError()
|
|
|
|
type authError struct {
|
|
*wrappedError
|
|
}
|
|
|
|
func (e authError) AuthError() bool {
|
|
return true
|
|
}
|
|
|
|
func NewAuthError(err error) error {
|
|
return authError{newWrappedError(err, "Authentication required")}
|
|
}
|
|
|
|
// Definitions for IsSmudgeError()
|
|
|
|
type smudgeError struct {
|
|
*wrappedError
|
|
}
|
|
|
|
func (e smudgeError) SmudgeError() bool {
|
|
return true
|
|
}
|
|
|
|
func NewSmudgeError(err error, oid, filename string) error {
|
|
e := smudgeError{newWrappedError(err, "Smudge error")}
|
|
SetContext(e, "OID", oid)
|
|
SetContext(e, "FileName", filename)
|
|
return e
|
|
}
|
|
|
|
// Definitions for IsCleanPointerError()
|
|
|
|
type cleanPointerError struct {
|
|
*wrappedError
|
|
}
|
|
|
|
func (e cleanPointerError) CleanPointerError() bool {
|
|
return true
|
|
}
|
|
|
|
func NewCleanPointerError(pointer interface{}, bytes []byte) error {
|
|
err := New("pointer error")
|
|
e := cleanPointerError{newWrappedError(err, "clean")}
|
|
SetContext(e, "pointer", pointer)
|
|
SetContext(e, "bytes", bytes)
|
|
return e
|
|
}
|
|
|
|
// Definitions for IsNotAPointerError()
|
|
|
|
type notAPointerError struct {
|
|
*wrappedError
|
|
}
|
|
|
|
func (e notAPointerError) NotAPointerError() bool {
|
|
return true
|
|
}
|
|
|
|
func NewNotAPointerError(err error) error {
|
|
return notAPointerError{newWrappedError(err, "Pointer file error")}
|
|
}
|
|
|
|
type badPointerKeyError struct {
|
|
Expected string
|
|
Actual string
|
|
|
|
*wrappedError
|
|
}
|
|
|
|
func (e badPointerKeyError) BadPointerKeyError() bool {
|
|
return true
|
|
}
|
|
|
|
func NewBadPointerKeyError(expected, actual string) error {
|
|
err := Errorf("Expected key %s, got %s", expected, actual)
|
|
return badPointerKeyError{expected, actual, newWrappedError(err, "pointer parsing")}
|
|
}
|
|
|
|
// Definitions for IsDownloadDeclinedError()
|
|
|
|
type downloadDeclinedError struct {
|
|
*wrappedError
|
|
}
|
|
|
|
func (e downloadDeclinedError) DownloadDeclinedError() bool {
|
|
return true
|
|
}
|
|
|
|
func NewDownloadDeclinedError(err error, msg string) error {
|
|
return downloadDeclinedError{newWrappedError(err, msg)}
|
|
}
|
|
|
|
// Definitions for IsUnprocessableEntityError()
|
|
|
|
type unprocessableEntityError struct {
|
|
*wrappedError
|
|
}
|
|
|
|
func (e unprocessableEntityError) UnprocessableEntityError() bool {
|
|
return true
|
|
}
|
|
|
|
func NewUnprocessableEntityError(err error) error {
|
|
return unprocessableEntityError{newWrappedError(err, "")}
|
|
}
|
|
|
|
// Definitions for IsRetriableError()
|
|
|
|
type retriableError struct {
|
|
*wrappedError
|
|
}
|
|
|
|
func (e retriableError) RetriableError() bool {
|
|
return true
|
|
}
|
|
|
|
func NewRetriableError(err error) error {
|
|
return retriableError{newWrappedError(err, "")}
|
|
}
|
|
|
|
func parentOf(err error) error {
|
|
type causer interface {
|
|
Cause() error
|
|
}
|
|
|
|
if c, ok := err.(causer); ok {
|
|
if innerC, innerOk := c.Cause().(causer); innerOk {
|
|
return innerC.Cause()
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|