jsonpath/path.go

247 lines
5.0 KiB
Go

package jsonpath
import (
"errors"
"fmt"
"strconv"
)
const (
opTypeIndex = iota
opTypeIndexRange
opTypeIndexWild
opTypeName
opTypeNameList
opTypeNameWild
)
type Path struct {
stringValue string
operators []*operator
captureEndValue bool
}
type operator struct {
typ int
indexStart int
indexEnd int
hasIndexEnd bool
keyStrings map[string]struct{}
whereClauseBytes []byte
dependentPaths []*Path
whereClause []Item
}
// 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")
}
switch t.typ {
case pathWildcard:
k.typ = opTypeIndexWild
k.indexStart = 0
if t, ok = tr.next(); !ok {
return nil, errors.New("expected ] after *, but got none")
}
if t.typ != pathBracketRight {
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)
}
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")
}
switch t.typ {
case pathIndexRange:
if t, ok = tr.next(); !ok {
return nil, errors.New("expected number or *, but got none")
}
switch t.typ {
case pathIndex:
v, err := strconv.Atoi(string(t.val))
if err != nil {
return nil, fmt.Errorf("could not parse %q into int64", t.val)
}
k.indexEnd = v - 1
k.hasIndexEnd = true
if t, ok = tr.next(); !ok || t.typ != pathBracketRight {
return nil, errors.New("expected ], but got none")
}
case pathBracketRight:
k.hasIndexEnd = false
default:
return nil, fmt.Errorf("unexpected value within brackets after index: %q", t.val)
}
k.typ = opTypeIndexRange
case pathBracketRight:
k.typ = opTypeIndex
default:
return nil, fmt.Errorf("unexpected value within brackets after index: %q", t.val)
}
case pathKey:
k.keyStrings = map[string]struct{}{
string(t.val[1 : len(t.val)-1]): {},
}
k.typ = opTypeName
if t, ok = tr.next(); !ok || t.typ != pathBracketRight {
return nil, errors.New("expected ], but got none")
}
default:
return nil, fmt.Errorf("unexpected value within brackets: %q", t.val)
}
return k, nil
}
func parsePath(pathString string) (*Path, error) {
lexer := NewSliceLexer([]byte(pathString), PATH)
p, err := tokensToOperators(lexer)
if err != nil {
return nil, err
}
p.stringValue = pathString
//Generate dependent paths
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))
}
// transform expression into postfix form
op.whereClause, err = infixToPostFix(items[:len(items)-1]) // trim EOF
if err != nil {
return nil, err
}
op.dependentPaths = make([]*Path, 0)
// parse all paths in expression
for _, item := range op.whereClause {
if item.typ == exprPath {
p, err := parsePath(string(item.val))
if err != nil {
return nil, err
}
op.dependentPaths = append(op.dependentPaths, p)
}
}
}
}
return p, nil
}
func tokensToOperators(tr tokenReader) (*Path, error) {
q := &Path{
stringValue: "",
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")
}
continue
case pathCurrent:
if len(q.operators) != 0 {
return nil, errors.New("unexpected current node after start")
}
continue
case pathPeriod:
continue
case pathBracketLeft:
k, err := genIndexKey(tr)
if err != nil {
return nil, err
}
q.operators = append(q.operators, k)
case pathKey:
keyName := p.val
if len(p.val) == 0 {
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{
typ: opTypeName,
keyStrings: map[string]struct{}{
string(keyName): {},
},
},
)
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")
}
last := q.operators[len(q.operators)-1]
if last.whereClauseBytes != nil {
return nil, errors.New("expression on last key already set")
}
last.whereClauseBytes = p.val
case pathError:
return q, errors.New(string(p.val))
}
}
return q, nil
}