Path: blob/main/vendor/github.com/onsi/gomega/matchers/contain_element_matcher.go
2880 views
// untested sections: 212package matchers34import (5"errors"6"fmt"7"reflect"89"github.com/onsi/gomega/format"10"github.com/onsi/gomega/matchers/internal/miter"11)1213type ContainElementMatcher struct {14Element any15Result []any16}1718func (matcher *ContainElementMatcher) Match(actual any) (success bool, err error) {19if !isArrayOrSlice(actual) && !isMap(actual) && !miter.IsIter(actual) {20return false, fmt.Errorf("ContainElement matcher expects an array/slice/map/iterator. Got:\n%s", format.Object(actual, 1))21}2223var actualT reflect.Type24var result reflect.Value25switch numResultArgs := len(matcher.Result); {26case numResultArgs > 1:27return false, errors.New("ContainElement matcher expects at most a single optional pointer to store its findings at")28case numResultArgs == 1:29// Check the optional result arg to point to a single value/array/slice/map30// of a type compatible with the actual value.31if reflect.ValueOf(matcher.Result[0]).Kind() != reflect.Ptr {32return false, fmt.Errorf("ContainElement matcher expects a non-nil pointer to store its findings at. Got\n%s",33format.Object(matcher.Result[0], 1))34}35actualT = reflect.TypeOf(actual)36resultReference := matcher.Result[0]37result = reflect.ValueOf(resultReference).Elem() // what ResultReference points to, to stash away our findings38switch result.Kind() {39case reflect.Array: // result arrays are not supported, as they cannot be dynamically sized.40if miter.IsIter(actual) {41_, actualvT := miter.IterKVTypes(actual)42return false, fmt.Errorf("ContainElement cannot return findings. Need *%s, got *%s",43reflect.SliceOf(actualvT), result.Type().String())44}45return false, fmt.Errorf("ContainElement cannot return findings. Need *%s, got *%s",46reflect.SliceOf(actualT.Elem()).String(), result.Type().String())4748case reflect.Slice: // result slice49// can we assign elements in actual to elements in what the result50// arg points to?51// - ✔ actual is an array or slice52// - ✔ actual is an iter.Seq producing "v" elements53// - ✔ actual is an iter.Seq2 producing "v" elements, ignoring54// the "k" elements.55switch {56case isArrayOrSlice(actual):57if !actualT.Elem().AssignableTo(result.Type().Elem()) {58return false, fmt.Errorf("ContainElement cannot return findings. Need *%s, got *%s",59actualT.String(), result.Type().String())60}6162case miter.IsIter(actual):63_, actualvT := miter.IterKVTypes(actual)64if !actualvT.AssignableTo(result.Type().Elem()) {65return false, fmt.Errorf("ContainElement cannot return findings. Need *%s, got *%s",66actualvT.String(), result.Type().String())67}6869default: // incompatible result reference70return false, fmt.Errorf("ContainElement cannot return findings. Need *%s, got *%s",71reflect.MapOf(actualT.Key(), actualT.Elem()).String(), result.Type().String())72}7374case reflect.Map: // result map75// can we assign elements in actual to elements in what the result76// arg points to?77// - ✔ actual is a map78// - ✔ actual is an iter.Seq2 (iter.Seq doesn't fit though)79switch {80case isMap(actual):81if !actualT.AssignableTo(result.Type()) {82return false, fmt.Errorf("ContainElement cannot return findings. Need *%s, got *%s",83actualT.String(), result.Type().String())84}8586case miter.IsIter(actual):87actualkT, actualvT := miter.IterKVTypes(actual)88if actualkT == nil {89return false, fmt.Errorf("ContainElement cannot return findings. Need *%s, got *%s",90reflect.SliceOf(actualvT).String(), result.Type().String())91}92if !reflect.MapOf(actualkT, actualvT).AssignableTo(result.Type()) {93return false, fmt.Errorf("ContainElement cannot return findings. Need *%s, got *%s",94reflect.MapOf(actualkT, actualvT), result.Type().String())95}9697default: // incompatible result reference98return false, fmt.Errorf("ContainElement cannot return findings. Need *%s, got *%s",99actualT.String(), result.Type().String())100}101102default:103// can we assign a (single) element in actual to what the result arg104// points to?105switch {106case miter.IsIter(actual):107_, actualvT := miter.IterKVTypes(actual)108if !actualvT.AssignableTo(result.Type()) {109return false, fmt.Errorf("ContainElement cannot return findings. Need *%s, got *%s",110actualvT.String(), result.Type().String())111}112default:113if !actualT.Elem().AssignableTo(result.Type()) {114return false, fmt.Errorf("ContainElement cannot return findings. Need *%s, got *%s",115actualT.Elem().String(), result.Type().String())116}117}118}119}120121// If the supplied matcher isn't an Omega matcher, default to the Equal122// matcher.123elemMatcher, elementIsMatcher := matcher.Element.(omegaMatcher)124if !elementIsMatcher {125elemMatcher = &EqualMatcher{Expected: matcher.Element}126}127128value := reflect.ValueOf(actual)129130var getFindings func() reflect.Value // abstracts how the findings are collected and stored131var lastError error132133if !miter.IsIter(actual) {134var valueAt func(int) any135var foundAt func(int)136// We're dealing with an array/slice/map, so in all cases we can iterate137// over the elements in actual using indices (that can be considered138// keys in case of maps).139if isMap(actual) {140keys := value.MapKeys()141valueAt = func(i int) any {142return value.MapIndex(keys[i]).Interface()143}144if result.Kind() != reflect.Invalid {145fm := reflect.MakeMap(actualT)146getFindings = func() reflect.Value { return fm }147foundAt = func(i int) {148fm.SetMapIndex(keys[i], value.MapIndex(keys[i]))149}150}151} else {152valueAt = func(i int) any {153return value.Index(i).Interface()154}155if result.Kind() != reflect.Invalid {156var fsl reflect.Value157if result.Kind() == reflect.Slice {158fsl = reflect.MakeSlice(result.Type(), 0, 0)159} else {160fsl = reflect.MakeSlice(reflect.SliceOf(result.Type()), 0, 0)161}162getFindings = func() reflect.Value { return fsl }163foundAt = func(i int) {164fsl = reflect.Append(fsl, value.Index(i))165}166}167}168169for i := 0; i < value.Len(); i++ {170elem := valueAt(i)171success, err := elemMatcher.Match(elem)172if err != nil {173lastError = err174continue175}176if success {177if result.Kind() == reflect.Invalid {178return true, nil179}180foundAt(i)181}182}183} else {184// We're dealing with an iterator as a first-class construct, so things185// are slightly different: there is no index defined as in case of186// arrays/slices/maps, just "ooooorder"187var found func(k, v reflect.Value)188if result.Kind() != reflect.Invalid {189if result.Kind() == reflect.Map {190fm := reflect.MakeMap(result.Type())191getFindings = func() reflect.Value { return fm }192found = func(k, v reflect.Value) { fm.SetMapIndex(k, v) }193} else {194var fsl reflect.Value195if result.Kind() == reflect.Slice {196fsl = reflect.MakeSlice(result.Type(), 0, 0)197} else {198fsl = reflect.MakeSlice(reflect.SliceOf(result.Type()), 0, 0)199}200getFindings = func() reflect.Value { return fsl }201found = func(_, v reflect.Value) { fsl = reflect.Append(fsl, v) }202}203}204205success := false206actualkT, _ := miter.IterKVTypes(actual)207if actualkT == nil {208miter.IterateV(actual, func(v reflect.Value) bool {209var err error210success, err = elemMatcher.Match(v.Interface())211if err != nil {212lastError = err213return true // iterate on...214}215if success {216if result.Kind() == reflect.Invalid {217return false // a match and no result needed, so we're done218}219found(reflect.Value{}, v)220}221return true // iterate on...222})223} else {224miter.IterateKV(actual, func(k, v reflect.Value) bool {225var err error226success, err = elemMatcher.Match(v.Interface())227if err != nil {228lastError = err229return true // iterate on...230}231if success {232if result.Kind() == reflect.Invalid {233return false // a match and no result needed, so we're done234}235found(k, v)236}237return true // iterate on...238})239}240if success && result.Kind() == reflect.Invalid {241return true, nil242}243}244245// when the expectation isn't interested in the findings except for success246// or non-success, then we're done here and return the last matcher error247// seen, if any, as well as non-success.248if result.Kind() == reflect.Invalid {249return false, lastError250}251252// pick up any findings the test is interested in as it specified a non-nil253// result reference. However, the expectation always is that there are at254// least one or multiple findings. So, if a result is expected, but we had255// no findings, then this is an error.256findings := getFindings()257if findings.Len() == 0 {258return false, lastError259}260261// there's just a single finding and the result is neither a slice nor a map262// (so it's a scalar): pick the one and only finding and return it in the263// place the reference points to.264if findings.Len() == 1 && !isArrayOrSlice(result.Interface()) && !isMap(result.Interface()) {265if isMap(actual) {266miter := findings.MapRange()267miter.Next()268result.Set(miter.Value())269} else {270result.Set(findings.Index(0))271}272return true, nil273}274275// at least one or even multiple findings and a the result references a276// slice or a map, so all we need to do is to store our findings where the277// reference points to.278if !findings.Type().AssignableTo(result.Type()) {279return false, fmt.Errorf("ContainElement cannot return multiple findings. Need *%s, got *%s",280findings.Type().String(), result.Type().String())281}282result.Set(findings)283return true, nil284}285286func (matcher *ContainElementMatcher) FailureMessage(actual any) (message string) {287return format.Message(actual, "to contain element matching", matcher.Element)288}289290func (matcher *ContainElementMatcher) NegatedFailureMessage(actual any) (message string) {291return format.Message(actual, "not to contain element matching", matcher.Element)292}293294295