From f8ccbe90b4be54ccc7ea7a9d6d7fab6cdacc9bfb Mon Sep 17 00:00:00 2001 From: Drew Bednar Date: Sat, 11 Jan 2025 14:28:41 -0500 Subject: [PATCH] Adding application error tests --- error.go | 60 +++++++++++++++++++++++++++++++++++++++++++++++++++ error_test.go | 57 ++++++++++++++++++++++++++++++++++++++++++++++++ go.sum | 0 user.go | 33 ++++++++++++++++++++++++++++ 4 files changed, 150 insertions(+) create mode 100644 error.go create mode 100644 error_test.go create mode 100644 go.sum create mode 100644 user.go diff --git a/error.go b/error.go new file mode 100644 index 0000000..e577a1c --- /dev/null +++ b/error.go @@ -0,0 +1,60 @@ +package ratchet + +import ( + "errors" + "fmt" +) + +const INTERNAL_ERROR_MESSAGE = "internal error" + +// Application error codes. +// +// Note: these are generic codes but map well to HTTP status codes. +const ( + EINTERNAL = "internal" + ENOTFOUND = "not_found" +) + +// Error respresents an application specific-error. Application errors can be +// unwrapped by the caller to extract out the code and message +// +// Any non-application error (like disk error) should be reported as an EINTERNAL +// error and the human user should only see "Internal error" as the message. +// These low-level internal error details should only be logged and reported to +// the operation of the application, not the end user. +type Error struct { + // Machine-readable error code. + Code string + + // Human-readable error message. + Message string +} + +// Error implements the error interface. Not used by the application otherwise. +func (e Error) Error() string { + return fmt.Sprintf("ratchet error: code=%s message=%s", e.Code, e.Message) +} + +// ErrorCode unwraps an application error and returns its code. +// Non-application errors always return EINTERNAL. +func ErrorCode(err error) string { + var e *Error + if err == nil { + return "" + } else if errors.As(err, &e) { + return e.Code + } + return EINTERNAL +} + +// ErrorMessage unwraps an application error and returns it's message. +// Non-application errors always return "Internal error". +func ErrorMessage(err error) string { + var e *Error + if err == nil { + return "" + } else if errors.As(err, &e) { + return e.Message + } + return INTERNAL_ERROR_MESSAGE +} diff --git a/error_test.go b/error_test.go new file mode 100644 index 0000000..ec1deac --- /dev/null +++ b/error_test.go @@ -0,0 +1,57 @@ +package ratchet + +import ( + "errors" + "testing" +) + +func AssertErrorString(t *testing.T, got, want string) { + t.Helper() + if got != want { + t.Errorf("Incorrect error code/message got %q want %q", got, want) + } +} + +func TestErrorCode(t *testing.T) { + + t.Run("should return empty", func(t *testing.T) { + got := ErrorCode(nil) + + AssertErrorString(t, got, "") + }) + + t.Run("should return internal error", func(t *testing.T) { + want := EINTERNAL + got := ErrorCode(errors.New("Mock disk error")) + + AssertErrorString(t, got, want) + }) + + t.Run("should return my code", func(t *testing.T) { + e := &Error{Code: "my_code", Message: "my_message"} + got := ErrorCode(e) + + AssertErrorString(t, got, e.Code) + }) +} + +func TestErrorMessage(t *testing.T) { + t.Run("empty error should return empty string", func(t *testing.T) { + got := ErrorMessage(nil) + + AssertErrorString(t, got, "") + }) + + t.Run("should return internal error message", func(t *testing.T) { + got := ErrorMessage(errors.New("Mock disk error")) + + AssertErrorString(t, got, INTERNAL_ERROR_MESSAGE) + }) + + t.Run("should return application error message", func(t *testing.T) { + e := &Error{Message: "my_message"} + got := ErrorMessage(e) + + AssertErrorString(t, got, e.Message) + }) +} diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..e69de29 diff --git a/user.go b/user.go new file mode 100644 index 0000000..940704c --- /dev/null +++ b/user.go @@ -0,0 +1,33 @@ +package ratchet + +import ( + "context" + "time" +) + +type User struct { + ID int `json:"id"` + + // User prefered name and email + Name string `json:"name"` + Email string `json:"email"` + + // Randomly generated API key for use with the API + // "-" omits the key from serialization + APIKey string `json:"-"` + + // Timestamps for user creatation and last update + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` + + // List of associated Oauth authentication Objects + // Not yet implemented + // Auths []*Auth `json:"auths"` +} + +// UserService represents a service for managing users. +type UserService interface { + // Retrieves a user by ID along with their associated auth objects + // Returns ENOTFOUND if user does not exist. + FindUserByID(ctx context.Context, id int) (*User, error) +}