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
}