1
0
mirror of https://github.com/MontFerret/ferret.git synced 2024-12-16 11:37:36 +02:00
ferret/pkg/runtime/values/object.go
3timeslazy f87fe1e669 refactoring: .GetIn (#353)
* sync with MontFerret/ferret

* fix --param handling

When params is converted to map it uses strings.Split,
which slices a string into all substrings separated by :.

* implement GetIn for values.Object

* implement GetIn for values.Array

* rewrite GetIn because values.Object and values.Array implement core.Getter now

* fix bug when GetIn return nil instead of None

* add tests for Object and Array .GetIn

* add GetIn comment and remove 'len(byPath)' check

* fix GetIn comment
2019-08-20 22:00:15 -04:00

297 lines
4.9 KiB
Go

package values
import (
"context"
"encoding/binary"
"encoding/json"
"hash/fnv"
"sort"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values/types"
)
type (
ObjectPredicate = func(value core.Value, key string) bool
ObjectProperty struct {
key string
value core.Value
}
Object struct {
value map[string]core.Value
}
)
func NewObjectProperty(name string, value core.Value) *ObjectProperty {
return &ObjectProperty{name, value}
}
func NewObject() *Object {
return &Object{make(map[string]core.Value)}
}
func NewObjectWith(props ...*ObjectProperty) *Object {
obj := NewObject()
for _, prop := range props {
obj.value[prop.key] = prop.value
}
return obj
}
func (t *Object) MarshalJSON() ([]byte, error) {
return json.Marshal(t.value)
}
func (t *Object) Type() core.Type {
return types.Object
}
func (t *Object) String() string {
marshaled, err := t.MarshalJSON()
if err != nil {
return "{}"
}
return string(marshaled)
}
// Compare compares the source object with other core.Value
// The behavior of the Compare is similar
// to the comparison of objects in ArangoDB
func (t *Object) Compare(other core.Value) int64 {
if other.Type() == t.Type() {
other := other.(*Object)
if t.Length() == 0 && other.Length() == 0 {
return 0
}
if t.Length() < other.Length() {
return -1
}
if t.Length() > other.Length() {
return 1
}
var res int64
tKeys := make([]string, 0, len(t.value))
for k := range t.value {
tKeys = append(tKeys, k)
}
sortedT := sort.StringSlice(tKeys)
sortedT.Sort()
otherKeys := make([]string, 0, other.Length())
other.ForEach(func(value core.Value, k string) bool {
otherKeys = append(otherKeys, k)
return true
})
sortedOther := sort.StringSlice(otherKeys)
sortedOther.Sort()
var tVal, otherVal core.Value
var tKey, otherKey string
for i := 0; i < len(t.value) && res == 0; i++ {
tKey, otherKey = sortedT[i], sortedOther[i]
if tKey == otherKey {
tVal, _ = t.Get(NewString(tKey))
otherVal, _ = other.Get(NewString(tKey))
res = tVal.Compare(otherVal)
continue
}
if tKey < otherKey {
res = 1
} else {
res = -1
}
break
}
return res
}
return types.Compare(types.Object, other.Type())
}
func (t *Object) Unwrap() interface{} {
obj := make(map[string]interface{})
for key, val := range t.value {
obj[key] = val.Unwrap()
}
return obj
}
func (t *Object) Hash() uint64 {
h := fnv.New64a()
h.Write([]byte(t.Type().String()))
h.Write([]byte(":"))
h.Write([]byte("{"))
keys := make([]string, 0, len(t.value))
for key := range t.value {
keys = append(keys, key)
}
// order does not really matter
// but it will give us a consistent hash sum
sort.Strings(keys)
endIndex := len(keys) - 1
for idx, key := range keys {
h.Write([]byte(key))
h.Write([]byte(":"))
el := t.value[key]
bytes := make([]byte, 8)
binary.LittleEndian.PutUint64(bytes, el.Hash())
h.Write(bytes)
if idx != endIndex {
h.Write([]byte(","))
}
}
h.Write([]byte("}"))
return h.Sum64()
}
func (t *Object) Copy() core.Value {
c := NewObject()
for k, v := range t.value {
c.Set(NewString(k), v)
}
return c
}
func (t *Object) Length() Int {
return Int(len(t.value))
}
func (t *Object) Keys() []String {
keys := make([]String, 0, len(t.value))
for k := range t.value {
keys = append(keys, NewString(k))
}
return keys
}
func (t *Object) Values() []core.Value {
keys := make([]core.Value, 0, len(t.value))
for _, v := range t.value {
keys = append(keys, v)
}
return keys
}
func (t *Object) ForEach(predicate ObjectPredicate) {
for key, val := range t.value {
if !predicate(val, key) {
break
}
}
}
func (t *Object) MustGet(key String) core.Value {
val, _ := t.Get(key)
return val
}
func (t *Object) Get(key String) (core.Value, Boolean) {
val, found := t.value[string(key)]
if found {
return val, NewBoolean(found)
}
return None, NewBoolean(found)
}
func (t *Object) Set(key String, value core.Value) {
if value != nil {
t.value[string(key)] = value
} else {
t.value[string(key)] = None
}
}
func (t *Object) Remove(key String) {
delete(t.value, string(key))
}
func (t *Object) Clone() core.Cloneable {
cloned := NewObject()
var value core.Value
var keyString String
for key := range t.value {
keyString = NewString(key)
value, _ = t.Get(keyString)
cloneable, ok := value.(core.Cloneable)
if ok {
value = cloneable.Clone()
}
cloned.Set(keyString, value)
}
return cloned
}
func (t *Object) GetIn(ctx context.Context, path []core.Value) (core.Value, error) {
if len(path) == 0 {
return None, nil
}
if typ := path[0].Type(); typ != types.String {
return None, core.TypeError(typ, types.String)
}
first, _ := t.Get(path[0].(String))
if len(path) == 1 {
return first, nil
}
getter, ok := first.(core.Getter)
if !ok {
return None, core.TypeError(
first.Type(),
core.NewType("Getter"),
)
}
return getter.GetIn(ctx, path[1:])
}