Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reflection chapter #15

Merged
merged 28 commits into from
Jun 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
66268fe
test for walk func
neftales Jun 1, 2024
7bfc165
implementing walk code e make the tests pass
neftales Jun 1, 2024
ad47572
assert by content walk return
neftales Jun 1, 2024
651b9e4
use reflection to get the value of struct in walk
neftales Jun 1, 2024
ee8fc51
Refactor test to testing more cases with flexibility
neftales Jun 1, 2024
0997bed
testing for two string fields in struct
neftales Jun 1, 2024
75e5020
refactor code to be able to compute any number of string fields
neftales Jun 1, 2024
94d375a
test for non string fields
neftales Jun 1, 2024
95b4bb7
check if field is string type before call function
neftales Jun 1, 2024
f647b50
add case test for nested fields
neftales Jun 1, 2024
3baf323
change de walk function to check nested fields
neftales Jun 1, 2024
dad2476
fix case test typo
neftales Jun 1, 2024
8d5b23a
test for pointers in struct
neftales Jun 1, 2024
3a17d04
check for pointers before call NumFields
neftales Jun 1, 2024
e422060
refactor to get value in dedicate function
neftales Jun 1, 2024
c0e8a5a
case test for slices
neftales Jun 1, 2024
81648af
change de code to works with slices
neftales Jun 1, 2024
96f70b8
refactor to check all types in switch
neftales Jun 1, 2024
419aa8c
iterate on fields in a unique point
neftales Jun 1, 2024
a524aff
case test for arrays
neftales Jun 1, 2024
2b1b326
change de walk code to works with array
neftales Jun 1, 2024
d70131e
case test for maps
neftales Jun 1, 2024
e9bfd61
change code to works with maps
neftales Jun 1, 2024
48e34ec
change the abstraction to only pass reflect.Value
neftales Jun 1, 2024
b5a8327
tests maps without order dependency
neftales Jun 1, 2024
603c471
test for channels
neftales Jun 1, 2024
921969b
case test for functions
neftales Jun 1, 2024
791b921
change code to works with functions
neftales Jun 1, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions reflection/reflection.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package reflection

import (
"reflect"
)

func walk(x interface{}, fn func(input string)) {
val := getValue(x)

walkValue := func(value reflect.Value) {
walk(value.Interface(), fn)
}

switch val.Kind() {
case reflect.String:
fn(val.String())
case reflect.Slice, reflect.Array:
for i := 0; i < val.Len(); i++ {
walkValue(val.Index(i))
}
case reflect.Struct:
for i := 0; i < val.NumField(); i++ {
walkValue(val.Field(i))
}
case reflect.Map:
for _, key := range val.MapKeys() {
walkValue(val.MapIndex(key))
}
case reflect.Chan:
for {
if v, ok := val.Recv(); ok {
walkValue(v)
} else {
break
}
}
case reflect.Func:
valResults := val.Call(nil)
for _, result := range valResults {
walkValue(result)
}
}
}

func getValue(x interface{}) reflect.Value {
val := reflect.ValueOf(x)

if val.Kind() == reflect.Pointer {
val = val.Elem()
}

return val
}
162 changes: 162 additions & 0 deletions reflection/reflection_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
package reflection

import (
"reflect"
"testing"
)

type Person struct {
Name string
Profile Profile
}

type Profile struct {
Age int
City string
}

func TestWalk(t *testing.T) {
cases := []struct {
Name string
Input interface{}
ExpectedCalls []string
}{
{
"struct with one string field",
struct {
Name string
}{"Chris"},
[]string{"Chris"},
},
{
"struct with two string fields",
struct {
Name string
City string
}{"Chris", "London"},
[]string{"Chris", "London"},
},
{
"struct with non string field",
struct {
Name string
Age int
}{"Chris", 33},
[]string{"Chris"},
},
{
"nested fields",
Person{
"Chris",
Profile{33, "London"},
},
[]string{"Chris", "London"},
},
{
"pointers to things",
&Person{
"Chris",
Profile{33, "London"},
},
[]string{"Chris", "London"},
},
{
"slices",
[]Profile{
{33, "London"},
{34, "Reykjavík"},
},
[]string{"London", "Reykjavík"},
},
{
"arrays",
[2]Profile{
{33, "London"},
{34, "Reykjavík"},
},
[]string{"London", "Reykjavík"},
},
}

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("got %v want %v", got, test.ExpectedCalls)
}
})
}

t.Run("with maps", 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 functions", 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)
}
})
}

func assertContains(t testing.TB, results []string, needle string) {
t.Helper()
contains := false

for _, x := range results {
if x == needle {
contains = true
}
}

if !contains {
t.Errorf("expected %v to contain %q but it didn't", results, needle)
}
}
Loading