func()

in internal/fields/validate.go [1120:1263]


func (v *Validator) parseSingleElementValue(key string, definition FieldDefinition, val any, doc common.MapStr) multierror.Error {
	invalidTypeError := func() multierror.Error {
		return multierror.Error{fmt.Errorf("field %q's Go type, %T, does not match the expected field type: %s (field value: %v)", key, val, definition.Type, val)}
	}

	stringValue := func() (string, bool) {
		switch val := val.(type) {
		case string:
			return val, true
		case bool, float64:
			if v.defaultNumericConversion || slices.Contains(v.numericKeywordFields, key) {
				return fmt.Sprintf("%v", val), true
			}
		}
		return "", false
	}

	switch definition.Type {
	// Constant keywords can define a value in the definition, if they do, all
	// values stored in this field should be this one.
	// If a pattern is provided, it checks if the value matches.
	case "constant_keyword":
		valStr, valid := stringValue()
		if !valid {
			return invalidTypeError()
		}

		if err := ensureConstantKeywordValueMatches(key, valStr, definition.Value); err != nil {
			return multierror.Error{err}
		}
		if err := ensurePatternMatches(key, valStr, definition.Pattern); err != nil {
			return multierror.Error{err}
		}
		if err := ensureAllowedValues(key, valStr, definition); err != nil {
			return multierror.Error{err}
		}
	// Normal text fields should be of type string.
	// If a pattern is provided, it checks if the value matches.
	case "keyword", "text":
		valStr, valid := stringValue()
		if !valid {
			return invalidTypeError()
		}

		if err := ensurePatternMatches(key, valStr, definition.Pattern); err != nil {
			return multierror.Error{err}
		}
		if err := ensureAllowedValues(key, valStr, definition); err != nil {
			return multierror.Error{err}
		}
	// Dates are expected to be formatted as strings or as seconds or milliseconds
	// since epoch.
	// If it is a string and a pattern is provided, it checks if the value matches.
	case "date":
		switch val := val.(type) {
		case string:
			if err := ensurePatternMatches(key, val, definition.Pattern); err != nil {
				return multierror.Error{err}
			}
		case float64:
			// date as seconds or milliseconds since epoch
			if definition.Pattern != "" {
				return multierror.Error{fmt.Errorf("numeric date in field %q, but pattern defined", key)}
			}
		default:
			return invalidTypeError()
		}
	// IP values should be actual IPs, included in the ranges of IPs available
	// in the geoip test database.
	// If a pattern is provided, it checks if the value matches.
	case "ip":
		valStr, valid := val.(string)
		if !valid {
			return invalidTypeError()
		}

		if err := ensurePatternMatches(key, valStr, definition.Pattern); err != nil {
			return multierror.Error{err}
		}

		if v.enabledAllowedIPCheck && !v.isAllowedIPValue(valStr) {
			return multierror.Error{fmt.Errorf("the IP %q is not one of the allowed test IPs (see: https://github.com/elastic/elastic-package/blob/main/internal/fields/_static/allowed_geo_ips.txt)", valStr)}
		}
	// Groups should only contain nested fields, not single values.
	case "group", "nested", "object":
		switch val := val.(type) {
		case map[string]any:
			// This is probably an element from an array of objects,
			// even if not recommended, it should be validated.
			if v.specVersion.LessThan(semver3_0_1) {
				break
			}
			errs := v.validateMapElement(key, common.MapStr(val), doc)
			if len(errs) == 0 {
				return nil
			}
			return errs.Unique()
		case []any:
			// This can be an array of array of objects. Elasticsearh will probably
			// flatten this. So even if this is quite unexpected, let's try to handle it.
			if v.specVersion.LessThan(semver3_0_1) {
				break
			}
			return forEachElementValue(key, definition, val, doc, v.parseSingleElementValue)
		case nil:
			// The document contains a null, let's consider this like an empty array.
			return nil
		default:
			switch {
			case definition.Type == "object" && definition.ObjectType != "":
				// This is the leaf element of an object without wildcards in the name, adapt the definition and try again.
				definition.Name = definition.Name + ".*"
				definition.Type = definition.ObjectType
				definition.ObjectType = ""
				return v.parseSingleElementValue(key, definition, val, doc)
			case definition.Type == "object" && definition.ObjectType == "":
				// Legacy mapping, ambiguous definition not allowed by recent versions of the spec, ignore it.
				return nil
			}

			return multierror.Error{fmt.Errorf("field %q is a group of fields of type %s, it cannot store values", key, definition.Type)}
		}
	// Numbers should have been parsed as float64, otherwise they are not numbers.
	case "float", "long", "double":
		switch val := val.(type) {
		case float64:
		case json.Number:
		case string:
			if !slices.Contains(v.stringNumberFields, key) {
				return invalidTypeError()
			}
			if _, err := strconv.ParseFloat(val, 64); err != nil {
				return invalidTypeError()
			}
		default:
			return invalidTypeError()
		}
	// All other types are considered valid not blocking validation.
	default:
		return nil
	}

	return nil
}