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)
6.2 KiB
Changelog — einherjar/core
All notable changes to this module are documented here. Format follows Keep a Changelog. This module adheres to Semantic Versioning.
1.0.0 — 2026-05-28
Added
launcher
Launcherinterface —Append,BeforeStart,Run,ShutdownNew(logger logging.Logger, opts ...Options) Launcher— constructs the lifecycle orchestrator; takescontracts/logging.Logger(not the concretelogztype)Hooktype (func() error) — assembly-phase callback registered viaBeforeStartConfigstruct —ComponentStopTimeout time.Durationwithenv:"EINHERJAR_COMPONENT_STOP_TIMEOUT" envDefault:"15s"(caarlos0/envsyntax)- Startup banner — printed to stdout before any slog output; disabled via
EINHERJAR_BANNER=offorEINHERJAR_BANNER=false - Components accepted as
lifecycle.Componentfromcontracts— not a locally defined interface; any type that satisfiescontracts/lifecycle.Componentis directly compatible
logz
Configstruct —Level slog.Level,JSON bool,StaticArgs []any,Writer io.WriterLevelandJSONcarryenvandenvDefaulttags (caarlos0/envsyntax);StaticArgsandWriterare programmatic-only fields without tags- Env vars:
EINHERJAR_LOG_LEVEL(defaultINFO),EINHERJAR_LOG_JSON(defaultfalse)
New(cfg Config) logging.Logger— returnscontracts/logging.Logger; the concreteslogLoggerstruct is unexported- Error enrichment — when the error passed to
Logger.Errorsatisfieserrs.CodedErrororerrs.ContextualError(fromcontracts/errs), the correspondingerror_codefield and context key-value pairs are automatically appended to the log record; this replaces the private duck-typed bridge used in micro-lib'slogz - Context helpers —
WithRequestID,GetRequestID,WithField,WithFields Logger.WithContext(ctx)— extractsrequest_idand extra fields from context and attaches them to every subsequent log record
xerrors
Codetype (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)
- New over micro-lib:
Code.Description()— human-readable description for each codeErrstruct —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)andvar _ errs.ContextualError = (*Err)(nil)— formalises the duck-type bridge from micro-lib into an explicit, verifiable contract
valid
Validatorinterface —Struct(v any) errorNew(opts ...Option) Validator— constructs a validator backed bygo-playground/validator/v10(backend is hidden; never exposed in the public API)MessageProviderinterface —Message(field, tag, param string) stringDefaultMessages— built-in English message providerSpanishMessages— opt-in Spanish message providerOptiontype andWithMessageProvider(mp MessageProvider) OptionFieldLevelinterface — passed to custom validator functions; exposesField() reflect.Value,Param() string,FieldName() string; go-playground backend never visibleWithCustomValidator(tag string, fn func(FieldLevel) bool) Option— registers a custom validation tag at construction time; panics on empty or conflicting tagOverrideProvider(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
DefaultMessagesandSpanishMessages— 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:
ErrInvalidInputfor constraint failures,ErrInternalfor non-struct arguments — both returned as*xerrors.Err
Design Notes
-
Contracts as the source of truth.
launcheracceptslifecycle.Componentandlogging.Loggerfromcontracts. It does not define its own lifecycle interface. Any type that satisfies the contracts interface is directly compatible — no adapters needed. -
Duck typing replaced by explicit interfaces. micro-lib's
logzdetected enrichable errors via privateerrorWithCode/errorWithContextinterfaces.core/logzdetects them viacontracts/errs.CodedErrorandcontracts/errs.ContextualError. The decoupling (logz does not import xerrors) is preserved; the contract is now visible. See ADR-002. -
Complete gRPC error code set. micro-lib's
xerrorshad 13 codes.core/xerrorsadds the three missing gRPC codes (OUT_OF_RANGE,ABORTED,DATA_LOSS) and provides a named convenience constructor for every code —New()andWrap()are reserved for edge cases. -
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.
-
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.