Adding reflection module
parent
a68ba74b5a
commit
6eb1756aa5
@ -0,0 +1,3 @@
|
||||
module reflection
|
||||
|
||||
go 1.23.1
|
@ -0,0 +1,99 @@
|
||||
package reflection
|
||||
|
||||
import "reflect"
|
||||
|
||||
// Go allows for `interface{}` which we can think as as just
|
||||
// any type. (any is just an alias for interface{}) this allows for
|
||||
// some metaprogramming features in the language reflection being one
|
||||
// of them. Reflection allows us to examine structure at runtime.
|
||||
|
||||
// The downside, you lose type safety and performance impact.
|
||||
|
||||
// Only use reflection if you really need to.
|
||||
|
||||
// See also https://blog.golang.org/laws-of-reflection
|
||||
|
||||
func walk(x interface{}, fn func(input string)) {
|
||||
// This code is very unsafe and very naive,
|
||||
val := getValue(x)
|
||||
|
||||
// if val.Kind() == reflect.Slice {
|
||||
// for i := 0; i < val.Len(); i++ {
|
||||
// walk(val.Index(i).Interface(), fn)
|
||||
// }
|
||||
// return
|
||||
// }
|
||||
|
||||
// // This would panic if there were no fields
|
||||
// field := val.Field(0)
|
||||
// // This would be wrong if the field had any value other than a string
|
||||
// fn(field.String())
|
||||
|
||||
// // change for test table ( will fail case 2)
|
||||
// for i := 0; i < val.NumField(); i++ {
|
||||
// field := val.Field(i)
|
||||
// fn(field.String())
|
||||
// }
|
||||
|
||||
// THIS GOT messy
|
||||
// for i := 0; i < val.NumField(); i++ {
|
||||
// field := val.Field(i)
|
||||
|
||||
// // if field.Kind() == reflect.String {
|
||||
// // fn(field.String())
|
||||
// // }
|
||||
|
||||
// // if field.Kind() == reflect.Struct {
|
||||
// // // it got recursive
|
||||
// // walk(field.Interface(), fn)
|
||||
// // }
|
||||
|
||||
// switch field.Kind() {
|
||||
// case reflect.String:
|
||||
// fn(field.String())
|
||||
// case reflect.Struct:
|
||||
// walk(field.Interface(), fn)
|
||||
// }
|
||||
// }
|
||||
|
||||
switch val.Kind() {
|
||||
case reflect.Struct:
|
||||
for i := 0; i < val.NumField(); i++ {
|
||||
walk(val.Field(i).Interface(), fn)
|
||||
}
|
||||
case reflect.Slice, reflect.Array:
|
||||
for i := 0; i < val.Len(); i++ {
|
||||
walk(val.Index(i).Interface(), fn)
|
||||
}
|
||||
case reflect.Map:
|
||||
for _, key := range val.MapKeys() {
|
||||
walk(val.MapIndex(key).Interface(), fn)
|
||||
}
|
||||
|
||||
case reflect.Chan:
|
||||
for {
|
||||
if v, ok := val.Recv(); ok {
|
||||
walk(v.Interface(), fn)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
case reflect.Func:
|
||||
valFnResult := val.Call(nil)
|
||||
for _, res := range valFnResult {
|
||||
walk(res.Interface(), fn)
|
||||
}
|
||||
case reflect.String:
|
||||
fn(val.String())
|
||||
}
|
||||
}
|
||||
|
||||
func getValue(x interface{}) reflect.Value {
|
||||
val := reflect.ValueOf(x)
|
||||
|
||||
if val.Kind() == reflect.Pointer {
|
||||
val = val.Elem()
|
||||
}
|
||||
|
||||
return val
|
||||
}
|
@ -0,0 +1,177 @@
|
||||
package reflection
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type Person struct {
|
||||
Name string
|
||||
Profile Profile
|
||||
}
|
||||
|
||||
type Profile struct {
|
||||
Age int
|
||||
City string
|
||||
}
|
||||
|
||||
func assertContains(t testing.TB, haystack []string, needle string) {
|
||||
t.Helper()
|
||||
contains := false
|
||||
for _, x := range haystack {
|
||||
if x == needle {
|
||||
contains = true
|
||||
}
|
||||
}
|
||||
if !contains {
|
||||
t.Errorf("expected %v to contain %q but it didn't", haystack, needle)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWalk(t *testing.T) {
|
||||
|
||||
cases := []struct {
|
||||
Name string
|
||||
Input interface{}
|
||||
ExpectedCalls []string
|
||||
}{
|
||||
// case 0
|
||||
{
|
||||
Name: "struct with on string field",
|
||||
Input: struct{ Name string }{"Drew"},
|
||||
ExpectedCalls: []string{"Drew"},
|
||||
},
|
||||
// case 1
|
||||
{
|
||||
"struct with two string fields",
|
||||
struct {
|
||||
Name string
|
||||
City string
|
||||
}{"Chris", "London"},
|
||||
[]string{"Chris", "London"},
|
||||
},
|
||||
// case 2
|
||||
{
|
||||
"struct with non string field",
|
||||
struct {
|
||||
Name string
|
||||
Age int
|
||||
}{"Chris", 33},
|
||||
[]string{"Chris"},
|
||||
},
|
||||
// case 3 Nested Struct
|
||||
{
|
||||
"nested fields",
|
||||
Person{
|
||||
"Chris",
|
||||
Profile{33, "London"},
|
||||
},
|
||||
[]string{"Chris", "London"},
|
||||
},
|
||||
// case 4 struct as pointer
|
||||
{
|
||||
"pointers to things",
|
||||
&Person{
|
||||
"Chris",
|
||||
Profile{33, "London"},
|
||||
},
|
||||
[]string{"Chris", "London"},
|
||||
},
|
||||
// case 5 slice of profiles
|
||||
{
|
||||
"slices",
|
||||
[]Profile{
|
||||
{33, "London"},
|
||||
{34, "Reykjavík"},
|
||||
},
|
||||
[]string{"London", "Reykjavík"},
|
||||
},
|
||||
// case 6 array
|
||||
{
|
||||
"arrays",
|
||||
[2]Profile{
|
||||
{33, "London"},
|
||||
{34, "Reykjavík"},
|
||||
},
|
||||
[]string{"London", "Reykjavík"},
|
||||
},
|
||||
// case 7 map THIS HAS A GOTCHA
|
||||
// maps in go do not guarantee order. It will eventually fake
|
||||
// moved to a separate test run to handle that case
|
||||
// {
|
||||
// "maps",
|
||||
// map[string]string{
|
||||
// "Cow": "Moo",
|
||||
// "Sheep": "Baa",
|
||||
// },
|
||||
// []string{"Moo", "Baa"},
|
||||
// },
|
||||
}
|
||||
|
||||
for _, test := range cases {
|
||||
t.Run(test.Name, func(t *testing.T) {
|
||||
var got []string
|
||||
walk(test.Input, func(input string) {
|
||||
got = append(got, input)
|
||||
})
|
||||
|
||||
if !reflect.DeepEqual(got, test.ExpectedCalls) {
|
||||
t.Errorf("get %v, want %v", got, test.ExpectedCalls)
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
t.Run("with mas", func(t *testing.T) {
|
||||
aMap := map[string]string{
|
||||
"Cow": "Moo",
|
||||
"Sheep": "Baa",
|
||||
}
|
||||
|
||||
var got []string
|
||||
walk(aMap, func(input string) {
|
||||
got = append(got, input)
|
||||
})
|
||||
|
||||
assertContains(t, got, "Moo")
|
||||
assertContains(t, got, "Baa")
|
||||
})
|
||||
|
||||
t.Run("with channels", func(t *testing.T) {
|
||||
aChannel := make(chan Profile)
|
||||
|
||||
go func() {
|
||||
aChannel <- Profile{33, "Berlin"}
|
||||
aChannel <- Profile{34, "Katowice"}
|
||||
close(aChannel)
|
||||
}()
|
||||
|
||||
var got []string
|
||||
want := []string{"Berlin", "Katowice"}
|
||||
|
||||
walk(aChannel, func(input string) {
|
||||
got = append(got, input)
|
||||
})
|
||||
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("got %v, want %v", got, want)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("with function", func(t *testing.T) {
|
||||
aFunction := func() (Profile, Profile) {
|
||||
return Profile{33, "Berlin"}, Profile{34, "Katowice"}
|
||||
}
|
||||
|
||||
var got []string
|
||||
want := []string{"Berlin", "Katowice"}
|
||||
|
||||
walk(aFunction, func(input string) {
|
||||
got = append(got, input)
|
||||
})
|
||||
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("got %v, want %v", got, want)
|
||||
}
|
||||
})
|
||||
}
|
Loading…
Reference in New Issue