You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

316 lines
10 KiB

//
// DISCLAIMER
//
// Copyright 2017-2021 ArangoDB GmbH, Cologne, Germany
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Copyright holder is ArangoDB GmbH, Cologne, Germany
//
// Author Ewout Prangsma
//
package driver
import (
"context"
"fmt"
"net"
"net/http"
"net/url"
"os"
)
const (
// general errors
ErrNotImplemented = 9
ErrForbidden = 11
ErrDisabled = 36
// HTTP error status codes
ErrHttpForbidden = 403
ErrHttpInternal = 501
// Internal ArangoDB storage errors
ErrArangoReadOnly = 1004
// External ArangoDB storage errors
ErrArangoCorruptedDatafile = 1100
ErrArangoIllegalParameterFile = 1101
ErrArangoCorruptedCollection = 1102
ErrArangoFileSystemFull = 1104
ErrArangoDataDirLocked = 1107
// General ArangoDB storage errors
ErrArangoConflict = 1200
ErrArangoDocumentNotFound = 1202
ErrArangoDataSourceNotFound = 1203
ErrArangoUniqueConstraintViolated = 1210
ErrArangoDatabaseNameInvalid = 1229
// ArangoDB cluster errors
ErrClusterLeadershipChallengeOngoing = 1495
ErrClusterNotLeader = 1496
// User management errors
ErrUserDuplicate = 1702
)
// ArangoError is a Go error with arangodb specific error information.
type ArangoError struct {
HasError bool `json:"error"`
Code int `json:"code"`
ErrorNum int `json:"errorNum"`
ErrorMessage string `json:"errorMessage"`
}
// Error returns the error message of an ArangoError.
func (ae ArangoError) Error() string {
if ae.ErrorMessage != "" {
return ae.ErrorMessage
}
return fmt.Sprintf("ArangoError: Code %d, ErrorNum %d", ae.Code, ae.ErrorNum)
}
// Timeout returns true when the given error is a timeout error.
func (ae ArangoError) Timeout() bool {
return ae.HasError && (ae.Code == http.StatusRequestTimeout || ae.Code == http.StatusGatewayTimeout)
}
// Temporary returns true when the given error is a temporary error.
func (ae ArangoError) Temporary() bool {
return ae.HasError && ae.Code == http.StatusServiceUnavailable
}
// newArangoError creates a new ArangoError with given values.
func newArangoError(code, errorNum int, errorMessage string) error {
return ArangoError{
HasError: true,
Code: code,
ErrorNum: errorNum,
ErrorMessage: errorMessage,
}
}
// IsArangoError returns true when the given error is an ArangoError.
func IsArangoError(err error) bool {
ae, ok := Cause(err).(ArangoError)
return ok && ae.HasError
}
// AsArangoError returns true when the given error is an ArangoError together with an object.
func AsArangoError(err error) (ArangoError, bool) {
ae, ok := Cause(err).(ArangoError)
if ok {
return ae, true
} else {
return ArangoError{}, false
}
}
// IsArangoErrorWithCode returns true when the given error is an ArangoError and its Code field is equal to the given code.
func IsArangoErrorWithCode(err error, code int) bool {
ae, ok := Cause(err).(ArangoError)
return ok && ae.Code == code
}
// IsArangoErrorWithErrorNum returns true when the given error is an ArangoError and its ErrorNum field is equal to one of the given numbers.
func IsArangoErrorWithErrorNum(err error, errorNum ...int) bool {
ae, ok := Cause(err).(ArangoError)
if !ok {
return false
}
for _, x := range errorNum {
if ae.ErrorNum == x {
return true
}
}
return false
}
// IsInvalidRequest returns true if the given error is an ArangoError with code 400, indicating an invalid request.
func IsInvalidRequest(err error) bool {
return IsArangoErrorWithCode(err, http.StatusBadRequest)
}
// IsUnauthorized returns true if the given error is an ArangoError with code 401, indicating an unauthorized request.
func IsUnauthorized(err error) bool {
return IsArangoErrorWithCode(err, http.StatusUnauthorized)
}
// IsForbidden returns true if the given error is an ArangoError with code 403, indicating a forbidden request.
func IsForbidden(err error) bool {
return IsArangoErrorWithCode(err, http.StatusForbidden)
}
// Deprecated: Use IsNotFoundGeneral instead.
// For ErrArangoDocumentNotFound error there is a chance that we get a different HTTP code if the API requires an existing document as input, which is not found.
//
// IsNotFound returns true if the given error is an ArangoError with code 404, indicating a object not found.
func IsNotFound(err error) bool {
return IsArangoErrorWithCode(err, http.StatusNotFound) ||
IsArangoErrorWithErrorNum(err, ErrArangoDocumentNotFound, ErrArangoDataSourceNotFound)
}
// IsNotFoundGeneral returns true if the given error is an ArangoError with code 404, indicating an object is not found.
func IsNotFoundGeneral(err error) bool {
return IsArangoErrorWithCode(err, http.StatusNotFound)
}
// IsDataSourceOrDocumentNotFound returns true if the given error is an Arango storage error, indicating an object is not found.
func IsDataSourceOrDocumentNotFound(err error) bool {
return IsArangoErrorWithCode(err, http.StatusNotFound) &&
IsArangoErrorWithErrorNum(err, ErrArangoDocumentNotFound, ErrArangoDataSourceNotFound)
}
// IsExternalStorageError returns true if ArangoDB is having an error with accessing or writing to storage.
func IsExternalStorageError(err error) bool {
return IsArangoErrorWithErrorNum(
err,
ErrArangoCorruptedDatafile,
ErrArangoIllegalParameterFile,
ErrArangoCorruptedCollection,
ErrArangoFileSystemFull,
ErrArangoDataDirLocked,
)
}
// IsConflict returns true if the given error is an ArangoError with code 409, indicating a conflict.
func IsConflict(err error) bool {
return IsArangoErrorWithCode(err, http.StatusConflict) || IsArangoErrorWithErrorNum(err, ErrUserDuplicate)
}
// IsPreconditionFailed returns true if the given error is an ArangoError with code 412, indicating a failed precondition.
func IsPreconditionFailed(err error) bool {
return IsArangoErrorWithCode(err, http.StatusPreconditionFailed) ||
IsArangoErrorWithErrorNum(err, ErrArangoConflict, ErrArangoUniqueConstraintViolated)
}
// IsNoLeader returns true if the given error is an ArangoError with code 503 error number 1496.
func IsNoLeader(err error) bool {
return IsArangoErrorWithCode(err, http.StatusServiceUnavailable) && IsArangoErrorWithErrorNum(err, ErrClusterNotLeader)
}
// IsNoLeaderOrOngoing return true if the given error is an ArangoError with code 503 and error number 1496 or 1495
func IsNoLeaderOrOngoing(err error) bool {
return IsArangoErrorWithCode(err, http.StatusServiceUnavailable) &&
IsArangoErrorWithErrorNum(err, ErrClusterLeadershipChallengeOngoing, ErrClusterNotLeader)
}
// InvalidArgumentError is returned when a go function argument is invalid.
type InvalidArgumentError struct {
Message string
}
// Error implements the error interface for InvalidArgumentError.
func (e InvalidArgumentError) Error() string {
return e.Message
}
// IsInvalidArgument returns true if the given error is an InvalidArgumentError.
func IsInvalidArgument(err error) bool {
_, ok := Cause(err).(InvalidArgumentError)
return ok
}
// NoMoreDocumentsError is returned by Cursor's, when an attempt is made to read documents when there are no more.
type NoMoreDocumentsError struct{}
// Error implements the error interface for NoMoreDocumentsError.
func (e NoMoreDocumentsError) Error() string {
return "no more documents"
}
// IsNoMoreDocuments returns true if the given error is an NoMoreDocumentsError.
func IsNoMoreDocuments(err error) bool {
_, ok := Cause(err).(NoMoreDocumentsError)
return ok
}
// A ResponseError is returned when a request was completely written to a server, but
// the server did not respond, or some kind of network error occurred during the response.
type ResponseError struct {
Err error
}
// Error returns the Error() result of the underlying error.
func (e *ResponseError) Error() string {
return e.Err.Error()
}
// IsResponse returns true if the given error is (or is caused by) a ResponseError.
func IsResponse(err error) bool {
return isCausedBy(err, func(e error) bool { _, ok := e.(*ResponseError); return ok })
}
// IsCanceled returns true if the given error is the result on a cancelled context.
func IsCanceled(err error) bool {
return isCausedBy(err, func(e error) bool { return e == context.Canceled })
}
// IsTimeout returns true if the given error is the result on a deadline that has been exceeded.
func IsTimeout(err error) bool {
return isCausedBy(err, func(e error) bool { return e == context.DeadlineExceeded })
}
// isCausedBy returns true if the given error returns true on the given predicate,
// unwrapping various standard library error wrappers.
func isCausedBy(err error, p func(error) bool) bool {
if p(err) {
return true
}
err = Cause(err)
for {
if p(err) {
return true
} else if err == nil {
return false
}
if xerr, ok := err.(*ResponseError); ok {
err = xerr.Err
} else if xerr, ok := err.(*url.Error); ok {
err = xerr.Err
} else if xerr, ok := err.(*net.OpError); ok {
err = xerr.Err
} else if xerr, ok := err.(*os.SyscallError); ok {
err = xerr.Err
} else {
return false
}
}
}
var (
// WithStack is called on every return of an error to add stacktrace information to the error.
// When setting this function, also set the Cause function.
// The interface of this function is compatible with functions in github.com/pkg/errors.
WithStack = func(err error) error { return err }
// Cause is used to get the root cause of the given error.
// The interface of this function is compatible with functions in github.com/pkg/errors.
Cause = func(err error) error { return err }
)
// ErrorSlice is a slice of errors
type ErrorSlice []error
// FirstNonNil returns the first error in the slice that is not nil.
// If all errors in the slice are nil, nil is returned.
func (l ErrorSlice) FirstNonNil() error {
for _, e := range l {
if e != nil {
return e
}
}
return nil
}