Documentation
¶
Overview ¶
Package cfgstore provides a tiny, stdlib-only way to attach structured metadata and sentinels to errors while staying fully composable with the Go standard library. The model is:
Each function builds an entry with New(...) passing an optional trailing cause: return doterr.New(ErrRepo, "key", val, cause) // cause last
With(...) is a flexible convenience for same-function enrichment. It can:
Enrich the rightmost doterr entry within an existing joined error, or
Join a new entry if no doterr entry exists, and
(Optionally) treat a final trailing error as the cause and join it last.
Combine([]error) bundles independent failures into a single error that unwraps to its members, preserving order.
Index ¶
- Constants
- Variables
- func AppConfigDir(configSlug dt.PathSegment) (cd dt.DirPath, err error)
- func AppendErr(errs []error, err error) []error
- func CLIConfigDir(configSlug dt.PathSegment) (cd dt.DirPath, err error)
- func CloseOrLog(c io.Closer)
- func CombineErrs(errs []error) error
- func ConfigDir(dirType DirType, configSlug dt.PathSegment, dp *DirsProvider) (cd dt.DirPath, err error)
- func CreateWriterLogger(args *WriterLoggerArgs) (wr cliutil.WriterLogger, err error)
- func EnsureConfigDirs(configDir dt.DirPath, subdirs []dt.PathSegment) (err error)
- func EnsureLogger() *slog.Logger
- func ErrValue[T any](err error, key string) (T, bool)
- func Errors(err error) []error
- func FindErr[T error](err error) (out T, ok bool)
- func GetAppCacheDir(slug, appName dt.PathSegment, opts ...CacheOptions) (dt.DirPath, error)
- func GetBaseFilename(fullPath string) string
- func GetSharedCacheDir(slug dt.PathSegment, opts ...CacheOptions) (dt.DirPath, error)
- func InitProjectConfig[RC any, PRC RootConfigPtr[RC]](configSlug dt.PathSegment, configFile dt.RelFilepath, opts Options) (prc PRC, err error)
- func LoadCLIConfig[RC any, PRC RootConfigPtr[RC]](args LoadConfigArgs) (PRC, error)
- func LoadConfig[RC any, PRC RootConfigPtr[RC]](args LoadConfigArgs) (prc PRC, err error)
- func LoadConfigStores[RC any, PRC RootConfigPtr[RC]](stores *ConfigStores, args RootConfigArgs) (prc PRC, err error)
- func LoadDefaultConfig[RC any, PRC RootConfigPtr[RC]](args LoadConfigArgs) (PRC, error)
- func LoadProjectConfig[RC any, PRC RootConfigPtr[RC]](args LoadConfigArgs) (PRC, error)
- func LogOnError(err error)
- func Logger() *slog.Logger
- func NewErr(parts ...any) error
- func NoSuchFileOrDirectory(err error) (is bool)
- func ProjectConfigDir(configSlug dt.PathSegment) (cd dt.DirPath, err error)
- func ReadFileIfExists(file string) (bytes []byte, err error)
- func SetLogger(l *slog.Logger)
- func WithErr(parts ...any) error
- type CacheOptions
- type ConfigStore
- type ConfigStoreArgs
- type ConfigStoreMap
- type ConfigStores
- func (stores *ConfigStores) AppConfigStore() (cs ConfigStore)
- func (stores *ConfigStores) CLIConfigStore() (cs ConfigStore)
- func (stores *ConfigStores) FirstStore() (cs ConfigStore)
- func (stores *ConfigStores) LastStore() (cs ConfigStore)
- func (stores *ConfigStores) ProjectConfigStore() (cs ConfigStore)
- type ConfigStoresArgs
- type DirFunc
- type DirType
- type DirsProvider
- type KV
- type LoadConfigArgs
- type NormalizeArgs
- type Options
- type RootConfig
- type RootConfigArgs
- type RootConfigMap
- type RootConfigPtr
- type WriterLoggerArgs
Constants ¶
const DefaultConfigDirType = CLIConfigDirType
DefaultConfigDirType is currently hardcoded for ~/.config but having this const will make it easy to track down how where to change it if we want to make it configurable.
const DotConfigPathSegment dt.PathSegment = ".config"
Variables ¶
var ( ErrMissingSentinel = errors.New("missing required sentinel error") ErrTrailingKey = errors.New("trailing key without value") ErrMisplacedError = errors.New("error in wrong position") ErrInvalidArgumentType = errors.New("invalid argument type") ErrOddKeyValueCount = errors.New("odd number of key-value arguments") ErrCrossPackageError = errors.New("error from different doterr package") ErrFailedTypeAssertion = errors.New("failed type assertion") )
Sentinel errors for validation failures
var ( ErrFailedToGetConfigFileSystem = errors.New("failed to get config file system") ErrFailedToReadFile = errors.New("failed to read file") ErrFailedToReadConfigFile = errors.New("failed to read config file") ErrFailedToUnmarshalConfigFile = errors.New("failed to unmarshal config file") ErrFileDoesNotExist = errors.New("file does not exist") )
var ( ErrConfigDirTypeNotSet = errors.New("config dir type not set") ErrInvalidConfigDirType = errors.New("invalid config dir type") ErrFailedGettingWorkingDir = errors.New("failed to get working dir") ErrFailedGettingUserConfigDir = errors.New("failed to get user config dir") ErrFailedGettingCLIConfigDir = errors.New("failed to get CLI config dir") ErrFailedGettingUserHomeDir = errors.New("failed to get user home dir") ErrFailedGettingUserCacheDir = errors.New("failed to get user cache dir") )
var ( ErrUsernameRequired = errors.New("username required") ErrInvalidUsername = errors.New("invalid username") )
var ErrConfigAlreadyExists = errors.New("config already exists")
var ErrDirTypeNotAssignAfterMerge = errors.New("dirType not assigned after merge")
var ErrFailedLoggerSetup = errors.New("failed logger setup")
var ErrFailedToEnsureConfig = errors.New("failed to ensure config")
var ErrFailedToLoadConfig = errors.New("failed to load config")
var ErrFailedToLoadJSON = errors.New("failed to load JSON")
var ErrFailedWriterSetup = errors.New("failed writer setup")
var ErrInvalidConfigFilepath = errors.New("invalid config filepath")
var ErrNoRootConfigsLoaded = errors.New("no root configs loaded")
var ErrNotValidConfigDirsAvailable = errors.New("not valid config dirs available")
Functions ¶
func AppConfigDir ¶
func AppConfigDir(configSlug dt.PathSegment) (cd dt.DirPath, err error)
func CLIConfigDir ¶
func CLIConfigDir(configSlug dt.PathSegment) (cd dt.DirPath, err error)
func CloseOrLog ¶
func CombineErrs ¶
CombineErrs bundles a slice of errors into a single composite error that unwraps to its members. Order is preserved and nils are skipped. Returns nil for an empty/fully-nil slice, or the sole error when there is exactly one.
func ConfigDir ¶ added in v0.1.2
func ConfigDir(dirType DirType, configSlug dt.PathSegment, dp *DirsProvider) (cd dt.DirPath, err error)
func CreateWriterLogger ¶ added in v0.2.1
func CreateWriterLogger(args *WriterLoggerArgs) (wr cliutil.WriterLogger, err error)
func EnsureConfigDirs ¶
func EnsureConfigDirs(configDir dt.DirPath, subdirs []dt.PathSegment) (err error)
EnsureConfigDirs creates the specified subdirectories under the given config directory. This is a generic utility that can be used by any app to create its required structure.
Parameters:
- configDir: The base config directory (e.g., ~/.config/xmlui/)
- subdirs: Slice of path segments to create under configDir
Example:
EnsureConfigDirs(configDir, []dt.PathSegment{"demos", "logs"})
creates ~/.config/xmlui/demos/ and ~/.config/xmlui/logs/
func EnsureLogger ¶
func ErrValue ¶
ErrValue extracts a single metadata value by key with type safety. Returns the value and true if found and the value is of type T. Returns the zero value of T and false if not found or type mismatch.
Example:
status, ok := doterr.ErrValue[int](err, "http_status")
if ok {
fmt.Printf("Status: %d\n", status)
}
name, ok := doterr.ErrValue[string](err, "parameter_name")
func Errors ¶
Errors returns the errors stored on a doterr entry. If err is a doterr entry, returns its errors. If err is a joined error (has Unwrap() []error), scans immediate children left-to-right and returns errors from the first doterr entry found. Otherwise returns nil. The returned slice preserves insertion order and is a copy.
Note: These errors may be sentinel errors (e.g., ErrRepo), custom error types (e.g., *rfc9457.Error), or any other error type stored in the entry.
func FindErr ¶
FindErr walks an error tree (including errors.Join trees) and returns the first match for target (via errors.As).
func GetAppCacheDir ¶
func GetAppCacheDir(slug, appName dt.PathSegment, opts ...CacheOptions) (dt.DirPath, error)
GetAppCacheDir returns an app-specific cache directory under the shared cache. Platform-specific paths:
- macOS: ~/Library/Caches/{slug}/{appName}/
- Linux: ~/.cache/{slug}/{appName}/
- Windows: %LOCALAPPDATA%\{slug}\{appName}\
Example: GetAppCacheDir("xmlui", "cli") → ~/.cache/xmlui/cli/ on Linux
func GetBaseFilename ¶
func GetSharedCacheDir ¶
func GetSharedCacheDir(slug dt.PathSegment, opts ...CacheOptions) (dt.DirPath, error)
GetSharedCacheDir returns the shared cache directory for the given slug. Platform-specific paths:
- macOS: ~/Library/Caches/{slug}/
- Linux: ~/.cache/{slug}/
- Windows: %LOCALAPPDATA%\{slug}\
Example: GetSharedCacheDir("xmlui") → ~/.cache/xmlui/ on Linux
func InitProjectConfig ¶ added in v0.4.0
func InitProjectConfig[RC any, PRC RootConfigPtr[RC]]( configSlug dt.PathSegment, configFile dt.RelFilepath, opts Options, ) (prc PRC, err error)
InitProjectConfig initializes a project config. Returns the initialized config and an error (ErrConfigAlreadyExists if config already exists).
func LoadCLIConfig ¶ added in v0.4.0
func LoadCLIConfig[RC any, PRC RootConfigPtr[RC]](args LoadConfigArgs) (PRC, error)
LoadCLIConfig loads configuration from CLI config directory only (~/.config/<slug>). This is a convenience function for the common case of loading only user-level CLI configuration.
Example:
config, err := cfgstore.LoadCLIConfig[MyConfig, *MyConfig](cfgstore.LoadConfigArgs{
ConfigSlug: dt.PathSegment("myapp"),
ConfigFile: dt.RelFilepath("config.json"),
Options: myOptions, // or nil
})
func LoadConfig ¶ added in v0.4.0
func LoadConfig[RC any, PRC RootConfigPtr[RC]](args LoadConfigArgs) (prc PRC, err error)
LoadConfig loads configuration from one or more config stores with sensible defaults. This is the core flexible function that all convenience functions delegate to.
Defaults applied: - DirTypes: [CLIConfigDirType, ProjectConfigDirType] if not specified - DirsProvider: DefaultDirsProvider() if not specified - Options: nil is acceptable (passed through to Normalize)
func LoadConfigStores ¶ added in v0.4.0
func LoadConfigStores[RC any, PRC RootConfigPtr[RC]](stores *ConfigStores, args RootConfigArgs) (prc PRC, err error)
LoadConfigStores loads and merges root configuration from multiple config stores. Later stores in the DirTypes array take precedence over earlier ones. This is the low-level function that provides maximum control over the loading process.
Use this when you need to: - Pre-create ConfigStores for reuse - Test with custom ConfigStores - Have fine-grained control over store creation and configuration loading
For simpler use cases, consider using LoadConfig, LoadCLIConfig, LoadProjectConfig, or LoadDefaultConfig instead.
func LoadDefaultConfig ¶ added in v0.4.0
func LoadDefaultConfig[RC any, PRC RootConfigPtr[RC]](args LoadConfigArgs) (PRC, error)
LoadDefaultConfig loads configuration with default precedence: CLI + Project. Project configuration takes precedence over CLI configuration. This is the most common multi-store pattern for applications that support both user-level defaults and project-specific overrides.
Example:
config, err := cfgstore.LoadDefaultConfig[MyConfig, *MyConfig](cfgstore.LoadConfigArgs{
ConfigSlug: dt.PathSegment("myapp"),
ConfigFile: dt.RelFilepath("config.json"),
Options: myOptions, // or nil
})
func LoadProjectConfig ¶ added in v0.4.0
func LoadProjectConfig[RC any, PRC RootConfigPtr[RC]](args LoadConfigArgs) (PRC, error)
LoadProjectConfig loads configuration from project directory only (./<slug>). This is a convenience function for the common case of loading only project-specific configuration.
Example:
config, err := cfgstore.LoadProjectConfig[MyConfig, *MyConfig](cfgstore.LoadConfigArgs{
ConfigSlug: dt.PathSegment("myapp"),
ConfigFile: dt.RelFilepath("config.json"),
Options: myOptions, // or nil
})
func LogOnError ¶
func LogOnError(err error)
func NewErr ¶
NewErr builds a standalone structured entry (no primary cause inside). Accepted parts:
- error — sentinel/tag (required: at least one, must be first)
- KV{Key,Value} — explicit key/value
- "key", value — implicit pair (value can be any type, including error)
- error — optional trailing cause (joined last via errors.Join)
Pattern: one or more sentinels (error), then zero or more key-value pairs, then optional trailing cause (error). After the first string key, all remaining args must form valid pairs, except for an optional final error. Returns nil if no meaningful parts are provided after validation. Returns a validation error joined with the partial entry if validation fails.
func NoSuchFileOrDirectory ¶
func ProjectConfigDir ¶
func ProjectConfigDir(configSlug dt.PathSegment) (cd dt.DirPath, err error)
func ReadFileIfExists ¶
func WithErr ¶
WithErr is a flexible enrichment helper. Typical uses:
// Enrich an existing composite error (err may be an errors.Join tree):
err = doterr.With(err, "Foo", 10)
// Build an entry and join a trailing cause in one shot:
err = doterr.With("endpoint", ep, ErrTemplate, cause) // 'cause' is last
Behavior:
If the FIRST arg is an error, it is treated as the base error to enrich: • If it is a doterr entry, merge KVs/sentinels into that entry. • If it is a multi-unwrap (errors.Join tree), find the RIGHTMOST doterr entry, merge into it, and rebuild preserving order. • If no doterr entry is found, a new entry will be joined in (see step 3).
After consuming the base (if present), if the LAST remaining arg is an error, it is treated as the CAUSE and joined LAST.
The remaining middle args (if any) are collected into an entry. If we enriched an existing doterr entry in step 1, that merged entry is used; otherwise, a fresh entry is created. If there is a trailing CAUSE from step 2, the result is errors.Join(entry, cause). If there is no cause, the entry is returned.
Note: For inter-function composition, prefer New() with trailing cause:
return doterr.New(ErrRepo, "key", val, cause) // cause last
Types ¶
type CacheOptions ¶
type CacheOptions struct {
DirsProvider *DirsProvider
}
CacheOptions provides optional configuration for cache directory functions
type ConfigStore ¶
type ConfigStore interface {
Load() ([]byte, error)
Save([]byte) error
LoadJSON(data any, opts ...jsonv2.Options) error
SaveJSON(data any) error
Exists() bool
GetFilepath() (dt.Filepath, error)
GetRelFilepath() dt.RelFilepath
SetRelFilepath(dt.RelFilepath)
SetConfigDir(dt.DirPath)
ConfigDir() (dt.DirPath, error)
EnsureDirs(subdirs []dt.PathSegment) error
WithDirType(DirType) ConfigStore
DirType() DirType
ConfigStore()
ConfigSlug() dt.PathSegment
}
ConfigStore provides file operations for Gmail APIConfig
func NewCLIConfigStore ¶
func NewCLIConfigStore(configSlug dt.PathSegment, configFile dt.RelFilepath) ConfigStore
func NewConfigStore ¶
func NewConfigStore(dirType DirType, args ConfigStoreArgs) ConfigStore
func NewProjectConfigStore ¶
func NewProjectConfigStore(configSlug dt.PathSegment, configFile dt.RelFilepath) ConfigStore
type ConfigStoreArgs ¶
type ConfigStoreArgs struct {
// ConfigSlug is the single path segment used for ~/.config/<slug>
ConfigSlug dt.PathSegment
// RelFilepath is the filename to be used for a file in the config directory which
// may optionally include one or more parent paths but should not be an absolute
// path.
RelFilepath dt.RelFilepath
// DirsProvider is typically never used for production code. It is intended only
// to be used for test code in conjunction with go-the fsfix package
DirsProvider *DirsProvider
}
type ConfigStoreMap ¶
type ConfigStoreMap map[DirType]ConfigStore
type ConfigStores ¶
type ConfigStores struct {
DirTypes []DirType
StoreMap ConfigStoreMap
}
func NewConfigStores ¶
func NewConfigStores(args ConfigStoresArgs) (css *ConfigStores)
func (*ConfigStores) AppConfigStore ¶
func (stores *ConfigStores) AppConfigStore() (cs ConfigStore)
func (*ConfigStores) CLIConfigStore ¶
func (stores *ConfigStores) CLIConfigStore() (cs ConfigStore)
func (*ConfigStores) FirstStore ¶
func (stores *ConfigStores) FirstStore() (cs ConfigStore)
FirstStore returns the store identified by the first element in the DirTypes array
func (*ConfigStores) LastStore ¶
func (stores *ConfigStores) LastStore() (cs ConfigStore)
LastStore returns the store identified by the last element in the DirTypes array
func (*ConfigStores) ProjectConfigStore ¶
func (stores *ConfigStores) ProjectConfigStore() (cs ConfigStore)
type ConfigStoresArgs ¶
type ConfigStoresArgs struct {
ConfigStoreArgs
DirTypes []DirType
DirsProvider *DirsProvider
}
type DirsProvider ¶
type DirsProvider struct {
UserHomeDirFunc DirFunc
GetwdFunc DirFunc
ProjectDirFunc DirFunc
UserConfigDirFunc DirFunc
CLIConfigDirFunc DirFunc
UserCacheDirFunc DirFunc
}
func DefaultDirsProvider ¶
func DefaultDirsProvider() *DirsProvider
func (*DirsProvider) CLIConfigDirType ¶ added in v0.1.2
func (dp *DirsProvider) CLIConfigDirType() (dir dt.DirPath, err error)
CLIConfigDirType returns the absolute of either ~/.config/ or XDG_CONFIG_HOME on Linux
type KV ¶
KV represents a key/value metadata pair. Keys are preserved in insertion order, and values may be of any type.
func ErrMeta ¶
ErrMeta returns the key/value pairs stored on a doterr entry. If err is a doterr entry, returns its metadata. If err is a joined error (has Unwrap() []error), scans immediate children left-to-right and returns metadata from the first doterr entry found. Otherwise returns nil. The returned slice preserves insertion order and is a copy.
type LoadConfigArgs ¶ added in v0.4.0
type LoadConfigArgs struct {
ConfigSlug dt.PathSegment
ConfigFile dt.RelFilepath
DirTypes []DirType // optional: defaults to [CLIConfigDirType, ProjectConfigDirType]
DirsProvider *DirsProvider // optional: defaults to DefaultDirsProvider()
Options Options // optional: can be nil
}
LoadConfigArgs provides arguments for loading configuration with sensible defaults.
type NormalizeArgs ¶
type RootConfig ¶
type RootConfig interface {
RootConfig()
Normalize(NormalizeArgs) error
Merge(RootConfig) RootConfig
}
type RootConfigArgs ¶
type RootConfigArgs struct {
DirTypes []DirType
Options Options
DirsProvider *DirsProvider
}
type RootConfigMap ¶
type RootConfigMap map[DirType]RootConfig
type RootConfigPtr ¶
type RootConfigPtr[RC any] interface { RootConfig *RC }