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:]) }