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.
83 lines
6.7 KiB
83 lines
6.7 KiB
|
5 months ago
|
# CLAUDE.md
|
||
|
|
|
||
|
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||
|
|
|
||
|
|
## Workspace layout
|
||
|
|
|
||
|
|
Go workspace (`go.work`) with two modules:
|
||
|
|
|
||
|
|
- Root module `active` (`go.mod`): currently empty placeholder.
|
||
|
|
- Module `core` at `src/core/` (`src/core/go.mod`): all real code lives here. Imports inside `core` use the bare module name, e.g. `import "core/btes"`, `import "core/errs"`.
|
||
|
|
|
||
|
|
Go version is 1.26 (uses generics throughout, including type parameters on slice aliases).
|
||
|
|
|
||
|
|
## Commands
|
||
|
|
|
||
|
|
All Go commands must be run with the working directory inside `src/core` (or use `-C src/core`), since that is the active module in the workspace.
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# Build everything in the core module
|
||
|
|
go -C src/core build ./...
|
||
|
|
|
||
|
|
# Run all tests across packages
|
||
|
|
go -C src/core test ./...
|
||
|
|
|
||
|
|
# Run tests in one package (note: tests live in the `test` subpackage)
|
||
|
|
go -C src/core test ./btes/test
|
||
|
|
go -C src/core test ./errs/test
|
||
|
|
go -C src/core test ./opts/test
|
||
|
|
|
||
|
|
# Run a single test by name
|
||
|
|
go -C src/core test ./errs/test -run TestValidateCodeAcceptsValidCodes
|
||
|
|
|
||
|
|
# Vet / format
|
||
|
|
go -C src/core vet ./...
|
||
|
|
gofmt -w src/core
|
||
|
|
```
|
||
|
|
|
||
|
|
Tests use Go's standard `testing` package (no external assertion libraries) and live in a sibling `test/` directory using a separate `package test` — i.e. external (black-box) tests. New tests should follow the same convention rather than `*_test.go` files alongside the code.
|
||
|
|
|
||
|
|
## Package architecture
|
||
|
|
|
||
|
|
The codebase is structured as a small set of foundational primitives that other packages (and future modules) will build on. Read these in order — `errs` depends on `btes`, and most callers of `errs` will use `opts` for configuration.
|
||
|
|
|
||
|
|
### `core/btes` — ASCII byte operations via LUT
|
||
|
|
|
||
|
|
`btes` implements zero-allocation, O(1) ASCII classification and case conversion using a 256-entry lookup table. The big-picture design:
|
||
|
|
|
||
|
|
- `Attr` (`uint8`) is a bitmask of properties. Predefined bits include `ASCIIAttrControl`, `ASCIIAttrPrintable`, `ASCIIAttrAlpha`, `ASCIIAttrAlphaUpper`, `ASCIIAttrAlphaLower`, `ASCIIAttrDigit`, `ASCIIAttrHex`, plus combined masks like `ASCIIAttrAlphaNum`.
|
||
|
|
- `Attrs` is a `[256]Attr` LUT. Generic `SetRange` / `AddRange` / `DelRange` / `ContainsAll` / `MatchesAll` (and the `Any` variants) operate on it without allocations.
|
||
|
|
- `ASCIIAttrs` wraps an initialized `Attrs` table and exposes domain helpers (`IsAlpha`, `IsDigit`, `EqualFold`, `ToUpper`, `Upperize`, etc.). It is immutable after construction and safe to share across goroutines.
|
||
|
|
- A package-level singleton is exposed via `btes.ASCII()` — use that rather than constructing a new one. Two case-conversion styles exist: `Upperize` / `Lowerize` mutate in place; `ToUpper` / `ToLower` use a pre-scan to return the input slice unchanged when no transformation is needed (zero-copy fast path).
|
||
|
|
|
||
|
|
### `core/errs` — hierarchical error codes with a separate cause plane
|
||
|
|
|
||
|
|
The error system is built around a single string-typed `Code` with a strict format: `module::segment[.segment...]`, e.g. `auth::user.login.failed`. Key design points:
|
||
|
|
|
||
|
|
- **Module + code**: `Module` is the namespace ("auth", "db"). Use `MustRegisterModule("name")` at package init for global uniqueness (it panics on duplicates), or `MustModule` for a non-registered local module. Build codes with `module.MustCode("login")` and extend hierarchies with `code.Sub("failed")`.
|
||
|
|
- **Validation** (`ValidateCode`, `ValidateModule`) is enforced strictly: only `[a-z0-9_]`, `_` may not touch other separators or segment ends, exactly one `::`, and `.` only allowed after `::`. The validator uses `btes.ASCII()` for fast byte classification.
|
||
|
|
- **Identity vs lineage**: `Code.Is` is strict equality; `Code.IsDescendantOf` and `IsOrDescendantOf` walk the dotted hierarchy (matching prefix + `.` boundary, so `user_group` is NOT a descendant of `user`). `Matches(err, target)` is the package-level helper that handles wrapping plus lineage in one shot.
|
||
|
|
- **Cause is a separate plane from Unwrap**. This is the most important invariant in the package:
|
||
|
|
- `Unwrap` carries the public identity chain — what `errors.Is` / `errors.As` see. It supports both `Unwrap() error` (chain) and `Unwrap() []error` (siblings, e.g. `MultipleError`).
|
||
|
|
- `Causer.Cause()` carries diagnostic origin information that should NOT change the error's identity. Cause is **always singular**: a single error has a single reason. If several errors arose at the same semantic level, use `MultipleError` (siblings on the public plane), not multiple causes.
|
||
|
|
- Example: a `user::cant_change_user` wrapping a `database::invalid_credentials` cause should still be `errors.Is(err, user::cant_change_user)`, not the database code. Use `WithCause` / `AddCause` to attach causes; use `GetCause` / `FindCause` / `HasCause` to inspect them (they traverse both `Unwrap() error` and `Unwrap() []error` chains). Keep this separation when adding new error types.
|
||
|
|
- **MultipleError** is a flat sibling collection (NOT a tree). `Unwrap() []error` returns the slice so `errors.Is`/`As` work across siblings; there is no causal relationship between elements. `MakeMultipleError` cleans nil entries.
|
||
|
|
- **PanicError** wraps recovered panics; pre-defined codes (`ErrInvalidModule`, `ErrInvalidCode`, `ErrNilError`, etc.) live in `a.go` under the reserved `errs::` module.
|
||
|
|
- **Adder pattern**: `Add(base, composers...)` applies a chain of `Adder`s to enrich a base error (e.g. `errs.Add(base, errs.AddCause(orig))`). Use this rather than ad-hoc wrappers when composing decorations.
|
||
|
|
|
||
|
|
### `core/opts` — functional options
|
||
|
|
|
||
|
|
Generic functional-options pattern in two flavors:
|
||
|
|
|
||
|
|
- `Option[T]` / `Options[T]` — infallible. `Apply(*T)` mutates; nil options are treated as no-ops to allow conditional composition.
|
||
|
|
- `TryOption[T]` / `TryOptions[T]` — can return an error (validation, resource acquisition). `TryOptions.Apply` short-circuits on first error WITHOUT rolling back prior mutations — opts are not transactional.
|
||
|
|
|
||
|
|
Use these types as the standard signature for configurable constructors instead of inventing per-package option types.
|
||
|
|
|
||
|
|
## Conventions
|
||
|
|
|
||
|
|
- **Source headers**: every `.go` file starts with the long Spanish/Elastic-License v2.0 header block. When creating new files, copy the header from a sibling file and update only `Fichero` (filename), `Package`, and `Creado` (date). Don't omit it.
|
||
|
|
- **Doc comments are in Spanish.** Match the existing language and tone when adding new exported docs. Code identifiers remain English.
|
||
|
|
- **No external dependencies.** The module is currently stdlib-only; do not introduce third-party deps without an explicit reason.
|
||
|
|
- **`Must...` vs `Validate...`**: by convention, `MustX` panics on invalid input (intended for package init / constants), and `ValidateX` returns an error (intended for runtime input). Preserve this split when adding new constructors.
|