Provisioning: Remove again dependency cycle between provisioning app and grafana (#110863)

* Remove dependency cycle between provisioning app and grafana

* Format code

* Fix linting
This commit is contained in:
Roberto Jiménez Sánchez
2025-09-10 14:40:44 +02:00
committed by GitHub
parent 5520a38726
commit 09ef9c8176
7 changed files with 445 additions and 16 deletions
+58
View File
@@ -0,0 +1,58 @@
package util
import "reflect"
// IsInterfaceNil checks if an interface is nil or holds a nil value.
//
// This function addresses the Go "nil interface" gotcha where an interface
// can be != nil but still hold a nil value of a specific type.
//
// The Problem:
// In Go, an interface value consists of two parts: a type and a value.
// An interface is only considered nil when both parts are nil.
// However, if you assign a typed nil (e.g., (*MyType)(nil)) to an interface,
// the interface becomes != nil even though it holds a nil value.
//
// Example of the gotcha:
//
// var p *int = nil // p is a nil pointer
// var i interface{} = p // i holds a typed nil (*int)(nil)
// fmt.Println(i == nil) // prints: false (this is the gotcha!)
// fmt.Println(IsInterfaceNil(i)) // prints: true (correctly identifies nil)
//
// Common scenario with error interfaces:
//
// func doSomething() error {
// var err *MyError = nil
// if someCondition {
// err = &MyError{msg: "failed"}
// }
// return err // returns interface{} containing (*MyError)(nil)
// }
//
// if err := doSomething(); err != nil { // this check fails!
// // This code won't run even when err contains nil
// }
//
// if err := doSomething(); !IsInterfaceNil(err) {
// // This correctly identifies the nil error
// }
//
// Supported types: Ptr, Slice, Map, Func, Interface
// Unsupported nilable types: Chan, UnsafePointer (these return false even when nil)
//
// See more about this Go gotcha at:
// https://go.dev/doc/faq#nil_error
// https://medium.com/@moksh.9/go-gotcha-when-nil-isnt-really-nil-ddf632720001
func IsInterfaceNil(i interface{}) bool {
iv := reflect.ValueOf(i)
if !iv.IsValid() {
return true
}
switch iv.Kind() {
case reflect.Ptr, reflect.Slice, reflect.Map, reflect.Func, reflect.Interface:
return iv.IsNil()
default:
return false
}
}
@@ -0,0 +1,172 @@
package util
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestIsInterfaceNil(t *testing.T) {
testCases := []struct {
name string
value interface{}
expected bool
}{
// True nil cases
{"true nil interface", nil, true},
{"nil pointer", (*int)(nil), true},
{"nil slice", ([]int)(nil), true},
{"nil map", (map[string]int)(nil), true},
{"nil function", (func())(nil), true},
{"nil interface wrapped in interface", (interface{})(nil), true},
// Channels are not handled by IsInterfaceNil (not in switch statement)
{"nil channel - not handled", (chan int)(nil), false},
// Non-nil cases
{"non-nil pointer", func() interface{} { val := 42; return &val }(), false},
{"non-nil slice", []int{1, 2, 3}, false},
{"empty slice", []int{}, false},
{"non-nil map", map[string]int{"key": 1}, false},
{"empty map", make(map[string]int), false},
{"non-nil function", func() {}, false},
{"non-nil channel", make(chan int), false},
// Basic value types
{"string value", "hello", false},
{"empty string", "", false},
{"int value", 42, false},
{"zero int", 0, false},
{"bool true", true, false},
{"bool false", false, false},
{"float64", 3.14, false},
{"complex128", complex(1, 2), false},
// Composite value types
{"struct value", struct{ x int }{x: 1}, false},
{"array value", [3]int{1, 2, 3}, false},
{"zero array", [3]int{}, false},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
result := IsInterfaceNil(tc.value)
assert.Equal(t, tc.expected, result, "IsInterfaceNil(%v) should return %t", tc.value, tc.expected)
})
}
}
func TestIsInterfaceNil_NestedInterfaces(t *testing.T) {
testCases := []struct {
name string
value interface{}
expected bool
}{
{
name: "nested interface with nil",
value: func() interface{} {
var inner *int = nil
var middle interface{} = inner
return middle
}(),
expected: true,
},
{
name: "nested interface with value",
value: func() interface{} {
val := 42
inner := &val
var middle interface{} = inner
return middle
}(),
expected: false,
},
{
name: "interface containing interface with value",
value: func() interface{} {
var inner interface{} = 42
outer := inner
return outer
}(),
expected: false,
},
{
name: "interface containing nil interface",
value: func() interface{} {
var inner interface{} = nil
outer := inner
return outer
}(),
expected: true,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
result := IsInterfaceNil(tc.value)
assert.Equal(t, tc.expected, result)
})
}
}
func TestIsInterfaceNil_ReflectKinds(t *testing.T) {
t.Run("handles specific nilable reflect kinds", func(t *testing.T) {
// Test the specific nilable kinds that the function handles
// according to its switch statement: Ptr, Slice, Map, Func, Interface
nilableTestCases := []struct {
name string
value interface{}
}{
{"nil pointer", (*int)(nil)},
{"nil slice", ([]int)(nil)},
{"nil map", (map[string]int)(nil)},
{"nil function", (func())(nil)},
{"nil interface", (interface{})(nil)},
}
for _, tc := range nilableTestCases {
t.Run(tc.name, func(t *testing.T) {
assert.True(t, IsInterfaceNil(tc.value), "%s should be detected as nil", tc.name)
})
}
})
t.Run("does not handle channels and other nilable types", func(t *testing.T) {
// Test that nilable kinds NOT in the switch statement return false
unhandledTestCases := []struct {
name string
value interface{}
}{
{"nil channel", (chan int)(nil)},
// UnsafePointer would be another example, but harder to test
}
for _, tc := range unhandledTestCases {
t.Run(tc.name, func(t *testing.T) {
assert.False(t, IsInterfaceNil(tc.value), "%s should not be detected as nil (unhandled type)", tc.name)
})
}
})
t.Run("handles non-nilable kinds", func(t *testing.T) {
// Test kinds that cannot be nil
nonNilableTestCases := []struct {
name string
value interface{}
}{
{"int", 42},
{"string", "test"},
{"bool", true},
{"struct", struct{ x int }{x: 1}},
{"array", [3]int{1, 2, 3}},
{"float64", 3.14},
{"complex128", complex(1, 2)},
}
for _, tc := range nonNilableTestCases {
t.Run(tc.name, func(t *testing.T) {
assert.False(t, IsInterfaceNil(tc.value), "%s should not be detected as nil (non-nilable)", tc.name)
})
}
})
}