Introduces `code.nochebuena.dev/einherjar/core` — the foundational implementation module of the Einherjar framework. Provides four sub-packages that together cover every service's baseline needs: lifecycle management, structured logging, typed errors, and struct validation. - launcher: Launcher interface — three-phase managed lifecycle (OnInit → BeforeStart hooks → OnStart → OS signal wait → OnStop in reverse). Accepts lifecycle.Component and logging.Logger from contracts. Prints an ASCII art banner at startup (EINHERJAR_BANNER=off to suppress). Banner includes core version via runtime/debug.ReadBuildInfo() and a loaded-module list for every registered component that implements observability.Identifiable. Config struct with EINHERJAR_COMPONENT_STOP_TIMEOUT env tag (caarlos0/env syntax, default 15s). - logz: Logger implementation backed by log/slog. Returns contracts/logging.Logger. Detects errs.CodedError and errs.ContextualError (from contracts/errs) to enrich log records automatically — replaces the private duck-typed bridge from micro-lib. Context helpers: WithRequestID, WithField, WithFields, GetRequestID. Config struct with EINHERJAR_LOG_LEVEL (default INFO) and EINHERJAR_LOG_JSON (default false) env tags (caarlos0/env syntax); programmatic-only fields StaticArgs and Writer carry no tags. - xerrors: Typed error codes with context enrichment. Complete gRPC canonical set (16 codes) plus HTTP 410 ErrGone. Adds ErrOutOfRange, ErrAborted, ErrDataLoss over micro-lib. One convenience constructor per code. *Err declares compile-time satisfaction of errs.CodedError and errs.ContextualError. - valid: Struct validation wrapping go-playground/validator/v10. Validator interface + MessageProvider interface with full built-in tag coverage (~150 tags) in both DefaultMessages (English) and SpanishMessages (Spanish). Backend fully hidden; returns *xerrors.Err with ErrInvalidInput or ErrInternal. FieldLevel interface abstracts the backend's field-level access for custom validators. WithCustomValidator registers custom validation tags at construction time; OverrideProvider chains a tag→handler map with a fallback MessageProvider for custom tag messages without re-implementing built-ins. Compliance test enforces CT-6 (at most one exported TypeSpec per file via AST) and verifies behavioural correctness of all four sub-packages, including custom validator registration and OverrideProvider composition. Compile-time var _ assertions prove interface satisfaction. docs: ADR-001 (core module composition), ADR-002 (logz contracts/errs adoption), ADR-003 (Config naming convention and caarlos0/env tag standard)
109 lines
6.2 KiB
Markdown
109 lines
6.2 KiB
Markdown
# Changelog — einherjar/core
|
|
|
|
All notable changes to this module are documented here.
|
|
Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
This module adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
|
---
|
|
|
|
## [1.0.0] — 2026-05-28
|
|
|
|
### Added
|
|
|
|
#### `launcher`
|
|
|
|
- `Launcher` interface — `Append`, `BeforeStart`, `Run`, `Shutdown`
|
|
- `New(logger logging.Logger, opts ...Options) Launcher` — constructs the lifecycle
|
|
orchestrator; takes `contracts/logging.Logger` (not the concrete `logz` type)
|
|
- `Hook` type (`func() error`) — assembly-phase callback registered via `BeforeStart`
|
|
- `Config` struct — `ComponentStopTimeout time.Duration` with `env:"EINHERJAR_COMPONENT_STOP_TIMEOUT" envDefault:"15s"` (`caarlos0/env` syntax)
|
|
- Startup banner — printed to stdout before any slog output; disabled via
|
|
`EINHERJAR_BANNER=off` or `EINHERJAR_BANNER=false`
|
|
- Components accepted as `lifecycle.Component` from `contracts` — not a locally
|
|
defined interface; any type that satisfies `contracts/lifecycle.Component` is
|
|
directly compatible
|
|
|
|
#### `logz`
|
|
|
|
- `Config` struct — `Level slog.Level`, `JSON bool`, `StaticArgs []any`, `Writer io.Writer`
|
|
- `Level` and `JSON` carry `env` and `envDefault` tags (`caarlos0/env` syntax); `StaticArgs` and `Writer` are programmatic-only fields without tags
|
|
- Env vars: `EINHERJAR_LOG_LEVEL` (default `INFO`), `EINHERJAR_LOG_JSON` (default `false`)
|
|
- `New(cfg Config) logging.Logger` — returns `contracts/logging.Logger`; the
|
|
concrete `slogLogger` struct is unexported
|
|
- Error enrichment — when the error passed to `Logger.Error` satisfies
|
|
`errs.CodedError` or `errs.ContextualError` (from `contracts/errs`), the
|
|
corresponding `error_code` field and context key-value pairs are automatically
|
|
appended to the log record; this replaces the private duck-typed bridge used in
|
|
micro-lib's `logz`
|
|
- Context helpers — `WithRequestID`, `GetRequestID`, `WithField`, `WithFields`
|
|
- `Logger.WithContext(ctx)` — extracts `request_id` and extra fields from context
|
|
and attaches them to every subsequent log record
|
|
|
|
#### `xerrors`
|
|
|
|
- `Code` type (stable string wire values, gRPC-aligned)
|
|
- **16 error code constants** — complete gRPC canonical set plus `ErrGone` (HTTP 410)
|
|
- New over micro-lib: `ErrOutOfRange` (gRPC OUT_OF_RANGE, HTTP 400),
|
|
`ErrAborted` (gRPC ABORTED, HTTP 409), `ErrDataLoss` (gRPC DATA_LOSS, HTTP 500)
|
|
- `Code.Description()` — human-readable description for each code
|
|
- `Err` struct — `code`, `message`, `err` (cause), `fields` (context), `platformCode`
|
|
- Base constructors: `New(code, message)`, `Wrap(code, message, err)`
|
|
- **16 convenience constructors** (one per code): `InvalidInput`, `OutOfRange`,
|
|
`Unauthorized`, `PermissionDenied`, `NotFound`, `AlreadyExists`, `Aborted`,
|
|
`Gone`, `PreconditionFailed`, `RateLimited`, `Cancelled`, `Internal`, `DataLoss`,
|
|
`NotImplemented`, `Unavailable`, `DeadlineExceeded`
|
|
- Builder methods: `WithContext`, `WithError`, `WithPlatformCode`
|
|
- Accessors: `Code()`, `Message()`, `Fields()`, `PlatformCode()`, `Detailed()`
|
|
- Standard interfaces: `error`, `Unwrap`, `json.Marshaler`
|
|
- Compile-time assertions: `var _ errs.CodedError = (*Err)(nil)` and
|
|
`var _ errs.ContextualError = (*Err)(nil)` — formalises the duck-type bridge
|
|
from micro-lib into an explicit, verifiable contract
|
|
|
|
#### `valid`
|
|
|
|
- `Validator` interface — `Struct(v any) error`
|
|
- `New(opts ...Option) Validator` — constructs a validator backed by
|
|
`go-playground/validator/v10` (backend is hidden; never exposed in the public API)
|
|
- `MessageProvider` interface — `Message(field, tag, param string) string`
|
|
- `DefaultMessages` — built-in English message provider
|
|
- `SpanishMessages` — opt-in Spanish message provider
|
|
- `Option` type and `WithMessageProvider(mp MessageProvider) Option`
|
|
- `FieldLevel` interface — passed to custom validator functions; exposes `Field() reflect.Value`, `Param() string`, `FieldName() string`; go-playground backend never visible
|
|
- `WithCustomValidator(tag string, fn func(FieldLevel) bool) Option` — registers a custom validation tag at construction time; panics on empty or conflicting tag
|
|
- `OverrideProvider(handlers map[string]func(field, param string) string, base MessageProvider) MessageProvider` — composes a tag→message handler map with a fallback; use for custom tag messages without re-implementing built-ins
|
|
- **Full built-in tag coverage** in `DefaultMessages` and `SpanishMessages` — all go-playground/validator tags have specific messages (fields, network, strings, format, comparisons, other; ~150 tags total)
|
|
- Field names in error context prefer the json struct tag, falling back to the Go
|
|
field name
|
|
- Error codes: `ErrInvalidInput` for constraint failures, `ErrInternal` for
|
|
non-struct arguments — both returned as `*xerrors.Err`
|
|
|
|
### Design Notes
|
|
|
|
1. **Contracts as the source of truth.** `launcher` accepts `lifecycle.Component`
|
|
and `logging.Logger` from `contracts`. It does not define its own lifecycle
|
|
interface. Any type that satisfies the contracts interface is directly compatible —
|
|
no adapters needed.
|
|
|
|
2. **Duck typing replaced by explicit interfaces.** micro-lib's `logz` detected
|
|
enrichable errors via private `errorWithCode`/`errorWithContext` interfaces.
|
|
`core/logz` detects them via `contracts/errs.CodedError` and `contracts/errs.ContextualError`.
|
|
The decoupling (logz does not import xerrors) is preserved; the contract is now
|
|
visible. See [ADR-002](docs/adr/ADR-002-logz-contracts-errs.md).
|
|
|
|
3. **Complete gRPC error code set.** micro-lib's `xerrors` had 13 codes. `core/xerrors`
|
|
adds the three missing gRPC codes (`OUT_OF_RANGE`, `ABORTED`, `DATA_LOSS`) and
|
|
provides a named convenience constructor for every code — `New()` and `Wrap()` are
|
|
reserved for edge cases.
|
|
|
|
4. **One module, four sub-packages.** The consolidation eliminates four-way version
|
|
coordination for a set of packages that always ship and upgrade together.
|
|
See [ADR-001](docs/adr/ADR-001-core-module-composition.md).
|
|
|
|
5. **Startup banner.** The launcher prints an ASCII art banner to stdout before any
|
|
structured log output. It is disabled via `EINHERJAR_BANNER=off`, not via code
|
|
changes, so production deployments can suppress it without modifying the service.
|
|
|
|
---
|
|
|
|
[1.0.0]: https://code.nochebuena.dev/einherjar/core/releases/tag/v1.0.0
|