youtubebeat/vendor/github.com/elastic/beats/libbeat/common/mapval/path.go

194 lines
4.9 KiB
Go

// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package mapval
import (
"fmt"
"reflect"
"regexp"
"strconv"
"strings"
"github.com/elastic/beats/libbeat/common"
)
// PathComponentType indicates the type of PathComponent.
type PathComponentType int
const (
// PCMapKey is the Type for map keys.
PCMapKey PathComponentType = 1 + iota
// PCSliceIdx is the Type for slice indices.
PCSliceIdx
)
func (pct PathComponentType) String() string {
if pct == PCMapKey {
return "map"
} else if pct == PCSliceIdx {
return "slice"
} else {
// This should never happen, but we don't want to return an
// error since that would unnecessarily complicate the fluid API
return "<unknown>"
}
}
// PathComponent structs represent one breadcrumb in a Path.
type PathComponent struct {
Type PathComponentType // One of PCMapKey or PCSliceIdx
Key string // Populated for maps
Index int // Populated for slices
}
func (pc PathComponent) String() string {
if pc.Type == PCSliceIdx {
return fmt.Sprintf("[%d]", pc.Index)
}
return pc.Key
}
// Path represents the path within a nested set of maps.
type Path []PathComponent
// ExtendSlice is used to add a new PathComponent of the PCSliceIdx type.
func (p Path) ExtendSlice(index int) Path {
return p.extend(
PathComponent{PCSliceIdx, "", index},
)
}
// ExtendMap adds a new PathComponent of the PCMapKey type.
func (p Path) ExtendMap(key string) Path {
return p.extend(
PathComponent{PCMapKey, key, -1},
)
}
func (p Path) extend(pc PathComponent) Path {
out := make(Path, len(p)+1)
copy(out, p)
out[len(p)] = pc
return out
}
// Concat combines two paths into a new path without modifying any existing paths.
func (p Path) Concat(other Path) Path {
out := make(Path, 0, len(p)+len(other))
out = append(out, p...)
return append(out, other...)
}
func (p Path) String() string {
out := make([]string, len(p))
for idx, pc := range p {
out[idx] = pc.String()
}
return strings.Join(out, ".")
}
// Last returns a pointer to the last PathComponent in this path. If the path empty,
// a nil pointer is returned.
func (p Path) Last() *PathComponent {
idx := len(p) - 1
if idx < 0 {
return nil
}
return &p[len(p)-1]
}
// GetFrom takes a map and fetches the given path from it.
func (p Path) GetFrom(m common.MapStr) (value interface{}, exists bool) {
value = m
exists = true
for _, pc := range p {
rt := reflect.TypeOf(value)
switch rt.Kind() {
case reflect.Map:
converted := interfaceToMapStr(value)
value, exists = converted[pc.Key]
case reflect.Slice:
converted := sliceToSliceOfInterfaces(value)
if pc.Index < len(converted) {
exists = true
value = converted[pc.Index]
} else {
exists = false
value = nil
}
default:
// If this case has been reached this means the expected type, say a map,
// is actually something else, like a string or an array. In this case we
// simply say the value doesn't exist. From a practical perspective this is
// the right behavior since it will cause validation to fail.
return nil, false
}
if exists == false {
return nil, exists
}
}
return value, exists
}
var arrMatcher = regexp.MustCompile("\\[(\\d+)\\]")
// InvalidPathString is the error type returned from unparseable paths.
type InvalidPathString string
func (ps InvalidPathString) Error() string {
return fmt.Sprintf("Invalid path Path: %#v", ps)
}
// ParsePath parses a path of form key.[0].otherKey.[1] into a Path object.
func ParsePath(in string) (p Path, err error) {
keyParts := strings.Split(in, ".")
p = make(Path, len(keyParts))
for idx, part := range keyParts {
r := arrMatcher.FindStringSubmatch(part)
pc := PathComponent{Index: -1}
if len(r) > 0 {
pc.Type = PCSliceIdx
// Cannot fail, validated by regexp already
pc.Index, err = strconv.Atoi(r[1])
if err != nil {
return p, err
}
} else if len(part) > 0 {
pc.Type = PCMapKey
pc.Key = part
} else {
return p, InvalidPathString(in)
}
p[idx] = pc
}
return p, nil
}
// MustParsePath is a convenience method for parsing paths that have been previously validated
func MustParsePath(in string) Path {
out, err := ParsePath(in)
if err != nil {
panic(err)
}
return out
}