func structMatcher()

in structmatcher/structmatcher.go [27:104]


func structMatcher(expected, actual reflect.Value, fieldPath string, shouldFilter bool, filterInclude bool, filterFields ...string) []string {
	// Add field names for the top-level struct to a filter map, and split off nested field names to pass down to nested structs
	filterMap := make(map[string]bool)
	nestedFilterFields := make([]string, 0)
	for i := 0; i < len(filterFields); i++ {
		fieldNames := strings.Split(filterFields[i], ".")
		if len(fieldNames) == 2 {
			nestedFilterFields = append(nestedFilterFields, fieldNames[1])
			// If we include a nested struct field, we also need to include the nested struct
			if filterInclude {
				filterMap[fieldNames[0]] = true
			}
		} else {
			filterMap[filterFields[i]] = true
		}
	}
	expectedStruct := reflect.Indirect(expected)
	actualStruct := reflect.Indirect(actual)
	mismatches := []string{}
	mismatches = append(mismatches, InterceptGomegaFailures(func() {
		structCanInterface := true
		for i := 0; i < expectedStruct.NumField(); i++ {
			expectedField := reflect.Indirect(expectedStruct.Field(i))
			actualField := reflect.Indirect(actualStruct.Field(i))
			fieldName := actualStruct.Type().Field(i).Name
			// If we're including, skip this field if the name doesn't match; if we're excluding, skip if it does match
			if shouldFilter && ((filterInclude && !filterMap[fieldName]) || (!filterInclude && filterMap[fieldName])) {
				continue
			}
			actualFieldIsNonemptySlice := actualField.Kind() == reflect.Slice && !actualField.IsNil() && actualField.Len() > 0
			expectedFieldIsNonemptySlice := expectedField.Kind() == reflect.Slice && !expectedField.IsNil() && expectedField.Len() > 0
			fieldIsStructSlice := actualFieldIsNonemptySlice && expectedFieldIsNonemptySlice && actualField.Len() == expectedField.Len() && actualField.Index(0).Kind() == reflect.Struct

			expectedFieldIsNilPtr := expectedStruct.Field(i).Kind() == reflect.Ptr && expectedStruct.Field(i).IsNil()
			actualFieldIsNilPtr := actualStruct.Field(i).Kind() == reflect.Ptr && actualStruct.Field(i).IsNil()

			if fieldIsStructSlice {
				for j := 0; j < actualField.Len(); j++ {
					expectedStructField := expectedStruct.Field(i).Index(j)
					actualStructField := actualStruct.Field(i).Index(j)
					subFieldPath := fmt.Sprintf("%s%s[%d].", fieldPath, fieldName, j)
					mismatches = append(mismatches, structMatcher(expectedStructField, actualStructField, subFieldPath, shouldFilter, filterInclude, nestedFilterFields...)...)
				}
			} else if actualFieldIsNilPtr != expectedFieldIsNilPtr {
				expectedValue := expectedStruct.Field(i).Interface()
				actualValue := actualStruct.Field(i).Interface()
				Expect(actualValue).To(Equal(expectedValue), "Mismatch on field %s%s", fieldPath, fieldName)
			} else if expectedStruct.Field(i).CanInterface() {
				if actualField.Kind() == reflect.Struct {
					expectedStructField := expectedStruct.Field(i)
					actualStructField := actualStruct.Field(i)
					subFieldPath := fmt.Sprintf("%s%s.", fieldPath, fieldName)
					mismatches = append(mismatches, structMatcher(expectedStructField, actualStructField, subFieldPath, shouldFilter, filterInclude, nestedFilterFields...)...)
				} else {
					expectedValue := expectedStruct.Field(i).Interface()
					actualValue := actualStruct.Field(i).Interface()
					Expect(actualValue).To(Equal(expectedValue), "Mismatch on field %s%s", fieldPath, fieldName)
				}
			} else {
				structCanInterface = false
			}

		}
		if !structCanInterface {
			extra := []interface{}{
				"Mismatch on unexported field within top level struct",
			}
			if fieldPath != "" {
				structName := fieldPath[0 : len(fieldPath)-1] // remove trailing dot.
				extra = []interface{}{
					"Mismatch on unexported field within %s", structName,
				}
			}
			Expect(actualStruct.Interface()).To(Equal(expectedStruct.Interface()), extra...)
		}
	})...)
	return mismatches
}