configafero

package module
v0.1.12 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 10, 2026 License: MIT Imports: 3 Imported by: 4

README

config-afero

Adapt an afero filesystem to config.FS, for consumers who already hold one

Go Reference Pipeline Coverage phpboyscout Go toolkit

Part of the phpboyscout Go toolkit — small, framework-free Go modules extracted from go-tool-base. Documented with the parent module at config.go.phpboyscout.uk


config defines its own six-method config.FS interface rather than depending on afero, so importing it does not oblige a consumer to import a filesystem library chosen on their behalf. This is the bridge for consumers who chose afero anyway.

store, err := config.NewStore(ctx,
	config.WithFiles(configafero.Wrap(fsys), "/etc/app.yaml"))

Wrap is the whole API.

You probably do not need it

Two implementations ship with config, and between them they cover most cases:

You want Use
the real filesystem config.OS()
a directory nothing can escape config.Dir(path)
a throwaway directory in a test config.Dir(t.TempDir())

That last row is worth stating plainly, because reaching for an in-memory filesystem in a test is the habit it replaces. config.Dir(t.TempDir()) needs no dependency, gives real file semantics including watching, and exercises the same code path production does.

Reach for this module when something upstream produced an afero.Fs and you have to pass it through — a tool built on afero, a copy-on-write overlay, a base-path root over a virtual worktree.

Why it is a module rather than twenty lines in your codebase

It very nearly is twenty lines. Six methods map onto afero almost directly.

The part worth not rediscovering is which optional interfaces the adapter may claim. config decides whether native filesystem notification can work by asking whether the filesystem satisfies config.RealPather. An adapter implementing that method unconditionally — returning false for an in-memory filesystem rather than not having the method — satisfies the interface for every filesystem. fsnotify gets selected, and the absence of anything to watch is discovered one path at a time instead of up front.

It compiles, it looks right, and it quietly costs native notification with nothing to report it.

So Wrap returns one of four concrete types, claiming RealPather and LinkReader independently:

Filesystem Real paths Symlinks Result
afero.OsFs yes yes native notification, writes follow links
afero.BasePathFs yes yes native notification, verified per path
afero.CopyOnWriteFs no yes polling, writes follow links
afero.MemMapFs no no polling, paths used as given

The copy-on-write row is the one that shows why the two capabilities are claimed separately rather than together: it can read links through its base layer but has no path the operating system can watch. It is also the filesystem go-tool-base uses for dry runs.

A BasePathFs is taken at face value, because afero exposes no way to ask what it wraps. That is safe: config treats a real path as a claim rather than proof and confirms with os.SameFile before watching, falling back to polling per path where the claim does not hold.

The same reasoning applies to config.LinkReader. An earlier version implemented Readlink unconditionally, so every wrapped filesystem claimed it — including in-memory ones that have never had a symlink — which made config's write path allocate and attempt a resolve on every single write. It is now claimed only where afero reports link support.

Dependencies

afero, config, and what those two already bring — asserted by an allowlist test, so an unforeseen transitive addition fails the build rather than arriving quietly.

License

See LICENSE.

Documentation

Overview

Package configafero adapts an afero.Fs to the config.FS interface, so code already holding an afero filesystem can hand it to a config Store unchanged.

The config module defines its own six-method filesystem interface rather than depending on afero, so that importing it does not oblige a consumer to import a filesystem library chosen for them. This adapter is for consumers who have made that choice already — a tool built on afero, or one wrapping a copy-on-write or in-memory filesystem it needs configuration to read through.

store, err := config.NewStore(ctx,
	config.WithFiles(configafero.Wrap(myAferoFs), "/etc/app.yaml"))

If all you need is the real filesystem, you do not need this package: config.OS is that, with no dependency. And a test wanting a throwaway directory wants config.Dir over t.TempDir(), which gives real file semantics including watching.

Why this is not twenty lines you write yourself

It very nearly is. Six methods map onto afero almost directly. The part worth not rediscovering is which *optional* interfaces the adapter may claim.

config asks whether a filesystem satisfies config.RealPather to decide whether native filesystem notification can work at all, and whether it satisfies config.LinkReader to decide whether a write should follow a symlink. An adapter implementing either method unconditionally satisfies the interface for every filesystem, including those that cannot honour it — so config takes a path it should not have, and finds out late or not at all.

Wrap therefore returns one of four types, claiming exactly the capabilities the wrapped filesystem actually has.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Wrap

func Wrap(filesystem afero.Fs) config.FS

Wrap adapts an afero filesystem to config.FS.

The concrete type returned claims only what the filesystem can support: config.RealPather when it has operating-system paths, and config.LinkReader when it can resolve symlinks. A filesystem with neither gets the plain adapter, so config polls it and uses paths as given.

Types

This section is empty.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL