comentado código

master
dev 1 year ago
parent b1f2a8ccd1
commit 8f20e8523d

@ -26,6 +26,10 @@
// Date Create 07/05/2025
// =============================================================================
// Package errs provides a robust infrastructure for structured error management
// in Go, specifically designed for distributed systems and microservices.
// It allows for hierarchical error classification, contextual metadata composition,
// and rich formatting to facilitate debugging and monitoring.
package errs
import (
@ -37,51 +41,68 @@ import (
)
const (
// errModuleSep is the separator used between the module name and the error class.
errModuleSep string = "::"
errClassSep string = "."
// errClassSep is the separator used between subclasses of an error.
errClassSep string = "."
// moduleName defines the default module name for internal package errors.
moduleName = errModuleSep
)
const (
// MetaClassName is the standard key name for the metadata representing the error class.
MetaClassName string = "class"
// MetaCauseName is the standard key name for the metadata representing the cause of an error.
MetaCauseName string = "cause"
)
var (
/*
G = struct {}{}
*/
// g contains global variables and configurations for the errs package.
g = struct {
// Regular expression validators for metadata names, subclasses, and error classes.
metaValidator,
subclassValidator,
classValidator *regexp.Regexp
formatOptions *FormatOptions
// formatOptions defines the formatting options for error representation.
formatOptions *FormatOptions
// errorFormatter is the function used to format errors.
errorFormatter func(w io.Writer, e error, o *FormatOptions, l int, s FormatShow)
}{
metaValidator: regexp.MustCompile(`^[a-zA-Z][a-zA-Z0-9_-]{0,62}[a-zA-Z0-9]$`),
classValidator: regexp.MustCompile(`^(?:[a-zA-Z0-9](?:[a-zA-Z0-9._-]*[a-zA-Z0-9])?|)::[a-zA-Z0-9](?:[a-zA-Z0-9._-]*[a-zA-Z0-9])?$`),
// metaValidator checks if a metadata name is valid.
metaValidator: regexp.MustCompile(`^[a-zA-Z][a-zA-Z0-9_-]{0,62}[a-zA-Z0-9]$`),
// classValidator checks if an error class name (including optional module) is valid.
classValidator: regexp.MustCompile(`^(?:[a-zA-Z0-9](?:[a-zA-Z0-9._-]*[a-zA-Z0-9])?|)::[a-zA-Z0-9](?:[a-zA-Z0-9._-]*[a-zA-Z0-9])?$`),
// subclassValidator checks if a subclass name is valid.
subclassValidator: regexp.MustCompile("^[a-zA-Z0-9](?:[a-zA-Z0-9._-]*[a-zA-Z0-9])?$"),
formatOptions: NewFormatOptionsColor(),
// formatOptions is initialized with default color formatting options.
formatOptions: NewFormatOptionsColor(),
// errorFormatter is initialized with the default error formatting function.
errorFormatter: defaultFormatError,
}
// E contains a collection of predefined errors used internally by the errs package.
E = struct {
MultipleErr,
// Multiple indicates that an operation resulted in multiple errors.
Multiple,
// InvalidNilError indicates that a nil error was provided where not allowed.
InvalidNilError,
// InvalidClass indicates that the provided error class name is invalid.
InvalidClass,
// InvalidErrValue indicates that the provided Err value is invalid.
InvalidErrValue,
// InvalidMetaName indicates that the provided metadata name is invalid.
InvalidMetaName,
// InvalidMetaType indicates that the provided metadata type is invalid.
InvalidMetaType,
// InvalidNilCause indicates that a nil cause was provided where not allowed.
InvalidNilCause Err
}{
MultipleErr: Err(moduleName + "multiple_errors"),
Multiple: Err(moduleName + "multiple_errors"),
InvalidErrValue: Err(moduleName + "invalid_err_name"),
InvalidClass: Err(moduleName + "invalid_class"),
InvalidClass: Err(moduleName + "invalid_class"),
InvalidNilError: Err(moduleName + "invalid_nil_error"),
InvalidMetaName: Err(moduleName + "invalid_meta_name"),
InvalidMetaType: Err(moduleName + "invalid_meta_type"),
@ -89,6 +110,9 @@ var (
}
)
// CreateSubclassOf creates a new subclass of an existing parent error.
// The subclass name is appended to the parent error name using errClassSep.
// Panic if the parent or subclass are not valid error names.
func CreateSubclassOf(parent Err, subclass string) Err {
if !IsValidSubclass(parent, subclass) {
panic(CreateParamErr("subclass", subclass))
@ -96,6 +120,12 @@ func CreateSubclassOf(parent Err, subclass string) Err {
return Err(string(parent) + errClassSep + subclass)
}
// ToError converts a value of any type to an error.
// If the value is nil, it returns nil.
// If the value is an error, it returns it directly.
// If the value is a slice of errors ([]error), it returns a composed error
// with E.Multiple and the causes attached (if the slice is not empty).
// For any other type, it uses fmt.Errorf to create a basic error.
func ToError(err any) error {
if err == nil {
return nil
@ -107,11 +137,16 @@ func ToError(err any) error {
if len(e) == 0 {
return nil
}
return Compose(E.MultipleErr, WithCauses(e...))
return Compose(E.Multiple, WithCauses(e...))
}
return fmt.Errorf("%v", err)
}
// ErrorToString converts an error to its string representation.
// If the error is nil, it returns an empty string.
// If the error is of type Err, it returns its underlying string value.
// If the error implements fmt.Stringer, it uses its String() method.
// Otherwise, it uses the error's Error() method.
func ErrorToString(err error) string {
if err == nil {
return ""
@ -127,16 +162,22 @@ func ErrorToString(err error) string {
}
}
// IsSubclassOf checks if 'err' is a subclass of 'parent'.
// This is checked for both Err types (using the hierarchy defined by errClassSep)
// and standard errors (checking if the error string starts with the parent string + ".").
// Returns false if either error is nil.
func IsSubclassOf(err, parent error) bool {
if err == nil || parent == nil {
return false
}
// Specific check for Err types
var e, p Err
if errors.As(err, &e) && errors.As(parent, &p) {
return e.IsSubclassOf(p)
}
// Generic check for errors implementing Unwrap
for e := err; e != nil; e = errors.Unwrap(e) {
errStr := ErrorToString(e)
parentStr := ErrorToString(parent)
@ -147,19 +188,24 @@ func IsSubclassOf(err, parent error) bool {
return false
}
// IsValidErr checks if a string is a valid error name according to the rules defined by classValidator.
func IsValidErr(value string) bool {
return len(value) > 0 && g.classValidator.MatchString(value)
}
// IsValidClass checks if a string is a valid class name according to the rules defined by classValidator.
func IsValidClass(class string) bool {
return len(class) > 0 && g.classValidator.MatchString(class)
}
// IsValidSubclass checks if 'subclass' is a valid subclass name for the 'parent' error.
// It verifies that both the parent and subclass are valid names and that the subclass is not identical
// to the final name of the parent (to prevent subclasses from having the same name as their direct parent).
func IsValidSubclass(parent Err, subclass string) bool {
if !IsValidErr(string(parent)) {
return false
}
if !IsValidClass(subclass) {
if !IsValidClass(subclass) { // Should this be IsValidSubclass name?
return false
}
@ -173,5 +219,7 @@ func IsValidSubclass(parent Err, subclass string) bool {
if c, h := strings.CutPrefix(parentName, errModuleSep); h {
return subclass != c
}
// If neither errClassSep nor errModuleSep is present, compare with the full parent name.
return subclass != parentName
}

File diff suppressed because it is too large Load Diff

@ -29,11 +29,17 @@
package errs
type (
// Causer is an interface that can be implemented by errors that have an underlying cause.
// It is similar to Go 1.13+'s Unwrap interface, but specifically for a single cause.
Causer interface {
// Cause returns the error that caused this error.
Cause() error
}
// CauseFnc is a function type that implements the Causer interface.
// It allows an anonymous function or an existing function to act as a cause.
CauseFnc func() error
)
// Cause implements the Causer interface for CauseFnc, executing the underlying function.
func (f CauseFnc) Cause() error { return f() }

@ -29,11 +29,17 @@
package errs
type (
// Causers is an interface that can be implemented by errors that have multiple underlying causes.
// This is useful for errors that aggregate failures from multiple operations.
Causers interface {
// Causes returns a slice of errors that caused this error.
Causes() []error
}
// CausesFnc is a function type that implements the Causers interface.
// It allows an anonymous function or an existing function to act as a provider of multiple causes.
CausesFnc func() []error
)
// Causes implements the Causers interface for CausesFnc, executing the underlying function.
func (f CausesFnc) Causes() []error { return f() }

@ -29,24 +29,38 @@
package errs
type (
// Composer is an interface that defines an object capable of composing (wrapping) an existing error.
// Composers are used to add metadata or functionality to an error.
Composer interface {
// Compose takes a base error and returns a new error that wraps it,
// adding the information or behavior of the composer.
Compose(err error) error
}
// ComposeFnc is a function type that implements the Composer interface.
// It allows an anonymous function or an existing function to act as an error composer.
ComposeFnc func(error) error
)
// Compose implements the Composer interface for ComposeFnc, executing the underlying function.
func (f ComposeFnc) Compose(err error) error { return f(err) }
// Compose applies a series of composers to a base error.
// Each composer wraps the result of the previous composer.
// Panics if the base error is nil.
// If no composers are provided, it returns the base error unchanged.
func Compose(base error, composers ...Composer) error {
if base == nil {
// Panics with a specific error if the base error is nil, indicating incorrect usage.
panic(ComposeWithCause(E.InvalidNilError, CreateParamErr("base", nil)))
}
if len(composers) == 0 {
return base
}
// Apply each composer sequentially.
for _, c := range composers {
if c != nil {
// If the composer returns a new non-nil error, update the base error.
if nb := c.Compose(base); nb != nil {
base = nb
}

@ -33,60 +33,96 @@ import (
"strings"
)
// Err is the fundamental type for structured errors in this package.
// It represents an error with a hierarchical class name (e.g., "module::class.subclass").
// It implements the standard Go error interface.
type Err string
// CreateErr creates a new Err instance with the provided string value.
// It validates that the value is a valid error name.
// Panics if the value is not valid.
func CreateErr(value string) Err {
if !IsValidErr(value) {
// Panics with an error indicating an invalid Err value and the cause (the parameter error).
panic(ComposeWithCause(E.InvalidErrValue, CreateParamErr("class", value)))
}
return Err(value)
}
// Module extracts the module name from the error, if present.
// The module is the part before the first "::".
// Returns an empty string if no module is present.
func (e Err) Module() string {
// Uses strings.CutSuffix to find and remove the "::" suffix if it exists.
if m, h := strings.CutSuffix(string(e), errModuleSep); h {
return m
}
return ""
}
// Class extracts the class name from the error.
// The class is the part after the first "::", or the full name if no module is present.
func (e Err) Class() string {
s := string(e)
// Uses strings.CutPrefix to find and remove the "::" prefix if it exists.
if n, h := strings.CutPrefix(s, errModuleSep); h {
return n
}
return s
}
// Parent returns the parent error in the subclass hierarchy, if it exists.
// For example, for "module::class.subclass", it returns "module::class".
// The second boolean value is true if a parent was found, false otherwise.
func (e Err) Parent() (Err, bool) {
s := string(e)
// Finds the last class separator ".".
if idx := strings.LastIndex(s, errClassSep); idx != -1 {
// Returns the substring up to the last separator as the parent error.
return Err(s[:idx]), true
}
return "", false
}
// Error implements the standard Go error interface.
// It returns the string representation of the error.
func (e Err) Error() string { return string(e) }
// String implements the fmt.Stringer interface.
// It returns the string representation of the error.
func (e Err) String() string { return string(e) }
// IsSubclassOf checks if this Err error is a subclass of the 'parent' error.
// It checks if the string of this error starts with the string of the parent followed by the class separator ".".
// It also verifies that the errors are not identical and that this error is longer than the parent.
// Returns false if the parent is not of type Err.
func (e Err) IsSubclassOf(parent error) bool {
var p Err
// Attempts to convert the parent error to type Err.
if errors.As(parent, &p) {
s, ps := string(e), string(p)
// Checks if the string of this error starts with the parent string + "."
// and if the strings are different and this one is longer.
return s != ps && len(s) > len(ps) && strings.HasPrefix(s, ps+errClassSep)
}
return false
}
// Is implements Go 1.13+'s errors.Is interface.
// It checks if this Err error is equal to the 'target' error or if it is a subclass of 'target'.
// The exact check (e == target) is performed first.
func (e Err) Is(target error) bool {
// Verificación exacta primero
// Exact check first: if this Err error is identical to the target (if the target is also Err).
var t Err
if errors.As(target, &t) && e == t {
return true
}
// Verificación de jerarquía
// Hierarchy check: if this error is a subclass of the target.
return IsSubclassOf(e, target)
}
// Unwrap implements Go 1.13+'s errors.Unwrap interface.
// For the base Err type, there is no wrapped error, so it returns nil.
func (_ Err) Unwrap() error { return nil }

@ -28,9 +28,11 @@
package errs
// ErrorList is a slice of errors with helper methods for managing the list.
// It can contain nil errors.
type ErrorList []error
// Any returns true if the list contains at least one non-nil error
// Any returns true if the list contains at least one non-nil error.
func (l ErrorList) Any() bool {
for _, e := range l {
if e != nil {
@ -40,12 +42,12 @@ func (l ErrorList) Any() bool {
return false
}
// Len returns the total length of the error list, including nil errors
// Len returns the total length of the error list, including nil errors.
func (l ErrorList) Len() int {
return len(l)
}
// Count returns the number of non-nil errors in the list
// Count returns the number of non-nil errors in the list.
func (l ErrorList) Count() int {
c := 0
for _, e := range l {
@ -56,8 +58,8 @@ func (l ErrorList) Count() int {
return c
}
// First returns the first non-nil error in the list
// If no non-nil error exists, returns nil
// First returns the first non-nil error in the list.
// If no non-nil error exists, returns nil.
func (l ErrorList) First() error {
for _, e := range l {
if e != nil {
@ -67,8 +69,8 @@ func (l ErrorList) First() error {
return nil
}
// Last returns the last non-nil error in the list
// If no non-nil error exists, returns nil
// Last returns the last non-nil error in the list.
// If no non-nil error exists, returns nil.
func (l ErrorList) Last() error {
for i := len(l) - 1; i >= 0; i-- {
if l[i] != nil {
@ -78,14 +80,14 @@ func (l ErrorList) Last() error {
return nil
}
// Append adds an error to the end of the list, regardless of whether it's nil
// Returns the modified error list
// Append adds an error to the end of the list, regardless of whether it's nil.
// Returns the modified error list.
func (l ErrorList) Append(err error) ErrorList {
return append(l, err)
}
// AppendNotNil adds an error to the list only if it's not nil
// Returns the modified error list or the original list if the error was nil
// AppendNotNil adds an error to the list only if it's not nil.
// Returns the modified error list or the original list if the error was nil.
func (l ErrorList) AppendNotNil(err error) ErrorList {
if err != nil {
return append(l, err)
@ -93,22 +95,22 @@ func (l ErrorList) AppendNotNil(err error) ErrorList {
return l
}
// Reset removes all errors from the list by resetting its length to zero
// Returns the emptied error list
// Reset removes all errors from the list by resetting its length to zero.
// Returns the emptied error list.
func (l ErrorList) Reset() ErrorList {
return l[:0]
}
// Clean removes any nil errors from the list
// Returns a new error list containing only non-nil errors
// Clean removes any nil errors from the list.
// Returns a new error list containing only non-nil errors.
func (l ErrorList) Clean() ErrorList {
return l.filter(func(e error) bool {
return e != nil
})
}
// filter creates a new error list containing only errors that satisfy the filter function
// Returns nil if no errors match the filter criteria
// filter creates a new error list containing only errors that satisfy the filter function.
// This is an internal helper method.
func (l ErrorList) filter(filter func(error) bool) ErrorList {
rs := make([]error, 0, len(l))
for _, e := range l {

@ -31,18 +31,24 @@ package errs
import "fmt"
type (
// ErrorWithCause is a type that wraps an error and adds a single underlying cause.
// This is the standard way to chain errors in Go using errors.Unwrap.
ErrorWithCause struct {
error
cause error
error // The wrapped error (the error being caused)
cause error // The underlying error that caused the wrapped error
}
)
// WithCause creates a Composer that adds a single cause to an error.
func WithCause(cause error) Composer {
return ComposeFnc(func(err error) error {
return ComposeWithCause(err, cause)
})
}
// ComposeWithCause creates a new ErrorWithCause by wrapping an existing error
// and associating it with a single cause.
// Panics if the provided error or cause is nil.
func ComposeWithCause(err, cause error) ErrorWithCause {
if err == nil {
panic(E.InvalidNilError)
@ -56,20 +62,29 @@ func ComposeWithCause(err, cause error) ErrorWithCause {
}
}
// Cause implements the Causer interface.
// It returns the underlying error that caused this error.
func (e ErrorWithCause) Cause() error {
return e.cause
}
// Unwrap implements Go 1.13+'s errors.Unwrap interface.
// It returns the wrapped error, allowing the error chain to be traversed.
func (e ErrorWithCause) Unwrap() error {
return e.error
}
// Format implements fmt.Formatter.
// It delegates the formatting to the global Format function.
func (e ErrorWithCause) Format(f fmt.State, v rune) { Format(e, f, v) }
// Metadata implements the MetadataProvider interface.
// It provides the cause as a Meta object with MetaTypeCause if the selector includes MetaSelectCause.
func (e ErrorWithCause) Metadata(selector MetaSelector) []Meta {
if !selector.SelectCause() {
return nil
}
// Creates a Meta object for the cause, using the cause's error string as the value.
return []Meta{
CreateMeta(MetaCauseName, e.cause.Error(), MetaTypeCause),
}

@ -31,49 +31,70 @@ package errs
import "fmt"
type (
// ErrorWithCauses is a type that wraps an error and associates it with multiple underlying causes.
// This is useful for aggregating errors from parallel operations.
ErrorWithCauses struct {
error
causes ErrorList
error // The wrapped error
causes ErrorList // The list of underlying errors that caused the wrapped error
}
)
// WithCauses creates a Composer that adds multiple causes to an error.
// Takes a variable number of errors as causes.
func WithCauses(causes ...error) Composer {
return ComposeFnc(func(err error) error {
return ComposeWithCauses(err, causes...)
})
}
// ComposeWithCauses creates a new ErrorWithCauses by wrapping an existing error
// and associating it with a list of causes.
// It cleans the provided list of causes to remove nil errors.
// Panic if the provided error is nil or if the cleaned list of causes is empty.
func ComposeWithCauses(err error, causes ...error) ErrorWithCauses {
if err == nil {
// Panics if the base error is nil.
panic(E.InvalidNilError)
}
// Clean the list of causes to remove any nil errors.
ls := ErrorList(causes).Clean()
if len(ls) == 0 {
// Panic if the list of causes is empty after cleaning.
panic(E.InvalidNilCause)
}
return ErrorWithCauses{
error: err,
causes: ls,
causes: ls, // Stores the cleaned list of causes.
}
}
// Causes returns the list of underlying errors that caused this error.
func (e ErrorWithCauses) Causes() []error {
return e.causes
}
// Unwrap implements Go 1.13+'s errors.Unwrap interface.
// It returns the wrapped error. Note: Standard Go errors.Unwrap only returns a single error.
// This type also implements the `interface{ Unwrap() []error }` pattern for Go 1.20+.
func (e ErrorWithCauses) Unwrap() error {
return e.error
}
// Format implements fmt.Formatter.
// It delegates the formatting to the global Format function.
func (e ErrorWithCauses) Format(f fmt.State, v rune) { Format(e, f, v) }
// Metadata implements the MetadataProvider interface.
// It provides the causes as a slice of Meta objects with MetaTypeCause if the selector includes MetaSelectCause.
// Each cause is represented as a Meta object with a name like "cause_0", "cause_1", etc.
func (e ErrorWithCauses) Metadata(selector MetaSelector) []Meta {
if !selector.SelectCause() {
return nil
}
// Create a slice of Meta objects, one for each cause.
mt := make([]Meta, len(e.causes))
for i, c := range e.causes {
// Create a Meta object for the cause, naming it "cause_i".
mt[i] = CreateMeta(fmt.Sprintf("%s_%d", MetaCauseName, i), c.Error(), MetaTypeCause)
}
return mt

@ -31,18 +31,24 @@ package errs
import "fmt"
type (
// ErrorWithCode is a type that wraps an error and adds an integer code.
// This code can represent application-specific error codes, HTTP status codes, etc.
ErrorWithCode struct {
error
code int
error // The wrapped error
code int // The integer error code
}
)
// WithCode creates a Composer that adds an integer code to an error.
func WithCode(code int) Composer {
return ComposeFnc(func(err error) error {
return ComposeWithCode(err, code)
})
}
// ComposeWithCode creates a new ErrorWithCode by wrapping an existing error
// and assigning it an integer code.
// Panics if the provided error is nil.
func ComposeWithCode(err error, code int) ErrorWithCode {
if err == nil {
panic(E.InvalidNilError)
@ -53,14 +59,21 @@ func ComposeWithCode(err error, code int) ErrorWithCode {
}
}
// Code returns the integer error code.
func (c ErrorWithCode) Code() int { return c.code }
// Unwrap implements Go 1.13+'s errors.Unwrap interface.
// It returns the wrapped error.
func (c ErrorWithCode) Unwrap() error {
return c.error
}
// Format implements fmt.Formatter.
// It delegates the formatting to the global Format function.
func (c ErrorWithCode) Format(f fmt.State, v rune) { Format(c, f, v) }
// Metadata implements the MetadataProvider interface.
// It provides the code as a Meta object with MetaTypeInt if the selector includes MetaSelectValue.
func (c ErrorWithCode) Metadata(selector MetaSelector) []Meta {
if !selector.SelectValue() {
return nil

@ -34,38 +34,50 @@ import (
)
type (
// ErrorWithDate is a type that wraps an error and adds a timestamp indicating when the error occurred or was created.
ErrorWithDate struct {
error
date time.Time
error // The wrapped error
date time.Time // The timestamp
}
)
// WithDate creates a Composer that adds the current time as a timestamp to an error.
func WithDate() Composer {
return ComposeFnc(func(err error) error {
return ComposeWithDate(err)
})
}
// ComposeWithDate creates a new ErrorWithDate by wrapping an existing error
// and adding the current time as its timestamp.
// Panics if the provided error is nil.
func ComposeWithDate(err error) ErrorWithDate {
if err == nil {
panic(E.InvalidNilError)
}
return ErrorWithDate{
error: err,
date: time.Now(),
date: time.Now(), // Captures the current time.
}
}
// Date returns the timestamp associated with the error.
func (c ErrorWithDate) Date() time.Time {
return c.date
}
// Unwrap implements Go 1.13+'s errors.Unwrap interface.
// It returns the wrapped error.
func (c ErrorWithDate) Unwrap() error {
return c.error
}
// Format implements fmt.Formatter.
// It delegates the formatting to the global Format function.
func (c ErrorWithDate) Format(f fmt.State, v rune) { Format(c, f, v) }
// Metadata implements the MetadataProvider interface.
// It provides the date as a Meta object with MetaTypeTime if the selector includes MetaSelectValue.
func (c ErrorWithDate) Metadata(selector MetaSelector) []Meta {
if !selector.SelectValue() {
return nil

@ -31,21 +31,27 @@ package errs
import "fmt"
type (
// ErrorWithInfo is a type that wraps an error and adds descriptive information
// like a description, help text, and a link to documentation.
ErrorWithInfo struct {
error
metas [3]Meta
desc,
help,
link string
error // The wrapped error
// metas [3]Meta // This field seems unused in the current implementation.
desc string // A descriptive message about the error
help string // Text providing guidance on how to resolve the error
link string // A URL pointing to documentation related to the error
}
)
// WithInfo creates a Composer that adds descriptive information (description, help, link) to an error.
func WithInfo(desc, help, link string) Composer {
return ComposeFnc(func(e error) error {
return ComposeWithInfo(e, desc, help, link)
})
}
// ComposeWithInfo creates a new ErrorWithInfo by wrapping an existing error
// and adding a description, help text, and a documentation link.
// Panics if the provided error is nil.
func ComposeWithInfo(err error, desc, help string, link string) ErrorWithInfo {
if err == nil {
panic(E.InvalidNilError)
@ -58,20 +64,31 @@ func ComposeWithInfo(err error, desc, help string, link string) ErrorWithInfo {
}
}
// Desc returns the descriptive message about the error.
func (c ErrorWithInfo) Desc() string { return c.desc }
// Help returns the help text for resolving the error.
func (c ErrorWithInfo) Help() string { return c.help }
// Link returns the documentation link for the error.
func (c ErrorWithInfo) Link() string { return c.link }
// Unwrap implements Go 1.13+'s errors.Unwrap interface.
// It returns the wrapped error.
func (c ErrorWithInfo) Unwrap() error { return c.error }
// Format implements fmt.Formatter.
// It delegates the formatting to the global Format function.
func (c ErrorWithInfo) Format(f fmt.State, v rune) { Format(c, f, v) }
// Metadata implements the MetadataProvider interface.
// It provides the description, help, and link as Meta objects with MetaTypeString
// if they are not empty and the selector includes MetaSelectValue.
func (c ErrorWithInfo) Metadata(selector MetaSelector) []Meta {
if !selector.SelectValue() {
return nil
}
// Collect non-empty info fields as Meta objects.
ls := make([]Meta, 0, 3)
if len(c.desc) > 0 {
ls = append(ls, CreateMeta("desc", c.desc, MetaTypeString))

@ -1,3 +1,4 @@
// =============================================================================
// Active-GO Framework
// Copyright (c) 2025 ActiveThing (https://activething.com)
@ -31,23 +32,32 @@ package errs
import "fmt"
type (
// ErrorWithMessage is a type that wraps an error and adds a descriptive message.
// This message can provide more context than the base error string,
// and can be formatted using fmt.Sprintf.
ErrorWithMessage struct {
error
message string
error // The wrapped error
message string // The descriptive message
}
)
// WithMessage creates a Composer that adds a descriptive message to an error.
// The message can be a format string with optional arguments.
func WithMessage(message string, args ...any) Composer {
return ComposeFnc(func(err error) error {
return ComposeWithMessage(err, message, args...)
})
}
// ComposeWithMessage creates a new ErrorWithMessage by wrapping an existing error
// and adding a formatted descriptive message.
// Panics if the provided error is nil.
func ComposeWithMessage(err error, message string, args ...any) ErrorWithMessage {
if err == nil {
panic(E.InvalidNilError)
}
m := message
// Format the message if arguments are provided.
if len(args) > 0 {
m = fmt.Sprintf(message, args...)
}
@ -57,16 +67,23 @@ func ComposeWithMessage(err error, message string, args ...any) ErrorWithMessage
}
}
// Message returns the descriptive message associated with the error.
func (c ErrorWithMessage) Message() string {
return c.message
}
// Unwrap implements Go 1.13+'s errors.Unwrap interface.
// It returns the wrapped error.
func (c ErrorWithMessage) Unwrap() error {
return c.error
}
// Format implements fmt.Formatter.
// It delegates the formatting to the global Format function.
func (c ErrorWithMessage) Format(f fmt.State, v rune) { Format(c, f, v) }
// Metadata implements the MetadataProvider interface.
// It provides the message as a Meta object with MetaTypeString if the selector includes MetaSelectValue.
func (c ErrorWithMessage) Metadata(selector MetaSelector) []Meta {
if !selector.SelectValue() {
return nil

@ -29,19 +29,26 @@
package errs
type (
// ErrorWithMeta is a type that wraps an error and adds a single piece of generic metadata.
// This allows attaching arbitrary key-value pairs to an error.
ErrorWithMeta struct {
error
name string // El nombre o clave del contexto (ej. "userId", "fileName", "statusCode")
value any // El valor asociado (puede ser de cualquier tipo)
error // The wrapped error
name string // The name or key of the context (e.g., "userId", "fileName", "statusCode")
value any // The associated value (can be of any type)
}
)
// WithMeta creates a Composer that adds a single piece of metadata to an error.
// Requires a valid metadata name and the value.
func WithMeta(name string, value any) Composer {
return ComposeFnc(func(err error) error {
return ComposeWithMeta(err, name, value)
})
}
// ComposeWithMeta creates a new ErrorWithMeta by wrapping an existing error
// and associating it with a single key-value metadata pair.
// Panics if the provided error is nil or the metadata name is invalid.
func ComposeWithMeta(err error, name string, value any) ErrorWithMeta {
if err == nil {
panic(E.InvalidNilError)
@ -52,22 +59,29 @@ func ComposeWithMeta(err error, name string, value any) ErrorWithMeta {
return ErrorWithMeta{
error: err,
name: name,
value: value, // Almacena el valor de cualquier tipo
value: value, // Stores the value of any type
}
}
// Unwrap implements Go 1.13+'s errors.Unwrap interface.
// It returns the wrapped error.
func (c ErrorWithMeta) Unwrap() error {
return c.error
}
// Name returns the name (key) of the metadata.
func (c ErrorWithMeta) Name() string {
return c.name
}
// Value returns the value of the metadata.
func (c ErrorWithMeta) Value() any {
return c.value
}
// Metadata implements the MetadataProvider interface.
// It provides the single metadata item as a Meta object if the selector includes MetaSelectValue.
// The MetaType is set to MetaTypeAny as the value can be of any type.
func (c ErrorWithMeta) Metadata(selector MetaSelector) []Meta {
if !selector.SelectValue() {
return nil

@ -30,26 +30,36 @@ package errs
import "fmt"
// ErrorWithMetas is a type that wraps an error and associates it with multiple arbitrary metadata items.
// This is a more general way to add context compared to specific ErrorWithX types.
type ErrorWithMetas struct {
error
types MetaSelector
metas []Meta
error // The wrapped error
types MetaSelector // A selector indicating the types of metas stored in this error
metas []Meta // The slice of metadata items
}
// WithMetas creates a Composer that adds multiple metadata items to an error.
// Takes a variable number of Meta objects.
func WithMetas(metas ...Meta) Composer {
return ComposeFnc(func(e error) error {
return ComposeWithMetas(e, metas...)
})
}
// ComposeWithMetas creates a new ErrorWithMetas by wrapping an existing error
// and associating it with a slice of metadata items.
// It determines the combined MetaSelector based on the types of the provided metas.
// Panics if the provided error is nil.
func ComposeWithMetas(err error, metas ...Meta) ErrorWithMetas {
if err == nil {
panic(E.InvalidNilError)
}
// Create a copy of the provided metas.
cp := make([]Meta, len(metas))
var sl MetaSelector
var sl MetaSelector // Initialize a selector to track the types of metas being added.
for i, m := range metas {
// Update the selector based on the type of each meta.
if m.mType == MetaTypeStack {
sl |= MetaSelectStack
} else if m.mType == MetaTypeCause {
@ -57,26 +67,35 @@ func ComposeWithMetas(err error, metas ...Meta) ErrorWithMetas {
} else {
sl |= MetaSelectValue
}
cp[i] = m
cp[i] = m // Copy the meta.
}
return ErrorWithMetas{
error: err,
types: sl,
metas: cp,
types: sl, // Store the combined selector.
metas: cp, // Store the copied metas.
}
}
// Metas returns the slice of metadata items associated with the error.
func (e ErrorWithMetas) Metas() []Meta { return e.metas }
// Unwrap implements Go 1.13+'s errors.Unwrap interface.
// It returns the wrapped error.
func (e ErrorWithMetas) Unwrap() error { return e.error }
// Format implements fmt.Formatter.
// It delegates the formatting to the global Format function.
func (e ErrorWithMetas) Format(f fmt.State, v rune) { Format(e, f, v) }
// Metadata implements the MetadataProvider interface.
// It returns the slice of Meta objects that match the provided selector and the types stored in this error.
func (e ErrorWithMetas) Metadata(selector MetaSelector) []Meta {
// If there's no overlap between the requested selector and the types of metas stored, return nil.
if selector&e.types == 0 {
return nil
}
// Filter the stored metas based on the provided selector.
l := make([]Meta, 0, len(e.metas))
for _, m := range e.metas {
switch m.mType {
@ -93,7 +112,7 @@ func (e ErrorWithMetas) Metadata(selector MetaSelector) []Meta {
continue
}
}
l = append(l, m)
l = append(l, m) // Add the meta if it matches the selector.
}
return l
}

@ -30,16 +30,23 @@ package errs
import "fmt"
// ErrorObservedBy is a type that wraps an error and indicates which component or service
// observed or handled this error. Useful for tracing errors across service boundaries.
type ErrorObservedBy struct {
error
observer string
instance string
error // The wrapped error
observer string // The name of the observer (e.g., service name, gateway name)
instance string // Optional instance identifier of the observer
}
// WithObservedBy creates a Composer that adds observer information to an error.
// The observer string is required, and an optional instance string can be provided.
func WithObservedBy(observer string, instance ...string) Composer {
return ComposeFnc(func(e error) error { return ComposeWithObservedBy(e, observer, instance...) })
}
// ComposeWithObservedBy creates a new ErrorObservedBy by wrapping an existing error
// and associating it with an observer and optional instance.
// Panics if the provided error is nil.
func ComposeWithObservedBy(err error, observer string, instance ...string) ErrorObservedBy {
if err == nil {
panic(E.InvalidNilError)
@ -55,14 +62,22 @@ func ComposeWithObservedBy(err error, observer string, instance ...string) Error
}
}
// Observer returns the name of the component or service that observed the error.
func (c ErrorObservedBy) Observer() string { return c.observer }
// Instance returns the optional instance identifier of the observer.
func (c ErrorObservedBy) Instance() string { return c.instance }
// Unwrap implements Go 1.13+'s errors.Unwrap interface.
// It returns the wrapped error.
func (c ErrorObservedBy) Unwrap() error { return c.error }
// Format implements fmt.Formatter.
// It delegates the formatting to the global Format function.
func (c ErrorObservedBy) Format(f fmt.State, v rune) { Format(c, f, v) }
// Metadata implements the MetadataProvider interface.
// It provides the observer and instance as Meta objects if the selector includes MetaSelectValue.
func (c ErrorObservedBy) Metadata(selector MetaSelector) []Meta {
if !selector.SelectValue() {
return nil

@ -30,16 +30,22 @@ package errs
import "fmt"
// ErrorWithOrigin is a type that wraps an error and indicates the service and
// optional instance where the error originally occurred. Crucial for distributed tracing.
type ErrorWithOrigin struct {
error
service string
instance string
error // The wrapped error
service string // The name of the service where the error originated
instance string // Optional instance identifier of the originating service
}
// WithOrigin creates a Composer that adds origin information (service and optional instance) to an error.
func WithOrigin(service string, instance ...string) Composer {
return ComposeFnc(func(e error) error { return ComposeWithOrigin(e, service, instance...) })
}
// ComposeWithOrigin creates a new ErrorWithOrigin by wrapping an existing error
// and associating it with the service and optional instance where it originated.
// Panics if the provided error is nil.
func ComposeWithOrigin(err error, service string, instance ...string) ErrorWithOrigin {
if err == nil {
panic(E.InvalidNilError)
@ -55,14 +61,22 @@ func ComposeWithOrigin(err error, service string, instance ...string) ErrorWithO
}
}
// Service returns the name of the service where the error originated.
func (c ErrorWithOrigin) Service() string { return c.service }
// Instance returns the optional instance identifier of the originating service.
func (c ErrorWithOrigin) Instance() string { return c.instance }
// Unwrap implements Go 1.13+'s errors.Unwrap interface.
// It returns the wrapped error.
func (c ErrorWithOrigin) Unwrap() error { return c.error }
// Format implements fmt.Formatter.
// It delegates the formatting to the global Format function.
func (c ErrorWithOrigin) Format(f fmt.State, v rune) { Format(c, f, v) }
// Metadata implements the MetadataProvider interface.
// It provides the service and instance as Meta objects if the selector includes MetaSelectValue.
func (c ErrorWithOrigin) Metadata(selector MetaSelector) []Meta {
if !selector.SelectValue() {
return nil

@ -1,3 +1,4 @@
// =============================================================================
// Active-GO Framework
// Copyright (c) 2025 ActiveThing (https://activething.com)
@ -34,26 +35,37 @@ import (
)
const (
// RetryableCanRetry is a flag indicating the error is potentially retryable.
RetryableCanRetry uint8 = 1 << iota
// RetryableIsTemporary is a flag indicating the error is temporary and might resolve on retry.
RetryableIsTemporary
// RetryableRetryAfterIsSet is a flag indicating that the 'After' duration is set and should be used for retry delay.
RetryableRetryAfterIsSet
// RetryableIsIdempotent is a flag indicating that retrying the operation is idempotent (safe to repeat).
RetryableIsIdempotent
)
type (
// ErrorWithRetryable is a type that wraps an error and adds information about its retryability.
// This is useful for implementing retry logic in clients or middleware.
ErrorWithRetryable struct {
error
ops uint8
after time.Duration
error // The wrapped error
ops uint8 // A bitmask of Retryable flags
after time.Duration // The suggested duration to wait before retrying (only valid if RetryableRetryAfterIsSet is set in ops)
}
)
// WithRetryable creates a Composer that adds retryable information to an error.
// Takes a bitmask of flags and an optional retry-after duration.
func WithRetryable(ops uint8, after time.Duration) Composer {
return ComposeFnc(func(e error) error {
return ComposeErrorWithRetryable(e, ops, after)
})
}
// ComposeErrorWithRetryable creates a new ErrorWithRetryable by wrapping an existing error
// and associating it with retryable flags and an optional retry-after duration.
// Panics if the provided error is nil.
func ComposeErrorWithRetryable(originalErr error, ops uint8, retryAfter time.Duration) ErrorWithRetryable {
if originalErr == nil {
panic(E.InvalidNilError)
@ -65,14 +77,20 @@ func ComposeErrorWithRetryable(originalErr error, ops uint8, retryAfter time.Dur
}
}
// CanRetry returns true if the RetryableCanRetry flag is set.
func (c ErrorWithRetryable) CanRetry() bool { return c.ops&RetryableCanRetry != 0 }
// IsTemporary returns true if the RetryableIsTemporary flag is set.
func (c ErrorWithRetryable) IsTemporary() bool { return c.ops&RetryableIsTemporary != 0 }
// IsIdempotent returns true if the RetryableIsIdempotent flag is set.
func (c ErrorWithRetryable) IsIdempotent() bool { return c.ops&RetryableIsIdempotent != 0 }
// After returns the raw retry-after duration.
// Use RetryAfter() to check if the duration is actually set.
func (c ErrorWithRetryable) After() time.Duration { return c.after }
// RetryAfter returns the retry-after duration and a boolean indicating if it was explicitly set.
func (c ErrorWithRetryable) RetryAfter() (time.Duration, bool) {
if c.ops&RetryableRetryAfterIsSet != 0 {
return c.after, true
@ -80,23 +98,30 @@ func (c ErrorWithRetryable) RetryAfter() (time.Duration, bool) {
return 0, false
}
// Unwrap implements Go 1.13+'s errors.Unwrap interface.
// It returns the wrapped error.
func (c ErrorWithRetryable) Unwrap() error { return c.error }
// Format implements fmt.Formatter.
// It delegates the formatting to the global Format function.
func (c ErrorWithRetryable) Format(f fmt.State, v rune) { Format(c, f, v) }
// Metadata implements the MetadataProvider interface.
// It provides retryable information (can_retry, is_temporary, is_idempotent, and optionally retry_after)
// as Meta objects if the selector includes MetaSelectValue.
func (c ErrorWithRetryable) Metadata(selector MetaSelector) []Meta {
if selector&MetaSelectValue == 0 {
return nil
}
var metas []Meta
// Siempre mostrar si es retryable, temporal o idempotente
// Always show can_retry, is_temporary, and is_idempotent flags as metadata.
metas = append(metas,
CreateMeta("can_retry", c.CanRetry(), MetaTypeBool),
CreateMeta("is_temporary", c.IsTemporary(), MetaTypeBool),
CreateMeta("is_idempotent", c.IsIdempotent(), MetaTypeBool),
)
// Mostrar retryAfter solo si el flag correspondiente está activo
// Show retry_after only if the corresponding flag is active.
if dur, ok := c.RetryAfter(); ok {
metas = append(metas, CreateMeta("retry_after", dur, MetaTypeDuration))
}

@ -30,17 +30,23 @@ package errs
import "fmt"
// ErrorWithSeverity is a type that wraps an error and adds a Severity level.
// This allows classifying the importance of an error.
type ErrorWithSeverity struct {
error
severity Severity
error // The wrapped error
severity Severity // The severity level of the error
}
// WithSeverity creates a Composer that adds a Severity level to an error.
func WithSeverity(level Severity) Composer {
return ComposeFnc(func(err error) error {
return ComposeWithSeverity(err, level)
})
}
// ComposeWithSeverity creates a new ErrorWithSeverity by wrapping an existing error
// and assigning it a Severity level.
// Panics if the provided error is nil.
func ComposeWithSeverity(err error, severity Severity) ErrorWithSeverity {
if err == nil {
panic(E.InvalidNilError)
@ -51,12 +57,19 @@ func ComposeWithSeverity(err error, severity Severity) ErrorWithSeverity {
}
}
// Severity returns the severity level of the error.
func (c ErrorWithSeverity) Severity() Severity { return c.severity }
// Unwrap implements Go 1.13+'s errors.Unwrap interface.
// It returns the wrapped error.
func (c ErrorWithSeverity) Unwrap() error { return c.error }
// Format implements fmt.Formatter.
// It delegates the formatting to the global Format function.
func (c ErrorWithSeverity) Format(f fmt.State, v rune) { Format(c, f, v) }
// Metadata implements the MetadataProvider interface.
// It provides the severity level as a Meta object if the selector includes MetaSelectValue.
func (c ErrorWithSeverity) Metadata(selector MetaSelector) []Meta {
if !selector.SelectValue() {
return nil

@ -29,27 +29,36 @@
package errs
import (
"core/excs"
"core/excs" // Assuming "core/excs" provides the Stack type and CreateStack function
"fmt"
)
type (
// ErrorWithStack is a type that wraps an error and adds a stack trace.
// This helps pinpoint the location in the code where the error was created or composed.
ErrorWithStack struct {
error
stack excs.Stack
error // The wrapped error
stack excs.Stack // The captured stack trace
}
)
// WithStack creates a Composer that adds a stack trace to an error.
// Optional integer values can control the offset and limit of stack frames captured.
func WithStack(values ...int) Composer {
return ComposeFnc(func(e error) error {
return ComposeWithStack(e, values...)
})
}
// ComposeWithStack creates a new ErrorWithStack by wrapping an existing error
// and capturing the current stack trace.
// Optional integer values can control the offset and limit of stack frames captured.
// Panics if the provided error is nil.
func ComposeWithStack(err error, values ...int) ErrorWithStack {
if err == nil {
panic(E.InvalidNilError)
}
// Determine offset and max frames from optional values.
ox, mx := 0, -1
if len(values) > 0 {
ox = values[0]
@ -60,24 +69,32 @@ func ComposeWithStack(err error, values ...int) ErrorWithStack {
return ErrorWithStack{
error: err,
stack: excs.CreateStack(ox, mx),
stack: excs.CreateStack(ox, mx), // Capture the stack trace.
}
}
// Stack returns the captured stack trace.
func (c ErrorWithStack) Stack() excs.Stack {
return c.stack
}
// Unwrap implements Go 1.13+'s errors.Unwrap interface.
// It returns the wrapped error.
func (c ErrorWithStack) Unwrap() error {
return c.error // Retorna el error embebido
return c.error // Return the embedded error
}
// Format implements fmt.Formatter.
// It delegates the formatting to the global Format function.
func (c ErrorWithStack) Format(f fmt.State, v rune) { Format(c, f, v) }
// Metadata implements the MetadataProvider interface.
// It provides the stack trace as a Meta object with MetaTypeStack if the selector includes MetaSelectStack.
func (c ErrorWithStack) Metadata(selector MetaSelector) []Meta {
if !selector.SelectStack() {
return nil
}
// Format the stack frames into a slice of strings for the Meta value.
lines := make([]string, 0, len(c.stack))
for i, f := range c.stack.Frames() {
lines = append(lines, fmt.Sprintf("[%d] %s.%s (%s:%d)", i, f.Package, f.FuncName, f.File, f.LineNumber))

@ -18,31 +18,36 @@
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
// =============================================================================
// Date Create 13/05/2025
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// =============================================================================
//Date Create 13/05/2025
//=============================================================================
package errs
import "fmt"
type (
// ErrorWithUser is a type that wraps an error and associates it with a user identifier or object.
// This helps in tracing errors back to specific user actions.
ErrorWithUser struct {
error
user any
error // The wrapped error
user any // The user identifier or object (can be of any type)
}
)
// WithUser creates a Composer that adds user information to an error.
func WithUser(user any) Composer {
return ComposeFnc(func(err error) error {
return ComposeWithUser(err, user)
})
}
// ComposeWithUser creates a new ErrorWithUser by wrapping an existing error
// and associating it with a user identifier or object.
// Panics if the provided error or user is nil.
func ComposeWithUser(err error, user any) ErrorWithUser {
if err == nil {
panic(E.InvalidNilError)
@ -50,23 +55,30 @@ func ComposeWithUser(err error, user any) ErrorWithUser {
if user == nil {
panic(CreateParamNilErr("user"))
}
// Podrías añadir validación para userId aquí si nil no es permitido, etc.
// You could add validation for userId here if nil is not allowed, etc.
return ErrorWithUser{
error: err, // Almacena el error original
user: user, // Almacena el ID de usuario de cualquier tipo
error: err, // Stores the original error
user: user, // Stores the user ID of any type
}
}
// User returns the user identifier or object associated with the error.
func (c ErrorWithUser) User() any {
return c.user
}
// Unwrap implements Go 1.13+'s errors.Unwrap interface.
// It returns the wrapped error.
func (c ErrorWithUser) Unwrap() error {
return c.error
}
// Format implements fmt.Formatter.
// It delegates the formatting to the global Format function.
func (c ErrorWithUser) Format(f fmt.State, v rune) { Format(c, f, v) }
// Metadata implements the MetadataProvider interface.
// It provides the user information as a Meta object if the selector includes MetaSelectValue.
func (c ErrorWithUser) Metadata(selector MetaSelector) []Meta {
if !selector.SelectValue() {
return nil

@ -35,199 +35,109 @@ import (
"strings"
)
// Format is the main entry point for formatting errors using the package's capabilities.
// It is typically called by types implementing fmt.Formatter.
// It delegates the actual formatting to the configured errorFormatter.
func Format(err error, f fmt.State, verb rune) {
// Delegates formatting to the global errorFormatter, passing formatting state,
// the error, current format options, initial indentation level (0),
// and formatting show options derived from the fmt verb and flags.
g.errorFormatter(f, err, g.formatOptions, 0, ShowOptionsFromVerb(f, verb))
}
// defaultFormatError provides a default implementation for formatting errors.
// It recursively formats the error and its wrapped errors (causes), displaying
// error messages, metadata, and stack traces based on the provided options and show flags.
func defaultFormatError(w io.Writer, err error, opts *FormatOptions, level int, show FormatShow) {
if err == nil {
return
}
var el []error
var stack *Meta
var el []error // Slice to collect wrapped errors (causes) for recursive formatting
var stack *Meta // Pointer to store the stack trace metadata if found
// Determine which metadata to select based on show options.
sm := MetaSelectValue
if show.ShowStack() {
sm |= MetaSelectStack
}
// If not showing hierarchy, don't select cause metadata at the current level
// to avoid duplicating cause information in the flat metadata list.
if !show.ShowHierarchy() {
sm |= MetaSelectCause
}
// Calculate the indentation for the current level.
indent := strings.Repeat(opts.Indent, level)
// Format the current error message. Use different formats for the root error (level 0) and causes.
if level == 0 {
_, _ = fmt.Fprintf(w, opts.FmtError, indent, err.Error())
} else {
_, _ = fmt.Fprintf(w, opts.FmtCause, indent, err.Error())
}
// Iterate through the error chain using errors.Unwrap.
for e := err; e != nil; e = errors.Unwrap(e) {
// If showing metadata and the current error provides metadata.
if show.ShowMetas() {
if mp, ok := e.(MetadataProvider); ok {
// Get metadata based on the current selection criteria.
ml := mp.Metadata(sm)
for _, m := range ml {
// Handle stack trace metadata separately if selected.
if sm.SelectStack() && m.mType == MetaTypeStack {
stack = &m
// Remove MetaSelectStack from selection for subsequent metadata at this level.
sm &^= MetaSelectStack
continue
}
// If showing hierarchy, skip cause metadata here to avoid duplication.
if show.ShowHierarchy() && sm.SelectCause() && m.mType == MetaTypeCause {
continue
}
// Format other metadata.
_, _ = fmt.Fprintf(w, opts.FmtMeta, indent, m.name, m.value)
}
}
}
// If showing hierarchy, collect the wrapped errors (causes) for recursive formatting.
if show.ShowHierarchy() {
switch cs := e.(type) {
case Causer:
// Handle single cause errors.
el = append(el, cs.Cause())
case Causers:
// Handle errors with multiple causes.
el = append(el, cs.Causes()...)
case interface{ Unwrap() []error }:
// Handle errors implementing the Unwrap() []error interface (Go 1.20+).
el = cs.Unwrap()
}
}
}
// Recursively format the collected wrapped errors (causes).
if len(el) > 0 {
// Adjust show options for deep formatting if not explicitly requested.
if !show.ShowDeepMetas() && show.ShowMetas() {
show &^= FormatShowMetas
show &^= FormatShowMetas // Turn off showing metas in deeper levels if not deep.
}
if !show.ShowDeepStacks() && show.ShowStack() {
show &^= FormatShowStack
show &^= FormatShowStack // Turn off showing stacks in deeper levels if not deep.
}
for _, ce := range el {
// Recursively call defaultFormatError for each cause, increasing the indentation level.
defaultFormatError(w, ce, opts, level+1, show)
}
}
// Format the stack trace if one was collected and formatting is enabled.
if stack != nil {
_, _ = fmt.Fprintf(w, opts.FmtStackH, indent)
_, _ = fmt.Fprintf(w, opts.FmtStackH, indent) // Format stack header.
// Format each line of the stack trace.
for _, l := range stack.value.([]string) {
_, _ = fmt.Fprintf(w, opts.FmtStackL, indent, l)
}
}
}
/* Descartado
func defaultFormatErrorRefactored(w io.Writer, currentErr error, opts *FormatOptions, level int, show FormatShow) {
if currentErr == nil {
return
}
// 1. Configuración de Indentación e Impresión del Mensaje del Error Actual
indent := strings.Repeat(opts.Indent, level)
if level == 0 {
_, _ = fmt.Fprintf(w, opts.FmtError, indent, currentErr.Error())
} else {
_, _ = fmt.Fprintf(w, opts.FmtCause, indent, currentErr.Error())
}
// 2. Recopilación y Visualización de Metadatos para currentErr y su cadena errors.Unwrap()
var stackTraceMeta *Meta // Para almacenar el stack trace si se encuentra.
var metadataToPrint []Meta // Para almacenar metadatos que no son de stack.
// Determinar qué metadatos seleccionar para el nivel actual.
metaSelectorForThisLevel := MetaSelectValue
if show.ShowStack() {
metaSelectorForThisLevel |= MetaSelectStack
}
// Si no se muestra la jerarquía, podríamos querer ver MetaTypeCause como metadato regular.
if !show.ShowHierarchy() {
metaSelectorForThisLevel |= MetaSelectCause
}
// Iterar sobre currentErr y su cadena errors.Unwrap() para obtener metadatos.
errNodeInChain := currentErr
for errNodeInChain != nil {
if provider, ok := errNodeInChain.(MetadataProvider); ok {
metasFromProvider := provider.Metadata(metaSelectorForThisLevel)
for _, m := range metasFromProvider {
if m.mType == MetaTypeStack {
// Capturar el primer stack trace si se solicita y aún no tenemos uno.
if stackTraceMeta == nil && show.ShowStack() {
stackTraceMeta = &m
}
continue // El stack trace se maneja por separado al final.
}
// Si se muestra la jerarquía, las causas se manejan por recursión.
// No mostrar MetaTypeCause como metadato regular en ese caso.
if m.mType == MetaTypeCause && show.ShowHierarchy() {
continue
}
metadataToPrint = append(metadataToPrint, m)
}
}
errNodeInChain = errors.Unwrap(errNodeInChain)
}
// Imprimir los metadatos recopilados (excluyendo el stack) si se solicita.
if show.ShowMetas() {
for _, m := range metadataToPrint {
_, _ = fmt.Fprintf(w, opts.FmtMeta, indent, m.name, m.value)
}
}
// 3. Recopilación de Causas Explícitas de currentErr para la Recursión
var explicitCauses []error
if show.ShowHierarchy() { // Solo recolectar si se va a mostrar la jerarquía.
switch cs := currentErr.(type) {
case Causer:
if cause := cs.Cause(); cause != nil {
explicitCauses = append(explicitCauses, cause)
}
case Causers:
causes := cs.Causes()
for _, cause := range causes { // Asegurarse de no añadir causas nil
if cause != nil {
explicitCauses = append(explicitCauses, cause)
}
}
case interface{ Unwrap() []error }: // Para compatibilidad con el original
unwrappedCauses := cs.Unwrap()
for _, cause := range unwrappedCauses {
if cause != nil {
explicitCauses = append(explicitCauses, cause)
}
}
}
}
// 4. Llamadas Recursivas para las Causas Explícitas
if len(explicitCauses) > 0 {
// Determinar las opciones de visualización para los niveles más profundos.
recursiveShowOptions := show
if !show.ShowDeepMetas() { // Si ShowDeepMetas es falso, no mostrar metas en niveles más profundos.
recursiveShowOptions &^= FormatShowMetas
}
if !show.ShowDeepStacks() { // Si ShowDeepStacks es falso, no mostrar stacks en niveles más profundos.
recursiveShowOptions &^= FormatShowStack
}
for _, causeToRecurse := range explicitCauses {
// La función se llama a sí misma para la causa.
defaultFormatErrorRefactored(w, causeToRecurse, opts, level+1, recursiveShowOptions)
}
}
// 5. Impresión del Stack Trace (si fue capturado y se solicitó para este nivel)
// Esto imprime el stack asociado a currentErr (o al primer wrapper que lo proporcionó).
if stackTraceMeta != nil && show.ShowStack() { // Asegurarse de que aún se desea mostrar el stack
_, _ = fmt.Fprintf(w, opts.FmtStackH, indent)
if stackLines, ok := stackTraceMeta.value.([]string); ok {
for _, line := range stackLines {
_, _ = fmt.Fprintf(w, opts.FmtStackL, indent, line)
}
} else if stackLine, ok := stackTraceMeta.value.(string); ok { // Manejar si el stack es una sola cadena
_, _ = fmt.Fprintf(w, opts.FmtStackL, indent, stackLine)
}
// Añadir un caso para otros posibles tipos de stackTraceMeta.value si es necesario.
}
}
*/

@ -1,79 +0,0 @@
// =============================================================================
// Active-GO Framework
// Copyright (c) 2025 ActiveThing (https://activething.com)
// Author: Juan V. Navarro juanvnl@activething.com
// =============================================================================
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
// =============================================================================
// Date Create 18/05/2025
// =============================================================================
package errs
/*
// Basic ANSI color codes
const (
ColorBlack = "\033[30m"
ColorRed = "\033[31m"
ColorGreen = "\033[32m"
ColorYellow = "\033[33m"
ColorBlue = "\033[34m"
ColorMagenta = "\033[35m"
ColorCyan = "\033[36m"
ColorWhite = "\033[37m"
ColorBrightBlack = "\033[90m"
ColorBrightRed = "\033[91m"
ColorBrightGreen = "\033[92m"
ColorBrightYellow = "\033[93m"
ColorBrightBlue = "\033[94m"
ColorBrightMagenta= "\033[95m"
ColorBrightCyan = "\033[96m"
ColorBrightWhite = "\033[97m"
BgBlack = "\033[40m"
BgRed = "\033[41m"
BgGreen = "\033[42m"
BgYellow = "\033[43m"
BgBlue = "\033[44m"
BgMagenta = "\033[45m"
BgCyan = "\033[46m"
BgWhite = "\033[47m"
BgBrightBlack = "\033[100m"
BgBrightRed = "\033[101m"
BgBrightGreen = "\033[102m"
BgBrightYellow = "\033[103m"
BgBrightBlue = "\033[104m"
BgBrightMagenta = "\033[105m"
BgBrightCyan = "\033[106m"
BgBrightWhite = "\033[107m"
TextBold = "\033[1m" // Bold text
TextFaint = "\033[2m" // Faint text (dim)
TextUnderline = "\033[4m" // Underlined text
TextReverse = "\033[7m" // Swap foreground and background colors
ColorReset = "\033[0m" // Reset all attributes
)
*/

@ -29,7 +29,7 @@
package errs
/*
// Basic ANSI color codes
// Basic ANSI color codes (Commented out, but values used in NewFormatOptionsColor)
const (
ColorBlack = "\033[30m"
ColorRed = "\033[31m"
@ -81,20 +81,30 @@ const (
*/
type (
// FormatOptions defines the options for controlling how errors are formatted.
FormatOptions struct {
Show FormatShow
// Show specifies which parts of the error (hierarchy, metas, stack) should be displayed.
Show FormatShow
// EnableColor indicates whether ANSI color codes should be included in the output.
EnableColor bool
// Indent is the string used for each level of indentation in the formatted output.
Indent string
FmtError string
FmtMeta string
FmtCause string
// FmtError is the format string used for the root error message.
FmtError string
// FmtMeta is the format string used for displaying metadata.
FmtMeta string
// FmtCause is the format string used for displaying wrapped error messages (causes).
FmtCause string
// FmtStackH is the format string used for the stack trace header.
FmtStackH string
// FmtStackL is the format string used for each line of the stack trace.
FmtStackL string
}
)
// NewFormatOptions creates a new FormatOptions instance with default plain text formatting.
func NewFormatOptions() *FormatOptions {
return &FormatOptions{
Indent: " ",
@ -106,14 +116,16 @@ func NewFormatOptions() *FormatOptions {
}
}
// NewFormatOptionsColor creates a new FormatOptions instance with default ANSI color formatting.
func NewFormatOptionsColor() *FormatOptions {
return &FormatOptions{
Indent: " ",
EnableColor: true,
FmtError: "%s\033[31m%s\033[0m\n",
FmtMeta: "%s\033[2m * %s: %v\033[0m\n",
FmtCause: "%s\033[33m%s\033[0m\n",
FmtStackH: "%s\033[37m * stack:\033[0m\n",
FmtStackL: "%s\033[37m %s\033[0m\n",
// Format strings include ANSI color codes for different parts of the error output.
FmtError: "%s\033[31m%s\033[0m\n", // Red for the main error message
FmtMeta: "%s\033[2m * %s: %v\033[0m\n", // Faint/Dim for metadata
FmtCause: "%s\033[33m%s\033[0m\n", // Yellow for wrapped error messages (causes)
FmtStackH: "%s\033[37m * stack:\033[0m\n", // White for stack header
FmtStackL: "%s\033[37m %s\033[0m\n", // White for stack lines
}
}

@ -18,49 +18,60 @@
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
// =============================================================================
// Date Create 18/05/2025
// =============================================================================
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n// THE SOFTWARE.\n//\n// =============================================================================\n// Date Create 18/05/2025\n// =============================================================================
package errs
import "fmt"
const (
FormatShowHierarchy FormatShow = 1 << iota // Muestra la jerarquía de errores
FormatShowMetas // Muestra metadatos del error actual
// FormatShowHierarchy is a flag to show the error hierarchy (wrapped errors/causes).
FormatShowHierarchy FormatShow = 1 << iota
// FormatShowMetas is a flag to show metadata of the current error in the hierarchy.
FormatShowMetas
// FormatShowDeepMetas is a flag to show metadata for all errors in the hierarchy.
FormatShowDeepMetas
FormatShowStack // Muestra el stack del error raíz
FormatShowDeepStacks // Muestra los stacks de todos los errores en la jerarquía si se ha definido
// FormatShowStack is a flag to show the stack trace of the root error.
FormatShowStack
// FormatShowDeepStacks is a flag to show stack traces for all errors in the hierarchy if defined.
FormatShowDeepStacks
// FormatShowNone is a flag to show nothing extra (only the basic error message).
FormatShowNone FormatShow = 0
)
// FormatShow is a bitmask type used to control which parts of an error are displayed during formatting.
type FormatShow uint8
// ShowMetas returns true if the FormatShow includes the FormatShowMetas flag.
func (o FormatShow) ShowMetas() bool { return o&FormatShowMetas != 0 }
// ShowHierarchy returns true if the FormatShow includes the FormatShowHierarchy flag.
func (o FormatShow) ShowHierarchy() bool { return o&FormatShowHierarchy != 0 }
// ShowDeepMetas returns true if the FormatShow includes the FormatShowDeepMetas flag.
func (o FormatShow) ShowDeepMetas() bool { return o&FormatShowDeepMetas != 0 }
// ShowStack returns true if the FormatShow includes the FormatShowStack flag.
func (o FormatShow) ShowStack() bool { return o&FormatShowStack != 0 }
// ShowDeepStacks returns true if the FormatShow includes the FormatShowDeepStacks flag.
func (o FormatShow) ShowDeepStacks() bool { return o&FormatShowDeepStacks != 0 }
// ShowOptionsFromVerb derives FormatShow options based on the fmt.State and verb.
// This function interprets standard fmt verbs and flags to determine what to display.
func ShowOptionsFromVerb(f fmt.State, verb rune) FormatShow {
s := FormatShowNone
switch verb {
case 's', 'q':
// For %s and %q, show nothing extra.
return s
case 'v':
// For %v, check flags to determine what to show.
if f.Flag('+') {
// %+v → Mostrar jerarquía, metas y stack completo
// %+v -> Show hierarchy, metas, and full stack
s |= FormatShowHierarchy |
FormatShowMetas |
FormatShowDeepMetas |
@ -68,12 +79,12 @@ func ShowOptionsFromVerb(f fmt.State, verb rune) FormatShow {
FormatShowDeepStacks
} else if f.Flag('#') {
// %#v → Solo metas y stack del error principal
// %#v -> Show metas and stack of the principal error
s |= FormatShowMetas |
FormatShowStack
} else {
// %v → Solo jerarquía y stack principal (opcional según tu criterio)
// %v -> Show hierarchy and principal stack (optional based on your criterion)
s |= FormatShowHierarchy |
FormatShowStack
}

@ -18,29 +18,28 @@
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
// =============================================================================
// Date Create 16/05/2025
// =============================================================================
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n// THE SOFTWARE.\n//\n// =============================================================================\n// Date Create 16/05/2025\n// =============================================================================
package errs
type (
// Meta represents a single piece of metadata associated with an error.
// It consists of a name (key), a value, and a type.
Meta struct {
name string
value any
mType MetaType
name string // The name or key of the metadata (e.g., "userId", "fileName")
value any // The value associated with the metadata (can be of any type)
mType MetaType // The type of the metadata value
}
)
// CreateMeta creates a new Meta instance.
// It validates the metadata name and type.
// Panics if the name or type are invalid.
func CreateMeta(name string, value any, metatype MetaType) Meta {
//El uso de panic responde a que la construcción de estas estructuras debe garantizarse
// antes de que el programa corra. Un error en el nombre es indicativo de un fallo en la
// lógica o en la definición estática, por lo que no tiene sentido manejarlo con un error
// que pueda ser ignorado o pospuesto.
// The use of panic here indicates that the construction of these structures must be guaranteed
// before the program runs. An error in the name or type is indicative of a failure in the
// logic or static definition, so it doesn't make sense to handle it with an error
// that could be ignored or postponed.
if !IsValidMetaName(name) {
panic(E.InvalidMetaName)
}
@ -50,14 +49,19 @@ func CreateMeta(name string, value any, metatype MetaType) Meta {
return Meta{name, value, metatype}
}
// Name returns the name (key) of the metadata.
func (m Meta) Name() string { return m.name }
// Value returns the value of the metadata.
func (m Meta) Value() any { return m.value }
// Type returns the type of the metadata value.
func (m Meta) Type() MetaType { return m.mType }
// IsValidMetaName checks if a string is a valid metadata name according to the rules defined by metaValidator.
func IsValidMetaName(meta string) bool {
return len(meta) > 0 && g.metaValidator.MatchString(meta)
}
// IsValidMetaType checks if a MetaType value is valid.
func IsValidMetaType(metatype MetaType) bool { return metatype < MetaTypeInvalid }

@ -29,16 +29,25 @@
package errs
const (
// MetaSelectValue is a flag to select general value metadata.
MetaSelectValue MetaSelector = 1 << iota
// MetaSelectCause is a flag to select cause metadata.
MetaSelectCause
// MetaSelectStack is a flag to select stack trace metadata.
MetaSelectStack
MetaSelectAll = 7
// MetaSelectAll is a flag to select all types of metadata.
MetaSelectAll = 7 // Combination of all selection flags
)
// MetaSelector is a bitmask type used to select which types of metadata to retrieve.
type MetaSelector uint8
// SelectValue returns true if the MetaSelector includes the MetaSelectValue flag.
func (s MetaSelector) SelectValue() bool { return s&MetaSelectValue != 0 }
// SelectCause returns true if the MetaSelector includes the MetaSelectCause flag.
func (s MetaSelector) SelectCause() bool { return s&MetaSelectCause != 0 }
// SelectStack returns true if the MetaSelector includes the MetaSelectStack flag.
func (s MetaSelector) SelectStack() bool { return s&MetaSelectStack != 0 }

@ -29,32 +29,35 @@
package errs
const (
// Scalar Types
MetaTypeAny MetaType = iota // 0 - Any type (fallback)
MetaTypeString // UTF-8 string
MetaTypeInt // Signed integer (64-bit)
MetaTypeUInt // Unsigned integer (64-bit)
MetaTypeFloat // Floating-point number (64-bit)
MetaTypeBool // Boolean
MetaTypeBytes // Arbitrary binary data (base64 in serialization)
MetaTypeTime // Time (RFC3339 / Unix)
MetaTypeDuration // Duration
// Escalares
MetaTypeAny MetaType = iota // 0
MetaTypeString // UTF-8
MetaTypeInt // Número entero con signo (64 bits)
MetaTypeUInt // Número entero sin signo (64 bits)
MetaTypeFloat // Número decimal (64 bits)
MetaTypeBool // Booleano
MetaTypeBytes // Binario arbitrario (base64 en serialización)
MetaTypeTime // Tiempo (RFC3339 / Unix)
MetaTypeDuration // Duración
// Composite Types
MetaTypeList // Ordered list of elements
MetaTypeMap // Dynamic key -> value map
MetaTypeObject // Defined fields (fixed structure)
// Compuestos
MetaTypeList // Lista ordenada de elementos
MetaTypeMap // Clave dinámica → valor
MetaTypeObject // Campos definidos (estructura fija)
// Special Types
MetaTypeCause // Represents an error cause
MetaTypeStack // Represents a stack trace
// Tipos especiales
MetaTypeCause
MetaTypeStack
MetaTypeInvalid
MetaTypeInvalid // Represents an invalid metadata type
)
// MetaType represents the type of a metadata value.
type MetaType uint8
// String returns the string representation of the MetaType.
func (t MetaType) String() string {
switch t {
case MetaTypeString:

@ -29,11 +29,18 @@
package errs
type (
// MetadataProvider is an interface implemented by types that can provide metadata.
// Composed error types typically implement this interface.
MetadataProvider interface {
// Metadata returns a slice of Meta objects based on the provided selector.
Metadata(selector MetaSelector) []Meta
}
// MetadataFnc is a function type that implements the MetadataProvider interface.
// It allows an anonymous function or an existing function to act as a metadata provider.
MetadataFnc func(selector MetaSelector) []Meta
)
// Metadata implements the MetadataProvider interface for MetadataFnc, executing the underlying function.
func (f MetadataFnc) Metadata(selector MetaSelector) []Meta { return f(selector) }

@ -29,23 +29,33 @@
package errs
const (
// errPanic is the error class name used to represent panics converted to errors.
errPanic = moduleName + "panic"
)
type (
// PanicErr is an error type that wraps a recovered panic.
// It allows treating a panic as a structured error.
PanicErr struct {
Err
cause error
Err // Embeds the Err type to inherit its methods and basic structure.
cause error // cause stores the recovered panic value, converted to an error type.
}
)
// CreatePanicErr creates a new PanicErr instance from a recovered panic value.
// It converts the panic value to an error type using ToError.
func CreatePanicErr(cause any) PanicErr {
return PanicErr{
Err: CreateErr(errPanic),
cause: ToError(cause),
Err: CreateErr(errPanic), // Creates a base error with the "panic" class.
cause: ToError(cause), // Converts the panic value to an error.
}
}
// Cause implements the Causer interface for PanicErr.
// It returns the underlying error representing the panic value.
func (e PanicErr) Cause() error { return e.cause }
// Unwrap implements Go 1.13+'s errors.Unwrap interface.
// It returns the underlying error representing the panic value, allowing
// the PanicErr to be unwrapped to access the original cause.
func (e PanicErr) Unwrap() error { return e.cause }

@ -64,15 +64,15 @@ func CreateParamErr(name string, value any) ParamErr {
}
func CreateParamNilErr(name string) ParamErr {
return createParamErr(Err(errParamNil), name, nil)
return createParamErr(Err(errParamNil),name, nil)
}
func CreateParamEmptyErr(name string) ParamErr {
return createParamErr(Err(errParamEmpty), name, nil)
return createParamErr(Err(errParamEmpty),name,"")
}
func CreateParamRange(name string, value any) ParamErr {
return createParamErr(Err(errParamRange), name, value)
return createParamErr(Err(errParamRange),name,value)
}
func CreateParamRangeMin(name string, value any) ParamErr {
@ -83,6 +83,9 @@ func CreateParamRangeMax(name string, value any) ParamErr {
return createParamErr(Err(errParamRangeMax), name, value)
}
func (e ParamErr) ParamName() string { return e.name }
func (e ParamErr) ParamValue() any { return e.value }

@ -0,0 +1,181 @@
# **errs: Gestión de Errores Estructurada para Go (Parte de Active-Go Core)**
errs es un módulo fundamental dentro de la librería core de Active-Go, diseñado para transformar la forma en que manejamos los errores en aplicaciones Go, especialmente en entornos distribuidos y arquitecturas de microservicios. Va más allá de la simplicidad del tipo error nativo de Go, ofreciendo una estructura enriquecida que facilita la **clasificación, composición y contextualización** de los errores.
## **Filosofía**
La filosofía central de errs se basa en la experiencia de desarrollar y operar sistemas complejos en ActiveThing.com. Nace de la necesidad de superar las limitaciones del error básico de Go para obtener:
* **Errores Estructurados y Clasificados:** Evita la frágil comparación de cadenas. Permite definir **jerarquías de errores** (clases y subclases) para una identificación precisa y un manejo más granular. Esto significa que podemos identificar un error como, por ejemplo, servicio::tipo\_negocio.subtipo\_específico y tomar decisiones lógicas basadas en su clasificación, no solo en su mensaje.
* **Composición Flexible y Contexto Enriquecido:** Un error rara vez ocurre de forma aislada. errs facilita la adición de **información contextual relevante** a los errores existentes sin modificar su tipo subyacente. A través del patrón de **"compositor"**, un error puede ser "envuelto" con metadatos cruciales como códigos, causas, información de usuario, detalles de reintentos, rastreos de pila, etc. Esto construye una **historia completa del error** a medida que se propaga por el sistema.
* **Trazabilidad Unificada en Entornos Distribuidos:** En microservicios, los errores viajan. errs proporciona un **lenguaje común para los errores** a través de distintos servicios. Un error que se origina en un servicio de autenticación y se propaga a un servicio de pedidos, y luego a un API Gateway, puede llevar consigo su contexto original y añadir metadatos de cada punto que atraviesa. Esto es vital para la depuración y el monitoreo en cascada.
* **Visibilidad Enriquecida para la Operación:** Simplifica la depuración y el diagnóstico en sistemas complejos. errs ofrece capacidades de formato avanzadas, incluyendo la visualización de jerarquías de errores, metadatos y rastreos de pila, lo que convierte un simple log en una herramienta de diagnóstico potente.
* **Inmutabilidad y Predictibilidad:** Los errores, una vez creados o compuestos, son inmutables. Esto contribuye a un comportamiento predecible y reduce los efectos secundarios inesperados en el manejo de errores.
* **Errores como Datos:** Al adjuntar **metadatos tipificados** a los errores, errs promueve la idea de que los errores son objetos de datos con información valiosa que puede ser utilizada programáticamente por la lógica de la aplicación, los sistemas de registro o las herramientas de monitoreo, en lugar de ser solo mensajes opacos.
## **Modo de Uso**
errs introduce el tipo **Err** como la base para errores estructurados. Los errores pueden crearse, clasificarse y enriquecerse con diversos metadatos utilizando las funciones y tipos proporcionados.
### **Creación de Errores Básicos**
Puedes crear un error base usando CreateErr. Los errores pueden tener una estructura de módulo y clase, por ejemplo, modulo::clase:
import "core/errs"
var (
ErrNotFound \= errs.CreateErr("app::not\_found")
ErrInvalidInput \= errs.CreateErr("app::invalid\_input")
)
### **Creación de Subclases de Errores**
Para crear jerarquías de errores, puedes definir subclases. Esto es útil para errores más específicos dentro de una categoría general:
import "core/errs"
var (
ErrUserNotFound \= errs.CreateSubclassOf(ErrNotFound, "user")
ErrProductNotFound \= errs.CreateSubclassOf(ErrNotFound, "product")
)
// Puedes verificar si un error es subclase de otro
if errs.IsSubclassOf(err, ErrNotFound) {
// Es un error de tipo "no encontrado", ya sea de usuario o producto.
}
### **Composición de Errores con Metadatos**
El patrón clave de errs es la **composición de errores**. Utiliza las funciones WithX o ComposeWithX para añadir información relevante sin cambiar el tipo base del error. Esto permite construir un error con todos los detalles necesarios:
import (
"core/errs"
"errors"
"fmt"
"time"
)
// Añadir un mensaje detallado al error
err := errs.ComposeWithMessage(ErrInvalidInput, "El campo '%s' es obligatorio", "nombre\_usuario")
// Añadir una causa subyacente (el error original que provocó este)
originalDBErr := errors.New("conexión a la base de datos perdida")
errWithCause := errs.ComposeWithCause(errs.CreateErr("db::internal\_error"), originalDBErr)
// Composición encadenada para construir un error con múltiples capas de contexto
complexErr := errs.Compose(
ErrUserNotFound,
errs.WithMessage("Usuario con ID %d no encontrado en el sistema principal", 12345),
errs.WithCode(404), // Código de aplicación o HTTP
errs.WithDate(), // Fecha y hora del error
errs.WithStack(), // Rastreo de pila en el punto de composición (requiere core/excs)
errs.WithRetryable(errs.RetryableCanRetry|errs.RetryableIsTemporary, 5\*time.Second), // Marcado como reintentable
errs.WithInfo("Este error indica que el usuario no existe", "Verifique el ID de usuario o el servicio de autenticación", "https://docs.active-go.com/errors/user-404"),
errs.WithOrigin("user\_service", "instance\_alpha"), // Origen del error
errs.WithObservedBy("api\_gateway"), // Dónde fue observado o modificado
// Puedes añadir metadatos genéricos:
errs.WithMeta("trace\_id", "abc123xyz"),
errs.WithMetas(
errs.CreateMeta("http\_status", 500, errs.MetaTypeInt),
errs.CreateMeta("is\_critical", true, errs.MetaTypeBool),
),
)
### **Acceso a Metadatos**
Puedes acceder a los metadatos de un error mediante la conversión de tipo o interfaces, lo que permite una lógica de manejo de errores más sofisticada:
if ewc, ok := complexErr.(errs.ErrorWithCode); ok {
fmt.Printf("Código de error: %d\\n", ewc.Code())
}
if ewc, ok := complexErr.(errs.ErrorWithCause); ok {
fmt.Printf("Causa: %v\\n", ewc.Cause())
}
if ewst, ok := complexErr.(errs.ErrorWithStack); ok {
fmt.Printf("Stack: %v\\n", ewst.Stack()) // Acceso al stack trace
}
// Acceso a metadatos genéricos
if ewm, ok := complexErr.(errs.ErrorWithMeta); ok {
if ewm.Name() \== "trace\_id" {
fmt.Printf("Trace ID: %v\\n", ewm.Value())
}
}
// Acceso a múltiples metadatos genéricos
if ewms, ok := complexErr.(errs.ErrorWithMetas); ok {
for \_, meta := range ewms.Metas() {
fmt.Printf("Meta: %s \= %v (Tipo: %s)\\n", meta.Name(), meta.Value(), meta.Type().String())
}
}
### **Formato de Errores para Depuración y Logs**
errs mejora significativamente la capacidad de formato de errores de Go. Implementa fmt.Formatter, permitiendo usar verbos especiales para controlar la cantidad de detalle en la salida:
fmt.Printf("Error básico: %v\\n", complexErr) // Muestra jerarquía y stack principal
fmt.Printf("Error estructurado: %s\\n", complexErr) // Muestra solo la clase del error (string del Err base)
fmt.Printf("Error detallado: %+v\\n", complexErr) // Muestra jerarquía completa, todos los metadatos y stack completo (recomendado para logs)
fmt.Printf("Error para depuración: %\#v\\n", complexErr) // Muestra metadatos del error actual y stack principal (útil para depuración)
Las opciones de formato se controlan mediante el tipo FormatShow y los modificadores de fmt.State, permitiendo incluso salida con **colores** en terminales compatibles para mejorar la legibilidad.
### **Manejo de Listas de Errores**
ErrorList es una colección útil para manejar múltiples errores, común en escenarios donde operaciones paralelas o validaciones acumulan fallos:
var errors errs.ErrorList
errors \= errors.Append(someValidationErr)
errors \= errors.AppendNotNil(anotherAPIError)
if errors.Any() { // Verifica si hay al menos un error no nulo
fmt.Printf("Se encontraron %d errores: %v\\n", errors.Count(), errors.Clean()) // Count() y Clean()
firstErr := errors.First() // Accede al primer error no nulo
lastErr := errors.Last() // Accede al último error no nulo
// ... otras operaciones con la lista
}
## **Funcionalidades Clave**
* **Implementación de Interfaces Estándar de Go:** errs se integra perfectamente con el ecosistema Go al implementar interfaces clave como error, fmt.Formatter, errors.Is, y errors.Unwrap.
* **Err Tipo Base:** Un tipo de cadena (string) que representa un error estructurado, permitiendo la definición de módulos y clases de errores (e.g., modulo::clase o modulo::clase.subclase).
* **Jerarquía de Errores:**
* CreateErr(value string): Crea un nuevo error con una clase y opcionalmente un módulo.
* CreateSubclassOf(parent Err, subclass string): Permite crear una subclase de un error existente, estableciendo una relación jerárquica (e.g., app::not\_found.user).
* IsSubclassOf(err, parent error): Verifica si un error es una subclase de otro, facilitando el manejo polimórfico de errores.
* Parent() (Err, bool): Devuelve el error padre en la jerarquía, si existe.
* Class(): Devuelve el nombre de la clase del error.
* Module(): Devuelve el nombre del módulo del error.
* **Composición de Errores (Composer):**
* Un patrón de diseño que permite añadir características o metadatos a un error existente sin modificar su tipo original.
* Funciones WithX y ComposeWithX para "envolver" un error con información adicional de forma aditiva.
* **Tipos de Composición de Errores:** errs provee una variedad de tipos que implementan error y añaden contexto específico:
* ErrorWithCause: Adjunta una causa subyacente al error (similar a errors.Unwrap).
* ErrorWithCauses: Permite adjuntar múltiples causas a un error (implementa el patrón interface{ Unwrap() \[\]error } de Go 1.20+).
* ErrorWithCode: Añade un código numérico al error (ej. código de negocio, HTTP).
* ErrorWithDate: Incluye la fecha y hora de creación del error.
* ErrorWithInfo: Proporciona una descripción, ayuda y un enlace a documentación para el error.
* ErrorWithMessage: Permite añadir un mensaje descriptivo más detallado al error, incluso con formato de cadena.
* ErrorWithMeta / ErrorWithMetas: Adjunta metadatos genéricos de clave-valor (Meta) al error. ErrorWithMeta usa MetaTypeAny por defecto, mientras que ErrorWithMetas permite especificar tipos más concretos si se crean objetos Meta directamente.
* ErrorObservedBy: Registra qué componente o servicio observó el error. Crucial para la trazabilidad en microservicios.
* ErrorWithOrigin: Indica el servicio y opcionalmente la instancia donde el error se originó.
* ErrorWithRetryable: Marca un error como reintentable, especificando si es temporal, si es idempotente y el tiempo de reintento sugerido.
* ErrorWithSeverity: Asigna un nivel de severidad al error (e.g., Info, Warn, Error, Fatal).
* ErrorWithStack: Captura el rastreo de pila en el punto de composición/creación del error. **Depende del paquete core/excs**.
* **ErrorList:** Una colección útil para manejar múltiples errores. Proporciona métodos como Any(), Len(), Count(), First(), Last(), Append(), AppendNotNil(), Reset(), y Clean() para una gestión conveniente.
* **MetadataProvider:** Una interfaz implementada por los errores compuestos que permite extraer los metadatos asociados de forma programática, controlando la selección con MetaSelector.
* **Formato Personalizado (fmt.Formatter):**
* Implementa la interfaz fmt.Formatter para un formato de error avanzado y configurable.
* Soporte para verbos de formato (%v, %+v, %\#v) para controlar la profundidad y el tipo de información mostrada (jerarquía, metadatos, stack traces).
* Opciones de formato configurables (FormatOptions), incluyendo la posibilidad de habilitar **colores ANSI** en la salida de la consola para una mejor legibilidad.
* **Validación Estricta:** Utiliza expresiones regulares para validar los nombres de los errores y los metadatos, asegurando que sigan un formato consistente en todo el sistema y previniendo errores estáticos.
* **Manejo de Errores Estándar:** ToError(err any) convierte una variedad de tipos (incluyendo nil, error, \[\]error) en un tipo error estándar.
* **Errores Predefinidos:** Proporciona errores comunes como E.Multiple, E.InvalidNilError, E.InvalidClass, etc., para errores internos del propio módulo errs.
* **Manejo de Panics:** PanicErr y CreatePanicErr proporcionan una forma estructurada de convertir un panic recuperado en un error manejable, adjuntando la causa original.
* **Errores de Parámetros:** Una serie de errores específicos (ParamErr, CreateParamNilErr, CreateParamEmptyErr, CreateParamRange) para indicar problemas con los parámetros de las funciones, estandarizando estos errores comunes de entrada.
### **Instalación**
errs es parte de la librería core de Active-Go. Para utilizarlo, deberás incluir la librería Active-Go Core en las dependencias de tu proyecto. Consulta la documentación principal de Active-Go Core para obtener instrucciones detalladas sobre cómo añadirla a tu proyecto Go.

@ -29,19 +29,32 @@
package errs
const (
// SeverityDebug indicates a severity level for debugging information.
SeverityDebug Severity = 1 << iota
// SeverityInfo indicates a severity level for general information.
SeverityInfo
// SeverityWarn indicates a severity level for warnings.
SeverityWarn
// SeverityError indicates a severity level for errors affecting functionality.
SeverityError
// SeverityCritical indicates a severity level for critical errors requiring immediate attention.
SeverityCritical
// SeverityFatal indicates a severity level for errors causing application termination.
SeverityFatal
)
// Severity represents the severity level of an error.
// It is used to classify the importance of an error.
type Severity uint8
// String returns the string representation of the severity level.
// Returns "UNKNOWN" for undefined severity values.
func (s Severity) String() string {
// Checks if the severity value is within the expected range.
if s <= 0 || s > SeverityFatal {
return "UNKNOWN"
}
return [...]string{"DEBUG", "INFO", "WARNING", "ERROR", "FATAL", "CRITICAL"}[s]
// Uses a string slice to map the severity value to its name.
// Note: Index 0 is not used as severity values start from 1 (1 << 0).
return [...]string{"DEBUG", "INFO", "WARNING", "ERROR", "FATAL", "CRITICAL"}[s-1] // Corrected index
}

Loading…
Cancel
Save

Powered by TurnKey Linux.