Add facade lifecycle wrappers
This commit is contained in:
@@ -2,6 +2,7 @@ package configkit_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
@@ -22,7 +23,12 @@ func TestFacadeValuesRemainRootOwned(t *testing.T) {
|
||||
for _, candidate := range []reflect.Type{
|
||||
reflect.TypeFor[configkit.Path](), reflect.TypeFor[configkit.Source](),
|
||||
reflect.TypeFor[configkit.Secret](), reflect.TypeFor[configkit.Diagnostic](),
|
||||
reflect.TypeFor[configkit.LoadError](),
|
||||
reflect.TypeFor[configkit.LoadError](), reflect.TypeFor[configkit.Metadata](),
|
||||
reflect.TypeFor[configkit.Resolution](), reflect.TypeFor[configkit.Validation](),
|
||||
reflect.TypeFor[configkit.Version](), reflect.TypeFor[configkit.VersionRule](),
|
||||
reflect.TypeFor[configkit.FilesystemContext](), reflect.TypeFor[configkit.Override](),
|
||||
reflect.TypeFor[configkit.Definition[struct{}]](), reflect.TypeFor[configkit.Options[struct{}]](),
|
||||
reflect.TypeFor[configkit.Result[struct{}]](),
|
||||
} {
|
||||
if candidate.PkgPath() != packagePath {
|
||||
t.Fatalf("type %s belongs to %q", candidate, candidate.PkgPath())
|
||||
@@ -41,3 +47,106 @@ func TestFacadeValuesRemainRootOwned(t *testing.T) {
|
||||
t.Fatalf("zero LoadError = %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFacadeLoadBridgesLifecycleCallbacks(t *testing.T) {
|
||||
t.Setenv("FACADE_SECRET_NAME", "credential")
|
||||
|
||||
type configuration struct {
|
||||
Version string `yaml:"version"`
|
||||
Input int `yaml:"input"`
|
||||
Derived int `yaml:"derived"`
|
||||
Secret configkit.Secret `yaml:"secret"`
|
||||
Labels map[string]string `yaml:"labels"`
|
||||
}
|
||||
var retainedResolution *configkit.Resolution
|
||||
var retainedValidation *configkit.Validation
|
||||
versionCalls, defaultsCalls, resolveCalls, validateCalls := 0, 0, 0, 0
|
||||
definition := configkit.Definition[configuration]{
|
||||
Version: &configkit.VersionRule{Path: configkit.Keys("version"), Check: func(version configkit.Version, validation *configkit.Validation) error {
|
||||
versionCalls++
|
||||
retainedValidation = validation
|
||||
if !version.Present || version.Text != "1" {
|
||||
validation.Error(configkit.Keys("version"), "unsupported version")
|
||||
}
|
||||
return nil
|
||||
}},
|
||||
Defaults: func() configuration {
|
||||
defaultsCalls++
|
||||
return configuration{Derived: 1, Labels: map[string]string{"default": "kept"}}
|
||||
},
|
||||
Resolve: func(configuration *configuration, resolution *configkit.Resolution) error {
|
||||
resolveCalls++
|
||||
retainedResolution = resolution
|
||||
if !resolution.IsExplicit(configkit.Keys("input")) {
|
||||
resolution.Error(configkit.Keys("input"), "input is required")
|
||||
}
|
||||
input, ok := resolution.InputInfo(configkit.Keys("input"))
|
||||
if !ok || !input.Explicit {
|
||||
resolution.Error(configkit.Keys("input"), "missing input provenance")
|
||||
}
|
||||
source, ok := resolution.Source("source-1")
|
||||
if !ok || source.Label != "facade.yml" {
|
||||
resolution.Error(configkit.Keys("input"), "missing source")
|
||||
}
|
||||
configkit.Default(resolution, configkit.Keys("derived"), &configuration.Derived, configuration.Input*2, "derived from input", configkit.Keys("input"))
|
||||
return nil
|
||||
},
|
||||
Validate: func(configuration *configuration, validation *configkit.Validation) {
|
||||
validateCalls++
|
||||
retainedValidation = validation
|
||||
if !configuration.Secret.IsResolved() || configuration.Secret.Value() != "credential" {
|
||||
validation.Error(configkit.Keys("secret"), "secret did not resolve")
|
||||
}
|
||||
},
|
||||
}
|
||||
if versionCalls != 0 || defaultsCalls != 0 || resolveCalls != 0 || validateCalls != 0 {
|
||||
t.Fatal("callbacks ran while constructing options")
|
||||
}
|
||||
|
||||
result, err := configkit.Load(configkit.Bytes("facade.yml", []byte("version: 1\ninput: 3\nsecret: FACADE_SECRET_NAME\n")), configkit.Options[configuration]{Definition: definition})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if versionCalls != 1 || defaultsCalls != 1 || resolveCalls != 1 || validateCalls != 1 || result.Config.Derived != 6 {
|
||||
t.Fatalf("callback counts/configuration = %d/%d/%d/%d %#v", versionCalls, defaultsCalls, resolveCalls, validateCalls, result.Config)
|
||||
}
|
||||
derived, ok := result.Metadata().Lookup(configkit.Keys("derived"))
|
||||
if !ok || derived.Explicit || derived.Origin.Operation != configkit.ChangeDerivedDefault {
|
||||
t.Fatalf("derived metadata = %#v, %v", derived, ok)
|
||||
}
|
||||
if sources := result.Sources(); len(sources) != 1 || sources[0].Label != "facade.yml" {
|
||||
t.Fatalf("sources = %#v", sources)
|
||||
}
|
||||
if result.Diagnostics() != nil {
|
||||
t.Fatalf("diagnostics = %#v", result.Diagnostics())
|
||||
}
|
||||
for _, context := range []func(){
|
||||
func() { retainedResolution.IsExplicit(configkit.Keys("input")) },
|
||||
func() { retainedValidation.Warning(configkit.Keys("input"), "too late") },
|
||||
} {
|
||||
func() {
|
||||
defer func() {
|
||||
if recover() == nil {
|
||||
t.Fatal("retained callback context remained active")
|
||||
}
|
||||
}()
|
||||
context()
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func TestFacadeLoadReturnsFacadeLoadError(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
type configuration struct {
|
||||
Value int `yaml:"value"`
|
||||
}
|
||||
result, err := configkit.Load[configuration](configkit.Bytes("invalid.yml", []byte("value: wrong\n")))
|
||||
if result != nil || err == nil {
|
||||
t.Fatalf("result, err = %#v, %v", result, err)
|
||||
}
|
||||
var loadError *configkit.LoadError
|
||||
if !errors.As(err, &loadError) || len(loadError.Diagnostics()) != 1 || loadError.Diagnostics()[0].Code != configkit.CodeDecodeType {
|
||||
t.Fatalf("facade error = %#v", err)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user