Skip to content

Commit

Permalink
feat: Add map and struct matcher
Browse files Browse the repository at this point in the history
  • Loading branch information
oxyno-zeta committed Sep 6, 2020
1 parent b643bc9 commit a9c3b11
Show file tree
Hide file tree
Showing 6 changed files with 934 additions and 0 deletions.
5 changes: 5 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module github.com/oxyno-zeta/gomock-extra-matcher

go 1.15

require github.com/golang/mock v1.4.4
8 changes: 8 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
143 changes: 143 additions & 0 deletions map-matcher.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
package extra

import (
"fmt"
"reflect"

"github.com/golang/mock/gomock"
)

// MMatcher interface improved to add functions
type MMatcher interface {
gomock.Matcher
// Add a matcher for a specific key
Key(key interface{}, match interface{}) MMatcher
}

type mStorage struct {
key interface{}
match interface{}
}

type mapMatcher struct {
keys []*mStorage
}

func (m *mapMatcher) String() string {
str := ""

// Loop over all keys
for in, v := range m.keys {
// Add separator for display
if in > 0 {
str += ", "
}

// Check if key is a gomock matcher
m, ok := v.key.(gomock.Matcher)
if ok {
str += fmt.Sprintf("key %s", m.String())
} else {
str += fmt.Sprintf("key %v", v.key)
}

// Try to cast to a matcher interface
m, ok = v.match.(gomock.Matcher)
// Check if cast is ok
if ok {
str += fmt.Sprintf(" must match %s", m.String())
} else {
str += fmt.Sprintf(" must be equal to %v", v.match)
}
}

return str
}

func (m *mapMatcher) Matches(x interface{}) bool {
// Check if x is nil
if x == nil {
return false
}

// Value of interface input
rval := reflect.ValueOf(x)
rkind := rval.Kind()
// Check if reflect value is supported or not
if rkind != reflect.Map {
return false
}

// Default case
res := len(m.keys) != 0

// Create reflect indirect
// indirect := reflect.Indirect(rval)
// Loop over all matcher keys
for _, kk := range m.keys {
// Store if matcher key can be found
matchKeyFound := false
// Loop over map keys
for _, kVal := range rval.MapKeys() {
// Get key data
keyD := kVal.Interface()
// Get reflect value from key
rv := rval.MapIndex(kVal)
// Get data from key
val := rv.Interface()
// Check if matcher key is matching current key
if !isMatchingData(kk.key, keyD) {
// Skip this key
continue
}

// Set match key as found
matchKeyFound = true

// Check map key value is matching
res = res && isMatchingData(kk.match, val)

// Break the loop at this step
// No need to continue to check map values
break
}

// Check if match key was found
if !matchKeyFound {
// Match key wasn't found, it is an error
return false
}

// Check result
if !res {
// If result isn't true at this step, stop now
return false
}
}

return res
}

func isMatchingData(matchKey, keyData interface{}) bool {
// Check if given key in matcher is a gomock matcher
mk, ok := matchKey.(gomock.Matcher)
if ok {
return mk.Matches(keyData)
}

return matchKey == keyData
}

func (m *mapMatcher) Key(key interface{}, match interface{}) MMatcher {
// Check if key exists
if key == nil {
return m
}
// Key name exists => add data
m.keys = append(m.keys, &mStorage{key: key, match: match})
// Return
return m
}

// MapMatcher will return a new map matcher
func MapMatcher() MMatcher { return &mapMatcher{} }
Loading

0 comments on commit a9c3b11

Please sign in to comment.