import "github.com/quenbyako/core"Package core supplies foundational, capability-oriented abstractions for building composable CLI and service applications on top of strongly typed, generic configuration values. The design emphasizes:
- Progressive enhancement: Optional features (I/O, logging, metrics) surface through small extension interfaces rather than a monolithic context.
- Type safety via generics: Actions receive their full concrete configuration type (T) without casting, reflection, or map indirection.
- Explicit capability discovery: Helper probes return (value, ok) allowing graceful degradation when a feature is absent.
- Testability: Slim interfaces plus an unimplemented configuration reduce boilerplate in fixtures and mock setups.
Concurrency Guidance:
- Prefer immutable configuration structs after construction.
- Treat optional capabilities as independent; absence is not an error.
- Helper probes (Stdin, Stdout, Logger, Observability) never panic.
To introduce a new capability (e.g., TracingAppContext):
- Define an interface embedding AppContext[T] plus accessor(s).
- Provide a generic helper: func Tracing[T ActionConfig](ctx AppContext[T]) (..., bool).
- Implement the interface only in contexts needing that feature.
Error Handling Philosophy: Capability helpers favor presence checks over sentinel errors, promoting resilient, feature-adaptive code paths without tightly coupling execution logic to wiring.
Overall, this package encourages clear ownership of configuration, lean interfaces, and explicit optional feature discovery—yielding maintainable, testable application composition in Go. Package core defines the foundational abstractions for building CLI / service style applications around a strongly-typed, generic configuration, optional pipeline I/O, logging, and observability features.
In this model a "configuration" is any concrete type (struct) implementing the ActionConfig interface. It encapsulates all resources required by an application: log level, certificate material, secret source DSNs, tracing endpoint, metrics address, etc. By coding against the ActionConfig interface instead of a concrete struct, application logic can remain decoupled from the mechanism of loading / assembling configuration (flags, env, files, remote stores). Each application (or sub-command) is free to introduce its own richer configuration type while still satisfying the minimal contract.
The generic parameter T on AppContext and related helpers provides:
- Callers that obtain [AppContext.Config] receive the full concrete type (T), not just the interface, eliminating repetitive casts.
- Additional fields unique to a given application configuration are immediately available wherever that specific T is in scope, with no need for map access or reflection-based extraction.
- Each executable unit (command/action) declares exactly which configuration type it expects, improving readability and maintainability.
- Go inlines interface method calls when feasible; generics avoid dynamic type assertions at use sites for the concrete config.
Rather than bloating AppContext with optional concerns, capabilities are modeled as additional interfaces that can be checked at runtime:
- Stdin()/Stdout(): Declarative access to input/output streams enabling pipeline-friendly commands without imposing streams on all contexts.
LoggerAppContext[T]:
- Log(): Provides a slog.Handler for structured logging emission without forcing every context to carry a logger.
- Observability(): Grants metrics instrumentation (Metrics interface) when available; absent contexts remain lightweight.
This list is not exhaustive, since runtime implementation technically MAY introduce some custom interfaces, however, it's highly recommended to keep the number of such interfaces minimal to reduce complexity.
- Separation of Concerns: Core logic depends only on interfaces; wiring layers populate concrete implementations.
- Progressive Enhancement: Features (I/O, logging, metrics) are additive.
- Testability: UnimplementedActionConfig + small interfaces ease mock creation.
- Explicit Capability Discovery: Boolean return pattern communicates optionality.
- Type Safety via Generics: Reduces accidental mismatches and casts.
To introduce new contextual capabilities (e.g., TracingAppContext, or whatever you want), define a new interface embedding AppContext[T] plus accessor methods, and supply a helper similar to Logger() or Observability().
By consolidating these patterns, the package offers a flexible, strongly typed, capability-driven foundation for building composable Go applications.
- Constants
- func BuildContext(name AppName, version AppVersion, pipeline Pipeline) (ctx context.Context, cancel context.CancelFunc)
- func Config[T ActionConfig](ctx AppContext[T]) T
- func Logger[T ActionConfig](ctx AppContext[T]) (slog.Handler, bool)
- func RegisterEnvParser[T any](f func(context.Context, string) (T, error))
- func RunJobs(ctx context.Context, jobs ...func(context.Context) error) error
- func Stdin[T ActionConfig](ctx AppContext[T]) (io.Reader, bool)
- func Stdout[T ActionConfig](ctx AppContext[T]) (io.Writer, bool)
- func WithAppName(ctx context.Context, v AppName) context.Context
- func WithPipelines(ctx context.Context, p Pipeline) context.Context
- func WithVersion(ctx context.Context, v AppVersion) context.Context
- type AcquireData
- type ActionConfig
- type ActionFunc
- type AppContext
- type AppName
- type AppVersion
- func NewVersion(versionRaw, commitRaw, dateRaw string) AppVersion
- func Version[T ActionConfig](ctx AppContext[T]) AppVersion
- func VersionFromContext(ctx context.Context) (AppVersion, bool)
- func (v AppVersion) CommitHash() ([sha1.Size]byte, bool)
- func (v AppVersion) Date() (time.Time, bool)
- func (v AppVersion) ShortHash() (short [7]byte, valid bool)
- func (v AppVersion) String() (res string)
- func (v AppVersion) Valid() bool
- func (v AppVersion) Version() (string, bool)
- func (v AppVersion) VersionCommit() (res string, valid bool)
- type ConfigureData
- type EnvParam
- type ExitCode
- type LoggerAppContext
- type Metrics
- type ObservabilityAppContext
- type Pipeline
- type PipelineAppContext
- type ShutdownData
- type UnimplementedActionConfig
- func (u UnimplementedActionConfig) ClientCertPaths() (cert, key string)
- func (u UnimplementedActionConfig) GetCertPaths() []string
- func (u UnimplementedActionConfig) GetLogLevel() slog.Level
- func (u UnimplementedActionConfig) GetMetricsAddr() *url.URL
- func (u UnimplementedActionConfig) GetSecretDSNs() map[string]*url.URL
- func (u UnimplementedActionConfig) GetTraceEndpoint() *url.URL
- type UnsafeActionConfig
const (
// DefaultAppName is the fallback stable identifier used when no explicit
// name is provided.
DefaultAppName = "unknown"
// DefaultAppTitle is the human-friendly title used when no explicit
// application title is supplied.
DefaultAppTitle = "Unknown Application"
)const (
DefaultVersion = "v0.0.0-dev"
DefaultCommit = "none"
DefaultDate = "unknown"
DefaultDateFormat = time.RFC3339
)func BuildContext(name AppName, version AppVersion, pipeline Pipeline) (ctx context.Context, cancel context.CancelFunc)BuildContext constructs a root application context annotated with identity (AppName), version (AppVersion) and pipeline I/O (Pipeline), and automatically wired to OS interrupt signals. The returned cancel function MUST be invoked by the caller to release signal resources.
Cancellation Sources:
- Incoming SIGINT / SIGKILL (os.Interrupt, os.Kill) trigger context cancellation for graceful shutdown.
- Manual invocation of the returned cancel function.
The supplied Pipeline is stored for later retrieval via PipelinesFromContext. Prefer passing explicit version / name values; fallback defaults remain available through helper extraction funcs.
func Config[T ActionConfig](ctx AppContext[T]) TConfig returns the concrete configuration value (type T) carried by the supplied AppContext. It is a thin, inline-able alias for ctx.Config() provided for symmetry with the Name and Version helpers and to improve readability at call sites.
Characteristics:
- Zero overhead: No copying beyond what ctx.Config() itself performs; the generic accessor typically inlines.
- Strongly typed: Callers receive the full concrete T, enabling direct field / method access without casts or interface assertions.
- Concurrency: Safety of the returned value depends on the AppContext implementation. Prefer immutable configuration structs after initialization.
Usage Example:
cfg := Config(appCtx) // obtains T
lvl := cfg.GetLogLevel() // invoke concrete methods directly
Mutability Guidance: Modifying cfg is discouraged unless the specific configuration type documents that such mutation is safe. Treat configuration as read-only in most application code.
Equivalent Call:
Config(appCtx) == appCtx.Config()
func Logger[T ActionConfig](ctx AppContext[T]) (slog.Handler, bool)Logger attempts to extract a [slog.Handler] logging capability from the provided AppContext. It performs a single type assertion against LoggerAppContext. Returns (handler, true) when the capability is present, or (nil, false) if the context does not supply structured logging.
Semantics:
- Absence is not an error; callers should branch on the boolean and degrade gracefully (e.g., use a no-op handler or skip logging).
- The returned [slog.Handler] SHOULD be safe for concurrent use; this is an implementation concern of the concrete AppContext.
Example:
if h, ok := Logger(appCtx); ok {
h.Handle(ctx, slog.Record{ /* ... */ })
}
func RegisterEnvParser[T any](f func(context.Context, string) (T, error))RegisterEnvParser registers a parser function for the concrete type T, enabling custom environment value decoding for that type.
Usage:
- Call only from init() to ensure deterministic, one-time registration.
- The registration is global; duplicate registrations for the same T panic.
- The lookup key is exactly reflect.Type. Pointer forms must be registered separately if they require distinct parsing.
Panics:
- If a parser for T is already present.
Concurrency:
- Expected to run during startup before other goroutines; no synchronization.
Parser Contract (parseFunc):
- Must be pure (no hidden global mutations).
- Should not panic on malformed input; return an error instead.
- May use context for cancellation or ancillary lookups.
Example:
func init() {
RegisterEnvParser[MyType](func(ctx context.Context, raw string) (MyType, error) {
var v MyType
if err := v.Unmarshal(raw); err != nil {
return MyType{}, err
}
return v, nil
})
}
Hint: extract parser function to separated named function for clarity. It's recommended to keep it private.
func RunJobs(ctx context.Context, jobs ...func(context.Context) error) errorRunJobs concurrently executes the provided job functions, cancelling all remaining work as soon as any job returns a non-context error. Each job receives a shared derived context that is cancelled on the first failure (excluding correct context cancellations).
Execution Model:
- All jobs start immediately in separate goroutines.
- A derived context is cancelled after the first non-context error.
- Subsequent jobs should observe cancellation and return promptly.
Error Handling:
- Errors equal to context.Canceled or context.DeadlineExceeded are suppressed.
- All other errors are collected and returned via errors.Join.
- If no non-context errors occur, the function returns nil.
Cancellation Semantics:
- Caller cancellation (ctx) propagates to all jobs.
- Internal cancellation (first failure) does not mask earlier successful results.
Ordering & Aggregation:
- Error order is not guaranteed.
- Multiple failures are joined; callers should inspect errors.Is/errors.As.
Usage Example:
err := RunJobs(ctx,
func(c context.Context) error { return workA(c) },
func(c context.Context) error { return workB(c) },
)
if err != nil {
// handle joined errors
}
func Stdin[T ActionConfig](ctx AppContext[T]) (io.Reader, bool)func Stdout[T ActionConfig](ctx AppContext[T]) (io.Writer, bool)func WithAppName(ctx context.Context, v AppName) context.ContextWithAppName returns a derived context carrying the provided AppName. It is a lightweight convenience used during application startup to annotate the root context with identity metadata that downstream code can retrieve via AppNameFromContext.
The stored value is immutable.
func WithPipelines(ctx context.Context, p Pipeline) context.ContextWithPipelines stores a Pipeline in a derived context for later retrieval. Use PipelinesFromContext to extract it; if absent, a cached default is provided.
func WithVersion(ctx context.Context, v AppVersion) context.ContextWithVersion attaches an AppVersion to a derived context for later retrieval via VersionFromContext. The stored value is immutable.
AcquireData inherits configuration values and allows acquisition logic (e.g., binding network listeners). Additional runtime derived fields can be layered in future without breaking implementers.
type AcquireData struct {
ConfigureData
}ActionConfig is the minimal contract every concrete configuration must satisfy. The private _ActionConfig method acts as a marker to avoid accidental interface satisfaction by unrelated types. Implementations typically hold immutable, application-wide tunables (log level, certificate paths, metrics endpoint, etc.).
type ActionConfig interface {
// log level
GetLogLevel() slog.Level
// paths to CA certificates. May return zero slice.
GetCertPaths() []string
// path to client certificate
ClientCertPaths() (cert, key string)
// secret DSNs
//
// TODO: two engines with one protocol? like vault-1:// and vault-2://?
GetSecretDSNs() map[string]*url.URL
// OTEL trace endpoint
GetTraceEndpoint() *url.URL
// Prometheus metrics address to listen. If nil, metrics export is disabled
GetMetricsAddr() *url.URL
// contains filtered or unexported methods
}ActionFunc is the canonical executable signature for an application action or subcommand. It receives:
- context.Context: For cancellation, deadlines, and cross-cutting values.
- AppContext[T]: A strongly typed application context exposing identity and configuration.
type ActionFunc[T ActionConfig] func(ctx context.Context, appCtx AppContext[T]) ExitCodetype AppContext[T ActionConfig] interface {
// Stable application identifier (not necessarily user-facing).
Name() AppName
// Semantic or custom version descriptor
Version() AppVersion
// Concrete configuration value of type T.
//
// Implementations SHOULD document whether Config() is safe for concurrent
// use. Immutable configs are preferred to simplify synchronization.
Config() T
}AppName represents both a machine-oriented stable identifier and a human-friendly display title for an application. Empty fields are normalized to defaults by NewAppName guaranteeing that calls to AppName.Name/AppName.Title always succeed with a non-empty fallback.
type AppName struct {
// contains filtered or unexported fields
}func AppNameFromContext(ctx context.Context) (AppName, bool)AppNameFromContext extracts an AppName previously attached with WithAppName. When no value is present a stable default is returned and the boolean is false, allowing callers to distinguish between implicit and explicit identity.
func Name[T ActionConfig](ctx AppContext[T]) AppNamefunc NewAppName(name, title string) AppNameNewAppName constructs a new AppName, normalizing empty inputs to DefaultAppName/DefaultAppTitle. Prefer passing explicit values when available; defaults keep logs and telemetry consistent for prototypes.
func (v AppName) Name() (string, bool)Name returns the stable identifier and a boolean indicating whether it was explicitly set or derived from a default.
func (v AppName) Title() (string, bool)Title returns the human-friendly application title with the same explicit/implicit semantics as Name.
AppVersion represents build-time version metadata including semantic version string, commit hash and build date. Raw values preserve original inputs; normalized fields store validated / parsed representations. AppVersion.Valid reports aggregate correctness allowing callers to warn on malformed embeddings without failing hard.
type AppVersion struct {
// contains filtered or unexported fields
}func NewVersion(versionRaw, commitRaw, dateRaw string) AppVersionNewVersion creates a version abstraction, based on raw compiled static variables.
IMPORTANT: this function WON'T panic or return an error on bad input. Since it's expected to provide correct values on compile-time, returning error or panicking might broke whole build.
Instead, Version has AppVersion.Valid method, that returns whether the object is correct or not. Caller may use this info to warn user that version info is invalid.
func Version[T ActionConfig](ctx AppContext[T]) AppVersionfunc VersionFromContext(ctx context.Context) (AppVersion, bool)VersionFromContext extracts an AppVersion previously attached with WithVersion. When absent it returns a lazily constructed default and false. Callers can use the boolean to differentiate explicit vs. fallback version data.
func (v AppVersion) CommitHash() ([sha1.Size]byte, bool)CommitHash returns the parsed full commit hash bytes and validity.
func (v AppVersion) Date() (time.Time, bool)Date returns the parsed build timestamp and validity.
func (v AppVersion) ShortHash() (short [7]byte, valid bool)ShortHash extracts the first 7 bytes of the commit hash along with validity.
func (v AppVersion) String() (res string)String returns a human-friendly composite string including raw version, short commit hash (or raw commit when invalid) and build date (parsed or raw). This is suitable for logging.
func (v AppVersion) Valid() boolValid reports whether version, commit hash and build date were all parsed successfully.
func (v AppVersion) Version() (string, bool)Version returns the normalized semantic version string and a boolean denoting validity.
func (v AppVersion) VersionCommit() (res string, valid bool)VersionCommit combines semantic version and short commit hash as version#<short-hash>.
More info: semver/semver#614
ConfigureData provides foundational wiring inputs for [EnvParam.Configure]. Fields may be nil when a capability is absent (e.g., Secrets, Metric). Implementations should not mutate shared values.
type ConfigureData struct {
AppCert tls.Certificate
Logger slog.Handler
Secrets secrets.Engine
Metric metric.MeterProvider
Trace trace.TracerProvider
Pool *x509.CertPool
Version AppVersion
}EnvParam models a lifecycle-aware configurable entity exposed through environment values (e.g., servers, listeners, credentials). The methods are invoked in order: [EnvParam.Configure] -> [EnvParam.Acquire] -> [EnvParam.Shutdown]. Implementations should be idempotent where feasible and release resources in Shutdown.
type EnvParam interface {
Configure(ctx context.Context, data *ConfigureData) error
Acquire(ctx context.Context, data *AcquireData) error
Shutdown(ctx context.Context, data *ShutdownData) error
}ExitCode represents the process exit status produced by an ActionFunc. The uint8 size mirrors conventional POSIX exit ranges (0–255) and communicates intent more clearly than a bare int.
type ExitCode uint8type LoggerAppContext[T ActionConfig] interface {
Log() slog.Handler
// contains filtered or unexported methods
}Metrics bundles logging, tracing and metrics emission capabilities into a single optional interface exposed by ObservabilityAppContext. It embeds slog.Handler for structured logging plus OTel tracer and meter providers. Implementations SHOULD be safe for concurrent use by multiple goroutines.
type Metrics interface {
slog.Handler
trace.TracerProvider
metric.MeterProvider
}func NoopMetrics() MetricsNoopMetrics returns a Metrics implementation that discards all log records and uses no-op tracer / meter providers. This is a lightweight default for tests or commands that do not yet wire observability features.
func Observability[T ActionConfig](ctx AppContext[T]) (Metrics, bool)type ObservabilityAppContext[T ActionConfig] interface {
Observability() Metrics
// contains filtered or unexported methods
}Pipeline captures standard stream handles plus a flag indicating whether stdin appears to be connected to a non-tty source (e.g., pipe or file) for pipeline-aware behavior. Accessors return the raw handles; callers should not close them directly.
type Pipeline struct {
// contains filtered or unexported fields
}func PipelineFromFiles(stdin, stdout, stderr *os.File) PipelinePipelineFromFiles builds a Pipeline from explicit \*os.File handles (nil values are replaced with process defaults). The IsPipeline bit is derived by inspecting stdin's mode to detect non-interactive usage.
func PipelinesFromContext(ctx context.Context) (Pipeline, bool)PipelinesFromContext retrieves a Pipeline previously attached with WithPipelines. The boolean reports whether an explicit value was set. When false a lazily-created default wrapping the process stdio streams is returned.
func (p Pipeline) IsPipeline() boolIsPipeline reports whether stdin appears to be a non-interactive source (pipe/file). This is useful for adapting behavior (e.g., buffered reads).
func (p Pipeline) Stderr() io.WriterStderr returns the error stream associated with the pipeline.
func (p Pipeline) Stdin() io.ReaderStdin returns the input stream associated with the pipeline.
func (p Pipeline) Stdout() io.WriterStdout returns the output stream associated with the pipeline.
type PipelineAppContext[T ActionConfig] interface {
Stdin() io.Reader
Stdout() io.Writer
// contains filtered or unexported methods
}ShutdownData inherits acquisition context for graceful teardown. Implementations should attempt best-effort cleanup returning rich errors rather than panicking.
type ShutdownData struct {
AcquireData
}UnimplementedActionConfig is a convenient neutral stub returning zero / nil values. It is useful for tests, prototypes, or commands that do not yet need to surface configuration. All getters return inert defaults (e.g., [slog.LevelInfo], nil slices, nil URLs).
type UnimplementedActionConfig struct {
UnsafeActionConfig
}func (u UnimplementedActionConfig) ClientCertPaths() (cert, key string)func (u UnimplementedActionConfig) GetCertPaths() []stringfunc (u UnimplementedActionConfig) GetLogLevel() slog.Levelfunc (u UnimplementedActionConfig) GetMetricsAddr() *url.URLfunc (u UnimplementedActionConfig) GetSecretDSNs() map[string]*url.URLfunc (u UnimplementedActionConfig) GetTraceEndpoint() *url.URLUnsafeActionConfig is an empty opt-in marker that satisfies ActionConfig via embedding. Use it when quickly scaffolding a config type; replace with explicit methods as requirements grow.
type UnsafeActionConfig struct{}Generated by gomarkdoc