// Copyright 2020 Kentaro Hibino. All rights reserved.
// Use of this source code is governed by a MIT license
// that can be found in the LICENSE file.

// Package errors defines the error type and functions used by // asynq and its internal packages.
package errors // Note: This package is inspired by a blog post about error handling in project Upspin // https://commandcenter.blogspot.com/2017/12/error-handling-in-upspin.html. import ( ) // Error is the type that implements the error interface. // It contains a number of fields, each of different type. // An Error value may leave some values unset. type Error struct { Code Code Op Op Err error } func ( *Error) () string { var strings.Builder if .Op != "" { .WriteString(string(.Op)) } if .Code != Unspecified { if .Len() > 0 { .WriteString(": ") } .WriteString(.Code.String()) } if .Err != nil { if .Len() > 0 { .WriteString(": ") } .WriteString(.Err.Error()) } return .String() } func ( *Error) () string { var strings.Builder if .Code != Unspecified { .WriteString(.Code.String()) } if .Err != nil { if .Len() > 0 { .WriteString(": ") } .WriteString(.Err.Error()) } return .String() } func ( *Error) () error { return .Err } // Code defines the canonical error code. type Code uint8 // List of canonical error codes. const ( Unspecified Code = iota NotFound FailedPrecondition Internal AlreadyExists Unknown // Note: If you add a new value here, make sure to update String method. ) func ( Code) () string { switch { case Unspecified: return "ERROR_CODE_UNSPECIFIED" case NotFound: return "NOT_FOUND" case FailedPrecondition: return "FAILED_PRECONDITION" case Internal: return "INTERNAL_ERROR" case AlreadyExists: return "ALREADY_EXISTS" case Unknown: return "UNKNOWN" } panic(fmt.Sprintf("unknown error code %d", )) } // Op describes an operation, usually as the package and method, // such as "rdb.Enqueue". type Op string // E builds an error value from its arguments. // There must be at least one argument or E panics. // The type of each argument determines its meaning. // If more than one argument of a given type is presented, // only the last one is recorded. // // The types are: // errors.Op // The operation being performed, usually the method // being invoked (Get, Put, etc.). // errors.Code // The canonical error code, such as NOT_FOUND. // string // Treated as an error message and assigned to the // Err field after a call to errors.New. // error // The underlying error that triggered this one. // // If the error is printed, only those items that have been // set to non-zero values will appear in the result. func ( ...interface{}) error { if len() == 0 { panic("call to errors.E with no arguments") } := &Error{} for , := range { switch arg := .(type) { case Op: .Op = case Code: .Code = case error: .Err = case string: .Err = errors.New() default: , , , := runtime.Caller(1) log.Printf("errors.E: bad call from %s:%d: %v", , , ) return fmt.Errorf("unknown type %T, value %v in error call", , ) } } return } // CanonicalCode returns the canonical code of the given error if one is present. // Otherwise it returns Unspecified. func ( error) Code { if == nil { return Unspecified } , := .(*Error) if ! { return Unspecified } if .Code == Unspecified { return (.Err) } return .Code } /****************************************** Domain Specific Error Types & Values *******************************************/ var ( // ErrNoProcessableTask indicates that there are no tasks ready to be processed. ErrNoProcessableTask = errors.New("no tasks are ready for processing") // ErrDuplicateTask indicates that another task with the same unique key holds the uniqueness lock. ErrDuplicateTask = errors.New("task already exists") // ErrTaskIdConflict indicates that another task with the same task ID already exist ErrTaskIdConflict = errors.New("task id conflicts with another task") ) // TaskNotFoundError indicates that a task with the given ID does not exist // in the given queue. type TaskNotFoundError struct { Queue string // queue name ID string // task id } func ( *TaskNotFoundError) () string { return fmt.Sprintf("cannot find task with id=%s in queue %q", .ID, .Queue) } // IsTaskNotFound reports whether any error in err's chain is of type TaskNotFoundError. func ( error) bool { var *TaskNotFoundError return As(, &) } // QueueNotFoundError indicates that a queue with the given name does not exist. type QueueNotFoundError struct { Queue string // queue name } func ( *QueueNotFoundError) () string { return fmt.Sprintf("queue %q does not exist", .Queue) } // IsQueueNotFound reports whether any error in err's chain is of type QueueNotFoundError. func ( error) bool { var *QueueNotFoundError return As(, &) } // QueueNotEmptyError indicates that the given queue is not empty. type QueueNotEmptyError struct { Queue string // queue name } func ( *QueueNotEmptyError) () string { return fmt.Sprintf("queue %q is not empty", .Queue) } // IsQueueNotEmpty reports whether any error in err's chain is of type QueueNotEmptyError. func ( error) bool { var *QueueNotEmptyError return As(, &) } // TaskAlreadyArchivedError indicates that the task in question is already archived. type TaskAlreadyArchivedError struct { Queue string // queue name ID string // task id } func ( *TaskAlreadyArchivedError) () string { return fmt.Sprintf("task is already archived: id=%s, queue=%s", .ID, .Queue) } // IsTaskAlreadyArchived reports whether any error in err's chain is of type TaskAlreadyArchivedError. func ( error) bool { var *TaskAlreadyArchivedError return As(, &) } // RedisCommandError indicates that the given redis command returned error. type RedisCommandError struct { Command string // redis command (e.g. LRANGE, ZADD, etc) Err error // underlying error } func ( *RedisCommandError) () string { return fmt.Sprintf("redis command error: %s failed: %v", strings.ToUpper(.Command), .Err) } func ( *RedisCommandError) () error { return .Err } // IsRedisCommandError reports whether any error in err's chain is of type RedisCommandError. func ( error) bool { var *RedisCommandError return As(, &) } /************************************************* Standard Library errors package functions *************************************************/ // New returns an error that formats as the given text. // Each call to New returns a distinct error value even if the text is identical. // // This function is the errors.New function from the standard library (https://golang.org/pkg/errors/#New). // It is exported from this package for import convenience. func ( string) error { return errors.New() } // Is reports whether any error in err's chain matches target. // // This function is the errors.Is function from the standard library (https://golang.org/pkg/errors/#Is). // It is exported from this package for import convenience. func (, error) bool { return errors.Is(, ) } // As finds the first error in err's chain that matches target, and if so, sets target to that error value and returns true. // Otherwise, it returns false. // // This function is the errors.As function from the standard library (https://golang.org/pkg/errors/#As). // It is exported from this package for import convenience. func ( error, interface{}) bool { return errors.As(, ) } // Unwrap returns the result of calling the Unwrap method on err, if err's type contains an Unwrap method returning error. // Otherwise, Unwrap returns nil. // // This function is the errors.Unwrap function from the standard library (https://golang.org/pkg/errors/#Unwrap). // It is exported from this package for import convenience. func ( error) error { return errors.Unwrap() }