master
dev 8 months ago
commit d0ef9f5eee

8
.idea/.gitignore vendored

@ -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

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="Go" enabled="true" />
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/goat.iml" filepath="$PROJECT_DIR$/.idea/goat.iml" />
</modules>
</component>
</project>

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

@ -0,0 +1,3 @@
module goat
go 1.25

@ -0,0 +1,6 @@
go 1.25
use (
./pkg/core
./pkg/tests
)

@ -0,0 +1,2 @@
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=

@ -0,0 +1,54 @@
/* ——————————————————————————————————————————————————————————————————————————
Proyecto goat
—————————————————————————————————————————————————————————————————————————————
Fichero a.go
Package btes
Autor Juan V. Navarro juanvnl@activething.com
Creado 02/02/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/goat
git : g.activething.com | github.com/activething/goat
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 btes
var (
ASCII *ASCIIAttrs = NewASCIIAttrs()
)

File diff suppressed because it is too large Load Diff

@ -0,0 +1,75 @@
/* ——————————————————————————————————————————————————————————————————————————
Proyecto goat
—————————————————————————————————————————————————————————————————————————————
Fichero attr.go
Package chrs
Autor Juan V. Navarro juanvnl@activething.com
Creado 02/02/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/goat
git : g.activething.com | github.com/activething/goat
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 btes
// Attr representa una máscara de bits (bitmask) de 8 bits para propiedades ASCII.
// Este diseño permite almacenar hasta 8 características booleanas distintas
// en un solo byte, optimizando el uso de la memoria caché del CPU.
type Attr uint8
// HasAll comprueba si el atributo contiene TODAS las propiedades indicadas en la máscara.
// Se utiliza para validaciones restrictivas.
//
// Ejemplo:
//
// attr.HasAll(IsAlpha | IsUpper) // ¿Es una letra Y además es mayúscula?
//
// Matemáticamente: (a & attr) == attr
func (a Attr) HasAll(attr Attr) bool { return a&attr == attr }
// HasAny comprueba si el atributo contiene AL MENOS UNA de las propiedades
// indicadas en la máscara. Se utiliza para validaciones de pertenencia a grupos.
//
// Ejemplo:
//
// attr.HasAny(IsDigit | IsAlpha) // ¿Es un número O una letra?
//
// Matemáticamente: (a & attr) != 0
func (a Attr) HasAny(attr Attr) bool { return a&attr != 0 }

@ -0,0 +1,192 @@
/* ——————————————————————————————————————————————————————————————————————————
Proyecto goat
—————————————————————————————————————————————————————————————————————————————
Fichero attrs.go
Package btes
Autor Juan V. Navarro juanvnl@activething.com
Creado 02/02/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/goat
git : g.activething.com | github.com/activething/goat
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 btes
const (
maxSize = 1 << 8
maxRange = maxSize - 1
)
// Attrs representa una Tabla de Búsqueda (LUT - Lookup Table) de alto rendimiento.
// Permite clasificar bytes y realizar validaciones en tiempo constante O(1)
// o lineal O(N) sin asignaciones de memoria.
type Attrs [maxSize]Attr
// SetRange asigna destructivamente el atributo 'att' a un rango de bytes [ini, end].
// Sobrescribe cualquier atributo previo en ese rango.
// Los límites se ajustan automáticamente a [0, 255].
func (t *Attrs) SetRange(att Attr, ini, end int) {
s, e := clamp(ini), clamp(end)
for i := s; i <= e; i++ {
t[i] = att
}
}
// AddRange agrega el atributo 'att' a un rango de bytes [ini, end] (operación OR).
// Mantiene los atributos existentes.
// Los límites se ajustan automáticamente a [0, 255].
func (t *Attrs) AddRange(att Attr, ini, end int) {
s, e := clamp(ini), clamp(end)
for i := s; i <= e; i++ {
t[i] |= att
}
}
// DelRange elimina el atributo 'att' de un rango de bytes [ini, end] (operación AND NOT).
// El resto de atributos del byte permanecen intactos.
// Los límites se ajustan automáticamente a [0, 255].
func (t *Attrs) DelRange(att Attr, ini, end int) {
s, e := clamp(ini), clamp(end)
for i := s; i <= e; i++ {
t[i] &^= att
}
}
// -------------------------------------------------------------------------
// Configuración por Lista (Slice Setters)
// -------------------------------------------------------------------------
// SetFrom asigna destructivamente el atributo 'att' a los bytes específicos en 'fromBytes'.
// Útil para conjuntos dispersos (ej: caracteres especiales "-_@").
func (t *Attrs) SetFrom(fromBytes []byte, att Attr) {
for _, bt := range fromBytes {
t[bt] = att
}
}
// AddFrom agrega el atributo 'att' a los bytes específicos en 'fromBytes'.
func (t *Attrs) AddFrom(fromBytes []byte, att Attr) {
for _, bt := range fromBytes {
t[bt] |= att
}
}
// DelFrom elimina el atributo 'att' de los bytes específicos en 'fromBytes'.
func (t *Attrs) DelFrom(fromBytes []byte, att Attr) {
for _, bt := range fromBytes {
t[bt] &^= att
}
}
// -------------------------------------------------------------------------
// Funciones de Búsqueda (Search / Existence)
// Responden a: "¿CONTIENE la lista algún byte que...?"
// -------------------------------------------------------------------------
// HasAll busca si EXISTE al menos un byte en 'fromBytes' que cumpla
// estrictamente con todos los bits de la máscara 'att'.
// Retorna true al encontrar el primer match.
func (t *Attrs) HasAll(fromBytes []byte, att Attr) bool {
for _, bt := range fromBytes {
if t[bt]&att == att {
return true
}
}
return false
}
// HasAny busca si EXISTE al menos un byte en 'fromBytes' que tenga
// cualquiera de los bits de la máscara 'att'.
// Retorna true al encontrar el primer match.
func (t *Attrs) HasAny(fromBytes []byte, att Attr) bool {
for _, bt := range fromBytes {
if t[bt]&att != 0 {
return true
}
}
return false
}
// -------------------------------------------------------------------------
// Funciones de Validación (Predicates)
// Responden a: "¿SON TODOS los bytes de la lista...?"
// -------------------------------------------------------------------------
// MatchesAll verifica el cumplimiento estricto (AND) en TODO el slice.
// Devuelve true solo si TODOS los bytes poseen TODOS los bits de 'att'.
// Retorna false si el slice está vacío.
func (t *Attrs) MatchesAll(source []byte, att Attr) bool {
if len(source) == 0 {
return false
}
for _, ch := range source {
if t[ch]&att != att {
return false
}
}
return true
}
// MatchesAny verifica el cumplimiento flexible (OR) en TODO el slice.
// Devuelve true si TODOS los bytes comparten AL MENOS UN bit con 'att'.
// Retorna false si el slice está vacío.
// Útil para validar que una cadena solo contenga caracteres de un grupo (ej: alfanuméricos).
func (t *Attrs) MatchesAny(source []byte, att Attr) bool {
if len(source) == 0 {
return false
}
for _, ch := range source {
if t[ch]&att == 0 {
return false
}
}
return true
}
// clamp asegura que cualquier entero esté en el rango válido de un byte
func clamp(v int) int {
if v < 0 {
return 0
}
if v > maxRange {
return maxRange
}
return v
}

@ -0,0 +1,77 @@
/* ——————————————————————————————————————————————————————————————————————————
Proyecto goat
—————————————————————————————————————————————————————————————————————————————
Fichero a.go
Package errs
Autor Juan V. Navarro juanvnl@activething.com
Creado 03/02/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/goat
git : g.activething.com | github.com/activething/goat
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 "core/btes"
const (
ModuleName = Module("errs")
ErrInvalidModule = Code(ModuleName + ModuleSep + "invalid_module_name")
ErrInvalidCode = Code(ModuleName + ModuleSep + "invalid_error_code")
ErrInvalidCodeLen = Code(string(ErrInvalidModule) + string(CodeSep) + "invalid_len")
ErrInvalidCodeChar = Code(string(ErrInvalidModule) + string(CodeSep) + "invalid_char")
)
var (
// isValidChar es el predicado base que define el conjunto de caracteres permitidos
// para los segmentos atómicos de un Code (nombres de módulo y términos de error).
//
// Al estar centralizado en esta variable, aseguramos consistencia en todo el
// paquete errs. Por defecto, utiliza btes.ASCII.IsAlphaNumLower para imponer:
// - Minúsculas [a-z]
// - Números [0-9]
//
// Nota: Los separadores ('.', '_', ':') se gestionan en la lógica estructural
// superior (ValidateCode), no aquí
//
isValidChar func(ch byte) bool = btes.ASCII.IsAlphaNumLower
)

@ -0,0 +1,366 @@
package errs
import (
"core/btes"
"errors"
"strings"
)
const (
ModuleName = Module("errs")
ErrInvalidModule = Code(ModuleName + ModuleSep + "invalid_module_name")
ErrInvalidCode = Code(ModuleName + ModuleSep + "invalid_error_code")
ErrInvalidCodeLen = Code(string(ErrInvalidModule) + string(CodeSep) + "invalid_len")
ErrInvalidCodeChar = Code(string(ErrInvalidModule) + string(CodeSep) + "invalid_char")
)
var (
// isValidChar es el predicado base que define el conjunto de caracteres permitidos
// para los segmentos atómicos de un Code (nombres de módulo y términos de error).
//
// Al estar centralizado en esta variable, aseguramos consistencia en todo el
// paquete errs. Por defecto, utiliza btes.ASCII.IsAlphaLowerNum para imponer:
// - Minúsculas [a-z]
// - Números [0-9]
//
// Nota: Los separadores ('.', '_', ':') se gestionan en la lógica estructural
// superior (ValidateCode), no aquí
//
isValidChar func(ch byte) bool = btes.ASCII.IsAlphaLowerNum
)
const (
// Minima longitud para un Code que sería
// la longitud minima para módulo, más
// la longitud del separador de módulo,
// más la longitud minima del segmento de error
// a::b
minCodeSeg = 1
minCodeLength = minModuleSeg + lenModuleSep + minCodeSeg
wordSep = '_'
codeSepChar = '.'
CodeSep = string(codeSepChar)
lenCodeSep = 1
)
// Code representa un identificador único de error con estructura jerárquica.
// El formato sigue el patrón: "modulo::categoria.subcategoria.error"
//
// Reglas de Estructura:
// 1. Módulo: Es obligatorio y no puede contener puntos ni separadores.
// 2. Separador de Módulo: Utiliza exactamente"::" para dividir el origen del error de su contexto.
// 3. Jerarquía de Error: La parte derecha puede contener puntos para definir niveles de detalle.
//
// Reglas de Caracteres:
// - Solo se permiten minúsculas [a-z] y números [0-9].
// - El único separador de palabras permitido es el guion bajo '_'.
// - No se permiten separadores ('.', '_', ':') al inicio o al final del código.
// - No se permiten separadores consecutivos (ej. "..", "::.", "._").
type Code string
func (c Code) Sub(segment string) Code {
if !isValidSegment(segment, minCodeSeg) {
panic(ErrInvalidCode)
}
return c + Code(CodeSep+segment)
}
func (c Code) Module() (Module, error) {
s := string(c)
if i := strings.Index(s, ModuleSep); i != -1 {
return Module(s[:i]), nil
}
return "", ErrInvalidModule
}
// Parent devuelve el nivel superior de la jerarquía de error.
// Si el código no tiene jerarquía interna (solo módulo + error), devuelve: "".
//
// Ejemplos:
//
// "auth::user.login.failed" -> "auth::user.login"
// "auth::user" -> ""
func (c Code) Parent() string {
s := string(c)
if i := strings.LastIndex(s, CodeSep); i >= 0 {
return s[:i]
}
return ""
}
// Leaf devuelve únicamente el segmento final del código (el más específico).
//
// Ejemplos:
//
// "auth::user.login.failed" -> "failed"
// "auth::user" -> "user"
func (c Code) Leaf() string {
s := string(c)
if i := strings.LastIndex(s, CodeSep); i >= 0 {
return s[i+1:]
}
if i := strings.LastIndex(s, ModuleSep); i >= 0 {
return s[i+lenModuleSep:]
}
return s
}
func (c Code) Error() string {
return string(c)
}
func (c Code) String() string {
return string(c)
}
func (c Code) Is(target error) bool {
var t Code
return errors.As(target, &t) && c == t
}
// IsDescendantOf verifica si el código pertenece a la línea sucesoria del padre.
// Devuelve true si es un descendiente jerárquico.
//
// Ejemplo:
//
// "auth::login.failed".IsDescendantOf("auth::login") -> true
// "auth::login".IsDescendantOf("auth::login") -> true
func (c Code) IsDescendantOf(err error) bool {
var p Code
if !errors.As(err, &p) {
return false
}
cd, ps := string(c), string(p)
if cd == ps {
return false
}
return strings.HasPrefix(cd, ps+CodeSep)
}
// IsOrDescendantOf verifica si el código es idéntico al objetivo o si desciende de él.
// Es la herramienta principal para realizar comprobaciones jerárquicas.
//
// Ejemplos:
//
// "auth::login.failed".IsOrDescendantOf("auth::login") -> true (descendiente)
// "auth::login".IsOrDescendantOf("auth::login") -> true (identidad)
// "auth::login".IsOrDescendantOf("auth::logout") -> false
func (c Code) IsOrDescendantOf(err error) bool {
var t Code
if !errors.As(err, &t) {
return false
}
sc, st := string(c), string(t)
if sc == st {
return true
}
return strings.HasPrefix(sc, st+string(codeSepChar))
}
// Parts devuelve las partes del código: [módulo, segmento1, segmento2, ...]
// La primera celda del slice corresponde al módulo,
// las siguientes celdas a cada uno de los segmentos del código.
//
// Ejemplos:
//
// "auth::failed" → ["auth", "failed"], nil
// "auth::failed.invalid-token" → ["auth", "failed", "invalid-token"], nil
// "auth::" → nil, ErrInvalidCode (no hay código después del módulo)
// "auth" → nil, ErrInvalidCode (falta separador de módulo)
func (c Code) Parts() ([]string, error) {
s := string(c)
n := len(s)
moduleEnd := strings.Index(s, ModuleSep)
if moduleEnd == -1 {
return nil, ErrInvalidModule
}
if moduleEnd+lenModuleSep >= n {
return nil, ErrInvalidCode
}
// Extraer módulo
var segments []string
segments = append(segments, s[:moduleEnd])
start := moduleEnd + lenModuleSep
for i := start; i <= n; i++ {
if i == n || s[i] == CodeSep[0] {
segment := s[start:i]
if len(segment) > 0 {
segments = append(segments, segment)
}
start = i + 1
}
}
return segments, nil
}
// ValidateCode verifica si un string cumple las reglas de formato de Code.
//
// # Reglas de Validación
//
// 1. Longitud mínima: 4 caracteres ("m::e")
// 2. Debe contener exactamente un separador de módulo "::"
// 3. Debe empezar con alfanumérico (no '.', ':', '-', '_')
// 4. Debe terminar con alfanumérico (no '.', ':', '-', '_')
// 5. Después de separadores ('::' o '.') debe venir alfanumérico
// 6. No puede tener separadores consecutivos ('..' o '::.' o '.::')
// 7. No puede tener '.' antes del '::'
// 8. Solo permite: a-z, A-Z, 0-9, '_', '.', '::'
//
// # Ejemplos Válidos
//
// "database::connection"
// "database::connection.timeout"
// "http::request.validation.missing_field"
//
// # Ejemplos Inválidos
//
// "error" // Sin módulo (falta ::)
// "::error" // Empieza con ::
// "mod::" // Termina con ::
// "mod..err" // Separadores consecutivos
// "mod.cat::err" // '.' antes de '::'
// "auth::inicio._sesion" // '_' después de '.'
// "auth::sin_apostrofe-" // Termina con '-'
// "auth::sin_apostrofe2_" // Termina con '-'
func ValidateCode(code string) error {
ln := len(code)
if ln < minCodeLength {
return ErrInvalidCodeLen
}
if !isValidChar(code[0]) || !isValidChar(code[ln-1]) {
return ErrInvalidCodeChar
}
hasModuleSep := false
prevWasSep := false
for i := 0; i < ln; i++ {
ch := code[i]
if isValidChar(ch) {
prevWasSep = false
continue
}
if ch == wordSep {
if prevWasSep {
return ErrInvalidCodeChar
}
prevWasSep = false
continue
}
if ch == moduleSepChar {
if hasModuleSep {
return ErrInvalidCodeChar
}
if i+1 >= ln || code[i+1] != moduleSepChar {
return ErrInvalidCodeChar
}
if prevWasSep {
return ErrInvalidCodeChar
}
hasModuleSep = true
prevWasSep = true
i++
continue
}
if ch == codeSepChar {
if !hasModuleSep {
return ErrInvalidCodeChar
}
if prevWasSep {
return ErrInvalidCodeChar
}
prevWasSep = true
continue
}
return ErrInvalidCodeChar
}
if !hasModuleSep {
return ErrInvalidModule
}
return nil
}
func isValidSegment(seg string, minLen int) bool {
ln := len(seg)
if ln < minLen {
return false
}
if seg[0] == wordSep || seg[ln-1] == wordSep {
return false
}
for i := 0; i < ln; i++ {
ch := seg[i]
if isValidChar(ch) || ch == wordSep {
continue
}
return false
}
return true
}
const (
moduleSepChar = ':'
ModuleSep = "::"
lenModuleSep = 2
minModuleSeg = 1
)
type Module string
// MakeModule crea un nuevo espacio de nombres para errores.
// Solo acepta nombres "puros" (ej. "auth", no "auth::").
// Si el nombre contiene caracteres no permitidos o el separador '::', lanza un pánico.
func MakeModule(name string) Module {
if !isValidSegment(name, minModuleSeg) {
panic(ErrInvalidModule)
}
return Module(name)
}
// MakeCode genera un Code perteneciente a este módulo.
// Ejemplo: authMod.MakeCode("user_not_found") -> "auth::user_not_found"
func (m Module) MakeCode(code string) Code {
if !isValidSegment(code, minCodeSeg) {
panic(ErrInvalidCode)
}
return Code(string(m) + ModuleSep + code)
}
func ValidateModule(module string) error {
if !isValidSegment(module, minModuleSeg) {
return ErrInvalidModule
}
return nil
}

@ -0,0 +1,498 @@
/* ——————————————————————————————————————————————————————————————————————————
Proyecto goat
—————————————————————————————————————————————————————————————————————————————
Fichero code.go
Package errs
Autor Juan V. Navarro juanvnl@activething.com
Creado 02/02/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/goat
git : g.activething.com | github.com/activething/goat
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 (
"core/btes"
"errors"
"strings"
)
const (
// Minima longitud para un Code que sería
// la longitud minima para módulo, más
// la longitud del separador de módulo,
// más la longitud minima del segmento de error
// a::b
minCodeSeg = 1
minCodeLength = minModuleSeg + lenModuleSep + minCodeSeg
wordSep = '_'
codeSepChar = '.'
CodeSep = string(codeSepChar)
lenCodeSep = 1
)
// Code representa un identificador único de error con estructura jerárquica.
// El formato sigue el patrón: "modulo::categoria.subcategoria.error"
//
// Reglas de Estructura:
// 1. Módulo: Es obligatorio y no puede contener puntos ni separadores.
// 2. Separador de Módulo: Utiliza exactamente"::" para dividir el origen del error de su contexto.
// 3. Jerarquía de Error: La parte derecha puede contener puntos para definir niveles de detalle.
//
// Reglas de Caracteres:
// - Solo se permiten minúsculas [a-z] y números [0-9].
// - El único separador de palabras permitido es el guion bajo '_'.
// - No se permiten separadores ('.', '_', ':') al inicio o al final del código.
// - No se permiten separadores consecutivos (ej. "..", "::.", "._").
type Code string
func (c Code) Sub(segment string) Code {
if !isValidSegment(segment, minCodeSeg) {
panic(ErrInvalidCode)
}
return c + Code(CodeSep+segment)
}
func (c Code) Module() (Module, error) {
s := string(c)
if i := strings.Index(s, ModuleSep); i != -1 {
return Module(s[:i]), nil
}
return "", ErrInvalidModule
}
// Parent devuelve el nivel superior de la jerarquía de error.
// Si el código no tiene jerarquía interna (solo módulo + error), devuelve: "".
//
// Ejemplos:
//
// "auth::user.login.failed" -> "auth::user.login"
// "auth::user" -> ""
func (c Code) Parent() string {
s := string(c)
if i := strings.LastIndex(s, CodeSep); i >= 0 {
return s[:i]
}
return ""
}
// Leaf devuelve únicamente el segmento final del código (el más específico).
//
// Ejemplos:
//
// "auth::user.login.failed" -> "failed"
// "auth::user" -> "user"
func (c Code) Leaf() string {
s := string(c)
// 1. Buscamos el último punto (jerarquía interna)
if i := strings.LastIndex(s, CodeSep); i >= 0 {
return s[i+1:]
}
// 2. Si no hay puntos, buscamos el separador de módulo
if i := strings.LastIndex(s, ModuleSep); i >= 0 {
return s[i+lenModuleSep:]
}
// Si por alguna razón no hay separadores, devolvemos el string completo
return s
}
func (c Code) Error() string {
return string(c)
}
func (c Code) String() string {
return string(c)
}
func (c Code) Is(target error) bool {
var t Code
return errors.As(target, &t) && c == t
}
// IsDescendantOf verifica si el código pertenece a la línea sucesoria del padre.
// Devuelve true si es un descendiente jerárquico.
//
// Ejemplo:
//
// "auth::login.failed".IsDescendantOf("auth::login") -> true
// "auth::login".IsDescendantOf("auth::login") -> true
func (c Code) IsDescendantOf(err error) bool {
var p Code
if !errors.As(err, &p) {
return false
}
cd, ps := string(c), string(p)
if cd == ps {
return false
}
return strings.HasPrefix(cd, ps+CodeSep)
}
// EqualOrDescendantOf verifica si el código es idéntico al objetivo o si desciende de él.
// Es la herramienta principal para realizar comprobaciones jerárquicas.
//
// Ejemplos:
//
// "auth::login.failed".EqualOrDescendantOf("auth::login") -> true (descendiente)
// "auth::login".EqualOrDescendantOf("auth::login") -> true (identidad)
// "auth::login".EqualOrDescendantOf("auth::logout") -> false
func (c Code) EqualOrDescendantOf(err error) bool {
var t Code
if !errors.As(err, &t) {
return false
}
sc, st := string(c), string(t)
// 1. Verificación de identidad
if sc == st {
return true
}
// 2. Verificación de linaje
// Añadimos CodeSep (".") para garantizar que "user" no sea padre de "users"
return strings.HasPrefix(sc, st+CodeSep)
}
// Parts devuelve las partes del código: [módulo, segmento1, segmento2, ...]
// La primera celda del slice corresponde al módulo,
// las siguientes celdas a cada uno de los segmentos del código.
//
// Ejemplos:
//
// "auth::failed" → ["auth", "failed"], nil
// "auth::failed.invalid-token" → ["auth", "failed", "invalid-token"], nil
// "auth::" → nil, ErrInvalidCode (no hay código después del módulo)
// "auth" → nil, ErrInvalidCode (falta separador de módulo)
func (c Code) Parts() ([]string, error) {
s := string(c)
n := len(s)
// Buscar separador de módulo
moduleEnd := strings.Index(s, ModuleSep)
// Validaciones
if moduleEnd == -1 {
return nil, ErrInvalidModule // Falta "::"
}
if moduleEnd+lenModuleSep >= n {
return nil, ErrInvalidCode // Solo módulo sin código (ej: "auth::")
}
// Extraer módulo
var segments []string
segments = append(segments, s[:moduleEnd])
// Extraer segmentos de código
start := moduleEnd + lenModuleSep
for i := start; i <= n; i++ {
if i == n || s[i] == CodeSep[0] {
segment := s[start:i]
if len(segment) > 0 { // Evitar segmentos vacíos
segments = append(segments, segment)
}
start = i + 1
}
}
return segments, nil
}
// ValidateCode verifica si un string cumple las reglas de formato de Code.
//
// # Reglas de Validación
//
// 1. Longitud mínima: 4 caracteres ("m::e")
// 2. Debe contener exactamente un separador de módulo "::"
// 3. Debe empezar con alfanumérico (no '.', ':', '-', '_')
// 4. Debe terminar con alfanumérico (no '.', ':', '-', '_')
// 5. Después de separadores ('::' o '.') debe venir alfanumérico
// 6. No puede tener separadores consecutivos ('..' o '::.' o '.::')
// 7. No puede tener '.' antes del '::'
// 8. Solo permite: a-z, A-Z, 0-9, '_', '.', '::'
//
// # Ejemplos Válidos
//
// "database::connection"
// "database::connection.timeout"
// "http::request.validation.missing_field"
//
// # Ejemplos Inválidos
//
// "error" // Sin módulo (falta ::)
// "::error" // Empieza con ::
// "mod::" // Termina con ::
// "mod..err" // Separadores consecutivos
// "mod.cat::err" // '.' antes de '::'
// "auth::inicio._sesion" // '_' después de '.'
// "auth::sin_apostrofe-" // Termina con '-'
// "auth::sin_apostrofe2_" // Termina con '-'
func ValidateCode(code string) error {
ln := len(code)
// 1. Verificar longitud mínima
if ln < minCodeLength {
return ErrInvalidCodeLen
}
// No puede empezar/terminar con '.', ':', '_'
if !btes.ASCII.IsAlphaNumLower(code[0]) || !btes.ASCII.IsAlphaNumLower(code[ln-1]) {
return ErrInvalidCodeChar
}
// 3. Validar estructura interna
hasModuleSep := false
prevWasSep := false
for i := 0; i < ln; i++ {
ch := code[i]
// Caracteres alfanuméricos (siempre permitidos)
if btes.ASCII.IsAlphaNumLower(ch) {
prevWasSep = false
continue
}
// WordSep
// ✅ FIX: Solo permitidos si NO venimos de un separador
if ch == wordSep {
if prevWasSep {
return ErrInvalidCodeChar // No permitir "::_" o ".-" o "::-"
}
prevWasSep = false
continue
}
// Separador de módulo '::'
if ch == moduleSepChar {
// Ya encontramos el módulo antes, no puede haber otro ':'
if hasModuleSep {
return ErrInvalidCodeChar
}
// Verificar que es '::', no ':' suelto
if i+1 >= ln || code[i+1] != moduleSepChar {
return ErrInvalidCodeChar
}
// No puede venir de otro separador
if prevWasSep {
return ErrInvalidCodeChar
}
hasModuleSep = true
prevWasSep = true
i++ // Saltar el segundo ':'
continue
}
// Separador de jerarquía '.'
if ch == codeSepChar {
// Regla: '.' solo después de '::'
if !hasModuleSep {
return ErrInvalidCodeChar
}
// No puede haber separadores consecutivos
if prevWasSep {
return ErrInvalidCodeChar
}
prevWasSep = true
continue
}
// Carácter inválido
return ErrInvalidCodeChar
}
// Válido solo si encontramos el separador de módulo
if !hasModuleSep {
return ErrInvalidModule
}
return nil
}
func ValidateCode2(code string) error {
ln := len(code)
// 1. Verificar longitud mínima
if ln < minCodeLength {
return ErrInvalidCodeLen
}
// No puede empezar/terminar con '.', ':', '_'
if !isValidChar(code[0]) || !isValidChar(code[ln-1]) {
return ErrInvalidCodeChar
}
// 3. Validar estructura interna
hasModuleSep := false
prevWasSep := false
for i := 0; i < ln; i++ {
ch := code[i]
// Caracteres alfanuméricos (siempre permitidos)
if isValidChar(ch) {
prevWasSep = false
continue
}
// WordSep
// ✅ FIX: Solo permitidos si NO venimos de un separador
if ch == wordSep {
if prevWasSep {
return ErrInvalidCodeChar // No permitir "::_" o ".-" o "::-"
}
prevWasSep = false
continue
}
// Separador de módulo '::'
if ch == moduleSepChar {
// Ya encontramos el módulo antes, no puede haber otro ':'
if hasModuleSep {
return ErrInvalidCodeChar
}
// Verificar que es '::', no ':' suelto
if i+1 >= ln || code[i+1] != moduleSepChar {
return ErrInvalidCodeChar
}
// No puede venir de otro separador
if prevWasSep {
return ErrInvalidCodeChar
}
hasModuleSep = true
prevWasSep = true
i++ // Saltar el segundo ':'
continue
}
// Separador de jerarquía '.'
if ch == codeSepChar {
// Regla: '.' solo después de '::'
if !hasModuleSep {
return ErrInvalidCodeChar
}
// No puede haber separadores consecutivos
if prevWasSep {
return ErrInvalidCodeChar
}
prevWasSep = true
continue
}
// Carácter inválido
return ErrInvalidCodeChar
}
// Válido solo si encontramos el separador de módulo
if !hasModuleSep {
return ErrInvalidModule
}
return nil
}
func ValidateCode3(code string) error {
ln := len(code)
if ln < minCodeLength {
return ErrInvalidCodeLen
}
if !btes.ASCII.IsAlphaNumLower(code[0]) || !btes.ASCII.IsAlphaNumLower(code[ln-1]) {
return ErrInvalidCodeChar
}
hasModuleSep := false
prevWasSep := false
for i := 0; i < ln; i++ {
ch := code[i]
if btes.ASCII.IsAlphaNumLower(ch) {
prevWasSep = false
continue
}
if ch == wordSep {
if prevWasSep {
return ErrInvalidCodeChar
}
prevWasSep = false
continue
}
if ch == moduleSepChar {
if hasModuleSep || i+1 >= ln || code[i+1] != moduleSepChar || prevWasSep {
return ErrInvalidCodeChar
}
hasModuleSep = true
prevWasSep = true
i++ // salto del segundo ':'
continue
}
if ch == codeSepChar {
if !hasModuleSep || prevWasSep {
return ErrInvalidCodeChar
}
prevWasSep = true
continue
}
// Cualquier otro char inválido
return ErrInvalidCodeChar
}
if !hasModuleSep {
return ErrInvalidModule
}
return nil
}
func isValidSegment(seg string, minLen int) bool {
ln := len(seg)
if ln < minLen {
return false
}
// Usamos tu variable centralizada isValidChar (que es IsAlphaNumLower)
// Los segmentos no pueden empezar ni terminar con '_'
if !(btes.ASCII.IsAlphaNumLower(seg[0]) && btes.ASCII.IsAlphaNumLower(seg[ln-1])) {
return false
}
for i := 0; i < ln; i++ {
ch := seg[i]
if btes.ASCII.IsAlphaNumLower(ch) || ch == wordSep {
continue
}
return false
}
return true
}

@ -0,0 +1,99 @@
/* ——————————————————————————————————————————————————————————————————————————
Proyecto goat
—————————————————————————————————————————————————————————————————————————————
Fichero module.go
Package errs
Autor Juan V. Navarro juanvnl@activething.com
Creado 03/02/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/goat
git : g.activething.com | github.com/activething/goat
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 (
moduleSepChar = ':'
ModuleSep = "::"
lenModuleSep = 2
minModuleSeg = 1
)
// Module representa un espacio de nombres (namespace) único y normalizado.
//
// Un módulo actúa como la raíz de una jerarquía de errores. Su propósito es
// garantizar que todos los códigos generados bajo su ámbito compartan un
// origen común, facilitando la trazabilidad y el filtrado en logs y métricas.
//
// Reglas de Valor:
// - Debe ser un nombre "puro" (ej. "database", no "database::").
// - Solo admite minúsculas [a-z], números [0-9] y guiones bajos '_'.
// - Se recomienda que sea corto, descriptivo y representativo de un
// componente o subsistema.
type Module string
// MakeModule crea un nuevo espacio de nombres para errores.
// Solo acepta nombres "puros" (ej. "auth", no "auth::").
// Si el nombre contiene caracteres no permitidos o el separador '::', lanza un pánico.
func MakeModule(name string) Module {
// Validamos que el nombre sea un segmento válido (minúsculas, números, _)
// y que cumpla la longitud mínima para módulos (ej. 1 o 2 caracteres)
if !isValidSegment(name, minModuleSeg) {
panic(ErrInvalidModule)
}
return Module(name)
}
// MakeCode genera un Code perteneciente a este módulo.
// Ejemplo: authMod.MakeCode("user_not_found") -> "auth::user_not_found"
func (m Module) MakeCode(code string) Code {
if !isValidSegment(code, minCodeSeg) {
panic(ErrInvalidCode)
}
return Code(string(m) + ModuleSep + code)
}
func ValidateModule(module string) error {
if !isValidSegment(module, minModuleSeg) {
return ErrInvalidModule
}
return nil
}

@ -0,0 +1,3 @@
module core
go 1.25

@ -0,0 +1,310 @@
/* ——————————————————————————————————————————————————————————————————————————
Proyecto goat
—————————————————————————————————————————————————————————————————————————————
Fichero ascii_bench_test.go
Package test_btes
Autor Juan V. Navarro juanvnl@activething.com
Creado 02/02/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/goat
git : g.activething.com | github.com/activething/goat
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 test_btes
import (
"core/btes"
"strings"
"testing"
)
// ═══════════════════════════════════════════════════════════════════════════
// BENCHMARKS - Comparación Original vs Optimizado
// ═══════════════════════════════════════════════════════════════════════════
var ascii = btes.NewASCIIAttrs()
// Test data - realistic HTTP routing scenarios
var (
pathMixed = []byte("/api/UsersService/GetUserById")
pathLower = []byte("/api/users/get-user-by-id")
pathUpper = []byte("/API/USERS/GET-USER-BY-ID")
methodGET = []byte("GET")
methodPost = []byte("post")
headerMixed = []byte("Content-Type")
snakeCaseInput = []byte("user_service_handler")
camelCaseOutput = []byte("UserServiceHandler")
)
// ═══════════════════════════════════════════════════════════════════════════
// Benchmark #1: EqualFold
// ═══════════════════════════════════════════════════════════════════════════
func BenchmarkEqualFold(b *testing.B) {
b.Run("Custom/MatchExact", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = ascii.EqualFold(methodGET, methodGET)
}
})
b.Run("Custom/MatchFold", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = ascii.EqualFold(methodGET, methodPost)
}
})
b.Run("Custom/NoMatch", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = ascii.EqualFold(methodGET, []byte("POST"))
}
})
b.Run("Stdlib/MatchExact", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = strings.EqualFold(string(methodGET), string(methodGET))
}
})
b.Run("Stdlib/MatchFold", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = strings.EqualFold(string(methodGET), string(methodPost))
}
})
}
// ═══════════════════════════════════════════════════════════════════════════
// Benchmark #2: ToUpper - Este es el CRÍTICO a optimizar
// ═══════════════════════════════════════════════════════════════════════════
func BenchmarkToUpper(b *testing.B) {
// Caso 1: No hay cambios necesarios (ya está en uppercase)
b.Run("ToUpper/NoChanges", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = ascii.ToUpper(pathUpper)
}
})
// Caso 2: Algunos cambios al principio
b.Run("ToUpper/EarlyChanges", func(b *testing.B) {
input := []byte("abc123DEF456GHI")
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = ascii.ToUpper(input)
}
})
// Caso 3: Cambios al final (peor caso en versión original)
b.Run("ToUpper/LateChanges", func(b *testing.B) {
input := []byte("ABCDEF123456xyz")
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = ascii.ToUpper(input)
}
})
// Caso 4: Todo minúsculas (todos necesitan cambio)
b.Run("ToUpper/AllChanges", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = ascii.ToUpper(pathLower)
}
})
// Caso 5: Mezclado (realista)
b.Run("ToUpper/Mixed", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = ascii.ToUpper(pathMixed)
}
})
// Caso 6: In-place (baseline - ya es óptimo)
b.Run("Upperize/InPlace", func(b *testing.B) {
for i := 0; i < b.N; i++ {
input := make([]byte, len(pathMixed))
copy(input, pathMixed)
ascii.Upperize(input)
}
})
// Comparación con stdlib
b.Run("Stdlib/ToUpper", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = strings.ToUpper(string(pathMixed))
}
})
}
// ═══════════════════════════════════════════════════════════════════════════
// Benchmark #3: ToCamel - También CRÍTICO a optimizar
// ═══════════════════════════════════════════════════════════════════════════
func BenchmarkToCamel(b *testing.B) {
// Caso 1: Ya está en CamelCase (no cambios)
b.Run("ToCamel/NoChanges", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = ascii.ToCamel(camelCaseOutput, '_')
}
})
// Caso 2: snake_case típico
b.Run("ToCamel/SnakeCase", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = ascii.ToCamel(snakeCaseInput, '_')
}
})
// Caso 3: Sin separadores
b.Run("ToCamel/NoSeparators", func(b *testing.B) {
input := []byte("alllowercase")
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = ascii.ToCamel(input, '_')
}
})
// Caso 4: Muchos separadores
b.Run("ToCamel/ManySeparators", func(b *testing.B) {
input := []byte("one_two_three_four_five_six")
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = ascii.ToCamel(input, '_')
}
})
// Caso 5: In-place (baseline)
b.Run("Camelize/InPlace", func(b *testing.B) {
for i := 0; i < b.N; i++ {
input := make([]byte, len(snakeCaseInput))
copy(input, snakeCaseInput)
ascii.Camelize(input, '_')
}
})
}
// ═══════════════════════════════════════════════════════════════════════════
// Benchmark #4: Operaciones individuales (baseline)
// ═══════════════════════════════════════════════════════════════════════════
func BenchmarkSingleByte(b *testing.B) {
b.Run("ToUpperByte", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = ascii.ToUpperByte('a')
}
})
b.Run("ToLowerByte", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = ascii.ToLowerByte('A')
}
})
b.Run("IsAlpha", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = ascii.IsAlpha('a')
}
})
b.Run("IsDigit", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = ascii.IsDigit('5')
}
})
}
// ═══════════════════════════════════════════════════════════════════════════
// Benchmark #5: Casos de uso reales en HTTP routing
// ═══════════════════════════════════════════════════════════════════════════
func BenchmarkHTTPRouting(b *testing.B) {
// Normalizar método HTTP (típicamente ya uppercase, zero-copy esperado)
b.Run("NormalizeMethod/AlreadyUpper", func(b *testing.B) {
method := []byte("GET")
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = ascii.ToUpper(method)
}
})
b.Run("NormalizeMethod/Lowerize", func(b *testing.B) {
method := []byte("get")
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = ascii.ToUpper(method)
}
})
// Comparar headers case-insensitive
b.Run("CompareHeader/Match", func(b *testing.B) {
h1 := []byte("Content-Type")
h2 := []byte("content-type")
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = ascii.EqualFold(h1, h2)
}
})
// Convertir path parameter a CamelCase
b.Run("PathParamToCamel", func(b *testing.B) {
param := []byte("user_id")
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = ascii.ToCamel(param, '_')
}
})
}
func BenchmarkToSnake(b *testing.B) {
input := []byte("MyVeryLongVariableNameForTestingPerformance")
b.Run("ToSnake/Complex", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = attrs.ToSnake(input, '_')
}
})
inputNoChange := []byte("my_very_long_variable_name")
b.Run("ToSnake/NoChange", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = attrs.ToSnake(inputNoChange, '_')
}
})
}

@ -0,0 +1,473 @@
/* ——————————————————————————————————————————————————————————————————————————
Proyecto goat
—————————————————————————————————————————————————————————————————————————————
Fichero ascii_test.go
Package btes
Autor Juan V. Navarro juanvnl@activething.com
Creado 02/02/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/goat
git : g.activething.com | github.com/activething/goat
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
————————————————————————————————————————————————————————————————————————————— */
// ascii_test.go
package test_btes
import (
"bytes"
"core/btes"
"testing"
)
var attrs = btes.NewASCIIAttrs() // instancia global para todos los tests (singleton)
func TestEqualFold(t *testing.T) {
tests := []struct {
name string
a, b []byte
want bool
}{
{"exact match", []byte("hello"), []byte("hello"), true},
{"case insensitive", []byte("HelloWorld"), []byte("helloworld"), true},
{"headers HTTP", []byte("Content-Type"), []byte("content-type"), true},
{"mezcla symbols", []byte("User-ID_123"), []byte("user-id_123"), true},
{"non-ASCII no afecta", []byte("café"), []byte("CafÉ"), false}, // 'é' se compara exacto
{"diferente longitud", []byte("hello"), []byte("hell"), false},
{"diferente char", []byte("hello"), []byte("hellp"), false},
{"vacío", []byte{}, []byte{}, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := attrs.EqualFold(tt.a, tt.b)
if got != tt.want {
t.Errorf("EqualFold(%q, %q) = %v, want %v", tt.a, tt.b, got, tt.want)
}
})
}
}
func TestToUpper(t *testing.T) {
tests := []struct {
name string
input []byte
want []byte
}{
{"lower to upper", []byte("hello world!"), []byte("HELLO WORLD!")},
{"mezcla case", []byte("HeLLo WoRlD"), []byte("HELLO WORLD")},
{"ya upper", []byte("HELLO"), []byte("HELLO")},
{"no letras", []byte("123!@#"), []byte("123!@#")},
{"vacío", []byte{}, []byte{}},
{"solo minúsculas", []byte("abc"), []byte("ABC")},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := attrs.ToUpper(tt.input)
if !bytes.Equal(got, tt.want) {
t.Errorf("ToUpper(%q) = %q, want %q", tt.input, got, tt.want)
}
// Verificar zero-copy cuando no hay cambios
if bytes.Equal(tt.input, tt.want) {
if len(got) == 0 {
// OK para vacío
return
}
if &got[0] != &tt.input[0] {
t.Errorf("ToUpper no hizo zero-copy en %q (backing diferente)", tt.name)
}
}
})
}
}
func TestUpper(t *testing.T) {
tests := []struct {
name string
input []byte
want []byte
}{
{"lower to upper", []byte("hello"), []byte("HELLO")},
{"mezcla", []byte("HeLLo"), []byte("HELLO")},
{"no cambios", []byte("HELLO"), []byte("HELLO")},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Copia porque muta in-place
clone := make([]byte, len(tt.input))
copy(clone, tt.input)
attrs.Upperize(clone)
if !bytes.Equal(clone, tt.want) {
t.Errorf("Upperize(%q) → %q, want %q", tt.input, clone, tt.want)
}
})
}
}
func TestToLower(t *testing.T) {
tests := []struct {
name string
input []byte
want []byte
}{
{"upper to lower", []byte("HELLO WORLD!"), []byte("hello world!")},
{"mezcla", []byte("HeLLo WoRlD"), []byte("hello world")},
{"ya lower", []byte("hello"), []byte("hello")},
{"vacío", []byte{}, []byte{}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := attrs.ToLower(tt.input)
if !bytes.Equal(got, tt.want) {
t.Errorf("ToLower(%q) = %q, want %q", tt.input, got, tt.want)
}
if bytes.Equal(tt.input, tt.want) {
if len(got) == 0 {
return
}
if &got[0] != &tt.input[0] {
t.Errorf("ToLower no hizo zero-copy en %q", tt.name)
}
}
})
}
}
func TestToCamel(t *testing.T) {
tests := []struct {
name string
input []byte
sep byte
want []byte
}{
{"snake_case", []byte("hello_world"), '_', []byte("HelloWorld")},
{"kebab-case", []byte("user-id"), '-', []byte("UserId")},
//{"ya camel sin sep", []byte("HelloWorld"), '_', []byte("HelloWorld")},
{"mezcla case", []byte("hElLo_wOrLd"), '_', []byte("HelloWorld")},
{"multiple sep", []byte("a_b_c"), '_', []byte("ABC")},
{"sin sep", []byte("hello"), '_', []byte("Hello")},
{"con números", []byte("user_123_id"), '_', []byte("User123Id")},
{"sep al inicio", []byte("_hello"), '_', []byte("Hello")},
{"sep al final", []byte("hello_"), '_', []byte("Hello")},
{"múltiples sep seguidos", []byte("a__b"), '_', []byte("AB")},
{"vacío", []byte{}, '_', []byte{}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := attrs.ToCamel(tt.input, tt.sep)
if !bytes.Equal(got, tt.want) {
t.Errorf("ToCamel(%q, %c) = %q, want %q", tt.input, tt.sep, got, tt.want)
}
})
}
}
func TestToUpperByte(t *testing.T) {
tests := []struct {
input byte
want byte
}{
{'a', 'A'},
{'z', 'Z'},
{'A', 'A'},
{'9', '9'},
{'!', '!'},
{0x00, 0x00},
}
for _, tt := range tests {
got := attrs.ToUpperByte(tt.input)
if got != tt.want {
t.Errorf("ToUpperByte(0x%02x %c) = 0x%02x %c, want 0x%02x %c", tt.input, tt.input, got, got, tt.want, tt.want)
}
}
}
func TestToLowerByte(t *testing.T) {
tests := []struct {
input byte
want byte
}{
{'A', 'a'},
{'Z', 'z'},
{'a', 'a'},
{'0', '0'},
{' ', ' '},
}
for _, tt := range tests {
got := attrs.ToLowerByte(tt.input)
if got != tt.want {
t.Errorf("ToLowerByte(0x%02x %c) = 0x%02x %c, want 0x%02x %c", tt.input, tt.input, got, got, tt.want, tt.want)
}
}
}
func TestHexDigitToByte(t *testing.T) {
tests := []struct {
input byte
want byte
}{
{'0', 0},
{'9', 9},
{'A', 10},
{'F', 15},
{'a', 10},
{'f', 15},
{'g', 0},
{'G', 0},
{'!', 0},
}
for _, tt := range tests {
got := attrs.HexDigitToByte(tt.input)
if got != tt.want {
t.Errorf("HexDigitToByte(%c) = %d, want %d", tt.input, got, tt.want)
}
}
}
func TestGetHex2DigitToByte(t *testing.T) {
tests := []struct {
high, low byte
want byte
ok bool
}{
{'A', 'F', 0xAF, true},
{'a', 'f', 0xAF, true},
{'0', '0', 0x00, true},
{'1', 'A', 0x1A, true},
{'g', '0', 0, false},
{'A', 'g', 0, false},
}
for _, tt := range tests {
got, ok := attrs.GetHex2DigitToByte(tt.high, tt.low)
if ok != tt.ok || got != tt.want {
t.Errorf("GetHex2DigitToByte(%c, %c) = 0x%02x, %v; want 0x%02x, %v", tt.high, tt.low, got, ok, tt.want, tt.ok)
}
}
}
func TestIsAlphaNum(t *testing.T) {
tests := []struct {
input byte
want bool
}{
{'a', true},
{'Z', true},
{'5', true},
{'_', false},
{' ', false},
{0x7F, false},
}
for _, tt := range tests {
got := attrs.IsAlphaNum(tt.input)
if got != tt.want {
t.Errorf("IsAlphaNum(%c) = %v, want %v", tt.input, got, tt.want)
}
}
}
func TestToSnakeCorrectness(t *testing.T) {
tests := []struct {
name string
input string
separator byte
expected string
}{
{"Simple", "HelloWorld", '_', "hello_world"},
{"Camel", "camelCase", '_', "camel_case"},
{"Pascal", "PascalCase", '_', "pascal_case"},
{"AcronymEnd", "HTMLParser", '_', "html_parser"}, // Prueba lógica avanzada
{"AcronymStart", "JSONData", '_', "json_data"},
{"Numbers", "User123ID", '_', "user123_id"}, // Números suelen pegar a la palabra anterior
{"AlreadySnake", "hello_world", '_', "hello_world"},
{"MixedSeparators", "hello-world", '_', "hello_world"}, // Normaliza '-' a '_'
{"Empty", "", '_', ""},
{"SingleUpper", "A", '_', "a"},
{"SingleLower", "a", '_', "a"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
input := []byte(tt.input)
// Nota: Asegúrate de que tu helper 'ascii' o la instancia 'attrs' esté disponible aquí
result := attrs.ToSnake(input, tt.separator)
if string(result) != tt.expected {
t.Errorf("ToSnake(%q) = %q, want %q", tt.input, string(result), tt.expected)
}
})
}
}
func TestToScreamingSnake(t *testing.T) {
tests := []struct {
input string
expected string
}{
{"helloWorld", "HELLO_WORLD"},
{"JSONData", "JSON_DATA"},
{"user-id", "USER_ID"},
{"already_UPPER", "ALREADY_UPPER"},
}
for _, tt := range tests {
res := attrs.ToScreamingSnake([]byte(tt.input), '_')
if string(res) != tt.expected {
t.Errorf("Para %s: esperado %s, obtenido %s", tt.input, tt.expected, string(res))
}
}
}
func TestScreamingSnakeize(t *testing.T) {
tests := []struct {
input string
expected string
}{
{"user-name", "USER_NAME"},
{"HTTP.server", "HTTP_SERVER"},
{"already_SNAKE", "ALREADY_SNAKE"},
{"lower", "LOWER"},
}
for _, tt := range tests {
input := []byte(tt.input)
// Probamos el retorno del slice
res := attrs.ScreamingSnakeize(input)
if string(res) != tt.expected {
t.Errorf("ScreamingSnakeize(%s) = %s; want %s", tt.input, string(res), tt.expected)
}
}
}
// ═══════════════════════════════════════════════════════════════════════════
// Tests de correctitud
// ═══════════════════════════════════════════════════════════════════════════
func TestToUpperCorrectness(t *testing.T) {
tests := []struct {
name string
input string
expected string
}{
{"AllLower", "hello", "HELLO"},
{"AllUpper", "HELLO", "HELLO"},
{"Mixed", "HeLLo", "HELLO"},
{"WithNumbers", "hello123", "HELLO123"},
{"WithSymbols", "hello-world_123", "HELLO-WORLD_123"},
{"Empty", "", ""},
{"Path", "/api/users/:id", "/API/USERS/:ID"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
input := []byte(tt.input)
result := ascii.ToUpper(input)
if string(result) != tt.expected {
t.Errorf("ToUpper(%q) = %q, want %q", tt.input, string(result), tt.expected)
}
})
}
}
func TestToCamelCorrectness(t *testing.T) {
tests := []struct {
name string
input string
separator byte
expected string
}{
{"Simple", "hello_world", '_', "HelloWorld"},
{"Multiple", "one_two_three", '_', "OneTwoThree"},
//{"AlreadyCamel", "HelloWorld", '_', "HelloWorld"},
{"Numbers", "user_123_name", '_', "User123Name"},
{"Trailing", "hello_world_", '_', "HelloWorld"},
{"Leading", "_hello_world", '_', "HelloWorld"},
{"Empty", "", '_', ""},
{"NoSep", "helloworld", '_', "Helloworld"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
input := []byte(tt.input)
result := ascii.ToCamel(input, tt.separator)
if string(result) != tt.expected {
t.Errorf("ToCamel(%q, %q) = %q, want %q", tt.input, tt.separator, string(result), tt.expected)
}
})
}
}
func TestEqualFoldCorrectness(t *testing.T) {
tests := []struct {
name string
a, b string
expected bool
}{
{"Exact", "hello", "hello", true},
{"DifferentCase", "HELLO", "hello", true},
{"MixedCase", "HeLLo", "hEllO", true},
{"Different", "hello", "world", false},
{"DifferentLength", "hello", "hello!", false},
{"WithNumbers", "abc123", "ABC123", true},
{"HTTPMethod", "GET", "get", true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := ascii.EqualFold([]byte(tt.a), []byte(tt.b))
if result != tt.expected {
t.Errorf("EqualFold(%q, %q) = %v, want %v", tt.a, tt.b, result, tt.expected)
}
})
}
}

@ -0,0 +1,87 @@
/* ——————————————————————————————————————————————————————————————————————————
Proyecto goat
—————————————————————————————————————————————————————————————————————————————
Fichero code_bench_test.go
Package errs
Autor Juan V. Navarro juanvnl@activething.com
Creado 02/02/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/goat
git : g.activething.com | github.com/activething/goat
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 test_errs
import (
"core/errs"
"regexp"
"testing"
)
var codeRegex = regexp.MustCompile(`^([a-z0-9]+(?:_[a-z0-9]+)*)::([a-z0-9]+(?:_[a-z0-9]+)*)(?:\.[a-z0-9]+(?:_[a-z0-9]+)*)*$`)
func BenchmarkValidationComparison(b *testing.B) {
input := "database::connection.pool.timeout_detected"
b.Run("Regex", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = codeRegex.MatchString(input)
}
})
b.Run("Custom", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = errs.ValidateCode(input)
}
})
b.Run("Custom-function", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = errs.ValidateCode2(input)
}
})
b.Run("Grok-the-looser", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = errs.ValidateCode3(input)
}
})
}

@ -0,0 +1,179 @@
/* ——————————————————————————————————————————————————————————————————————————
Proyecto goat
—————————————————————————————————————————————————————————————————————————————
Fichero code_test.go
Package errs
Autor Juan V. Navarro juanvnl@activething.com
Creado 02/02/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/goat
git : g.activething.com | github.com/activething/goat
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 test_errs
import (
"core/errs"
"github.com/stretchr/testify/assert"
"testing"
)
func IsValid(err error) bool { return err == nil }
func TestCode_IsValid(t *testing.T) {
tests := []struct {
name string
code errs.Code
want bool
}{
// --- CASOS VÁLIDOS ---
{"Simple", "sys::auth", true},
{"ConJerarquia", "auth::login.failed", true},
{"JerarquiaLarga", "db::connection.pool.timeout", true},
{"ConGuiones", "my_module::internal_error", true},
{"ConNumeros", "v1::api.error_404", true},
{"MinimoPosible", "a::b", true},
// --- CASOS INVÁLIDOS: ESTRUCTURA ---
{"Vacio", "", false},
{"MuyCorto", "a:b", false},
{"SinModulo", "login.failed", false},
{"SoloModulo", "auth::", false}, // Termina en ::
{"ModuloVacio", "::login", false},
{"DobleModulo", "auth::db::error", false},
{"ColonSuelto", "auth:login", false},
// --- CASOS INVÁLIDOS: PUNTOS ---
{"PuntoEnModulo", "core.db::timeout", false},
{"PuntoAlInicio", ".auth::error", false},
{"PuntoAlFinal", "auth::error.", false},
{"PuntosConsecutivos", "auth::login..failed", false},
{"PuntoTrasModulo", "auth::.login", false},
{"PuntoTrasModulo", "auth::.login..dff", false},
{"PuntoTrasModulo", "auth::.log_.in..dff", false},
{"PuntoTrasModulo", "auth::log_._.in.dff", false},
// --- CASOS INVÁLIDOS: CARACTERES ---
{"Espacios", "auth::login failed", false},
{"Simbolos", "auth::login#failed", false},
{"UTF8", "auth::inicio._sesion", false}, // Solo ASCII alfanumérico
{"UTF8", "auth::sin_apostrofe_", false}, //
{"UTF8", "auth::sin_apostrofe2-", false}, //
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := IsValid(errs.ValidateCode(string(tt.code))); got != tt.want {
t.Errorf("Code.ValidateCode() para %q = %v, se esperaba %v", tt.code, got, tt.want)
}
})
}
}
func TestCode_Parts(t *testing.T) {
tests := []struct {
code errs.Code
expected []string
wantErr bool
}{
{
code: "auth::failed",
expected: []string{"auth", "failed"},
wantErr: false,
},
{
code: "auth::failed.invalid-token",
expected: []string{"auth", "failed", "invalid-token"},
wantErr: false,
},
{
code: "auth::failed.invalid-token.expired",
expected: []string{"auth", "failed", "invalid-token", "expired"},
wantErr: false,
},
{
code: "auth::", // Solo módulo
expected: nil,
wantErr: true,
},
{
code: "auth", // Sin ::
expected: nil,
wantErr: true,
},
{
code: "::failed", // Módulo vacío
expected: []string{"", "failed"},
wantErr: false, // Si permites módulo vacío
},
{
code: "auth::failed..token", // Doble punto
expected: []string{"auth", "failed", "token"}, // Segmento vacío filtrado
wantErr: false,
},
}
for _, tt := range tests {
t.Run(string(tt.code), func(t *testing.T) {
parts, err := tt.code.Parts()
if tt.wantErr {
assert.Error(t, err)
assert.Nil(t, parts)
} else {
assert.NoError(t, err)
assert.Equal(t, tt.expected, parts)
}
})
}
}
/*
func BenchmarkCode_IsValid(b *testing.B) {
// Caso de éxito típico para medir rendimiento real
code := errs.Code("database::connection.pool.timeout_detected")
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = errs.ValidateCode(string(code))
}
}
*/

@ -0,0 +1,11 @@
module tests
go 1.25
require github.com/stretchr/testify v1.11.1
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

@ -0,0 +1,10 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
Loading…
Cancel
Save

Powered by TurnKey Linux.