errors.go (154 lines of code) (raw):
// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you 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.
package txfile
import (
"fmt"
"github.com/elastic/go-txfile/internal/strbld"
"github.com/elastic/go-txfile/internal/vfs"
"github.com/elastic/go-txfile/txerr"
)
// reason is used as package internal error type. It's used to guarantee all
// package level errors generated or returned by txfile are compatible to txerr.Error.
type reason interface {
txerr.Error
}
// Error is the actual error type returned by all functions/methods within the
// txfile package.
// The Error is compatible to error and txerr.Error, but adds a few additional
// meta-data for applications to report and handle errors.
// Each single field in Error is optional. Fields can be accessed by methods only.
// As fields can being optional and Error being used to wrap other errors
// as well, txerr should be for inspecting errors.
type Error struct {
op string
kind error
cause error
ctx errorCtx
msg string
}
// errorCtx stores additional metadata associated with an error and it's root cause.
// When adding an error cause, the context is merged, such that no two context
// variables with same contents will be reported twice.
type errorCtx struct {
// database filename. Empty string if error is not related to a file
file string
// exact file offset an error was detected at
offset int64
isOff bool // set if offset is valid
// active transaction ID
txid uint
isTx bool // set if txid is valid
// page number an error was detected for
page PageID
isPage bool // set if the page id is valid
}
var _ reason = &Error{}
// Error formats the error message. The cause will not be included in the error
// string. Use fmt with %+v to create a formatted multiline error.
func (e *Error) Error() string { return txerr.Report(e, false) }
// Format adds support for fmt.Formatter to Error.
// The format patterns %v and %s print the top-level error message only
// (similar to `(*Error).Error()`). The format pattern "q" is similar to "%s",
// but adds double quotes before and after the message.
// Use %+v to create a multiline string containing the full trace of errors.
func (e *Error) Format(s fmt.State, c rune) { txerr.Format(e, s, c) }
// Op returns the operation the error occured at. Returns "" if the error value
// is used to wrap another error. Better use `txerr.GetOp(err)` to query an error value for
// the causing operation.
func (e *Error) Op() string { return e.op }
// Kind returns the error kind of the error. The kind should be used by
// applications to check if it is possible to recover from an error condition.
// Kind return nil if the error value does not define a kind. Better use
// `txerr.Is` or `txerr.GetKind` to query the error kind.
func (e *Error) Kind() error { return e.kind }
// Context returns a formatted string of the related meta-data as key/value
// pairs.
func (e *Error) Context() string { return e.ctx.String() }
// Message returns the user-focused error message.
func (e *Error) Message() string { return e.msg }
// Cause returns the causing error, if any.
func (e *Error) Cause() error { return e.cause }
// Errors is similar to `Cause()`, but returns a slice of errors. This way the
// error value can be consumed and formatted by zap (and propably other
// loggers).
func (e *Error) Errors() []error {
if e.cause == nil {
return nil
}
return []error{e.cause}
}
// ErrKind defines txfile error kinds(codes). ErrKind is compatible to error, so it can be used with `txerr.Is()`.
type ErrKind int
// internal txfile error kinds
//go:generate stringer -type=ErrKind -linecomment=true
const (
NoError ErrKind = iota // no error
InternalError // internal error
FileCreationFailed // can not create file
InitFailed // failed to initialize from file
InvalidConfig // configuration error
InvalidFileSize // invalid file size
InvalidMetaPage // meta page invalid
InvalidOp // invalid operation
InvalidPageID // page id out of bounds
InvalidParam // invalid parameter
OutOfMemory // out of memory
TxCommitFail // transaction failed during commit
TxRollbackFail // transaction failed during rollback
TxFailed // transaction failed
TxFinished // finished transaction
TxReadOnly // readonly transaction
endOfErrKind // unknown error kind
)
// re-export file system error kinds (from internal/vfs)
const (
PermissionError = vfs.ErrPermission
FileExists = vfs.ErrExist
FileDoesNotExist = vfs.ErrNotExist
FileClosed = vfs.ErrClosed
NoDiskSpace = vfs.ErrNoSpace
FDLimit = vfs.ErrFDLimit
CantResolvePath = vfs.ErrResolvePath
IOError = vfs.ErrIO
OSOtherError = vfs.ErrOSOther
OperationNotSupported = vfs.ErrNotSupported
LockFailed = vfs.ErrLockFailed
)
// Error returns a user readable error message.
func (k ErrKind) Error() string {
if k > endOfErrKind {
k = endOfErrKind
}
return k.String()
}
func (e *Error) of(kind ErrKind) *Error { e.kind = kind; return e }
func (e *Error) report(m string) *Error { e.msg = m; return e }
func (e *Error) reportf(m string, vs ...interface{}) *Error { return e.report(fmt.Sprintf(m, vs...)) }
// causedBy adds a cause to e and returns the modified e itself.
// The error contexts are merged (duplicates are removed from the cause), if
// the cause is `*Error`.
func (e *Error) causedBy(cause error) *Error {
e.cause = cause
other, ok := cause.(*Error)
if !ok {
return e
}
errCtx := &e.ctx
causeCtx := &other.ctx
if errCtx.file == causeCtx.file {
causeCtx.file = ""
}
if errCtx.isTx && causeCtx.isTx && errCtx.txid == causeCtx.txid {
causeCtx.isTx = false // delete common tx id from cause context
}
if errCtx.isPage && causeCtx.isPage && errCtx.page == causeCtx.page {
causeCtx.isPage = false // delete common page id from cause context
}
if errCtx.isOff && causeCtx.isOff && errCtx.offset == causeCtx.offset {
causeCtx.isOff = false // delete common page id from cause context
}
return e
}
func (ctx *errorCtx) String() string {
buf := &strbld.Builder{}
if ctx.file != "" {
buf.Fmt("file='%s'", ctx.file)
}
if ctx.isTx {
buf.Pad(" ")
buf.Fmt("tx=%v", ctx.txid)
}
if ctx.isPage {
buf.Pad(" ")
buf.Fmt("page=%v", ctx.page)
}
if ctx.isOff {
buf.Pad(" ")
buf.Fmt("offset=%v", ctx.offset)
}
return buf.String()
}
func (ctx *errorCtx) SetPage(id PageID) {
ctx.isPage, ctx.page = true, id
}
func (ctx *errorCtx) SetOffset(off int64) {
ctx.isOff, ctx.offset = true, off
}
func errOp(op string) *Error {
return &Error{op: op}
}
func errOf(kind ErrKind) *Error {
return &Error{kind: kind}
}
func wrapErr(err error) *Error {
return &Error{cause: err}
}
func raiseInvalidParam(msg string) reason {
return &Error{kind: InvalidParam, msg: msg}
}
func raiseInvalidParamf(msg string, vs ...interface{}) reason {
return raiseInvalidParam(fmt.Sprintf(msg, vs...))
}
func raiseOutOfBounds(id PageID) reason {
return &Error{
kind: InvalidPageID,
ctx: errorCtx{
isPage: true,
page: id,
},
msg: "out put bounds page id",
}
}