Adding reflection module

pull/1/head
Drew Bednar 4 months ago
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…
Cancel
Save