All checks were successful
Go CI/CD / go-ci (push) Successful in 1m47s
Introduce 'enforcer' package with `Sentinel` for rich error context and `ApplicationError` for custom error codes. Include tests, documentation updates, CI workflows, and foundational files like `GO.mod` for project setup.
93 lines
2.7 KiB
Go
93 lines
2.7 KiB
Go
package enforcer
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// Sentinel represents a structured error with metadata and context information.
|
|
type Sentinel struct {
|
|
HTTPStatus int `json:"http_status"`
|
|
Code ApplicationError `json:"code"`
|
|
Message string `json:"message"`
|
|
OriginalError error `json:"-"`
|
|
Metadata map[string]interface{} `json:"metadata"`
|
|
Timestamp time.Time `json:"timestamp"`
|
|
FunctionRef string `json:"function_ref"`
|
|
}
|
|
|
|
// Error constructs and returns the string representation of the error.
|
|
func (s *Sentinel) Error() string {
|
|
metadataStr := ""
|
|
if len(s.Metadata) > 0 {
|
|
metadataBytes, err := json.Marshal(s.Metadata)
|
|
if err == nil {
|
|
metadataStr = fmt.Sprintf(" | METADATA: %s", string(metadataBytes))
|
|
}
|
|
}
|
|
|
|
if s.OriginalError != nil {
|
|
return fmt.Sprintf(
|
|
"[Application Error %d] %s | CAUSE: %s%s",
|
|
s.Code, s.Message, s.OriginalError.Error(), metadataStr,
|
|
)
|
|
}
|
|
return fmt.Sprintf(
|
|
"[Application Error %d] %s%s", s.Code, s.Message, metadataStr,
|
|
)
|
|
}
|
|
|
|
// WithMeta adds a key-value pair to the Metadata field of the Sentinel.
|
|
// It returns the updated Sentinel object for method chaining.
|
|
func (s *Sentinel) WithMeta(key string, value interface{}) *Sentinel {
|
|
s.Metadata[key] = value
|
|
return s
|
|
}
|
|
|
|
// Unwrap returns the underlying original error wrapped by the Sentinel.
|
|
func (s *Sentinel) Unwrap() error {
|
|
return s.OriginalError
|
|
}
|
|
|
|
// NewSentinel creates a new Sentinel error instance with the provided details.
|
|
// It initializes the HTTPStatus, Code, Message, OriginalError, Metadata,
|
|
// Timestamp, and Summoner fields based on the input parameters.
|
|
// appErr provides the application-specific error information.
|
|
// original represents the original error that caused this error, if any.
|
|
// args can be used to format the message of the ApplicationError.
|
|
func NewSentinel(
|
|
appErr ApplicationError, original error, args ...interface{},
|
|
) *Sentinel {
|
|
return &Sentinel{
|
|
HTTPStatus: appErr.GetHTTPStatus(),
|
|
Code: appErr,
|
|
Message: fmt.Sprintf(appErr.GetMessage(), args...),
|
|
OriginalError: original,
|
|
Metadata: make(map[string]interface{}),
|
|
Timestamp: time.Now().UTC(),
|
|
FunctionRef: identifyInvoker(),
|
|
}
|
|
}
|
|
|
|
// identifyInvoker retrieves and formats the caller's file, line, and function name.
|
|
func identifyInvoker() string {
|
|
pc, file, line, ok := runtime.Caller(2)
|
|
if !ok {
|
|
return "unknown:0 Unknown"
|
|
}
|
|
|
|
fn := runtime.FuncForPC(pc)
|
|
filename := filepath.Base(file)
|
|
|
|
funcName := fn.Name()
|
|
if lastSlash := strings.LastIndex(funcName, "/"); lastSlash != -1 {
|
|
funcName = funcName[lastSlash+1:]
|
|
}
|
|
|
|
return fmt.Sprintf("%s:%d %s", filename, line, funcName)
|
|
}
|