mirror of
https://github.com/gotenberg/gotenberg.git
synced 2026-08-14 11:22:15 +01:00
huge refactoring
This commit is contained in:
7
internal/pkg/xerror/doc.go
Normal file
7
internal/pkg/xerror/doc.go
Normal file
@@ -0,0 +1,7 @@
|
||||
/*
|
||||
Package xerror helps standardizing
|
||||
the errors through the application.
|
||||
|
||||
Credits: https://middlemost.com/failure-is-your-domain/
|
||||
*/
|
||||
package xerror
|
||||
152
internal/pkg/xerror/xerror.go
Normal file
152
internal/pkg/xerror/xerror.go
Normal file
@@ -0,0 +1,152 @@
|
||||
package xerror
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ErrorCode is machine-readable error code.
|
||||
type ErrorCode string
|
||||
|
||||
const (
|
||||
// InternalCode is an internal error.
|
||||
InternalCode ErrorCode = "internal"
|
||||
// InvalidCode occurs when a validation
|
||||
// failed.
|
||||
InvalidCode ErrorCode = "invalid"
|
||||
// TimeoutCode occurs when something
|
||||
// timed out.
|
||||
TimeoutCode ErrorCode = "timeout"
|
||||
)
|
||||
|
||||
// Error defines our standard application
|
||||
// error.
|
||||
type Error struct {
|
||||
code ErrorCode
|
||||
message string
|
||||
op string
|
||||
err error
|
||||
}
|
||||
|
||||
// Error returns the string representation of the error message.
|
||||
func (e Error) Error() string {
|
||||
var buf bytes.Buffer
|
||||
// if wrapping an error, print its Error() message.
|
||||
// Otherwise print the error code & message.
|
||||
if e.err != nil {
|
||||
buf.WriteString(e.err.Error())
|
||||
} else {
|
||||
if e.code != "" {
|
||||
fmt.Fprintf(&buf, "<%s> ", e.code)
|
||||
}
|
||||
buf.WriteString(e.message)
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
/*
|
||||
New returns a xerror.Error.
|
||||
|
||||
Should be used for wrapping an error
|
||||
at the end of a function.
|
||||
*/
|
||||
func New(op string, previous error) error {
|
||||
return &Error{
|
||||
op: op,
|
||||
err: previous,
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Invalid returns a xerror.Error.
|
||||
|
||||
Should be used when an input
|
||||
is wrong.
|
||||
*/
|
||||
func Invalid(op, message string, previous error) error {
|
||||
return &Error{
|
||||
code: InvalidCode,
|
||||
message: message,
|
||||
op: op,
|
||||
err: previous,
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Timeout returns a xerror.Error.
|
||||
|
||||
Should be used when a timeout occurs.
|
||||
*/
|
||||
func Timeout(op, message string, previous error) error {
|
||||
return &Error{
|
||||
code: TimeoutCode,
|
||||
message: message,
|
||||
op: op,
|
||||
err: previous,
|
||||
}
|
||||
}
|
||||
|
||||
// Code returns the code of the root error, if available.
|
||||
// Otherwise returns InternalCode.
|
||||
func Code(err error) ErrorCode {
|
||||
if err == nil {
|
||||
return ""
|
||||
}
|
||||
e, ok := err.(*Error)
|
||||
if ok && e.code != "" {
|
||||
return e.code
|
||||
}
|
||||
if ok && e.err != nil {
|
||||
return Code(e.err)
|
||||
}
|
||||
return InternalCode
|
||||
}
|
||||
|
||||
const defaultMessage string = "an internal error has occurred: please contact technical support"
|
||||
|
||||
// Message returns the human-readable message of the error, if available.
|
||||
// Otherwise returns a generic error message.
|
||||
func Message(err error) string {
|
||||
if err == nil {
|
||||
return ""
|
||||
}
|
||||
e, ok := err.(*Error)
|
||||
if ok && e.message != "" {
|
||||
return e.message
|
||||
}
|
||||
if ok && e.err != nil {
|
||||
return Message(e.err)
|
||||
}
|
||||
return defaultMessage
|
||||
}
|
||||
|
||||
// Op returns the logical operation of the error, if available.
|
||||
// Otherwise returns an empty string.
|
||||
func Op(err error) string {
|
||||
if err == nil {
|
||||
return ""
|
||||
}
|
||||
e, ok := err.(*Error)
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
nestedOp := Op(e.err)
|
||||
if nestedOp != "" {
|
||||
// we want to avoid having the same op chained.
|
||||
if e.op != "" && !strings.Contains(nestedOp, e.op) {
|
||||
fmt.Fprintf(&buf, "%s: %s", e.op, nestedOp)
|
||||
} else {
|
||||
fmt.Fprintf(&buf, "%s", nestedOp)
|
||||
}
|
||||
} else if e.op != "" {
|
||||
fmt.Fprintf(&buf, "%s", e.op)
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// Compile-time checks to ensure type implements desired interfaces.
|
||||
var (
|
||||
_ = error(new(Error))
|
||||
)
|
||||
97
internal/pkg/xerror/xerror_test.go
Normal file
97
internal/pkg/xerror/xerror_test.go
Normal file
@@ -0,0 +1,97 @@
|
||||
package xerror
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
/*
|
||||
Error 1.0: op = "foo"
|
||||
Error 1.1: op = "bar"
|
||||
Error 1.2: code = "invalid", op = "baz", message = "nested error"
|
||||
Error 1.3: message = "root error"
|
||||
*/
|
||||
func scenario1() error {
|
||||
rootErr := errors.New("root error")
|
||||
nestedErr := Invalid("baz", "nested error", rootErr)
|
||||
wrappingErr := New("bar", nestedErr)
|
||||
return New("foo", wrappingErr)
|
||||
}
|
||||
|
||||
/*
|
||||
Error 2.0: op = "foo"
|
||||
Error 2.1: op = "bar"
|
||||
Error 2.2: code = "timeout", op = "bar", message = "nested error"
|
||||
*/
|
||||
func scenario2() error {
|
||||
nestedErr := Timeout("bar", "nested error", nil)
|
||||
wrappingErr := New("bar", nestedErr)
|
||||
return New("foo", wrappingErr)
|
||||
}
|
||||
|
||||
// Error 3.0: code = "", op = "foo"
|
||||
func scenario3() error {
|
||||
return New("foo", nil)
|
||||
}
|
||||
|
||||
func TestError(t *testing.T) {
|
||||
// should return the Error 1.3
|
||||
// message.
|
||||
err := scenario1()
|
||||
assert.Equal(t, "root error", err.Error())
|
||||
// should return the Error 2.2 message with
|
||||
// its code.
|
||||
err = scenario2()
|
||||
assert.Equal(t, "<timeout> nested error", err.Error())
|
||||
}
|
||||
|
||||
func TestCode(t *testing.T) {
|
||||
// should be an empty code if no error.
|
||||
assert.Equal(t, "", fmt.Sprintf("%s", Code(nil)))
|
||||
// should be the code of Error 1.2.
|
||||
err := scenario1()
|
||||
assert.Equal(t, InvalidCode, Code(err))
|
||||
// should be the code of Error 2.2.
|
||||
err = scenario2()
|
||||
assert.Equal(t, TimeoutCode, Code(err))
|
||||
// should be the default code.
|
||||
err = scenario3()
|
||||
assert.Equal(t, InternalCode, Code(err))
|
||||
err = errors.New("some error")
|
||||
assert.Equal(t, InternalCode, Code(err))
|
||||
}
|
||||
|
||||
func TestMessage(t *testing.T) {
|
||||
// should be an empty message if no error.
|
||||
assert.Equal(t, "", Message(nil))
|
||||
// should be the message of Error 1.2.
|
||||
err := scenario1()
|
||||
assert.Equal(t, "nested error", Message(err))
|
||||
// should be the default message.
|
||||
err = errors.New("some error")
|
||||
assert.Equal(t, defaultMessage, Message(err))
|
||||
}
|
||||
|
||||
func TestOp(t *testing.T) {
|
||||
// should be an empty op if no error.
|
||||
assert.Equal(t, "", Op(nil))
|
||||
// should be the chain of op in this order:
|
||||
// Error 1.0 -> Error 1.1 -> Error 1.2.
|
||||
err := scenario1()
|
||||
assert.Equal(t, "foo: bar: baz", Op(err))
|
||||
/*
|
||||
should be the chain of op in this order:
|
||||
Error 2.0 -> Error 2.1.
|
||||
|
||||
As Error 2.1 and Error 2.2 shares the same
|
||||
op, Error 2.2 op is not displayed.
|
||||
*/
|
||||
err = scenario2()
|
||||
assert.Equal(t, "foo: bar", Op(err))
|
||||
// should be an empty op if not Error.
|
||||
err = errors.New("some error")
|
||||
assert.Equal(t, "", Op(err))
|
||||
}
|
||||
Reference in New Issue
Block a user