From 376eb0be6662cbda1ac553421c04814a6fa64f75 Mon Sep 17 00:00:00 2001 From: Stepan Pyzhov <32341341+spyzhov@users.noreply.github.com> Date: Sat, 6 Nov 2021 21:55:46 +0100 Subject: [PATCH] #35 implemented `node.Set` and `node.SetNode` methods (#40) * Implemented `node.Set` and `node.SetNode` methods * Added `go1.17.x` version to test * Updated release version * Added default results for `(*Node)(nil)` methods --- .github/workflows/main.yml | 1 + .travis.yml | 1 + README.md | 2 +- buffer.go | 35 ++++- cmd/ajson/main.go | 2 +- errors.go | 46 +++++- errors_test.go | 55 +++++++ jsonpath.go | 6 + jsonpath_test.go | 14 +- node.go | 121 +++++++++++++-- node_mutations.go | 108 ++++++++++--- node_mutations_test.go | 310 +++++++++++++++++++++++++++++++++++++ node_test.go | 189 +++++++++++++++++++++- 13 files changed, 844 insertions(+), 46 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 9f84044..b9232fc 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -25,6 +25,7 @@ jobs: # Optional: golangci-lint command line arguments. # args: --issues-exit-code=0 + args: -v # Optional: show only new issues if it's a pull request. The default value is `false`. # only-new-issues: true diff --git a/.travis.yml b/.travis.yml index 94e94d1..b00c1fe 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,6 +13,7 @@ go: - 1.14.x - 1.15.x - 1.16.x + - 1.17.x env: - GO111MODULE=on diff --git a/README.md b/README.md index 27a5ea8..2c6f414 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ func main() { You can download `ajson` cli from the [release page](https://github.com/spyzhov/ajson/releases), or install from the source: ```shell script -go get github.com/spyzhov/ajson/cmd/ajson@v0.5.0 +go get github.com/spyzhov/ajson/cmd/ajson@v0.6.0 ``` Usage: diff --git a/buffer.go b/buffer.go index 6778b1e..8355b99 100644 --- a/buffer.go +++ b/buffer.go @@ -1,9 +1,10 @@ package ajson import ( - . "github.com/spyzhov/ajson/internal" "io" "strings" + + . "github.com/spyzhov/ajson/internal" ) type buffer struct { @@ -773,3 +774,35 @@ func str(key string) (string, bool) { // } // return unquote(bString, quotes) } + +func numeric2float64(value interface{}) (result float64, err error) { + switch typed := value.(type) { + case float64: + result = typed + case float32: + result = float64(typed) + case int: + result = float64(typed) + case int8: + result = float64(typed) + case int16: + result = float64(typed) + case int32: + result = float64(typed) + case int64: + result = float64(typed) + case uint: + result = float64(typed) + case uint8: + result = float64(typed) + case uint16: + result = float64(typed) + case uint32: + result = float64(typed) + case uint64: + result = float64(typed) + default: + err = unsupportedType(value) + } + return +} diff --git a/cmd/ajson/main.go b/cmd/ajson/main.go index fd4ff94..9d160cb 100644 --- a/cmd/ajson/main.go +++ b/cmd/ajson/main.go @@ -12,7 +12,7 @@ import ( "github.com/spyzhov/ajson" ) -var version = "v0.4.2" +var version = "v0.6.0" func usage() { text := `` diff --git a/errors.go b/errors.go index ff2e4dc..523d0ae 100644 --- a/errors.go +++ b/errors.go @@ -8,6 +8,7 @@ type Error struct { Index int Char byte Message string + Value interface{} } // ErrorType is container for reflection type of error @@ -24,34 +25,61 @@ const ( WrongRequest // Unparsed means that json structure wasn't parsed yet Unparsed + // UnsupportedType means that wrong type was given + UnsupportedType ) func errorSymbol(b *buffer) error { - c, err := b.current() + symbol, err := b.current() if err != nil { - c = 0 + symbol = 0 + } + return Error{ + Type: WrongSymbol, + Index: b.index, + Char: symbol, } - return Error{Type: WrongSymbol, Index: b.index, Char: c} } func errorAt(index int, symbol byte) error { - return Error{Type: WrongSymbol, Index: index, Char: symbol} + return Error{ + Type: WrongSymbol, + Index: index, + Char: symbol, + } } func errorEOF(b *buffer) error { - return Error{Type: UnexpectedEOF, Index: b.index} + return Error{ + Type: UnexpectedEOF, + Index: b.index, + } } func errorType() error { - return Error{Type: WrongType} + return Error{ + Type: WrongType, + } +} + +func unsupportedType(value interface{}) error { + return Error{ + Type: UnsupportedType, + Value: value, + } } func errorUnparsed() error { - return Error{Type: Unparsed} + return Error{ + Type: Unparsed, + } } func errorRequest(format string, args ...interface{}) error { - return Error{Type: WrongRequest, Message: fmt.Sprintf(format, args...)} + return Error{ + Type: WrongRequest, + Message: fmt.Sprintf(format, args...), + } } // Error interface implementation @@ -63,6 +91,8 @@ func (err Error) Error() string { return "unexpected end of file" case WrongType: return "wrong type of Node" + case UnsupportedType: + return fmt.Sprintf("unsupported type was given: '%T'", err.Value) case Unparsed: return "not parsed yet" case WrongRequest: diff --git a/errors_test.go b/errors_test.go index 7f44a88..4a1e369 100644 --- a/errors_test.go +++ b/errors_test.go @@ -28,3 +28,58 @@ func TestError_Error(t *testing.T) { }) } } + +func Test_unsupportedType(t *testing.T) { + f := 1. + type args struct { + value interface{} + } + tests := []struct { + name string + args args + result string + }{ + { + name: "nil", + args: args{ + value: nil, + }, + result: "unsupported type was given: ''", + }, + { + name: "int", + args: args{ + value: int(10), + }, + result: "unsupported type was given: 'int'", + }, + { + name: "float64", + args: args{ + value: float64(10), + }, + result: "unsupported type was given: 'float64'", + }, + { + name: "*float64", + args: args{ + value: &f, + }, + result: "unsupported type was given: '*float64'", + }, + { + name: "[]float64", + args: args{ + value: []float64{1.}, + }, + result: "unsupported type was given: '[]float64'", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := unsupportedType(tt.args.value); err.Error() != tt.result { + t.Errorf("unsupportedType() error = %v, wantErr %v", err, tt.result) + } + }) + } +} diff --git a/jsonpath.go b/jsonpath.go index 935bbc7..7f81b14 100644 --- a/jsonpath.go +++ b/jsonpath.go @@ -314,6 +314,9 @@ func ParseJSONPath(path string) (result []string, err error) { // result, _ := ApplyJSONPath(node, commands) // func ApplyJSONPath(node *Node, commands []string) (result []*Node, err error) { + if node == nil { + return nil, nil + } result = make([]*Node, 0) var ( temporary []*Node @@ -585,6 +588,9 @@ func Eval(node *Node, cmd string) (result *Node, err error) { } func eval(node *Node, expression rpn, cmd string) (result *Node, err error) { + if node == nil { + return nil, nil + } var ( stack = make([]*Node, 0) slice []*Node diff --git a/jsonpath_test.go b/jsonpath_test.go index 5408dd7..a623288 100644 --- a/jsonpath_test.go +++ b/jsonpath_test.go @@ -687,6 +687,13 @@ func TestEval(t *testing.T) { expected: NumericNode("", 18), wantErr: false, }, + { + name: "nil", + root: nil, + eval: "round(avg($..price)+pi)", + expected: nil, + wantErr: false, + }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { @@ -698,11 +705,14 @@ func TestEval(t *testing.T) { if test.wantErr { return } - if result == nil { + if test.expected != nil && result == nil { t.Errorf("Eval() result in nil") return } + if test.expected == nil && result == nil { + return + } if ok, err := result.Eq(test.expected); !ok { t.Errorf("result.Eq(): wrong result:\nExpected: %#+v\nActual: %#+v", test.expected, result.value.Load()) } else if err != nil { @@ -1431,7 +1441,7 @@ func TestApplyJSONPath(t *testing.T) { node: nil, commands: nil, }, - wantResult: make([]*Node, 0), + wantResult: nil, wantErr: false, }, { diff --git a/node.go b/node.go index b6be1eb..310cf64 100644 --- a/node.go +++ b/node.go @@ -1,7 +1,6 @@ package ajson import ( - "fmt" "math" "sort" "strconv" @@ -192,12 +191,18 @@ func valueNode(parent *Node, key string, _type NodeType, value interface{}) (cur // Parent returns link to the parent of current node, nil for root func (n *Node) Parent() *Node { + if n == nil { + return nil + } return n.parent } // Source returns slice of bytes, which was identified to be current node func (n *Node) Source() []byte { - if n.ready() && !n.dirty { + if n == nil { + return nil + } + if n.ready() && !n.dirty && n.data != nil { return (*n.data)[n.borders[0]:n.borders[1]] } return nil @@ -205,38 +210,62 @@ func (n *Node) Source() []byte { // String is implementation of Stringer interface, returns string based on source part func (n *Node) String() string { + if n == nil { + return "" + } if n.ready() && !n.dirty { return string(n.Source()) } - val := n.value.Load() - if val != nil { - return fmt.Sprint(val) + val, err := Marshal(n) + if err != nil { + return "Error: " + err.Error() } - return "null" + return string(val) } // Type will return type of current node func (n *Node) Type() NodeType { + if n == nil { + return Null + } return n._type } // Key will return key of current node, please check, that parent of this node has an Object type func (n *Node) Key() string { + if n == nil { + return "" + } + if n.key == nil { + return "" + } return *n.key } // Index will return index of current node, please check, that parent of this node has an Array type func (n *Node) Index() int { + if n == nil { + return -1 + } + if n.index == nil { + return -1 + } return *n.index } // Size will return count of children of current node, please check, that parent of this node has an Array type func (n *Node) Size() int { + if n == nil { + return 0 + } return len(n.children) } // Keys will return count all keys of children of current node, please check, that parent of this node has an Object type func (n *Node) Keys() (result []string) { + if n == nil { + return nil + } result = make([]string, 0, len(n.children)) for key := range n.children { result = append(result, key) @@ -246,31 +275,49 @@ func (n *Node) Keys() (result []string) { // IsArray returns true if current node is Array func (n *Node) IsArray() bool { + if n == nil { + return false + } return n._type == Array } // IsObject returns true if current node is Object func (n *Node) IsObject() bool { + if n == nil { + return false + } return n._type == Object } // IsNull returns true if current node is Null func (n *Node) IsNull() bool { + if n == nil { + return false + } return n._type == Null } // IsNumeric returns true if current node is Numeric func (n *Node) IsNumeric() bool { + if n == nil { + return false + } return n._type == Numeric } // IsString returns true if current node is String func (n *Node) IsString() bool { + if n == nil { + return false + } return n._type == String } // IsBool returns true if current node is Bool func (n *Node) IsBool() bool { + if n == nil { + return false + } return n._type == Bool } @@ -292,6 +339,9 @@ func (n *Node) IsBool() bool { // // Value will be calculated only once and saved into atomic.Value. func (n *Node) Value() (value interface{}, err error) { + if n == nil { + return nil, errorUnparsed() + } switch n._type { case Null: return n.GetNull() @@ -356,6 +406,9 @@ func (n *Node) getValue() (value interface{}, err error) { // GetNull returns nil, if current type is Null, else: WrongType error func (n *Node) GetNull() (interface{}, error) { + if n == nil { + return nil, errorUnparsed() + } if n._type != Null { return nil, errorType() } @@ -364,6 +417,9 @@ func (n *Node) GetNull() (interface{}, error) { // GetNumeric returns float64, if current type is Numeric, else: WrongType error func (n *Node) GetNumeric() (value float64, err error) { + if n == nil { + return 0, errorUnparsed() + } if n._type != Numeric { return value, errorType() } @@ -380,6 +436,9 @@ func (n *Node) GetNumeric() (value float64, err error) { // GetString returns string, if current type is String, else: WrongType error func (n *Node) GetString() (value string, err error) { + if n == nil { + return "", errorUnparsed() + } if n._type != String { return value, errorType() } @@ -396,6 +455,9 @@ func (n *Node) GetString() (value string, err error) { // GetBool returns bool, if current type is Bool, else: WrongType error func (n *Node) GetBool() (value bool, err error) { + if n == nil { + return value, errorUnparsed() + } if n._type != Bool { return value, errorType() } @@ -412,6 +474,9 @@ func (n *Node) GetBool() (value bool, err error) { // GetArray returns []*Node, if current type is Array, else: WrongType error func (n *Node) GetArray() (value []*Node, err error) { + if n == nil { + return nil, errorUnparsed() + } if n._type != Array { return value, errorType() } @@ -428,6 +493,9 @@ func (n *Node) GetArray() (value []*Node, err error) { // GetObject returns map[string]*Node, if current type is Object, else: WrongType error func (n *Node) GetObject() (value map[string]*Node, err error) { + if n == nil { + return nil, errorUnparsed() + } if n._type != Object { return value, errorType() } @@ -498,6 +566,9 @@ func (n *Node) MustObject() (value map[string]*Node) { // Unpack will produce current node to it's interface, recursively with all underlying nodes (in contrast to Node.Value). func (n *Node) Unpack() (value interface{}, err error) { + if n == nil { + return nil, errorUnparsed() + } switch n._type { case Null: return nil, nil @@ -541,6 +612,9 @@ func (n *Node) Unpack() (value interface{}, err error) { // GetIndex will return child node of current array node. If current node is not Array, or index is unavailable, will return error func (n *Node) GetIndex(index int) (*Node, error) { + if n == nil { + return nil, errorUnparsed() + } if n._type != Array { return nil, errorType() } @@ -565,6 +639,9 @@ func (n *Node) MustIndex(index int) (value *Node) { // GetKey will return child node of current object node. If current node is not Object, or key is unavailable, will return error func (n *Node) GetKey(key string) (*Node, error) { + if n == nil { + return nil, errorUnparsed() + } if n._type != Object { return nil, errorType() } @@ -586,22 +663,28 @@ func (n *Node) MustKey(key string) (value *Node) { // HasKey will return boolean value, if current object node has custom key func (n *Node) HasKey(key string) bool { + if n == nil { + return false + } _, ok := n.children[key] return ok } // Empty method check if current container node has no children func (n *Node) Empty() bool { + if n == nil { + return false + } return len(n.children) == 0 } // Path returns full JsonPath of current Node func (n *Node) Path() string { + if n == nil { + return "" + } if n.parent == nil { - if n.key == nil { - return "$" - } - return n.Key() + return "$" } if n.key != nil { return n.parent.Path() + "['" + n.Key() + "']" @@ -611,6 +694,9 @@ func (n *Node) Path() string { // Eq check if nodes value are the same func (n *Node) Eq(node *Node) (result bool, err error) { + if n == nil || node == nil { + return false, errorUnparsed() + } if n.Type() == node.Type() { switch n.Type() { case Bool: @@ -685,6 +771,9 @@ func (n *Node) Neq(node *Node) (result bool, err error) { // Le check if nodes value is lesser than given func (n *Node) Le(node *Node) (result bool, err error) { + if n == nil || node == nil { + return false, errorUnparsed() + } if n.Type() == node.Type() { switch n.Type() { case Numeric: @@ -708,6 +797,9 @@ func (n *Node) Le(node *Node) (result bool, err error) { // Leq check if nodes value is lesser or equal than given func (n *Node) Leq(node *Node) (result bool, err error) { + if n == nil || node == nil { + return false, errorUnparsed() + } if n.Type() == node.Type() { switch n.Type() { case Numeric: @@ -731,6 +823,9 @@ func (n *Node) Leq(node *Node) (result bool, err error) { // Ge check if nodes value is greater than given func (n *Node) Ge(node *Node) (result bool, err error) { + if n == nil || node == nil { + return false, errorUnparsed() + } if n.Type() == node.Type() { switch n.Type() { case Numeric: @@ -754,6 +849,9 @@ func (n *Node) Ge(node *Node) (result bool, err error) { // Geq check if nodes value is greater or equal than given func (n *Node) Geq(node *Node) (result bool, err error) { + if n == nil || node == nil { + return false, errorUnparsed() + } if n.Type() == node.Type() { switch n.Type() { case Numeric: @@ -810,6 +908,9 @@ func (n *Node) getUInteger() (uint, error) { // Inheritors return sorted by keys/index slice of children func (n *Node) Inheritors() (result []*Node) { + if n == nil { + return nil + } size := len(n.children) if n.IsObject() { result = make([]*Node, size) diff --git a/node_mutations.go b/node_mutations.go index a64aecc..786e1ac 100644 --- a/node_mutations.go +++ b/node_mutations.go @@ -10,37 +10,85 @@ func (n *Node) IsDirty() bool { return n.dirty } -// SetNull update current node value with Null value +// Set updates current node value with the value of any type +func (n *Node) Set(value interface{}) error { + if value == nil { + return n.SetNull() + } + switch result := value.(type) { + case float64, float32, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64: + if tValue, err := numeric2float64(value); err != nil { + return err + } else { + return n.SetNumeric(tValue) + } + case string: + return n.SetString(result) + case bool: + return n.SetBool(result) + case []*Node: + return n.SetArray(result) + case map[string]*Node: + return n.SetObject(result) + case *Node: + return n.SetNode(result) + default: + return unsupportedType(value) + } +} + +// SetNull updates current node value with Null value func (n *Node) SetNull() error { return n.update(Null, nil) } -// SetNumeric update current node value with Numeric value +// SetNumeric updates current node value with Numeric value func (n *Node) SetNumeric(value float64) error { return n.update(Numeric, value) } -// SetString update current node value with String value +// SetString updates current node value with String value func (n *Node) SetString(value string) error { return n.update(String, value) } -// SetBool update current node value with Bool value +// SetBool updates current node value with Bool value func (n *Node) SetBool(value bool) error { return n.update(Bool, value) } -// SetArray update current node value with Array value +// SetArray updates current node value with Array value func (n *Node) SetArray(value []*Node) error { return n.update(Array, value) } -// SetObject update current node value with Object value +// SetObject updates current node value with Object value func (n *Node) SetObject(value map[string]*Node) error { return n.update(Object, value) } -// AppendArray append current Array node values with Node values +// SetNode updates current node value with the clone of the given Node value +// NB! The result will be the clone of the given Node! +func (n *Node) SetNode(value *Node) error { + if n == value { + // Attempt to set current node as the value: node.SetNode(node) + return nil + } + if n.isParentOrSelfNode(value) { + return errorRequest("attempt to create infinite loop") + } + + node := value.Clone() + node.setReference(n.parent, n.key, n.index) + n.setReference(nil, nil, nil) + *n = *node + if n.parent != nil { + n.parent.mark() + } + return nil +} + +// AppendArray appends current Array node values with Node values func (n *Node) AppendArray(value ...*Node) error { if !n.IsArray() { return errorType() @@ -54,7 +102,7 @@ func (n *Node) AppendArray(value ...*Node) error { return nil } -// AppendObject append current Object node value with key:value +// AppendObject appends current Object node value with key:value func (n *Node) AppendObject(key string, value *Node) error { if !n.IsObject() { return errorType() @@ -119,9 +167,7 @@ func (n *Node) Delete() error { // Clone creates full copy of current Node. With all child, but without link to the parent. func (n *Node) Clone() *Node { node := n.clone() - node.parent = nil - node.key = nil - node.index = nil + node.setReference(nil, nil, nil) return node } @@ -143,7 +189,7 @@ func (n *Node) clone() *Node { return node } -// update stored value, with validations +// update method updates stored value, with validations func (n *Node) update(_type NodeType, value interface{}) error { // validate err := n.validate(_type, value) @@ -180,8 +226,11 @@ func (n *Node) update(_type NodeType, value interface{}) error { return nil } -// validate stored value, before update +// validate method validates stored value, before update func (n *Node) validate(_type NodeType, value interface{}) error { + if n == nil { + return errorUnparsed() + } switch _type { case Null: if value != nil { @@ -215,7 +264,7 @@ func (n *Node) validate(_type NodeType, value interface{}) error { return nil } -// update stored value, without validations +// remove method removes value from current container func (n *Node) remove(value *Node) error { if !n.isContainer() { return errorType() @@ -246,10 +295,10 @@ func (n *Node) dropindex(index int) { } } -// appendNode append current Node node value with new Node value, by key or index +// appendNode appends current Node node value with new Node value, by key or index func (n *Node) appendNode(key *string, value *Node) error { - if n.isParentNode(value) { - return errorRequest("try to create infinite loop") + if n.isParentOrSelfNode(value) { + return errorRequest("attempt to create infinite loop") } if value.parent != nil { if err := value.parent.remove(value); err != nil { @@ -294,12 +343,31 @@ func (n *Node) clear() { n.children = nil } +// isParentOrSelfNode check if current node is the same as given one of parents +func (n *Node) isParentOrSelfNode(node *Node) bool { + return n == node || n.isParentNode(node) +} + // isParentNode check if current node is one of the parents func (n *Node) isParentNode(node *Node) bool { - for current := n; current != nil; current = current.parent { - if current == node { - return true + if n != nil { + for current := n.parent; current != nil; current = current.parent { + if current == node { + return true + } } } return false } + +// setReference updates references of current node +func (n *Node) setReference(parent *Node, key *string, index *int) { + n.parent = parent + if key == nil { + n.key = nil + } else { + temp := *key + n.key = &temp + } + n.index = index +} diff --git a/node_mutations_test.go b/node_mutations_test.go index 4fe4806..47b11c7 100644 --- a/node_mutations_test.go +++ b/node_mutations_test.go @@ -1479,3 +1479,313 @@ func ExampleNode_Clone() { // } // } + +func TestNode_SetNode(t *testing.T) { + iValue := `{"foo": [{"bar":"baz"}]}` + idempotent := Must(Unmarshal([]byte(iValue))) + child := StringNode("", "example") + parent := ArrayNode("", []*Node{child}) + array := ArrayNode("", []*Node{}) + proxy := func(root *Node) *Node { + return root + } + + tests := []struct { + name string + root *Node + getter func(root *Node) *Node + value *Node + result string + after func(t *testing.T) + wantErr bool + }{ + { + name: "Null->Numeric(1)", + root: NullNode(""), + getter: proxy, + value: NumericNode("", 1), + result: `1`, + wantErr: false, + }, + { + name: "Null->Object", + root: NullNode(""), + getter: proxy, + value: Must(Unmarshal([]byte(`{"bar":"baz"}`))), + result: `{"bar":"baz"}`, + wantErr: false, + }, + { + name: "Null->Object(ref)", + root: NullNode(""), + getter: proxy, + value: idempotent.MustObject()["foo"].MustArray()[0], + result: `{"bar":"baz"}`, + wantErr: false, + }, + { + name: "[Numeric(1)]->[Object]", + root: Must(Unmarshal([]byte(`[1]`))), + getter: func(root *Node) *Node { + return root.MustArray()[0] + }, + value: Must(Unmarshal([]byte(`{"bar":"baz"}`))), + result: `[{"bar":"baz"}]`, + wantErr: false, + }, + { + name: "[Numeric(1)]->[Object(ref)]", + root: Must(Unmarshal([]byte(`[1]`))), + getter: func(root *Node) *Node { + return root.MustIndex(0) + }, + value: idempotent.MustKey("foo").MustIndex(0), + result: `[{"bar":"baz"}]`, + wantErr: false, + }, + { + name: "{foo:Null}->{foo:Object(ref)}", + root: Must(Unmarshal([]byte(`{"foo":null}`))), + getter: func(root *Node) *Node { + return root.MustKey("foo") + }, + value: idempotent.MustKey("foo").MustIndex(0), + result: `{"foo":{"bar":"baz"}}`, + wantErr: false, + }, + { + name: "parent[child]->parent[parent]", + root: parent, + getter: func(_ *Node) *Node { + return child + }, + value: parent, + result: ``, + wantErr: true, + }, + { + name: "array[]->array[]", + root: array, + getter: proxy, + value: array, + result: `[]`, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + node := tt.getter(tt.root) + if err := node.SetNode(tt.value); (err != nil) != tt.wantErr { + t.Errorf("SetNode() error = %v, wantErr %v", err, tt.wantErr) + return + } else if tt.wantErr { + return + } + if tt.root.String() != tt.result { + t.Errorf("SetNode() value not match: \nExpected: %s\nActual: %s", tt.result, tt.root.String()) + return + } + if idempotent.String() != iValue { + t.Errorf("SetNode() unexpected update value: \nUpdated: %s", idempotent.String()) + return + } + if tt.after != nil { + tt.after(t) + } + }) + } +} + +func TestNode_Set(t *testing.T) { + node := func(data string) *Node { + return Must(Unmarshal([]byte(data))) + } + tests := []struct { + name string + node *Node + getter func(root *Node) *Node + value interface{} + result string + wantErr bool + }{ + { + name: "Null->float64(123.456)", + node: node("null"), + value: float64(123.456), + result: "123.456", + wantErr: false, + }, + { + name: "Null->float32(123)", + node: node("null"), + value: float32(123), + result: "123", + wantErr: false, + }, + { + name: "Null->int(123)", + node: node("null"), + value: 123, + result: "123", + wantErr: false, + }, + { + name: "Null->int8(123)", + node: node("null"), + value: int8(123), + result: "123", + wantErr: false, + }, + { + name: "Null->int16(123)", + node: node("null"), + value: int16(123), + result: "123", + wantErr: false, + }, + { + name: "Null->int32(123)", + node: node("null"), + value: int32(123), + result: "123", + wantErr: false, + }, + { + name: "Null->int64(123)", + node: node("null"), + value: int64(123), + result: "123", + wantErr: false, + }, + { + name: "Null->uint8(123)", + node: node("null"), + value: uint8(123), + result: "123", + wantErr: false, + }, + { + name: "Null->uint16(123)", + node: node("null"), + value: uint16(123), + result: "123", + wantErr: false, + }, + { + name: "Null->uint32(123)", + node: node("null"), + value: uint32(123), + result: "123", + wantErr: false, + }, + { + name: "Null->uint64(123)", + node: node("null"), + value: uint64(123), + result: "123", + wantErr: false, + }, + { + name: "Null->uint(123)", + node: node("null"), + value: uint(123), + result: "123", + wantErr: false, + }, + { + name: "Array[]->string", + node: node("[123]"), + value: "example value", + result: `"example value"`, + wantErr: false, + }, + { + name: "Object[]->bool", + node: node(`{"foo":["bar"]}`), + value: true, + result: `true`, + wantErr: false, + }, + { + name: "Object[V]->bool", + node: node(`{"foo":["bar"]}`), + getter: func(root *Node) *Node { + return root.MustKey("foo").MustIndex(0) + }, + value: true, + result: `{"foo":[true]}`, + wantErr: false, + }, + { + name: "Object[V]->nil", + node: node(`{"foo":["bar"]}`), + getter: func(root *Node) *Node { + return root.MustKey("foo").MustIndex(0) + }, + value: nil, + result: `{"foo":[null]}`, + wantErr: false, + }, + { + name: "Array[V]->Array[Array[]]", + node: node(`[null]`), + getter: func(root *Node) *Node { + return root.MustIndex(0) + }, + value: []*Node{node(`1`)}, + result: `[[1]]`, + wantErr: false, + }, + { + name: "Array[V]->Array[Object[]]", + node: node(`[null]`), + getter: func(root *Node) *Node { + return root.MustIndex(0) + }, + value: map[string]*Node{}, + result: `[{}]`, + wantErr: false, + }, + { + name: "Array[V]->Array[Node]", + node: node(`[null]`), + getter: func(root *Node) *Node { + return root.MustIndex(0) + }, + value: node(`{}`), + result: `[{}]`, + wantErr: false, + }, + { + name: "wrong_type", + node: node(`[null]`), + value: new(string), + wantErr: true, + }, + { + name: "nil", + node: nil, + value: nil, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + value := tt.node + if tt.getter != nil { + value = tt.getter(tt.node) + } + if err := value.Set(tt.value); (err != nil) != tt.wantErr { + t.Errorf("Set() error = %v, wantErr %v", err, tt.wantErr) + return + } + if tt.wantErr { + return + } + if tt.node.String() != tt.result { + t.Errorf("Set() value not match: \nExpected: %s\nActual: %s", tt.result, tt.node.String()) + return + } + }) + } +} diff --git a/node_test.go b/node_test.go index c75ca8c..281043d 100644 --- a/node_test.go +++ b/node_test.go @@ -85,6 +85,13 @@ func TestNode_Unpack(t *testing.T) { } } +func TestNode_Unpack_nil(t *testing.T) { + _, err := (*Node)(nil).Unpack() + if err == nil { + t.Errorf("(nil).Unpack() should be an error") + } +} + func TestNode_getValue(t *testing.T) { root, err := Unmarshal([]byte(`{ "category": null, "author": "Evelyn Waugh", @@ -145,6 +152,9 @@ func TestNode_Empty(t *testing.T) { if !value["sub1"].Empty() { t.Errorf("Node `sub1` is empty") } + if (*Node)(nil).Empty() { + t.Errorf("(nil).Empty() is empty") + } } func TestNode_GetArray(t *testing.T) { @@ -166,6 +176,9 @@ func TestNode_GetArray(t *testing.T) { if err == nil { t.Errorf("Error on root.GetArray(): NullNode") } + if _, err := (*Node)(nil).GetArray(); err == nil { + t.Errorf("(nil).GetArray() should be an error") + } } func TestNode_MustArray(t *testing.T) { @@ -199,6 +212,9 @@ func TestNode_GetBool(t *testing.T) { if err == nil { t.Errorf("Error on root.GetBool(): NullNode") } + if _, err := (*Node)(nil).GetBool(); err == nil { + t.Errorf("(nil).GetBool() should be an error") + } } func TestNode_MustBool(t *testing.T) { @@ -234,6 +250,9 @@ func TestNode_GetIndex(t *testing.T) { if value != nil { t.Errorf("Error on root.GetIndex() - wrong value") } + if _, err := (*Node)(nil).GetIndex(0); err == nil { + t.Errorf("(nil).GetIndex() should be an error") + } } func TestNode_MustIndex(t *testing.T) { @@ -269,6 +288,9 @@ func TestNode_GetKey(t *testing.T) { if value != nil { t.Errorf("Error on root.GetKey() - wrong value") } + if _, err := (*Node)(nil).GetKey(""); err == nil { + t.Errorf("(nil).GetKey() should be an error") + } } func TestNode_MustKey(t *testing.T) { @@ -302,6 +324,9 @@ func TestNode_GetNull(t *testing.T) { if err == nil { t.Errorf("Error expected on root.GetNull() using NumericNode") } + if _, err := (*Node)(nil).GetNull(); err == nil { + t.Errorf("(nil).GetNull() should be an error") + } } func TestNode_MustNull(t *testing.T) { @@ -341,6 +366,9 @@ func TestNode_GetNumeric(t *testing.T) { if err == nil { t.Errorf("Error on root.GetNumeric() wrong data") } + if _, err := (*Node)(nil).GetNumeric(); err == nil { + t.Errorf("(nil).GetNumeric() should be an error") + } } func TestNode_MustNumeric(t *testing.T) { @@ -377,6 +405,9 @@ func TestNode_GetObject(t *testing.T) { if err == nil { t.Errorf("Error on root.GetArray(): NullNode") } + if _, err := (*Node)(nil).GetObject(); err == nil { + t.Errorf("(nil).GetObject() should be an error") + } } func TestNode_MustObject(t *testing.T) { @@ -413,6 +444,9 @@ func TestNode_GetString(t *testing.T) { if err == nil { t.Errorf("Error on root.GetString(): NumericNode") } + if _, err := (*Node)(nil).GetString(); err == nil { + t.Errorf("(nil).GetString() should be an error") + } } func TestNode_MustString(t *testing.T) { @@ -439,6 +473,15 @@ func TestNode_Index(t *testing.T) { t.Errorf("Wrong node.Index(): %d != %d", i, node.Index()) } } + if (*Node)(nil).Index() != -1 { + t.Errorf("Wrong value for (*Node)(nil).Index()") + } + if NullNode("").Index() != -1 { + t.Errorf("Wrong value for Null.Index()") + } + if ObjectNode("", nil).Index() != -1 { + t.Errorf("Wrong value for Null.Index()") + } } func TestNode_Key(t *testing.T) { @@ -453,6 +496,12 @@ func TestNode_Key(t *testing.T) { t.Errorf("Wrong node.Index(): '%s' != '%s'", key, node.Key()) } } + if (*Node)(nil).Key() != "" { + t.Errorf("Wrong value for (*Node)(nil).Key()") + } + if root.MustKey("foo").Clone().Key() != "" { + t.Errorf("Wrong value for Cloned.Key()") + } } func TestNode_IsArray(t *testing.T) { @@ -594,21 +643,39 @@ func TestNode_IsNull(t *testing.T) { if root.IsArray() { t.Errorf("Wrong root.IsArray()") } + if (*Node)(nil).IsArray() { + t.Errorf("Wrong (*Node)(nil).IsArray()") + } if root.IsObject() { t.Errorf("Wrong root.IsObject()") } + if (*Node)(nil).IsObject() { + t.Errorf("Wrong (*Node)(nil).IsObject()") + } if root.IsString() { t.Errorf("Wrong root.IsString()") } + if (*Node)(nil).IsString() { + t.Errorf("Wrong (*Node)(nil).IsString()") + } if root.IsNumeric() { t.Errorf("Wrong root.IsNumeric()") } + if (*Node)(nil).IsNumeric() { + t.Errorf("Wrong (*Node)(nil).IsNumeric()") + } if root.IsBool() { t.Errorf("Wrong root.IsBool()") } + if (*Node)(nil).IsBool() { + t.Errorf("Wrong (*Node)(nil).IsBool()") + } if !root.IsNull() { t.Errorf("Wrong root.IsNull()") } + if (*Node)(nil).IsNull() { + t.Errorf("Wrong (*Node)(nil).IsNull()") + } } func TestNode_Keys(t *testing.T) { @@ -627,6 +694,9 @@ func TestNode_Keys(t *testing.T) { if value[1] != "foo" && value[1] != "bar" { t.Errorf("Wrong value in 1") } + if (*Node)(nil).Keys() != nil { + t.Errorf("Wrong value for (*Node)(nil).Keys()") + } } func TestNode_Size(t *testing.T) { @@ -639,6 +709,9 @@ func TestNode_Size(t *testing.T) { if value != 4 { t.Errorf("Wrong root.Size()") } + if (*Node)(nil).Size() != 0 { + t.Errorf("Wrong (*Node)(nil).Size()") + } } func TestNode_Parent(t *testing.T) { @@ -655,6 +728,9 @@ func TestNode_Parent(t *testing.T) { if value.Parent().String() != root.String() { t.Errorf("Wrong value.Parent()") } + if (*Node)(nil).Parent() != nil { + t.Errorf("Wrong value for (*Node)(nil).Parent()") + } } func TestNode_Source(t *testing.T) { @@ -667,6 +743,9 @@ func TestNode_Source(t *testing.T) { if !bytes.Equal(value, []byte(`{"foo":true,"bar":null}`)) { t.Errorf("Wrong root.Source()") } + if (*Node)(nil).Source() != nil { + t.Errorf("Wrong value for (*Node)(nil).Source()") + } } func TestNode_String(t *testing.T) { @@ -682,7 +761,7 @@ func TestNode_String(t *testing.T) { root = StringNode("", "foo") value = root.String() - if value != "foo" { + if value != `"foo"` { t.Errorf("Wrong (StringNode) root.String()") } @@ -691,6 +770,16 @@ func TestNode_String(t *testing.T) { if value != "null" { t.Errorf("Wrong (NullNode) root.String()") } + if (*Node)(nil).String() != "" { + t.Errorf("Wrong value for (*Node)(nil).String()") + } + + node := Must(Unmarshal([]byte(`{"foo":"bar"}`))) + node.borders[1] = 0 // broken borders + broken := node.String() + if broken != "Error: not parsed yet" { + t.Errorf("Wrong broken.String() value, actual value: %s", broken) + } } func TestNode_Type(t *testing.T) { @@ -721,6 +810,12 @@ func TestNode_Type(t *testing.T) { } } +func TestNode_Type_null(t *testing.T) { + if (*Node)(nil).Type() != Null { + t.Errorf("Wrong value for (*Node)(nil).Type()") + } +} + func TestNode_HasKey(t *testing.T) { root, err := Unmarshal([]byte(`{"foo":true,"bar":null}`)) if err != nil { @@ -736,6 +831,9 @@ func TestNode_HasKey(t *testing.T) { if root.HasKey("baz") { t.Errorf("Wrong root.HasKey('bar')") } + if (*Node)(nil).HasKey("baz") { + t.Errorf("Wrong (*Node)(nil).HasKey('bar')") + } } func TestNode_Path(t *testing.T) { @@ -765,6 +863,9 @@ func TestNode_Path(t *testing.T) { if element.Path() != "$['Image']['Thumbnail']['Url']" { t.Errorf("Wrong element.Path()") } + if (*Node)(nil).Path() != "" { + t.Errorf("Wrong (nil).Path()") + } } func TestNode_Eq(t *testing.T) { @@ -906,6 +1007,18 @@ func TestNode_Eq(t *testing.T) { right: valueNode(nil, "", Object, float64(1)), error: true, }, + { + name: "nil/value", + left: nil, + right: StringNode("", "foo"), + error: true, + }, + { + name: "value/nil", + left: StringNode("", "foo"), + right: nil, + error: true, + }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { @@ -928,6 +1041,7 @@ func TestNode_Neq(t *testing.T) { name string left, right *Node expected bool + error bool }{ { name: "simple", @@ -983,11 +1097,27 @@ func TestNode_Neq(t *testing.T) { right: NumericNode("", 1.00011), expected: true, }, + { + name: "nil/value", + left: nil, + right: StringNode("", "foo"), + error: true, + }, + { + name: "value/nil", + left: StringNode("", "foo"), + right: nil, + error: true, + }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { actual, err := test.left.Neq(test.right) - if err != nil { + if test.error { + if err == nil { + t.Errorf("Error expected: nil given") + } + } else if err != nil { t.Errorf("Error on node.Neq(): %s", err.Error()) } else if actual != test.expected { t.Errorf("Failed node.Neq()") @@ -1087,6 +1217,18 @@ func TestNode_Ge(t *testing.T) { right: StringNode("", "foo"), error: true, }, + { + name: "nil/value", + left: nil, + right: StringNode("", "foo"), + error: true, + }, + { + name: "value/nil", + left: StringNode("", "foo"), + right: nil, + error: true, + }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { @@ -1098,7 +1240,6 @@ func TestNode_Ge(t *testing.T) { } else if actual != test.expected { t.Errorf("Failed node.Ge()") } - }) } } @@ -1194,6 +1335,18 @@ func TestNode_Geq(t *testing.T) { right: StringNode("", "foo"), error: true, }, + { + name: "nil/value", + left: nil, + right: StringNode("", "foo"), + error: true, + }, + { + name: "value/nil", + left: StringNode("", "foo"), + right: nil, + error: true, + }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { @@ -1301,6 +1454,18 @@ func TestNode_Le(t *testing.T) { right: StringNode("", "foo"), error: true, }, + { + name: "nil/value", + left: nil, + right: StringNode("", "foo"), + error: true, + }, + { + name: "value/nil", + left: StringNode("", "foo"), + right: nil, + error: true, + }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { @@ -1408,6 +1573,18 @@ func TestNode_Leq(t *testing.T) { right: StringNode("", "foo"), error: true, }, + { + name: "nil/value", + left: nil, + right: StringNode("", "foo"), + error: true, + }, + { + name: "value/nil", + left: StringNode("", "foo"), + right: nil, + error: true, + }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { @@ -1798,6 +1975,12 @@ func TestNode_Value(t *testing.T) { wantValue: nil, wantErr: true, }, + { + name: "nil", + node: nil, + wantValue: nil, + wantErr: true, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) {