From 1a74c26bbeaf14c9ff137d314733adbc0e51ea6f Mon Sep 17 00:00:00 2001 From: "@mpyw" Date: Fri, 12 Dec 2025 15:14:30 +0900 Subject: [PATCH] Add Inspection pattern for ergonomic value retrieval MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit introduces a new Inspection pattern that separates context value retrieval from display logic, providing a more ergonomic API. ## Breaking Changes - Remove DebugValue/DebugString methods (replaced by Inspection.String()) ## New Features ### Inspection[V] struct - Captures Key, Value, and Ok (whether set) in one call - Provides helper methods without requiring context: - Get() / TryGet() / GetOrDefault() / MustGet() - IsSet() / IsNotSet() - Implements fmt.Stringer: "key-name: value" or "key-name: " - Implements fmt.GoStringer with package qualification ### BoolInspection struct - Specialized wrapper for boolean feature flags - Provides Enabled() / Disabled() / ExplicitlyDisabled() - Embeds Inspection[bool] for full functionality ### Key[V] interface additions - Inspect(ctx) Inspection[V] - retrieves value with metadata - fmt.GoStringer - returns "feature.Key[T]{name: \"...\"}" ### BoolKey interface additions - InspectBool(ctx) BoolInspection ## Internal Changes - Refactor existing Key methods to use Inspection internally - Split inspection-related code into inspection.go - Split inspection tests into inspection_test.go - Achieve 100% test coverage 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- feature.go | 82 ++++++----- feature_test.go | 129 +++++------------ inspection.go | 117 +++++++++++++++ inspection_test.go | 347 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 541 insertions(+), 134 deletions(-) create mode 100644 inspection.go create mode 100644 inspection_test.go diff --git a/feature.go b/feature.go index c4c102d..e8ccf88 100644 --- a/feature.go +++ b/feature.go @@ -42,6 +42,15 @@ // ctx = MaxItemsKey.WithValue(ctx, 100) // limit := MaxItemsKey.Get(ctx) // Returns 100 // +// # Inspecting Values +// +// Use Inspect to retrieve both the value and whether it was set in one call: +// +// var MaxItems = feature.NewNamed[int]("max-items") +// inspection := MaxItems.Inspect(ctx) +// fmt.Println(inspection) // Output: "max-items: 100" or "max-items: " +// fmt.Println(inspection.IsSet()) // Output: true or false +// // # Key Properties // // - Type-safe: Uses generics to ensure type safety at compile time @@ -93,13 +102,14 @@ type Key[V any] interface { // It is equivalent to !IsSet(ctx). IsNotSet(ctx context.Context) bool - // DebugValue returns a string representation combining the key name and its value from the context. - // This is useful for debugging and logging purposes. - // Format: ": " or ": ". - DebugValue(ctx context.Context) string + // Inspect retrieves the value from the context and returns an Inspection + // that provides convenient methods for working with the result. + Inspect(ctx context.Context) Inspection[V] fmt.Stringer + fmt.GoStringer + // downcast is an internal method used to retrieve the underlying key implementation. // also used for sealing the interface. downcast() key[V] @@ -131,6 +141,10 @@ type BoolKey interface { // WithDisabled returns a new context with this feature flag disabled (set to false). // The original context is not modified. WithDisabled(ctx context.Context) context.Context + + // InspectBool retrieves the value from the context and returns a BoolInspection + // that provides convenience methods for working with boolean feature flags. + InspectBool(ctx context.Context) BoolInspection } // Option is a function that configures the behavior of a feature flag key. @@ -145,7 +159,7 @@ type options struct { } // WithName returns an option that sets a debug name for the key. -// This name is included in the String() output and used in DebugValue() for easier debugging. +// This name is included in the String() output for easier debugging. // // Example: // @@ -284,22 +298,26 @@ type boolKey struct { } // String returns the debug name of the key. +// This implements fmt.Stringer. func (k key[V]) String() string { return k.name } -// DebugValue returns a string representation combining the key name and its value from the context. -// This is useful for debugging and logging purposes. -// Format: ": " or ": ". -func (k key[V]) DebugValue(ctx context.Context) string { - keyName := k.String() +// GoString returns a Go syntax representation of the key. +// This implements fmt.GoStringer. +func (k key[V]) GoString() string { + return fmt.Sprintf("feature.Key[%T]{name: %q}", *new(V), k.name) +} + +// Inspect retrieves the value from the context and returns an Inspection. +func (k key[V]) Inspect(ctx context.Context) Inspection[V] { val, ok := k.TryGet(ctx) - if !ok { - return keyName + ": " + return Inspection[V]{ + Key: k, + Value: val, + Ok: ok, } - - return fmt.Sprintf("%s: %v", keyName, val) } func (k key[V]) downcast() key[V] { @@ -314,9 +332,7 @@ func (k key[V]) WithValue(ctx context.Context, value V) context.Context { // Get retrieves the value associated with this key from the context. // If the key is not set in the context, it returns the zero value of type V. func (k key[V]) Get(ctx context.Context) V { - val, _ := k.TryGet(ctx) - - return val + return k.Inspect(ctx).Get() } // TryGet attempts to retrieve the value associated with this key from the context. @@ -330,51 +346,43 @@ func (k key[V]) TryGet(ctx context.Context) (V, bool) { // GetOrDefault retrieves the value associated with this key from the context. // If the key is not set, it returns the provided default value. func (k key[V]) GetOrDefault(ctx context.Context, defaultValue V) V { - if val, ok := k.TryGet(ctx); ok { - return val - } - - return defaultValue + return k.Inspect(ctx).GetOrDefault(defaultValue) } // MustGet retrieves the value associated with this key from the context. // If the key is not set, it panics with a descriptive error message. func (k key[V]) MustGet(ctx context.Context) V { - val, ok := k.TryGet(ctx) - if !ok { - panic(fmt.Sprintf("key %s is not set in context", k.String())) - } - - return val + return k.Inspect(ctx).MustGet() } // IsSet returns true if this key has been set in the context. func (k key[V]) IsSet(ctx context.Context) bool { - _, ok := k.TryGet(ctx) - - return ok + return k.Inspect(ctx).IsSet() } // IsNotSet returns true if this key has not been set in the context. func (k key[V]) IsNotSet(ctx context.Context) bool { - return !k.IsSet(ctx) + return k.Inspect(ctx).IsNotSet() +} + +// InspectBool retrieves the value from the context and returns a BoolInspection. +func (k boolKey) InspectBool(ctx context.Context) BoolInspection { + return BoolInspection{Inspection: k.Inspect(ctx)} } // Enabled returns true if the feature flag is set to true in the context. func (k boolKey) Enabled(ctx context.Context) bool { - return k.Get(ctx) + return k.InspectBool(ctx).Enabled() } // Disabled returns true if the feature flag is either not set or set to false. func (k boolKey) Disabled(ctx context.Context) bool { - return !k.Enabled(ctx) + return k.InspectBool(ctx).Disabled() } // ExplicitlyDisabled returns true if the feature flag is explicitly set to false. func (k boolKey) ExplicitlyDisabled(ctx context.Context) bool { - val, ok := k.TryGet(ctx) - - return ok && !val + return k.InspectBool(ctx).ExplicitlyDisabled() } // WithEnabled returns a new context with this feature flag enabled (set to true). diff --git a/feature_test.go b/feature_test.go index 2f1db4e..faf4ef4 100644 --- a/feature_test.go +++ b/feature_test.go @@ -491,122 +491,52 @@ func TestString(t *testing.T) { }) } -// TestDebugValue tests the DebugValue method. -func TestDebugValue(t *testing.T) { +// TestGoString tests the GoString method for keys. +func TestGoString(t *testing.T) { t.Parallel() - t.Run("unset key shows not set", func(t *testing.T) { + t.Run("Key GoString includes package name and type", func(t *testing.T) { t.Parallel() - ctx := context.Background() key := feature.NewNamed[string]("test-key") + goStr := key.GoString() - debugValue := key.DebugValue(ctx) - - want := "test-key: " - - if debugValue != want { - t.Errorf("DebugValue() = %q, want %q", debugValue, want) - } - }) - - t.Run("set key shows name and value", func(t *testing.T) { - t.Parallel() - - ctx := context.Background() - key := feature.NewNamed[int]("max-retries") - ctx = key.WithValue(ctx, 5) - debugValue := key.DebugValue(ctx) - want := "max-retries: 5" - - if debugValue != want { - t.Errorf("DebugValue() = %q, want %q", debugValue, want) + if !strings.Contains(goStr, "feature.Key[string]") { + t.Errorf("GoString() = %q, want to contain %q", goStr, "feature.Key[string]") } - }) - - t.Run("bool key shows name and value when unset", func(t *testing.T) { - t.Parallel() - ctx := context.Background() - flag := feature.NewNamedBool("enable-feature") - debugValue := flag.DebugValue(ctx) - want := "enable-feature: " - - if debugValue != want { - t.Errorf("DebugValue() unset = %q, want %q", debugValue, want) + if !strings.Contains(goStr, "name:") { + t.Errorf("GoString() = %q, want to contain field name %q", goStr, "name:") } - }) - - t.Run("bool key shows name and value when enabled", func(t *testing.T) { - t.Parallel() - - ctx := context.Background() - flag := feature.NewNamedBool("enable-feature") - ctx = flag.WithEnabled(ctx) - debugValue := flag.DebugValue(ctx) - want := "enable-feature: true" - if debugValue != want { - t.Errorf("DebugValue() enabled = %q, want %q", debugValue, want) + if !strings.Contains(goStr, "test-key") { + t.Errorf("GoString() = %q, want to contain %q", goStr, "test-key") } }) - t.Run("bool key shows name and value when disabled", func(t *testing.T) { + t.Run("Key GoString with int type", func(t *testing.T) { t.Parallel() - ctx := context.Background() - flag := feature.NewNamedBool("enable-feature") - ctx = flag.WithDisabled(ctx) - debugValue := flag.DebugValue(ctx) - want := "enable-feature: false" + key := feature.NewNamed[int]("max-retries") + goStr := key.GoString() - if debugValue != want { - t.Errorf("DebugValue() disabled = %q, want %q", debugValue, want) + if !strings.Contains(goStr, "feature.Key[int]") { + t.Errorf("GoString() = %q, want to contain %q", goStr, "feature.Key[int]") } }) - t.Run("anonymous key shows call site info in name", func(t *testing.T) { + t.Run("BoolKey GoString includes bool type", func(t *testing.T) { t.Parallel() - ctx := context.Background() - key := feature.New[string]() - ctx = key.WithValue(ctx, "value") - - debugValue := key.DebugValue(ctx) - // Should contain "anonymous(" (call site info) and "@0x" (address) and ": value" - if !strings.Contains(debugValue, "anonymous(") { - t.Errorf("DebugValue() = %q, want to contain %q", debugValue, "anonymous(") - } - - if !strings.Contains(debugValue, "@0x") { - t.Errorf("DebugValue() = %q, want to contain %q", debugValue, "@0x") - } - - if !strings.Contains(debugValue, ": value") { - t.Errorf("DebugValue() = %q, want to contain %q", debugValue, ": value") - } - }) - - t.Run("complex value types are formatted", func(t *testing.T) { - t.Parallel() + flag := feature.NewNamedBool("my-feature") + goStr := flag.GoString() - type Config struct { - MaxRetries int - Timeout string + if !strings.Contains(goStr, "feature.Key[bool]") { + t.Errorf("GoString() = %q, want to contain %q", goStr, "feature.Key[bool]") } - ctx := context.Background() - key := feature.NewNamed[Config]("config") - ctx = key.WithValue(ctx, Config{MaxRetries: 3, Timeout: "30s"}) - - debugValue := key.DebugValue(ctx) - // Should contain the key name and struct representation - if !strings.Contains(debugValue, "config:") { - t.Errorf("DebugValue() = %q, want to contain %q", debugValue, "config:") - } - // Check that it contains the struct values - if !strings.Contains(debugValue, "3") || !strings.Contains(debugValue, "30s") { - t.Errorf("DebugValue() = %q, want to contain struct values", debugValue) + if !strings.Contains(goStr, "my-feature") { + t.Errorf("GoString() = %q, want to contain %q", goStr, "my-feature") } }) } @@ -978,22 +908,27 @@ func ExampleKey_IsNotSet() { // Using cache size: 1024 } -func ExampleKey_DebugValue() { +func ExampleKey_Inspect() { ctx := context.Background() // Create a named key for better debug output var MaxRetries = feature.NewNamed[int]("max-retries") - // Check debug value when not set - fmt.Println(MaxRetries.DebugValue(ctx)) + // Inspect when not set + fmt.Println(MaxRetries.Inspect(ctx)) - // Set a value and check again + // Set a value and inspect again ctx = MaxRetries.WithValue(ctx, 5) - fmt.Println(MaxRetries.DebugValue(ctx)) + inspection := MaxRetries.Inspect(ctx) + fmt.Println(inspection) + fmt.Println("Value:", inspection.Get()) + fmt.Println("Is set:", inspection.IsSet()) // Output: // max-retries: // max-retries: 5 + // Value: 5 + // Is set: true } func ExampleKey_String() { diff --git a/inspection.go b/inspection.go new file mode 100644 index 0000000..27db814 --- /dev/null +++ b/inspection.go @@ -0,0 +1,117 @@ +package feature + +import "fmt" + +// Inspection holds the result of inspecting a key's value in a context. +// It captures both the key, its value, and whether the value was set. +// This type provides convenient methods for working with the inspection result +// without needing to pass the context again. +type Inspection[V any] struct { + // Key is the key that was inspected. + Key Key[V] + // Value is the value retrieved from the context. + // If Ok is false, this will be the zero value of type V. + Value V + // Ok indicates whether the key was set in the context. + Ok bool +} + +// Get returns the value from the inspection. +// If the key was not set, it returns the zero value of type V. +func (i Inspection[V]) Get() V { + return i.Value +} + +// TryGet returns the value and whether it was set. +func (i Inspection[V]) TryGet() (V, bool) { + return i.Value, i.Ok +} + +// GetOrDefault returns the value if set, otherwise returns the provided default. +func (i Inspection[V]) GetOrDefault(defaultValue V) V { + if i.Ok { + return i.Value + } + + return defaultValue +} + +// MustGet returns the value if set, otherwise panics. +func (i Inspection[V]) MustGet() V { + if !i.Ok { + panic(fmt.Sprintf("key %s is not set in context", i.Key.String())) + } + + return i.Value +} + +// IsSet returns true if the key was set in the context. +func (i Inspection[V]) IsSet() bool { + return i.Ok +} + +// IsNotSet returns true if the key was not set in the context. +func (i Inspection[V]) IsNotSet() bool { + return !i.Ok +} + +// String returns a string representation combining the key name and its value. +// Format: ": " or ": ". +// This implements fmt.Stringer. +func (i Inspection[V]) String() string { + if !i.Ok { + return i.Key.String() + ": " + } + + return fmt.Sprintf("%s: %v", i.Key.String(), i.Value) +} + +// GoString returns a Go syntax representation of the inspection. +// This implements fmt.GoStringer. +func (i Inspection[V]) GoString() string { + if !i.Ok { + return fmt.Sprintf("feature.Inspection[%T]{Key: %#v, Ok: false}", *new(V), i.Key) + } + + return fmt.Sprintf("feature.Inspection[%T]{Key: %#v, Value: %#v, Ok: true}", *new(V), i.Key, i.Value) +} + +// BoolInspection is a specialized Inspection for boolean feature flags. +// It provides convenience methods for working with boolean values. +type BoolInspection struct { + Inspection[bool] +} + +// Enabled returns true if the feature flag is set to true. +// If the key was not set, it returns false (the zero value). +func (i BoolInspection) Enabled() bool { + return i.Value +} + +// Disabled returns true if the feature flag is either not set or set to false. +func (i BoolInspection) Disabled() bool { + return !i.Enabled() +} + +// ExplicitlyDisabled returns true if the feature flag is explicitly set to false. +// It returns false if the key was not set (distinguishing from Disabled). +func (i BoolInspection) ExplicitlyDisabled() bool { + return i.Ok && !i.Value +} + +// String returns a string representation combining the key name and its value. +// Delegates to the embedded Inspection.String(). +// This implements fmt.Stringer. +func (i BoolInspection) String() string { + return i.Inspection.String() +} + +// GoString returns a Go syntax representation of the inspection. +// This implements fmt.GoStringer. +func (i BoolInspection) GoString() string { + if !i.Ok { + return fmt.Sprintf("feature.BoolInspection{Key: %#v, Ok: false}", i.Key) + } + + return fmt.Sprintf("feature.BoolInspection{Key: %#v, Value: %v, Ok: true}", i.Key, i.Value) +} diff --git a/inspection_test.go b/inspection_test.go new file mode 100644 index 0000000..3956701 --- /dev/null +++ b/inspection_test.go @@ -0,0 +1,347 @@ +package feature_test + +import ( + "context" + "strings" + "testing" + + "github.com/mpyw/feature" +) + +// checkContains is a helper to check if got contains want. +func checkContains(t *testing.T, got, want string) { + t.Helper() + + if !strings.Contains(got, want) { + t.Errorf("got %q, want to contain %q", got, want) + } +} + +func TestInspectionString(t *testing.T) { + t.Parallel() + + t.Run("unset key shows not set", func(t *testing.T) { + t.Parallel() + + ctx := context.Background() + key := feature.NewNamed[string]("test-key") + + inspection := key.Inspect(ctx) + + if inspection.Ok { + t.Error("Inspection.Ok = true, want false") + } + + want := "test-key: " + if got := inspection.String(); got != want { + t.Errorf("Inspection.String() = %q, want %q", got, want) + } + }) + + t.Run("set key shows name and value", func(t *testing.T) { + t.Parallel() + + ctx := context.Background() + key := feature.NewNamed[int]("max-retries") + ctx = key.WithValue(ctx, 5) + inspection := key.Inspect(ctx) + + if !inspection.Ok { + t.Error("Inspection.Ok = false, want true") + } + + if inspection.Value != 5 { + t.Errorf("Inspection.Value = %d, want 5", inspection.Value) + } + + want := "max-retries: 5" + if got := inspection.String(); got != want { + t.Errorf("Inspection.String() = %q, want %q", got, want) + } + }) + + t.Run("anonymous key shows call site info in name", func(t *testing.T) { + t.Parallel() + + ctx := context.Background() + key := feature.New[string]() + ctx = key.WithValue(ctx, "value") + + inspection := key.Inspect(ctx) + str := inspection.String() + // Should contain "anonymous(" (call site info) and "@0x" (address) and ": value" + checkContains(t, str, "anonymous(") + checkContains(t, str, "@0x") + checkContains(t, str, ": value") + }) + + t.Run("complex value types are formatted", func(t *testing.T) { + t.Parallel() + + type Config struct { + MaxRetries int + Timeout string + } + + ctx := context.Background() + key := feature.NewNamed[Config]("config") + ctx = key.WithValue(ctx, Config{MaxRetries: 3, Timeout: "30s"}) + + inspection := key.Inspect(ctx) + str := inspection.String() + // Should contain the key name and struct representation + checkContains(t, str, "config:") + // Check that it contains the struct values + checkContains(t, str, "3") + checkContains(t, str, "30s") + }) +} + +func TestBoolInspectionString(t *testing.T) { + t.Parallel() + + t.Run("unset", func(t *testing.T) { + t.Parallel() + + ctx := context.Background() + flag := feature.NewNamedBool("enable-feature") + inspection := flag.InspectBool(ctx) + want := "enable-feature: " + + if got := inspection.String(); got != want { + t.Errorf("BoolInspection.String() unset = %q, want %q", got, want) + } + }) + + t.Run("enabled", func(t *testing.T) { + t.Parallel() + + ctx := context.Background() + flag := feature.NewNamedBool("enable-feature") + ctx = flag.WithEnabled(ctx) + inspection := flag.InspectBool(ctx) + want := "enable-feature: true" + + if got := inspection.String(); got != want { + t.Errorf("BoolInspection.String() enabled = %q, want %q", got, want) + } + }) + + t.Run("disabled", func(t *testing.T) { + t.Parallel() + + ctx := context.Background() + flag := feature.NewNamedBool("enable-feature") + ctx = flag.WithDisabled(ctx) + inspection := flag.InspectBool(ctx) + want := "enable-feature: false" + + if got := inspection.String(); got != want { + t.Errorf("BoolInspection.String() disabled = %q, want %q", got, want) + } + }) +} + +func TestInspectionHelperMethods(t *testing.T) { + t.Parallel() + + t.Run("set key", func(t *testing.T) { + t.Parallel() + + ctx := context.Background() + key := feature.NewNamed[int]("test") + ctx = key.WithValue(ctx, 42) + + inspection := key.Inspect(ctx) + + // Test Get + if got := inspection.Get(); got != 42 { + t.Errorf("Inspection.Get() = %d, want 42", got) + } + + // Test TryGet + val, ok := inspection.TryGet() + if !ok || val != 42 { + t.Errorf("Inspection.TryGet() = (%d, %v), want (42, true)", val, ok) + } + + // Test GetOrDefault + if got := inspection.GetOrDefault(100); got != 42 { + t.Errorf("Inspection.GetOrDefault(100) = %d, want 42", got) + } + + // Test IsSet + if !inspection.IsSet() { + t.Error("Inspection.IsSet() = false, want true") + } + + // Test IsNotSet + if inspection.IsNotSet() { + t.Error("Inspection.IsNotSet() = true, want false") + } + }) + + t.Run("unset key", func(t *testing.T) { + t.Parallel() + + ctx := context.Background() + key := feature.NewNamed[int]("test") + + inspection := key.Inspect(ctx) + + // Test Get returns zero value + if got := inspection.Get(); got != 0 { + t.Errorf("Inspection.Get() = %d, want 0", got) + } + + // Test TryGet returns not ok + val, ok := inspection.TryGet() + if ok || val != 0 { + t.Errorf("Inspection.TryGet() = (%d, %v), want (0, false)", val, ok) + } + + // Test GetOrDefault returns default + if got := inspection.GetOrDefault(100); got != 100 { + t.Errorf("Inspection.GetOrDefault(100) = %d, want 100", got) + } + + // Test IsSet + if inspection.IsSet() { + t.Error("Inspection.IsSet() = true, want false") + } + + // Test IsNotSet + if !inspection.IsNotSet() { + t.Error("Inspection.IsNotSet() = false, want true") + } + }) + + t.Run("MustGet panics for unset key", func(t *testing.T) { + t.Parallel() + + ctx := context.Background() + key := feature.NewNamed[int]("panic-test") + + inspection := key.Inspect(ctx) + + defer func() { + if r := recover(); r == nil { + t.Error("MustGet() did not panic for unset key") + } + }() + + inspection.MustGet() + }) +} + +func TestBoolInspectionHelperMethods(t *testing.T) { + t.Parallel() + + t.Run("unset flag", func(t *testing.T) { + t.Parallel() + + ctx := context.Background() + flag := feature.NewNamedBool("test") + + inspection := flag.InspectBool(ctx) + if inspection.Enabled() { + t.Error("BoolInspection.Enabled() = true for unset flag, want false") + } + + if !inspection.Disabled() { + t.Error("BoolInspection.Disabled() = false for unset flag, want true") + } + + if inspection.ExplicitlyDisabled() { + t.Error("BoolInspection.ExplicitlyDisabled() = true for unset flag, want false") + } + }) + + t.Run("enabled flag", func(t *testing.T) { + t.Parallel() + + ctx := context.Background() + flag := feature.NewNamedBool("test") + ctx = flag.WithEnabled(ctx) + + inspection := flag.InspectBool(ctx) + if !inspection.Enabled() { + t.Error("BoolInspection.Enabled() = false for enabled flag, want true") + } + + if inspection.Disabled() { + t.Error("BoolInspection.Disabled() = true for enabled flag, want false") + } + }) + + t.Run("explicitly disabled flag", func(t *testing.T) { + t.Parallel() + + ctx := context.Background() + flag := feature.NewNamedBool("test") + ctx = flag.WithDisabled(ctx) + + inspection := flag.InspectBool(ctx) + if inspection.Enabled() { + t.Error("BoolInspection.Enabled() = true for disabled flag, want false") + } + + if !inspection.ExplicitlyDisabled() { + t.Error("BoolInspection.ExplicitlyDisabled() = false for disabled flag, want true") + } + }) +} + +func TestInspectionGoString(t *testing.T) { + t.Parallel() + + t.Run("Inspection format", func(t *testing.T) { + t.Parallel() + + ctx := context.Background() + key := feature.NewNamed[string]("test-key") + + // Unset + inspection := key.Inspect(ctx) + + goStr := inspection.GoString() + checkContains(t, goStr, "feature.Inspection[string]") + checkContains(t, goStr, "test-key") + checkContains(t, goStr, "Ok: false") + + // Set + ctx = key.WithValue(ctx, "hello") + inspection = key.Inspect(ctx) + + goStr = inspection.GoString() + checkContains(t, goStr, "feature.Inspection[string]") + checkContains(t, goStr, "test-key") + checkContains(t, goStr, "hello") + checkContains(t, goStr, "Ok: true") + }) + + t.Run("BoolInspection format", func(t *testing.T) { + t.Parallel() + + ctx := context.Background() + flag := feature.NewNamedBool("test-flag") + + // Unset + inspection := flag.InspectBool(ctx) + + goStr := inspection.GoString() + checkContains(t, goStr, "feature.BoolInspection") + checkContains(t, goStr, "test-flag") + checkContains(t, goStr, "Ok: false") + + // Set to true + ctx = flag.WithEnabled(ctx) + inspection = flag.InspectBool(ctx) + + goStr = inspection.GoString() + checkContains(t, goStr, "feature.BoolInspection") + checkContains(t, goStr, "test-flag") + checkContains(t, goStr, "Value: true") + checkContains(t, goStr, "Ok: true") + }) +}