Merge pull request #171 from timstclair/gstruct

Introduce new 'gstruct' library
diff --git a/gstruct/elements.go b/gstruct/elements.go
new file mode 100644
index 0000000..9b6b134
--- /dev/null
+++ b/gstruct/elements.go
@@ -0,0 +1,141 @@
+package gstruct
+
+import (
+	"errors"
+	"fmt"
+	"reflect"
+	"runtime/debug"
+
+	"github.com/onsi/gomega/format"
+	errorsutil "github.com/onsi/gomega/gstruct/errors"
+	"github.com/onsi/gomega/types"
+)
+
+//MatchAllElements succeeds if every element of a slice matches the element matcher it maps to
+//through the id function, and every element matcher is matched.
+//  Expect([]string{"a", "b"}).To(MatchAllElements(idFn, matchers.Elements{
+//      "a": BeEqual("a"),
+//      "b": BeEqual("b"),
+//  })
+func MatchAllElements(identifier Identifier, 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.
+//  Expect([]string{"a", "c"}).To(MatchElements(idFn, IgnoreMissing|IgnoreExtra, matchers.Elements{
+//      "a": BeEqual("a")
+//      "b": BeEqual("b"),
+//  })
+func MatchElements(identifier Identifier, options Options, elements Elements) types.GomegaMatcher {
+	return &ElementsMatcher{
+		Identifier:    identifier,
+		Elements:      elements,
+		IgnoreExtras:  options&IgnoreExtras != 0,
+		IgnoreMissing: options&IgnoreMissing != 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
+
+	// Whether to ignore extra elements or consider it an error.
+	IgnoreExtras bool
+	// Whether to ignore missing elements or consider it an error.
+	IgnoreMissing bool
+
+	// State.
+	failures []error
+}
+
+// Element ID to matcher.
+type Elements map[string]types.GomegaMatcher
+
+// Function for identifying (mapping) elements.
+type Identifier func(element interface{}) string
+
+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)
+	}
+
+	m.failures = m.matchElements(actual)
+	if len(m.failures) > 0 {
+		return false, nil
+	}
+	return true, nil
+}
+
+func (m *ElementsMatcher) matchElements(actual interface{}) (errs []error) {
+	// Provide more useful error messages in the case of a panic.
+	defer func() {
+		if err := recover(); err != nil {
+			errs = append(errs, fmt.Errorf("panic checking %+v: %v\n%s", actual, err, debug.Stack()))
+		}
+	}()
+
+	val := reflect.ValueOf(actual)
+	elements := map[string]bool{}
+	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
+		}
+		elements[id] = true
+
+		matcher, expected := m.Elements[id]
+		if !expected {
+			if !m.IgnoreExtras {
+				errs = append(errs, fmt.Errorf("unexpected element %s", id))
+			}
+			continue
+		}
+
+		match, err := matcher.Match(element)
+		if match {
+			continue
+		}
+
+		if err == nil {
+			if nesting, ok := matcher.(errorsutil.NestingMatcher); ok {
+				err = errorsutil.AggregateError(nesting.Failures())
+			} else {
+				err = errors.New(matcher.FailureMessage(element))
+			}
+		}
+		errs = append(errs, errorsutil.Nest(fmt.Sprintf("[%s]", id), err))
+	}
+
+	for id := range m.Elements {
+		if !elements[id] && !m.IgnoreMissing {
+			errs = append(errs, fmt.Errorf("missing expected element %s", id))
+		}
+	}
+
+	return errs
+}
+
+func (m *ElementsMatcher) FailureMessage(actual interface{}) (message string) {
+	failure := errorsutil.AggregateError(m.failures)
+	return format.Message(actual, fmt.Sprintf("to match elements: %v", failure))
+}
+
+func (m *ElementsMatcher) NegatedFailureMessage(actual interface{}) (message string) {
+	return format.Message(actual, "not to match elements")
+}
+
+func (m *ElementsMatcher) Failures() []error {
+	return m.failures
+}
diff --git a/gstruct/elements_test.go b/gstruct/elements_test.go
new file mode 100644
index 0000000..8a63920
--- /dev/null
+++ b/gstruct/elements_test.go
@@ -0,0 +1,85 @@
+package gstruct_test
+
+import (
+	. "github.com/onsi/ginkgo"
+	. "github.com/onsi/gomega"
+	. "github.com/onsi/gomega/gstruct"
+)
+
+var _ = Describe("Slice", 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 strictly match all elements", func() {
+		m := MatchAllElements(id, Elements{
+			"b": Equal("b"),
+			"a": Equal("a"),
+		})
+		Ω(allElements).Should(m, "should match all elements")
+		Ω(missingElements).ShouldNot(m, "should fail with missing elements")
+		Ω(extraElements).ShouldNot(m, "should fail with extra elements")
+		Ω(duplicateElements).ShouldNot(m, "should fail with duplicate elements")
+		Ω(nils).ShouldNot(m, "should fail with an uninitialized slice")
+
+		m = MatchAllElements(id, Elements{
+			"a": Equal("a"),
+			"b": Equal("fail"),
+		})
+		Ω(allElements).ShouldNot(m, "should run nested matchers")
+
+		m = MatchAllElements(id, Elements{})
+		Ω(empty).Should(m, "should handle empty slices")
+		Ω(allElements).ShouldNot(m, "should handle only empty slices")
+		Ω(nils).Should(m, "should handle nil slices")
+	})
+
+	It("should ignore extra elements", func() {
+		m := MatchElements(id, IgnoreExtras, Elements{
+			"b": Equal("b"),
+			"a": Equal("a"),
+		})
+		Ω(allElements).Should(m, "should match all elements")
+		Ω(missingElements).ShouldNot(m, "should fail with missing elements")
+		Ω(extraElements).Should(m, "should ignore extra elements")
+		Ω(duplicateElements).ShouldNot(m, "should fail with duplicate elements")
+		Ω(nils).ShouldNot(m, "should fail with an uninitialized slice")
+	})
+
+	It("should ignore missing elements", func() {
+		m := MatchElements(id, IgnoreMissing, Elements{
+			"a": Equal("a"),
+			"b": Equal("b"),
+		})
+		Ω(allElements).Should(m, "should match all elements")
+		Ω(missingElements).Should(m, "should ignore missing elements")
+		Ω(extraElements).ShouldNot(m, "should fail with extra elements")
+		Ω(duplicateElements).ShouldNot(m, "should fail with duplicate elements")
+		Ω(nils).Should(m, "should ignore an uninitialized slice")
+	})
+
+	It("should ignore missing and extra elements", func() {
+		m := MatchElements(id, IgnoreMissing|IgnoreExtras, Elements{
+			"a": Equal("a"),
+			"b": Equal("b"),
+		})
+		Ω(allElements).Should(m, "should match all elements")
+		Ω(missingElements).Should(m, "should ignore missing elements")
+		Ω(extraElements).Should(m, "should ignore extra elements")
+		Ω(duplicateElements).ShouldNot(m, "should fail with duplicate elements")
+		Ω(nils).Should(m, "should ignore an uninitialized slice")
+
+		m = MatchElements(id, IgnoreExtras|IgnoreMissing, Elements{
+			"a": Equal("a"),
+			"b": Equal("fail"),
+		})
+		Ω(allElements).ShouldNot(m, "should run nested matchers")
+	})
+})
+
+func id(element interface{}) string {
+	return element.(string)
+}
diff --git a/gstruct/errors/nested_types.go b/gstruct/errors/nested_types.go
new file mode 100644
index 0000000..188492b
--- /dev/null
+++ b/gstruct/errors/nested_types.go
@@ -0,0 +1,72 @@
+package errors
+
+import (
+	"fmt"
+	"strings"
+
+	"github.com/onsi/gomega/types"
+)
+
+// A stateful matcher that nests other matchers within it and preserves the error types of the
+// nested matcher failures.
+type NestingMatcher interface {
+	types.GomegaMatcher
+
+	// Returns the failures of nested matchers.
+	Failures() []error
+}
+
+// An error type for labeling errors on deeply nested matchers.
+type NestedError struct {
+	Path string
+	Err  error
+}
+
+func (e *NestedError) Error() string {
+	// Indent Errors.
+	indented := strings.Replace(e.Err.Error(), "\n", "\n\t", -1)
+	return fmt.Sprintf("%s:\n\t%v", e.Path, indented)
+}
+
+// Create a NestedError with the given path.
+// If err is a NestedError, prepend the path to it.
+// If err is an AggregateError, recursively Nest each error.
+func Nest(path string, err error) error {
+	if ag, ok := err.(AggregateError); ok {
+		var errs AggregateError
+		for _, e := range ag {
+			errs = append(errs, Nest(path, e))
+		}
+		return errs
+	}
+	if ne, ok := err.(*NestedError); ok {
+		return &NestedError{
+			Path: path + ne.Path,
+			Err:  ne.Err,
+		}
+	}
+	return &NestedError{
+		Path: path,
+		Err:  err,
+	}
+}
+
+// An error type for treating multiple errors as a single error.
+type AggregateError []error
+
+// Error is part of the error interface.
+func (err AggregateError) Error() string {
+	if len(err) == 0 {
+		// This should never happen, really.
+		return ""
+	}
+	if len(err) == 1 {
+		return err[0].Error()
+	}
+	result := fmt.Sprintf("[%s", err[0].Error())
+	for i := 1; i < len(err); i++ {
+		result += fmt.Sprintf(", %s", err[i].Error())
+	}
+	result += "]"
+	return result
+}
diff --git a/gstruct/fields.go b/gstruct/fields.go
new file mode 100644
index 0000000..f3c1575
--- /dev/null
+++ b/gstruct/fields.go
@@ -0,0 +1,141 @@
+package gstruct
+
+import (
+	"errors"
+	"fmt"
+	"reflect"
+	"runtime/debug"
+	"strings"
+
+	"github.com/onsi/gomega/format"
+	errorsutil "github.com/onsi/gomega/gstruct/errors"
+	"github.com/onsi/gomega/types"
+)
+
+//MatchAllFields succeeds if every field of a struct matches the field matcher associated with
+//it, and every element matcher is matched.
+//  Expect([]string{"a", "b"}).To(MatchAllFields(idFn, gstruct.Fields{
+//      "a": BeEqual("a"),
+//      "b": BeEqual("b"),
+//  })
+func MatchAllFields(fields Fields) types.GomegaMatcher {
+	return &FieldsMatcher{
+		Fields: fields,
+	}
+}
+
+//MatchFields succeeds if each element of a struct matches the field matcher associated with
+//it. It can ignore extra fields and/or missing fields.
+//  Expect([]string{"a", "c"}).To(MatchFields(idFn, IgnoreMissing|IgnoreExtra, gstruct.Fields{
+//      "a": BeEqual("a")
+//      "b": BeEqual("b"),
+//  })
+func MatchFields(options Options, fields Fields) types.GomegaMatcher {
+	return &FieldsMatcher{
+		Fields:        fields,
+		IgnoreExtras:  options&IgnoreExtras != 0,
+		IgnoreMissing: options&IgnoreMissing != 0,
+	}
+}
+
+type FieldsMatcher struct {
+	// Matchers for each field.
+	Fields Fields
+
+	// Whether to ignore extra elements or consider it an error.
+	IgnoreExtras bool
+	// Whether to ignore missing elements or consider it an error.
+	IgnoreMissing bool
+
+	// State.
+	failures []error
+}
+
+// Field name to matcher.
+type Fields map[string]types.GomegaMatcher
+
+func (m *FieldsMatcher) Match(actual interface{}) (success bool, err error) {
+	if reflect.TypeOf(actual).Kind() != reflect.Struct {
+		return false, fmt.Errorf("%v is type %T, expected struct", actual, actual)
+	}
+
+	m.failures = m.matchFields(actual)
+	if len(m.failures) > 0 {
+		return false, nil
+	}
+	return true, nil
+}
+
+func (m *FieldsMatcher) matchFields(actual interface{}) (errs []error) {
+	val := reflect.ValueOf(actual)
+	typ := val.Type()
+	fields := map[string]bool{}
+	for i := 0; i < val.NumField(); i++ {
+		fieldName := typ.Field(i).Name
+		fields[fieldName] = true
+
+		err := func() (err error) {
+			// This test relies heavily on reflect, which tends to panic.
+			// Recover here to provide more useful error messages in that case.
+			defer func() {
+				if r := recover(); r != nil {
+					err = fmt.Errorf("panic checking %+v: %v\n%s", actual, r, debug.Stack())
+				}
+			}()
+
+			matcher, expected := m.Fields[fieldName]
+			if !expected {
+				if !m.IgnoreExtras {
+					return fmt.Errorf("unexpected field %s: %+v", fieldName, actual)
+				}
+				return nil
+			}
+
+			var field interface{}
+			if val.Field(i).IsValid() {
+				field = val.Field(i).Interface()
+			} else {
+				field = reflect.Zero(typ.Field(i).Type)
+			}
+
+			match, err := matcher.Match(field)
+			if err != nil {
+				return err
+			} else if !match {
+				if nesting, ok := matcher.(errorsutil.NestingMatcher); ok {
+					return errorsutil.AggregateError(nesting.Failures())
+				}
+				return errors.New(matcher.FailureMessage(field))
+			}
+			return nil
+		}()
+		if err != nil {
+			errs = append(errs, errorsutil.Nest("."+fieldName, err))
+		}
+	}
+
+	for field := range m.Fields {
+		if !fields[field] && !m.IgnoreMissing {
+			errs = append(errs, fmt.Errorf("missing expected field %s", field))
+		}
+	}
+
+	return errs
+}
+
+func (m *FieldsMatcher) FailureMessage(actual interface{}) (message string) {
+	failures := make([]string, len(m.failures))
+	for i := range m.failures {
+		failures[i] = m.failures[i].Error()
+	}
+	return format.Message(reflect.TypeOf(actual).Name(),
+		fmt.Sprintf("to match fields: {\n%v\n}\n", strings.Join(failures, "\n")))
+}
+
+func (m *FieldsMatcher) NegatedFailureMessage(actual interface{}) (message string) {
+	return format.Message(actual, "not to match fields")
+}
+
+func (m *FieldsMatcher) Failures() []error {
+	return m.failures
+}
diff --git a/gstruct/fields_test.go b/gstruct/fields_test.go
new file mode 100644
index 0000000..61f4afc
--- /dev/null
+++ b/gstruct/fields_test.go
@@ -0,0 +1,76 @@
+package gstruct_test
+
+import (
+	. "github.com/onsi/ginkgo"
+	. "github.com/onsi/gomega"
+	. "github.com/onsi/gomega/gstruct"
+)
+
+var _ = Describe("Struct", func() {
+	allFields := struct{ A, B string }{"a", "b"}
+	missingFields := struct{ A string }{"a"}
+	extraFields := struct{ A, B, C string }{"a", "b", "c"}
+	emptyFields := struct{ A, B string }{}
+
+	It("should strictly match all fields", func() {
+		m := MatchAllFields(Fields{
+			"B": Equal("b"),
+			"A": Equal("a"),
+		})
+		Ω(allFields).Should(m, "should match all fields")
+		Ω(missingFields).ShouldNot(m, "should fail with missing fields")
+		Ω(extraFields).ShouldNot(m, "should fail with extra fields")
+		Ω(emptyFields).ShouldNot(m, "should fail with empty fields")
+
+		m = MatchAllFields(Fields{
+			"A": Equal("a"),
+			"B": Equal("fail"),
+		})
+		Ω(allFields).ShouldNot(m, "should run nested matchers")
+	})
+
+	It("should handle empty structs", func() {
+		m := MatchAllFields(Fields{})
+		Ω(struct{}{}).Should(m, "should handle empty structs")
+		Ω(allFields).ShouldNot(m, "should fail with extra fields")
+	})
+
+	It("should ignore missing fields", func() {
+		m := MatchFields(IgnoreMissing, Fields{
+			"B": Equal("b"),
+			"A": Equal("a"),
+		})
+		Ω(allFields).Should(m, "should match all fields")
+		Ω(missingFields).Should(m, "should ignore missing fields")
+		Ω(extraFields).ShouldNot(m, "should fail with extra fields")
+		Ω(emptyFields).ShouldNot(m, "should fail with empty fields")
+	})
+
+	It("should ignore extra fields", func() {
+		m := MatchFields(IgnoreExtras, Fields{
+			"B": Equal("b"),
+			"A": Equal("a"),
+		})
+		Ω(allFields).Should(m, "should match all fields")
+		Ω(missingFields).ShouldNot(m, "should fail with missing fields")
+		Ω(extraFields).Should(m, "should ignore extra fields")
+		Ω(emptyFields).ShouldNot(m, "should fail with empty fields")
+	})
+
+	It("should ignore missing and extra fields", func() {
+		m := MatchFields(IgnoreMissing|IgnoreExtras, Fields{
+			"B": Equal("b"),
+			"A": Equal("a"),
+		})
+		Ω(allFields).Should(m, "should match all fields")
+		Ω(missingFields).Should(m, "should ignore missing fields")
+		Ω(extraFields).Should(m, "should ignore extra fields")
+		Ω(emptyFields).ShouldNot(m, "should fail with empty fields")
+
+		m = MatchFields(IgnoreMissing|IgnoreExtras, Fields{
+			"A": Equal("a"),
+			"B": Equal("fail"),
+		})
+		Ω(allFields).ShouldNot(m, "should run nested matchers")
+	})
+})
diff --git a/gstruct/gstruct_tests_suite_test.go b/gstruct/gstruct_tests_suite_test.go
new file mode 100644
index 0000000..d475663
--- /dev/null
+++ b/gstruct/gstruct_tests_suite_test.go
@@ -0,0 +1,13 @@
+package gstruct_test
+
+import (
+	"testing"
+
+	. "github.com/onsi/ginkgo"
+	. "github.com/onsi/gomega"
+)
+
+func Test(t *testing.T) {
+	RegisterFailHandler(Fail)
+	RunSpecs(t, "Gstruct Suite")
+}
diff --git a/gstruct/ignore.go b/gstruct/ignore.go
new file mode 100644
index 0000000..0365f32
--- /dev/null
+++ b/gstruct/ignore.go
@@ -0,0 +1,37 @@
+package gstruct
+
+import (
+	"github.com/onsi/gomega/types"
+)
+
+//Ignore ignores the actual value and always succeeds.
+//  Expect(nil).To(Ignore())
+//  Expect(true).To(Ignore())
+func Ignore() types.GomegaMatcher {
+	return &IgnoreMatcher{true}
+}
+
+//Reject ignores the actual value and always fails. It can be used in conjunction with IgnoreMissing
+//to catch problematic elements, or to verify tests are running.
+//  Expect(nil).NotTo(Reject())
+//  Expect(true).NotTo(Reject())
+func Reject() types.GomegaMatcher {
+	return &IgnoreMatcher{false}
+}
+
+// A matcher that either always succeeds or always fails.
+type IgnoreMatcher struct {
+	Succeed bool
+}
+
+func (m *IgnoreMatcher) Match(actual interface{}) (bool, error) {
+	return m.Succeed, nil
+}
+
+func (m *IgnoreMatcher) FailureMessage(_ interface{}) (message string) {
+	return "Unconditional failure"
+}
+
+func (m *IgnoreMatcher) NegatedFailureMessage(_ interface{}) (message string) {
+	return "Unconditional success"
+}
diff --git a/gstruct/ignore_test.go b/gstruct/ignore_test.go
new file mode 100644
index 0000000..70e1d40
--- /dev/null
+++ b/gstruct/ignore_test.go
@@ -0,0 +1,23 @@
+package gstruct_test
+
+import (
+	. "github.com/onsi/ginkgo"
+	. "github.com/onsi/gomega"
+	. "github.com/onsi/gomega/gstruct"
+)
+
+var _ = Describe("Ignore", func() {
+	It("should always succeed", func() {
+		Ω(nil).Should(Ignore())
+		Ω(struct{}{}).Should(Ignore())
+		Ω(0).Should(Ignore())
+		Ω(false).Should(Ignore())
+	})
+
+	It("should always fail", func() {
+		Ω(nil).ShouldNot(Reject())
+		Ω(struct{}{}).ShouldNot(Reject())
+		Ω(1).ShouldNot(Reject())
+		Ω(true).ShouldNot(Reject())
+	})
+})
diff --git a/gstruct/pointer.go b/gstruct/pointer.go
new file mode 100644
index 0000000..0a2f35d
--- /dev/null
+++ b/gstruct/pointer.go
@@ -0,0 +1,56 @@
+package gstruct
+
+import (
+	"fmt"
+	"reflect"
+
+	"github.com/onsi/gomega/format"
+	"github.com/onsi/gomega/types"
+)
+
+//PointTo applies the given matcher to the value pointed to by actual. It fails if the pointer is
+//nil.
+//  actual := 5
+//  Expect(&actual).To(PointTo(Equal(5)))
+func PointTo(matcher types.GomegaMatcher) types.GomegaMatcher {
+	return &PointerMatcher{
+		Matcher: matcher,
+	}
+}
+
+type PointerMatcher struct {
+	Matcher types.GomegaMatcher
+
+	// Failure message.
+	failure string
+}
+
+func (m *PointerMatcher) Match(actual interface{}) (bool, error) {
+	val := reflect.ValueOf(actual)
+
+	// return error if actual type is not a pointer
+	if val.Kind() != reflect.Ptr {
+		return false, fmt.Errorf("PointerMatcher expects a pointer but we have '%s'", val.Kind())
+	}
+
+	if !val.IsValid() || val.IsNil() {
+		m.failure = format.Message(actual, "not to be <nil>")
+		return false, nil
+	}
+
+	// Forward the value.
+	elem := val.Elem().Interface()
+	match, err := m.Matcher.Match(elem)
+	if !match {
+		m.failure = m.Matcher.FailureMessage(elem)
+	}
+	return match, err
+}
+
+func (m *PointerMatcher) FailureMessage(_ interface{}) (message string) {
+	return m.failure
+}
+
+func (m *PointerMatcher) NegatedFailureMessage(actual interface{}) (message string) {
+	return m.Matcher.NegatedFailureMessage(actual)
+}
diff --git a/gstruct/pointer_test.go b/gstruct/pointer_test.go
new file mode 100644
index 0000000..b02081c
--- /dev/null
+++ b/gstruct/pointer_test.go
@@ -0,0 +1,33 @@
+package gstruct_test
+
+import (
+	. "github.com/onsi/ginkgo"
+	. "github.com/onsi/gomega"
+	. "github.com/onsi/gomega/gstruct"
+)
+
+var _ = Describe("PointTo", func() {
+	It("should fail when passed nil", func() {
+		var p *struct{}
+		Ω(p).Should(BeNil())
+	})
+
+	It("should succeed when passed non-nil pointer", func() {
+		var s struct{}
+		Ω(&s).Should(PointTo(Ignore()))
+	})
+
+	It("should unwrap the pointee value", func() {
+		i := 1
+		Ω(&i).Should(PointTo(Equal(1)))
+		Ω(&i).ShouldNot(PointTo(Equal(2)))
+	})
+
+	It("should work with nested pointers", func() {
+		i := 1
+		ip := &i
+		ipp := &ip
+		Ω(ipp).Should(PointTo(PointTo(Equal(1))))
+		Ω(ipp).ShouldNot(PointTo(PointTo(Equal(2))))
+	})
+})
diff --git a/gstruct/types.go b/gstruct/types.go
new file mode 100644
index 0000000..0b7a124
--- /dev/null
+++ b/gstruct/types.go
@@ -0,0 +1,11 @@
+package gstruct
+
+//Options is the type for options passed to some matchers.
+type Options int
+
+const (
+	//IgnoreExtras tells the matcher to ignore extra elements or fields, rather than triggering a failure.
+	IgnoreExtras Options = 1 << iota
+	//IgnoreMissing tells the matcher to ignore missing elements or fields, rather than triggering a failure.
+	IgnoreMissing
+)