commit b91e6546afd4eefbad0cb90bab4bd9f5cf7a71b3 Author: dev Date: Thu Jan 22 14:14:28 2026 +0100 Errs module diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/FWS.iml b/.idea/FWS.iml new file mode 100644 index 0000000..5e764c4 --- /dev/null +++ b/.idea/FWS.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/dictionaries/project.xml b/.idea/dictionaries/project.xml new file mode 100644 index 0000000..a49d483 --- /dev/null +++ b/.idea/dictionaries/project.xml @@ -0,0 +1,7 @@ + + + + segs + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..40f091e --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/apps/apperrs/go.mod b/src/apps/apperrs/go.mod new file mode 100644 index 0000000..f013d18 --- /dev/null +++ b/src/apps/apperrs/go.mod @@ -0,0 +1,3 @@ +module apperrs + +go 1.25 diff --git a/src/apps/apperrs/main.go b/src/apps/apperrs/main.go new file mode 100644 index 0000000..6bc0deb --- /dev/null +++ b/src/apps/apperrs/main.go @@ -0,0 +1,86 @@ +/* —————————————————————————————————————————————————————————————————————————— + Proyecto FWS +————————————————————————————————————————————————————————————————————————————— + Fichero main.go + Package apperrs + Autor Juan V. Navarro juanvnl@activething.com + Creado 07/01/2026 +————————————————————————————————————————————————————————————————————————————— + + LICENSES AND TERMS OF USE + ------------------------- + + This software is licensed under the Elastic License v2.0 (the "License"). + For full terms and additional information regarding permitted and prohibited + uses, please visit: + https://activething.com/ATGO/licenses + + You may use, copy, modify, and redistribute this software internally within + your organization for any purpose, including research, development, and + testing, subject to the terms of this License. + + You may NOT, however, use, provide, distribute, or make this software + available to any third party as part of a hosted service, SaaS offering, or + commercial product without first obtaining a commercial license from + Active Thing. + + You may combine this software with other code, provided that such + combination does not circumvent the restrictions of this License. + + 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 NON-INFRINGEMENT. In no event shall + the authors or copyright holders be liable for any claim, damages, or other + liability arising from the use of this software. + +————————————————————————————————————————————————————————————————————————————— + + Web : activething.com | activething.com/FWS + git : g.activething.com | github.com/activething/FWS + Correo : dev@activething.com + +————————————————————————————————————————————————————————————————————————————— + No deseo caminar sobre el agua", dijo Siddhartha. + Que los antiguos chamanes se contenten con tales habilidades. + —— Hermann Hesse, Siddhartha +————————————————————————————————————————————————————————————————————————————— + Copyright (c) 2026 Active Thing +————————————————————————————————————————————————————————————————————————————— */ + +package main + +import ( + "errors" + "errs" + "fmt" + "os" +) + +func main() { + baseErr := errs.Code("auth::err_auth") + + err := errs.WithMessage( + errs.WithCause( + errs.WithSeverity( + errs.WithStack(baseErr), + errs.SeverityCritical, + ), + errors.New("db error"), + ), + "authentication failed", + ) + + errs.NewTree(err).Print(os.Stdout) + + otro := errs.Add( + errs.Code("::Mi_otro_error"), + errs.AddMessage("Esto es mi mengage"), + errs.AddUser("usuarion_anonimo"), + errs.AddStack(), + errs.AddCauses(errs.Code("::error_cause"))) + + errs.NewTree(otro).Print(os.Stdout) + + fmt.Printf("%#v\n", errs.NewTree(err)) + +} diff --git a/src/core/errs/a.go b/src/core/errs/a.go new file mode 100644 index 0000000..5635455 --- /dev/null +++ b/src/core/errs/a.go @@ -0,0 +1,141 @@ +/* —————————————————————————————————————————————————————————————————————————— + Proyecto FWS +————————————————————————————————————————————————————————————————————————————— + Fichero a.go + Package errs + Autor Juan V. Navarro juanvnl@activething.com + Creado 06/01/2026 +————————————————————————————————————————————————————————————————————————————— + + LICENSES AND TERMS OF USE + ------------------------- + + This software is licensed under the Elastic License v2.0 (the "License"). + For full terms and additional information regarding permitted and prohibited + uses, please visit: + https://activething.com/ATGO/licenses + + You may use, copy, modify, and redistribute this software internally within + your organization for any purpose, including research, development, and + testing, subject to the terms of this License. + + You may NOT, however, use, provide, distribute, or make this software + available to any third party as part of a hosted service, SaaS offering, or + commercial product without first obtaining a commercial license from + Active Thing. + + You may combine this software with other severity, provided that such + combination does not circumvent the restrictions of this License. + + 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 NON-INFRINGEMENT. In no event shall + the authors or copyright holders be liable for any claim, damages, or other + liability arising from the use of this software. + +————————————————————————————————————————————————————————————————————————————— + + Web : activething.com | activething.com/FWS + git : g.activething.com | github.com/activething/FWS + Correo : dev@activething.com + +————————————————————————————————————————————————————————————————————————————— + No deseo caminar sobre el agua", dijo Siddhartha. + Que los antiguos chamanes se contenten con tales habilidades. + —— Hermann Hesse, Siddhartha +————————————————————————————————————————————————————————————————————————————— + Copyright (c) 2026 Active Thing +————————————————————————————————————————————————————————————————————————————— */ + +package errs + +import ( + "errors" + "fmt" + "regexp" +) + +const ( + RootModule Module = Module("") + ErrsModule Module = Module("errs") +) + +var ( + + // Global sentinels (no module prefix) + ErrNilError = RootModule.Code("nil-error") + ErrNilCause = RootModule.Code("nil-cause") + + // Internal sentinels (errs module) + ErrInvalidModule = ErrsModule.Code("invalid-module") + ErrInvalidCode = ErrsModule.Code("invalid-severity") + ErrInvalidMetaName = ErrsModule.Code("invalid-meta-name") + ErrInvalidParamName = ErrsModule.Code("invalid-param-name") + + paramRgx = regexp.MustCompile(`^[a-zA-Z][a-zA-Z0-9_]{0,62}[a-zA-Z0-9]$`) + metaRgx = regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_-]{0,62}[a-zA-Z0-9_]$`) + codeRgx = regexp.MustCompile(`^[a-zA-Z0-9](?:[a-zA-Z0-9_-]*[a-zA-Z0-9])?$`) + severityName = []string{"UNKNOWN", "DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL", "FATAL"} +) + +func isValidSegment(code string) bool { return len(code) > 0 && codeRgx.MatchString(code) } + +func ToError(err any) error { + if err == nil { + return nil + } + switch t := err.(type) { + case error: + return t + case []error: + return MakeMultiError(t) + default: + return fmt.Errorf("%v", err) + } +} + +func ToString(err error) string { + if err == nil { + return "" + } + switch e := err.(type) { + case Code: + return string(e) + case *Code: + return string(*e) + case fmt.Stringer: + return e.String() + default: + return err.Error() + } +} + +func Matches(err, target error) bool { + var t Code + if !errors.As(target, &t) { + return false + } + + // Itera por toda la cadena de unwrap + for e := err; e != nil; e = errors.Unwrap(e) { + var c Code + if errors.As(e, &c) && c.IsOrFrom(t) { + return true + } + } + return false +} + +func IsSubOf(parent, err error) bool { + var p Code + if !errors.As(parent, &p) { + return false + } + for e := err; e != nil; e = errors.Unwrap(e) { + var c Code + if errors.As(e, &c) && c.IsSubOf(p) { + return true + } + } + return false +} diff --git a/src/core/errs/adder.go b/src/core/errs/adder.go new file mode 100644 index 0000000..ac2d171 --- /dev/null +++ b/src/core/errs/adder.go @@ -0,0 +1,80 @@ +/* —————————————————————————————————————————————————————————————————————————— + Proyecto FWS +————————————————————————————————————————————————————————————————————————————— + Fichero adder.go + Package errs + Autor Juan V. Navarro juanvnl@activething.com + Creado 16/01/2026 +————————————————————————————————————————————————————————————————————————————— + + LICENSES AND TERMS OF USE + ------------------------- + + This software is licensed under the Elastic License v2.0 (the "License"). + For full terms and additional information regarding permitted and prohibited + uses, please visit: + https://activething.com/ATGO/licenses + + You may use, copy, modify, and redistribute this software internally within + your organization for any purpose, including research, development, and + testing, subject to the terms of this License. + + You may NOT, however, use, provide, distribute, or make this software + available to any third party as part of a hosted service, SaaS offering, or + commercial product without first obtaining a commercial license from + Active Thing. + + You may combine this software with other severity, provided that such + combination does not circumvent the restrictions of this License. + + 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 NON-INFRINGEMENT. In no event shall + the authors or copyright holders be liable for any claim, damages, or other + liability arising from the use of this software. + +————————————————————————————————————————————————————————————————————————————— + + Web : activething.com | activething.com/FWS + git : g.activething.com | github.com/activething/FWS + Correo : dev@activething.com + +————————————————————————————————————————————————————————————————————————————— + No deseo caminar sobre el agua", dijo Siddhartha. + Que los antiguos chamanes se contenten con tales habilidades. + —— Hermann Hesse, Siddhartha +————————————————————————————————————————————————————————————————————————————— + Copyright (c) 2026 Active Thing +————————————————————————————————————————————————————————————————————————————— */ + +package errs + +type ( + Adder interface { + Add(err error) error + } + + AddFnc func(err error) error +) + +func (f AddFnc) Add(err error) error { return f(err) } + +func Add(base error, composers ...Adder) error { + if base == nil { + // Panics with a specific error if the base error is nil, indicating incorrect usage. + panic(MakePanicError(ErrNilError)) + } + 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.Add(base); nb != nil { + base = nb + } + } + } + return base +} diff --git a/src/core/errs/all.gos b/src/core/errs/all.gos new file mode 100644 index 0000000..b38ee6d --- /dev/null +++ b/src/core/errs/all.gos @@ -0,0 +1,1689 @@ +package errs + +import ( + "bufio" + "bytes" + "errors" + "fmt" + + "io" + "os" + "regexp" + "runtime" + "strings" + "time" +) + +const ( + RootModule Module = Module("") + ErrsModule Module = Module("errs") +) + +var ( + + // Global sentinels (no module prefix) + ErrNilError = RootModule.Code("nil-error") + ErrNilCause = RootModule.Code("nil-cause") + + // Internal sentinels (errs module) + ErrInvalidModule = ErrsModule.Code("invalid-module") + ErrInvalidCode = ErrsModule.Code("invalid-severity") + ErrInvalidMetaName = ErrsModule.Code("invalid-meta-name") + ErrInvalidParamName = ErrsModule.Code("invalid-param-name") + + paramRgx = regexp.MustCompile(`^[a-zA-Z][a-zA-Z0-9_]{0,62}[a-zA-Z0-9]$`) + metaRgx = regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_-]{0,62}[a-zA-Z0-9_]$`) + codeRgx = regexp.MustCompile(`^[a-zA-Z0-9](?:[a-zA-Z0-9_-]*[a-zA-Z0-9])?$`) + severityName = []string{"UNKNOWN", "DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL", "FATAL"} +) + +func isValidSegment(code string) bool { return len(code) > 0 && codeRgx.MatchString(code) } + +func ToError(err any) error { + if err == nil { + return nil + } + switch t := err.(type) { + case error: + return t + case []error: + return MakeMultiError(t) + default: + return fmt.Errorf("%v", err) + } +} + +func ToString(err error) string { + if err == nil { + return "" + } + switch e := err.(type) { + case Code: + return string(e) + case *Code: + return string(*e) + case fmt.Stringer: + return e.String() + default: + return err.Error() + } +} + +func Matches(err, target error) bool { + var t Code + if !errors.As(target, &t) { + return false + } + + for e := err; e != nil; e = errors.Unwrap(e) { + var c Code + if errors.As(e, &c) && c.IsOrFrom(t) { + return true + } + } + return false +} + +func IsSubOf(parent, err error) bool { + var p Code + if !errors.As(parent, &p) { + return false + } + for e := err; e != nil; e = errors.Unwrap(e) { + var c Code + if errors.As(e, &c) && c.IsSubOf(p) { + return true + } + } + return false +} + +type ( + Adder interface { + Add(err error) error + } + + AddFnc func(err error) error +) + +func (f AddFnc) Add(err error) error { return f(err) } + +func Add(base error, composers ...Adder) error { + if base == nil { + + panic(MakePanicError(ErrNilError)) + } + if len(composers) == 0 { + return base + } + + for _, c := range composers { + if c != nil { + + if nb := c.Add(base); nb != nil { + base = nb + } + } + } + return base +} + +type Causer interface { + Cause() error +} +type Causers interface { + Causes() []error +} +type Code string + +func (c Code) Sub(newCode string) Code { return makeCode(string(c), CodeSep, newCode) } + +func (c Code) Module() Module { + s := string(c) + if i := strings.Index(s, ModuleSep); i != -1 { + return Module(s[:i]) + } + return RootModule +} + +func (c Code) Leaf() string { + s := string(c) + if i := strings.LastIndex(s, CodeSep); i >= 0 { + return string(c[i+lenCodeSep:]) + } + if i := strings.Index(s, ModuleSep); i != -1 { + return s[i+lenModuleSep:] + } + return s +} + +func (c Code) Parent() Code { + if i := strings.LastIndex(string(c), CodeSep); i >= 0 { + return c[:i] + } + return "" +} + +func (c Code) Segs() []string { + s := string(c) + n := len(s) + var segments []string + + moduleEnd := strings.Index(s, ModuleSep) + if moduleEnd == -1 || moduleEnd+lenModuleSep >= n { + return []string{s} + } + segments = append(segments, s[:moduleEnd]) + + start := moduleEnd + lenModuleSep + for i := start; i <= n; i++ { + if i == n || s[i] == CodeSep[0] { + segments = append(segments, s[start:i]) + start = i + 1 + } + } + return segments +} + +func (c Code) IsSubOf(parent error) bool { + var p Code + if !errors.As(parent, &p) { + return false + } + s, ps := string(c), string(p) + if s == ps { + return false + } + return strings.HasPrefix(s, ps+CodeSep) +} + +func (c Code) Is(target error) bool { + var t Code + return errors.As(target, &t) && c == t +} + +func (c Code) IsOrFrom(target error) bool { + var t Code + if !errors.As(target, &t) { + return false + } + sc := string(c) + st := string(t) + return sc == st || strings.HasPrefix(sc, st+CodeSep) +} + +func (c Code) Error() string { return string(c) } + +func (c Code) String() string { return string(c) } + +type ( + Collectable interface { + Collect(collector Collector) + } + + CollectFnc func(collector Collector) +) + +func (f CollectFnc) Collect(collector Collector) { f(collector) } + +type Collector interface { + Options() CollectorFlags + + AddMetas(meta ...Meta) + AddCauses(causes ...error) + AddStack(stack Stack) +} + +const ( + CollectorFlagMetas CollectorFlags = 1 << iota + CollectorFlagCauses + CollectorFlagStack + + CollectorFlagEmpty + CollectorFlagsAll = (1 << iota) - 1 +) + +// CollectorOptss is a bitmask type used to select which types of metadata to retrieve. +type CollectorFlags uint8 + +// CanAcceptMetas returns true if the CollectorOptss includes the MetaFilterField flag. +func (s CollectorFlags) HasMetas() bool { return s&CollectorFlagMetas != 0 } + +// HasCauses returns true if the CollectorOptss includes the MetaFilterCause flag. +func (s CollectorFlags) HasCauses() bool { return s&CollectorFlagCauses != 0 } + +// HasStack returns true if the CollectorOptss includes the MetaFilterStack flag. +func (s CollectorFlags) HasStack() bool { return s&CollectorFlagStack != 0 } + +func (s CollectorFlags) HasEmpty() bool { return s&CollectorFlagEmpty != 0 } + +func (s CollectorFlags) Can(flag CollectorFlags) bool { return s&flag != 0 } + +func (s CollectorFlags) Del(flag CollectorFlags) CollectorFlags { return s &^ flag } + +func (s CollectorFlags) Add(flag CollectorFlags) CollectorFlags { return s | flag } + +type ErrorList []error + +func (l ErrorList) Any() bool { + for _, e := range l { + if e != nil { + return true + } + } + return false +} + +func (l ErrorList) Count() int { + c := 0 + for _, e := range l { + if e != nil { + c++ + } + } + return c +} + +func (l ErrorList) First() error { + for _, e := range l { + if e != nil { + return e + } + } + return nil +} + +func (l ErrorList) Last() error { + for i := len(l) - 1; i >= 0; i-- { + if l[i] != nil { + return l[i] + } + } + return nil +} + +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. +func (l ErrorList) AppendNotNil(err error) ErrorList { + if err != nil { + return append(l, err) + } + return l +} + +func (l ErrorList) Reset() ErrorList { + return l[:0] +} + +func (l ErrorList) filter(filter func(error) bool) ErrorList { + rs := make([]error, 0, len(l)) + for _, e := range l { + if filter(e) { + rs = append(rs, e) + } + } + return rs +} + +func (l ErrorList) Clean() ErrorList { + return l.filter(func(e error) bool { + return e != nil + }) +} +func format(w io.Writer, err error, level int, ops FormatOptions) error { + if err == nil { + return nil + } + + return nil +} + +const ( + // FormatShowHierarchy is a flag to show the error hierarchy (wrapped errors/causes). + FormatShowHierarchy FormatterFlags = 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 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 + + // FormatShowAll is a convenience flag to show everything. + FormatShowAll = (1 << iota) - 1 + + // FormatShowNone is a flag to show nothing extra (only the basic error message). + FormatShowNone FormatterFlags = 0 +) + +type ( + + // FormatterFlags is a bitmask type used to control which parts of an error are displayed during formatting. + FormatterFlags uint8 +) + +// HasMetas returns true if the FormatterFlags includes the FormatShowMetas flag. +func (o FormatterFlags) HasMetas() bool { return o&FormatShowMetas != 0 } + +// HasHierarchy returns true if the FormatterFlags includes the FormatShowHierarchy flag. +func (o FormatterFlags) HasHierarchy() bool { return o&FormatShowHierarchy != 0 } + +// HasDeepMetas returns true if the FormatterFlags includes the FormatShowDeepMetas flag. +func (o FormatterFlags) HasDeepMetas() bool { return o&FormatShowDeepMetas != 0 } + +// HasStack returns true if the FormatterFlags includes the FormatShowStack flag. +func (o FormatterFlags) HasStack() bool { return o&FormatShowStack != 0 } + +// HasDeepStacks returns true if the FormatterFlags includes the FormatShowDeepStacks flag. +func (o FormatterFlags) HasDeepStacks() bool { return o&FormatShowDeepStacks != 0 } + +func (o FormatterFlags) IsDeepMode() bool { return o.HasDeepMetas() || o.HasDeepStacks() } + +func (o FormatterFlags) IsMinimal() bool { return o == FormatShowNone } + +func (o FormatterFlags) Normalize() FormatterFlags { + + if o.HasDeepMetas() { + o |= FormatShowMetas + } + if o.HasDeepStacks() { + o |= FormatShowStack + } + return o +} + +// String returns a human-readable representation of the FormatterFlags flags. +func (o FormatterFlags) String() string { + if o == FormatShowNone { + return "none" + } + var parts []string + if o.HasHierarchy() { + parts = append(parts, "hierarchy") + } + if o.HasMetas() { + if o.HasDeepMetas() { + parts = append(parts, "deep-metas") + } else { + parts = append(parts, "metas") + } + } + if o.HasStack() { + if o.HasDeepStacks() { + parts = append(parts, "deep-stacks") + } else { + parts = append(parts, "stack") + } + } + + return strings.Join(parts, "|") +} + +// FormatFlagsFromVerb derives FormatterFlags flags based on the fmt.State and verb. +// This function interprets standard fmt verbs and flags to determine what to display. +func FormatFlagsFromVerb(f fmt.State, verb rune) FormatterFlags { + s := FormatShowNone + switch verb { + case 'v': + + if f.Flag('+') { + + s = FormatShowAll + } else if f.Flag('#') { + + s |= FormatShowMetas | FormatShowStack + } else if f.Flag('-') { + s |= FormatShowHierarchy | FormatShowMetas + } else { + + s |= FormatShowHierarchy | FormatShowStack + } + + default: + return s + } + + return s.Normalize() +} + +type ( + FormatOptions struct { + Flags FormatterFlags + EnableColor bool + Indent string + + FmtError string + FmtMeta string + FmtCause string + FmtStackHead string + FmtStackLine string + } +) + +func NewFormatOptions() *FormatOptions { + return &FormatOptions{ + Indent: " ", + FmtError: "%s%s\n", + FmtMeta: "%s * %s: %v\n", + FmtCause: "%s%s\n", + FmtStackHead: "%s * stack:\n", + FmtStackLine: "%s %s\n", + } +} + +type ( + Formatter interface { + Format(writer io.Writer, err error) error + } + + FormatFnc func(writer io.Writer, err error) error +) + +func (f FormatFnc) Format(writer io.Writer, err error) error { return f(writer, err) } + +type Meta interface { + Name() string + Value() any +} + +type meta struct { + name string + value any +} + +func MakeMeta(name string, value any) Meta { + if len(name) == 0 || !metaRgx.MatchString(name) { + panic(MakePanicError(ErrInvalidMetaName)) + } + return &meta{ + name: name, + value: value, + } +} + +func (m meta) Name() string { return m.name } + +func (m meta) Value() any { return m.value } + +func IsValidMetaName(meta string) bool { + return len(meta) > 0 && metaRgx.MatchString(meta) +} + +const ( + CodeSep = "." + ModuleSep = "::" + lenModuleSep = 2 + lenCodeSep = 1 +) + +type Module string + +func MakeModule(name string) Module { + if !isValidSegment(name) { + panic(ErrInvalidModule) + } + return Module(name) +} + +func (m Module) Code(code string) Code { return makeCode(string(m), ModuleSep, code) } + +func (m Module) String() string { return string(m) } + +func makeCode(parent string, sep string, code string) Code { + if !isValidSegment(code) { + panic(ErrInvalidCode) + } + return Code(parent + sep + code) +} + +const CodeMultiError = Code(ModuleSep + "multi_error") + +type MultiError struct { + Code + ErrorList +} + +func MakeMultiError(causes ErrorList) MultiError { + return MultiError{ + Code: CodeMultiError, + ErrorList: causes.Clean()} +} + +// FindIs encuentra el primer error en la lista para el que errors.Is devuelve true. +// Devuelve el error encontrado o nil si ninguno coincide. +// +// Ejemplo: +// +// if found := multi.FindIs(SomeCode); found != nil { +// fmt.Println("Encontrado error de esa categoría:", found) +// } +func (w MultiError) FindIs(target error) error { + for _, e := range w.ErrorList { + if errors.Is(e, target) { + return e + } + } + return nil +} + +func (w MultiError) Is(target error) bool { return w.FindIs(target) != nil || Matches(w, target) } + +// FindAs encuentra el primer error en la lista que coincide con target. +// target debe ser un puntero a un tipo que implemente error o una interfaz. +// Devuelve el error encontrado o nil si no se encuentra. +func (w MultiError) FindAs(target any) error { + for _, err := range w.ErrorList { + if errors.As(err, target) { + return err + } + } + return nil +} + +func (w MultiError) Unwrap() []error { return w.ErrorList } + +// Meta interface implementación +func (w MultiError) Name() string { return MetaCauseName } +func (w MultiError) Value() any { return w.ErrorList } + +func (w MultiError) Collect(collector Collector) { + if collector.Options().HasCauses() { + collector.AddCauses(w.ErrorList...) + } +} + +const ( + CodePanic = Code(ModuleSep + "panic") +) + +type PanicError struct { + Code + cause error +} + +func MakePanicError(cause any) PanicError { + return PanicError{ + Code: CodePanic, + cause: ToError(cause), + } +} + +func (c PanicError) Unwrap() error { return c.cause } + +func (c PanicError) Cause() error { return c.cause } + +func (c PanicError) Collect(collector Collector) { + if collector.Options().HasCauses() { + collector.AddCauses(c.Cause()) + } +} + +const ( + CodeParamError = Code(ModuleSep + "invalid_param") + CodeParamNilError = CodeParamError + CodeSep + "nil" + CodeParamEmptyError = CodeParamError + CodeSep + "empty" + CodeParamTypeError = CodeParamError + CodeSep + "invalid_type" + CodeParamFormatError = CodeParamError + CodeSep + "invalid_format" + CodeParamRangeError = CodeParamError + CodeSep + "range" + CodeParamRangeMinError = CodeParamRangeError + CodeSep + "min" + CodeParamRangeMaxError = CodeParamRangeError + CodeSep + "max" + + MetaParamName = "param_name" + MetaParamValue = "param_value" +) + +type ParamError struct { + Code + param string + value any +} + +func MakeParamError(param string, value any) ParamError { + if len(param) == 0 || !paramRgx.MatchString(param) { + panic(MakePanicError(ErrInvalidParamName)) + } + return ParamError{ + Code: CodeParamError, + param: param, + value: value} +} + +func MakeParamNilError(param string) ParamError { + return ParamError{Code: CodeParamNilError, param: param} +} + +func MakeParamEmptyError(param string) ParamError { + return ParamError{Code: CodeParamEmptyError, param: param} +} + +func MakeParamTypeError(param string) ParamError { + return ParamError{Code: CodeParamTypeError, param: param} +} + +func MakeParamFormatError(param string, value any) ParamError { + return ParamError{Code: CodeParamFormatError, param: param, value: value} +} + +func MakeParamRangeError(param string, value any) ParamError { + return ParamError{Code: CodeParamRangeError, param: param, value: value} +} + +func MakeParamRangeMinError(param string, value any) ParamError { + return ParamError{Code: CodeParamRangeMinError, param: param, value: value} +} + +func MakeParamRangeMaxError(param string, value any) ParamError { + return ParamError{Code: CodeParamRangeMaxError, param: param, value: value} +} + +func (e ParamError) Param() string { return e.param } + +func (e ParamError) Value() any { return e.value } + +func (e ParamError) Collect(collector Collector) { + if collector.Options().HasMetas() { + collector.AddMetas( + MakeMeta(MetaParamName, e.param), + MakeMeta(MetaParamValue, e.value)) + } +} + +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 { + + if s <= 0 || s > SeverityFatal { + return severityName[0] + } + + return severityName[s] +} + +// Constants used for stack trace configuration and handling. +const ( + // stackSkipFrames defines how many initial frames are skipped when capturing the stack. + // 2 frames are skipped to exclude internal system calls (runtime.Callers and this function). + stackSkipFrames = 2 + + // stackMaxFrames defines the maximum number of frames that will be captured. + // Limits the depth of the stack trace to 32 frames by default. + stackMaxFrames = 32 +) + +// Stack is a slice of program counters, representing a captured stack trace. +type Stack []uintptr + +// CreateStack captures the current execution stack trace. +// It takes an offset (number of frames to skip in addition to the default) +// and maxFrames (maximum number of frames to capture). +// Returns a Stack slice containing the captured program counters. +func CreateStack(options ...int) Stack { + offset := 0 + maxFrames := stackMaxFrames + if len(options) > 0 { + offset = options[0] + if offset < 0 { + offset = 0 + } + if len(options) > 1 { + maxFrames = options[1] + if maxFrames < 0 || maxFrames > stackMaxFrames { + maxFrames = stackMaxFrames + } + } + } + ls := make([]uintptr, maxFrames) + + cn := runtime.Callers(offset+stackSkipFrames, ls) + + return ls[:cn] +} + +// Frames converts the Stack (slice of program counters) into a slice of StackFrame objects. +// Each StackFrame provides detailed information about a single frame in the stack. +func (s Stack) Frames() []StackFrame { + + f := make([]StackFrame, len(s)) + + for i, p := range s { + f[i] = CreateStackFrame(p) + } + + return f +} + +const ( + // stackUnknownInfo is the default value when information about a stack frame cannot be obtained. + stackUnknownInfo = "???" +) + +type ( + // StackFrame represents a single frame in a stack trace. + // It contains detailed information about the function call at that point. + StackFrame struct { + // LineNumber is the line number in the source file where this frame was created. + LineNumber int + + // File is the full path to the source file containing this frame. + File string + + // FuncName is the name of the function where this frame was created, + // with any receiver types removed. + FuncName string + + // Package is the full package path where this frame was created. + Package string + + // Counter is the program counter address for this frame. + Counter uintptr + } +) + +// CreateStackFrame creates a new StackFrame from a program counter address. +// It uses runtime information to populate the frame details. +// Returns an empty StackFrame if the counter is 0. +func CreateStackFrame(counter uintptr) StackFrame { + sf := StackFrame{} + if counter == 0 { + return sf + } + sf.Counter = counter + fn := runtime.FuncForPC(counter) + if fn == nil { + sf.FuncName = stackUnknownInfo + return sf + } + + sf.Package, sf.FuncName = packageAndName(fn) + sf.File, sf.LineNumber = fn.FileLine(counter - 1) + return sf +} + +// SourceLine attempts to retrieve the source severity line for this stack frame. +// It reads the source file and returns the content of the line specified by LineNumber. +// Returns stackUnknownInfo if the line number is invalid or an error occurs while reading the file. +func (f StackFrame) SourceLine() string { + if f.LineNumber <= 0 || f.File == "" { + return stackUnknownInfo + } + + if _, err := os.Stat(f.File); os.IsNotExist(err) { + return "" + } + + file, err := os.Open(f.File) + if err != nil { + return "" + } + defer func() { _ = file.Close() }() + + scanner := bufio.NewScanner(file) + currentLine := 1 + + for scanner.Scan() { + if currentLine == f.LineNumber { + return string(bytes.TrimSpace(scanner.Bytes())) + } + currentLine++ + + if currentLine > f.LineNumber { + break + } + } + + return stackUnknownInfo +} + +// packageAndName extracts the package path and function name from a runtime.Func. +// It handles different naming conventions, including methods. +func packageAndName(fn *runtime.Func) (string, string) { + nm := fn.Name() + pg := "" + + if ls := strings.LastIndex(nm, "/"); ls >= 0 { + pg = nm[:ls] + "/" + nm = nm[ls+1:] + } + + if pd := strings.Index(nm, "."); pd >= 0 { + pg += nm[:pd] + nm = nm[pd+1:] + } + + return pg, strings.Replace(nm, "·", ".", -1) +} + +const MaxDeep = 32 + +type ( + Tree struct { + flags CollectorFlags + maxDeep int + level int + + Err error + Causes []*Tree + Metas []Meta + Stack Stack + } + + TreeFnc func(*Tree) +) + +func NewTree(err error, ops ...TreeFnc) *Tree { + return newTree(err, 0) +} + +func newTree(err error, level int) *Tree { + if err == nil || (level > MaxDeep) { + return nil + } + + node := &Tree{ + Err: err, + level: level, + maxDeep: MaxDeep, + flags: CollectorFlagsAll, + } + + for e := err; e != nil; { + if c, ok := e.(Collectable); ok { + c.Collect(node) + } + e = errors.Unwrap(e) + } + + return node +} + +func (t *Tree) Options() CollectorFlags { return t.flags } + +func (t *Tree) MaxDeep() int { return t.maxDeep } + +// AddStack stores the first captured stack trace. +// +// Once a stack is set, further stack collection is disabled. +// This enforces a single authoritative stack trace per error, +// avoiding noise and preserving a clear origin. +func (t *Tree) AddStack(stack Stack) { + t.Stack = stack + t.flags = t.flags.Del(CollectorFlagStack) +} + +func (t *Tree) AddMetas(meta ...Meta) { t.Metas = append(t.Metas, meta...) } + +func (t *Tree) AddCauses(causes ...error) { + if !t.flags.HasCauses() { + return + } + for _, cause := range causes { + if cause == nil { + continue + } + if child := newTree(cause, t.level+1); child != nil { + t.Causes = append(t.Causes, child) + } + } +} + +func (t *Tree) print(w io.Writer, prefix string, isLast bool) { + marker := "├── " + if isLast { + marker = "└── " + if t.level == 0 { + marker = "─── " + } + } + + fmt.Fprintf(w, "%s%s%v\n", prefix, marker, t.Err.Error()) + + childPrefix := prefix + if isLast { + childPrefix += " " + } else { + childPrefix += "│ " + } + + for i, meta := range t.Metas { + isLastMeta := i == len(t.Metas)-1 && len(t.Causes) == 0 && len(t.Stack) != 0 + metaMarker := "├── " + if isLastMeta { + metaMarker = "└── " + } + fmt.Fprintf(w, "%s%s[%s: %v]\n", childPrefix, metaMarker, meta.Name(), meta.Value()) + } + + if len(t.Stack) != 0 { + isLastStack := len(t.Causes) == 0 + stackMarker := "├── " + if isLastStack { + stackMarker = "└── " + } + frames := t.Stack.Frames() + fmt.Fprintf(w, "%s%s[stack: %d frames]\n", childPrefix, stackMarker, len(frames)) + for i := 0; i < len(frames) && i < 3; i++ { + frame := frames[i] + fmt.Fprintf(w, "%s %s:%d %s\n", childPrefix, shortenPath(frame.File), frame.LineNumber, frame.FuncName) + } + } + + for i, cause := range t.Causes { + isLastCause := i == len(t.Causes)-1 + cause.print(w, childPrefix, isLastCause) + } +} + +// shortenPath acorta rutas largas para mejor legibilidad +func shortenPath(path string) string { + parts := strings.Split(path, "/") + if len(parts) > 3 { + return ".../" + strings.Join(parts[len(parts)-2:], "/") + } + return path +} + +func (t *Tree) Print(w io.Writer) { + if t == nil { + return + } + t.print(w, "", true) +} + +// String implementa fmt.Stringer para print directo +func (t *Tree) String() string { + var buf strings.Builder + t.Print(&buf) + return buf.String() +} + +const ( + MetaCauseName = "cause" +) + +type withCause struct { + error + cause error +} + +func AddCause(cause error) Adder { + return AddFnc(func(err error) error { return WithCause(err, cause) }) +} + +func WithCause(err error, cause error) error { + if err == nil { + if cause != nil { + return cause + } + panic(MakePanicError(ErrNilError)) + } + if cause == nil { + return err + } + + return withCause{ + error: err, + cause: cause, + } +} + +func (w withCause) Unwrap() error { return w.error } + +func (w withCause) Cause() error { return w.cause } + +func (w withCause) Collect(c Collector) { + if c.Options().HasCauses() { + c.AddCauses(w.cause) + } +} + +const MetaCausesName = "causes" + +// withCauses is an internal wrapper that associates one or more *causal errors* +// to an error without changing its semantic identity. +// +// IMPORTANT DESIGN NOTES: +// +// - Causes are NOT part of the Go unwrap chain. +// - Causes do NOT represent "previous" errors in the sense of errors.Is / errors.As. +// - Causes are purely explanatory: they answer *why* an error occurred, +// not *what* error it is. +// +// Each withCauses instance owns ONLY the causes it introduces. +// Causes are NOT inherited, merged, or propagated from wrapped errors. +// This avoids duplication and ensures that causes appear exactly once +// when traversing the error chain. +// +// The wrapped `error` represents the semantic identity of the error +// and is the only value returned by Unwrap(). +type withCauses struct { + error + causes ErrorList // Uses ErrorList to normalize, clean and manage cause lists +} + +// AddCauses returns an Adder that attaches one or more causal errors to an error. +// +// This function is syntactic sugar around WithCauses, intended to be used +// in error composition pipelines. +// +// Causes added through this function are local to the wrapper created by +// WithCauses and are not propagated to inner errors. +func AddCauses(causes ...error) Adder { + return AddFnc(func(err error) error { + return WithCauses(err, causes...) + }) +} + +func WithCauses(err error, causes ...error) error { + lc := ErrorList(causes).Clean() + + if err == nil { + if len(lc) > 0 { + return MakeMultiError(lc) + } + panic(MakePanicError(ErrNilError)) + } + + if len(lc) == 0 { + return err + } + + return withCauses{ + error: err, + causes: lc, + } +} + +// Causes returns the causal errors introduced by this wrapper. +// +// The returned causes are local to this withCauses instance and do not +// include causes from wrapped errors. +func (w withCauses) Causes() []error { + return w.causes +} + +// Unwrap returns the semantic identity of the error. +// +// Causes are intentionally NOT part of the unwrap chain. +// This preserves compatibility with Go's errors.Is / errors.As mechanisms +// and keeps causality orthogonal to error identity. +func (w withCauses) Unwrap() error { + return w.error +} + +// Collect exports the causes to the provided Collector if the collector +// is configured to accept causes. +// +// Cause collection is explicit and opt-in, reinforcing the idea that +// causality is not part of the implicit unwrap traversal. +func (w withCauses) Collect(c Collector) { + if c.Options().HasCauses() && len(w.causes) > 0 { + c.AddCauses(w.causes...) + } +} + +const MetaDateName = "date" + +type withDate struct { + error + date time.Time +} + +func AddDate() Adder { + return AddFnc(WithDate) +} + +func WithDate(err error) error { + if err == nil { + panic(MakePanicError(ErrNilError)) + } + return withDate{ + error: err, + date: time.Now(), + } +} + +func (w withDate) Unwrap() error { + return w.error +} + +func (w withDate) Date() time.Time { return w.date } + +// Meta interface implementación +func (w withDate) Name() string { return MetaDateName } +func (w withDate) Value() any { return w.date } + +func (w withDate) Collect(c Collector) { + if c.Options().HasMetas() { + c.AddMetas(w) + } +} + +const ( + MetaInfoDescName = "desc" + MetaInfoHelpName = "help" + MetaInfoLinkName = "link" +) + +type withInfo struct { + error // The wrapped error + 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 +} + +func AddInfo(desc, help, link string) Adder { + return AddFnc(func(e error) error { return WithInfo(e, desc, help, link) }) +} + +func WithInfo(err error, desc, help, link string) error { + if err == nil { + panic(MakePanicError(ErrNilError)) + } + return withInfo{ + error: err, + desc: desc, + help: help, + link: link, + } +} + +// Desc returns the descriptive message about the error. +func (w withInfo) Desc() string { return w.desc } + +// Help returns the help text for resolving the error. +func (w withInfo) Help() string { return w.help } + +// Link returns the documentation link for the error. +func (w withInfo) Link() string { return w.link } + +// Unwrap implements Go 1.13+'s errors.Unwrap interface. +// It returns the wrapped error. +func (w withInfo) Unwrap() error { return w.error } + +func (w withInfo) Collect(collector Collector) { + if collector.Options().HasMetas() { + if collector.Options().HasEmpty() { + collector.AddMetas( + MakeMeta(MetaInfoDescName, w.desc), + MakeMeta(MetaInfoHelpName, w.help), + MakeMeta(MetaInfoLinkName, w.link)) + return + } + if len(w.desc) > 0 { + collector.AddMetas(MakeMeta(MetaInfoDescName, w.desc)) + } + if len(w.help) > 0 { + collector.AddMetas(MakeMeta(MetaInfoHelpName, w.help)) + } + if len(w.link) > 0 { + collector.AddMetas(MakeMeta(MetaInfoLinkName, w.link)) + } + } +} + +const MetaMessageName = "message" + +type withMessage struct { + error + message string +} + +func AddMessage(message string, args ...any) Adder { + return AddFnc(func(err error) error { return WithMessage(err, message, args...) }) +} + +func WithMessage(err error, message string, args ...any) error { + if err == nil { + panic(MakePanicError(ErrNilError)) + } + m := message + if len(args) > 0 { + m = fmt.Sprintf(message, args...) + } + return withMessage{ + error: err, + message: m, + } +} + +func (w withMessage) Unwrap() error { + return w.error +} + +func (w withMessage) Message() string { return w.message } + +// Meta interface implementación +func (w withMessage) Name() string { return MetaMessageName } +func (w withMessage) Value() any { return w.message } + +func (w withMessage) Collect(collector Collector) { + if collector.Options().HasMetas() && (len(w.message) > 0 || collector.Options().HasEmpty()) { + collector.AddMetas(w) + } +} + +type withMetas struct { + error + metas []Meta +} + +func AddMetas(metas ...Meta) Adder { + return AddFnc(func(err error) error { return WithMetas(err, metas...) }) +} + +func WithMetas(err error, metas ...Meta) error { + if err == nil { + panic(MakePanicError(ErrNilError)) + } + if len(metas) == 0 { + return err + } + return withMetas{ + error: err, + metas: metas, + } +} + +func (w withMetas) Metas() []Meta { return w.metas } + +func (w withMetas) Unwrap() error { + return w.error +} + +func (w withMetas) Collect(collector Collector) { + if collector.Options().HasMetas() { + collector.AddMetas(w.metas...) + } +} + +const ( + MetaObservedObserverName = "observed_by" + MetaObservedInstanceName = "observed_instance" +) + +type withObserved struct { + error + observer string + instance string +} + +func AddObserved(observer string, instance ...string) Adder { + return AddFnc(func(err error) error { return WithObserved(err, observer, instance...) }) +} + +func WithObserved(err error, observer string, instance ...string) error { + if err == nil { + panic(MakePanicError(ErrNilError)) + } + var inst string + if len(instance) > 0 { + inst = instance[0] + } + return withObserved{ + error: err, + observer: observer, + instance: inst, + } +} + +// Observer returns the name of the component or service that observed the error. +func (w withObserved) Observer() string { return w.observer } + +// Instance returns the optional instance identifier of the observer. +func (w withObserved) Instance() string { return w.instance } + +// Unwrap implements Go 1.13+'s errors.Unwrap interface. +// It returns the wrapped error. +func (w withObserved) Unwrap() error { return w.error } + +func (w withObserved) Collect(collector Collector) { + if collector.Options().HasMetas() { + if collector.Options().HasEmpty() { + collector.AddMetas( + MakeMeta(MetaObservedObserverName, w.observer), + MakeMeta(MetaObservedInstanceName, w.instance)) + return + } + if len(w.observer) > 0 { + collector.AddMetas(MakeMeta(MetaObservedObserverName, w.observer)) + if len(w.instance) > 0 { + collector.AddMetas(MakeMeta(MetaObservedInstanceName, w.instance)) + } + } + } +} + +const ( + MetaOriginClusterName = "origin_cluster" + MetaOriginServerName = "origin_server" + MetaOriginServiceName = "origin_service" + MetaOriginInstanceName = "origin_instance" +) + +type withOrigin struct { + error + cluster, + server, + service, + instance string +} + +func AddOrigin(cluster, server, service, instance string) Adder { + return AddFnc(func(e error) error { return WithOrigin(e, cluster, server, service, instance) }) +} + +func WithOrigin(err error, cluster, server, service, instance string) error { + if err == nil { + panic(MakePanicError(ErrNilError)) + } + return withOrigin{ + error: err, + cluster: cluster, + server: server, + service: service, + instance: instance, + } +} + +func (w withOrigin) Cluster() string { return w.cluster } + +// Service returns the name of the service where the error originated. +func (w withOrigin) Service() string { return w.service } + +// Instance returns the optional instance identifier of the originating service. +func (w withOrigin) Instance() string { return w.instance } + +// Unwrap implements Go 1.13+'s errors.Unwrap interface. +// It returns the wrapped error. +func (w withOrigin) Unwrap() error { return w.error } + +func (w withOrigin) Collect(collector Collector) { + if collector.Options().HasMetas() { + if collector.Options().HasEmpty() { + collector.AddMetas( + MakeMeta(MetaOriginClusterName, w.cluster), + MakeMeta(MetaOriginServerName, w.server), + MakeMeta(MetaOriginServiceName, w.service), + MakeMeta(MetaOriginInstanceName, w.instance)) + return + } + if len(w.cluster) > 0 { + collector.AddMetas(MakeMeta(MetaOriginClusterName, w.cluster)) + } + if len(w.server) > 0 { + collector.AddMetas(MakeMeta(MetaOriginServerName, w.server)) + } + if len(w.service) > 0 { + collector.AddMetas(MakeMeta(MetaOriginServiceName, w.service)) + } + if len(w.instance) > 0 { + collector.AddMetas(MakeMeta(MetaOriginInstanceName, w.instance)) + } + } +} + +const ( + MetaRetryEnabledName = "retry_enabled" + MetaRetryTemporaryName = "retry_temporary" + MetaRetryIdempotentName = "retry_idempotent" + MetaRetryAfterName = "retry_after" +) + +const ( + // RetryEnabled is a flag indicating the error is potentially retryable. + RetryEnabled uint8 = 1 << iota + // RetryIsTemporary is a flag indicating the error is temporary and might resolve on retry. + RetryIsTemporary + // RetryRetryAfterIsSet is a flag indicating that the 'After' duration is set and should be used for retry delay. + RetryRetryAfterIsSet + // RetryIsIdempotent is a flag indicating that retrying the operation is idempotent (safe to repeat). + RetryIsIdempotent +) + +type ( + // withRetry is a type that wraps an error and adds information about its retryability. + // This is useful for implementing retry logic in clients or middleware. + withRetry struct { + error // The wrapped error + flags uint8 // A bitmask of Retry flags + after time.Duration // The suggested duration to wait before retrying (only valid if RetryRetryAfterIsSet is set in flags) + } +) + +// AddRetry creates a Adder that adds retryable information to an error. +// Takes a bitmask of flags and an optional retry-after duration. +func AddRetry(ops uint8, after time.Duration) Adder { + return AddFnc(func(e error) error { + return WithRetry(e, ops, after) + }) +} + +// WithRetry creates a new withRetry 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 WithRetry(err error, ops uint8, retryAfter time.Duration) error { + if err == nil { + panic(MakePanicError(ErrNilError)) + } + return withRetry{ + error: err, + flags: ops, + after: retryAfter, + } +} + +// CanRetry returns true if the RetryEnabled flag is set. +func (w withRetry) CanRetry() bool { return w.flags&RetryEnabled != 0 } + +// IsTemporary returns true if the RetryIsTemporary flag is set. +func (w withRetry) IsTemporary() bool { return w.flags&RetryIsTemporary != 0 } + +// IsIdempotent returns true if the RetryIsIdempotent flag is set. +func (w withRetry) IsIdempotent() bool { return w.flags&RetryIsIdempotent != 0 } + +// After returns the retry-after duration and a boolean indicating if it was explicitly set. +func (w withRetry) After() time.Duration { + if w.flags&RetryRetryAfterIsSet != 0 { + return w.after + } + return -1 +} + +// Unwrap implements Go 1.13+'s errors.Unwrap interface. +// It returns the wrapped error. +func (w withRetry) Unwrap() error { return w.error } + +func (w withRetry) Collect(collector Collector) { + if collector.Options().HasMetas() { + collector.AddMetas( + MakeMeta(MetaRetryEnabledName, w.CanRetry()), + MakeMeta(MetaRetryTemporaryName, w.IsTemporary()), + MakeMeta(MetaRetryIdempotentName, w.IsIdempotent())) + if dur := w.After(); dur != -1 { + collector.AddMetas(MakeMeta(MetaRetryAfterName, dur)) + } + return + } +} + +const MetaSeverityName = "severity" + +type withSeverity struct { + error + severity Severity +} + +func AddSeverity(severity Severity) Adder { + return AddFnc(func(err error) error { return WithSeverity(err, severity) }) +} + +func WithSeverity(err error, severity Severity) error { + if err == nil { + panic(MakePanicError(ErrNilError)) + } + return withSeverity{ + error: err, + severity: severity, + } +} + +func (w withSeverity) Severity() Severity { return w.severity } + +// Meta interface implementación +func (w withSeverity) Name() string { return MetaSeverityName } +func (w withSeverity) Value() any { return w.severity } + +func (w withSeverity) Unwrap() error { return w.error } + +func (w withSeverity) Collect(collector Collector) { + fmt.Printf("%v\n", collector.Options()) + if collector.Options().HasMetas() { + collector.AddMetas(w) + } +} + +const MetaStackName = "stack" + +type withStack struct { + error + stack Stack +} + +func AddStack(values ...int) Adder { + return AddFnc(func(err error) error { return WithStack(err, values...) }) +} + +func WithStack(err error, values ...int) error { + if err == nil { + panic(MakePanicError(ErrNilError)) + } + + ox, mx := 0, -1 + if len(values) > 0 { + ox = values[0] + if len(values) > 1 { + mx = values[1] + } + } + + return withStack{ + error: err, + stack: CreateStack(ox, mx), + } +} + +func (w withStack) Stack() Stack { return w.stack } + +func (w withStack) Unwrap() error { + return w.error +} + +// Meta interface implementación +func (w withStack) Name() string { return MetaStackName } +func (w withStack) Value() any { return w.stack } + +func (w withStack) Collect(collector Collector) { + if collector.Options().HasStack() { + collector.AddStack(w.stack) + } +} + +const MetaStatusName = "status" // Cambiado para no colisionar con "severity" + +type withStatus struct { + error + status int +} + +func AddStatus(status int) Adder { + return AddFnc(func(err error) error { return WithStatus(err, status) }) +} + +func WithStatus(err error, status int) error { + if err == nil { + panic(MakePanicError(ErrNilError)) + } + return withStatus{ + error: err, + status: status, + } +} + +// Status devuelve el código de estado numérico asociado. +func (w withStatus) Status() int { return w.status } + +// Meta interface implementación +func (w withStatus) Name() string { return MetaStatusName } +func (w withStatus) Value() any { return w.status } + +func (w withStatus) Collect(collector Collector) { + if collector.Options().HasMetas() { + collector.AddMetas(w) + } +} + +const MetaUserName = "user" + +type withUser struct { + error + user string +} + +func AddUser(user string) Adder { + return AddFnc(func(err error) error { return WithUser(err, user) }) +} + +func WithUser(err error, user string) error { + if err == nil { + panic(MakePanicError(ErrNilError)) + } + return withUser{ + + error: err, + user: user, + } +} + +func (w withUser) Unwrap() error { + return w.error +} + +func (w withUser) User() string { return w.user } + +// Meta interface implementación +func (w withUser) Name() string { return MetaUserName } +func (w withUser) Value() any { return w.user } + +func (w withUser) Collect(collector Collector) { + if collector.Options().HasMetas() { + collector.AddMetas(w) + } +} diff --git a/src/core/errs/causer.go b/src/core/errs/causer.go new file mode 100644 index 0000000..1887ab6 --- /dev/null +++ b/src/core/errs/causer.go @@ -0,0 +1,54 @@ +/* —————————————————————————————————————————————————————————————————————————— + Proyecto FWS +————————————————————————————————————————————————————————————————————————————— + Fichero causer.go + Package errs + Autor Juan V. Navarro juanvnl@activething.com + Creado 15/01/2026 +————————————————————————————————————————————————————————————————————————————— + + LICENSES AND TERMS OF USE + ------------------------- + + This software is licensed under the Elastic License v2.0 (the "License"). + For full terms and additional information regarding permitted and prohibited + uses, please visit: + https://activething.com/ATGO/licenses + + You may use, copy, modify, and redistribute this software internally within + your organization for any purpose, including research, development, and + testing, subject to the terms of this License. + + You may NOT, however, use, provide, distribute, or make this software + available to any third party as part of a hosted service, SaaS offering, or + commercial product without first obtaining a commercial license from + Active Thing. + + You may combine this software with other severity, provided that such + combination does not circumvent the restrictions of this License. + + 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 NON-INFRINGEMENT. In no event shall + the authors or copyright holders be liable for any claim, damages, or other + liability arising from the use of this software. + +————————————————————————————————————————————————————————————————————————————— + + Web : activething.com | activething.com/FWS + git : g.activething.com | github.com/activething/FWS + Correo : dev@activething.com + +————————————————————————————————————————————————————————————————————————————— + No deseo caminar sobre el agua", dijo Siddhartha. + Que los antiguos chamanes se contenten con tales habilidades. + —— Hermann Hesse, Siddhartha +————————————————————————————————————————————————————————————————————————————— + Copyright (c) 2026 Active Thing +————————————————————————————————————————————————————————————————————————————— */ + +package errs + +type Causer interface { + Cause() error +} diff --git a/src/core/errs/causers.go b/src/core/errs/causers.go new file mode 100644 index 0000000..c3b3384 --- /dev/null +++ b/src/core/errs/causers.go @@ -0,0 +1,54 @@ +/* —————————————————————————————————————————————————————————————————————————— + Proyecto FWS +————————————————————————————————————————————————————————————————————————————— + Fichero causers.go + Package errs + Autor Juan V. Navarro juanvnl@activething.com + Creado 15/01/2026 +————————————————————————————————————————————————————————————————————————————— + + LICENSES AND TERMS OF USE + ------------------------- + + This software is licensed under the Elastic License v2.0 (the "License"). + For full terms and additional information regarding permitted and prohibited + uses, please visit: + https://activething.com/ATGO/licenses + + You may use, copy, modify, and redistribute this software internally within + your organization for any purpose, including research, development, and + testing, subject to the terms of this License. + + You may NOT, however, use, provide, distribute, or make this software + available to any third party as part of a hosted service, SaaS offering, or + commercial product without first obtaining a commercial license from + Active Thing. + + You may combine this software with other severity, provided that such + combination does not circumvent the restrictions of this License. + + 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 NON-INFRINGEMENT. In no event shall + the authors or copyright holders be liable for any claim, damages, or other + liability arising from the use of this software. + +————————————————————————————————————————————————————————————————————————————— + + Web : activething.com | activething.com/FWS + git : g.activething.com | github.com/activething/FWS + Correo : dev@activething.com + +————————————————————————————————————————————————————————————————————————————— + No deseo caminar sobre el agua", dijo Siddhartha. + Que los antiguos chamanes se contenten con tales habilidades. + —— Hermann Hesse, Siddhartha +————————————————————————————————————————————————————————————————————————————— + Copyright (c) 2026 Active Thing +————————————————————————————————————————————————————————————————————————————— */ + +package errs + +type Causers interface { + Causes() []error +} diff --git a/src/core/errs/code.go b/src/core/errs/code.go new file mode 100644 index 0000000..960f3e9 --- /dev/null +++ b/src/core/errs/code.go @@ -0,0 +1,138 @@ +/* —————————————————————————————————————————————————————————————————————————— + Proyecto FWS +————————————————————————————————————————————————————————————————————————————— + Fichero severity.go + Package errs + Autor Juan V. Navarro juanvnl@activething.com + Creado 03/01/2026 +————————————————————————————————————————————————————————————————————————————— + + LICENSES AND TERMS OF USE + ------------------------- + + This software is licensed under the Elastic License v2.0 (the "License"). + For full terms and additional information regarding permitted and prohibited + uses, please visit: + https://activething.com/ATGO/licenses + + You may use, copy, modify, and redistribute this software internally within + your organization for any purpose, including research, development, and + testing, subject to the terms of this License. + + You may NOT, however, use, provide, distribute, or make this software + available to any third party as part of a hosted service, SaaS offering, or + commercial product without first obtaining a commercial license from + Active Thing. + + You may combine this software with other severity, provided that such + combination does not circumvent the restrictions of this License. + + 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 NON-INFRINGEMENT. In no event shall + the authors or copyright holders be liable for any claim, damages, or other + liability arising from the use of this software. + +————————————————————————————————————————————————————————————————————————————— + + Web : activething.com | activething.com/FWS + git : g.activething.com | github.com/activething/FWS + Correo : dev@activething.com + +————————————————————————————————————————————————————————————————————————————— + No deseo caminar sobre el agua", dijo Siddhartha. + Que los antiguos chamanes se contenten con tales habilidades. + —— Hermann Hesse, Siddhartha +————————————————————————————————————————————————————————————————————————————— + Copyright (c) 2026 Active Thing +————————————————————————————————————————————————————————————————————————————— */ + +package errs + +import ( + "errors" + "strings" +) + +type Code string + +func (c Code) Sub(newCode string) Code { return makeCode(string(c), CodeSep, newCode) } + +func (c Code) Module() Module { + s := string(c) + if i := strings.Index(s, ModuleSep); i != -1 { + return Module(s[:i]) + } + return RootModule +} + +func (c Code) Leaf() string { + s := string(c) + if i := strings.LastIndex(s, CodeSep); i >= 0 { + return string(c[i+lenCodeSep:]) + } + if i := strings.Index(s, ModuleSep); i != -1 { + return s[i+lenModuleSep:] + } + return s +} + +func (c Code) Parent() Code { + if i := strings.LastIndex(string(c), CodeSep); i >= 0 { + return c[:i] + } + return "" +} + +func (c Code) Segs() []string { + s := string(c) + n := len(s) + var segments []string + + // encontrar módulo + moduleEnd := strings.Index(s, ModuleSep) + if moduleEnd == -1 || moduleEnd+lenModuleSep >= n { + return []string{s} // solo módulo o cadena simple + } + segments = append(segments, s[:moduleEnd]) + // iterar sobre el resto de la cadena + start := moduleEnd + lenModuleSep + for i := start; i <= n; i++ { + if i == n || s[i] == CodeSep[0] { + segments = append(segments, s[start:i]) + start = i + 1 + } + } + return segments +} + +func (c Code) IsSubOf(parent error) bool { + var p Code + if !errors.As(parent, &p) { + return false + } + s, ps := string(c), string(p) + if s == ps { + return false + } + return strings.HasPrefix(s, ps+CodeSep) +} + +func (c Code) Is(target error) bool { + var t Code + return errors.As(target, &t) && c == t +} + +func (c Code) IsOrFrom(target error) bool { + var t Code + if !errors.As(target, &t) { + return false + } + sc := string(c) + st := string(t) + return sc == st || strings.HasPrefix(sc, st+CodeSep) +} + +func (c Code) Error() string { return string(c) } + +func (c Code) String() string { return string(c) } diff --git a/src/core/errs/collectable.go b/src/core/errs/collectable.go new file mode 100644 index 0000000..1929527 --- /dev/null +++ b/src/core/errs/collectable.go @@ -0,0 +1,60 @@ +/* —————————————————————————————————————————————————————————————————————————— + Proyecto FWS +————————————————————————————————————————————————————————————————————————————— + Fichero collectable.go + Package errs + Autor Juan V. Navarro juanvnl@activething.com + Creado 20/01/2026 +————————————————————————————————————————————————————————————————————————————— + + LICENSES AND TERMS OF USE + ------------------------- + + This software is licensed under the Elastic License v2.0 (the "License"). + For full terms and additional information regarding permitted and prohibited + uses, please visit: + https://activething.com/ATGO/licenses + + You may use, copy, modify, and redistribute this software internally within + your organization for any purpose, including research, development, and + testing, subject to the terms of this License. + + You may NOT, however, use, provide, distribute, or make this software + available to any third party as part of a hosted service, SaaS offering, or + commercial product without first obtaining a commercial license from + Active Thing. + + You may combine this software with other code, provided that such + combination does not circumvent the restrictions of this License. + + 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 NON-INFRINGEMENT. In no event shall + the authors or copyright holders be liable for any claim, damages, or other + liability arising from the use of this software. + +————————————————————————————————————————————————————————————————————————————— + + Web : activething.com | activething.com/FWS + git : g.activething.com | github.com/activething/FWS + Correo : dev@activething.com + +————————————————————————————————————————————————————————————————————————————— + No deseo caminar sobre el agua", dijo Siddhartha. + Que los antiguos chamanes se contenten con tales habilidades. + —— Hermann Hesse, Siddhartha +————————————————————————————————————————————————————————————————————————————— + Copyright (c) 2026 Active Thing +————————————————————————————————————————————————————————————————————————————— */ + +package errs + +type ( + Collectable interface { + Collect(collector Collector) + } + + CollectFnc func(collector Collector) +) + +func (f CollectFnc) Collect(collector Collector) { f(collector) } diff --git a/src/core/errs/collector.go b/src/core/errs/collector.go new file mode 100644 index 0000000..628dd7c --- /dev/null +++ b/src/core/errs/collector.go @@ -0,0 +1,58 @@ +/* —————————————————————————————————————————————————————————————————————————— + Proyecto FWS +————————————————————————————————————————————————————————————————————————————— + Fichero collector.go + Package errs + Autor Juan V. Navarro juanvnl@activething.com + Creado 20/01/2026 +————————————————————————————————————————————————————————————————————————————— + + LICENSES AND TERMS OF USE + ------------------------- + + This software is licensed under the Elastic License v2.0 (the "License"). + For full terms and additional information regarding permitted and prohibited + uses, please visit: + https://activething.com/ATGO/licenses + + You may use, copy, modify, and redistribute this software internally within + your organization for any purpose, including research, development, and + testing, subject to the terms of this License. + + You may NOT, however, use, provide, distribute, or make this software + available to any third party as part of a hosted service, SaaS offering, or + commercial product without first obtaining a commercial license from + Active Thing. + + You may combine this software with other code, provided that such + combination does not circumvent the restrictions of this License. + + 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 NON-INFRINGEMENT. In no event shall + the authors or copyright holders be liable for any claim, damages, or other + liability arising from the use of this software. + +————————————————————————————————————————————————————————————————————————————— + + Web : activething.com | activething.com/FWS + git : g.activething.com | github.com/activething/FWS + Correo : dev@activething.com + +————————————————————————————————————————————————————————————————————————————— + No deseo caminar sobre el agua", dijo Siddhartha. + Que los antiguos chamanes se contenten con tales habilidades. + —— Hermann Hesse, Siddhartha +————————————————————————————————————————————————————————————————————————————— + Copyright (c) 2026 Active Thing +————————————————————————————————————————————————————————————————————————————— */ + +package errs + +type Collector interface { + Options() CollectorFlags + + AddMetas(meta ...Meta) + AddCauses(causes ...error) + AddStack(stack Stack) +} diff --git a/src/core/errs/collector_flags.go b/src/core/errs/collector_flags.go new file mode 100644 index 0000000..719d943 --- /dev/null +++ b/src/core/errs/collector_flags.go @@ -0,0 +1,79 @@ +/* —————————————————————————————————————————————————————————————————————————— + Proyecto FWS +————————————————————————————————————————————————————————————————————————————— + Fichero meta_filter.go + Package errs + Autor Juan V. Navarro juanvnl@activething.com + Creado 15/01/2026 +————————————————————————————————————————————————————————————————————————————— + + LICENSES AND TERMS OF USE + ------------------------- + + This software is licensed under the Elastic License v2.0 (the "License"). + For full terms and additional information regarding permitted and prohibited + uses, please visit: + https://activething.com/ATGO/licenses + + You may use, copy, modify, and redistribute this software internally within + your organization for any purpose, including research, development, and + testing, subject to the terms of this License. + + You may NOT, however, use, provide, distribute, or make this software + available to any third party as part of a hosted service, SaaS offering, or + commercial product without first obtaining a commercial license from + Active Thing. + + You may combine this software with other severity, provided that such + combination does not circumvent the restrictions of this License. + + 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 NON-INFRINGEMENT. In no event shall + the authors or copyright holders be liable for any claim, damages, or other + liability arising from the use of this software. + +————————————————————————————————————————————————————————————————————————————— + + Web : activething.com | activething.com/FWS + git : g.activething.com | github.com/activething/FWS + Correo : dev@activething.com + +————————————————————————————————————————————————————————————————————————————— + No deseo caminar sobre el agua", dijo Siddhartha. + Que los antiguos chamanes se contenten con tales habilidades. + —— Hermann Hesse, Siddhartha +————————————————————————————————————————————————————————————————————————————— + Copyright (c) 2026 Active Thing +————————————————————————————————————————————————————————————————————————————— */ + +package errs + +const ( + CollectorFlagMetas CollectorFlags = 1 << iota + CollectorFlagCauses + CollectorFlagStack + + CollectorFlagEmpty + CollectorFlagsAll = (1 << iota) - 1 +) + +// CollectorOptss is a bitmask type used to select which types of metadata to retrieve. +type CollectorFlags uint8 + +// CanAcceptMetas returns true if the CollectorOptss includes the MetaFilterField flag. +func (s CollectorFlags) HasMetas() bool { return s&CollectorFlagMetas != 0 } + +// HasCauses returns true if the CollectorOptss includes the MetaFilterCause flag. +func (s CollectorFlags) HasCauses() bool { return s&CollectorFlagCauses != 0 } + +// HasStack returns true if the CollectorOptss includes the MetaFilterStack flag. +func (s CollectorFlags) HasStack() bool { return s&CollectorFlagStack != 0 } + +func (s CollectorFlags) HasEmpty() bool { return s&CollectorFlagEmpty != 0 } + +func (s CollectorFlags) Can(flag CollectorFlags) bool { return s&flag != 0 } + +func (s CollectorFlags) Del(flag CollectorFlags) CollectorFlags { return s &^ flag } + +func (s CollectorFlags) Add(flag CollectorFlags) CollectorFlags { return s | flag } diff --git a/src/core/errs/error_list.go b/src/core/errs/error_list.go new file mode 100644 index 0000000..7f5e845 --- /dev/null +++ b/src/core/errs/error_list.go @@ -0,0 +1,122 @@ +/* —————————————————————————————————————————————————————————————————————————— + Proyecto FWS +————————————————————————————————————————————————————————————————————————————— + Fichero error_list.go + Package errs + Autor Juan V. Navarro juanvnl@activething.com + Creado 09/01/2026 +————————————————————————————————————————————————————————————————————————————— + + LICENSES AND TERMS OF USE + ------------------------- + + This software is licensed under the Elastic License v2.0 (the "License"). + For full terms and additional information regarding permitted and prohibited + uses, please visit: + https://activething.com/ATGO/licenses + + You may use, copy, modify, and redistribute this software internally within + your organization for any purpose, including research, development, and + testing, subject to the terms of this License. + + You may NOT, however, use, provide, distribute, or make this software + available to any third party as part of a hosted service, SaaS offering, or + commercial product without first obtaining a commercial license from + Active Thing. + + You may combine this software with other severity, provided that such + combination does not circumvent the restrictions of this License. + + 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 NON-INFRINGEMENT. In no event shall + the authors or copyright holders be liable for any claim, damages, or other + liability arising from the use of this software. + +————————————————————————————————————————————————————————————————————————————— + + Web : activething.com | activething.com/FWS + git : g.activething.com | github.com/activething/FWS + Correo : dev@activething.com + +————————————————————————————————————————————————————————————————————————————— + No deseo caminar sobre el agua", dijo Siddhartha. + Que los antiguos chamanes se contenten con tales habilidades. + —— Hermann Hesse, Siddhartha +————————————————————————————————————————————————————————————————————————————— + Copyright (c) 2026 Active Thing +————————————————————————————————————————————————————————————————————————————— */ + +package errs + +type ErrorList []error + +func (l ErrorList) Any() bool { + for _, e := range l { + if e != nil { + return true + } + } + return false +} + +func (l ErrorList) Count() int { + c := 0 + for _, e := range l { + if e != nil { + c++ + } + } + return c +} + +func (l ErrorList) First() error { + for _, e := range l { + if e != nil { + return e + } + } + return nil +} + +func (l ErrorList) Last() error { + for i := len(l) - 1; i >= 0; i-- { + if l[i] != nil { + return l[i] + } + } + return nil +} + +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. +func (l ErrorList) AppendNotNil(err error) ErrorList { + if err != nil { + return append(l, err) + } + return l +} + +func (l ErrorList) Reset() ErrorList { + return l[:0] +} + +func (l ErrorList) filter(filter func(error) bool) ErrorList { + rs := make([]error, 0, len(l)) + for _, e := range l { + if filter(e) { + rs = append(rs, e) + } + } + return rs +} + +func (l ErrorList) Clean() ErrorList { + return l.filter(func(e error) bool { + return e != nil + }) +} diff --git a/src/core/errs/format.go b/src/core/errs/format.go new file mode 100644 index 0000000..b4640d2 --- /dev/null +++ b/src/core/errs/format.go @@ -0,0 +1,63 @@ +/* —————————————————————————————————————————————————————————————————————————— + Proyecto FWS +————————————————————————————————————————————————————————————————————————————— + Fichero format.go + Package errs + Autor Juan V. Navarro juanvnl@activething.com + Creado 19/01/2026 +————————————————————————————————————————————————————————————————————————————— + + LICENSES AND TERMS OF USE + ------------------------- + + This software is licensed under the Elastic License v2.0 (the "License"). + For full terms and additional information regarding permitted and prohibited + uses, please visit: + https://activething.com/ATGO/licenses + + You may use, copy, modify, and redistribute this software internally within + your organization for any purpose, including research, development, and + testing, subject to the terms of this License. + + You may NOT, however, use, provide, distribute, or make this software + available to any third party as part of a hosted service, SaaS offering, or + commercial product without first obtaining a commercial license from + Active Thing. + + You may combine this software with other code, provided that such + combination does not circumvent the restrictions of this License. + + 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 NON-INFRINGEMENT. In no event shall + the authors or copyright holders be liable for any claim, damages, or other + liability arising from the use of this software. + +————————————————————————————————————————————————————————————————————————————— + + Web : activething.com | activething.com/FWS + git : g.activething.com | github.com/activething/FWS + Correo : dev@activething.com + +————————————————————————————————————————————————————————————————————————————— + No deseo caminar sobre el agua", dijo Siddhartha. + Que los antiguos chamanes se contenten con tales habilidades. + —— Hermann Hesse, Siddhartha +————————————————————————————————————————————————————————————————————————————— + Copyright (c) 2026 Active Thing +————————————————————————————————————————————————————————————————————————————— */ + +package errs + +import ( + "io" +) + +func format(w io.Writer, err error, level int, ops FormatOptions) error { + if err == nil { + return nil + } + //todo + + return nil +} diff --git a/src/core/errs/format_flags.go b/src/core/errs/format_flags.go new file mode 100644 index 0000000..c4fb549 --- /dev/null +++ b/src/core/errs/format_flags.go @@ -0,0 +1,171 @@ +/* —————————————————————————————————————————————————————————————————————————— + Proyecto FWS +————————————————————————————————————————————————————————————————————————————— + Fichero format_flags.go + Package errs + Autor Juan V. Navarro juanvnl@activething.com + Creado 19/01/2026 +————————————————————————————————————————————————————————————————————————————— + + LICENSES AND TERMS OF USE + ------------------------- + + This software is licensed under the Elastic License v2.0 (the "License"). + For full terms and additional information regarding permitted and prohibited + uses, please visit: + https://activething.com/ATGO/licenses + + You may use, copy, modify, and redistribute this software internally within + your organization for any purpose, including research, development, and + testing, subject to the terms of this License. + + You may NOT, however, use, provide, distribute, or make this software + available to any third party as part of a hosted service, SaaS offering, or + commercial product without first obtaining a commercial license from + Active Thing. + + You may combine this software with other code, provided that such + combination does not circumvent the restrictions of this License. + + 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 NON-INFRINGEMENT. In no event shall + the authors or copyright holders be liable for any claim, damages, or other + liability arising from the use of this software. + +————————————————————————————————————————————————————————————————————————————— + + Web : activething.com | activething.com/FWS + git : g.activething.com | github.com/activething/FWS + Correo : dev@activething.com + +————————————————————————————————————————————————————————————————————————————— + No deseo caminar sobre el agua", dijo Siddhartha. + Que los antiguos chamanes se contenten con tales habilidades. + —— Hermann Hesse, Siddhartha +————————————————————————————————————————————————————————————————————————————— + Copyright (c) 2026 Active Thing +————————————————————————————————————————————————————————————————————————————— */ + +package errs + +import ( + "fmt" + "strings" +) + +const ( + // FormatShowHierarchy is a flag to show the error hierarchy (wrapped errors/causes). + FormatShowHierarchy FormatterFlags = 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 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 + + // FormatShowAll is a convenience flag to show everything. + FormatShowAll = (1 << iota) - 1 + + // FormatShowNone is a flag to show nothing extra (only the basic error message). + FormatShowNone FormatterFlags = 0 +) + +type ( + + // FormatterFlags is a bitmask type used to control which parts of an error are displayed during formatting. + FormatterFlags uint8 +) + +// HasMetas returns true if the FormatterFlags includes the FormatShowMetas flag. +func (o FormatterFlags) HasMetas() bool { return o&FormatShowMetas != 0 } + +// HasHierarchy returns true if the FormatterFlags includes the FormatShowHierarchy flag. +func (o FormatterFlags) HasHierarchy() bool { return o&FormatShowHierarchy != 0 } + +// HasDeepMetas returns true if the FormatterFlags includes the FormatShowDeepMetas flag. +func (o FormatterFlags) HasDeepMetas() bool { return o&FormatShowDeepMetas != 0 } + +// HasStack returns true if the FormatterFlags includes the FormatShowStack flag. +func (o FormatterFlags) HasStack() bool { return o&FormatShowStack != 0 } + +// HasDeepStacks returns true if the FormatterFlags includes the FormatShowDeepStacks flag. +func (o FormatterFlags) HasDeepStacks() bool { return o&FormatShowDeepStacks != 0 } + +func (o FormatterFlags) IsDeepMode() bool { return o.HasDeepMetas() || o.HasDeepStacks() } + +func (o FormatterFlags) IsMinimal() bool { return o == FormatShowNone } + +func (o FormatterFlags) Normalize() FormatterFlags { + // Si HasDeepMetas está activo, HasMetas también debería estarlo + if o.HasDeepMetas() { + o |= FormatShowMetas + } + if o.HasDeepStacks() { + o |= FormatShowStack + } + return o +} + +// String returns a human-readable representation of the FormatterFlags flags. +func (o FormatterFlags) String() string { + if o == FormatShowNone { + return "none" + } + var parts []string + if o.HasHierarchy() { + parts = append(parts, "hierarchy") + } + if o.HasMetas() { + if o.HasDeepMetas() { + parts = append(parts, "deep-metas") + } else { + parts = append(parts, "metas") + } + } + if o.HasStack() { + if o.HasDeepStacks() { + parts = append(parts, "deep-stacks") + } else { + parts = append(parts, "stack") + } + } + + return strings.Join(parts, "|") +} + +// FormatFlagsFromVerb derives FormatterFlags flags based on the fmt.State and verb. +// This function interprets standard fmt verbs and flags to determine what to display. +func FormatFlagsFromVerb(f fmt.State, verb rune) FormatterFlags { + s := FormatShowNone + switch verb { + case 'v': + // For %v, check flags to determine what to show. + if f.Flag('+') { + // %+v -> Show hierarchy, metas, and full stack + s = FormatShowAll + } else if f.Flag('#') { + // %#v -> Show metas and stack of the principal error + s |= FormatShowMetas | FormatShowStack + } else if f.Flag('-') { + s |= FormatShowHierarchy | FormatShowMetas + } else { + // %v -> Show hierarchy and principal stack (optional based on your criterion) + s |= FormatShowHierarchy | FormatShowStack + } + /* + case 's', 'q': + case 'd', 'o', 'x', 'X': + */ + default: + return s + } + + return s.Normalize() +} diff --git a/src/core/errs/format_options.go b/src/core/errs/format_options.go new file mode 100644 index 0000000..cf3e13e --- /dev/null +++ b/src/core/errs/format_options.go @@ -0,0 +1,75 @@ +/* —————————————————————————————————————————————————————————————————————————— + Proyecto FWS +————————————————————————————————————————————————————————————————————————————— + Fichero format_options.go + Package errs + Autor Juan V. Navarro juanvnl@activething.com + Creado 19/01/2026 +————————————————————————————————————————————————————————————————————————————— + + LICENSES AND TERMS OF USE + ------------------------- + + This software is licensed under the Elastic License v2.0 (the "License"). + For full terms and additional information regarding permitted and prohibited + uses, please visit: + https://activething.com/ATGO/licenses + + You may use, copy, modify, and redistribute this software internally within + your organization for any purpose, including research, development, and + testing, subject to the terms of this License. + + You may NOT, however, use, provide, distribute, or make this software + available to any third party as part of a hosted service, SaaS offering, or + commercial product without first obtaining a commercial license from + Active Thing. + + You may combine this software with other code, provided that such + combination does not circumvent the restrictions of this License. + + 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 NON-INFRINGEMENT. In no event shall + the authors or copyright holders be liable for any claim, damages, or other + liability arising from the use of this software. + +————————————————————————————————————————————————————————————————————————————— + + Web : activething.com | activething.com/FWS + git : g.activething.com | github.com/activething/FWS + Correo : dev@activething.com + +————————————————————————————————————————————————————————————————————————————— + No deseo caminar sobre el agua", dijo Siddhartha. + Que los antiguos chamanes se contenten con tales habilidades. + —— Hermann Hesse, Siddhartha +————————————————————————————————————————————————————————————————————————————— + Copyright (c) 2026 Active Thing +————————————————————————————————————————————————————————————————————————————— */ + +package errs + +type ( + FormatOptions struct { + Flags FormatterFlags + EnableColor bool + Indent string + + FmtError string + FmtMeta string + FmtCause string + FmtStackHead string + FmtStackLine string + } +) + +func NewFormatOptions() *FormatOptions { + return &FormatOptions{ + Indent: " ", + FmtError: "%s%s\n", + FmtMeta: "%s * %s: %v\n", + FmtCause: "%s%s\n", + FmtStackHead: "%s * stack:\n", + FmtStackLine: "%s %s\n", + } +} diff --git a/src/core/errs/formatter.go b/src/core/errs/formatter.go new file mode 100644 index 0000000..08d6fc2 --- /dev/null +++ b/src/core/errs/formatter.go @@ -0,0 +1,62 @@ +/* —————————————————————————————————————————————————————————————————————————— + Proyecto FWS +————————————————————————————————————————————————————————————————————————————— + Fichero formatter.go + Package errs + Autor Juan V. Navarro juanvnl@activething.com + Creado 19/01/2026 +————————————————————————————————————————————————————————————————————————————— + + LICENSES AND TERMS OF USE + ------------------------- + + This software is licensed under the Elastic License v2.0 (the "License"). + For full terms and additional information regarding permitted and prohibited + uses, please visit: + https://activething.com/ATGO/licenses + + You may use, copy, modify, and redistribute this software internally within + your organization for any purpose, including research, development, and + testing, subject to the terms of this License. + + You may NOT, however, use, provide, distribute, or make this software + available to any third party as part of a hosted service, SaaS offering, or + commercial product without first obtaining a commercial license from + Active Thing. + + You may combine this software with other code, provided that such + combination does not circumvent the restrictions of this License. + + 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 NON-INFRINGEMENT. In no event shall + the authors or copyright holders be liable for any claim, damages, or other + liability arising from the use of this software. + +————————————————————————————————————————————————————————————————————————————— + + Web : activething.com | activething.com/FWS + git : g.activething.com | github.com/activething/FWS + Correo : dev@activething.com + +————————————————————————————————————————————————————————————————————————————— + No deseo caminar sobre el agua", dijo Siddhartha. + Que los antiguos chamanes se contenten con tales habilidades. + —— Hermann Hesse, Siddhartha +————————————————————————————————————————————————————————————————————————————— + Copyright (c) 2026 Active Thing +————————————————————————————————————————————————————————————————————————————— */ + +package errs + +import "io" + +type ( + Formatter interface { + Format(writer io.Writer, err error) error + } + + FormatFnc func(writer io.Writer, err error) error +) + +func (f FormatFnc) Format(writer io.Writer, err error) error { return f(writer, err) } diff --git a/src/core/errs/go.mod b/src/core/errs/go.mod new file mode 100644 index 0000000..9f1644b --- /dev/null +++ b/src/core/errs/go.mod @@ -0,0 +1,3 @@ +module errs + +go 1.25 diff --git a/src/core/errs/meta.go b/src/core/errs/meta.go new file mode 100644 index 0000000..b7916b8 --- /dev/null +++ b/src/core/errs/meta.go @@ -0,0 +1,78 @@ +/* —————————————————————————————————————————————————————————————————————————— + Proyecto FWS +————————————————————————————————————————————————————————————————————————————— + Fichero meta.go + Package errs + Autor Juan V. Navarro juanvnl@activething.com + Creado 15/01/2026 +————————————————————————————————————————————————————————————————————————————— + + LICENSES AND TERMS OF USE + ------------------------- + + This software is licensed under the Elastic License v2.0 (the "License"). + For full terms and additional information regarding permitted and prohibited + uses, please visit: + https://activething.com/ATGO/licenses + + You may use, copy, modify, and redistribute this software internally within + your organization for any purpose, including research, development, and + testing, subject to the terms of this License. + + You may NOT, however, use, provide, distribute, or make this software + available to any third party as part of a hosted service, SaaS offering, or + commercial product without first obtaining a commercial license from + Active Thing. + + You may combine this software with other severity, provided that such + combination does not circumvent the restrictions of this License. + + 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 NON-INFRINGEMENT. In no event shall + the authors or copyright holders be liable for any claim, damages, or other + liability arising from the use of this software. + +————————————————————————————————————————————————————————————————————————————— + + Web : activething.com | activething.com/FWS + git : g.activething.com | github.com/activething/FWS + Correo : dev@activething.com + +————————————————————————————————————————————————————————————————————————————— + No deseo caminar sobre el agua", dijo Siddhartha. + Que los antiguos chamanes se contenten con tales habilidades. + —— Hermann Hesse, Siddhartha +————————————————————————————————————————————————————————————————————————————— + Copyright (c) 2026 Active Thing +————————————————————————————————————————————————————————————————————————————— */ + +package errs + +type Meta interface { + Name() string + Value() any +} + +type meta struct { + name string + value any +} + +func MakeMeta(name string, value any) Meta { + if len(name) == 0 || !metaRgx.MatchString(name) { + panic(MakePanicError(ErrInvalidMetaName)) + } + return &meta{ + name: name, + value: value, + } +} + +func (m meta) Name() string { return m.name } + +func (m meta) Value() any { return m.value } + +func IsValidMetaName(meta string) bool { + return len(meta) > 0 && metaRgx.MatchString(meta) +} diff --git a/src/core/errs/module.go b/src/core/errs/module.go new file mode 100644 index 0000000..6f1a97f --- /dev/null +++ b/src/core/errs/module.go @@ -0,0 +1,77 @@ +/* —————————————————————————————————————————————————————————————————————————— + Proyecto FWS +————————————————————————————————————————————————————————————————————————————— + Fichero module.go + Package errs + Autor Juan V. Navarro juanvnl@activething.com + Creado 06/01/2026 +————————————————————————————————————————————————————————————————————————————— + + LICENSES AND TERMS OF USE + ------------------------- + + This software is licensed under the Elastic License v2.0 (the "License"). + For full terms and additional information regarding permitted and prohibited + uses, please visit: + https://activething.com/ATGO/licenses + + You may use, copy, modify, and redistribute this software internally within + your organization for any purpose, including research, development, and + testing, subject to the terms of this License. + + You may NOT, however, use, provide, distribute, or make this software + available to any third party as part of a hosted service, SaaS offering, or + commercial product without first obtaining a commercial license from + Active Thing. + + You may combine this software with other severity, provided that such + combination does not circumvent the restrictions of this License. + + 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 NON-INFRINGEMENT. In no event shall + the authors or copyright holders be liable for any claim, damages, or other + liability arising from the use of this software. + +————————————————————————————————————————————————————————————————————————————— + + Web : activething.com | activething.com/FWS + git : g.activething.com | github.com/activething/FWS + Correo : dev@activething.com + +————————————————————————————————————————————————————————————————————————————— + No deseo caminar sobre el agua", dijo Siddhartha. + Que los antiguos chamanes se contenten con tales habilidades. + —— Hermann Hesse, Siddhartha +————————————————————————————————————————————————————————————————————————————— + Copyright (c) 2026 Active Thing +————————————————————————————————————————————————————————————————————————————— */ + +package errs + +const ( + CodeSep = "." + ModuleSep = "::" + lenModuleSep = 2 + lenCodeSep = 1 +) + +type Module string + +func MakeModule(name string) Module { + if !isValidSegment(name) { + panic(ErrInvalidModule) + } + return Module(name) +} + +func (m Module) Code(code string) Code { return makeCode(string(m), ModuleSep, code) } + +func (m Module) String() string { return string(m) } + +func makeCode(parent string, sep string, code string) Code { + if !isValidSegment(code) { + panic(ErrInvalidCode) + } + return Code(parent + sep + code) +} diff --git a/src/core/errs/multi_error.go b/src/core/errs/multi_error.go new file mode 100644 index 0000000..384d532 --- /dev/null +++ b/src/core/errs/multi_error.go @@ -0,0 +1,110 @@ +/* —————————————————————————————————————————————————————————————————————————— + Proyecto FWS +————————————————————————————————————————————————————————————————————————————— + Fichero multi_error.go + Package errs + Autor Juan V. Navarro juanvnl@activething.com + Creado 09/01/2026 +————————————————————————————————————————————————————————————————————————————— + + LICENSES AND TERMS OF USE + ------------------------- + + This software is licensed under the Elastic License v2.0 (the "License"). + For full terms and additional information regarding permitted and prohibited + uses, please visit: + https://activething.com/ATGO/licenses + + You may use, copy, modify, and redistribute this software internally within + your organization for any purpose, including research, development, and + testing, subject to the terms of this License. + + You may NOT, however, use, provide, distribute, or make this software + available to any third party as part of a hosted service, SaaS offering, or + commercial product without first obtaining a commercial license from + Active Thing. + + You may combine this software with other severity, provided that such + combination does not circumvent the restrictions of this License. + + 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 NON-INFRINGEMENT. In no event shall + the authors or copyright holders be liable for any claim, damages, or other + liability arising from the use of this software. + +————————————————————————————————————————————————————————————————————————————— + + Web : activething.com | activething.com/FWS + git : g.activething.com | github.com/activething/FWS + Correo : dev@activething.com + +————————————————————————————————————————————————————————————————————————————— + No deseo caminar sobre el agua", dijo Siddhartha. + Que los antiguos chamanes se contenten con tales habilidades. + —— Hermann Hesse, Siddhartha +————————————————————————————————————————————————————————————————————————————— + Copyright (c) 2026 Active Thing +————————————————————————————————————————————————————————————————————————————— */ + +package errs + +import ( + "errors" +) + +const CodeMultiError = Code(ModuleSep + "multi_error") + +type MultiError struct { + Code + ErrorList +} + +func MakeMultiError(causes ErrorList) MultiError { + return MultiError{ + Code: CodeMultiError, + ErrorList: causes.Clean()} +} + +// FindIs encuentra el primer error en la lista para el que errors.Is devuelve true. +// Devuelve el error encontrado o nil si ninguno coincide. +// +// Ejemplo: +// +// if found := multi.FindIs(SomeCode); found != nil { +// fmt.Println("Encontrado error de esa categoría:", found) +// } +func (w MultiError) FindIs(target error) error { + for _, e := range w.ErrorList { + if errors.Is(e, target) { + return e + } + } + return nil +} + +func (w MultiError) Is(target error) bool { return w.FindIs(target) != nil || Matches(w, target) } + +// FindAs encuentra el primer error en la lista que coincide con target. +// target debe ser un puntero a un tipo que implemente error o una interfaz. +// Devuelve el error encontrado o nil si no se encuentra. +func (w MultiError) FindAs(target any) error { + for _, err := range w.ErrorList { + if errors.As(err, target) { + return err + } + } + return nil +} + +func (w MultiError) Unwrap() []error { return w.ErrorList } + +// Meta interface implementación +func (w MultiError) Name() string { return MetaCauseName } +func (w MultiError) Value() any { return w.ErrorList } + +func (w MultiError) Collect(collector Collector) { + if collector.Options().HasCauses() { + collector.AddCauses(w.ErrorList...) + } +} diff --git a/src/core/errs/panic_error.go b/src/core/errs/panic_error.go new file mode 100644 index 0000000..f049419 --- /dev/null +++ b/src/core/errs/panic_error.go @@ -0,0 +1,76 @@ +/* —————————————————————————————————————————————————————————————————————————— + Proyecto FWS +————————————————————————————————————————————————————————————————————————————— + Fichero panic_error.go + Package errs + Autor Juan V. Navarro juanvnl@activething.com + Creado 08/01/2026 +————————————————————————————————————————————————————————————————————————————— + + LICENSES AND TERMS OF USE + ------------------------- + + This software is licensed under the Elastic License v2.0 (the "License"). + For full terms and additional information regarding permitted and prohibited + uses, please visit: + https://activething.com/ATGO/licenses + + You may use, copy, modify, and redistribute this software internally within + your organization for any purpose, including research, development, and + testing, subject to the terms of this License. + + You may NOT, however, use, provide, distribute, or make this software + available to any third party as part of a hosted service, SaaS offering, or + commercial product without first obtaining a commercial license from + Active Thing. + + You may combine this software with other severity, provided that such + combination does not circumvent the restrictions of this License. + + 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 NON-INFRINGEMENT. In no event shall + the authors or copyright holders be liable for any claim, damages, or other + liability arising from the use of this software. + +————————————————————————————————————————————————————————————————————————————— + + Web : activething.com | activething.com/FWS + git : g.activething.com | github.com/activething/FWS + Correo : dev@activething.com + +————————————————————————————————————————————————————————————————————————————— + No deseo caminar sobre el agua", dijo Siddhartha. + Que los antiguos chamanes se contenten con tales habilidades. + —— Hermann Hesse, Siddhartha +————————————————————————————————————————————————————————————————————————————— + Copyright (c) 2026 Active Thing +————————————————————————————————————————————————————————————————————————————— */ + +package errs + +const ( + CodePanic = Code(ModuleSep + "panic") +) + +type PanicError struct { + Code + cause error +} + +func MakePanicError(cause any) PanicError { + return PanicError{ + Code: CodePanic, + cause: ToError(cause), + } +} + +func (c PanicError) Unwrap() error { return c.cause } + +func (c PanicError) Cause() error { return c.cause } + +func (c PanicError) Collect(collector Collector) { + if collector.Options().HasCauses() { + collector.AddCauses(c.Cause()) + } +} diff --git a/src/core/errs/param_error.go b/src/core/errs/param_error.go new file mode 100644 index 0000000..b1befc8 --- /dev/null +++ b/src/core/errs/param_error.go @@ -0,0 +1,120 @@ +/* —————————————————————————————————————————————————————————————————————————— + Proyecto FWS +————————————————————————————————————————————————————————————————————————————— + Fichero param_error.go + Package errs + Autor Juan V. Navarro juanvnl@activething.com + Creado 08/01/2026 +————————————————————————————————————————————————————————————————————————————— + + LICENSES AND TERMS OF USE + ------------------------- + + This software is licensed under the Elastic License v2.0 (the "License"). + For full terms and additional information regarding permitted and prohibited + uses, please visit: + https://activething.com/ATGO/licenses + + You may use, copy, modify, and redistribute this software internally within + your organization for any purpose, including research, development, and + testing, subject to the terms of this License. + + You may NOT, however, use, provide, distribute, or make this software + available to any third party as part of a hosted service, SaaS offering, or + commercial product without first obtaining a commercial license from + Active Thing. + + You may combine this software with other severity, provided that such + combination does not circumvent the restrictions of this License. + + 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 NON-INFRINGEMENT. In no event shall + the authors or copyright holders be liable for any claim, damages, or other + liability arising from the use of this software. + +————————————————————————————————————————————————————————————————————————————— + + Web : activething.com | activething.com/FWS + git : g.activething.com | github.com/activething/FWS + Correo : dev@activething.com + +————————————————————————————————————————————————————————————————————————————— + No deseo caminar sobre el agua", dijo Siddhartha. + Que los antiguos chamanes se contenten con tales habilidades. + —— Hermann Hesse, Siddhartha +————————————————————————————————————————————————————————————————————————————— + Copyright (c) 2026 Active Thing +————————————————————————————————————————————————————————————————————————————— */ + +package errs + +const ( + CodeParamError = Code(ModuleSep + "invalid_param") + CodeParamNilError = CodeParamError + CodeSep + "nil" + CodeParamEmptyError = CodeParamError + CodeSep + "empty" + CodeParamTypeError = CodeParamError + CodeSep + "invalid_type" + CodeParamFormatError = CodeParamError + CodeSep + "invalid_format" + CodeParamRangeError = CodeParamError + CodeSep + "range" + CodeParamRangeMinError = CodeParamRangeError + CodeSep + "min" + CodeParamRangeMaxError = CodeParamRangeError + CodeSep + "max" + + MetaParamName = "param_name" + MetaParamValue = "param_value" +) + +type ParamError struct { + Code + param string + value any +} + +func MakeParamError(param string, value any) ParamError { + if len(param) == 0 || !paramRgx.MatchString(param) { + panic(MakePanicError(ErrInvalidParamName)) + } + return ParamError{ + Code: CodeParamError, + param: param, + value: value} +} + +func MakeParamNilError(param string) ParamError { + return ParamError{Code: CodeParamNilError, param: param} +} + +func MakeParamEmptyError(param string) ParamError { + return ParamError{Code: CodeParamEmptyError, param: param} +} + +func MakeParamTypeError(param string) ParamError { + return ParamError{Code: CodeParamTypeError, param: param} +} + +func MakeParamFormatError(param string, value any) ParamError { + return ParamError{Code: CodeParamFormatError, param: param, value: value} +} + +func MakeParamRangeError(param string, value any) ParamError { + return ParamError{Code: CodeParamRangeError, param: param, value: value} +} + +func MakeParamRangeMinError(param string, value any) ParamError { + return ParamError{Code: CodeParamRangeMinError, param: param, value: value} +} + +func MakeParamRangeMaxError(param string, value any) ParamError { + return ParamError{Code: CodeParamRangeMaxError, param: param, value: value} +} + +func (e ParamError) Param() string { return e.param } + +func (e ParamError) Value() any { return e.value } + +func (e ParamError) Collect(collector Collector) { + if collector.Options().HasMetas() { + collector.AddMetas( + MakeMeta(MetaParamName, e.param), + MakeMeta(MetaParamValue, e.value)) + } +} diff --git a/src/core/errs/severity.go b/src/core/errs/severity.go new file mode 100644 index 0000000..5cc1e34 --- /dev/null +++ b/src/core/errs/severity.go @@ -0,0 +1,81 @@ +/* —————————————————————————————————————————————————————————————————————————— + Proyecto FWS +————————————————————————————————————————————————————————————————————————————— + Fichero severity.go + Package errs + Autor Juan V. Navarro juanvnl@activething.com + Creado 16/01/2026 +————————————————————————————————————————————————————————————————————————————— + + LICENSES AND TERMS OF USE + ------------------------- + + This software is licensed under the Elastic License v2.0 (the "License"). + For full terms and additional information regarding permitted and prohibited + uses, please visit: + https://activething.com/ATGO/licenses + + You may use, copy, modify, and redistribute this software internally within + your organization for any purpose, including research, development, and + testing, subject to the terms of this License. + + You may NOT, however, use, provide, distribute, or make this software + available to any third party as part of a hosted service, SaaS offering, or + commercial product without first obtaining a commercial license from + Active Thing. + + You may combine this software with other severity, provided that such + combination does not circumvent the restrictions of this License. + + 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 NON-INFRINGEMENT. In no event shall + the authors or copyright holders be liable for any claim, damages, or other + liability arising from the use of this software. + +————————————————————————————————————————————————————————————————————————————— + + Web : activething.com | activething.com/FWS + git : g.activething.com | github.com/activething/FWS + Correo : dev@activething.com + +————————————————————————————————————————————————————————————————————————————— + No deseo caminar sobre el agua", dijo Siddhartha. + Que los antiguos chamanes se contenten con tales habilidades. + —— Hermann Hesse, Siddhartha +————————————————————————————————————————————————————————————————————————————— + Copyright (c) 2026 Active Thing +————————————————————————————————————————————————————————————————————————————— */ + +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 severityName[0] + } + // 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 severityName[s] +} diff --git a/src/core/errs/stack.go b/src/core/errs/stack.go new file mode 100644 index 0000000..2396a04 --- /dev/null +++ b/src/core/errs/stack.go @@ -0,0 +1,113 @@ +/* —————————————————————————————————————————————————————————————————————————— + Proyecto FWS +————————————————————————————————————————————————————————————————————————————— + Fichero stack.go + Package errs + Autor Juan V. Navarro juanvnl@activething.com + Creado 16/01/2026 +————————————————————————————————————————————————————————————————————————————— + + LICENSES AND TERMS OF USE + ------------------------- + + This software is licensed under the Elastic License v2.0 (the "License"). + For full terms and additional information regarding permitted and prohibited + uses, please visit: + https://activething.com/ATGO/licenses + + You may use, copy, modify, and redistribute this software internally within + your organization for any purpose, including research, development, and + testing, subject to the terms of this License. + + You may NOT, however, use, provide, distribute, or make this software + available to any third party as part of a hosted service, SaaS offering, or + commercial product without first obtaining a commercial license from + Active Thing. + + You may combine this software with other severity, provided that such + combination does not circumvent the restrictions of this License. + + 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 NON-INFRINGEMENT. In no event shall + the authors or copyright holders be liable for any claim, damages, or other + liability arising from the use of this software. + +————————————————————————————————————————————————————————————————————————————— + + Web : activething.com | activething.com/FWS + git : g.activething.com | github.com/activething/FWS + Correo : dev@activething.com + +————————————————————————————————————————————————————————————————————————————— + No deseo caminar sobre el agua", dijo Siddhartha. + Que los antiguos chamanes se contenten con tales habilidades. + —— Hermann Hesse, Siddhartha +————————————————————————————————————————————————————————————————————————————— + Copyright (c) 2026 Active Thing +————————————————————————————————————————————————————————————————————————————— */ + +package errs + +import ( + "runtime" +) + +// Constants used for stack trace configuration and handling. +const ( + // stackSkipFrames defines how many initial frames are skipped when capturing the stack. + // 2 frames are skipped to exclude internal system calls (runtime.Callers and this function). + stackSkipFrames = 2 + + // stackMaxFrames defines the maximum number of frames that will be captured. + // Limits the depth of the stack trace to 32 frames by default. + stackMaxFrames = 32 +) + +// Stack is a slice of program counters, representing a captured stack trace. +type Stack []uintptr + +// CreateStack captures the current execution stack trace. +// It takes an offset (number of frames to skip in addition to the default) +// and maxFrames (maximum number of frames to capture). +// Returns a Stack slice containing the captured program counters. +func CreateStack(options ...int) Stack { + offset := 0 + maxFrames := stackMaxFrames + if len(options) > 0 { + offset = options[0] + if offset < 0 { + offset = 0 + } + if len(options) > 1 { + maxFrames = options[1] + if maxFrames < 0 || maxFrames > stackMaxFrames { + maxFrames = stackMaxFrames + } + } + } + ls := make([]uintptr, maxFrames) + + // Capture the stack trace using runtime.Callers. + // runtime.Callers fills the slice 'ls' with program counters, + // skipping 'offset + stackSkipFrames' frames. + // 'cn' will contain the actual number of frames captured. + cn := runtime.Callers(offset+stackSkipFrames, ls) + + // Return only the slice containing valid program counters (up to 'cn'). + return ls[:cn] +} + +// Frames converts the Stack (slice of program counters) into a slice of StackFrame objects. +// Each StackFrame provides detailed information about a single frame in the stack. +func (s Stack) Frames() []StackFrame { + // Create a slice to store StackFrame objects with the same length as the stack. + f := make([]StackFrame, len(s)) + + // Convert each program counter to a StackFrame object. + for i, p := range s { + f[i] = CreateStackFrame(p) + } + + return f +} diff --git a/src/core/errs/stack_frame.go b/src/core/errs/stack_frame.go new file mode 100644 index 0000000..231fbe7 --- /dev/null +++ b/src/core/errs/stack_frame.go @@ -0,0 +1,165 @@ +/* —————————————————————————————————————————————————————————————————————————— + Proyecto FWS +————————————————————————————————————————————————————————————————————————————— + Fichero stack_frame.go + Package errs + Autor Juan V. Navarro juanvnl@activething.com + Creado 16/01/2026 +————————————————————————————————————————————————————————————————————————————— + + LICENSES AND TERMS OF USE + ------------------------- + + This software is licensed under the Elastic License v2.0 (the "License"). + For full terms and additional information regarding permitted and prohibited + uses, please visit: + https://activething.com/ATGO/licenses + + You may use, copy, modify, and redistribute this software internally within + your organization for any purpose, including research, development, and + testing, subject to the terms of this License. + + You may NOT, however, use, provide, distribute, or make this software + available to any third party as part of a hosted service, SaaS offering, or + commercial product without first obtaining a commercial license from + Active Thing. + + You may combine this software with other severity, provided that such + combination does not circumvent the restrictions of this License. + + 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 NON-INFRINGEMENT. In no event shall + the authors or copyright holders be liable for any claim, damages, or other + liability arising from the use of this software. + +————————————————————————————————————————————————————————————————————————————— + + Web : activething.com | activething.com/FWS + git : g.activething.com | github.com/activething/FWS + Correo : dev@activething.com + +————————————————————————————————————————————————————————————————————————————— + No deseo caminar sobre el agua", dijo Siddhartha. + Que los antiguos chamanes se contenten con tales habilidades. + —— Hermann Hesse, Siddhartha +————————————————————————————————————————————————————————————————————————————— + Copyright (c) 2026 Active Thing +————————————————————————————————————————————————————————————————————————————— */ + +package errs + +import ( + "bufio" + "bytes" + "os" + "runtime" + "strings" +) + +const ( + // stackUnknownInfo is the default value when information about a stack frame cannot be obtained. + stackUnknownInfo = "???" +) + +type ( + // StackFrame represents a single frame in a stack trace. + // It contains detailed information about the function call at that point. + StackFrame struct { + // LineNumber is the line number in the source file where this frame was created. + LineNumber int + + // File is the full path to the source file containing this frame. + File string + + // FuncName is the name of the function where this frame was created, + // with any receiver types removed. + FuncName string + + // Package is the full package path where this frame was created. + Package string + + // Counter is the program counter address for this frame. + Counter uintptr + } +) + +// CreateStackFrame creates a new StackFrame from a program counter address. +// It uses runtime information to populate the frame details. +// Returns an empty StackFrame if the counter is 0. +func CreateStackFrame(counter uintptr) StackFrame { + sf := StackFrame{} + if counter == 0 { + return sf + } + sf.Counter = counter + fn := runtime.FuncForPC(counter) + if fn == nil { + sf.FuncName = stackUnknownInfo + return sf + } + + sf.Package, sf.FuncName = packageAndName(fn) + sf.File, sf.LineNumber = fn.FileLine(counter - 1) + return sf +} + +// SourceLine attempts to retrieve the source severity line for this stack frame. +// It reads the source file and returns the content of the line specified by LineNumber. +// Returns stackUnknownInfo if the line number is invalid or an error occurs while reading the file. +func (f StackFrame) SourceLine() string { + if f.LineNumber <= 0 || f.File == "" { + return stackUnknownInfo + } + + // 1. Verificación rápida: ¿Existe el archivo? + // Evita intentar abrir archivos que no existen (común en Docker/Prod). + if _, err := os.Stat(f.File); os.IsNotExist(err) { + return "" // Retorna vacío silenciosamente en lugar de "???" para no ensuciar logs. + } + + // 2. Lectura segura + file, err := os.Open(f.File) + if err != nil { + return "" + } + defer func() { _ = file.Close() }() + + scanner := bufio.NewScanner(file) + currentLine := 1 + + for scanner.Scan() { + if currentLine == f.LineNumber { + return string(bytes.TrimSpace(scanner.Bytes())) + } + currentLine++ + // Optimización: Si pasamos la línea, dejar de leer. + if currentLine > f.LineNumber { + break + } + } + + return stackUnknownInfo +} + +// packageAndName extracts the package path and function name from a runtime.Func. +// It handles different naming conventions, including methods. +func packageAndName(fn *runtime.Func) (string, string) { + nm := fn.Name() // Get the full name, e.g., "main.(*MyType).MyMethod" or "main.myFunction" + pg := "" // Initialize package path + + // Find the last slash, which separates package path components. + if ls := strings.LastIndex(nm, "/"); ls >= 0 { + pg = nm[:ls] + "/" // Extract the path part + nm = nm[ls+1:] // Keep the rest of the name + } + + // Find the first dot after the last slash, which separates package name from function name. + if pd := strings.Index(nm, "."); pd >= 0 { + pg += nm[:pd] // Add the package name to the path + nm = nm[pd+1:] // Keep the function name + } + + // Replace the Go internal method separator '·' with '.' for readability. + return pg, strings.Replace(nm, "·", ".", -1) +} diff --git a/src/core/errs/tree.go b/src/core/errs/tree.go new file mode 100644 index 0000000..8728401 --- /dev/null +++ b/src/core/errs/tree.go @@ -0,0 +1,204 @@ +/* —————————————————————————————————————————————————————————————————————————— + Proyecto FWS +————————————————————————————————————————————————————————————————————————————— + Fichero tree.go + Package errs + Autor Juan V. Navarro juanvnl@activething.com + Creado 20/01/2026 +————————————————————————————————————————————————————————————————————————————— + + LICENSES AND TERMS OF USE + ------------------------- + + This software is licensed under the Elastic License v2.0 (the "License"). + For full terms and additional information regarding permitted and prohibited + uses, please visit: + https://activething.com/ATGO/licenses + + You may use, copy, modify, and redistribute this software internally within + your organization for any purpose, including research, development, and + testing, subject to the terms of this License. + + You may NOT, however, use, provide, distribute, or make this software + available to any third party as part of a hosted service, SaaS offering, or + commercial product without first obtaining a commercial license from + Active Thing. + + You may combine this software with other code, provided that such + combination does not circumvent the restrictions of this License. + + 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 NON-INFRINGEMENT. In no event shall + the authors or copyright holders be liable for any claim, damages, or other + liability arising from the use of this software. + +————————————————————————————————————————————————————————————————————————————— + + Web : activething.com | activething.com/FWS + git : g.activething.com | github.com/activething/FWS + Correo : dev@activething.com + +————————————————————————————————————————————————————————————————————————————— + No deseo caminar sobre el agua", dijo Siddhartha. + Que los antiguos chamanes se contenten con tales habilidades. + —— Hermann Hesse, Siddhartha +————————————————————————————————————————————————————————————————————————————— + Copyright (c) 2026 Active Thing +————————————————————————————————————————————————————————————————————————————— */ + +package errs + +import ( + "errors" + "fmt" + "io" + "strings" +) + +const MaxDeep = 32 + +type ( + Tree struct { + flags CollectorFlags + maxDeep int + level int + + Err error + Causes []*Tree + Metas []Meta + Stack Stack + } + + TreeFnc func(*Tree) +) + +func NewTree(err error, ops ...TreeFnc) *Tree { + return newTree(err, 0) +} + +func newTree(err error, level int) *Tree { + if err == nil || (level > MaxDeep) { + return nil + } + + node := &Tree{ + Err: err, + level: level, + maxDeep: MaxDeep, + flags: CollectorFlagsAll, + } + + for e := err; e != nil; { + if c, ok := e.(Collectable); ok { + c.Collect(node) + } + e = errors.Unwrap(e) + } + + return node +} + +func (t *Tree) Options() CollectorFlags { return t.flags } + +func (t *Tree) MaxDeep() int { return t.maxDeep } + +// AddStack stores the first captured stack trace. +// +// Once a stack is set, further stack collection is disabled. +// This enforces a single authoritative stack trace per error, +// avoiding noise and preserving a clear origin. +func (t *Tree) AddStack(stack Stack) { + t.Stack = stack + t.flags = t.flags.Del(CollectorFlagStack) +} + +func (t *Tree) AddMetas(meta ...Meta) { t.Metas = append(t.Metas, meta...) } + +func (t *Tree) AddCauses(causes ...error) { + if !t.flags.HasCauses() { + return + } + for _, cause := range causes { + if cause == nil { + continue + } + if child := newTree(cause, t.level+1); child != nil { + t.Causes = append(t.Causes, child) + } + } +} + +func (t *Tree) print(w io.Writer, prefix string, isLast bool) { + marker := "├── " + if isLast { + marker = "└── " + if t.level == 0 { + marker = "─── " + } + } + + // Root error (use Error() for formatted) + fmt.Fprintf(w, "%s%s%v\n", prefix, marker, t.Err.Error()) // Use Error() not %v + + childPrefix := prefix + if isLast { + childPrefix += " " + } else { + childPrefix += "│ " + } + + // All Metas (current) + for i, meta := range t.Metas { + isLastMeta := i == len(t.Metas)-1 && len(t.Causes) == 0 && len(t.Stack) != 0 + metaMarker := "├── " + if isLastMeta { + metaMarker = "└── " + } + fmt.Fprintf(w, "%s%s[%s: %v]\n", childPrefix, metaMarker, meta.Name(), meta.Value()) + } + + // Stack (current) + if len(t.Stack) != 0 { + isLastStack := len(t.Causes) == 0 + stackMarker := "├── " + if isLastStack { + stackMarker = "└── " + } + frames := t.Stack.Frames() + fmt.Fprintf(w, "%s%s[stack: %d frames]\n", childPrefix, stackMarker, len(frames)) + for i := 0; i < len(frames) && i < 3; i++ { + frame := frames[i] + fmt.Fprintf(w, "%s %s:%d %s\n", childPrefix, shortenPath(frame.File), frame.LineNumber, frame.FuncName) + } + } + + // Recurse Causes (show full child) + for i, cause := range t.Causes { + isLastCause := i == len(t.Causes)-1 + cause.print(w, childPrefix, isLastCause) + } +} + +// shortenPath acorta rutas largas para mejor legibilidad +func shortenPath(path string) string { + parts := strings.Split(path, "/") + if len(parts) > 3 { + return ".../" + strings.Join(parts[len(parts)-2:], "/") + } + return path +} + +func (t *Tree) Print(w io.Writer) { + if t == nil { + return + } + t.print(w, "", true) +} + +// String implementa fmt.Stringer para print directo +func (t *Tree) String() string { + var buf strings.Builder + t.Print(&buf) + return buf.String() +} diff --git a/src/core/errs/with_cause.go b/src/core/errs/with_cause.go new file mode 100644 index 0000000..38ee1d8 --- /dev/null +++ b/src/core/errs/with_cause.go @@ -0,0 +1,90 @@ +/* —————————————————————————————————————————————————————————————————————————— + Proyecto FWS +————————————————————————————————————————————————————————————————————————————— + Fichero with_cause.go + Package errs + Autor Juan V. Navarro juanvnl@activething.com + Creado 08/01/2026 +————————————————————————————————————————————————————————————————————————————— + + LICENSES AND TERMS OF USE + ------------------------- + + This software is licensed under the Elastic License v2.0 (the "License"). + For full terms and additional information regarding permitted and prohibited + uses, please visit: + https://activething.com/ATGO/licenses + + You may use, copy, modify, and redistribute this software internally within + your organization for any purpose, including research, development, and + testing, subject to the terms of this License. + + You may NOT, however, use, provide, distribute, or make this software + available to any third party as part of a hosted service, SaaS offering, or + commercial product without first obtaining a commercial license from + Active Thing. + + You may combine this software with other severity, provided that such + combination does not circumvent the restrictions of this License. + + 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 NON-INFRINGEMENT. In no event shall + the authors or copyright holders be liable for any claim, damages, or other + liability arising from the use of this software. + +————————————————————————————————————————————————————————————————————————————— + + Web : activething.com | activething.com/FWS + git : g.activething.com | github.com/activething/FWS + Correo : dev@activething.com + +————————————————————————————————————————————————————————————————————————————— + No deseo caminar sobre el agua", dijo Siddhartha. + Que los antiguos chamanes se contenten con tales habilidades. + —— Hermann Hesse, Siddhartha +————————————————————————————————————————————————————————————————————————————— + Copyright (c) 2026 Active Thing +————————————————————————————————————————————————————————————————————————————— */ + +package errs + +const ( + MetaCauseName = "cause" +) + +type withCause struct { + error + cause error +} + +func AddCause(cause error) Adder { + return AddFnc(func(err error) error { return WithCause(err, cause) }) +} + +func WithCause(err error, cause error) error { + if err == nil { + if cause != nil { + return cause + } + panic(MakePanicError(ErrNilError)) + } + if cause == nil { + return err + } + + return withCause{ + error: err, + cause: cause, + } +} + +func (w withCause) Unwrap() error { return w.error } + +func (w withCause) Cause() error { return w.cause } + +func (w withCause) Collect(c Collector) { + if c.Options().HasCauses() { + c.AddCauses(w.cause) + } +} diff --git a/src/core/errs/with_causes.go b/src/core/errs/with_causes.go new file mode 100644 index 0000000..8ad45e3 --- /dev/null +++ b/src/core/errs/with_causes.go @@ -0,0 +1,163 @@ +/* —————————————————————————————————————————————————————————————————————————— + Proyecto FWS +————————————————————————————————————————————————————————————————————————————— + Fichero with_causes.go + Package errs + Autor Juan V. Navarro juanvnl@activething.com + Creado 15/01/2026 +————————————————————————————————————————————————————————————————————————————— + + LICENSES AND TERMS OF USE + ------------------------- + + This software is licensed under the Elastic License v2.0 (the "License"). + For full terms and additional information regarding permitted and prohibited + uses, please visit: + https://activething.com/ATGO/licenses + + You may use, copy, modify, and redistribute this software internally within + your organization for any purpose, including research, development, and + testing, subject to the terms of this License. + + You may NOT, however, use, provide, distribute, or make this software + available to any third party as part of a hosted service, SaaS offering, or + commercial product without first obtaining a commercial license from + Active Thing. + + You may combine this software with other severity, provided that such + combination does not circumvent the restrictions of this License. + + 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 NON-INFRINGEMENT. In no event shall + the authors or copyright holders be liable for any claim, damages, or other + liability arising from the use of this software. + +————————————————————————————————————————————————————————————————————————————— + + Web : activething.com | activething.com/FWS + git : g.activething.com | github.com/activething/FWS + Correo : dev@activething.com + +————————————————————————————————————————————————————————————————————————————— + No deseo caminar sobre el agua", dijo Siddhartha. + Que los antiguos chamanes se contenten con tales habilidades. + —— Hermann Hesse, Siddhartha +————————————————————————————————————————————————————————————————————————————— + Copyright (c) 2026 Active Thing +————————————————————————————————————————————————————————————————————————————— */ + +package errs + +const MetaCausesName = "causes" + +// withCauses is an internal wrapper that associates one or more *causal errors* +// to an error without changing its semantic identity. +// +// IMPORTANT DESIGN NOTES: +// +// - Causes are NOT part of the Go unwrap chain. +// - Causes do NOT represent "previous" errors in the sense of errors.Is / errors.As. +// - Causes are purely explanatory: they answer *why* an error occurred, +// not *what* error it is. +// +// Each withCauses instance owns ONLY the causes it introduces. +// Causes are NOT inherited, merged, or propagated from wrapped errors. +// This avoids duplication and ensures that causes appear exactly once +// when traversing the error chain. +// +// The wrapped `error` represents the semantic identity of the error +// and is the only value returned by Unwrap(). +type withCauses struct { + error + causes ErrorList // Uses ErrorList to normalize, clean and manage cause lists +} + +// AddCauses returns an Adder that attaches one or more causal errors to an error. +// +// This function is syntactic sugar around WithCauses, intended to be used +// in error composition pipelines. +// +// Causes added through this function are local to the wrapper created by +// WithCauses and are not propagated to inner errors. +func AddCauses(causes ...error) Adder { + return AddFnc(func(err error) error { + return WithCauses(err, causes...) + }) +} + +// WithCauses associates one or more causal errors to err. +// +// Conceptual model: +// +// - `err` represents the *identity* of the error (what failed). +// - `causes` represent the *explanation* (why it failed). +// +// Causes are NOT merged with existing causes in wrapped errors. +// Each call to WithCauses introduces a new causal layer that owns +// its causes exclusively. +// +// This design guarantees that: +// - causes are not duplicated when unwrapping errors +// - the causal graph remains stable and predictable +// - error identity and causality are clearly separated +// +// Behaviour: +// - If err is nil and causes are provided, a MultiError is created. +// - If err is nil and no causes are provided, the function panics. +// - If no valid causes remain after cleaning, err is returned unchanged. +// Example: +// err := errs.WithCauses(baseErr, io.EOF, os.ErrNotExist) +// // err identity = baseErr +// // err causes = [io.EOF, os.ErrNotExist] +// // errors.Is(err, baseErr) == true +// // errors.Is(err, io.EOF) == false (causes not in Is chain) + +func WithCauses(err error, causes ...error) error { + lc := ErrorList(causes).Clean() + + if err == nil { + if len(lc) > 0 { + return MakeMultiError(lc) + } + panic(MakePanicError(ErrNilError)) + } + + // No causes → no wrapper + if len(lc) == 0 { + return err + } + + return withCauses{ + error: err, + causes: lc, + } +} + +// Causes returns the causal errors introduced by this wrapper. +// +// The returned causes are local to this withCauses instance and do not +// include causes from wrapped errors. +func (w withCauses) Causes() []error { + return w.causes +} + +// Unwrap returns the semantic identity of the error. +// +// Causes are intentionally NOT part of the unwrap chain. +// This preserves compatibility with Go's errors.Is / errors.As mechanisms +// and keeps causality orthogonal to error identity. +func (w withCauses) Unwrap() error { + return w.error +} + +// Collect exports the causes to the provided Collector if the collector +// is configured to accept causes. +// +// Cause collection is explicit and opt-in, reinforcing the idea that +// causality is not part of the implicit unwrap traversal. +func (w withCauses) Collect(c Collector) { + if c.Options().HasCauses() && len(w.causes) > 0 { + c.AddCauses(w.causes...) + } +} diff --git a/src/core/errs/with_date.go b/src/core/errs/with_date.go new file mode 100644 index 0000000..e190878 --- /dev/null +++ b/src/core/errs/with_date.go @@ -0,0 +1,91 @@ +/* —————————————————————————————————————————————————————————————————————————— + Proyecto FWS +————————————————————————————————————————————————————————————————————————————— + Fichero with_date.go + Package errs + Autor Juan V. Navarro juanvnl@activething.com + Creado 16/01/2026 +————————————————————————————————————————————————————————————————————————————— + + LICENSES AND TERMS OF USE + ------------------------- + + This software is licensed under the Elastic License v2.0 (the "License"). + For full terms and additional information regarding permitted and prohibited + uses, please visit: + https://activething.com/ATGO/licenses + + You may use, copy, modify, and redistribute this software internally within + your organization for any purpose, including research, development, and + testing, subject to the terms of this License. + + You may NOT, however, use, provide, distribute, or make this software + available to any third party as part of a hosted service, SaaS offering, or + commercial product without first obtaining a commercial license from + Active Thing. + + You may combine this software with other severity, provided that such + combination does not circumvent the restrictions of this License. + + 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 NON-INFRINGEMENT. In no event shall + the authors or copyright holders be liable for any claim, damages, or other + liability arising from the use of this software. + +————————————————————————————————————————————————————————————————————————————— + + Web : activething.com | activething.com/FWS + git : g.activething.com | github.com/activething/FWS + Correo : dev@activething.com + +————————————————————————————————————————————————————————————————————————————— + No deseo caminar sobre el agua", dijo Siddhartha. + Que los antiguos chamanes se contenten con tales habilidades. + —— Hermann Hesse, Siddhartha +————————————————————————————————————————————————————————————————————————————— + Copyright (c) 2026 Active Thing +————————————————————————————————————————————————————————————————————————————— */ + +package errs + +import ( + "time" +) + +const MetaDateName = "date" + +type withDate struct { + error + date time.Time +} + +func AddDate() Adder { + return AddFnc(WithDate) +} + +func WithDate(err error) error { + if err == nil { + panic(MakePanicError(ErrNilError)) + } + return withDate{ + error: err, + date: time.Now(), + } +} + +func (w withDate) Unwrap() error { + return w.error +} + +func (w withDate) Date() time.Time { return w.date } + +// Meta interface implementación +func (w withDate) Name() string { return MetaDateName } +func (w withDate) Value() any { return w.date } + +func (w withDate) Collect(c Collector) { + if c.Options().HasMetas() { + c.AddMetas(w) + } +} diff --git a/src/core/errs/with_info.go b/src/core/errs/with_info.go new file mode 100644 index 0000000..0b3ded2 --- /dev/null +++ b/src/core/errs/with_info.go @@ -0,0 +1,113 @@ +/* —————————————————————————————————————————————————————————————————————————— + Proyecto FWS +————————————————————————————————————————————————————————————————————————————— + Fichero with_info.go + Package errs + Autor Juan V. Navarro juanvnl@activething.com + Creado 16/01/2026 +————————————————————————————————————————————————————————————————————————————— + + LICENSES AND TERMS OF USE + ------------------------- + + This software is licensed under the Elastic License v2.0 (the "License"). + For full terms and additional information regarding permitted and prohibited + uses, please visit: + https://activething.com/ATGO/licenses + + You may use, copy, modify, and redistribute this software internally within + your organization for any purpose, including research, development, and + testing, subject to the terms of this License. + + You may NOT, however, use, provide, distribute, or make this software + available to any third party as part of a hosted service, SaaS offering, or + commercial product without first obtaining a commercial license from + Active Thing. + + You may combine this software with other severity, provided that such + combination does not circumvent the restrictions of this License. + + 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 NON-INFRINGEMENT. In no event shall + the authors or copyright holders be liable for any claim, damages, or other + liability arising from the use of this software. + +————————————————————————————————————————————————————————————————————————————— + + Web : activething.com | activething.com/FWS + git : g.activething.com | github.com/activething/FWS + Correo : dev@activething.com + +————————————————————————————————————————————————————————————————————————————— + No deseo caminar sobre el agua", dijo Siddhartha. + Que los antiguos chamanes se contenten con tales habilidades. + —— Hermann Hesse, Siddhartha +————————————————————————————————————————————————————————————————————————————— + Copyright (c) 2026 Active Thing +————————————————————————————————————————————————————————————————————————————— */ + +package errs + +const ( + MetaInfoDescName = "desc" + MetaInfoHelpName = "help" + MetaInfoLinkName = "link" +) + +type withInfo struct { + error // The wrapped error + 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 +} + +func AddInfo(desc, help, link string) Adder { + return AddFnc(func(e error) error { return WithInfo(e, desc, help, link) }) +} + +func WithInfo(err error, desc, help, link string) error { + if err == nil { + panic(MakePanicError(ErrNilError)) + } + return withInfo{ + error: err, + desc: desc, + help: help, + link: link, + } +} + +// Desc returns the descriptive message about the error. +func (w withInfo) Desc() string { return w.desc } + +// Help returns the help text for resolving the error. +func (w withInfo) Help() string { return w.help } + +// Link returns the documentation link for the error. +func (w withInfo) Link() string { return w.link } + +// Unwrap implements Go 1.13+'s errors.Unwrap interface. +// It returns the wrapped error. +func (w withInfo) Unwrap() error { return w.error } + +func (w withInfo) Collect(collector Collector) { + if collector.Options().HasMetas() { + if collector.Options().HasEmpty() { + collector.AddMetas( + MakeMeta(MetaInfoDescName, w.desc), + MakeMeta(MetaInfoHelpName, w.help), + MakeMeta(MetaInfoLinkName, w.link)) + return + } + if len(w.desc) > 0 { + collector.AddMetas(MakeMeta(MetaInfoDescName, w.desc)) + } + if len(w.help) > 0 { + collector.AddMetas(MakeMeta(MetaInfoHelpName, w.help)) + } + if len(w.link) > 0 { + collector.AddMetas(MakeMeta(MetaInfoLinkName, w.link)) + } + } +} diff --git a/src/core/errs/with_message.go b/src/core/errs/with_message.go new file mode 100644 index 0000000..2248b27 --- /dev/null +++ b/src/core/errs/with_message.go @@ -0,0 +1,93 @@ +/* —————————————————————————————————————————————————————————————————————————— + Proyecto FWS +————————————————————————————————————————————————————————————————————————————— + Fichero with_message.go + Package errs + Autor Juan V. Navarro juanvnl@activething.com + Creado 16/01/2026 +————————————————————————————————————————————————————————————————————————————— + + LICENSES AND TERMS OF USE + ------------------------- + + This software is licensed under the Elastic License v2.0 (the "License"). + For full terms and additional information regarding permitted and prohibited + uses, please visit: + https://activething.com/ATGO/licenses + + You may use, copy, modify, and redistribute this software internally within + your organization for any purpose, including research, development, and + testing, subject to the terms of this License. + + You may NOT, however, use, provide, distribute, or make this software + available to any third party as part of a hosted service, SaaS offering, or + commercial product without first obtaining a commercial license from + Active Thing. + + You may combine this software with other severity, provided that such + combination does not circumvent the restrictions of this License. + + 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 NON-INFRINGEMENT. In no event shall + the authors or copyright holders be liable for any claim, damages, or other + liability arising from the use of this software. + +————————————————————————————————————————————————————————————————————————————— + + Web : activething.com | activething.com/FWS + git : g.activething.com | github.com/activething/FWS + Correo : dev@activething.com + +————————————————————————————————————————————————————————————————————————————— + No deseo caminar sobre el agua", dijo Siddhartha. + Que los antiguos chamanes se contenten con tales habilidades. + —— Hermann Hesse, Siddhartha +————————————————————————————————————————————————————————————————————————————— + Copyright (c) 2026 Active Thing +————————————————————————————————————————————————————————————————————————————— */ + +package errs + +import "fmt" + +const MetaMessageName = "message" + +type withMessage struct { + error + message string +} + +func AddMessage(message string, args ...any) Adder { + return AddFnc(func(err error) error { return WithMessage(err, message, args...) }) +} + +func WithMessage(err error, message string, args ...any) error { + if err == nil { + panic(MakePanicError(ErrNilError)) + } + m := message + if len(args) > 0 { + m = fmt.Sprintf(message, args...) + } + return withMessage{ + error: err, + message: m, + } +} + +func (w withMessage) Unwrap() error { + return w.error +} + +func (w withMessage) Message() string { return w.message } + +// Meta interface implementación +func (w withMessage) Name() string { return MetaMessageName } +func (w withMessage) Value() any { return w.message } + +func (w withMessage) Collect(collector Collector) { + if collector.Options().HasMetas() && (len(w.message) > 0 || collector.Options().HasEmpty()) { + collector.AddMetas(w) + } +} diff --git a/src/core/errs/with_metas.go b/src/core/errs/with_metas.go new file mode 100644 index 0000000..60817aa --- /dev/null +++ b/src/core/errs/with_metas.go @@ -0,0 +1,84 @@ +/* —————————————————————————————————————————————————————————————————————————— + Proyecto FWS +————————————————————————————————————————————————————————————————————————————— + Fichero with_metas.go + Package errs + Autor Juan V. Navarro juanvnl@activething.com + Creado 16/01/2026 +————————————————————————————————————————————————————————————————————————————— + + LICENSES AND TERMS OF USE + ------------------------- + + This software is licensed under the Elastic License v2.0 (the "License"). + For full terms and additional information regarding permitted and prohibited + uses, please visit: + https://activething.com/ATGO/licenses + + You may use, copy, modify, and redistribute this software internally within + your organization for any purpose, including research, development, and + testing, subject to the terms of this License. + + You may NOT, however, use, provide, distribute, or make this software + available to any third party as part of a hosted service, SaaS offering, or + commercial product without first obtaining a commercial license from + Active Thing. + + You may combine this software with other code, provided that such + combination does not circumvent the restrictions of this License. + + 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 NON-INFRINGEMENT. In no event shall + the authors or copyright holders be liable for any claim, damages, or other + liability arising from the use of this software. + +————————————————————————————————————————————————————————————————————————————— + + Web : activething.com | activething.com/FWS + git : g.activething.com | github.com/activething/FWS + Correo : dev@activething.com + +————————————————————————————————————————————————————————————————————————————— + No deseo caminar sobre el agua", dijo Siddhartha. + Que los antiguos chamanes se contenten con tales habilidades. + —— Hermann Hesse, Siddhartha +————————————————————————————————————————————————————————————————————————————— + Copyright (c) 2026 Active Thing +————————————————————————————————————————————————————————————————————————————— */ + +package errs + +type withMetas struct { + error + metas []Meta +} + +func AddMetas(metas ...Meta) Adder { + return AddFnc(func(err error) error { return WithMetas(err, metas...) }) +} + +func WithMetas(err error, metas ...Meta) error { + if err == nil { + panic(MakePanicError(ErrNilError)) + } + if len(metas) == 0 { + return err + } + return withMetas{ + error: err, + metas: metas, + } +} + +func (w withMetas) Metas() []Meta { return w.metas } + +func (w withMetas) Unwrap() error { + return w.error +} + +func (w withMetas) Collect(collector Collector) { + if collector.Options().HasMetas() { + collector.AddMetas(w.metas...) + } +} diff --git a/src/core/errs/with_observed.go b/src/core/errs/with_observed.go new file mode 100644 index 0000000..2ed7238 --- /dev/null +++ b/src/core/errs/with_observed.go @@ -0,0 +1,107 @@ +/* —————————————————————————————————————————————————————————————————————————— + Proyecto FWS +————————————————————————————————————————————————————————————————————————————— + Fichero with_observed.go + Package errs + Autor Juan V. Navarro juanvnl@activething.com + Creado 16/01/2026 +————————————————————————————————————————————————————————————————————————————— + + LICENSES AND TERMS OF USE + ------------------------- + + This software is licensed under the Elastic License v2.0 (the "License"). + For full terms and additional information regarding permitted and prohibited + uses, please visit: + https://activething.com/ATGO/licenses + + You may use, copy, modify, and redistribute this software internally within + your organization for any purpose, including research, development, and + testing, subject to the terms of this License. + + You may NOT, however, use, provide, distribute, or make this software + available to any third party as part of a hosted service, SaaS offering, or + commercial product without first obtaining a commercial license from + Active Thing. + + You may combine this software with other severity, provided that such + combination does not circumvent the restrictions of this License. + + 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 NON-INFRINGEMENT. In no event shall + the authors or copyright holders be liable for any claim, damages, or other + liability arising from the use of this software. + +————————————————————————————————————————————————————————————————————————————— + + Web : activething.com | activething.com/FWS + git : g.activething.com | github.com/activething/FWS + Correo : dev@activething.com + +————————————————————————————————————————————————————————————————————————————— + No deseo caminar sobre el agua", dijo Siddhartha. + Que los antiguos chamanes se contenten con tales habilidades. + —— Hermann Hesse, Siddhartha +————————————————————————————————————————————————————————————————————————————— + Copyright (c) 2026 Active Thing +————————————————————————————————————————————————————————————————————————————— */ + +package errs + +const ( + MetaObservedObserverName = "observed_by" + MetaObservedInstanceName = "observed_instance" +) + +type withObserved struct { + error + observer string + instance string +} + +func AddObserved(observer string, instance ...string) Adder { + return AddFnc(func(err error) error { return WithObserved(err, observer, instance...) }) +} + +func WithObserved(err error, observer string, instance ...string) error { + if err == nil { + panic(MakePanicError(ErrNilError)) + } + var inst string + if len(instance) > 0 { + inst = instance[0] + } + return withObserved{ + error: err, + observer: observer, + instance: inst, + } +} + +// Observer returns the name of the component or service that observed the error. +func (w withObserved) Observer() string { return w.observer } + +// Instance returns the optional instance identifier of the observer. +func (w withObserved) Instance() string { return w.instance } + +// Unwrap implements Go 1.13+'s errors.Unwrap interface. +// It returns the wrapped error. +func (w withObserved) Unwrap() error { return w.error } + +func (w withObserved) Collect(collector Collector) { + if collector.Options().HasMetas() { + if collector.Options().HasEmpty() { + collector.AddMetas( + MakeMeta(MetaObservedObserverName, w.observer), + MakeMeta(MetaObservedInstanceName, w.instance)) + return + } + if len(w.observer) > 0 { + collector.AddMetas(MakeMeta(MetaObservedObserverName, w.observer)) + if len(w.instance) > 0 { + collector.AddMetas(MakeMeta(MetaObservedInstanceName, w.instance)) + } + } + } +} diff --git a/src/core/errs/with_origin.go b/src/core/errs/with_origin.go new file mode 100644 index 0000000..54c7e6b --- /dev/null +++ b/src/core/errs/with_origin.go @@ -0,0 +1,119 @@ +/* —————————————————————————————————————————————————————————————————————————— + Proyecto FWS +————————————————————————————————————————————————————————————————————————————— + Fichero with_origin.go + Package errs + Autor Juan V. Navarro juanvnl@activething.com + Creado 16/01/2026 +————————————————————————————————————————————————————————————————————————————— + + LICENSES AND TERMS OF USE + ------------------------- + + This software is licensed under the Elastic License v2.0 (the "License"). + For full terms and additional information regarding permitted and prohibited + uses, please visit: + https://activething.com/ATGO/licenses + + You may use, copy, modify, and redistribute this software internally within + your organization for any purpose, including research, development, and + testing, subject to the terms of this License. + + You may NOT, however, use, provide, distribute, or make this software + available to any third party as part of a hosted service, SaaS offering, or + commercial product without first obtaining a commercial license from + Active Thing. + + You may combine this software with other severity, provided that such + combination does not circumvent the restrictions of this License. + + 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 NON-INFRINGEMENT. In no event shall + the authors or copyright holders be liable for any claim, damages, or other + liability arising from the use of this software. + +————————————————————————————————————————————————————————————————————————————— + + Web : activething.com | activething.com/FWS + git : g.activething.com | github.com/activething/FWS + Correo : dev@activething.com + +————————————————————————————————————————————————————————————————————————————— + No deseo caminar sobre el agua", dijo Siddhartha. + Que los antiguos chamanes se contenten con tales habilidades. + —— Hermann Hesse, Siddhartha +————————————————————————————————————————————————————————————————————————————— + Copyright (c) 2026 Active Thing +————————————————————————————————————————————————————————————————————————————— */ + +package errs + +const ( + MetaOriginClusterName = "origin_cluster" + MetaOriginServerName = "origin_server" + MetaOriginServiceName = "origin_service" + MetaOriginInstanceName = "origin_instance" +) + +type withOrigin struct { + error + cluster, + server, + service, + instance string +} + +func AddOrigin(cluster, server, service, instance string) Adder { + return AddFnc(func(e error) error { return WithOrigin(e, cluster, server, service, instance) }) +} + +func WithOrigin(err error, cluster, server, service, instance string) error { + if err == nil { + panic(MakePanicError(ErrNilError)) + } + return withOrigin{ + error: err, + cluster: cluster, + server: server, + service: service, + instance: instance, + } +} + +func (w withOrigin) Cluster() string { return w.cluster } + +// Service returns the name of the service where the error originated. +func (w withOrigin) Service() string { return w.service } + +// Instance returns the optional instance identifier of the originating service. +func (w withOrigin) Instance() string { return w.instance } + +// Unwrap implements Go 1.13+'s errors.Unwrap interface. +// It returns the wrapped error. +func (w withOrigin) Unwrap() error { return w.error } + +func (w withOrigin) Collect(collector Collector) { + if collector.Options().HasMetas() { + if collector.Options().HasEmpty() { + collector.AddMetas( + MakeMeta(MetaOriginClusterName, w.cluster), + MakeMeta(MetaOriginServerName, w.server), + MakeMeta(MetaOriginServiceName, w.service), + MakeMeta(MetaOriginInstanceName, w.instance)) + return + } + if len(w.cluster) > 0 { + collector.AddMetas(MakeMeta(MetaOriginClusterName, w.cluster)) + } + if len(w.server) > 0 { + collector.AddMetas(MakeMeta(MetaOriginServerName, w.server)) + } + if len(w.service) > 0 { + collector.AddMetas(MakeMeta(MetaOriginServiceName, w.service)) + } + if len(w.instance) > 0 { + collector.AddMetas(MakeMeta(MetaOriginInstanceName, w.instance)) + } + } +} diff --git a/src/core/errs/with_retryability.go b/src/core/errs/with_retryability.go new file mode 100644 index 0000000..7e53aec --- /dev/null +++ b/src/core/errs/with_retryability.go @@ -0,0 +1,138 @@ +/* —————————————————————————————————————————————————————————————————————————— + Proyecto FWS +————————————————————————————————————————————————————————————————————————————— + Fichero with_retryability.go + Package errs + Autor Juan V. Navarro juanvnl@activething.com + Creado 16/01/2026 +————————————————————————————————————————————————————————————————————————————— + + LICENSES AND TERMS OF USE + ------------------------- + + This software is licensed under the Elastic License v2.0 (the "License"). + For full terms and additional information regarding permitted and prohibited + uses, please visit: + https://activething.com/ATGO/licenses + + You may use, copy, modify, and redistribute this software internally within + your organization for any purpose, including research, development, and + testing, subject to the terms of this License. + + You may NOT, however, use, provide, distribute, or make this software + available to any third party as part of a hosted service, SaaS offering, or + commercial product without first obtaining a commercial license from + Active Thing. + + You may combine this software with other severity, provided that such + combination does not circumvent the restrictions of this License. + + 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 NON-INFRINGEMENT. In no event shall + the authors or copyright holders be liable for any claim, damages, or other + liability arising from the use of this software. + +————————————————————————————————————————————————————————————————————————————— + + Web : activething.com | activething.com/FWS + git : g.activething.com | github.com/activething/FWS + Correo : dev@activething.com + +————————————————————————————————————————————————————————————————————————————— + No deseo caminar sobre el agua", dijo Siddhartha. + Que los antiguos chamanes se contenten con tales habilidades. + —— Hermann Hesse, Siddhartha +————————————————————————————————————————————————————————————————————————————— + Copyright (c) 2026 Active Thing +————————————————————————————————————————————————————————————————————————————— */ + +package errs + +import ( + "time" +) + +const ( + MetaRetryEnabledName = "retry_enabled" + MetaRetryTemporaryName = "retry_temporary" + MetaRetryIdempotentName = "retry_idempotent" + MetaRetryAfterName = "retry_after" +) + +const ( + // RetryEnabled is a flag indicating the error is potentially retryable. + RetryEnabled uint8 = 1 << iota + // RetryIsTemporary is a flag indicating the error is temporary and might resolve on retry. + RetryIsTemporary + // RetryRetryAfterIsSet is a flag indicating that the 'After' duration is set and should be used for retry delay. + RetryRetryAfterIsSet + // RetryIsIdempotent is a flag indicating that retrying the operation is idempotent (safe to repeat). + RetryIsIdempotent +) + +type ( + // withRetry is a type that wraps an error and adds information about its retryability. + // This is useful for implementing retry logic in clients or middleware. + withRetry struct { + error // The wrapped error + flags uint8 // A bitmask of Retry flags + after time.Duration // The suggested duration to wait before retrying (only valid if RetryRetryAfterIsSet is set in flags) + } +) + +// AddRetry creates a Adder that adds retryable information to an error. +// Takes a bitmask of flags and an optional retry-after duration. +func AddRetry(ops uint8, after time.Duration) Adder { + return AddFnc(func(e error) error { + return WithRetry(e, ops, after) + }) +} + +// WithRetry creates a new withRetry 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 WithRetry(err error, ops uint8, retryAfter time.Duration) error { + if err == nil { + panic(MakePanicError(ErrNilError)) + } + return withRetry{ + error: err, + flags: ops, + after: retryAfter, + } +} + +// CanRetry returns true if the RetryEnabled flag is set. +func (w withRetry) CanRetry() bool { return w.flags&RetryEnabled != 0 } + +// IsTemporary returns true if the RetryIsTemporary flag is set. +func (w withRetry) IsTemporary() bool { return w.flags&RetryIsTemporary != 0 } + +// IsIdempotent returns true if the RetryIsIdempotent flag is set. +func (w withRetry) IsIdempotent() bool { return w.flags&RetryIsIdempotent != 0 } + +// After returns the retry-after duration and a boolean indicating if it was explicitly set. +func (w withRetry) After() time.Duration { + if w.flags&RetryRetryAfterIsSet != 0 { + return w.after + } + return -1 +} + +// Unwrap implements Go 1.13+'s errors.Unwrap interface. +// It returns the wrapped error. +func (w withRetry) Unwrap() error { return w.error } + +func (w withRetry) Collect(collector Collector) { + if collector.Options().HasMetas() { + collector.AddMetas( + MakeMeta(MetaRetryEnabledName, w.CanRetry()), + MakeMeta(MetaRetryTemporaryName, w.IsTemporary()), + MakeMeta(MetaRetryIdempotentName, w.IsIdempotent())) + if dur := w.After(); dur != -1 { + collector.AddMetas(MakeMeta(MetaRetryAfterName, dur)) + } + return + } +} diff --git a/src/core/errs/with_severity.go b/src/core/errs/with_severity.go new file mode 100644 index 0000000..00949c9 --- /dev/null +++ b/src/core/errs/with_severity.go @@ -0,0 +1,88 @@ +/* —————————————————————————————————————————————————————————————————————————— + Proyecto FWS +————————————————————————————————————————————————————————————————————————————— + Fichero with_severity.go + Package errs + Autor Juan V. Navarro juanvnl@activething.com + Creado 16/01/2026 +————————————————————————————————————————————————————————————————————————————— + + LICENSES AND TERMS OF USE + ------------------------- + + This software is licensed under the Elastic License v2.0 (the "License"). + For full terms and additional information regarding permitted and prohibited + uses, please visit: + https://activething.com/ATGO/licenses + + You may use, copy, modify, and redistribute this software internally within + your organization for any purpose, including research, development, and + testing, subject to the terms of this License. + + You may NOT, however, use, provide, distribute, or make this software + available to any third party as part of a hosted service, SaaS offering, or + commercial product without first obtaining a commercial license from + Active Thing. + + You may combine this software with other severity, provided that such + combination does not circumvent the restrictions of this License. + + 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 NON-INFRINGEMENT. In no event shall + the authors or copyright holders be liable for any claim, damages, or other + liability arising from the use of this software. + +————————————————————————————————————————————————————————————————————————————— + + Web : activething.com | activething.com/FWS + git : g.activething.com | github.com/activething/FWS + Correo : dev@activething.com + +————————————————————————————————————————————————————————————————————————————— + No deseo caminar sobre el agua", dijo Siddhartha. + Que los antiguos chamanes se contenten con tales habilidades. + —— Hermann Hesse, Siddhartha +————————————————————————————————————————————————————————————————————————————— + Copyright (c) 2026 Active Thing +————————————————————————————————————————————————————————————————————————————— */ + +package errs + +import "fmt" + +const MetaSeverityName = "severity" + +type withSeverity struct { + error + severity Severity +} + +func AddSeverity(severity Severity) Adder { + return AddFnc(func(err error) error { return WithSeverity(err, severity) }) +} + +func WithSeverity(err error, severity Severity) error { + if err == nil { + panic(MakePanicError(ErrNilError)) + } + return withSeverity{ + error: err, + severity: severity, + } +} + +func (w withSeverity) Severity() Severity { return w.severity } + +// Meta interface implementación +func (w withSeverity) Name() string { return MetaSeverityName } +func (w withSeverity) Value() any { return w.severity } + +func (w withSeverity) Unwrap() error { return w.error } + +func (w withSeverity) Collect(collector Collector) { + fmt.Printf("%v\n", collector.Options()) + if collector.Options().HasMetas() { + collector.AddMetas(w) + } +} diff --git a/src/core/errs/with_stack.go b/src/core/errs/with_stack.go new file mode 100644 index 0000000..08a6b37 --- /dev/null +++ b/src/core/errs/with_stack.go @@ -0,0 +1,96 @@ +/* —————————————————————————————————————————————————————————————————————————— + Proyecto FWS +————————————————————————————————————————————————————————————————————————————— + Fichero with_stack.go + Package errs + Autor Juan V. Navarro juanvnl@activething.com + Creado 16/01/2026 +————————————————————————————————————————————————————————————————————————————— + + LICENSES AND TERMS OF USE + ------------------------- + + This software is licensed under the Elastic License v2.0 (the "License"). + For full terms and additional information regarding permitted and prohibited + uses, please visit: + https://activething.com/ATGO/licenses + + You may use, copy, modify, and redistribute this software internally within + your organization for any purpose, including research, development, and + testing, subject to the terms of this License. + + You may NOT, however, use, provide, distribute, or make this software + available to any third party as part of a hosted service, SaaS offering, or + commercial product without first obtaining a commercial license from + Active Thing. + + You may combine this software with other severity, provided that such + combination does not circumvent the restrictions of this License. + + 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 NON-INFRINGEMENT. In no event shall + the authors or copyright holders be liable for any claim, damages, or other + liability arising from the use of this software. + +————————————————————————————————————————————————————————————————————————————— + + Web : activething.com | activething.com/FWS + git : g.activething.com | github.com/activething/FWS + Correo : dev@activething.com + +————————————————————————————————————————————————————————————————————————————— + No deseo caminar sobre el agua", dijo Siddhartha. + Que los antiguos chamanes se contenten con tales habilidades. + —— Hermann Hesse, Siddhartha +————————————————————————————————————————————————————————————————————————————— + Copyright (c) 2026 Active Thing +————————————————————————————————————————————————————————————————————————————— */ + +package errs + +const MetaStackName = "stack" + +type withStack struct { + error + stack Stack +} + +func AddStack(values ...int) Adder { + return AddFnc(func(err error) error { return WithStack(err, values...) }) +} + +func WithStack(err error, values ...int) error { + if err == nil { + panic(MakePanicError(ErrNilError)) + } + // Determine offset and max frames from optional values. + ox, mx := 0, -1 + if len(values) > 0 { + ox = values[0] + if len(values) > 1 { + mx = values[1] + } + } + + return withStack{ + error: err, + stack: CreateStack(ox, mx), // Capture the stack trace. + } +} + +func (w withStack) Stack() Stack { return w.stack } + +func (w withStack) Unwrap() error { + return w.error +} + +// Meta interface implementación +func (w withStack) Name() string { return MetaStackName } +func (w withStack) Value() any { return w.stack } + +func (w withStack) Collect(collector Collector) { + if collector.Options().HasStack() { + collector.AddStack(w.stack) + } +} diff --git a/src/core/errs/with_status.go b/src/core/errs/with_status.go new file mode 100644 index 0000000..48d08cd --- /dev/null +++ b/src/core/errs/with_status.go @@ -0,0 +1,84 @@ +/* —————————————————————————————————————————————————————————————————————————— + Proyecto FWS +————————————————————————————————————————————————————————————————————————————— + Fichero with_status.go + Package errs + Autor Juan V. Navarro juanvnl@activething.com + Creado 16/01/2026 +————————————————————————————————————————————————————————————————————————————— + + LICENSES AND TERMS OF USE + ------------------------- + + This software is licensed under the Elastic License v2.0 (the "License"). + For full terms and additional information regarding permitted and prohibited + uses, please visit: + https://activething.com/ATGO/licenses + + You may use, copy, modify, and redistribute this software internally within + your organization for any purpose, including research, development, and + testing, subject to the terms of this License. + + You may NOT, however, use, provide, distribute, or make this software + available to any third party as part of a hosted service, SaaS offering, or + commercial product without first obtaining a commercial license from + Active Thing. + + You may combine this software with other severity, provided that such + combination does not circumvent the restrictions of this License. + + 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 NON-INFRINGEMENT. In no event shall + the authors or copyright holders be liable for any claim, damages, or other + liability arising from the use of this software. + +————————————————————————————————————————————————————————————————————————————— + + Web : activething.com | activething.com/FWS + git : g.activething.com | github.com/activething/FWS + Correo : dev@activething.com + +————————————————————————————————————————————————————————————————————————————— + No deseo caminar sobre el agua", dijo Siddhartha. + Que los antiguos chamanes se contenten con tales habilidades. + —— Hermann Hesse, Siddhartha +————————————————————————————————————————————————————————————————————————————— + Copyright (c) 2026 Active Thing +————————————————————————————————————————————————————————————————————————————— */ + +package errs + +const MetaStatusName = "status" // Cambiado para no colisionar con "severity" + +type withStatus struct { + error + status int +} + +func AddStatus(status int) Adder { + return AddFnc(func(err error) error { return WithStatus(err, status) }) +} + +func WithStatus(err error, status int) error { + if err == nil { + panic(MakePanicError(ErrNilError)) + } + return withStatus{ + error: err, + status: status, + } +} + +// Status devuelve el código de estado numérico asociado. +func (w withStatus) Status() int { return w.status } + +// Meta interface implementación +func (w withStatus) Name() string { return MetaStatusName } +func (w withStatus) Value() any { return w.status } + +func (w withStatus) Collect(collector Collector) { + if collector.Options().HasMetas() { + collector.AddMetas(w) + } +} diff --git a/src/core/errs/with_user.go b/src/core/errs/with_user.go new file mode 100644 index 0000000..5ac1425 --- /dev/null +++ b/src/core/errs/with_user.go @@ -0,0 +1,88 @@ +/* —————————————————————————————————————————————————————————————————————————— + Proyecto FWS +————————————————————————————————————————————————————————————————————————————— + Fichero with_user.go + Package errs + Autor Juan V. Navarro juanvnl@activething.com + Creado 16/01/2026 +————————————————————————————————————————————————————————————————————————————— + + LICENSES AND TERMS OF USE + ------------------------- + + This software is licensed under the Elastic License v2.0 (the "License"). + For full terms and additional information regarding permitted and prohibited + uses, please visit: + https://activething.com/ATGO/licenses + + You may use, copy, modify, and redistribute this software internally within + your organization for any purpose, including research, development, and + testing, subject to the terms of this License. + + You may NOT, however, use, provide, distribute, or make this software + available to any third party as part of a hosted service, SaaS offering, or + commercial product without first obtaining a commercial license from + Active Thing. + + You may combine this software with other severity, provided that such + combination does not circumvent the restrictions of this License. + + 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 NON-INFRINGEMENT. In no event shall + the authors or copyright holders be liable for any claim, damages, or other + liability arising from the use of this software. + +————————————————————————————————————————————————————————————————————————————— + + Web : activething.com | activething.com/FWS + git : g.activething.com | github.com/activething/FWS + Correo : dev@activething.com + +————————————————————————————————————————————————————————————————————————————— + No deseo caminar sobre el agua", dijo Siddhartha. + Que los antiguos chamanes se contenten con tales habilidades. + —— Hermann Hesse, Siddhartha +————————————————————————————————————————————————————————————————————————————— + Copyright (c) 2026 Active Thing +————————————————————————————————————————————————————————————————————————————— */ + +package errs + +const MetaUserName = "user" + +type withUser struct { + error + user string +} + +func AddUser(user string) Adder { + return AddFnc(func(err error) error { return WithUser(err, user) }) +} + +func WithUser(err error, user string) error { + if err == nil { + panic(MakePanicError(ErrNilError)) + } + return withUser{ + + error: err, + user: user, + } +} + +func (w withUser) Unwrap() error { + return w.error +} + +func (w withUser) User() string { return w.user } + +// Meta interface implementación +func (w withUser) Name() string { return MetaUserName } +func (w withUser) Value() any { return w.user } + +func (w withUser) Collect(collector Collector) { + if collector.Options().HasMetas() { + collector.AddMetas(w) + } +} diff --git a/src/go.mod b/src/go.mod new file mode 100644 index 0000000..794dd9d --- /dev/null +++ b/src/go.mod @@ -0,0 +1,3 @@ +module src + +go 1.25 diff --git a/src/go.work b/src/go.work new file mode 100644 index 0000000..d0ed84f --- /dev/null +++ b/src/go.work @@ -0,0 +1,9 @@ +go 1.25 + + +use ( + + ./core/errs + ./apps/apperrs + +) \ No newline at end of file