allow specifying that a matcher can apply to multiple members of a slice addresses #184
diff --git a/gstruct/elements.go b/gstruct/elements.go index 9b6b134..a315fa1 100644 --- a/gstruct/elements.go +++ b/gstruct/elements.go
@@ -32,10 +32,11 @@ // }) func MatchElements(identifier Identifier, options Options, elements Elements) types.GomegaMatcher { return &ElementsMatcher{ - Identifier: identifier, - Elements: elements, - IgnoreExtras: options&IgnoreExtras != 0, - IgnoreMissing: options&IgnoreMissing != 0, + Identifier: identifier, + Elements: elements, + IgnoreExtras: options&IgnoreExtras != 0, + IgnoreMissing: options&IgnoreMissing != 0, + AllowDuplicates: options&AllowDuplicates != 0, } } @@ -52,6 +53,8 @@ IgnoreExtras bool // Whether to ignore missing elements or consider it an error. IgnoreMissing bool + // Whether to key duplicates when matching IDs. + AllowDuplicates bool // State. failures []error @@ -88,10 +91,11 @@ for i := 0; i < val.Len(); i++ { element := val.Index(i).Interface() id := m.Identifier(element) - // TODO: Add options to ignore & match duplicates. if elements[id] { - errs = append(errs, fmt.Errorf("found duplicate element ID %s", id)) - continue + if !m.AllowDuplicates { + errs = append(errs, fmt.Errorf("found duplicate element ID %s", id)) + continue + } } elements[id] = true
diff --git a/gstruct/elements_test.go b/gstruct/elements_test.go index 8a63920..8ba78cb 100644 --- a/gstruct/elements_test.go +++ b/gstruct/elements_test.go
@@ -78,6 +78,65 @@ }) Ω(allElements).ShouldNot(m, "should run nested matchers") }) + + Context("with elements that share a key", func() { + nonUniqueID := func(element interface{}) string { + return element.(string)[0:1] + } + + allElements := []string{"a123", "a213", "b321"} + includingBadElements := []string{"a123", "b123", "b5555"} + extraElements := []string{"a123", "b1234", "c345"} + missingElements := []string{"b123", "b1234", "b1345"} + + It("should strictly allow multiple matches", func() { + m := MatchElements(nonUniqueID, AllowDuplicates, Elements{ + "a": ContainSubstring("1"), + "b": ContainSubstring("1"), + }) + Ω(allElements).Should(m, "should match all elements") + Ω(includingBadElements).ShouldNot(m, "should reject if a member fails the matcher") + Ω(extraElements).ShouldNot(m, "should reject with extra keys") + Ω(missingElements).ShouldNot(m, "should reject with missing keys") + Ω(nils).ShouldNot(m, "should fail with an uninitialized slice") + }) + + It("should ignore missing", func() { + m := MatchElements(nonUniqueID, AllowDuplicates|IgnoreMissing, Elements{ + "a": ContainSubstring("1"), + "b": ContainSubstring("1"), + }) + Ω(allElements).Should(m, "should match all elements") + Ω(includingBadElements).ShouldNot(m, "should reject if a member fails the matcher") + Ω(extraElements).ShouldNot(m, "should reject with extra keys") + Ω(missingElements).Should(m, "should allow missing keys") + Ω(nils).Should(m, "should allow an uninitialized slice") + }) + + It("should ignore extras", func() { + m := MatchElements(nonUniqueID, AllowDuplicates|IgnoreExtras, Elements{ + "a": ContainSubstring("1"), + "b": ContainSubstring("1"), + }) + Ω(allElements).Should(m, "should match all elements") + Ω(includingBadElements).ShouldNot(m, "should reject if a member fails the matcher") + Ω(extraElements).Should(m, "should allow extra keys") + Ω(missingElements).ShouldNot(m, "should reject missing keys") + Ω(nils).ShouldNot(m, "should reject an uninitialized slice") + }) + + It("should ignore missing and extras", func() { + m := MatchElements(nonUniqueID, AllowDuplicates|IgnoreExtras|IgnoreMissing, Elements{ + "a": ContainSubstring("1"), + "b": ContainSubstring("1"), + }) + Ω(allElements).Should(m, "should match all elements") + Ω(includingBadElements).ShouldNot(m, "should reject if a member fails the matcher") + Ω(extraElements).Should(m, "should allow extra keys") + Ω(missingElements).Should(m, "should allow missing keys") + Ω(nils).Should(m, "should allow an uninitialized slice") + }) + }) }) func id(element interface{}) string {
diff --git a/gstruct/types.go b/gstruct/types.go index 0b7a124..48cbbe8 100644 --- a/gstruct/types.go +++ b/gstruct/types.go
@@ -8,4 +8,8 @@ IgnoreExtras Options = 1 << iota //IgnoreMissing tells the matcher to ignore missing elements or fields, rather than triggering a failure. IgnoreMissing + //AllowDuplicates tells the matcher to permit multiple members of the slice to produce the same ID when + //considered by the indentifier function. All members that map to a given key must still match successfully + //with the matcher that is provided for that key. + AllowDuplicates )