Skip to content

quenbyako/core

Repository files navigation

core

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):

  1. Define an interface embedding AppContext[T] plus accessor(s).
  2. Provide a generic helper: func Tracing[T ActionConfig](ctx AppContext[T]) (..., bool).
  3. 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.

What is a "configuration"

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.

Why use a generic \(T ActionConfig\)

The generic parameter T on AppContext and related helpers provides:

  1. Callers that obtain [AppContext.Config] receive the full concrete type (T), not just the interface, eliminating repetitive casts.
  2. 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.
  3. Each executable unit (command/action) declares exactly which configuration type it expects, improving readability and maintainability.
  4. Go inlines interface method calls when feasible; generics avoid dynamic type assertions at use sites for the concrete config.

Interfaces and Layering

Rather than bloating AppContext with optional concerns, capabilities are modeled as additional interfaces that can be checked at runtime:

PipelineAppContext[T]:

  • 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.

ObservabilityAppContext[T]:

  • 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.

Design Principles

  • 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.

Extension Guidance

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.

Index

Constants

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

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

func Config[T ActionConfig](ctx AppContext[T]) T

Config 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

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

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

func RunJobs(ctx context.Context, jobs ...func(context.Context) error) error

RunJobs 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:

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

func Stdin[T ActionConfig](ctx AppContext[T]) (io.Reader, bool)

func Stdout

func Stdout[T ActionConfig](ctx AppContext[T]) (io.Writer, bool)

func WithAppName

func WithAppName(ctx context.Context, v AppName) context.Context

WithAppName 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

func WithPipelines(ctx context.Context, p Pipeline) context.Context

WithPipelines stores a Pipeline in a derived context for later retrieval. Use PipelinesFromContext to extract it; if absent, a cached default is provided.

func WithVersion

func WithVersion(ctx context.Context, v AppVersion) context.Context

WithVersion attaches an AppVersion to a derived context for later retrieval via VersionFromContext. The stored value is immutable.

type AcquireData

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
}

type ActionConfig

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
}

type ActionFunc

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]) ExitCode

type AppContext

type 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
}

type AppName

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

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

func Name[T ActionConfig](ctx AppContext[T]) AppName

func NewAppName

func NewAppName(name, title string) AppName

NewAppName 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 (AppName) Name

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 (AppName) Title

func (v AppName) Title() (string, bool)

Title returns the human-friendly application title with the same explicit/implicit semantics as Name.

type AppVersion

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

func NewVersion(versionRaw, commitRaw, dateRaw string) AppVersion

NewVersion 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

func Version[T ActionConfig](ctx AppContext[T]) AppVersion

func VersionFromContext

func 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 (AppVersion) CommitHash

func (v AppVersion) CommitHash() ([sha1.Size]byte, bool)

CommitHash returns the parsed full commit hash bytes and validity.

func (AppVersion) Date

func (v AppVersion) Date() (time.Time, bool)

Date returns the parsed build timestamp and validity.

func (AppVersion) ShortHash

func (v AppVersion) ShortHash() (short [7]byte, valid bool)

ShortHash extracts the first 7 bytes of the commit hash along with validity.

func (AppVersion) String

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 (AppVersion) Valid

func (v AppVersion) Valid() bool

Valid reports whether version, commit hash and build date were all parsed successfully.

func (AppVersion) Version

func (v AppVersion) Version() (string, bool)

Version returns the normalized semantic version string and a boolean denoting validity.

func (AppVersion) VersionCommit

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

type ConfigureData

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
}

type EnvParam

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
}

type ExitCode

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 uint8

type LoggerAppContext

type LoggerAppContext[T ActionConfig] interface {
    Log() slog.Handler
    // contains filtered or unexported methods
}

type Metrics

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

func NoopMetrics() Metrics

NoopMetrics 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

func Observability[T ActionConfig](ctx AppContext[T]) (Metrics, bool)

type ObservabilityAppContext

type ObservabilityAppContext[T ActionConfig] interface {
    Observability() Metrics
    // contains filtered or unexported methods
}

type Pipeline

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

func PipelineFromFiles(stdin, stdout, stderr *os.File) Pipeline

PipelineFromFiles 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

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 (Pipeline) IsPipeline

func (p Pipeline) IsPipeline() bool

IsPipeline reports whether stdin appears to be a non-interactive source (pipe/file). This is useful for adapting behavior (e.g., buffered reads).

func (Pipeline) Stderr

func (p Pipeline) Stderr() io.Writer

Stderr returns the error stream associated with the pipeline.

func (Pipeline) Stdin

func (p Pipeline) Stdin() io.Reader

Stdin returns the input stream associated with the pipeline.

func (Pipeline) Stdout

func (p Pipeline) Stdout() io.Writer

Stdout returns the output stream associated with the pipeline.

type PipelineAppContext

type PipelineAppContext[T ActionConfig] interface {
    Stdin() io.Reader
    Stdout() io.Writer
    // contains filtered or unexported methods
}

type ShutdownData

ShutdownData inherits acquisition context for graceful teardown. Implementations should attempt best-effort cleanup returning rich errors rather than panicking.

type ShutdownData struct {
    AcquireData
}

type UnimplementedActionConfig

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 (UnimplementedActionConfig) ClientCertPaths

func (u UnimplementedActionConfig) ClientCertPaths() (cert, key string)

func (UnimplementedActionConfig) GetCertPaths

func (u UnimplementedActionConfig) GetCertPaths() []string

func (UnimplementedActionConfig) GetLogLevel

func (u UnimplementedActionConfig) GetLogLevel() slog.Level

func (UnimplementedActionConfig) GetMetricsAddr

func (u UnimplementedActionConfig) GetMetricsAddr() *url.URL

func (UnimplementedActionConfig) GetSecretDSNs

func (u UnimplementedActionConfig) GetSecretDSNs() map[string]*url.URL

func (UnimplementedActionConfig) GetTraceEndpoint

func (u UnimplementedActionConfig) GetTraceEndpoint() *url.URL

type UnsafeActionConfig

UnsafeActionConfig 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

About

Core library for golang projects layout

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages