noxarion-x/enforcer/sentinel.go
Rene Nochebuena 06c778db51
All checks were successful
Go CI/CD / go-ci (push) Successful in 1m47s
Add initial enforcer module with structured error handling and CI
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.
2025-04-27 00:07:04 -06:00

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)
}