diff --git a/util/data/data.go b/util/data/data.go index 6d5c8548..a5bf8747 100644 --- a/util/data/data.go +++ b/util/data/data.go @@ -16,53 +16,19 @@ package data import ( "errors" + "fmt" "reflect" + "strconv" "strings" - "time" - "github.com/ugorji/go/codec" + "github.com/abcum/surreal/util/pack" ) -// ----------------------------------------------------------------------------------------- - -var ( - // ErrOutOfBounds - Index out of bounds. - ErrOutOfBounds = errors.New("out of bounds") - - // ErrNotObj - The target is not an object type. - ErrNotObj = errors.New("not an object") - - // ErrNotArray - The target is not an array type. - ErrNotArray = errors.New("not an array") - - // ErrNotUnique - The target is not an array type. - ErrNotUnique = errors.New("not a unique array item") - - // ErrPathCollision - Creating a path failed because an element collided with an existing value. - ErrPathCollision = errors.New("encountered value collision whilst building path") - - // ErrInvalidInputObj - The input value was not a map[string]interface{}. - ErrInvalidInputObj = errors.New("invalid input object") - - // ErrInvalidInputText - The input data could not be parsed. - ErrInvalidInputText = errors.New("input text could not be parsed") - - // ErrInvalidPath - The filepath was not valid. - ErrInvalidPath = errors.New("invalid file path") - - // ErrInvalidBuffer - The input buffer contained an invalid JSON string - ErrInvalidBuffer = errors.New("input buffer contained invalid JSON") -) - -// ----------------------------------------------------------------------------------------- - // Doc holds a reference to the core data object, or a selected path. type Doc struct { data interface{} } -// ----------------------------------------------------------------------------------------- - // New creates a new data object. func New() *Doc { return &Doc{map[string]interface{}{}} @@ -73,107 +39,34 @@ func Consume(input interface{}) *Doc { return &Doc{input} } -// NewFromJSON converts a JSON byte slice into a data object. -func NewFromJSON(input []byte) *Doc { - - doc := New() - - var opt codec.JsonHandle - opt.MapType = reflect.TypeOf(map[string]interface{}(nil)) - - codec.NewDecoderBytes(input, &opt).Decode(doc.data) - - return doc - -} - -// NewFromCBOR converts a CBOR byte slice into a data object. -func NewFromCBOR(input []byte) *Doc { - - doc := New() - - var opt codec.CborHandle - opt.MapType = reflect.TypeOf(map[string]interface{}(nil)) - opt.SetInterfaceExt(reflect.TypeOf(time.Time{}), 1, extTime{}) - - codec.NewDecoderBytes(input, &opt).Decode(doc.data) - - return doc - -} - -// NewFromPACK converts a MsgPack byte slice into a data object. -func NewFromPACK(input []byte) *Doc { - - doc := New() - - var opt codec.MsgpackHandle - opt.WriteExt = true - opt.RawToString = true - opt.MapType = reflect.TypeOf(map[string]interface{}(nil)) - opt.SetBytesExt(reflect.TypeOf(time.Time{}), 1, extTime{}) - - codec.NewDecoderBytes(input, &opt).Decode(doc.data) - - return doc - -} - // Data returns the internal data object as an interface. func (d *Doc) Data() interface{} { return d.data } -// Reset resets and empties the internal data object. -func (d *Doc) Reset() { - d.data = nil +// Data returns the internal data object as an interface. +func (d *Doc) Copy() (i interface{}) { + return pack.Copy(d.data) } -// ToJSON converts the data object to a JSON byte slice. -func (d *Doc) ToJSON() (data []byte) { - - var opt codec.JsonHandle - opt.Canonical = true - opt.AsSymbols = codec.AsSymbolDefault - - codec.NewEncoderBytes(&data, &opt).Encode(d.data) +// JSON converts the data object to a JSON byte slice. +func (d *Doc) JSON() (data []byte) { + return pack.ToJSON(d.data) +} +// Encode encodes the data object to a byte slice. +func (d *Doc) Encode() (dst []byte) { + dst = pack.ToPACK(d.data) return - } -// ToCBOR converts the data object to a CBOR byte slice. -func (d *Doc) ToCBOR() (data []byte) { - - var opt codec.CborHandle - opt.Canonical = true - opt.AsSymbols = codec.AsSymbolDefault - opt.SetInterfaceExt(reflect.TypeOf(time.Time{}), 1, extTime{}) - - codec.NewEncoderBytes(&data, &opt).Encode(d.data) - - return - +// Decode decodes the byte slice into a data object. +func (d *Doc) Decode(src []byte) *Doc { + pack.FromPACK(src, d.data) + return d } -// ToPACK converts the data object to a MsgPack byte slice. -func (d *Doc) ToPACK() (data []byte) { - - var opt codec.MsgpackHandle - opt.WriteExt = true - opt.Canonical = true - opt.AsSymbols = codec.AsSymbolDefault - opt.SetBytesExt(reflect.TypeOf(time.Time{}), 1, extTime{}) - - codec.NewEncoderBytes(&data, &opt).Encode(d.data) - - return - -} - -// ----------------------------------------------------------------------------------------- - -// d.path("this", "is", "a.string", "and.something", "else") +// -------------------------------------------------------------------------------- func (d *Doc) path(path ...string) (paths []string) { for _, p := range path { @@ -182,115 +75,233 @@ func (d *Doc) path(path ...string) (paths []string) { return } -// Search - Attempt to find and return an object within the JSON structure by specifying the hierarchy -// of field names to locate the target. If the search encounters an array and has not reached the end -// target then it will iterate each object of the array for the target and return all of the results in -// a JSON array. +// -------------------------------------------------------------------------------- -// Exists - Checks whether a path exists. -func (d *Doc) Exists(path ...string) bool { - - path = d.path(path...) - - var object interface{} - - object = d.data - for target := 0; target < len(path); target++ { - if mmap, ok := object.(map[string]interface{}); ok { - object, ok = mmap[path[target]] - if !ok { - return false - } - } else { - return false - } - } - return true +// Reset empties and resets the data at the specified path. +func (d *Doc) Reset(path ...string) (*Doc, error) { + return d.Set(nil, path...) } -// Index - Attempt to find and return an object with a JSON array by specifying the index of the -// target. -func (d *Doc) Index(index int) *Doc { - if array, ok := d.Data().([]interface{}); ok { - if index >= len(array) { - return &Doc{nil} - } - return &Doc{array[index]} - } - return &Doc{nil} +// Array sets the specified path to an array. +func (d *Doc) Array(path ...string) (*Doc, error) { + return d.Set([]interface{}{}, path...) } -// Children - Return a slice of all the children of the array. This also works for objects, however, -// the children returned for an object will NOT be in order and you lose the names of the returned -// objects this way. -func (d *Doc) Children() ([]*Doc, error) { - if array, ok := d.Data().([]interface{}); ok { - children := make([]*Doc, len(array)) - for i := 0; i < len(array); i++ { - children[i] = &Doc{array[i]} - } - return children, nil - } - return nil, ErrNotArray +// Object sets the specified path to an object. +func (d *Doc) Object(path ...string) (*Doc, error) { + return d.Set(map[string]interface{}{}, path...) } -// ChildrenMap - Return a map of all the children of an object. -func (d *Doc) ChildrenMap() (map[string]*Doc, error) { - if mmap, ok := d.Data().(map[string]interface{}); ok { - children := map[string]*Doc{} - for name, obj := range mmap { - children[name] = &Doc{obj} - } - return children, nil - } - return nil, ErrNotObj -} +// -------------------------------------------------------------------------------- -// ----------------------------------------------------------------------------------------- - -// Search - Attempt to find and return an object within the JSON structure by specifying the hierarchy -// of field names to locate the target. If the search encounters an array and has not reached the end -// target then it will iterate each object of the array for the target and return all of the results in -// a JSON array. -func (d *Doc) Get(path ...string) *Doc { - - path = d.path(path...) - - var object interface{} - - object = d.data - for target := 0; target < len(path); target++ { - if mmap, ok := object.(map[string]interface{}); ok { - object = mmap[path[target]] - } else if marray, ok := object.([]interface{}); ok { - tmpArray := []interface{}{} - for _, val := range marray { - tmp := &Doc{val} - res := tmp.Get(path[target:]...).Data() - if res != nil { - tmpArray = append(tmpArray, res) - } - } - if len(tmpArray) == 0 { - return &Doc{nil} - } - return &Doc{tmpArray} - } else { - return &Doc{nil} - } - } - return &Doc{object} -} - -// Set the value at the specified path if it does not exist. +// New sets the value at the specified path if it does not exist. func (d *Doc) New(value interface{}, path ...string) (*Doc, error) { if !d.Exists(path...) { return d.Set(value, path...) } - return nil, nil + return d.Get(path...), nil } -// Set the value at the specified path. +// Iff sets the value at the specified path if it is not nil, or deleted it. +func (d *Doc) Iff(value interface{}, path ...string) (*Doc, error) { + if value != nil { + return d.Set(value, path...) + } + return &Doc{nil}, d.Del(path...) +} + +// -------------------------------------------------------------------------------- + +// Exists checks whether the specified path exists. +func (d *Doc) Exists(path ...string) bool { + + path = d.path(path...) + + // If the value found at the current + // path part is undefined, then just + // return false immediately + + if d.data == nil { + return false + } + + // Define the temporary object so + // that we can loop over and traverse + // down the path parts of the data + + object := d.data + + // Loop over each part of the path + // whilst detecting if the data at + // the current path is an {} or [] + + for k, p := range path { + + // If the value found at the current + // path part is an object, then move + // to the next part of the path + + if m, ok := object.(map[string]interface{}); ok { + if object, ok = m[p]; !ok { + return false + } + continue + } + + // If the value found at the current + // path part is an array, then perform + // the query on the specified items + + if a, ok := object.([]interface{}); ok { + + var i int + var e error + + if p == "*" { + e = errors.New("") + } else if p == "first" { + i = 0 + } else if p == "last" { + i = len(a) - 1 + } else { + i, e = strconv.Atoi(p) + } + + // If the path part is a numeric index + // then run the query on the specified + // index of the current data array + + if e == nil { + if 0 == len(a) || i >= len(a) { + return false + } + return Consume(a[i]).Exists(path[k+1:]...) + } + + // If the path part is an asterisk + // then run the query on all of the + // items in the current data array + + if p == "*" { + + for _, v := range a { + if Consume(v).Exists(path[k+1:]...) { + return true + } + } + + } + + } + + return false + + } + + return true + +} + +// -------------------------------------------------------------------------------- + +// Get gets the value or values at a specified path. +func (d *Doc) Get(path ...string) *Doc { + + path = d.path(path...) + + // If the value found at the current + // path part is undefined, then just + // return false immediately + + if d.data == nil { + return &Doc{nil} + } + + // Define the temporary object so + // that we can loop over and traverse + // down the path parts of the data + + object := d.data + + // Loop over each part of the path + // whilst detecting if the data at + // the current path is an {} or [] + + for k, p := range path { + + // If the value found at the current + // path part is an object, then move + // to the next part of the path + + if m, ok := object.(map[string]interface{}); ok { + object = m[p] + continue + } + + // If the value found at the current + // path part is an array, then perform + // the query on the specified items + + if a, ok := object.([]interface{}); ok { + + var i int + var e error + + if p == "*" { + e = errors.New("") + } else if p == "first" { + i = 0 + } else if p == "last" { + i = len(a) - 1 + } else if p == "length" { + return &Doc{len(a)} + } else { + i, e = strconv.Atoi(p) + } + + // If the path part is a numeric index + // then run the query on the specified + // index of the current data array + + if e == nil { + if 0 == len(a) || i >= len(a) { + return &Doc{nil} + } + return Consume(a[i]).Get(path[k+1:]...) + } + + // If the path part is an asterisk + // then run the query on all of the + // items in the current data array + + if p == "*" { + + out := []interface{}{} + + for _, v := range a { + res := Consume(v).Get(path[k+1:]...) + if res.data != nil { + out = append(out, res.data) + } + } + + return &Doc{out} + + } + + } + + return &Doc{nil} + + } + + return &Doc{object} + +} + +// -------------------------------------------------------------------------------- + +// Set sets the value or values at a specified path. func (d *Doc) Set(value interface{}, path ...string) (*Doc, error) { path = d.path(path...) @@ -299,158 +310,372 @@ func (d *Doc) Set(value interface{}, path ...string) (*Doc, error) { d.data = value return d, nil } - var object interface{} + + // If the value found at the current + // path part is undefined, then ensure + // that it is an object + if d.data == nil { d.data = map[string]interface{}{} } - object = d.data - for target := 0; target < len(path); target++ { - if mmap, ok := object.(map[string]interface{}); ok { - if target == len(path)-1 { - mmap[path[target]] = value - } else if mmap[path[target]] == nil { - mmap[path[target]] = map[string]interface{}{} + + // Define the temporary object so + // that we can loop over and traverse + // down the path parts of the data + + object := d.data + + // Loop over each part of the path + // whilst detecting if the data at + // the current path is an {} or [] + + for k, p := range path { + + // If the value found at the current + // path part is an object, then move + // to the next part of the path + + if m, ok := object.(map[string]interface{}); ok { + if k == len(path)-1 { + m[p] = value + } else if m[p] == nil { + m[p] = map[string]interface{}{} } - object = mmap[path[target]] - } else { - return &Doc{nil}, ErrPathCollision + object = m[p] } + + // If the value found at the current + // path part is an array, then perform + // the query on the specified items + + if a, ok := object.([]interface{}); ok { + + var i int + var e error + + if p == "*" { + e = errors.New("") + } else if p == "first" { + i = 0 + } else if p == "last" { + i = len(a) - 1 + } else { + i, e = strconv.Atoi(p) + } + + // If the path part is a numeric index + // then run the query on the specified + // index of the current data array + + if e == nil { + + if 0 == len(a) || i >= len(a) { + return &Doc{nil}, fmt.Errorf("No item with index %d in array, using path %s", i, path) + } + + if k == len(path)-1 { + a[i] = value + object = a[i] + } else { + return Consume(a[i]).Set(value, path[k+1:]...) + } + + } + + // If the path part is an asterisk + // then run the query on all of the + // items in the current data array + + if p == "*" { + + out := []interface{}{} + + for i, _ := range a { + + if k == len(path)-1 { + a[i] = value + out = append(out, a[i]) + } else { + res, _ := Consume(a[i]).Set(value, path[k+1:]...) + if res.data != nil { + out = append(out, res.data) + } + } + + } + + return &Doc{out}, nil + + } + + } + } + return &Doc{object}, nil + } -// Del - Delete an element at a JSON path, an error is returned if the element does not exist. +// -------------------------------------------------------------------------------- + +// Del deletes the value or values at a specified path. func (d *Doc) Del(path ...string) error { path = d.path(path...) - var object interface{} + // If the value found at the current + // path part is undefined, then return + // a not an object error if d.data == nil { - return ErrNotObj + return fmt.Errorf("Item is not an object") } - object = d.data - for target := 0; target < len(path); target++ { - if mmap, ok := object.(map[string]interface{}); ok { - if target == len(path)-1 { - delete(mmap, path[target]) - } else if mmap[path[target]] == nil { - return ErrNotObj + + // Define the temporary object so + // that we can loop over and traverse + // down the path parts of the data + + object := d.data + + // Loop over each part of the path + // whilst detecting if the data at + // the current path is an {} or [] + + for k, p := range path { + + // If the value found at the current + // path part is an object, then move + // to the next part of the path + + if m, ok := object.(map[string]interface{}); ok { + if k == len(path)-1 { + delete(m, p) + } else if m[p] == nil { + return fmt.Errorf("Item at path %s is not an object", path) } - object = mmap[path[target]] - } else { - return ErrNotObj + object = m[p] } + + // If the value found at the current + // path part is an array, then perform + // the query on the specified items + + if a, ok := object.([]interface{}); ok { + + var i int + var e error + + if p == "*" { + e = errors.New("") + } else if p == "first" { + i = 0 + } else if p == "last" { + i = len(a) - 1 + } else { + i, e = strconv.Atoi(p) + } + + // If the path part is a numeric index + // then run the query on the specified + // index of the current data array + + if e == nil { + + if 0 == len(a) || i >= len(a) { + return fmt.Errorf("No item with index %d in array, using path %s", i, path) + } + + if k == len(path)-1 { + copy(a[i:], a[i+1:]) + a[len(a)-1] = nil + a = a[:len(a)-1] + d.Set(a, path[:len(path)-1]...) + } else { + return Consume(a[i]).Del(path[k+1:]...) + } + + } + + // If the path part is an asterisk + // then run the query on all of the + // items in the current data array + + if p == "*" { + + for i := len(a) - 1; i >= 0; i-- { + + if k == len(path)-1 { + copy(a[i:], a[i+1:]) + a[len(a)-1] = nil + a = a[:len(a)-1] + d.Set(a, path[:len(path)-1]...) + } else { + Consume(a[i]).Del(path[k+1:]...) + } + + } + + } + + } + } + return nil + } -// SetIndex - Set a value of an array element based on the index. -func (d *Doc) SetIndex(value interface{}, index int) (*Doc, error) { - if array, ok := d.Data().([]interface{}); ok { - if index >= len(array) { - return &Doc{nil}, ErrOutOfBounds +// -------------------------------------------------------------------------------- + +// ArrayDel appends an item or an array of items to an array at the specified path. +func (d *Doc) ArrayAdd(value interface{}, path ...string) (*Doc, error) { + + a, ok := d.Get(path...).Data().([]interface{}) + if !ok { + return &Doc{nil}, fmt.Errorf("Not an array") + } + + if values, ok := value.([]interface{}); ok { + outer: + for _, value := range values { + for _, v := range a { + if reflect.DeepEqual(v, value) { + continue outer + } + } + a = append(a, value) } - array[index] = value - return &Doc{array[index]}, nil - } - return &Doc{nil}, ErrNotArray -} - -// Array - Create a new JSON array at a path. Returns an error if the path contains a collision with -// a non object type. -func (d *Doc) Array(path ...string) (*Doc, error) { - return d.Set([]interface{}{}, path...) -} - -// Object - Create a new JSON object at a path. Returns an error if the path contains a collision with -// a non object type. -func (d *Doc) Object(path ...string) (*Doc, error) { - return d.Set(map[string]interface{}{}, path...) -} - -// ----------------------------------------------------------------------------------------- - -// ArrayAdd - Append a unique value onto a JSON array. -func (d *Doc) ArrayAdd(value interface{}, path ...string) error { - array, ok := d.Get(path...).Data().([]interface{}) - if !ok { - return ErrNotArray - } - for _, item := range array { - if reflect.DeepEqual(item, value) { - return ErrNotUnique - } - } - array = append(array, value) - _, err := d.Set(array, path...) - return err -} - -// ArrayDel - Append a unique value onto a JSON array. -func (d *Doc) ArrayDel(value interface{}, path ...string) error { - array, ok := d.Get(path...).Data().([]interface{}) - if !ok { - return ErrNotArray - } - for i, item := range array { - if reflect.DeepEqual(item, value) { - array = append(array[:i], array[i+1:]...) - break - } - } - _, err := d.Set(array, path...) - return err -} - -// ArrayAppend - Append a value onto a JSON array. -func (d *Doc) ArrayAppend(value interface{}, path ...string) error { - array, ok := d.Get(path...).Data().([]interface{}) - if !ok { - return ErrNotArray - } - array = append(array, value) - _, err := d.Set(array, path...) - return err -} - -// ArrayRemove - Remove an element from a JSON array. -func (d *Doc) ArrayRemove(index int, path ...string) error { - if index < 0 { - return ErrOutOfBounds - } - array, ok := d.Get(path...).Data().([]interface{}) - if !ok { - return ErrNotArray - } - if index < len(array) { - array = append(array[:index], array[index+1:]...) } else { - return ErrOutOfBounds + for _, v := range a { + if reflect.DeepEqual(v, value) { + return nil, nil + } + } + a = append(a, value) } - _, err := d.Set(array, path...) - return err + + return d.Set(a, path...) + } -// ArrayElement - Access an element from a JSON array. -func (d *Doc) ArrayElement(index int, path ...string) (*Doc, error) { - if index < 0 { - return &Doc{nil}, ErrOutOfBounds - } - array, ok := d.Get(path...).Data().([]interface{}) +// ArrayDel deletes an item or an array of items from an array at the specified path. +func (d *Doc) ArrayDel(value interface{}, path ...string) (*Doc, error) { + + a, ok := d.Get(path...).Data().([]interface{}) if !ok { - return &Doc{nil}, ErrNotArray + return &Doc{nil}, fmt.Errorf("Not an array") } - if index < len(array) { - return &Doc{array[index]}, nil + + if values, ok := value.([]interface{}); ok { + for _, value := range values { + for i := len(a) - 1; i >= 0; i-- { + v := a[i] + if reflect.DeepEqual(v, value) { + copy(a[i:], a[i+1:]) + a[len(a)-1] = nil + a = a[:len(a)-1] + } + } + } + } else { + for i := len(a) - 1; i >= 0; i-- { + v := a[i] + if reflect.DeepEqual(v, value) { + copy(a[i:], a[i+1:]) + a[len(a)-1] = nil + a = a[:len(a)-1] + } + } } - return &Doc{nil}, ErrOutOfBounds + + return d.Set(a, path...) + } -// ArrayCount - Count the number of elements in a JSON array. -func (d *Doc) ArrayCount(path ...string) (int, error) { - if array, ok := d.Get(path...).Data().([]interface{}); ok { - return len(array), nil +// -------------------------------------------------------------------------------- + +// Contains checks whether the value exists within the array at the specified path. +func (d *Doc) Contains(value interface{}, path ...string) bool { + + a, ok := d.Get(path...).Data().([]interface{}) + if !ok { + return false } - return 0, ErrNotArray + + for _, v := range a { + if reflect.DeepEqual(v, value) { + return true + } + } + + return false + +} + +// -------------------------------------------------------------------------------- + +// Inc increments an item, or appends an item to an array at the specified path. +func (d *Doc) Inc(value interface{}, path ...string) (*Doc, error) { + + switch cur := d.Get(path...).Data().(type) { + case nil: + switch inc := value.(type) { + case int64: + return d.Set(0+inc, path...) + case float64: + return d.Set(0+inc, path...) + } + case int64: + switch inc := value.(type) { + case int64: + return d.Set(cur+inc, path...) + case float64: + return d.Set(float64(cur)+inc, path...) + } + case float64: + switch inc := value.(type) { + case int64: + return d.Set(cur+float64(inc), path...) + case float64: + return d.Set(cur+inc, path...) + } + case []interface{}: + return d.ArrayAdd(value, path...) + } + + return &Doc{nil}, fmt.Errorf("Not possible to increment.") + +} + +// Dec decrements an item, or removes an item from an array at the specified path. +func (d *Doc) Dec(value interface{}, path ...string) (*Doc, error) { + + switch cur := d.Get(path...).Data().(type) { + case nil: + switch inc := value.(type) { + case int64: + return d.Set(0-inc, path...) + case float64: + return d.Set(0-inc, path...) + } + case int64: + switch inc := value.(type) { + case int64: + return d.Set(cur-inc, path...) + case float64: + return d.Set(float64(cur)-inc, path...) + } + case float64: + switch inc := value.(type) { + case int64: + return d.Set(cur-float64(inc), path...) + case float64: + return d.Set(cur-inc, path...) + } + case []interface{}: + return d.ArrayDel(value, path...) + } + + return &Doc{nil}, fmt.Errorf("Not possible to decrement.") + } diff --git a/util/data/data_test.go b/util/data/data_test.go new file mode 100644 index 00000000..595556d5 --- /dev/null +++ b/util/data/data_test.go @@ -0,0 +1,783 @@ +// Copyright © 2016 Abcum Ltd +// +// Licensed 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 data + +import ( + "testing" + "time" + + . "github.com/smartystreets/goconvey/convey" +) + +func TestConversion(t *testing.T) { + + Convey("Can encode and decode", t, func() { + doc := Consume(map[string]interface{}{ + "bool": true, + "time": time.Now().UTC(), + }) + enc := doc.Encode() + dec := doc.Decode(enc) + So(doc, ShouldResemble, dec) + So(doc.JSON(), ShouldResemble, dec.JSON()) + }) + +} + +func TestOperations(t *testing.T) { + + // ---------------------------------------------------------------------- + // Ability to set and del nil + // ---------------------------------------------------------------------- + + Convey("Can get nil", t, func() { + doc := Consume(nil) + So(doc, ShouldHaveSameTypeAs, &Doc{}) + So(doc.Exists("nil"), ShouldBeFalse) + So(doc.Get("nil").Data(), ShouldEqual, nil) + }) + + Convey("Can set nil", t, func() { + doc := Consume(nil) + set, err := doc.Set("OK", "nil") + So(err, ShouldBeNil) + So(doc, ShouldHaveSameTypeAs, &Doc{}) + So(set.Data(), ShouldResemble, "OK") + So(doc.Get("nil").Data(), ShouldEqual, "OK") + }) + + Convey("Can't del nil", t, func() { + doc := Consume(nil) + err := doc.Del("nil") + So(err, ShouldNotBeNil) + }) + + // ---------------------------------------------------------------------- + // Ability to attempt new() + // ---------------------------------------------------------------------- + + Convey("Can attempt to use New()", t, func() { + doc := New() + one, err := doc.New("OK", "item") + So(err, ShouldBeNil) + So(one.Data(), ShouldEqual, "OK") + So(doc.Exists("item"), ShouldBeTrue) + So(doc.Get("item").Data(), ShouldEqual, "OK") + two, err := doc.New("NOT OK", "item") + So(err, ShouldBeNil) + So(two.Data(), ShouldEqual, "OK") + So(doc.Exists("item"), ShouldBeTrue) + So(doc.Get("item").Data(), ShouldEqual, "OK") + }) + + // ---------------------------------------------------------------------- + // Ability to attempt iff() + // ---------------------------------------------------------------------- + + Convey("Can attempt to use Iff()", t, func() { + doc := New() + one, err := doc.Iff("OK", "item") + So(err, ShouldBeNil) + So(one.Data(), ShouldEqual, "OK") + So(doc.Exists("item"), ShouldBeTrue) + So(doc.Get("item").Data(), ShouldEqual, "OK") + two, err := doc.Iff(nil, "item") + So(err, ShouldBeNil) + So(two.Data(), ShouldEqual, nil) + So(doc.Exists("item"), ShouldBeFalse) + So(doc.Get("item").Data(), ShouldEqual, nil) + }) + + // ---------------------------------------------------------------------- + // Ability to set and get array + // ---------------------------------------------------------------------- + + Convey("Can set base array", t, func() { + doc := New() + obj, err := doc.Array("array") + So(err, ShouldBeNil) + So(obj, ShouldHaveSameTypeAs, &Doc{}) + So(doc.Get("array").Data(), ShouldResemble, []interface{}{}) + }) + + // ---------------------------------------------------------------------- + // Ability to set and get object + // ---------------------------------------------------------------------- + + Convey("Can set base object", t, func() { + doc := New() + obj, err := doc.Object("object") + So(err, ShouldBeNil) + So(obj, ShouldHaveSameTypeAs, &Doc{}) + So(doc.Get("object").Data(), ShouldResemble, map[string]interface{}{}) + }) + + // ---------------------------------------------------------------------- + // Ability to set and get basic types + // ---------------------------------------------------------------------- + + Convey("Can set and get basic number", t, func() { + doc := New() + obj, err := doc.Set(1, "number") + So(err, ShouldBeNil) + So(obj, ShouldHaveSameTypeAs, &Doc{}) + So(doc.Get("number").Data(), ShouldResemble, 1) + }) + + Convey("Can set and get basic string", t, func() { + doc := New() + obj, err := doc.Set("a", "string") + So(err, ShouldBeNil) + So(obj, ShouldHaveSameTypeAs, &Doc{}) + So(doc.Get("string").Data(), ShouldResemble, "a") + }) + + Convey("Can set and get basic array", t, func() { + doc := New() + obj, err := doc.Set([]interface{}{1, 2, 3}, "array") + So(err, ShouldBeNil) + So(obj, ShouldHaveSameTypeAs, &Doc{}) + So(doc.Get("array").Data(), ShouldResemble, []interface{}{1, 2, 3}) + }) + + Convey("Can set and get basic object", t, func() { + doc := New() + obj, err := doc.Set(map[string]interface{}{"test": true}, "object") + So(err, ShouldBeNil) + So(obj, ShouldHaveSameTypeAs, &Doc{}) + So(doc.Get("object").Data(), ShouldResemble, map[string]interface{}{"test": true}) + }) + + // ---------------------------------------------------------------------- + // Ability to set and get basic embedded types + // ---------------------------------------------------------------------- + + Convey("Can set and get basic embedded number", t, func() { + doc := New() + obj, err := doc.Set(1, "sub.number") + So(err, ShouldBeNil) + So(obj, ShouldHaveSameTypeAs, &Doc{}) + So(doc.Get("sub.number").Data(), ShouldResemble, 1) + }) + + Convey("Can set and get basic embedded string", t, func() { + doc := New() + obj, err := doc.Set("a", "sub.string") + So(err, ShouldBeNil) + So(obj, ShouldHaveSameTypeAs, &Doc{}) + So(doc.Get("sub.string").Data(), ShouldResemble, "a") + }) + + Convey("Can set and get basic embedded array", t, func() { + doc := New() + obj, err := doc.Set([]interface{}{1, 2, 3}, "sub.array") + So(err, ShouldBeNil) + So(obj, ShouldHaveSameTypeAs, &Doc{}) + So(doc.Get("sub.array").Data(), ShouldResemble, []interface{}{1, 2, 3}) + }) + + Convey("Can set and get basic embedded object", t, func() { + doc := New() + obj, err := doc.Set(map[string]interface{}{"test": true}, "sub.object") + So(err, ShouldBeNil) + So(obj, ShouldHaveSameTypeAs, &Doc{}) + So(doc.Get("sub.object").Data(), ShouldResemble, map[string]interface{}{"test": true}) + }) + + // ---------------------------------------------------------------------- + // Ability to inc and dec basic types + // ---------------------------------------------------------------------- + + Convey("Can inc basic number", t, func() { + doc := New() + obj, err := doc.Inc(int64(100), "number") + So(err, ShouldBeNil) + So(obj, ShouldHaveSameTypeAs, &Doc{}) + So(doc.Get("number").Data(), ShouldResemble, int64(100)) + }) + + Convey("Can dec basic number", t, func() { + doc := New() + obj, err := doc.Dec(int64(100), "number") + So(err, ShouldBeNil) + So(obj, ShouldHaveSameTypeAs, &Doc{}) + So(doc.Get("number").Data(), ShouldResemble, int64(-100)) + }) + + Convey("Can inc basic double", t, func() { + doc := New() + obj, err := doc.Inc(float64(100), "double") + So(err, ShouldBeNil) + So(obj, ShouldHaveSameTypeAs, &Doc{}) + So(doc.Get("double").Data(), ShouldResemble, float64(100)) + }) + + Convey("Can dec basic double", t, func() { + doc := New() + obj, err := doc.Dec(float64(100), "double") + So(err, ShouldBeNil) + So(obj, ShouldHaveSameTypeAs, &Doc{}) + So(doc.Get("double").Data(), ShouldResemble, float64(-100)) + }) + + // ---------------------------------------------------------------------------------------------------- + + doc := New() + + obj := map[string]interface{}{ + "bool": true, + "number": 10, + "string": "s", + "tags": []interface{}{ + "Hot", + }, + "object": map[string]interface{}{ + "enabled": false, + }, + "arrays": []interface{}{ + map[string]interface{}{ + "id": 1, + "one": "one", + "selected": map[string]interface{}{ + "city": "London", + }, + "addresses": []interface{}{ + map[string]interface{}{ + "city": "London", + }, + map[string]interface{}{ + "city": "New York", + }, + }, + }, + map[string]interface{}{ + "id": 2, + "two": "two", + "selected": map[string]interface{}{ + "city": "Tonbridge", + }, + "addresses": []interface{}{ + map[string]interface{}{ + "city": "Paris", + }, + map[string]interface{}{ + "city": "Tonbridge", + }, + }, + }, + }, + } + + // ---------------------------------------------------------------------------------------------------- + + Convey("Can't del undefined", t, func() { + err := doc.Del("the.item") + So(err, ShouldNotBeNil) + }) + + Convey("Can set object", t, func() { + def, err := doc.Set(obj, "the.item") + So(err, ShouldBeNil) + So(def, ShouldHaveSameTypeAs, &Doc{}) + }) + + // ---------------------------------------------------------------------------------------------------- + + Convey("Does unset item exist", t, func() { + So(doc.Exists("the.none"), ShouldBeFalse) + }) + + Convey("Does item exist", t, func() { + So(doc.Exists("the.item"), ShouldBeTrue) + }) + + Convey("Does array item exist", t, func() { + So(doc.Exists("the.item.arrays.0.id"), ShouldBeTrue) + So(doc.Exists("the.item.arrays.first.id"), ShouldBeTrue) + }) + + Convey("Does array item exist", t, func() { + So(doc.Exists("the.item.arrays.1.id"), ShouldBeTrue) + So(doc.Exists("the.item.arrays.last.id"), ShouldBeTrue) + }) + + Convey("Does out of bounds array item exist", t, func() { + So(doc.Exists("the.item.arrays.5.id"), ShouldBeFalse) + }) + + Convey("Does unset array item exist", t, func() { + So(doc.Exists("the.item.arrays.0.none"), ShouldBeFalse) + }) + + Convey("Does incorrectly embedded array item exist", t, func() { + So(doc.Exists("the.item.arrays.0.id.arggghh"), ShouldBeFalse) + }) + + Convey("Does incorrectly embedded object item exist", t, func() { + So(doc.Exists("the.item.object.enabled.arggghh"), ShouldBeFalse) + }) + + Convey("Does multi array item exist", t, func() { + So(doc.Exists("the.item.arrays.*.id"), ShouldBeTrue) + }) + + // ---------------------------------------------------------------------------------------------------- + + // ---------------------------------------------------------------------------------------------------- + + Convey("Can get unset item", t, func() { + So(doc.Get("the.item.none").Data(), ShouldResemble, nil) + So(doc.Get("the.item.none.arggghh").Data(), ShouldResemble, nil) + }) + + Convey("Can set unset item", t, func() { + set, err := doc.Set("OK", "the.item.none") + So(err, ShouldBeNil) + So(set.Data(), ShouldResemble, "OK") + So(doc.Get("the.item.none").Data(), ShouldResemble, "OK") + }) + + Convey("Can del unset item", t, func() { + err := doc.Del("the.item.none") + So(err, ShouldBeNil) + So(doc.Get("the.item.none").Data(), ShouldResemble, nil) + }) + + // ---------------------------------------------------------------------------------------------------- + + Convey("Can get basic bool", t, func() { + So(doc.Get("the.item.bool").Data(), ShouldBeTrue) + }) + + Convey("Can set basic bool", t, func() { + set, err := doc.Set(false, "the.item.bool") + So(err, ShouldBeNil) + So(set.Data(), ShouldBeFalse) + So(doc.Get("the.item.bool").Data(), ShouldBeFalse) + }) + + // ---------------------------------------------------------------------------------------------------- + + Convey("Can get basic number", t, func() { + So(doc.Get("the.item.number").Data(), ShouldResemble, 10) + }) + + Convey("Can set basic number", t, func() { + set, err := doc.Set(20, "the.item.number") + So(err, ShouldBeNil) + So(set.Data(), ShouldResemble, 20) + So(doc.Get("the.item.number").Data(), ShouldResemble, 20) + }) + + // ---------------------------------------------------------------------------------------------------- + + Convey("Can get basic string", t, func() { + So(doc.Get("the.item.string").Data(), ShouldResemble, "s") + }) + + Convey("Can set basic string", t, func() { + set, err := doc.Set("t", "the.item.string") + So(err, ShouldBeNil) + So(set.Data(), ShouldResemble, "t") + So(doc.Get("the.item.string").Data(), ShouldResemble, "t") + }) + + // ---------------------------------------------------------------------------------------------------- + + Convey("Can inc += 1", t, func() { + obj, err := doc.Inc(int64(1), "the.item.tester") + So(err, ShouldBeNil) + So(obj, ShouldHaveSameTypeAs, &Doc{}) + So(doc.Get("the.item.tester").Data(), ShouldResemble, int64(1)) + }) + + Convey("Can inc += 4", t, func() { + obj, err := doc.Inc(int64(4), "the.item.tester") + So(err, ShouldBeNil) + So(obj, ShouldHaveSameTypeAs, &Doc{}) + So(doc.Get("the.item.tester").Data(), ShouldResemble, int64(5)) + }) + + Convey("Can inc += 3.87659", t, func() { + obj, err := doc.Inc(float64(3.87659), "the.item.tester") + So(err, ShouldBeNil) + So(obj, ShouldHaveSameTypeAs, &Doc{}) + So(doc.Get("the.item.tester").Data(), ShouldResemble, float64(8.87659)) + }) + + Convey("Can inc += 1.12341", t, func() { + obj, err := doc.Inc(float64(1.12341), "the.item.tester") + So(err, ShouldBeNil) + So(obj, ShouldHaveSameTypeAs, &Doc{}) + So(doc.Get("the.item.tester").Data(), ShouldResemble, float64(10)) + }) + + Convey("Can inc += 5", t, func() { + obj, err := doc.Inc(int64(5), "the.item.tester") + So(err, ShouldBeNil) + So(obj, ShouldHaveSameTypeAs, &Doc{}) + So(doc.Get("the.item.tester").Data(), ShouldResemble, float64(15)) + }) + + // ---------------------------------------------------------------------------------------------------- + + Convey("Can reset tester", t, func() { + obj, err := doc.Set(int64(15), "the.item.tester") + So(err, ShouldBeNil) + So(obj, ShouldHaveSameTypeAs, &Doc{}) + So(doc.Get("the.item.tester").Data(), ShouldResemble, int64(15)) + }) + + // ---------------------------------------------------------------------------------------------------- + + Convey("Can dec -= 5", t, func() { + obj, err := doc.Dec(int64(5), "the.item.tester") + So(err, ShouldBeNil) + So(obj, ShouldHaveSameTypeAs, &Doc{}) + So(doc.Get("the.item.tester").Data(), ShouldResemble, int64(10)) + }) + + Convey("Can dec -= 1.12341", t, func() { + obj, err := doc.Dec(float64(1.12341), "the.item.tester") + So(err, ShouldBeNil) + So(obj, ShouldHaveSameTypeAs, &Doc{}) + So(doc.Get("the.item.tester").Data(), ShouldResemble, float64(8.87659)) + }) + + Convey("Can dec -= 3.87659", t, func() { + obj, err := doc.Dec(float64(3.87659), "the.item.tester") + So(err, ShouldBeNil) + So(obj, ShouldHaveSameTypeAs, &Doc{}) + So(doc.Get("the.item.tester").Data(), ShouldResemble, float64(5)) + }) + + Convey("Can dec -= 4", t, func() { + obj, err := doc.Dec(int64(4), "the.item.tester") + So(err, ShouldBeNil) + So(obj, ShouldHaveSameTypeAs, &Doc{}) + So(doc.Get("the.item.tester").Data(), ShouldResemble, float64(1)) + }) + + Convey("Can dec -= 1", t, func() { + obj, err := doc.Dec(int64(1), "the.item.tester") + So(err, ShouldBeNil) + So(obj, ShouldHaveSameTypeAs, &Doc{}) + So(doc.Get("the.item.tester").Data(), ShouldResemble, float64(0)) + }) + + // ---------------------------------------------------------------------------------------------------- + + Convey("Can reset bool", t, func() { + obj, err := doc.Set(true, "the.item.bool") + So(err, ShouldBeNil) + So(obj, ShouldHaveSameTypeAs, &Doc{}) + So(doc.Get("the.item.bool").Data(), ShouldBeTrue) + }) + + Convey("Can't inc non incable item", t, func() { + obj, err := doc.Inc(int64(1), "the.item.bool") + So(err, ShouldNotBeNil) + So(obj, ShouldHaveSameTypeAs, &Doc{}) + So(doc.Get("the.item.bool").Data(), ShouldBeTrue) + }) + + Convey("Can't dec non decable item", t, func() { + obj, err := doc.Dec(int64(1), "the.item.bool") + So(err, ShouldNotBeNil) + So(obj, ShouldHaveSameTypeAs, &Doc{}) + So(doc.Get("the.item.bool").Data(), ShouldBeTrue) + }) + + // ---------------------------------------------------------------------------------------------------- + + Convey("Can get array", t, func() { + So(doc.Get("the.item.tags").Data(), ShouldResemble, []interface{}{"Hot"}) + So(doc.Get("the.item.tags.length").Data(), ShouldResemble, 1) + }) + + Convey("Can set array", t, func() { + set, err := doc.Set([]interface{}{"Hot", "Humid", "Sticky", "Warm"}, "the.item.tags") + So(err, ShouldBeNil) + So(set.Data(), ShouldResemble, []interface{}{"Hot", "Humid", "Sticky", "Warm"}) + So(doc.Get("the.item.tags").Data(), ShouldResemble, []interface{}{"Hot", "Humid", "Sticky", "Warm"}) + So(doc.Get("the.item.tags.length").Data(), ShouldResemble, 4) + }) + + Convey("Can see if array contains valid", t, func() { + So(doc.Contains("Hot", "the.item.tags"), ShouldBeTrue) + }) + + Convey("Can see if array contains invalid", t, func() { + So(doc.Contains("Cold", "the.item.tags"), ShouldBeFalse) + }) + + Convey("Can add single to array", t, func() { + _, err := doc.Inc("Sunny", "the.item.tags") + So(err, ShouldBeNil) + So(doc.Get("the.item.tags").Data(), ShouldResemble, []interface{}{"Hot", "Humid", "Sticky", "Warm", "Sunny"}) + So(doc.Get("the.item.tags.length").Data(), ShouldResemble, 5) + }) + + Convey("Can add duplicate to array", t, func() { + _, err := doc.Inc("Sunny", "the.item.tags") + So(err, ShouldBeNil) + So(doc.Get("the.item.tags").Data(), ShouldResemble, []interface{}{"Hot", "Humid", "Sticky", "Warm", "Sunny"}) + So(doc.Get("the.item.tags.length").Data(), ShouldResemble, 5) + }) + + Convey("Can add multiple to array", t, func() { + _, err := doc.Inc([]interface{}{"Sunny", "Snowy", "Icy"}, "the.item.tags") + So(err, ShouldBeNil) + So(doc.Get("the.item.tags").Data(), ShouldResemble, []interface{}{"Hot", "Humid", "Sticky", "Warm", "Sunny", "Snowy", "Icy"}) + So(doc.Get("the.item.tags.length").Data(), ShouldResemble, 7) + }) + + Convey("Can del single from array", t, func() { + _, err := doc.Dec("Sunny", "the.item.tags") + So(err, ShouldBeNil) + So(doc.Get("the.item.tags").Data(), ShouldResemble, []interface{}{"Hot", "Humid", "Sticky", "Warm", "Snowy", "Icy"}) + So(doc.Get("the.item.tags.length").Data(), ShouldResemble, 6) + }) + + Convey("Can del multiple from array", t, func() { + _, err := doc.Dec([]interface{}{"Snowy", "Icy"}, "the.item.tags") + So(err, ShouldBeNil) + So(doc.Get("the.item.tags").Data(), ShouldResemble, []interface{}{"Hot", "Humid", "Sticky", "Warm"}) + So(doc.Get("the.item.tags.length").Data(), ShouldResemble, 4) + }) + + Convey("Can get array → *", t, func() { + So(doc.Get("the.item.tags.*").Data(), ShouldResemble, []interface{}{"Hot", "Humid", "Sticky", "Warm"}) + So(doc.Get("the.item.tags.length").Data(), ShouldResemble, 4) + }) + + Convey("Can del array → 2", t, func() { + err := doc.Del("the.item.tags.2") + So(err, ShouldBeNil) + So(doc.Get("the.item.tags").Data(), ShouldResemble, []interface{}{"Hot", "Humid", "Warm"}) + So(doc.Get("the.item.tags.length").Data(), ShouldResemble, 3) + }) + + Convey("Can't del array → 5", t, func() { + err := doc.Del("the.item.tags.5") + So(err, ShouldNotBeNil) + So(doc.Get("the.item.tags").Data(), ShouldResemble, []interface{}{"Hot", "Humid", "Warm"}) + So(doc.Get("the.item.tags.length").Data(), ShouldResemble, 3) + }) + + Convey("Can set array → 0", t, func() { + set, err := doc.Set("Tepid", "the.item.tags.0") + So(err, ShouldBeNil) + So(set.Data(), ShouldResemble, "Tepid") + So(doc.Get("the.item.tags").Data(), ShouldResemble, []interface{}{"Tepid", "Humid", "Warm"}) + So(doc.Get("the.item.tags.length").Data(), ShouldResemble, 3) + }) + + Convey("Can't set array → 5", t, func() { + set, err := doc.Set("Other", "the.item.tags.5") + So(err, ShouldNotBeNil) + So(set.Data(), ShouldResemble, nil) + So(doc.Get("the.item.tags").Data(), ShouldResemble, []interface{}{"Tepid", "Humid", "Warm"}) + So(doc.Get("the.item.tags.length").Data(), ShouldResemble, 3) + }) + + Convey("Can set array → first", t, func() { + set, err := doc.Set("Test1", "the.item.tags.first") + So(err, ShouldBeNil) + So(set.Data(), ShouldResemble, "Test1") + So(doc.Get("the.item.tags").Data(), ShouldResemble, []interface{}{"Test1", "Humid", "Warm"}) + So(doc.Get("the.item.tags.0").Data(), ShouldResemble, doc.Get("the.item.tags.first").Data()) + So(doc.Get("the.item.tags.length").Data(), ShouldResemble, 3) + }) + + Convey("Can set array → last", t, func() { + set, err := doc.Set("Test2", "the.item.tags.last") + So(err, ShouldBeNil) + So(set.Data(), ShouldResemble, "Test2") + So(doc.Get("the.item.tags").Data(), ShouldResemble, []interface{}{"Test1", "Humid", "Test2"}) + So(doc.Get("the.item.tags.2").Data(), ShouldResemble, doc.Get("the.item.tags.last").Data()) + So(doc.Get("the.item.tags.length").Data(), ShouldResemble, 3) + }) + + Convey("Can del array → first", t, func() { + err := doc.Del("the.item.tags.first") + So(err, ShouldBeNil) + So(doc.Get("the.item.tags").Data(), ShouldResemble, []interface{}{"Humid", "Test2"}) + So(doc.Get("the.item.tags.0").Data(), ShouldResemble, doc.Get("the.item.tags.first").Data()) + So(doc.Get("the.item.tags.length").Data(), ShouldResemble, 2) + }) + + Convey("Can del array → last", t, func() { + err := doc.Del("the.item.tags.last") + So(err, ShouldBeNil) + So(doc.Get("the.item.tags").Data(), ShouldResemble, []interface{}{"Humid"}) + So(doc.Get("the.item.tags.0").Data(), ShouldResemble, doc.Get("the.item.tags.last").Data()) + So(doc.Get("the.item.tags.length").Data(), ShouldResemble, 1) + }) + + Convey("Can set array → *", t, func() { + set, err := doc.Set("Unknown", "the.item.tags.*") + So(err, ShouldBeNil) + So(set.Data(), ShouldResemble, []interface{}{"Unknown"}) + So(doc.Get("the.item.tags").Data(), ShouldResemble, []interface{}{"Unknown"}) + So(doc.Get("the.item.tags.length").Data(), ShouldResemble, 1) + }) + + Convey("Can del array → *", t, func() { + err := doc.Del("the.item.tags.*") + So(err, ShouldBeNil) + So(doc.Get("the.item.tags").Data(), ShouldResemble, []interface{}{}) + So(doc.Get("the.item.tags.length").Data(), ShouldResemble, 0) + }) + + Convey("Can del array", t, func() { + err := doc.Del("the.item.tags") + So(err, ShouldBeNil) + }) + + Convey("Can't add to not array", t, func() { + _, err := doc.ArrayAdd("None", "the.item.tags") + So(err, ShouldNotBeNil) + }) + + Convey("Can't del from not array", t, func() { + _, err := doc.ArrayDel("None", "the.item.tags") + So(err, ShouldNotBeNil) + }) + + Convey("Can't see if array contains", t, func() { + So(doc.Contains("None", "the.item.tags"), ShouldBeFalse) + }) + + // ---------------------------------------------------------------------------------------------------- + + Convey("Can get object → key", t, func() { + So(doc.Get("the.item.object.enabled").Data(), ShouldBeFalse) + }) + + Convey("Can set object → key", t, func() { + set, err := doc.Set(true, "the.item.object.enabled") + So(err, ShouldBeNil) + So(set.Data(), ShouldBeTrue) + So(doc.Get("the.item.object.enabled").Data(), ShouldBeTrue) + }) + + Convey("Can del object → key", t, func() { + err := doc.Del("the.item.object.enabled") + So(err, ShouldBeNil) + So(doc.Exists("the.item.object.enabled"), ShouldBeFalse) + So(doc.Get("the.item.object.enabled").Data(), ShouldResemble, nil) + }) + + // ---------------------------------------------------------------------------------------------------- + + Convey("Can get array → * → key", t, func() { + So(doc.Get("the.item.arrays.*.id").Data(), ShouldResemble, []interface{}{1, 2}) + }) + + Convey("Can set array → * → key", t, func() { + set, err := doc.Set("ID", "the.item.arrays.*.id") + So(err, ShouldBeNil) + So(set.Data(), ShouldResemble, []interface{}{"ID", "ID"}) + So(doc.Get("the.item.arrays.*.id").Data(), ShouldResemble, []interface{}{"ID", "ID"}) + }) + + Convey("Can set array → 0 → key", t, func() { + set, err := doc.Set("ID1", "the.item.arrays.0.id") + So(err, ShouldBeNil) + So(set.Data(), ShouldResemble, "ID1") + So(doc.Get("the.item.arrays.0.id").Data(), ShouldResemble, "ID1") + So(doc.Get("the.item.arrays.*.id").Data(), ShouldResemble, []interface{}{"ID1", "ID"}) + }) + + Convey("Can set array → 1 → key", t, func() { + set, err := doc.Set("ID2", "the.item.arrays.1.id") + So(err, ShouldBeNil) + So(set.Data(), ShouldResemble, "ID2") + So(doc.Get("the.item.arrays.1.id").Data(), ShouldResemble, "ID2") + So(doc.Get("the.item.arrays.*.id").Data(), ShouldResemble, []interface{}{"ID1", "ID2"}) + }) + + Convey("Can del array → 0 → key", t, func() { + err := doc.Del("the.item.arrays.0.id") + So(err, ShouldBeNil) + So(doc.Get("the.item.arrays.0.id").Data(), ShouldResemble, nil) + So(doc.Get("the.item.arrays.*.id").Data(), ShouldResemble, []interface{}{"ID2"}) + }) + + Convey("Can del array → * → key", t, func() { + err := doc.Del("the.item.arrays.*.id") + So(err, ShouldBeNil) + So(doc.Get("the.item.arrays.0.id").Data(), ShouldResemble, nil) + So(doc.Get("the.item.arrays.*.id").Data(), ShouldResemble, []interface{}{}) + }) + + // ---------------------------------------------------------------------------------------------------- + + Convey("Can get object → key", t, func() { + So(doc.Get("the.item.arrays.*.one").Data(), ShouldResemble, []interface{}{"one"}) + }) + + Convey("Can get object → key", t, func() { + So(doc.Get("the.item.arrays.*.two").Data(), ShouldResemble, []interface{}{"two"}) + }) + + // ---------------------------------------------------------------------------------------------------- + + Convey("Can get array → * → object → !", t, func() { + So(doc.Get("the.item.arrays.*.selected.none").Data(), ShouldResemble, []interface{}{}) + }) + + Convey("Can set array → * → object → !", t, func() { + set, err := doc.Set("OK", "the.item.arrays.*.selected.none") + So(err, ShouldBeNil) + So(set.Data(), ShouldResemble, []interface{}{"OK", "OK"}) + So(doc.Get("the.item.arrays.*.selected.none").Data(), ShouldResemble, []interface{}{"OK", "OK"}) + }) + + Convey("Can get array → * → object → key", t, func() { + So(doc.Get("the.item.arrays.*.selected.city").Data(), ShouldResemble, []interface{}{"London", "Tonbridge"}) + }) + + Convey("Can get array → 0 → arrays → 0 → key", t, func() { + So(doc.Get("the.item.arrays.0.addresses.0.city").Data(), ShouldResemble, "London") + }) + + Convey("Can get array → * → arrays → 0 → key", t, func() { + So(doc.Get("the.item.arrays.*.addresses.0.city").Data(), ShouldResemble, []interface{}{"London", "Paris"}) + }) + + Convey("Can get array → * → arrays → * → key", t, func() { + So(doc.Get("the.item.arrays.*.addresses.*.city").Data(), ShouldResemble, []interface{}{[]interface{}{"London", "New York"}, []interface{}{"Paris", "Tonbridge"}}) + }) + + Convey("Can get array → ! → arrays → 0 → key", t, func() { + So(doc.Get("the.item.arrays.5.addresses.0.city").Data(), ShouldResemble, nil) + }) + + // ---------------------------------------------------------------------------------------------------- + + Convey("Can copy object", t, func() { + So(doc.Copy(), ShouldResemble, doc.Data()) + }) + + // ---------------------------------------------------------------------------------------------------- + + Convey("Can reset object", t, func() { + _, err := doc.Reset() + So(err, ShouldBeNil) + }) + +} diff --git a/util/data/time.go b/util/data/time.go deleted file mode 100644 index 2caa88ca..00000000 --- a/util/data/time.go +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright © 2016 Abcum Ltd -// -// Licensed 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 data - -import ( - "time" -) - -type extTime struct{} - -func (x extTime) ReadExt(dst interface{}, src []byte) { - dst.(*time.Time).UnmarshalBinary(src) -} - -func (x extTime) WriteExt(src interface{}) (dst []byte) { - switch obj := src.(type) { - case time.Time: - dst, _ = obj.MarshalBinary() - case *time.Time: - dst, _ = obj.MarshalBinary() - } - return -} - -func (x extTime) UpdateExt(dest interface{}, v interface{}) { - tt := dest.(*time.Time) - switch v2 := v.(type) { - case int64: - *tt = time.Unix(0, v2).UTC() - case uint64: - *tt = time.Unix(0, int64(v2)).UTC() - } -} - -func (x extTime) ConvertExt(v interface{}) interface{} { - switch v2 := v.(type) { - case time.Time: - return v2.UTC().UnixNano() - case *time.Time: - return v2.UTC().UnixNano() - default: - return nil - } -}