Errs module

master
dev 9 months ago
parent f41f738621
commit fb2243c3b1

@ -56,22 +56,7 @@ import (
)
func main() {
baseErr := errs.Code("auth::err_auth")
err := errs.WithMessage(
errs.WithCause(
errs.WithSeverity(
errs.WithStack(baseErr),
errs.SeverityCritical,
),
errs.WithCauses(
errors.New("db error"),
errors.New("sql error")),
),
"authentication failed",
)
errs.NewTree(err).Print(os.Stdout)
otro := errs.Add(
errs.Code("::Mi_otro_error"),
@ -81,9 +66,13 @@ func main() {
errs.AddCauses(
errors.New("::db error"),
errors.New("::sql error"),
errs.WithStack(errs.WithMessage(errs.Code("::error_cause"),"Esto es un mensaje %d ",3333))))
errs.WithStack(
errs.WithCause(
errs.WithMessage(
errs.Code("::error_cause"),"Esto es un mensaje %d ",3333),
errs.WithStack(errs.Code("POr otro error"),10)))))
errs.NewTree(otro).Print(os.Stdout)
errs.NewTree(otro,errs.WithMaxStackDepth(-1)).Print(os.Stdout)

@ -66,6 +66,12 @@ const (
var (
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"}
// Global sentinels (no module prefix)
ErrNilError = Code(RootModule + ModuleSep + "nil-error")
ErrNilCause = Code(RootModule + ModuleSep + "nil-cause")
@ -76,10 +82,9 @@ var (
ErrInvalidMetaName = Code(ErrsModule + ModuleSep + "invalid-meta-name")
ErrInvalidParamName = Code(ErrsModule + ModuleSep + "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"}
ErrorExtractors = NewExtractors()
)
func isValidSegment(code string) bool { return len(code) > 0 && codeRgx.MatchString(code) }

@ -0,0 +1,68 @@
/* ——————————————————————————————————————————————————————————————————————————
Proyecto FWS
—————————————————————————————————————————————————————————————————————————————
Fichero extractor.go
Package errs
Autor Juan V. Navarro juanvnl@activething.com
Creado 24/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 (
Extractor interface {
Extract (err error, collector Collector)
}
ExtractFnc func(err error, collector Collector)
)
func (f ExtractFnc) Extract(err error, collector Collector) { f(err, collector) }

@ -0,0 +1,81 @@
/* ——————————————————————————————————————————————————————————————————————————
Proyecto FWS
—————————————————————————————————————————————————————————————————————————————
Fichero Extractors.go
Package errs
Autor Juan V. Navarro juanvnl@activething.com
Creado 24/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 "sync"
type Extractors struct {
rw sync.RWMutex
extractors []Extractor
}
func NewExtractors() *Extractors { return &Extractors{} }
func (e *Extractors) AddExtractors(extractors ... Extractor) {
if len(extractors) == 0 { return }
e.rw.Lock(); defer e.rw.Unlock()
for _,x := range extractors {
if x != nil { e.extractors = append(e.extractors, x) }
}
}
func (e *Extractors) Extract (err error, collector Collector) {
e.rw.RLock()
defer e.rw.RUnlock()
for _,x := range e.extractors { x.Extract(err,collector) }
}

@ -59,22 +59,17 @@ import (
const MaxDeep = 32
const (
StackStrategyNone uint8 = iota
StackStrategyTop // Solo el primer stack encontrado
StackStrategyRoot // Solo el último stack (el más profundo)
StackStrategyAll // Todos los stack disponibles se recopilan
)
type (
Tree struct {
maxExtDepth int // Define si se van a usar extractores (-1) si a todos los niveles (0) ninguno (x) hasta ese nivel
maxDepth int
// Define hasta que profundidad del arbol de causas se recopilaran metas
maxMetaDepth int
// Define hasta que profundidad del arbol se añadira la pila
maxStackDepth int
// define la estrategia de incorporar el stack al arbol
stackStrategy uint8
maxMetaDepth int // Define hasta que profundidad del arbol de causas se recopilaran metas
// Control Vertical (Profundidad del árbol)
// -1: Infinito (captura en todos los niveles)
// 0: Off (no captura stacks en absoluto)
// >0: Captura hasta el nivel N (ej: 1 = solo raíz)
maxStackDepth int // Define hasta que profundidad del arbol se añadira la pila
stackFirst bool // indica true que el primer stack que se captura es el primero, si no es el ultimo, solo se captura uno por nivel de nodo
root *TreeNode
}
@ -83,9 +78,11 @@ type (
func NewTree(err error, ops ...opts.Option[Tree]) *Tree {
tree := opts.Options[Tree](ops).ApplyAndRet (&Tree{
maxExtDepth : MaxDeep,
maxDepth : MaxDeep,
maxMetaDepth : MaxDeep,
stackStrategy: StackStrategyTop,
maxStackDepth: MaxDeep,
stackFirst : true,
})
tree.root = newTreeNode(err,0,tree)
return tree
@ -132,74 +129,108 @@ func (t *Tree) String() string {
// RenderTree imprime la jerarquía de errores de manera más visual y semántica.
// Representa solo las causas como árbol. Metas y stack como atributos del nodo.
// RenderTree imprime la jerarquía de errores de manera visual y semántica.
// Iconos:
// ❌ error
// ℹ️ meta
// ⚠️ stack
// ❌ Error
// ℹ️ Meta / atributo
// ⚠️ Stack
// Conectores para causas:
// ├─▶ (child)
// └─▶ (last)
func RenderTree(t *Tree, w io.Writer) error {
var walker func(*TreeNode, string, bool) error
walker = func(n *TreeNode, prefix string, isLast bool) error {
// Dibujar línea actual
connector := "├── "
if isLast {
connector = "└── "
var walk func(n *TreeNode, indent string, indent_width int, hasSibling bool, isRoot bool) error
walk = func(n *TreeNode, indent string, indent_width int, hasSibling bool, isRoot bool) error {
// --- imprimir nodo ---
var pre_width int
if isRoot {
fmt.Fprintf(w, "❌ %v\n", n.Err)
pre_width = 3 // width of "❌ " (2 for emoji + 1 for space)
} else {
conn := "├─▶ "
conn_width := 4 // width of "├─▶ " or "└─▶ "
if !hasSibling {
conn = "└─▶ "
}
fmt.Fprintf(w, "%s%s❌ %v\n", indent, conn, n.Err)
pre_width = indent_width + conn_width + 3 // +3 for "❌ "
}
if _, err := fmt.Fprintf(w, "%s%s%v\n", prefix, connector, n.Err); err != nil {
return err
attrIndent := strings.Repeat(" ", pre_width)
// --- imprimir metas ---
for _, meta := range n.Metas {
fmt.Fprintf(w, "%sℹ️ %s = %v\n", attrIndent, meta.Name(), meta.Value())
}
// Preparar prefijo para hijos
newPrefix := prefix
if isLast {
newPrefix += " "
// --- imprimir stack ---
if len(n.Stack) > 0 {
frames := n.Stack.Frames()
fmt.Fprintf(w, "%s⚠️ stack (%d frames)\n", attrIndent, len(frames))
for _, frame := range frames {
fmt.Fprintf(w, "%s %s:%d %s\n", attrIndent, shortenPath(frame.File), frame.LineNumber, frame.FuncName)
}
}
// --- recorrer hijos ---
for i, c := range n.Causes {
last := i == len(n.Causes)-1
// calcular indent para hijos
childIndent := indent
childWidth := indent_width
if !isRoot {
if hasSibling {
childIndent += "│ "
childWidth += 4
} else {
newPrefix += "│ "
childIndent += " "
childWidth += 4
}
// All Metas (current)
for i, meta := range n.Metas {
isLastMeta := i == len(n.Metas)-1 && len(n.Causes) == 0 && len(n.Stack) != 0
metaMarker := "├── "
if isLastMeta {
metaMarker = "└── "
}
fmt.Fprintf(w, "%s%s[%s: %v]\n", newPrefix, metaMarker, meta.Name(), meta.Value())
if err := walk(c, childIndent, childWidth, !last, false); err != nil {
return err
}
// Stack (current)
if len(n.Stack) != 0 {
isLastStack := len(n.Causes) == 0
stackMarker := "├── "
if isLastStack {
stackMarker = "└── "
}
frames := n.Stack.Frames()
fmt.Fprintf(w, "%s%s[stack: %d frames]\n", newPrefix, stackMarker, len(frames))
for i := 0; i < len(frames) && i < 3; i++ {
frame := frames[i]
fmt.Fprintf(w, "%s %s:%d %s\n", newPrefix, shortenPath(frame.File), frame.LineNumber, frame.FuncName)
return nil
}
return walk(t.root, "", 0, false, true)
}
// Recorrer hijos
for i, child := range n.Causes {
lastChild := i == len(n.Causes)-1
if err := walker(child, newPrefix, lastChild); err != nil {
return err
// WithMaxDepth establece el límite máximo de profundidad para causas en el árbol.
// Usa -1 para ilimitado (UnlimitedDepth).
func WithMaxDepth(depth int) opts.Option[Tree] {
if depth < -1 { depth = 0 }
return func(t *Tree) { t.maxDepth = depth }}
// WithMaxMetaDepth establece el límite de profundidad para recopilar metas.
// Usa -1 para ilimitado.
func WithMaxMetaDepth(depth int) opts.Option[Tree] {
if depth < -1 { depth = 0 }
return func(t *Tree) { t.maxMetaDepth = depth }}
// WithMaxStackDepth establece el límite de profundidad para recopilar stacks.
// Usa -1 para ilimitado.
func WithMaxStackDepth(depth int) opts.Option[Tree] {
if depth < -1 { depth = 0 }
return func(t *Tree) { t.maxStackDepth = depth }}
// WithStackFirst establece la estrategia para incorporar stacks al árbol.
func WithStackFirst(first bool) opts.Option[Tree] {
return func(t *Tree) { t.stackFirst = first }
}
func WithExtractors (depth int) opts.Option[Tree] {
if depth < -1 { depth = 0 }
return func(t *Tree) {
t.maxExtDepth = depth
}
return nil
}
return walker(t.root, "", true)
}

@ -51,6 +51,7 @@ package errs
import (
"errors"
"fmt"
)
type TreeNode struct {
@ -80,7 +81,9 @@ func newTreeNode (err error, level int, tree *Tree) *TreeNode {
}
// intentamos extraer meta informacion de el error por otros
// medios y extractores definidos a nivel global
// TODO
if tree.maxExtDepth != 0 && level < tree.maxExtDepth {
ErrorExtractors.Extract(e,node)
}
}
return node
@ -114,26 +117,26 @@ func (n *TreeNode) AddCauses(causes ...error) {
}
}
func (n *TreeNode) AddStack(stack Stack) {
if stack == nil || n.tree.stackStrategy == StackStrategyNone {
fmt.Println("CXaptturando stack %d level %d\n",n.tree.stackFirst,n.tree.stackFirst)
// 1. VALIDACIÓN GLOBAL (Nivel 0 = OFF)
// 2. VALIDACIÓN VERTICAL (Profundidad)
// Si NO es infinito (-1) Y el nivel actual supera o iguala el límite.
// Ej: maxStackDepth=1. Acepta nivel 0. Rechaza nivel 1.
if stack == nil || n.tree.maxStackDepth == 0 || (n.tree.maxStackDepth > 0 && n.level >= n.tree.maxStackDepth) {
return
}
// Filtro por profundidad de stack
if n.tree.maxStackDepth != -1 && n.level > n.tree.maxStackDepth {
return
// 3. VALIDACIÓN HORIZONTAL (Linealidad dentro del nodo)
if n.tree.stackFirst {
if n.Stack == nil {
n.Stack = stack
}
// Aplicación de estrategia (Ejemplo: Top solo en nivel 0)
if n.tree.stackStrategy == StackStrategyTop && n.level != 0 {
return
}
// Evitamos sobrescribir si ya tiene stack (o permitimos append según lógica deseada)
if len(n.Stack) == 0 {
n.Stack = stack
}
}

Loading…
Cancel
Save

Powered by TurnKey Linux.