Preserve closed callback failures through the facade
This commit is contained in:
51
facade_context_test.go
Normal file
51
facade_context_test.go
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
package configkit_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
configkit "gitea.maximumdirect.net/eric/ConfigKit"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFacadeInactiveContextsPreserveClosedFailure(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
for _, state := range []struct {
|
||||||
|
name string
|
||||||
|
resolution *configkit.Resolution
|
||||||
|
validation *configkit.Validation
|
||||||
|
}{
|
||||||
|
{name: "nil"},
|
||||||
|
{name: "zero", resolution: &configkit.Resolution{}, validation: &configkit.Validation{}},
|
||||||
|
} {
|
||||||
|
t.Run(state.name, func(t *testing.T) {
|
||||||
|
path := configkit.Keys("value")
|
||||||
|
var target int
|
||||||
|
for _, operation := range []struct {
|
||||||
|
name string
|
||||||
|
call func()
|
||||||
|
}{
|
||||||
|
{"IsExplicit", func() { state.resolution.IsExplicit(path) }},
|
||||||
|
{"InputInfo", func() { state.resolution.InputInfo(path) }},
|
||||||
|
{"Source", func() { state.resolution.Source("source") }},
|
||||||
|
{"Record", func() { state.resolution.Record(path, configkit.ChangeNormalization, "reason") }},
|
||||||
|
{"Resolution.Error", func() { state.resolution.Error(path, "message") }},
|
||||||
|
{"Resolution.Warning", func() { state.resolution.Warning(path, "message") }},
|
||||||
|
{"Default", func() { configkit.Default(state.resolution, path, &target, 1, "reason") }},
|
||||||
|
{"Normalize", func() { configkit.Normalize(state.resolution, path, &target, 1, "reason") }},
|
||||||
|
{"Validation.Error", func() { state.validation.Error(path, "message") }},
|
||||||
|
{"Validation.Warning", func() { state.validation.Warning(path, "message") }},
|
||||||
|
} {
|
||||||
|
t.Run(operation.name, func(t *testing.T) {
|
||||||
|
defer func() {
|
||||||
|
if got := recover(); got != "configkit: callback context is no longer active" {
|
||||||
|
t.Errorf("panic = %v; want fixed closed-context failure", got)
|
||||||
|
}
|
||||||
|
if target != 0 {
|
||||||
|
t.Error("inactive helper changed target")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
operation.call()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
37
lifecycle.go
37
lifecycle.go
@@ -134,7 +134,9 @@ func (r *Result[T]) Diagnostics() []Diagnostic {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// IsExplicit reports whether surviving input explicitly defined path.
|
// IsExplicit reports whether surviving input explicitly defined path.
|
||||||
func (r *Resolution) IsExplicit(path Path) bool { return r.value.IsExplicit(toEnginePath(path)) }
|
func (r *Resolution) IsExplicit(path Path) bool {
|
||||||
|
return r.engineContext().IsExplicit(toEnginePath(path))
|
||||||
|
}
|
||||||
|
|
||||||
// InputInfo returns detached entry-state metadata for path. The entry state is
|
// InputInfo returns detached entry-state metadata for path. The entry state is
|
||||||
// the decoded configuration, including retained static defaults, immediately
|
// the decoded configuration, including retained static defaults, immediately
|
||||||
@@ -144,7 +146,7 @@ func (r *Resolution) IsExplicit(path Path) bool { return r.value.IsExplicit(toEn
|
|||||||
// InputInfo is valid only during the resolution callback. See the configuration
|
// InputInfo is valid only during the resolution callback. See the configuration
|
||||||
// contract for source and provenance semantics.
|
// contract for source and provenance semantics.
|
||||||
func (r *Resolution) InputInfo(path Path) (FieldInfo, bool) {
|
func (r *Resolution) InputInfo(path Path) (FieldInfo, bool) {
|
||||||
value, ok := r.value.InputInfo(toEnginePath(path))
|
value, ok := r.engineContext().InputInfo(toEnginePath(path))
|
||||||
return fromEngineFieldInfo(value), ok
|
return fromEngineFieldInfo(value), ok
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,47 +157,62 @@ func (r *Resolution) InputInfo(path Path) (FieldInfo, bool) {
|
|||||||
// Source is valid only during the resolution callback. See the configuration
|
// Source is valid only during the resolution callback. See the configuration
|
||||||
// contract for source and provenance semantics.
|
// contract for source and provenance semantics.
|
||||||
func (r *Resolution) Source(id string) (SourceInfo, bool) {
|
func (r *Resolution) Source(id string) (SourceInfo, bool) {
|
||||||
value, ok := r.value.Source(id)
|
value, ok := r.engineContext().Source(id)
|
||||||
return fromEngineSourceInfo(value), ok
|
return fromEngineSourceInfo(value), ok
|
||||||
}
|
}
|
||||||
|
|
||||||
// Record declares the intent for a resolver mutation. A container path covers
|
// Record declares the intent for a resolver mutation. A container path covers
|
||||||
// mutations to its descendants.
|
// mutations to its descendants.
|
||||||
func (r *Resolution) Record(path Path, kind ChangeKind, reason string, dependencies ...Path) {
|
func (r *Resolution) Record(path Path, kind ChangeKind, reason string, dependencies ...Path) {
|
||||||
r.value.Record(toEnginePath(path), engine.ChangeKind(kind), reason, toEnginePaths(dependencies)...)
|
r.engineContext().Record(toEnginePath(path), engine.ChangeKind(kind), reason, toEnginePaths(dependencies)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Error reports a safe, value-free resolution error at path.
|
// Error reports a safe, value-free resolution error at path.
|
||||||
func (r *Resolution) Error(path Path, message string, related ...Path) {
|
func (r *Resolution) Error(path Path, message string, related ...Path) {
|
||||||
r.value.Error(toEnginePath(path), message, toEnginePaths(related)...)
|
r.engineContext().Error(toEnginePath(path), message, toEnginePaths(related)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Warning reports a safe, value-free resolution warning at path.
|
// Warning reports a safe, value-free resolution warning at path.
|
||||||
func (r *Resolution) Warning(path Path, message string, related ...Path) {
|
func (r *Resolution) Warning(path Path, message string, related ...Path) {
|
||||||
r.value.Warning(toEnginePath(path), message, toEnginePaths(related)...)
|
r.engineContext().Warning(toEnginePath(path), message, toEnginePaths(related)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default assigns and records value only when path was absent from surviving
|
// Default assigns and records value only when path was absent from surviving
|
||||||
// input. Static defaults are not considered explicit.
|
// input. Static defaults are not considered explicit.
|
||||||
func Default[V any](resolution *Resolution, path Path, target *V, value V, reason string, dependencies ...Path) {
|
func Default[V any](resolution *Resolution, path Path, target *V, value V, reason string, dependencies ...Path) {
|
||||||
engine.Default(resolution.value, toEnginePath(path), target, value, reason, toEnginePaths(dependencies)...)
|
engine.Default(resolution.engineContext(), toEnginePath(path), target, value, reason, toEnginePaths(dependencies)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Normalize assigns and records value regardless of input presence.
|
// Normalize assigns and records value regardless of input presence.
|
||||||
func Normalize[V any](resolution *Resolution, path Path, target *V, value V, reason string, dependencies ...Path) {
|
func Normalize[V any](resolution *Resolution, path Path, target *V, value V, reason string, dependencies ...Path) {
|
||||||
engine.Normalize(resolution.value, toEnginePath(path), target, value, reason, toEnginePaths(dependencies)...)
|
engine.Normalize(resolution.engineContext(), toEnginePath(path), target, value, reason, toEnginePaths(dependencies)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Error reports a value-free application error at path. Related paths are
|
// Error reports a value-free application error at path. Related paths are
|
||||||
// resolved to source locations when possible.
|
// resolved to source locations when possible.
|
||||||
func (v *Validation) Error(path Path, message string, related ...Path) {
|
func (v *Validation) Error(path Path, message string, related ...Path) {
|
||||||
v.value.Error(toEnginePath(path), message, toEnginePaths(related)...)
|
v.engineContext().Error(toEnginePath(path), message, toEnginePaths(related)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Warning reports a value-free application warning at path. Related paths are
|
// Warning reports a value-free application warning at path. Related paths are
|
||||||
// resolved to source locations when possible.
|
// resolved to source locations when possible.
|
||||||
func (v *Validation) Warning(path Path, message string, related ...Path) {
|
func (v *Validation) Warning(path Path, message string, related ...Path) {
|
||||||
v.value.Warning(toEnginePath(path), message, toEnginePaths(related)...)
|
v.engineContext().Warning(toEnginePath(path), message, toEnginePaths(related)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pass nil through so the engine remains the owner of the callback guard.
|
||||||
|
func (r *Resolution) engineContext() *engine.Resolution {
|
||||||
|
if r == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return r.value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *Validation) engineContext() *engine.Validation {
|
||||||
|
if v == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return v.value
|
||||||
}
|
}
|
||||||
|
|
||||||
func toEngineOptions[T any](options Options[T]) engine.Options[T] {
|
func toEngineOptions[T any](options Options[T]) engine.Options[T] {
|
||||||
|
|||||||
Reference in New Issue
Block a user