Enable shared configuration version checks
This commit is contained in:
@@ -17,6 +17,7 @@ import (
|
||||
)
|
||||
|
||||
type document struct {
|
||||
Version *string `yaml:"version"`
|
||||
Backends map[string]backendConfig `yaml:"backends"`
|
||||
Profiles map[string]profileConfig `yaml:"profiles"`
|
||||
Prompts map[string]promptConfig `yaml:"prompts"`
|
||||
@@ -152,8 +153,13 @@ func load(main configkit.Input, underlays []configkit.Underlay) (*loaded, error)
|
||||
promptMetadata: make(map[string][]promptInputPresence),
|
||||
}
|
||||
result, err := configkit.LoadLayered(main, configkit.LayeredOptions[document]{
|
||||
Underlays: underlays,
|
||||
Underlays: underlays,
|
||||
SourceVersionMode: configkit.SourceVersionModeIfPresent,
|
||||
Definition: configkit.Definition[document]{
|
||||
Version: &configkit.VersionRule{
|
||||
Path: configkit.Keys("version"),
|
||||
Check: checkConfigurationVersion,
|
||||
},
|
||||
Secret: secretProviderPath,
|
||||
Resolve: func(config *document, resolution *configkit.Resolution) error {
|
||||
captureFields(config.Backends, bases.backends, backendFields, resolution, state.backends)
|
||||
@@ -186,6 +192,16 @@ func load(main configkit.Input, underlays []configkit.Underlay) (*loaded, error)
|
||||
return state, nil
|
||||
}
|
||||
|
||||
func checkConfigurationVersion(version configkit.Version, validation *configkit.Validation) error {
|
||||
if !version.Present {
|
||||
return nil
|
||||
}
|
||||
if version.Null || version.Text != "1" {
|
||||
validation.Error(configkit.Keys("version"), "unsupported configuration specification version")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type promptInputPresence struct {
|
||||
contentType fieldState
|
||||
description fieldState
|
||||
|
||||
@@ -2,6 +2,7 @@ package configuration
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"gitea.maximumdirect.net/eric/ConfigKit"
|
||||
@@ -11,6 +12,77 @@ func mapping(label string, values map[string]any) configkit.Input {
|
||||
return configkit.Input{Source: configkit.Mapping(label, values)}
|
||||
}
|
||||
|
||||
func TestLoadChecksOptionalConfigurationVersion(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
name string
|
||||
version any
|
||||
include bool
|
||||
valid bool
|
||||
}{
|
||||
{name: "string one", version: "1", include: true, valid: true},
|
||||
{name: "integer one", version: int64(1), include: true, valid: true},
|
||||
{name: "omitted", valid: true},
|
||||
{name: "explicit null", include: true},
|
||||
{name: "unsupported string", version: "2", include: true},
|
||||
} {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
values := map[string]any{}
|
||||
if test.include {
|
||||
values["version"] = test.version
|
||||
}
|
||||
loaded, err := load(mapping("main", values), nil)
|
||||
if test.valid {
|
||||
if err != nil {
|
||||
t.Fatalf("load accepted version: %v", err)
|
||||
}
|
||||
if test.include && (loaded.result.Config.Version == nil || *loaded.result.Config.Version != "1") {
|
||||
t.Fatalf("typed version = %v, want 1", loaded.result.Config.Version)
|
||||
}
|
||||
if !test.include && loaded.result.Config.Version != nil {
|
||||
t.Fatalf("omitted version = %q, want nil", *loaded.result.Config.Version)
|
||||
}
|
||||
return
|
||||
}
|
||||
var loadErr *configkit.LoadError
|
||||
if err == nil || !errors.As(err, &loadErr) {
|
||||
t.Fatalf("load error = %v, want ConfigKit load error", err)
|
||||
}
|
||||
if strings.Contains(err.Error(), "2") {
|
||||
t.Fatalf("load error disclosed the unsupported value: %v", err)
|
||||
}
|
||||
found := false
|
||||
for _, diagnostic := range loadErr.Diagnostics() {
|
||||
if diagnostic.Path.String() == "/version" && diagnostic.Message == "unsupported configuration specification version" {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Fatalf("load diagnostics = %+v, want safe version diagnostic", loadErr.Diagnostics())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadConsumesAcceptedUnderlayVersionAndValidatesHeaderlessPayload(t *testing.T) {
|
||||
underlay := mapping("declared underlay", map[string]any{
|
||||
"version": int64(1),
|
||||
})
|
||||
loaded, err := load(mapping("headerless main", map[string]any{}), []configkit.Underlay{{Inputs: []configkit.Input{underlay}}})
|
||||
if err != nil {
|
||||
t.Fatalf("load accepted non-main declaration: %v", err)
|
||||
}
|
||||
if loaded.result.Config.Version != nil {
|
||||
t.Fatalf("consumed non-main declaration remained in effective payload: %q", *loaded.result.Config.Version)
|
||||
}
|
||||
|
||||
invalidFragment := mapping("headerless prompt fragment", map[string]any{
|
||||
"prompts": map[string]any{"incomplete": map[string]any{"description": "still validated"}},
|
||||
})
|
||||
if _, err := load(configkit.Input{Source: configkit.NoSource()}, []configkit.Underlay{{Inputs: []configkit.Input{invalidFragment}}}); err == nil || !strings.Contains(err.Error(), "at least one message is required") {
|
||||
t.Fatalf("headerless fragment validation error = %v, want final prompt validation", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadComposesLiteralKeysAndEffectivePresence(t *testing.T) {
|
||||
base := mapping("defaults", map[string]any{
|
||||
"backends": map[string]any{
|
||||
|
||||
Reference in New Issue
Block a user