...

Source file src/github.com/onsi/gomega/gstruct/fields.go

Documentation: github.com/onsi/gomega/gstruct

     1  // untested sections: 6
     2  
     3  package gstruct
     4  
     5  import (
     6  	"errors"
     7  	"fmt"
     8  	"reflect"
     9  	"runtime/debug"
    10  	"strings"
    11  
    12  	"github.com/onsi/gomega/format"
    13  	errorsutil "github.com/onsi/gomega/gstruct/errors"
    14  	"github.com/onsi/gomega/types"
    15  )
    16  
    17  //MatchAllFields succeeds if every field of a struct matches the field matcher associated with
    18  //it, and every element matcher is matched.
    19  //    actual := struct{
    20  //      A int
    21  //      B []bool
    22  //      C string
    23  //    }{
    24  //      A: 5,
    25  //      B: []bool{true, false},
    26  //      C: "foo",
    27  //    }
    28  //
    29  //    Expect(actual).To(MatchAllFields(Fields{
    30  //      "A": Equal(5),
    31  //      "B": ConsistOf(true, false),
    32  //      "C": Equal("foo"),
    33  //    }))
    34  func MatchAllFields(fields Fields) types.GomegaMatcher {
    35  	return &FieldsMatcher{
    36  		Fields: fields,
    37  	}
    38  }
    39  
    40  //MatchFields succeeds if each element of a struct matches the field matcher associated with
    41  //it. It can ignore extra fields and/or missing fields.
    42  //    actual := struct{
    43  //      A int
    44  //      B []bool
    45  //      C string
    46  //    }{
    47  //      A: 5,
    48  //      B: []bool{true, false},
    49  //      C: "foo",
    50  //    }
    51  //
    52  //    Expect(actual).To(MatchFields(IgnoreExtras, Fields{
    53  //      "A": Equal(5),
    54  //      "B": ConsistOf(true, false),
    55  //    }))
    56  //    Expect(actual).To(MatchFields(IgnoreMissing, Fields{
    57  //      "A": Equal(5),
    58  //      "B": ConsistOf(true, false),
    59  //      "C": Equal("foo"),
    60  //      "D": Equal("extra"),
    61  //    }))
    62  func MatchFields(options Options, fields Fields) types.GomegaMatcher {
    63  	return &FieldsMatcher{
    64  		Fields:        fields,
    65  		IgnoreExtras:  options&IgnoreExtras != 0,
    66  		IgnoreMissing: options&IgnoreMissing != 0,
    67  	}
    68  }
    69  
    70  type FieldsMatcher struct {
    71  	// Matchers for each field.
    72  	Fields Fields
    73  
    74  	// Whether to ignore extra elements or consider it an error.
    75  	IgnoreExtras bool
    76  	// Whether to ignore missing elements or consider it an error.
    77  	IgnoreMissing bool
    78  
    79  	// State.
    80  	failures []error
    81  }
    82  
    83  // Field name to matcher.
    84  type Fields map[string]types.GomegaMatcher
    85  
    86  func (m *FieldsMatcher) Match(actual interface{}) (success bool, err error) {
    87  	if reflect.TypeOf(actual).Kind() != reflect.Struct {
    88  		return false, fmt.Errorf("%v is type %T, expected struct", actual, actual)
    89  	}
    90  
    91  	m.failures = m.matchFields(actual)
    92  	if len(m.failures) > 0 {
    93  		return false, nil
    94  	}
    95  	return true, nil
    96  }
    97  
    98  func (m *FieldsMatcher) matchFields(actual interface{}) (errs []error) {
    99  	val := reflect.ValueOf(actual)
   100  	typ := val.Type()
   101  	fields := map[string]bool{}
   102  	for i := 0; i < val.NumField(); i++ {
   103  		fieldName := typ.Field(i).Name
   104  		fields[fieldName] = true
   105  
   106  		err := func() (err error) {
   107  			// This test relies heavily on reflect, which tends to panic.
   108  			// Recover here to provide more useful error messages in that case.
   109  			defer func() {
   110  				if r := recover(); r != nil {
   111  					err = fmt.Errorf("panic checking %+v: %v\n%s", actual, r, debug.Stack())
   112  				}
   113  			}()
   114  
   115  			matcher, expected := m.Fields[fieldName]
   116  			if !expected {
   117  				if !m.IgnoreExtras {
   118  					return fmt.Errorf("unexpected field %s: %+v", fieldName, actual)
   119  				}
   120  				return nil
   121  			}
   122  
   123  			field := val.Field(i).Interface()
   124  
   125  			match, err := matcher.Match(field)
   126  			if err != nil {
   127  				return err
   128  			} else if !match {
   129  				if nesting, ok := matcher.(errorsutil.NestingMatcher); ok {
   130  					return errorsutil.AggregateError(nesting.Failures())
   131  				}
   132  				return errors.New(matcher.FailureMessage(field))
   133  			}
   134  			return nil
   135  		}()
   136  		if err != nil {
   137  			errs = append(errs, errorsutil.Nest("."+fieldName, err))
   138  		}
   139  	}
   140  
   141  	for field := range m.Fields {
   142  		if !fields[field] && !m.IgnoreMissing {
   143  			errs = append(errs, fmt.Errorf("missing expected field %s", field))
   144  		}
   145  	}
   146  
   147  	return errs
   148  }
   149  
   150  func (m *FieldsMatcher) FailureMessage(actual interface{}) (message string) {
   151  	failures := make([]string, len(m.failures))
   152  	for i := range m.failures {
   153  		failures[i] = m.failures[i].Error()
   154  	}
   155  	return format.Message(reflect.TypeOf(actual).Name(),
   156  		fmt.Sprintf("to match fields: {\n%v\n}\n", strings.Join(failures, "\n")))
   157  }
   158  
   159  func (m *FieldsMatcher) NegatedFailureMessage(actual interface{}) (message string) {
   160  	return format.Message(actual, "not to match fields")
   161  }
   162  
   163  func (m *FieldsMatcher) Failures() []error {
   164  	return m.failures
   165  }
   166  

View as plain text