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