error.go

  1package sqlite3
  2
  3import (
  4	"errors"
  5	"strings"
  6
  7	"github.com/ncruces/go-sqlite3/internal/util"
  8)
  9
 10// Error wraps an SQLite Error Code.
 11//
 12// https://sqlite.org/c3ref/errcode.html
 13type Error struct {
 14	msg  string
 15	sql  string
 16	code res_t
 17}
 18
 19// Code returns the primary error code for this error.
 20//
 21// https://sqlite.org/rescode.html
 22func (e *Error) Code() ErrorCode {
 23	return ErrorCode(e.code)
 24}
 25
 26// ExtendedCode returns the extended error code for this error.
 27//
 28// https://sqlite.org/rescode.html
 29func (e *Error) ExtendedCode() ExtendedErrorCode {
 30	return xErrorCode(e.code)
 31}
 32
 33// Error implements the error interface.
 34func (e *Error) Error() string {
 35	var b strings.Builder
 36	b.WriteString(util.ErrorCodeString(uint32(e.code)))
 37
 38	if e.msg != "" {
 39		b.WriteString(": ")
 40		b.WriteString(e.msg)
 41	}
 42
 43	return b.String()
 44}
 45
 46// Is tests whether this error matches a given [ErrorCode] or [ExtendedErrorCode].
 47//
 48// It makes it possible to do:
 49//
 50//	if errors.Is(err, sqlite3.BUSY) {
 51//		// ... handle BUSY
 52//	}
 53func (e *Error) Is(err error) bool {
 54	switch c := err.(type) {
 55	case ErrorCode:
 56		return c == e.Code()
 57	case ExtendedErrorCode:
 58		return c == e.ExtendedCode()
 59	}
 60	return false
 61}
 62
 63// As converts this error to an [ErrorCode] or [ExtendedErrorCode].
 64func (e *Error) As(err any) bool {
 65	switch c := err.(type) {
 66	case *ErrorCode:
 67		*c = e.Code()
 68		return true
 69	case *ExtendedErrorCode:
 70		*c = e.ExtendedCode()
 71		return true
 72	}
 73	return false
 74}
 75
 76// Temporary returns true for [BUSY] errors.
 77func (e *Error) Temporary() bool {
 78	return e.Code() == BUSY
 79}
 80
 81// Timeout returns true for [BUSY_TIMEOUT] errors.
 82func (e *Error) Timeout() bool {
 83	return e.ExtendedCode() == BUSY_TIMEOUT
 84}
 85
 86// SQL returns the SQL starting at the token that triggered a syntax error.
 87func (e *Error) SQL() string {
 88	return e.sql
 89}
 90
 91// Error implements the error interface.
 92func (e ErrorCode) Error() string {
 93	return util.ErrorCodeString(uint32(e))
 94}
 95
 96// Temporary returns true for [BUSY] errors.
 97func (e ErrorCode) Temporary() bool {
 98	return e == BUSY || e == INTERRUPT
 99}
100
101// ExtendedCode returns the extended error code for this error.
102func (e ErrorCode) ExtendedCode() ExtendedErrorCode {
103	return xErrorCode(e)
104}
105
106// Error implements the error interface.
107func (e ExtendedErrorCode) Error() string {
108	return util.ErrorCodeString(uint32(e))
109}
110
111// Is tests whether this error matches a given [ErrorCode].
112func (e ExtendedErrorCode) Is(err error) bool {
113	c, ok := err.(ErrorCode)
114	return ok && c == ErrorCode(e)
115}
116
117// As converts this error to an [ErrorCode].
118func (e ExtendedErrorCode) As(err any) bool {
119	c, ok := err.(*ErrorCode)
120	if ok {
121		*c = ErrorCode(e)
122	}
123	return ok
124}
125
126// Temporary returns true for [BUSY] errors.
127func (e ExtendedErrorCode) Temporary() bool {
128	return ErrorCode(e) == BUSY || ErrorCode(e) == INTERRUPT
129}
130
131// Timeout returns true for [BUSY_TIMEOUT] errors.
132func (e ExtendedErrorCode) Timeout() bool {
133	return e == BUSY_TIMEOUT
134}
135
136// Code returns the primary error code for this error.
137func (e ExtendedErrorCode) Code() ErrorCode {
138	return ErrorCode(e)
139}
140
141func errorCode(err error, def ErrorCode) (msg string, code res_t) {
142	switch code := err.(type) {
143	case nil:
144		return "", _OK
145	case ErrorCode:
146		return "", res_t(code)
147	case xErrorCode:
148		return "", res_t(code)
149	case *Error:
150		return code.msg, res_t(code.code)
151	}
152
153	var ecode ErrorCode
154	var xcode xErrorCode
155	switch {
156	case errors.As(err, &xcode):
157		code = res_t(xcode)
158	case errors.As(err, &ecode):
159		code = res_t(ecode)
160	default:
161		code = res_t(def)
162	}
163	return err.Error(), code
164}