You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

499 lines
14 KiB

/* ——————————————————————————————————————————————————————————————————————————
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
}

Powered by TurnKey Linux.