diff --git a/.golangci.yaml b/.golangci.yaml index fc96817..05b413b 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -6,6 +6,7 @@ linters: - gochecknoglobals - gocritic - funlen + - godox linters-settings: lll: line-length: 180 @@ -21,4 +22,6 @@ issues: - lll - goconst - unparam - - unused \ No newline at end of file + - unused + max-issues-per-linter: 0 + max-same-issues: 0 \ No newline at end of file diff --git a/eval.go b/eval.go index 63d8592..94b3e6e 100644 --- a/eval.go +++ b/eval.go @@ -259,17 +259,17 @@ func pathEndValue(q *query, e *Eval, i *Item) queryStateFn { switch q.firstType { case jsonBraceLeft: - r.Type = JsonObject + r.Type = JSONObject case jsonString: - r.Type = JsonString + r.Type = JSONString case jsonBracketLeft: - r.Type = JsonArray + r.Type = JSONArray case jsonNull: - r.Type = JsonNull + r.Type = JSONNull case jsonBool: - r.Type = JsonBool + r.Type = JSONBool case jsonNumber: - r.Type = JsonNumber + r.Type = JSONNumber default: r.Type = -1 } @@ -297,7 +297,7 @@ func (b *exprBucket) evaluate() (bool, error) { for _, q := range b.queries { result := q.resultQueue.Pop() if result != nil { - t, err := getJsonTokenType(result.Value) + t, err := getJSONTokenType(result.Value) if err != nil { return false, err } diff --git a/eval_test.go b/eval_test.go index f5f8983..49d005e 100644 --- a/eval_test.go +++ b/eval_test.go @@ -15,32 +15,32 @@ type test struct { } var tests = []test{ - {`key selection`, `{"aKey":32}`, `$.aKey+`, []Result{newResult(`32`, JsonNumber, `aKey`)}}, - {`nested key selection`, `{"aKey":{"bKey":32}}`, `$.aKey+`, []Result{newResult(`{"bKey":32}`, JsonObject, `aKey`)}}, - {`empty array`, `{"aKey":[]}`, `$.aKey+`, []Result{newResult(`[]`, JsonArray, `aKey`)}}, - {`multiple same-level keys, weird spacing`, `{ "aKey" : true , "bKey": [ 1 , 2 ], "cKey" : true } `, `$.bKey+`, []Result{newResult(`[1,2]`, JsonArray, `bKey`)}}, + {`key selection`, `{"aKey":32}`, `$.aKey+`, []Result{newResult(`32`, JSONNumber, `aKey`)}}, + {`nested key selection`, `{"aKey":{"bKey":32}}`, `$.aKey+`, []Result{newResult(`{"bKey":32}`, JSONObject, `aKey`)}}, + {`empty array`, `{"aKey":[]}`, `$.aKey+`, []Result{newResult(`[]`, JSONArray, `aKey`)}}, + {`multiple same-level keys, weird spacing`, `{ "aKey" : true , "bKey": [ 1 , 2 ], "cKey" : true } `, `$.bKey+`, []Result{newResult(`[1,2]`, JSONArray, `bKey`)}}, - {`array index selection`, `{"aKey":[123,456]}`, `$.aKey[1]+`, []Result{newResult(`456`, JsonNumber, `aKey`, 1)}}, - {`array wild index selection`, `{"aKey":[123,456]}`, `$.aKey[*]+`, []Result{newResult(`123`, JsonNumber, `aKey`, 0), newResult(`456`, JsonNumber, `aKey`, 1)}}, - {`array range index selection`, `{"aKey":[11,22,33,44]}`, `$.aKey[1:3]+`, []Result{newResult(`22`, JsonNumber, `aKey`, 1), newResult(`33`, JsonNumber, `aKey`, 2)}}, + {`array index selection`, `{"aKey":[123,456]}`, `$.aKey[1]+`, []Result{newResult(`456`, JSONNumber, `aKey`, 1)}}, + {`array wild index selection`, `{"aKey":[123,456]}`, `$.aKey[*]+`, []Result{newResult(`123`, JSONNumber, `aKey`, 0), newResult(`456`, JSONNumber, `aKey`, 1)}}, + {`array range index selection`, `{"aKey":[11,22,33,44]}`, `$.aKey[1:3]+`, []Result{newResult(`22`, JSONNumber, `aKey`, 1), newResult(`33`, JSONNumber, `aKey`, 2)}}, {`array range (no index) selection`, `{"aKey":[11,22,33,44]}`, `$.aKey[1:1]+`, []Result{}}, - {`array range (no upper bound) selection`, `{"aKey":[11,22,33]}`, `$.aKey[1:]+`, []Result{newResult(`22`, JsonNumber, `aKey`, 1), newResult(`33`, JsonNumber, `aKey`, 2)}}, + {`array range (no upper bound) selection`, `{"aKey":[11,22,33]}`, `$.aKey[1:]+`, []Result{newResult(`22`, JSONNumber, `aKey`, 1), newResult(`33`, JSONNumber, `aKey`, 2)}}, {`empty array - try selection`, `{"aKey":[]}`, `$.aKey[1]+`, []Result{}}, - {`null selection`, `{"aKey":[null]}`, `$.aKey[0]+`, []Result{newResult(`null`, JsonNull, `aKey`, 0)}}, - {`empty object`, `{"aKey":{}}`, `$.aKey+`, []Result{newResult(`{}`, JsonObject, `aKey`)}}, - {`object w/ height=2`, `{"aKey":{"bKey":32}}`, `$.aKey.bKey+`, []Result{newResult(`32`, JsonNumber, `aKey`, `bKey`)}}, - {`array of multiple types`, `{"aKey":[1,{"s":true},"asdf"]}`, `$.aKey[1]+`, []Result{newResult(`{"s":true}`, JsonObject, `aKey`, 1)}}, - {`nested array selection`, `{"aKey":{"bKey":[123,456]}}`, `$.aKey.bKey+`, []Result{newResult(`[123,456]`, JsonArray, `aKey`, `bKey`)}}, - {`nested array`, `[[[[[]], [true, false, []]]]]`, `$[0][0][1][2]+`, []Result{newResult(`[]`, JsonArray, 0, 0, 1, 2)}}, - {`index of array selection`, `{"aKey":{"bKey":[123, 456, 789]}}`, `$.aKey.bKey[1]+`, []Result{newResult(`456`, JsonNumber, `aKey`, `bKey`, 1)}}, - {`index of array selection (more than one)`, `{"aKey":{"bKey":[123,456]}}`, `$.aKey.bKey[1]+`, []Result{newResult(`456`, JsonNumber, `aKey`, `bKey`, 1)}}, - {`multi-level object/array`, `{"1Key":{"aKey": null, "bKey":{"trash":[1,2]}, "cKey":[123,456] }, "2Key":false}`, `$.1Key.bKey.trash[0]+`, []Result{newResult(`1`, JsonNumber, `1Key`, `bKey`, `trash`, 0)}}, - {`multi-level array`, `{"aKey":[true,false,null,{"michael":[5,6,7]}, ["s", "3"] ]}`, `$.*[*].michael[1]+`, []Result{newResult(`6`, JsonNumber, `aKey`, 3, `michael`, 1)}}, - {`multi-level array 2`, `{"aKey":[true,false,null,{"michael":[5,6,7]}, ["s", "3"] ]}`, `$.*[*][1]+`, []Result{newResult(`"3"`, JsonString, `aKey`, 4, 1)}}, + {`null selection`, `{"aKey":[null]}`, `$.aKey[0]+`, []Result{newResult(`null`, JSONNull, `aKey`, 0)}}, + {`empty object`, `{"aKey":{}}`, `$.aKey+`, []Result{newResult(`{}`, JSONObject, `aKey`)}}, + {`object w/ height=2`, `{"aKey":{"bKey":32}}`, `$.aKey.bKey+`, []Result{newResult(`32`, JSONNumber, `aKey`, `bKey`)}}, + {`array of multiple types`, `{"aKey":[1,{"s":true},"asdf"]}`, `$.aKey[1]+`, []Result{newResult(`{"s":true}`, JSONObject, `aKey`, 1)}}, + {`nested array selection`, `{"aKey":{"bKey":[123,456]}}`, `$.aKey.bKey+`, []Result{newResult(`[123,456]`, JSONArray, `aKey`, `bKey`)}}, + {`nested array`, `[[[[[]], [true, false, []]]]]`, `$[0][0][1][2]+`, []Result{newResult(`[]`, JSONArray, 0, 0, 1, 2)}}, + {`index of array selection`, `{"aKey":{"bKey":[123, 456, 789]}}`, `$.aKey.bKey[1]+`, []Result{newResult(`456`, JSONNumber, `aKey`, `bKey`, 1)}}, + {`index of array selection (more than one)`, `{"aKey":{"bKey":[123,456]}}`, `$.aKey.bKey[1]+`, []Result{newResult(`456`, JSONNumber, `aKey`, `bKey`, 1)}}, + {`multi-level object/array`, `{"1Key":{"aKey": null, "bKey":{"trash":[1,2]}, "cKey":[123,456] }, "2Key":false}`, `$.1Key.bKey.trash[0]+`, []Result{newResult(`1`, JSONNumber, `1Key`, `bKey`, `trash`, 0)}}, + {`multi-level array`, `{"aKey":[true,false,null,{"michael":[5,6,7]}, ["s", "3"] ]}`, `$.*[*].michael[1]+`, []Result{newResult(`6`, JSONNumber, `aKey`, 3, `michael`, 1)}}, + {`multi-level array 2`, `{"aKey":[true,false,null,{"michael":[5,6,7]}, ["s", "3"] ]}`, `$.*[*][1]+`, []Result{newResult(`"3"`, JSONString, `aKey`, 4, 1)}}, - {`evaluation literal equality`, `{"items":[ {"name":"alpha", "value":11}]}`, `$.items[*]?("bravo" == "bravo").value+`, []Result{newResult(`11`, JsonNumber, `items`, 0, `value`)}}, - {`evaluation based on string equal to path value`, `{"items":[ {"name":"alpha", "value":11}, {"name":"bravo", "value":22}, {"name":"charlie", "value":33} ]}`, `$.items[*]?(@.name == "bravo").value+`, []Result{newResult(`22`, JsonNumber, `items`, 1, `value`)}}, + {`evaluation literal equality`, `{"items":[ {"name":"alpha", "value":11}]}`, `$.items[*]?("bravo" == "bravo").value+`, []Result{newResult(`11`, JSONNumber, `items`, 0, `value`)}}, + {`evaluation based on string equal to path value`, `{"items":[ {"name":"alpha", "value":11}, {"name":"bravo", "value":22}, {"name":"charlie", "value":33} ]}`, `$.items[*]?(@.name == "bravo").value+`, []Result{newResult(`22`, JSONNumber, `items`, 1, `value`)}}, } func TestPathQuery(t *testing.T) { @@ -52,14 +52,16 @@ func TestPathQuery(t *testing.T) { eval, err := EvalPathsInBytes([]byte(t.json), paths) if as.NoError(err, "Testing: %s", t.name) { res := toResultArray(eval) + if as.NoError(eval.Error) { as.EqualValues(t.expected, res, "Testing of %q", t.name) } } - eval_reader, err := EvalPathsInReader(strings.NewReader(t.json), paths) + evalReader, err := EvalPathsInReader(strings.NewReader(t.json), paths) if as.NoError(err, "Testing: %s", t.name) { - res := toResultArray(eval_reader) + res := toResultArray(evalReader) + if as.NoError(eval.Error) { as.EqualValues(t.expected, res, "Testing of %q", t.name) } diff --git a/expression.go b/expression.go index 3c15077..e201238 100644 --- a/expression.go +++ b/expression.go @@ -64,11 +64,11 @@ func infixToPostFix(items []Item) (out []Item, err error) { found := false for { // pop item ("(" or operator) from stack - op_interface, ok := stack.pop() + opInterface, ok := stack.pop() if !ok { return nil, errors.New(exprErrorMismatchedParens) } - op := op_interface.(Item) + op := opInterface.(Item) if op.typ == exprParenLeft { found = true break // discard "(" @@ -83,8 +83,8 @@ func infixToPostFix(items []Item) (out []Item, err error) { // token is an operator for stack.len() > 0 { // consider top item on stack - op_int, _ := stack.peek() - op := op_int.(Item) + opInt, _ := stack.peek() + op := opInt.(Item) if o2, isOp := opa[op.typ]; !isOp || o1.prec > o2.prec || o1.prec == o2.prec && o1.rAssoc { break @@ -102,8 +102,8 @@ func infixToPostFix(items []Item) (out []Item, err error) { } // drain stack to result for stack.len() > 0 { - op_int, _ := stack.pop() - op := op_int.(Item) + opInt, _ := stack.pop() + op := opInt.(Item) if op.typ == exprParenLeft { return nil, errors.New(exprErrorMismatchedParens) } @@ -308,7 +308,7 @@ func evaluatePostFix(postFixItems []Item, pathValues map[string]Item) (interface } if a == 0.0 { - return false, errors.New("Cannot divide by zero") + return false, errors.New("cannot divide by zero") } s.push(b / a) case exprOpStar: @@ -340,15 +340,16 @@ func evaluatePostFix(postFixItems []Item, pathValues map[string]Item) (interface s.push(!a) // Other default: - return false, fmt.Errorf("Token not supported in evaluator: %v", exprTokenNames[item.typ]) + return false, fmt.Errorf("token not supported in evaluator: %v", exprTokenNames[item.typ]) } } if s.len() != 1 { return false, fmt.Errorf(exprErrorBadExpression) } - end_int, _ := s.pop() - return end_int, nil + + endInt, _ := s.pop() + return endInt, nil } func take1Bool(s *stack, op int) (bool, error) { diff --git a/json_states.go b/json_states.go index 960a2db..7225a94 100644 --- a/json_states.go +++ b/json_states.go @@ -38,12 +38,14 @@ var jsonTokenNames = map[int]string{ jsonBool: "BOOL", } -var JSON = lexJsonRoot +var JSON = lexJSONRoot -func lexJsonRoot(l lexer, state *intStack) stateFn { +func lexJSONRoot(l lexer, state *intStack) stateFn { ignoreSpaceRun(l) cur := l.peek() + var next stateFn + switch cur { case '{': next = stateJSONObjectOpen diff --git a/json_states_test.go b/json_states_test.go index c7d94b7..d2f883f 100644 --- a/json_states_test.go +++ b/json_states_test.go @@ -33,7 +33,7 @@ func TestValidJson(t *testing.T) { } } -var errorJsonTests = []lexTest{ +var errorJSONTests = []lexTest{ {"Missing end brace", `{`, []int{jsonBraceLeft, jsonError}}, {"Missing start brace", `}`, []int{jsonError}}, {"Missing key start quote", `{key":true}`, []int{jsonBraceLeft, jsonError}}, @@ -50,7 +50,7 @@ var errorJsonTests = []lexTest{ func TestMalformedJson(t *testing.T) { as := assert.New(t) - for _, test := range errorJsonTests { + for _, test := range errorJSONTests { lexer := NewSliceLexer([]byte(test.input), JSON) types := itemsToTypes(readerToArray(lexer)) diff --git a/lexer_slice.go b/lexer_slice.go index ec62cc0..6126f31 100644 --- a/lexer_slice.go +++ b/lexer_slice.go @@ -34,14 +34,14 @@ func (l *SliceLexer) takeString() error { inputLen := len(l.input) if int(curPos) >= inputLen { - return errors.New("End of file where string expected") + return errors.New("end of file where string expected") } cur := int(l.input[curPos]) curPos++ if cur != '"' { l.pos = curPos - return fmt.Errorf("Expected \" as start of string instead of %#U", cur) + return fmt.Errorf("expected \" as start of string instead of %#U", cur) } var previous int @@ -49,7 +49,7 @@ looper: for { if int(curPos) >= inputLen { l.pos = curPos - return errors.New("End of file where string expected") + return errors.New("end of file where string expected") } cur := int(l.input[curPos]) curPos++ diff --git a/misc.go b/misc.go index 0e9ec2c..3cde65c 100644 --- a/misc.go +++ b/misc.go @@ -16,13 +16,13 @@ func takeExponent(l lexer) error { case '+', '-': // Check digit immediately follows sign if d := l.peek(); !(d >= '0' && d <= '9') { - return fmt.Errorf("Expected digit after numeric sign instead of %#U", d) + return fmt.Errorf("expected digit after numeric sign instead of %#U", d) } takeDigits(l) case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': takeDigits(l) default: - return fmt.Errorf("Expected digit after 'e' instead of %#U", r) + return fmt.Errorf("expected digit after 'e' instead of %#U", r) } return nil } @@ -33,13 +33,13 @@ func takeJSONNumeric(l lexer) error { case '-': // Check digit immediately follows sign if d := l.peek(); !(d >= '0' && d <= '9') { - return fmt.Errorf("Expected digit after dash instead of %#U", d) + return fmt.Errorf("expected digit after dash instead of %#U", d) } takeDigits(l) case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': takeDigits(l) default: - return fmt.Errorf("Expected digit or dash instead of %#U", cur) + return fmt.Errorf("expected digit or dash instead of %#U", cur) } // fraction or exponent @@ -49,7 +49,7 @@ func takeJSONNumeric(l lexer) error { l.take() // Check digit immediately follows period if d := l.peek(); !(d >= '0' && d <= '9') { - return fmt.Errorf("Expected digit after '.' instead of %#U", d) + return fmt.Errorf("expected digit after '.' instead of %#U", d) } takeDigits(l) if err := takeExponent(l); err != nil { @@ -158,10 +158,11 @@ func abs(x int) int { } //TODO: Kill the need for this -func getJsonTokenType(val []byte) (int, error) { +func getJSONTokenType(val []byte) (int, error) { if len(val) == 0 { - return -1, errors.New("No Value") + return -1, errors.New("no value") } + switch val[0] { case '{': return jsonBraceLeft, nil @@ -176,6 +177,6 @@ func getJsonTokenType(val []byte) (int, error) { case '-', '+', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': return jsonNumber, nil default: - return -1, errors.New("Unrecognized Json Value") + return -1, errors.New("unrecognized JSON value") } } diff --git a/path.go b/path.go index dcfdb94..4b99b8f 100644 --- a/path.go +++ b/path.go @@ -36,10 +36,13 @@ type operator struct { // nolint:gocognit func genIndexKey(tr tokenReader) (*operator, error) { k := &operator{} + var t *Item + var ok bool + if t, ok = tr.next(); !ok { - return nil, errors.New("Expected number, key, or *, but got none") + return nil, errors.New("expected number, key, or *, but got none") } switch t.typ { @@ -47,22 +50,23 @@ func genIndexKey(tr tokenReader) (*operator, error) { k.typ = opTypeIndexWild k.indexStart = 0 if t, ok = tr.next(); !ok { - return nil, errors.New("Expected ] after *, but got none") + return nil, errors.New("expected ] after *, but got none") } if t.typ != pathBracketRight { - return nil, fmt.Errorf("Expected ] after * instead of %q", t.val) + return nil, fmt.Errorf("expected ] after * instead of %q", t.val) } case pathIndex: v, err := strconv.Atoi(string(t.val)) if err != nil { - return nil, fmt.Errorf("Could not parse %q into int64", t.val) + return nil, fmt.Errorf("could not parse %q into int64", t.val) } + k.indexStart = v k.indexEnd = v k.hasIndexEnd = true if t, ok = tr.next(); !ok { - return nil, errors.New("Expected number or *, but got none") + return nil, errors.New("expected number or *, but got none") } switch t.typ { @@ -77,6 +81,7 @@ func genIndexKey(tr tokenReader) (*operator, error) { if err != nil { return nil, fmt.Errorf("could not parse %q into int64", t.val) } + k.indexEnd = v - 1 k.hasIndexEnd = true @@ -113,6 +118,7 @@ func genIndexKey(tr tokenReader) (*operator, error) { func parsePath(pathString string) (*Path, error) { lexer := NewSliceLexer([]byte(pathString), PATH) + p, err := tokensToOperators(lexer) if err != nil { return nil, err @@ -124,9 +130,11 @@ func parsePath(pathString string) (*Path, error) { for _, op := range p.operators { if len(op.whereClauseBytes) > 0 { var err error + trimmed := op.whereClauseBytes[1 : len(op.whereClauseBytes)-1] whereLexer := NewSliceLexer(trimmed, EXPRESSION) items := readerToArray(whereLexer) + if errItem, found := findErrors(items); found { return nil, errors.New(string(errItem.val)) } @@ -136,6 +144,7 @@ func parsePath(pathString string) (*Path, error) { if err != nil { return nil, err } + op.dependentPaths = make([]*Path, 0) // parse all paths in expression for _, item := range op.whereClause { @@ -144,11 +153,13 @@ func parsePath(pathString string) (*Path, error) { if err != nil { return nil, err } + op.dependentPaths = append(op.dependentPaths, p) } } } } + return p, nil } @@ -158,27 +169,28 @@ func tokensToOperators(tr tokenReader) (*Path, error) { captureEndValue: false, operators: make([]*operator, 0), } + for { p, ok := tr.next() if !ok { break } + switch p.typ { case pathRoot: if len(q.operators) != 0 { - return nil, errors.New("Unexpected root node after start") + return nil, errors.New("unexpected root node after start") } - continue + continue case pathCurrent: if len(q.operators) != 0 { - return nil, errors.New("Unexpected current node after start") + return nil, errors.New("unexpected current node after start") } - continue + continue case pathPeriod: continue - case pathBracketLeft: k, err := genIndexKey(tr) if err != nil { @@ -190,11 +202,13 @@ func tokensToOperators(tr tokenReader) (*Path, error) { keyName := p.val if len(p.val) == 0 { - return nil, fmt.Errorf("Key length is zero at %d", p.pos) + return nil, fmt.Errorf("key length is zero at %d", p.pos) } + if p.val[0] == '"' && p.val[len(p.val)-1] == '"' { keyName = p.val[1 : len(p.val)-1] } + q.operators = append( q.operators, &operator{ @@ -204,28 +218,28 @@ func tokensToOperators(tr tokenReader) (*Path, error) { }, }, ) - case pathWildcard: q.operators = append(q.operators, &operator{typ: opTypeNameWild}) case pathValue: q.captureEndValue = true - case pathWhere: case pathExpression: if len(q.operators) == 0 { - return nil, errors.New("Cannot add where clause on last key") + return nil, errors.New("cannot add where clause on last key") } + last := q.operators[len(q.operators)-1] if last.whereClauseBytes != nil { - return nil, errors.New("Expression on last key already set") + return nil, errors.New("expression on last key already set") } - last.whereClauseBytes = p.val + last.whereClauseBytes = p.val case pathError: return q, errors.New(string(p.val)) } } + return q, nil } diff --git a/result.go b/result.go index 8078ea9..eb2685d 100644 --- a/result.go +++ b/result.go @@ -6,12 +6,12 @@ import ( ) const ( - JsonObject = iota - JsonArray - JsonString - JsonNumber - JsonNull - JsonBool + JSONObject = iota + JSONArray + JSONString + JSONNumber + JSONNull + JSONBool ) type Result struct {