Errs module

master
dev 9 months ago
parent fb2243c3b1
commit e80fcdbb42

@ -72,7 +72,7 @@ func main() {
errs.Code("::error_cause"),"Esto es un mensaje %d ",3333),
errs.WithStack(errs.Code("POr otro error"),10)))))
errs.NewTree(otro,errs.WithMaxStackDepth(-1)).Print(os.Stdout)
errs.CTreeWriter(errs.NewTree(otro,errs.WithMaxStackDepth(-1)),os.Stdout)

@ -53,6 +53,7 @@ import (
"errors"
"fmt"
"regexp"
"sync"
)
const (
@ -66,11 +67,14 @@ const (
var (
rw = sync.RWMutex{}
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"}
defaultRender= TreeWriter(WriteTreeFnc(CTreeWriter))
// Global sentinels (no module prefix)
ErrNilError = Code(RootModule + ModuleSep + "nil-error")
@ -89,6 +93,28 @@ var (
func isValidSegment(code string) bool { return len(code) > 0 && codeRgx.MatchString(code) }
func SetDefaultTreeWriter (treeWriter TreeWriter) {
rw.RLock()
if treeWriter == nil {
defaultRender = WriteTreeFnc(CTreeWriter)
} else {
defaultRender = treeWriter
}
rw.RUnlock()
}
func GetDefaultTreeWriter () TreeWriter {
rw.RLock()
wt := defaultRender
rw.RUnlock()
return wt
}
func ToError(err any) error {
if err == nil {
return nil

@ -49,30 +49,61 @@
package errs
import "sync"
import (
"reflect"
"sync"
)
type Extractors struct {
rw sync.RWMutex
extractors []Extractor
byType map[reflect.Type]ExtractFnc
interfaces []Extractor
}
func NewExtractors() *Extractors { return &Extractors{} }
func NewExtractors() *Extractors {
return &Extractors{}
}
func (e *Extractors) RegisterByType (err error, fn ExtractFnc) {
if err == nil || fn == nil {
return
}
tp := reflect.TypeOf(err)
e.rw.Lock()
if e.byType == nil {
e.byType = make(map[reflect.Type]ExtractFnc)
}
e.byType[tp] = fn
defer e.rw.Unlock()
}
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) RegisterInterface(fnc ExtractFnc) {
if fnc == nil {
return
}
e.rw.Lock()
e.interfaces = append(e.interfaces, fnc)
e.rw.Unlock()
}
func (e *Extractors) Extract (err error, collector Collector) {
rf := reflect.TypeOf(err)
e.rw.RLock()
defer e.rw.RUnlock()
for _,x := range e.extractors { x.Extract(err,collector) }
if ex,ok := e.byType[rf]; ok {
e.rw.Unlock()
ex(err,collector)
return
}
for _,x := range e.interfaces {
x.Extract(err,collector)
}
e.rw.RUnlock()
}

@ -51,7 +51,6 @@ package errs
import (
"core/opts"
"fmt"
"io"
"strings"
)
@ -98,106 +97,30 @@ func (t *Tree) Walk (walker TreeWalkFnc) {
// 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) {
func (t *Tree) Write(w io.Writer, treeWriter TreeWriter) error {
if t == nil || t.root == nil {
return
return nil
}
_ = RenderTree(t,w)
if treeWriter != nil {
return treeWriter.Write(t,w)
}
return GetDefaultTreeWriter().Write(t,w)
}
// String implementa fmt.Stringer para print directo
func (t *Tree) String() string {
func (t *Tree) String(treeWriter TreeWriter) string {
if t == nil || t.root == nil {
return ""
}
var buf strings.Builder
// Reutilizamos el renderizador pasando el builder de strings
_ = RenderTree(t, &buf)
_ = t.Write(&buf, treeWriter)
return buf.String()
}
// RenderTree imprime la jerarquía de errores de manera visual y semántica.
// Iconos:
// ❌ Error
// ℹ️ Meta / atributo
// ⚠️ Stack
// Conectores para causas:
// ├─▶ (child)
// └─▶ (last)
func RenderTree(t *Tree, w io.Writer) error {
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 "❌ "
}
attrIndent := strings.Repeat(" ", pre_width)
// --- imprimir metas ---
for _, meta := range n.Metas {
fmt.Fprintf(w, "%sℹ️ %s = %v\n", attrIndent, meta.Name(), meta.Value())
}
// --- 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 {
childIndent += " "
childWidth += 4
}
}
if err := walk(c, childIndent, childWidth, !last, false); err != nil {
return err
}
}
return nil
}
return walk(t.root, "", 0, false, true)
}
// WithMaxDepth establece el límite máximo de profundidad para causas en el árbol.

@ -51,7 +51,6 @@ package errs
import (
"errors"
"fmt"
)
type TreeNode struct {
@ -118,7 +117,6 @@ func (n *TreeNode) AddCauses(causes ...error) {
}
func (n *TreeNode) AddStack(stack Stack) {
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.

@ -0,0 +1,199 @@
/* ——————————————————————————————————————————————————————————————————————————
Proyecto FWS
—————————————————————————————————————————————————————————————————————————————
Fichero tree_writer.go
Package errs
Autor Juan V. Navarro juanvnl@activething.com
Creado 25/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 (
"bufio"
"fmt"
"io"
"strconv"
"strings"
)
type (
TreeWriter interface {
Write (t *Tree, w io.Writer) error
}
WriteTreeFnc func (t *Tree, w io.Writer) error
)
func (f WriteTreeFnc) Write(t *Tree, w io.Writer) error { return f(t, w) }
// Optimizamos shortenPath para evitar split/join y asignaciones innecesarias.
func shortenPathOptimized(path string) string {
last := strings.LastIndexByte(path, '/')
if last == -1 {
return path
}
// Buscamos el penúltimo separador
secondLast := strings.LastIndexByte(path[:last], '/')
if secondLast == -1 {
return path
}
// Devolvemos una rodaja de la cadena original precedida por .../
return ".../" + path[secondLast+1:]
}
// CTreeWriter imprime la jerarquía de errores de manera visual y semántica.
// Iconos:
// ❌ Error
// ℹ️ Meta / atributo
// ⚠️ Stack
// Conectores para causas:
// ├─▶ (child)
// └─▶ (last)
func CTreeWriter(t *Tree, w io.Writer) error {
if t == nil || t.root == nil {
return nil
}
// Usamos bufio para agrupar escrituras pequeñas y evitar syscalls constantes
bw := bufio.NewWriter(w)
defer bw.Flush()
// Buffer para gestionar la indentación sin crear constantes strings
// "│ " o " " ocupan 4 bytes por nivel. 32 niveles = 128 bytes.
indentBuf := make([]byte, 0, 128)
var walk func(n *TreeNode, hasSibling bool, isRoot bool) error
walk = func(n *TreeNode, hasSibling bool, isRoot bool) error {
// 1. DIBUJAR NODO
if isRoot {
bw.WriteString("❌ ")
bw.WriteString(n.Err.Error())
bw.WriteByte('\n')
} else {
bw.Write(indentBuf)
if hasSibling {
bw.WriteString("├─▶ ❌ ")
} else {
bw.WriteString("└─▶ ❌ ")
}
bw.WriteString(n.Err.Error())
bw.WriteByte('\n')
}
// 2. CALCULAR PREFIJO PARA ATRIBUTOS (Metas y Stacks)
// El prefijo depende de si el nodo actual tiene hermanos o no
oldLen := len(indentBuf)
if !isRoot {
if hasSibling {
indentBuf = append(indentBuf, "│ "...)
} else {
indentBuf = append(indentBuf, " "...)
}
}
// Prefijo base para metas: indentación actual + espacio del icono
attrPrefix := " " // Espacio equivalente al conector "├─▶ "
// 3. RENDERIZAR METAS
for _, m := range n.Metas {
bw.Write(indentBuf)
bw.WriteString(attrPrefix)
bw.WriteString("ℹ️ ")
bw.WriteString(m.Name())
bw.WriteString(" = ")
fmt.Fprint(bw, m.Value()) // Solo usamos fmt para valores desconocidos
bw.WriteByte('\n')
}
// 4. RENDERIZAR STACK
if len(n.Stack) > 0 {
frames := n.Stack.Frames()
bw.Write(indentBuf)
bw.WriteString(attrPrefix)
bw.WriteString("⚠️ stack (")
bw.WriteString(strconv.Itoa(len(frames)))
bw.WriteString(" frames)\n")
for _, f := range frames {
bw.Write(indentBuf)
bw.WriteString(attrPrefix)
bw.WriteString(" ")
bw.WriteString(shortenPathOptimized(f.File))
bw.WriteByte(':')
bw.WriteString(strconv.Itoa(f.LineNumber))
bw.WriteByte(' ')
bw.WriteString(f.FuncName)
bw.WriteByte('\n')
}
}
// 5. RECURSIÓN PARA CAUSAS
for i, c := range n.Causes {
isLast := i == len(n.Causes)-1
if err := walk(c, !isLast, false); err != nil {
return err
}
}
// Restaurar el buffer de indentación al volver de la recursión
indentBuf = indentBuf[:oldLen]
return nil
}
return walk(t.root, false, true)
}
Loading…
Cancel
Save

Powered by TurnKey Linux.