Demonstrate main-relative companion loading

This commit is contained in:
2026-11-09 20:41:54 +00:00
parent 761dd0a4e0
commit 08ea01921b
10 changed files with 113 additions and 1 deletions

View File

@@ -9,7 +9,7 @@ import (
configkit "go.maximumdirect.net/configkit"
)
//go:embed testdata/native-source testdata/layered-bundle testdata/collection-bundle testdata/ordinary-imports testdata/source-version
//go:embed testdata/native-source testdata/layered-bundle testdata/collection-bundle testdata/ordinary-imports testdata/source-version testdata/main-relative-bundle
var bundledFiles embed.FS
// BundledConfig is a small configuration loaded directly from an embedded file.

View File

@@ -0,0 +1,47 @@
package consumers
import (
"io/fs"
"path/filepath"
configkit "go.maximumdirect.net/configkit"
)
// CompanionConfig demonstrates independently bundled and operator prompt tiers.
type CompanionConfig struct {
Mode string `yaml:"mode"`
Prompts []CompanionPrompt `yaml:"prompts"`
}
type CompanionPrompt struct {
ID string `yaml:"id"`
Text string `yaml:"text"`
Note string `yaml:"note"`
}
// LoadCompanionConfig discovers a main file and binds operator imports and the
// selected overlay to its directory while retaining the embedded catalog tier.
func LoadCompanionConfig(mainPath, overlay string) (*configkit.Result[CompanionConfig], error) {
files, err := fs.Sub(bundledFiles, "testdata/main-relative-bundle")
if err != nil {
return nil, err
}
bundle := configkit.NewFilesystem("bundled catalog", files)
absolute, err := filepath.Abs(mainPath)
if err != nil {
return nil, err
}
declarations := map[string]any{"config": map[string]any{"imports": []any{
map[string]any{"path": "prompts.yml", "destination": []any{"prompts"}, "placement": "sequence-item"},
map[string]any{"dir": "operator", "recursive": true, "pattern": []any{"*.yml", "*.yaml"}, "destination": []any{"prompts"}, "placement": "sequence-item"},
}}}
main := configkit.SearchConfig("", configkit.Searches(configkit.SearchPaths(absolute)), configkit.SearchOptions{Required: true})
return configkit.LoadLayered(configkit.Input{Source: main}, configkit.LayeredOptions[CompanionConfig]{
Underlays: []configkit.Underlay{
{Inputs: []configkit.Input{{Source: bundle.File("catalog.yml")}}},
{Inputs: []configkit.Input{{Source: configkit.Mapping("operator imports", declarations), Imports: true, MainRelative: configkit.MainRelative()}}},
},
Overlay: overlay,
Definition: configkit.Definition[CompanionConfig]{SequenceIdentities: []configkit.SequenceIdentity{configkit.SequenceIdentityAt(configkit.Keys("prompts"), "id")}},
})
}

View File

@@ -0,0 +1,46 @@
package consumers_test
import (
"fmt"
"os"
"path/filepath"
"testing"
configkit "go.maximumdirect.net/configkit"
"go.maximumdirect.net/configkit/examples/consumers"
)
func Example_mainRelativeCompanions() {
result, err := consumers.LoadCompanionConfig(fixture("main-relative", "main.yml"), "live")
if err != nil {
fmt.Println(err)
return
}
fmt.Println(result.Config.Mode, result.Config.Prompts)
// Output: overlay [{shared operator replacement } {bundled bundled text } {local local text transitive detail}]
}
func TestMainRelativeStrictPeers(t *testing.T) {
root := t.TempDir()
for name, contents := range map[string]string{
"main.yml": "mode: main\n", "prompts.yml": "id: duplicate\ntext: first\n", "operator/second.yml": "id: duplicate\ntext: second\n",
} {
file := filepath.Join(root, name)
if err := os.MkdirAll(filepath.Dir(file), 0700); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(file, []byte(contents), 0600); err != nil {
t.Fatal(err)
}
}
_, err := consumers.LoadCompanionConfig(filepath.Join(root, "main.yml"), "")
if err == nil {
t.Fatal("duplicate peer identities succeeded")
}
for _, d := range err.(*configkit.LoadError).Diagnostics() {
if d.Code == configkit.CodeComposeConflict {
return
}
}
t.Fatalf("diagnostics = %#v", err.(*configkit.LoadError).Diagnostics())
}

View File

@@ -0,0 +1,7 @@
mode: embedded
prompts:
- id: shared
text: embedded text
note: replaced
- id: bundled
text: bundled text

View File

@@ -0,0 +1 @@
mode: main

View File

@@ -0,0 +1,5 @@
config:
imports:
- path: ../../shared/extra.yml
id: local
text: local text

View File

@@ -0,0 +1 @@
mode: overlay

View File

@@ -0,0 +1,2 @@
id: shared
text: operator replacement

View File

@@ -0,0 +1 @@
note: transitive detail