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