Skip to content

Commit

Permalink
Merge pull request #113 from Valentin-Kaiser/error
Browse files Browse the repository at this point in the history
  • Loading branch information
Valentin-Kaiser authored Feb 23, 2024
2 parents 2eac3af + 0bbf35b commit ba6b770
Show file tree
Hide file tree
Showing 22 changed files with 516 additions and 483 deletions.
1 change: 0 additions & 1 deletion .github/workflows/linters.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,5 @@ jobs:
uses: super-linter/super-linter/slim@v6
env:
VALIDATE_GO: true
DEFAULT_BRANCH: main
VALIDATE_ALL_CODEBASE: true
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
10 changes: 10 additions & 0 deletions dbase/constants.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package dbase

import (
"math"
"reflect"
"time"
)
Expand Down Expand Up @@ -131,3 +132,12 @@ func (t DataType) Reflect() (reflect.Type, error) {
// is a byte arry where one bit indicates wether the column is nullable and another bit indicates
// wether the column has a variable length.
var nullFlagColumn = [11]byte{0x5F, 0x4E, 0x75, 0x6C, 0x6C, 0x46, 0x6C, 0x61, 0x67, 0x73}

const (
MaxColumnNameLength = 10
MaxCharacterLength = 254
MaxNumericLength = 20
MaxFloatLength = 20
MaxIntegerValue = math.MaxInt32
MinIntegerValue = math.MinInt32
)
16 changes: 8 additions & 8 deletions dbase/conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ func parseDate(raw []byte) (time.Time, error) {
}
t, err := time.Parse("20060102", string(raw))
if err != nil {
return t, newError("dbase-interpreter-parsedate-1", err)
return t, NewError("failed to parse date").Details(err)
}
return t, nil
}
Expand Down Expand Up @@ -76,7 +76,7 @@ func parseNumericInt(raw []byte) (int64, error) {
}
i, err := strconv.ParseInt(trimmed, 10, 64)
if err != nil {
return i, newError("dbase-conversion-parseint-1", err)
return i, NewError("failed to parse int").Details(err)
}
return i, nil
}
Expand All @@ -89,7 +89,7 @@ func parseFloat(raw []byte) (float64, error) {
}
f, err := strconv.ParseFloat(strings.TrimSpace(trimmed), 64)
if err != nil {
return f, newError("dbase-conversion-parsefloat-1", err)
return f, NewError("failed to parse float").Details(err)
}
return f, nil
}
Expand All @@ -98,7 +98,7 @@ func parseFloat(raw []byte) (float64, error) {
func toUTF8String(raw []byte, converter EncodingConverter) (string, error) {
utf8, err := converter.Decode(raw)
if err != nil {
return string(raw), newError("dbase-conversion-toutf8string-1", err)
return string(raw), WrapError(err)
}
return string(utf8), nil
}
Expand All @@ -107,7 +107,7 @@ func toUTF8String(raw []byte, converter EncodingConverter) (string, error) {
func fromUtf8String(raw []byte, converter EncodingConverter) ([]byte, error) {
utf8, err := converter.Encode(raw)
if err != nil {
return raw, newError("dbase-conversion-fromutf8string-1", err)
return raw, WrapError(err)
}
return utf8, nil
}
Expand All @@ -117,7 +117,7 @@ func toBinary(data interface{}) ([]byte, error) {
buf := new(bytes.Buffer)
err := binary.Write(buf, binary.LittleEndian, data)
if err != nil {
return nil, newError("dbase-interpreter-tobinary-1", err)
return nil, NewError("failed to convert to binary").Details(err)
}
return buf.Bytes(), nil
}
Expand Down Expand Up @@ -187,7 +187,7 @@ func setStructField(tags map[string]string, obj interface{}, name string, value
return nil
}
if !structFieldValue.CanSet() {
return newError("dbase-conversion-setstructfield-1", fmt.Errorf("cannot set %s field value", name))
return NewError("failed to set struct field value").Details(fmt.Errorf("cannot set %s field value", name))
}
structFieldType := structFieldValue.Type()
value = cast(value, structFieldType)
Expand All @@ -201,7 +201,7 @@ func setStructField(tags map[string]string, obj interface{}, name string, value
}

if structFieldType != val.Type() {
return newError("dbase-conversion-setstructfield-2", fmt.Errorf("provided value type %v didn't match obj field type %v", val.Type(), structFieldType))
return NewError("provided value type didn't match obj field type").Details(fmt.Errorf("provided value type %v didn't match obj field type %v", val.Type(), structFieldType))
}
structFieldValue.Set(val)
return nil
Expand Down
21 changes: 10 additions & 11 deletions dbase/database.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package dbase

import (
"errors"
"fmt"
"path"
"path/filepath"
Expand All @@ -17,38 +16,38 @@ type Database struct {
// The database file must be a DBC file and the tables must be DBF files and in the same directory as the database
func OpenDatabase(config *Config) (*Database, error) {
if config == nil {
return nil, newError("dbase-io-opendatabase-1", errors.New("missing config"))
return nil, NewError("missing dbase configuration")
}
if len(strings.TrimSpace(config.Filename)) == 0 {
return nil, newError("dbase-io-opendatabase-2", errors.New("missing filename"))
return nil, NewError("missing dbase filename")
}
if strings.ToUpper(filepath.Ext(config.Filename)) != string(DBC) {
return nil, newError("dbase-io-opendatabase-3", fmt.Errorf("invalid file name: %v", config.Filename))
return nil, NewError("invalid dbase filename").Details(fmt.Errorf("file extension must be %v", DBC))
}
debugf("Opening database: %v", config.Filename)
databaseTable, err := OpenTable(config)
if err != nil {
return nil, newError("dbase-io-opendatabase-4", fmt.Errorf("opening database table failed with error: %w", err))
return nil, WrapError(err)
}
// Search by all records where object type is table
typeField, err := databaseTable.NewFieldByName("OBJECTTYPE", "Table")
if err != nil {
return nil, newError("dbase-io-opendatabase-5", fmt.Errorf("creating type field failed with error: %w", err))
return nil, WrapError(err)
}
rows, err := databaseTable.Search(typeField, true)
if err != nil {
return nil, newError("dbase-io-opendatabase-6", fmt.Errorf("searching for type field failed with error: %w", err))
return nil, WrapError(err)
}
// Try to load the table files
tables := make(map[string]*File, 0)
for _, row := range rows {
objectName, err := row.ValueByName("OBJECTNAME")
if err != nil {
return nil, newError("dbase-io-opendatabase-7", fmt.Errorf("getting table name failed with error: %w", err))
return nil, WrapError(err)
}
tableName, ok := objectName.(string)
if !ok {
return nil, newError("dbase-io-opendatabase-8", errors.New("table name is not a string"))
return nil, NewError("table name is not a string")
}
tableName = strings.Trim(tableName, " ")
if tableName == "" {
Expand All @@ -75,7 +74,7 @@ func OpenDatabase(config *Config) (*Database, error) {
// Load the table
table, err := OpenTable(tableConfig)
if err != nil {
return nil, newError("dbase-io-opendatabase-9", fmt.Errorf("opening table failed with error: %w", err))
return nil, WrapError(err)
}
if table != nil {
tables[tableName] = table
Expand All @@ -88,7 +87,7 @@ func OpenDatabase(config *Config) (*Database, error) {
func (db *Database) Close() error {
for _, table := range db.tables {
if err := table.Close(); err != nil {
return newError("dbase-io-close-1", err)
return WrapError(err)
}
}
return db.file.Close()
Expand Down
6 changes: 0 additions & 6 deletions dbase/debug.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,3 @@ func debugf(format string, v ...interface{}) {
debugLogger.Printf(format, v...)
}
}

func errorf(format string, v ...interface{}) {
if debug {
errorLogger.Printf(format, v...)
}
}
4 changes: 2 additions & 2 deletions dbase/encoding.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ func (c DefaultConverter) Decode(in []byte) ([]byte, error) {
r := transform.NewReader(bytes.NewReader(in), c.encoding.NewDecoder())
data, err := io.ReadAll(r)
if err != nil {
return nil, newError("dbase-encoding-decode-1", err)
return nil, NewError("decoding with default converter failed").Details(err)
}
return data, nil
}
Expand All @@ -40,7 +40,7 @@ func (c DefaultConverter) Encode(in []byte) ([]byte, error) {
enc := c.encoding.NewEncoder()
nDst, _, err := enc.Transform(out, in, false)
if err != nil {
return nil, newError("dbase-encoding-encode-1", err)
return nil, NewError("encoding with default converter failed").Details(err)
}
return out[:nDst], nil
}
Expand Down
102 changes: 61 additions & 41 deletions dbase/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package dbase
import (
"errors"
"fmt"
"runtime"
)

var (
Expand All @@ -24,62 +25,81 @@ var (

// Error is a wrapper for errors that occur in the dbase package
type Error struct {
context []string
err error
trace []string
details []error
msg string
}

func newError(context string, err error) Error {
errorf("%s:%s", context, GetErrorTrace(err))
if err != nil {
var dbaseError Error
if errors.As(err, &dbaseError) {
ctx := dbaseError.Context()
ctx = append(ctx, context)
dbaseError = Error{
context: ctx,
err: err,
}
return dbaseError
}
// NewError creates a new Error
func NewError(err string) Error {
e := Error{
msg: err,
trace: make([]string, 0),
details: make([]error, 0),
}
return Error{
context: []string{context},
err: err,
e.trace = trace(e, 2)
return e
}

func NewErrorf(format string, a ...interface{}) Error {
e := Error{
msg: fmt.Sprintf(format, a...),
trace: make([]string, 0),
details: make([]error, 0),
}
e.trace = trace(e, 2)
return e
}

func newErrorf(context string, format string, a ...interface{}) Error {
return newError(context, fmt.Errorf(format, a...))
func (e Error) Details(err error) Error {
e.details = append(e.details, err)
return e
}

// Error returns the error message of the underlying error
func (e Error) Error() string {
return e.err.Error()
}
details := ""
for _, d := range e.details {
details += "=> " + d.Error()
}

// Context returns the context of the error in the dbase package
func (e Error) Context() []string {
return e.context
}
if debug && len(e.trace) > 0 {
trace := ""
for i := len(e.trace) - 1; i >= 0; i-- {
trace += e.trace[i]
if i > 0 {
trace += " -> "
}
}

// trace returns the context of the error in the dbase package as a string
func (e Error) trace() string {
trace := ""
// append reverse order
for i := len(e.context) - 1; i >= 0; i-- {
trace += e.context[i] + ":"
return fmt.Sprintf("%s: %s %s", trace, e.msg, details)
}
return trace

return fmt.Sprintf("%s %s", e.msg, details)
}

// GetErrorTrace returns the context and the error produced by the dbase package as a string
func GetErrorTrace(err error) error {
func WrapError(err error) Error {
if err == nil {
return nil
return NewError("unknown error occurred - cant wrap nil error")
}
var dbaseError Error
if errors.As(err, &dbaseError) {
return fmt.Errorf("%s%w", dbaseError.trace(), dbaseError)
if e, ok := err.(Error); ok {
e.trace = trace(e, 2)
return e
}
return err
e := Error{
msg: err.Error(),
trace: make([]string, 0),
details: make([]error, 0),
}
e.trace = trace(e, 2)
return e
}

func trace(e Error, level int) []string {
_, file, line, ok := runtime.Caller(level)
if !ok {
return e.trace
}

e.trace = append(e.trace, fmt.Sprintf("%s:%d", file, line))
return e.trace
}
Loading

0 comments on commit ba6b770

Please sign in to comment.