internal/pkg/agent/transpiler/ast.go (954 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 2.0; // you may not use this file except in compliance with the Elastic License 2.0. package transpiler import ( "crypto/sha256" "encoding/base64" "fmt" "reflect" "sort" "strconv" "strings" "github.com/cespare/xxhash/v2" "github.com/elastic/elastic-agent/internal/pkg/eql" ) const ( selectorSep = "." // conditionKey is the name of the reserved key that will be computed using EQL to a boolean result. // // This makes the key "condition" inside of a dictionary a reserved name. conditionKey = "condition" ) // Selector defines a path to access an element in the Tree, currently selectors only works when the // target is a Dictionary, accessing list values are not currently supported by any methods using // selectors. type Selector = string var ( trueVal = []byte{1} falseVal = []byte{0} ) // Processors represent an attached list of processors. type Processors []map[string]interface{} // Node represents a node in the configuration Tree a Node can point to one or multiples children // nodes. type Node interface { fmt.Stringer // Find search a string in the current node. Find(string) (Node, bool) // Value returns the value of the node. Value() interface{} //Close clones the current node. Clone() Node // ShallowClone makes a shallow clone of the node. ShallowClone() Node // Hash compute a sha256 hash of the current node and recursively call any children. Hash() []byte // Hash64With recursively computes the given hash for the Node and its children Hash64With(h *xxhash.Digest) error // Vars adds to the array with the variables identified in the node. Returns the array in-case // the capacity of the array had to be changed. Vars([]string, string) []string // Apply apply the current vars, returning the new value for the node. This does not modify the original Node. Apply(*Vars) (Node, error) // Processors returns any attached processors, because of variable substitution. Processors() Processors } // AST represents a raw configuration which is purely data, only primitives are currently supported, // Int, float, string and bool. Complex are not taking into consideration. The Tree allow to define // operation on the retrieves options in a more structured way. We are using this new structure to // create filtering rules or manipulation rules to convert a configuration to another format. type AST struct { root Node } func (a *AST) String() string { return "{AST:" + a.root.String() + "}" } // Dict represents a dictionary in the Tree, where each key is a entry into an array. The Dict will // keep the ordering. type Dict struct { value []Node processors []map[string]interface{} } // NewDict creates a new dict with provided nodes. func NewDict(nodes []Node) *Dict { return NewDictWithProcessors(nodes, nil) } // NewDictWithProcessors creates a new dict with provided nodes and attached processors. func NewDictWithProcessors(nodes []Node, processors Processors) *Dict { return &Dict{nodes, processors} } // Find takes a string which is a key and try to find the elements in the associated K/V. func (d *Dict) Find(key string) (Node, bool) { for _, i := range d.value { if i == nil { continue } if i.(*Key).name == key { return i, true } } return nil, false } // Insert inserts a value into a collection. func (d *Dict) Insert(node Node) { d.value = append(d.value, node) } func (d *Dict) String() string { var sb strings.Builder for i, node := range d.value { if node == nil { continue } sb.WriteString("{") sb.WriteString(node.String()) sb.WriteString("}") if i < len(d.value)-1 { sb.WriteString(",") } } return sb.String() } // Value returns the value of dict which is a slice of node. func (d *Dict) Value() interface{} { return d.value } // Clone clones the values and return a new dictionary. func (d *Dict) Clone() Node { nodes := make([]Node, 0, len(d.value)) for _, i := range d.value { if i == nil { continue } nodes = append(nodes, i.Clone()) } return &Dict{value: nodes} } // ShallowClone makes a shallow clone of the node. func (d *Dict) ShallowClone() Node { nodes := make([]Node, 0, len(d.value)) for _, i := range d.value { if i == nil { continue } // Dict nodes are key-value pairs, and we do want to make a copy of the key here nodes = append(nodes, i.ShallowClone()) } return &Dict{value: nodes} } // Hash compute a sha256 hash of the current node and recursively call any children. func (d *Dict) Hash() []byte { h := sha256.New() for _, v := range d.value { if v == nil { continue } h.Write(v.Hash()) } return h.Sum(nil) } // Hash64With recursively computes the given hash for the Node and its children func (d *Dict) Hash64With(h *xxhash.Digest) error { for _, v := range d.value { if v == nil { continue } if err := v.Hash64With(h); err != nil { return err } } return nil } // Vars returns a list of all variables referenced in the dictionary. func (d *Dict) Vars(vars []string, defaultProvider string) []string { for _, v := range d.value { if v == nil { continue } k := v.(*Key) vars = k.Vars(vars, defaultProvider) } return vars } // Apply applies the vars to all the nodes in the dictionary. This does not modify the original dictionary. func (d *Dict) Apply(vars *Vars) (Node, error) { nodes := make([]Node, 0, len(d.value)) for _, v := range d.value { if v == nil { continue } k := v.(*Key) n, err := k.Apply(vars) if err != nil { return nil, err } if n == nil { continue } if k.name == conditionKey { b := n.Value().(*BoolVal) if !b.value { // condition failed; whole dictionary should be removed return nil, nil } // condition successful, but don't include condition in result continue } nodes = append(nodes, n) } return &Dict{nodes, nil}, nil } // Processors returns any attached processors, because of variable substitution. func (d *Dict) Processors() Processors { if d.processors != nil { return d.processors } for _, v := range d.value { if v == nil { continue } if p := v.Processors(); p != nil { return p } } return nil } // sort sorts the keys in the dictionary func (d *Dict) sort() { sort.Slice(d.value, func(i, j int) bool { return d.value[i].(*Key).name < d.value[j].(*Key).name }) } // Key represents a Key / value pair in the dictionary. type Key struct { name string value Node condition *eql.Expression } // NewKey creates a new key with provided name node pair. func NewKey(name string, val Node) *Key { return &Key{name: name, value: val} } func (k *Key) String() string { var sb strings.Builder sb.WriteString(k.name) sb.WriteString(":") if k.value == nil { sb.WriteString("nil") } else { sb.WriteString(k.value.String()) } return sb.String() } // Find finds a key in a Dictionary or a list. func (k *Key) Find(key string) (Node, bool) { switch v := k.value.(type) { case *Dict: return v.Find(key) case *List: return v.Find(key) default: return nil, false } } // Name returns the name for the key. func (k *Key) Name() string { return k.name } // Value returns the raw value. func (k *Key) Value() interface{} { return k.value } // Clone returns a clone of the current key and his embedded values. func (k *Key) Clone() Node { if k.value != nil { return &Key{name: k.name, value: k.value.Clone()} } return &Key{name: k.name, value: nil} } // ShallowClone makes a shallow clone of the node. func (k *Key) ShallowClone() Node { return &Key{name: k.name, value: k.value} } // Hash compute a sha256 hash of the current node and recursively call any children. func (k *Key) Hash() []byte { h := sha256.New() h.Write([]byte(k.name)) if k.value != nil { h.Write(k.value.Hash()) } return h.Sum(nil) } // Hash64With recursively computes the given hash for the Node and its children func (k *Key) Hash64With(h *xxhash.Digest) error { if _, err := h.WriteString(k.name); err != nil { return err } if k.value != nil { return k.value.Hash64With(h) } return nil } // Vars returns a list of all variables referenced in the value. func (k *Key) Vars(vars []string, defaultProvider string) []string { if k.value == nil { return vars } return k.value.Vars(vars, defaultProvider) } // Apply applies the vars to the value. This does not modify the original node. func (k *Key) Apply(vars *Vars) (Node, error) { if k.value == nil { return k, nil } if k.name == conditionKey { switch v := k.value.(type) { case *BoolVal: return k, nil case *StrVal: var err error if k.condition == nil { k.condition, err = eql.New(v.value) if err != nil { return nil, fmt.Errorf(`invalid condition "%s": %w`, v.value, err) } } cond, err := k.condition.Eval(vars, true) if err != nil { return nil, fmt.Errorf(`condition "%s" evaluation failed: %w`, v.value, err) } return &Key{name: k.name, value: NewBoolVal(cond)}, nil } return nil, fmt.Errorf("condition key's value must be a string; received %T", k.value) } v, err := k.value.Apply(vars) if err != nil { return nil, err } if v == nil { return nil, nil } return &Key{name: k.name, value: v}, nil } // Processors returns any attached processors, because of variable substitution. func (k *Key) Processors() Processors { if k.value != nil { return k.value.Processors() } return nil } // List represents a slice in our Tree. type List struct { value []Node processors Processors } // NewList creates a new list with provided nodes. func NewList(nodes []Node) *List { return NewListWithProcessors(nodes, nil) } // NewListWithProcessors creates a new list with provided nodes with processors attached. func NewListWithProcessors(nodes []Node, processors Processors) *List { return &List{nodes, processors} } func (l *List) String() string { var sb strings.Builder sb.WriteString("[") for i, v := range l.value { if v == nil { continue } sb.WriteString(v.String()) if i < len(l.value)-1 { sb.WriteString(",") } } sb.WriteString("]") return sb.String() } // Hash compute a sha256 hash of the current node and recursively call any children. func (l *List) Hash() []byte { h := sha256.New() for _, v := range l.value { if v == nil { continue } h.Write(v.Hash()) } return h.Sum(nil) } // Hash64With recursively computes the given hash for the Node and its children func (l *List) Hash64With(h *xxhash.Digest) error { for _, v := range l.value { if v == nil { continue } if err := v.Hash64With(h); err != nil { return err } } return nil } // Find takes an index and return the values at that index. func (l *List) Find(idx string) (Node, bool) { i, err := strconv.Atoi(idx) if err != nil { return nil, false } if l.value == nil { return nil, false } if i > len(l.value)-1 || i < 0 { return nil, false } return l.value[i], true } // Value returns the raw value. func (l *List) Value() interface{} { return l.value } // Clone clones a new list and the clone items. func (l *List) Clone() Node { nodes := make([]Node, 0, len(l.value)) for _, i := range l.value { if i == nil { continue } nodes = append(nodes, i.Clone()) } return &List{value: nodes} } // ShallowClone makes a shallow clone of the node. func (l *List) ShallowClone() Node { nodes := make([]Node, 0, len(l.value)) for _, i := range l.value { if i == nil { continue } nodes = append(nodes, i) } return &List{value: nodes} } // Vars returns a list of all variables referenced in the list. func (l *List) Vars(vars []string, defaultProvider string) []string { for _, v := range l.value { if v == nil { continue } vars = v.Vars(vars, defaultProvider) } return vars } // Apply applies the vars to all nodes in the list. This does not modify the original list. func (l *List) Apply(vars *Vars) (Node, error) { nodes := make([]Node, 0, len(l.value)) for _, v := range l.value { if v == nil { continue } n, err := v.Apply(vars) if err != nil { return nil, err } if n == nil { continue } nodes = append(nodes, n) } return NewList(nodes), nil } // Processors returns any attached processors, because of variable substitution. func (l *List) Processors() Processors { if l.processors != nil { return l.processors } for _, v := range l.value { if v == nil { continue } if p := v.Processors(); p != nil { return p } } return nil } // StrVal represents a string. type StrVal struct { value string processors Processors } // NewStrVal creates a new string value node with provided value. func NewStrVal(val string) *StrVal { return NewStrValWithProcessors(val, nil) } // NewStrValWithProcessors creates a new string value node with provided value and processors. func NewStrValWithProcessors(val string, processors Processors) *StrVal { return &StrVal{val, processors} } // Find receive a key and return false since the node is not a List or Dict. func (s *StrVal) Find(key string) (Node, bool) { return nil, false } func (s *StrVal) String() string { return s.value } // Value returns the value. func (s *StrVal) Value() interface{} { return s.value } // Clone clone the value. func (s *StrVal) Clone() Node { k := *s return &k } // ShallowClone makes a shallow clone of the node. func (s *StrVal) ShallowClone() Node { return s.Clone() } // Hash we return the byte slice of the string. func (s *StrVal) Hash() []byte { return []byte(s.value) } // Hash64With recursively computes the given hash for the Node and its children func (s *StrVal) Hash64With(h *xxhash.Digest) error { _, err := h.WriteString(s.value) return err } // Vars returns a list of all variables referenced in the string. func (s *StrVal) Vars(vars []string, defaultProvider string) []string { // errors are ignored (if there is an error determine the vars it will also error computing the policy) _, _ = replaceVars(s.value, func(variable string) (Node, Processors, bool) { vars = append(vars, variable) return nil, nil, false }, false, defaultProvider) return vars } // Apply applies the vars to the string value. This does not modify the original string. func (s *StrVal) Apply(vars *Vars) (Node, error) { return vars.Replace(s.value) } // Processors returns any linked processors that are now connected because of Apply. func (s *StrVal) Processors() Processors { return s.processors } // IntVal represents an int. type IntVal struct { value int processors Processors } // NewIntVal creates a new int value node with provided value. func NewIntVal(val int) *IntVal { return NewIntValWithProcessors(val, nil) } // NewIntValWithProcessors creates a new int value node with provided value and attached processors. func NewIntValWithProcessors(val int, processors Processors) *IntVal { return &IntVal{val, processors} } // Find receive a key and return false since the node is not a List or Dict. func (s *IntVal) Find(key string) (Node, bool) { return nil, false } func (s *IntVal) String() string { return strconv.Itoa(s.value) } // Value returns the value. func (s *IntVal) Value() interface{} { return s.value } // Clone clone the value. func (s *IntVal) Clone() Node { k := *s return &k } // ShallowClone makes a shallow clone of the node. func (s *IntVal) ShallowClone() Node { return s.Clone() } // Vars does nothing. Cannot have variable in an IntVal. func (s *IntVal) Vars(vars []string, defaultProvider string) []string { return vars } // Apply does nothing. func (s *IntVal) Apply(_ *Vars) (Node, error) { return s, nil } // Hash we convert the value into a string and return the byte slice. func (s *IntVal) Hash() []byte { return []byte(s.String()) } // Hash64With recursively computes the given hash for the Node and its children func (s *IntVal) Hash64With(h *xxhash.Digest) error { _, err := h.WriteString(s.String()) return err } // Processors returns any linked processors that are now connected because of Apply. func (s *IntVal) Processors() Processors { return s.processors } // UIntVal represents an int. type UIntVal struct { value uint64 processors Processors } // NewUIntVal creates a new uint value node with provided value. func NewUIntVal(val uint64) *UIntVal { return NewUIntValWithProcessors(val, nil) } // NewUIntValWithProcessors creates a new uint value node with provided value with processors attached. func NewUIntValWithProcessors(val uint64, processors Processors) *UIntVal { return &UIntVal{val, processors} } // Find receive a key and return false since the node is not a List or Dict. func (s *UIntVal) Find(key string) (Node, bool) { return nil, false } func (s *UIntVal) String() string { return strconv.FormatUint(s.value, 10) } // Value returns the value. func (s *UIntVal) Value() interface{} { return s.value } // Clone clone the value. func (s *UIntVal) Clone() Node { k := *s return &k } // ShallowClone makes a shallow clone of the node. func (s *UIntVal) ShallowClone() Node { return s.Clone() } // Hash we convert the value into a string and return the byte slice. func (s *UIntVal) Hash() []byte { return []byte(s.String()) } // Hash64With recursively computes the given hash for the Node and its children func (s *UIntVal) Hash64With(h *xxhash.Digest) error { _, err := h.WriteString(s.String()) return err } // Vars does nothing. Cannot have variable in an UIntVal. func (s *UIntVal) Vars(vars []string, defaultProvider string) []string { return vars } // Apply does nothing. func (s *UIntVal) Apply(_ *Vars) (Node, error) { return s, nil } // Processors returns any linked processors that are now connected because of Apply. func (s *UIntVal) Processors() Processors { return s.processors } // FloatVal represents a float. // NOTE: We will convert float32 to a float64. type FloatVal struct { value float64 processors Processors } // NewFloatVal creates a new float value node with provided value. func NewFloatVal(val float64) *FloatVal { return NewFloatValWithProcessors(val, nil) } // NewFloatValWithProcessors creates a new float value node with provided value with processors attached. func NewFloatValWithProcessors(val float64, processors Processors) *FloatVal { return &FloatVal{val, processors} } // Find receive a key and return false since the node is not a List or Dict. func (s *FloatVal) Find(key string) (Node, bool) { return nil, false } func (s *FloatVal) String() string { return fmt.Sprintf("%f", s.value) } // Value return the raw value. func (s *FloatVal) Value() interface{} { return s.value } // Clone clones the value. func (s *FloatVal) Clone() Node { k := *s return &k } // ShallowClone makes a shallow clone of the node. func (s *FloatVal) ShallowClone() Node { return s.Clone() } // Hash return a string representation of the value, we try to return the minimal precision we can. func (s *FloatVal) Hash() []byte { return []byte(s.hashString()) } // Hash64With recursively computes the given hash for the Node and its children func (s *FloatVal) Hash64With(h *xxhash.Digest) error { _, err := h.WriteString(s.hashString()) return err } // hashString returns a string representation of s suitable for hashing. func (s *FloatVal) hashString() string { return strconv.FormatFloat(s.value, 'f', -1, 64) } // Vars does nothing. Cannot have variable in an FloatVal. func (s *FloatVal) Vars(vars []string, defaultProvider string) []string { return vars } // Apply does nothing. func (s *FloatVal) Apply(_ *Vars) (Node, error) { return s, nil } // Processors returns any linked processors that are now connected because of Apply. func (s *FloatVal) Processors() Processors { return s.processors } // BoolVal represents a boolean in our Tree. type BoolVal struct { value bool processors Processors } // NewBoolVal creates a new bool value node with provided value. func NewBoolVal(val bool) *BoolVal { return NewBoolValWithProcessors(val, nil) } // NewBoolValWithProcessors creates a new bool value node with provided value with processors attached. func NewBoolValWithProcessors(val bool, processors Processors) *BoolVal { return &BoolVal{val, processors} } // Find receive a key and return false since the node is not a List or Dict. func (s *BoolVal) Find(key string) (Node, bool) { return nil, false } func (s *BoolVal) String() string { if s.value { return "true" } return "false" } // Value returns the value. func (s *BoolVal) Value() interface{} { return s.value } // Clone clones the value. func (s *BoolVal) Clone() Node { k := *s return &k } // ShallowClone makes a shallow clone of the node. func (s *BoolVal) ShallowClone() Node { return s.Clone() } // Hash returns a single byte to represent the boolean value. func (s *BoolVal) Hash() []byte { if s.value { return trueVal } return falseVal } // Hash64With recursively computes the given hash for the Node and its children func (s *BoolVal) Hash64With(h *xxhash.Digest) error { var encodedBool []byte if s.value { encodedBool = trueVal } else { encodedBool = falseVal } _, err := h.Write(encodedBool) return err } // Vars does nothing. Cannot have variable in an BoolVal. func (s *BoolVal) Vars(vars []string, defaultProvider string) []string { return vars } // Apply does nothing. func (s *BoolVal) Apply(_ *Vars) (Node, error) { return s, nil } // Processors returns any linked processors that are now connected because of Apply. func (s *BoolVal) Processors() Processors { return s.processors } // NewAST takes a map and convert it to an internal Tree, allowing us to executes rules on the // data to shape it in a different way or to filter some of the information. func NewAST(m map[string]interface{}) (*AST, error) { root, err := loadForNew(m) if err != nil { return nil, err } return &AST{root: root}, nil } func loadForNew(val interface{}) (Node, error) { root, err := load(reflect.ValueOf(val)) if err != nil { return nil, fmt.Errorf("could not parse configuration into a tree, error: %w", err) } return root, nil } func load(val reflect.Value) (Node, error) { val = lookupVal(val) switch val.Kind() { case reflect.Map: return loadMap(val) case reflect.Slice, reflect.Array: return loadSliceOrArray(val) case reflect.String: return &StrVal{value: val.Interface().(string)}, nil case reflect.Int: return &IntVal{value: val.Interface().(int)}, nil case reflect.Int64: return &IntVal{value: int(val.Interface().(int64))}, nil case reflect.Uint: return &UIntVal{value: uint64(val.Interface().(uint))}, nil case reflect.Uint64: return &UIntVal{value: val.Interface().(uint64)}, nil case reflect.Float64: return &FloatVal{value: val.Interface().(float64)}, nil case reflect.Float32: return &FloatVal{value: float64(val.Interface().(float32))}, nil case reflect.Bool: return &BoolVal{value: val.Interface().(bool)}, nil default: if val.IsNil() { return nil, nil } return nil, fmt.Errorf("unknown type %T for %+v", val.Interface(), val) } } // Accept takes a visitor and will visit each node of the Tree while calling the right methods on // the visitor. // NOTE(ph): Some operation could be refactored to use a visitor, I plan to add a checksum visitor. func (a *AST) Accept(visitor Visitor) { a.dispatch(a.root, visitor) } func (a *AST) dispatch(n Node, visitor Visitor) { switch t := n.(type) { case *Dict: visitorDict := visitor.OnDict() for _, child := range t.value { key := child.(*Key) visitorDict.OnKey(key.name) subvisitor := visitorDict.Visitor() a.dispatch(key.value, subvisitor) visitorDict.OnValue(subvisitor) } visitorDict.OnComplete() case *List: visitorList := visitor.OnList() for _, child := range t.value { subvisitor := visitorList.Visitor() a.dispatch(child, subvisitor) visitorList.OnValue(subvisitor) } visitorList.OnComplete() case *StrVal: visitor.OnStr(t.value) case *IntVal: visitor.OnInt(t.value) case *UIntVal: visitor.OnUInt(t.value) case *BoolVal: visitor.OnBool(t.value) case *FloatVal: visitor.OnFloat(t.value) } } // Clone clones the object. func (a *AST) Clone() *AST { return &AST{root: a.root.Clone()} } // ShallowClone makes a shallow clone of the node. func (a *AST) ShallowClone() *AST { return &AST{root: a.root.ShallowClone()} } // Hash calculates a hash from all the included nodes in the tree. func (a *AST) Hash() []byte { return a.root.Hash() } // Hash64With recursively computes the given hash for the Node and its children func (a *AST) Hash64With(h *xxhash.Digest) error { return a.root.Hash64With(h) } // HashStr return the calculated hash as a base64 url encoded string. func (a *AST) HashStr() string { return base64.URLEncoding.EncodeToString(a.root.Hash()) } // Equal check if two AST are equals by using the computed hash. func (a *AST) Equal(other *AST) bool { if a.root == nil || other.root == nil { return a.root == other.root } hasher := xxhash.New() _ = a.Hash64With(hasher) thisHash := hasher.Sum64() hasher.Reset() _ = other.Hash64With(hasher) otherHash := hasher.Sum64() return thisHash == otherHash } // Lookup looks for a value from the AST. // // Return type is in the native form and not in the Node types from the AST. func (a *AST) Lookup(name string) (interface{}, bool) { node, ok := Lookup(a, name) if !ok { return nil, false } _, isKey := node.(*Key) if isKey { // matched on a key, return the value node = node.Value().(Node) } m := &MapVisitor{} a.dispatch(node, m) return m.Content, true } func splitPath(s Selector) []string { if s == "" { return nil } return strings.Split(s, selectorSep) } func loadMap(val reflect.Value) (Node, error) { node := &Dict{} mapKeys := val.MapKeys() names := make([]string, 0, len(mapKeys)) for _, aKey := range mapKeys { names = append(names, aKey.Interface().(string)) } sort.Strings(names) for _, name := range names { aValue, err := load(val.MapIndex(reflect.ValueOf(name))) if err != nil { return nil, err } keys := strings.Split(name, selectorSep) if !isDictOrKey(aValue) { node.value = append(node.value, &Key{name: name, value: aValue}) continue } // get last known existing node var lastKnownKeyIdx int var knownNode Node = node for i, k := range keys { n, isDict := knownNode.Find(k) if !isDict { break } lastKnownKeyIdx = i knownNode = n } // Produce remainder restKeys := keys[lastKnownKeyIdx+1:] restDict := &Dict{} if len(restKeys) == 0 { if avd, ok := aValue.(*Dict); ok { restDict.value = avd.value } else if avd, ok := aValue.(*Key); ok { restDict.value = []Node{avd.value} } else { restDict.value = append(restDict.value, aValue) } } else { for i := len(restKeys) - 1; i >= 0; i-- { if len(restDict.value) == 0 { // this is the first one restDict.value = []Node{&Key{name: restKeys[i], value: aValue}} continue } restDict.value = []Node{&Key{name: restKeys[i], value: restDict.Clone()}} } } // Attach remainder to last known node restKey := &Key{name: keys[lastKnownKeyIdx], value: restDict} if knownNodeDict, ok := knownNode.(*Dict); ok { knownNodeDict.value = append(knownNodeDict.value, restKey) } else if knownNodeKey, ok := knownNode.(*Key); ok { dict, ok := knownNodeKey.value.(*Dict) if ok { dict.value = append(dict.value, restDict.value...) } } } return node, nil } func isDictOrKey(val Node) bool { if _, ok := val.(*Key); ok { return true } if _, ok := val.(*Dict); ok { return true } return false } func loadSliceOrArray(val reflect.Value) (Node, error) { node := &List{} for i := 0; i < val.Len(); i++ { aValue, err := load(val.Index(i)) if err != nil { return nil, err } node.value = append(node.value, aValue) } return node, nil } func lookupVal(val reflect.Value) reflect.Value { for (val.Kind() == reflect.Ptr || val.Kind() == reflect.Interface) && !val.IsNil() { val = val.Elem() } return val } func attachProcessors(node Node, processors Processors) Node { switch n := node.(type) { case *Dict: n.processors = processors case *List: n.processors = processors case *StrVal: n.processors = processors case *IntVal: n.processors = processors case *UIntVal: n.processors = processors case *FloatVal: n.processors = processors case *BoolVal: n.processors = processors } return node } // Lookup accept an AST and a selector and return the matching Node at that position. func Lookup(a *AST, selector Selector) (Node, bool) { // Be defensive and ensure that the ast is usable. if a == nil || a.root == nil { return nil, false } // Run through the graph and find matching nodes. current := a.root for _, part := range splitPath(selector) { n, ok := current.Find(part) if !ok { return nil, false } current = n } return current, true } // Insert inserts an AST into an existing AST, will return and error if the target position cannot // accept a new node. func (a *AST) Insert(b *AST, to Selector) error { return Insert(a, b.root, to) } // Insert inserts a node into an existing AST, will return and error if the target position cannot // accept a new node. func Insert(a *AST, node Node, to Selector) error { current := a.root for _, part := range splitPath(to) { n, ok := current.Find(part) if !ok { switch t := current.(type) { case *Key: switch vt := t.value.(type) { case *Dict: newNode := &Key{name: part, value: &Dict{}} vt.value = append(vt.value, newNode) vt.sort() current = newNode continue case *List: // inserting at index but array empty newNode := &Dict{} vt.value = append(vt.value, newNode) current = newNode continue default: return fmt.Errorf("expecting collection and received %T for '%s'", to, to) } case *Dict: newNode := &Key{name: part, value: &Dict{}} t.value = append(t.value, newNode) t.sort() current = newNode continue default: return fmt.Errorf("expecting Dict and received %T for '%s'", t, to) } } current = n } // Apply the current node and replace any existing elements, // that could exist after the selector. d, ok := current.(*Key) if !ok { return fmt.Errorf("expecting Key and received %T for '%s'", current, to) } switch nt := node.(type) { case *Dict: d.value = node case *List: d.value = node case *Key: // adding key to existing dictionary // should overwrite the current key if it exists dValue, ok := d.value.(*Dict) if !ok { // not a dictionary (replace it all) d.value = &Dict{[]Node{node}, nil} } else { // remove the duplicate key (if it exists) for i, key := range dValue.value { if k, ok := key.(*Key); ok { if k.name == nt.name { dValue.value[i] = dValue.value[len(dValue.value)-1] dValue.value = dValue.value[:len(dValue.value)-1] break } } } // add the new key dValue.value = append(dValue.value, nt) dValue.sort() } default: d.value = &Dict{[]Node{node}, nil} } return nil } // Map transforms the AST into a map[string]interface{} and will abort and return any errors related // to type conversion. func (a *AST) Map() (map[string]interface{}, error) { m := &MapVisitor{} a.Accept(m) mapped, ok := m.Content.(map[string]interface{}) if !ok { return nil, fmt.Errorf("could not convert to map[string]iface, type is %T", m.Content) } return mapped, nil }