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

feature: add index to gstruct element func #419

Merged
merged 1 commit into from Mar 3, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
74 changes: 72 additions & 2 deletions gstruct/elements.go
Expand Up @@ -7,6 +7,7 @@ import (
"fmt"
"reflect"
"runtime/debug"
"strconv"

"github.com/onsi/gomega/format"
errorsutil "github.com/onsi/gomega/gstruct/errors"
Expand All @@ -30,6 +31,23 @@ func MatchAllElements(identifier Identifier, elements Elements) types.GomegaMatc
}
}

//MatchAllElementsWithIndex succeeds if every element of a slice matches the element matcher it maps to
//through the id with index function, and every element matcher is matched.
// idFn := func(index int, element interface{}) string {
// return strconv.Itoa(index)
// }
//
// Expect([]string{"a", "b"}).To(MatchAllElements(idFn, Elements{
// "0": Equal("a"),
// "1": Equal("b"),
// }))
func MatchAllElementsWithIndex(identifier IdentifierWithIndex, elements Elements) types.GomegaMatcher {
return &ElementsMatcher{
Identifier: identifier,
Elements: elements,
}
}

//MatchElements succeeds if each element of a slice matches the element matcher it maps to
//through the id function. It can ignore extra elements and/or missing elements.
// idFn := func(element interface{}) string {
Expand All @@ -56,14 +74,40 @@ func MatchElements(identifier Identifier, options Options, elements Elements) ty
}
}

//MatchElementsWithIndex succeeds if each element of a slice matches the element matcher it maps to
//through the id with index function. It can ignore extra elements and/or missing elements.
// idFn := func(index int, element interface{}) string {
// return strconv.Itoa(index)
// }
//
// Expect([]string{"a", "b", "c"}).To(MatchElements(idFn, IgnoreExtras, Elements{
// "0": Equal("a"),
// "1": Equal("b"),
// }))
// Expect([]string{"a", "c"}).To(MatchElements(idFn, IgnoreMissing, Elements{
// "0": Equal("a"),
// "1": Equal("b"),
// "2": Equal("c"),
// "3": Equal("d"),
// }))
func MatchElementsWithIndex(identifier IdentifierWithIndex, options Options, elements Elements) types.GomegaMatcher {
return &ElementsMatcher{
Identifier: identifier,
Elements: elements,
IgnoreExtras: options&IgnoreExtras != 0,
IgnoreMissing: options&IgnoreMissing != 0,
AllowDuplicates: options&AllowDuplicates != 0,
}
}

// ElementsMatcher is a NestingMatcher that applies custom matchers to each element of a slice mapped
// by the Identifier function.
// TODO: Extend this to work with arrays & maps (map the key) as well.
type ElementsMatcher struct {
// Matchers for each element.
Elements Elements
// Function mapping an element to the string key identifying its matcher.
Identifier Identifier
Identifier Identify

// Whether to ignore extra elements or consider it an error.
IgnoreExtras bool
Expand All @@ -82,6 +126,32 @@ type Elements map[string]types.GomegaMatcher
// Function for identifying (mapping) elements.
type Identifier func(element interface{}) string

// Calls the underlying fucntion with the provided params.
// Identifier drops the index.
func (i Identifier) WithIndexAndElement(index int, element interface{}) string {
return i(element)
}

// Uses the index and element to generate an element name
type IdentifierWithIndex func(index int, element interface{}) string

// Calls the underlying fucntion with the provided params.
// IdentifierWithIndex uses the index.
func (i IdentifierWithIndex) WithIndexAndElement(index int, element interface{}) string {
return i(index, element)
}

// Interface for identifing the element
type Identify interface {
WithIndexAndElement(i int, element interface{}) string
}

// IndexIdentity is a helper function for using an index as
// the key in the element map
func IndexIdentity(index int, _ interface{}) string {
return strconv.Itoa(index)
}

func (m *ElementsMatcher) Match(actual interface{}) (success bool, err error) {
if reflect.TypeOf(actual).Kind() != reflect.Slice {
return false, fmt.Errorf("%v is type %T, expected slice", actual, actual)
Expand All @@ -106,7 +176,7 @@ func (m *ElementsMatcher) matchElements(actual interface{}) (errs []error) {
elements := map[string]bool{}
for i := 0; i < val.Len(); i++ {
element := val.Index(i).Interface()
id := m.Identifier(element)
id := m.Identifier.WithIndexAndElement(i, element)
if elements[id] {
if !m.AllowDuplicates {
errs = append(errs, fmt.Errorf("found duplicate element ID %s", id))
Expand Down
32 changes: 32 additions & 0 deletions gstruct/elements_test.go
Expand Up @@ -137,6 +137,38 @@ var _ = Describe("Slice", func() {
Expect(nils).Should(m, "should allow an uninitialized slice")
})
})

Context("with index identifier", func() {
allElements := []string{"a", "b"}
missingElements := []string{"a"}
extraElements := []string{"a", "b", "c"}
duplicateElements := []string{"a", "a", "b"}
empty := []string{}
var nils []string

It("should use index", func() {
m := MatchAllElementsWithIndex(IndexIdentity, Elements{
"0": Equal("a"),
"1": Equal("b"),
})
Expect(allElements).Should(m, "should match all elements")
Expect(missingElements).ShouldNot(m, "should fail with missing elements")
Expect(extraElements).ShouldNot(m, "should fail with extra elements")
Expect(duplicateElements).ShouldNot(m, "should fail with duplicate elements")
Expect(nils).ShouldNot(m, "should fail with an uninitialized slice")

m = MatchAllElementsWithIndex(IndexIdentity, Elements{
"0": Equal("a"),
"1": Equal("fail"),
})
Expect(allElements).ShouldNot(m, "should run nested matchers")

m = MatchAllElementsWithIndex(IndexIdentity, Elements{})
Expect(empty).Should(m, "should handle empty slices")
Expect(allElements).ShouldNot(m, "should handle only empty slices")
Expect(nils).Should(m, "should handle nil slices")
})
})
})

func id(element interface{}) string {
Expand Down