internal/fields/exceptionfields.go (110 lines of code) (raw):

// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one // or more contributor license agreements. Licensed under the Elastic License; // you may not use this file except in compliance with the Elastic License. package fields import ( "slices" "strings" "github.com/elastic/elastic-package/internal/common" "github.com/elastic/elastic-package/internal/logger" ) // ListExceptionFields validates the provided document as common.MapStr. func (v *Validator) ListExceptionFields(body common.MapStr) []string { return v.listExceptionFieldsMapElement("", body) } func (v *Validator) listExceptionFieldsMapElement(root string, elem common.MapStr) []string { all := []string{} for name, val := range elem { key := strings.TrimLeft(root+"."+name, ".") switch val := val.(type) { case []map[string]any: for _, m := range val { fields := v.listExceptionFieldsMapElement(key, m) all = append(all, fields...) } case map[string]any: if isFieldTypeFlattened(key, v.Schema) { // Do not traverse into objects with flattened data types // because the entire object is mapped as a single field. continue } fields := v.listExceptionFieldsMapElement(key, val) all = append(all, fields...) default: if skipLeafOfObject(root, name, v.specVersion, v.Schema) { logger.Tracef("Skip validating leaf of object (spec %q): %q", v.specVersion, key) all = append(all, key) // Till some versions we skip some validations on leaf of objects, check if it is the case. break } fields := v.listExceptionFieldsScalarElement(key, val) all = append(all, fields...) } } slices.Sort(all) all = slices.Compact(all) return all } func (v *Validator) listExceptionFieldsScalarElement(key string, val any) []string { if key == "" { return nil // root key is always valid } definition := FindElementDefinition(key, v.Schema) if definition == nil { return nil } return findElements(key, *definition, val, v.parseExceptionField) } // findElements visits a function for each element in the given value if // it is an array. If it is not an array, it calls the function with it. func findElements(key string, definition FieldDefinition, val any, fn func(string, FieldDefinition, any) []string) []string { arr, isArray := val.([]any) if !isArray { return fn(key, definition, val) } var all []string for _, element := range arr { fields := fn(key, definition, element) all = append(all, fields...) } return all } // parseExceptionField performs validations on individual values of each element. func (v *Validator) parseExceptionField(key string, definition FieldDefinition, val any) []string { switch definition.Type { case "constant_keyword": case "keyword", "text": case "date": case "float", "long", "double": case "ip": case "array": if v.specVersion.LessThan(semver2_0_0) { logger.Tracef("Skip validating field of type array with spec < 2.0.0 (key %q type %q spec %q)", key, definition.Type, v.specVersion) return []string{key} } return nil // 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) { logger.Tracef("Skip validating object (map[string]any) in package spec < 3.0.1 (key %q type %q spec %q)", key, definition.Type, v.specVersion) return []string{key} } return nil 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) { logger.Tracef("Skip validating object ([]any) because spec < 3.0.1 (key %q type %q spec %q)", key, definition.Type, v.specVersion) return []string{key} } return nil case nil: // The document contains a null, let's consider this like an empty array. logger.Tracef("Skip validating object empty array because spec < 3.0.1 (key %q type %q spec %q)", key, definition.Type, v.specVersion) return []string{key} 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.parseExceptionField(key, definition, val) case definition.Type == "object" && definition.ObjectType == "": // Legacy mapping, ambiguous definition not allowed by recent versions of the spec, ignore it. logger.Tracef("Skip legacy mapping: object field without \"object_type\" parameter: %q", key) return []string{key} } return nil } default: return nil } return nil }