dir
API
dir
packageAPI reference for the dir
package.
Imports
(12)
S
struct
Provider
Provider implements configuration.Provider for a directory of JSON files.
core/configuration/source/dir/provider.go:30-39
type Provider struct
Methods
Load
Method
Load reads every matching file in the directory and returns a flat key-value map. Each file's content is flattened with colon-separated keys prefixed by the file base name without extension: "tenants/acme.json" -> "acme:quota". Files are processed in sorted name order so the result is deterministic.
Parameters
ctx
context.Context
Returns
map[string]any
error
func (*Provider) Load(ctx context.Context) (map[string]any, error)
{
if err := ctx.Err(); err != nil {
return nil, err
}
entries, err := os.ReadDir(p.Dir)
if err != nil {
return nil, err
}
excluded := make(map[string]bool, len(p.Exclude))
for _, e := range p.Exclude {
excluded[e] = true
}
names := make([]string, 0, len(entries))
for _, entry := range entries {
if entry.IsDir() || excluded[entry.Name()] {
continue
}
ok, err := filepath.Match(p.Pattern, entry.Name())
if err != nil {
return nil, fmt.Errorf("configuration: invalid dir pattern %q: %w", p.Pattern, err)
}
if ok {
names = append(names, entry.Name())
}
}
sort.Strings(names)
flat := make(map[string]any)
for _, name := range names {
select {
case <-ctx.Done():
return nil, ctx.Err()
default:
}
data, err := readLimited(filepath.Join(p.Dir, name))
if err != nil {
return nil, fmt.Errorf("configuration: %s: %w", name, err)
}
var parsed map[string]any
if err := json.Unmarshal(data, &parsed); err != nil {
return nil, fmt.Errorf("configuration: %s: %w", name, err)
}
prefix := strings.TrimSuffix(name, filepath.Ext(name))
flatten(prefix, parsed, flat)
}
return flat, nil
}
Fields
| Name | Type | Description |
|---|---|---|
| Dir | string | |
| Pattern | string | |
| Exclude | []string |
F
function
New
New creates a new directory provider. pattern is a glob like “*.json”.
Parameters
dir
string
pattern
string
exclude
...string
Returns
core/configuration/source/dir/provider.go:42-44
func New(dir, pattern string, exclude ...string) *Provider
{
return &Provider{Dir: dir, Pattern: pattern, Exclude: exclude}
}
F
function
readLimited
Parameters
path
string
Returns
[]byte
error
core/configuration/source/dir/provider.go:111-125
func readLimited(path string) ([]byte, error)
{
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
data, err := io.ReadAll(io.LimitReader(f, maxFileSize+1))
if err != nil {
return nil, err
}
if len(data) > maxFileSize {
return nil, errors.New("file exceeds 4 MiB limit")
}
return data, nil
}
F
function
flatten
flatten mirrors source/file: nested objects become colon-separated keys.
Parameters
prefix
string
src
map[string]any
dst
map[string]any
core/configuration/source/dir/provider.go:128-137
func flatten(prefix string, src map[string]any, dst map[string]any)
{
for k, v := range src {
key := prefix + ":" + k
if nested, ok := v.(map[string]any); ok {
flatten(key, nested, dst)
} else {
dst[key] = v
}
}
}
F
function
writeFile
Parameters
core/configuration/source/dir/provider_test.go:11-16
func writeFile(t *testing.T, dir, name, content string)
{
t.Helper()
if err := os.WriteFile(filepath.Join(dir, name), []byte(content), 0o644); err != nil {
t.Fatal(err)
}
}
F
function
TestLoadFlattensEachFileUnderItsName
Parameters
t
core/configuration/source/dir/provider_test.go:18-44
func TestLoadFlattensEachFileUnderItsName(t *testing.T)
{
dir := t.TempDir()
writeFile(t, dir, "a.example.json", `{"domain":"a.example","port":8080,"ssl":{"enabled":true}}`)
writeFile(t, dir, "b.example.json", `{"domain":"b.example","port":9090}`)
p := New(dir, "*.json")
got, err := p.Load(context.Background())
if err != nil {
t.Fatal(err)
}
want := map[string]any{
"a.example:domain": "a.example",
"a.example:port": float64(8080),
"a.example:ssl:enabled": true,
"b.example:domain": "b.example",
"b.example:port": float64(9090),
}
if len(got) != len(want) {
t.Fatalf("got %d keys, want %d: %v", len(got), len(want), got)
}
for k, v := range want {
if got[k] != v {
t.Errorf("key %q: got %v, want %v", k, got[k], v)
}
}
}
F
function
TestLoadHonorsPatternAndExclude
Parameters
t
core/configuration/source/dir/provider_test.go:46-74
func TestLoadHonorsPatternAndExclude(t *testing.T)
{
dir := t.TempDir()
writeFile(t, dir, "alpha.json", `{"a":1}`)
writeFile(t, dir, "conf.global.json", `{"b":2}`)
writeFile(t, dir, "notes.txt", `{"c":3}`)
sub := filepath.Join(dir, "nested")
if err := os.Mkdir(sub, 0o755); err != nil {
t.Fatal(err)
}
writeFile(t, sub, "inner.json", `{"d":4}`)
p := New(dir, "*.json", "conf.global.json")
got, err := p.Load(context.Background())
if err != nil {
t.Fatal(err)
}
if _, ok := got["alpha:a"]; !ok {
t.Error("alpha.json not loaded")
}
if _, ok := got["conf.global:b"]; ok {
t.Error("excluded file was loaded")
}
if _, ok := got["notes:c"]; ok {
t.Error("non-matching pattern was loaded")
}
if _, ok := got["inner:d"]; ok {
t.Error("subdirectory file was loaded")
}
}
F
function
TestLoadReportsBadJSONWithFileName
Parameters
t
core/configuration/source/dir/provider_test.go:76-88
func TestLoadReportsBadJSONWithFileName(t *testing.T)
{
dir := t.TempDir()
writeFile(t, dir, "broken.json", `{invalid`)
p := New(dir, "*.json")
_, err := p.Load(context.Background())
if err == nil {
t.Fatal("expected error")
}
if got := err.Error(); !strings.Contains(got, "broken.json") {
t.Errorf("error %q does not name the file", got)
}
}
F
function
TestLoadMissingDirectory
Parameters
t
core/configuration/source/dir/provider_test.go:90-95
func TestLoadMissingDirectory(t *testing.T)
{
p := New(filepath.Join(t.TempDir(), "nope"), "*.json")
if _, err := p.Load(context.Background()); err == nil {
t.Fatal("expected error for missing directory")
}
}
F
function
TestLoadCanceledContext
Parameters
t
core/configuration/source/dir/provider_test.go:97-104
func TestLoadCanceledContext(t *testing.T)
{
ctx, cancel := context.WithCancel(context.Background())
cancel()
p := New(t.TempDir(), "*.json")
if _, err := p.Load(ctx); err == nil {
t.Fatal("expected context error")
}
}