diff --git a/.github/workflows/testing.yaml b/.github/workflows/testing.yaml
new file mode 100644
index 0000000..f311ebe
--- /dev/null
+++ b/.github/workflows/testing.yaml
@@ -0,0 +1,23 @@
+name: Run Go tests
+on: [ push ]
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Setup Go
+ uses: actions/setup-go@v5
+ with:
+ go-version: '1.23.x'
+
+ - name: Install dependencies
+ run: go get .
+
+ - name: Build
+ run: go build
+
+ - name: Test with the Go CLI
+ run: go test
\ No newline at end of file
diff --git a/README.md b/README.md
index 657e255..cc76d23 100644
--- a/README.md
+++ b/README.md
@@ -8,8 +8,8 @@
A traefik Plugin for securing the upstream service with OpenID Connect acting as a relying party.
-> [!NOTE]
-> This document always represents the latest version, which may not have been released yet.
+> [!NOTE]
+> This document always represents the latest version, which may not have been released yet.
> Therefore, some features may not be available currently but will be available soon.
> You can use the GIT-Tags to check individual versions.
@@ -51,14 +51,18 @@ http:
Authorization:
AssertClaims:
- Name: "preferred_username"
- Values: "alice@gmail.com,bob@gmail.com"
+ AnyOf: ["alice@gmail.com", "bob@gmail.com"]
+ - Name: "roles"
+ AllOf: ["admin", "media"]
+ - Name: "user.first_name"
+ AnyOf: ["Alice"]
Headers:
MapClaims:
- Claim: "preferred_username"
Header: "X-Oidc-Username"
- Claim: "sub"
Header: "X-Oidc-Subject"
-
+
routers:
whoami:
entryPoints: ["web"]
@@ -110,11 +114,69 @@ http:
### ClaimAssertion Block
+If only the `Name` property is set and no additional assertions are defined it is only checked whether there exist any matches for the name of this claim without any verification on their values.
+Additionaly, the `Name` field can be any [json path](https://jsonpath.com/). The `Name` gets prefixed with `$.` to match from the root element. The usage of json paths allows for assertions on deeply nested json structures.
+
| Name | Required | Type | Default | Description |
|---|---|---|---|---|
| Name | yes | `string` | *none* | The name of the claim in the access token. |
-| Value | no | `string` | *none* | The required value of the claim. If *Value* and *Values* are not set, only the presence of the claim will be checked. |
-| Values | no | `string[]` | *none* | An array of allowed strings. The user is authorized if the claim matched any of these. |
+| AnyOf | no | `string[]` | *none* | An array of allowed strings. The user is authorized if any value matching the name of the claim contains (or is) a value of this array. |
+| AllOf | no | `string[]` | *none* | An array of required strings. The user is only authorized if any value matching the name of the claim contains (or is) a value of this array and all values of this array are covered in the end. |
+
+It is possible to combine `AnyOf` and `AllOf` quantifiers for one assertion
+
+
+
+ Examples
+
+ All of the examples below work on this json structure:
+
+ ```json
+ {
+ "store": {
+ "bicycle": {
+ "color": "red",
+ "price": 19.95
+ },
+ "book": [
+ {
+ "author": "Herman Melville",
+ "category": "fiction",
+ "isbn": "0-553-21311-3",
+ "price": 8.99,
+ "title": "Moby Dick"
+ },
+ {
+ "author": "J. R. R. Tolkien",
+ "category": "fiction",
+ "isbn": "0-395-19395-8",
+ "price": 22.99,
+ "title": "The Lord of the Rings"
+ }
+ ],
+ }
+ }
+ ```
+
+ **Example**: Expect array to contain a set of values
+ ```yaml
+ Name: store.book[*].price
+ AllOf: [ 22.99, 8.99 ]
+ ```
+ This assertion would succeed as the `book` array contains all values specified by the `AllOf` quantifier
+ ```yaml
+ Name: store.book[*].price
+ AllOf: [ 22.99, 8.99, 1 ]
+ ```
+ This assertion would fail as the `book` array contains no entry for which the `price` is `1`
+
+ **Example**: Expect object key to be any value of a set of values
+ ```yaml
+ Name: store.bicycle.color
+ AnyOf: [ "red", "blue", "green" ]
+ ```
+ This assertion would succeed as the `store` object contains a `bicycle` object whose `color` is `red`
+
### Headers Block
@@ -141,5 +203,5 @@ CLIENT_SECRET=...
The run `docker compose up` to run traefik locally.
-Now browse to http://localhost:9080. You should be redirected to your IDP.
+Now browse to http://localhost:9080. You should be redirected to your IDP.
After you've logged in, you should be redirected back to http://localhost:9080 and see a WHOAMI page.
diff --git a/authorization.go b/authorization.go
new file mode 100644
index 0000000..8c4597a
--- /dev/null
+++ b/authorization.go
@@ -0,0 +1,131 @@
+package traefik_oidc_auth
+
+import (
+ "encoding/json"
+ "fmt"
+ "slices"
+ "strings"
+
+ "github.com/golang-jwt/jwt/v5"
+ "github.com/spyzhov/ajson"
+)
+
+func (toa *TraefikOidcAuth) isAuthorized(claims *jwt.MapClaims) bool {
+ authorization := toa.Config.Authorization
+
+ if authorization.AssertClaims != nil && len(authorization.AssertClaims) > 0 {
+ parsed, err := json.Marshal(*claims)
+ if err != nil {
+ log(toa.Config.LogLevel, LogLevelWarn, "Error whilst marshalling claims object: %s", err.Error())
+ return false
+ }
+
+ assertions:
+ for _, assertion := range authorization.AssertClaims {
+ value, err := ajson.JSONPath(parsed, fmt.Sprintf("$.%s", assertion.Name))
+ if err != nil {
+ log(toa.Config.LogLevel, LogLevelWarn, "Error whilst parsing path for claim %s in token claims: %s", assertion.Name, err.Error())
+ return false
+ } else if len(value) == 0 {
+ log(toa.Config.LogLevel, LogLevelWarn, "Unauthorized. Unable to find claim %s in token claims.", assertion.Name)
+ return false
+ }
+
+ if len(assertion.AllOf) == 0 && len(assertion.AnyOf) == 0 {
+ log(toa.Config.LogLevel, LogLevelDebug, "Authorized claim %s. No assertions were defined and claim exists", assertion.Name)
+ continue assertions
+ }
+
+ // check all matched nodes whether for one of the nodes all assertions hold
+ // should the assertions hold for no node we return `false` to indicate
+ // an unauthorized state
+
+ allMatches := make([]bool, len(assertion.AllOf))
+ anyMatch := false
+
+ matches:
+ for _, val := range value {
+ unpacked, err := val.Unpack()
+ if err != nil {
+ log(toa.Config.LogLevel, LogLevelError, "Error whilst unpacking json node: %s", err.Error())
+ continue matches
+ }
+
+ switch val := unpacked.(type) {
+ // the value is any array
+ case []interface{}:
+ mapped := make([]string, len(val))
+ for i, rawVal := range val {
+ mapped[i] = fmt.Sprintf("%v", rawVal)
+ }
+
+ // first check whether allOf assertion is fulfilled -> return false if not
+ if len(assertion.AllOf) > 0 {
+ for _, assert := range assertion.AllOf {
+ if !slices.Contains(mapped, assert) {
+ break matches
+ }
+ }
+ }
+ // should allOf assertion be fulfilled check whether anyOf assertion is fulfilled -> return true when fulfilled
+ if len(assertion.AnyOf) > 0 {
+ for _, assert := range assertion.AnyOf {
+ if slices.Contains(mapped, assert) {
+ log(toa.Config.LogLevel, LogLevelDebug, "Authorized claim %s: Found value %s which is any of [%s]", assertion.Name, assert, strings.Join(assertion.AnyOf, ", "))
+ continue assertions
+ }
+ }
+ continue matches
+ }
+ log(toa.Config.LogLevel, LogLevelDebug, "Authorized claim %s: Found all values of [%s]", assertion.Name, strings.Join(assertion.AllOf, ", "))
+ continue assertions
+ // the value is any other json type
+ default:
+ strVal := fmt.Sprintf("%v", val)
+ if len(assertion.AnyOf) > 0 {
+ if slices.Contains(assertion.AnyOf, strVal) {
+ anyMatch = true
+ }
+ }
+ if len(assertion.AllOf) > 0 {
+ for i, assert := range assertion.AllOf {
+ if assert == strVal {
+ allMatches[i] = true
+ break
+ }
+ }
+ }
+ continue matches
+ }
+ }
+
+ if len(assertion.AnyOf) > 0 && anyMatch && len(assertion.AllOf) > 0 && !slices.Contains(allMatches, false) {
+ log(toa.Config.LogLevel, LogLevelDebug, "Authorized claim %s: Found any value of [%s] and all values of [%s]", assertion.Name, strings.Join(assertion.AnyOf, ", "), strings.Join(assertion.AllOf, ", "))
+ continue assertions
+ } else if len(assertion.AnyOf) > 0 && anyMatch && len(assertion.AllOf) == 0 {
+ log(toa.Config.LogLevel, LogLevelDebug, "Authorized claim %s: Found any value of [%s]", assertion.Name, strings.Join(assertion.AnyOf, ", "))
+ continue assertions
+ } else if len(assertion.AllOf) > 0 && !slices.Contains(allMatches, false) && len(assertion.AnyOf) == 0 {
+ log(toa.Config.LogLevel, LogLevelDebug, "Authorized claim %s: Found all values of [%s]", assertion.Name, strings.Join(assertion.AllOf, ", "))
+ continue assertions
+ }
+
+ if len(assertion.AllOf) > 0 && len(assertion.AnyOf) > 0 {
+ log(toa.Config.LogLevel, LogLevelWarn, "Unauthorized. Expected claim %s to contain any value of [%s] and all values of [%s]", assertion.Name, strings.Join(assertion.AnyOf, ", "), strings.Join(assertion.AllOf, ", "))
+ } else if len(assertion.AllOf) > 0 {
+ log(toa.Config.LogLevel, LogLevelWarn, "Unauthorized. Expected claim %s to contain all values of [%s]", assertion.Name, strings.Join(assertion.AllOf, ", "))
+ } else if len(assertion.AnyOf) > 0 {
+ log(toa.Config.LogLevel, LogLevelWarn, "Unauthorized. Expected claim %s to contain any value of [%s]", assertion.Name, strings.Join(assertion.AnyOf, ", "))
+ }
+
+ log(toa.Config.LogLevel, LogLevelDebug, "Available claims are:")
+ for key, val := range *claims {
+ log(toa.Config.LogLevel, LogLevelDebug, " %v = %v", key, val)
+ }
+
+ return false
+ }
+ }
+
+ return true
+}
diff --git a/authorization_test.go b/authorization_test.go
new file mode 100644
index 0000000..745c44e
--- /dev/null
+++ b/authorization_test.go
@@ -0,0 +1,230 @@
+package traefik_oidc_auth
+
+import (
+ "encoding/json"
+ "testing"
+
+ "github.com/golang-jwt/jwt/v5"
+)
+
+func createAuthInstance(claims []ClaimAssertion) TraefikOidcAuth {
+ return TraefikOidcAuth{
+ Config: &Config{
+ Authorization: &AuthorizationConfig{
+ AssertClaims: claims,
+ },
+ },
+ }
+}
+
+func getTestClaims() *jwt.MapClaims {
+ bytes := []byte(`{
+ "name": "Alice",
+ "age": 67,
+ "children": [
+ { "name": "Bob", "age": 25 },
+ { "name": "Eve", "age": 22 }
+ ],
+ "roles": [
+ "support",
+ "accountant",
+ "administrator"
+ ],
+ "address": {
+ "country": "USA",
+ "street": "Freedom Rd.",
+ "neighbours": [
+ "Joe",
+ "Sam"
+ ]
+ }
+ }`)
+
+ claims := jwt.MapClaims{}
+ err := json.Unmarshal(bytes, &claims)
+ if err != nil {
+ panic(err)
+ }
+ return &claims
+}
+
+func TestClaimNameExists(t *testing.T) {
+ claims := getTestClaims()
+ toa := createAuthInstance([]ClaimAssertion {
+ { Name: "name" },
+ })
+
+ if !toa.isAuthorized(claims) {
+ t.Fatal("Should authorize as a claim with the provided name exists")
+ }
+
+ toa = createAuthInstance([]ClaimAssertion {
+ { Name: "names" },
+ })
+
+ if toa.isAuthorized(claims) {
+ t.Fatal("Should not authorize as no claim with the provided name exists")
+ }
+}
+
+func TestSimpleAssertions(t *testing.T) {
+ claims := getTestClaims()
+ toa := createAuthInstance([]ClaimAssertion {
+ { Name: "name", AnyOf: []string { "Alice", "Bob", "Bruno" } },
+ })
+
+ if !toa.isAuthorized(claims) {
+ t.Fatal("Should authorize since value is any of the provided values")
+ }
+
+ toa = createAuthInstance([]ClaimAssertion {
+ { Name: "name", AnyOf: []string { "Ben", "Joe", "Sam" } },
+ })
+
+ if toa.isAuthorized(claims) {
+ t.Fatal("Should not authorize since value is none of the provided values")
+ }
+
+ toa = createAuthInstance([]ClaimAssertion {
+ { Name: "name", AllOf: []string { "Alice" } },
+ })
+
+ if !toa.isAuthorized(claims) {
+ t.Fatal("Should authorize since the single value matches all of the provided values")
+ }
+
+ toa = createAuthInstance([]ClaimAssertion {
+ { Name: "name", AllOf: []string { "Alice", "Bob", "Bruno" } },
+ })
+
+ if toa.isAuthorized(claims) {
+ t.Fatal("Should not authorize since the single value match cannot contain all values of array")
+ }
+}
+
+func TestNestedAssertions(t *testing.T) {
+ claims := getTestClaims()
+ toa := createAuthInstance([]ClaimAssertion {
+ { Name: "address.street", AnyOf: []string { "Freedom Rd.", "Eagle St." } },
+ })
+
+ if !toa.isAuthorized(claims) {
+ t.Fatal("Should authorize since nested value is any of the provided values")
+ }
+
+ toa = createAuthInstance([]ClaimAssertion {
+ { Name: "address.street", AnyOf: []string { "Concrete HWY" } },
+ })
+
+ if toa.isAuthorized(claims) {
+ t.Fatal("Should not authorize since nested value is none of the provided values")
+ }
+
+ toa = createAuthInstance([]ClaimAssertion {
+ { Name: "address.street", AllOf: []string { "Freedom Rd." } },
+ })
+
+ if !toa.isAuthorized(claims) {
+ t.Fatal("Should authorize since the single value matches all of the provided values")
+ }
+
+ toa = createAuthInstance([]ClaimAssertion {
+ { Name: "address.street", AllOf: []string { "Freedom Rd.", "Eagle St." } },
+ })
+
+ if toa.isAuthorized(claims) {
+ t.Fatal("Should not authorize since the single value match cannot contain all values of array")
+ }
+}
+
+func TestArrayAssertions(t *testing.T) {
+ claims := getTestClaims()
+ toa := createAuthInstance([]ClaimAssertion {
+ { Name: "children[*].name", AnyOf: []string { "Joe", "Bob", "Sam" } },
+ })
+
+ if !toa.isAuthorized(claims) {
+ t.Fatal("Should authorize since some of the values are part of the provided values")
+ }
+
+ toa = createAuthInstance([]ClaimAssertion {
+ { Name: "children[*].name", AnyOf: []string { "Joe", "Sam", "Alex" } },
+ })
+
+ if toa.isAuthorized(claims) {
+ t.Fatal("Should not authorize since values are none of the provided values")
+ }
+
+ toa = createAuthInstance([]ClaimAssertion {
+ { Name: "children[*].name", AllOf: []string { "Bob", "Eve" } },
+ })
+
+ if !toa.isAuthorized(claims) {
+ t.Fatal("Should authorize since all of the provided values have a matching claim value")
+ }
+
+ toa = createAuthInstance([]ClaimAssertion {
+ { Name: "children[*].name", AllOf: []string { "Bob", "Eve", "Alex" } },
+ })
+
+ if toa.isAuthorized(claims) {
+ t.Fatal("Should not authorize since not all of the provided values have a matching claim value")
+ }
+}
+
+func TestCombinedAssertions(t *testing.T) {
+ claims := getTestClaims()
+ toa := createAuthInstance([]ClaimAssertion {
+ { Name: "children[*].name", AnyOf: []string { "Joe", "Bob", "Sam" }, AllOf: []string { "Eve", "Bob" } },
+ })
+
+ if !toa.isAuthorized(claims) {
+ t.Fatal("Should authorize since both assertion quantifiers have matching values")
+ }
+
+ toa = createAuthInstance([]ClaimAssertion {
+ { Name: "children[*].name", AnyOf: []string { "Joe", "Bob", "Sam" }, AllOf: []string { "Eve", "Bob", "Alex" } },
+ })
+
+ if toa.isAuthorized(claims) {
+ t.Fatal("Should not authorize since not all values of the allOf quantifier are matched")
+ }
+
+ toa = createAuthInstance([]ClaimAssertion {
+ { Name: "children[*].name", AnyOf: []string { "Sam" }, AllOf: []string { "Eve", "Bob" } },
+ })
+
+ if toa.isAuthorized(claims) {
+ t.Fatal("Should not authorize since no value of the anyOf quantifier is matched")
+ }
+}
+
+func TestMultipleAssertions(t *testing.T) {
+ claims := getTestClaims()
+ toa := createAuthInstance([]ClaimAssertion {
+ { Name: "children[*].name", AnyOf: []string { "Joe", "Bob", "Sam" }, AllOf: []string { "Eve", "Bob" } },
+ { Name: "name", AnyOf: []string { "Alice", "Alex" } },
+ })
+
+ if !toa.isAuthorized(claims) {
+ t.Fatal("Should authorize since both assertions hold against the provided claims")
+ }
+
+ toa = createAuthInstance([]ClaimAssertion {
+ { Name: "children[*].name", AnyOf: []string { "Joe", "Bob", "Sam" }, AllOf: []string { "Eve", "Bob", "Alex" } },
+ { Name: "name", AnyOf: []string { "Alice", "Alex" } },
+ })
+
+ if toa.isAuthorized(claims) {
+ t.Fatal("Should not authorize since one of the assertions does not hold")
+ }
+
+ toa = createAuthInstance([]ClaimAssertion {
+ { Name: "children[*].name", AnyOf: []string { "Joe", "Bob", "Sam" }, AllOf: []string { "Eve", "Bob", "Alex" } },
+ { Name: "name", AnyOf: []string { "Alex", "Ben" } },
+ })
+
+ if toa.isAuthorized(claims) {
+ t.Fatal("Should not authorize since both of the assertions do not hold")
+ }
+}
\ No newline at end of file
diff --git a/config.go b/config.go
index 8d88df3..a6c3418 100644
--- a/config.go
+++ b/config.go
@@ -72,9 +72,9 @@ type AuthorizationConfig struct {
}
type ClaimAssertion struct {
- Name string `json:"name"`
- Value string `json:"value"`
- Values []string `json:"values"`
+ Name string `json:"name"`
+ AnyOf []string `json:"anyOf"`
+ AllOf []string `json:"allOf"`
}
type HeadersConfig struct {
diff --git a/go.mod b/go.mod
index 78f1e16..1a0292c 100644
--- a/go.mod
+++ b/go.mod
@@ -2,4 +2,7 @@ module github.com/sevensolutions/traefik-oidc-auth
go 1.21.4
-require github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
+require (
+ github.com/golang-jwt/jwt/v5 v5.2.1
+ github.com/spyzhov/ajson v0.9.4
+)
diff --git a/go.sum b/go.sum
index f56d3e6..18e28b8 100644
--- a/go.sum
+++ b/go.sum
@@ -1,2 +1,4 @@
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
+github.com/spyzhov/ajson v0.9.4 h1:MVibcTCgO7DY4IlskdqIlCmDOsUOZ9P7oKj8ifdcf84=
+github.com/spyzhov/ajson v0.9.4/go.mod h1:a6oSw0MMb7Z5aD2tPoPO+jq11ETKgXUr2XktHdT8Wt8=
diff --git a/http.yml b/http.yml
index 8d47783..2926e7f 100644
--- a/http.yml
+++ b/http.yml
@@ -23,6 +23,12 @@ http:
Header: "X-Oidc-Username"
- Claim: "sub"
Header: "X-Oidc-Subject"
+ # Authorization:
+ # AssertClaims:
+ # - Name: roles
+ # AnyOf: ["admin", "media"]
+ # - Name: some.nested.key
+ # AnyOf: ["some value"]
# If set, only the /login endpoint will initiate the login flow
#LoginUri: "/login"
#PostLoginRedirectUri: "/"
diff --git a/main.go b/main.go
index 25874fe..9641a04 100644
--- a/main.go
+++ b/main.go
@@ -8,11 +8,8 @@ import (
"io"
"net/http"
"net/url"
- "slices"
"strings"
"time"
-
- "github.com/golang-jwt/jwt/v5"
)
type TraefikOidcAuth struct {
@@ -226,49 +223,6 @@ func (toa *TraefikOidcAuth) handleLogout(rw http.ResponseWriter, req *http.Reque
http.Redirect(rw, req, endSessionURL.String(), http.StatusFound)
}
-func (toa *TraefikOidcAuth) isAuthorized(claims *jwt.MapClaims) bool {
- authorization := toa.Config.Authorization
-
- if authorization.AssertClaims != nil && len(authorization.AssertClaims) > 0 {
- for _, assertion := range authorization.AssertClaims {
- found := false
- isArray := assertion.Values != nil && len(assertion.Values) > 0
-
- for key, val := range *claims {
- strVal := fmt.Sprintf("%v", val)
- if key == assertion.Name {
- if isArray {
- if slices.Contains(assertion.Values, strVal) {
- found = true
- break
- }
- } else if assertion.Value == "" || assertion.Value == strVal {
- found = true
- break
- }
- }
- }
-
- if !found {
- if isArray {
- log(toa.Config.LogLevel, LogLevelWarn, "Unauthorized. Missing claim %s with value one of [%s].", assertion.Name, strings.Join(assertion.Values, ", "))
- } else {
- log(toa.Config.LogLevel, LogLevelWarn, "Unauthorized. Missing claim %s with value %s.", assertion.Name, assertion.Value)
- }
-
- log(toa.Config.LogLevel, LogLevelInfo, "Available claims are:")
- for key, val := range *claims {
- log(toa.Config.LogLevel, LogLevelInfo, " %v = %v", key, val)
- }
-
- return false
- }
- }
- }
-
- return true
-}
-
func (toa *TraefikOidcAuth) handleUnauthorized(rw http.ResponseWriter, req *http.Request) {
if toa.Config.LoginUri == "" {
toa.redirectToProvider(rw, req)
diff --git a/vendor/github.com/spyzhov/ajson/.gitignore b/vendor/github.com/spyzhov/ajson/.gitignore
new file mode 100644
index 0000000..72bfb67
--- /dev/null
+++ b/vendor/github.com/spyzhov/ajson/.gitignore
@@ -0,0 +1,19 @@
+# Binaries for programs and plugins
+*.exe
+*.exe~
+*.dll
+*.so
+*.dylib
+bin/
+
+# Test binary, build with `go test -c`
+*.test
+
+# Output of the go coverage tool, specifically when used with LiteIDE
+*.out
+
+# GoLand IDE=
+.idea
+
+# Golang
+vendor/
diff --git a/vendor/github.com/spyzhov/ajson/.golangci.yml b/vendor/github.com/spyzhov/ajson/.golangci.yml
new file mode 100644
index 0000000..5dcf2f0
--- /dev/null
+++ b/vendor/github.com/spyzhov/ajson/.golangci.yml
@@ -0,0 +1,13 @@
+linters:
+ disable-all: true
+ enable:
+# - gofmt # removed due to https://github.com/golangci/golangci-lint/issues/3229
+ - govet
+ - errcheck
+ - staticcheck
+ - unused
+ - gosimple
+ - ineffassign
+ - typecheck
+ - gosec
+ - unused
diff --git a/vendor/github.com/spyzhov/ajson/LICENSE b/vendor/github.com/spyzhov/ajson/LICENSE
new file mode 100644
index 0000000..28bce57
--- /dev/null
+++ b/vendor/github.com/spyzhov/ajson/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2019 Pyzhov Stepan
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/vendor/github.com/spyzhov/ajson/README.md b/vendor/github.com/spyzhov/ajson/README.md
new file mode 100644
index 0000000..5f0adf0
--- /dev/null
+++ b/vendor/github.com/spyzhov/ajson/README.md
@@ -0,0 +1,645 @@
+# Abstract JSON
+
+[![Build](https://github.com/spyzhov/ajson/actions/workflows/main.yml/badge.svg)](https://github.com/spyzhov/ajson/actions/workflows/main.yml)
+[![Go Report Card](https://goreportcard.com/badge/github.com/spyzhov/ajson)](https://goreportcard.com/report/github.com/spyzhov/ajson)
+[![GoDoc](https://godoc.org/github.com/spyzhov/ajson?status.svg)](https://godoc.org/github.com/spyzhov/ajson)
+[![codecov](https://codecov.io/gh/spyzhov/ajson/branch/master/graph/badge.svg)](https://codecov.io/gh/spyzhov/ajson)
+[![Awesome](https://cdn.rawgit.com/sindresorhus/awesome/d7305f38d29fed78fa85652e3a63e154dd8e8829/media/badge.svg)](https://github.com/avelino/awesome-go#json)
+
+Abstract [JSON](https://www.json.org/) is a small golang package provides a parser for JSON with support of JSONPath, in case when you are not sure in its structure.
+
+Method `Unmarshal` will scan all the byte slice to create a root node of JSON structure, with all its behaviors.
+
+Method `Marshal` will serialize current `Node` object to JSON structure.
+
+Each `Node` has its own type and calculated value, which will be calculated on demand.
+Calculated value saves in `atomic.Value`, so it's thread safe.
+
+Method `JSONPath` will returns slice of found elements in current JSON data, by [JSONPath](http://goessner.net/articles/JsonPath/) request.
+
+## Compare with other solutions
+
+Check the [cburgmer/json-path-comparison](https://cburgmer.github.io/json-path-comparison/) project.
+
+# Usage
+
+[Playground](https://play.golang.com/p/iIxkktxN0SK)
+
+```go
+package main
+
+import (
+ "fmt"
+ "github.com/spyzhov/ajson"
+)
+
+func main() {
+ json := []byte(`...`)
+
+ root, _ := ajson.Unmarshal(json)
+ nodes, _ := root.JSONPath("$..price")
+ for _, node := range nodes {
+ node.SetNumeric(node.MustNumeric() * 1.25)
+ node.Parent().AppendObject("currency", ajson.StringNode("", "EUR"))
+ }
+ result, _ := ajson.Marshal(root)
+
+ fmt.Printf("%s", result)
+}
+```
+
+# Console application
+
+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.9.4
+```
+
+Usage:
+
+```
+Usage: ajson [-mq] "jsonpath" ["input"]
+ Read JSON and evaluate it with JSONPath.
+Parameters:
+ -m, --multiline Input file/stream will be read as a multiline JSON. Each line should have a full valid JSON.
+ -q, --quiet Do not print errors into the STDERR.
+Argument:
+ jsonpath Valid JSONPath or evaluate string (Examples: "$..[?(@.price)]", "$..price", "avg($..price)")
+ input Path to the JSON file. Leave it blank to use STDIN.
+```
+
+Examples:
+
+```shell script
+ ajson "avg($..registered.age)" "https://randomuser.me/api/?results=5000"
+ ajson "$.results.*.name" "https://randomuser.me/api/?results=10"
+ curl -s "https://randomuser.me/api/?results=10" | ajson "$..coordinates"
+ ajson "$" example.json
+ echo "3" | ajson "2 * pi * $"
+ docker logs image-name -f | ajson -qm 'root($[?(@=="ERROR" && key(@)=="severity")])'
+```
+
+# JSONPath
+
+Current package supports JSONPath selection described at [http://goessner.net/articles/JsonPath/](http://goessner.net/articles/JsonPath/).
+
+JSONPath expressions always refer to a JSON structure in the same way as XPath expression are used in combination with an XML document.
+Since a JSON structure is usually anonymous and doesn't necessarily have a "root member object" JSONPath assumes the abstract name $ assigned to the outer level object.
+
+JSONPath expressions can use the dot–notation
+
+`$.store.book[0].title`
+
+or the bracket–notation
+
+`$['store']['book'][0]['title']`
+
+for input paths. Internal or output paths will always be converted to the more general bracket–notation.
+
+JSONPath allows the wildcard symbol `*` for member names and array indices.
+It borrows the descendant operator `..` from E4X and the array slice syntax proposal `[start:end:step]` from ECMASCRIPT 4.
+
+Expressions of the underlying scripting language `()` can be used as an alternative to explicit names or indices as in
+
+`$.store.book[(@.length-1)].title`
+
+using the symbol `@` for the current object. Filter expressions are supported via the syntax `?()` as in
+
+`$.store.book[?(@.price < 10)].title`
+
+Here is a complete overview and a side by side comparison of the JSONPath syntax elements with its XPath counterparts.
+
+| JSONPath | Description |
+|----------|---|
+| `$` | the root object/element |
+| `@` | the current object/element |
+| `.` or `[]` | child operator |
+| `..` | recursive descent. JSONPath borrows this syntax from E4X. |
+| `*` | wildcard. All objects/elements regardless their names. |
+| `[]` | subscript operator. XPath uses it to iterate over element collections and for predicates. In Javascript and JSON it is the native array operator. |
+| `[,]` | Union operator in XPath results in a combination of node sets. JSONPath allows alternate names or array indices as a set. |
+| `[start:end:step]` | array slice operator borrowed from ES4. |
+| `?()` | applies a filter (script) expression. |
+| `()` | script expression, using the underlying script engine. |
+
+## Script engine
+
+### Predefined constant
+
+Package has several predefined constants.
+
+ e math.E float64
+ pi math.Pi float64
+ phi math.Phi float64
+
+ sqrt2 math.Sqrt2 float64
+ sqrte math.SqrtE float64
+ sqrtpi math.SqrtPi float64
+ sqrtphi math.SqrtPhi float64
+
+ ln2 math.Ln2 float64
+ log2e math.Log2E float64
+ ln10 math.Ln10 float64
+ log10e math.Log10E float64
+
+ true true bool
+ false false bool
+ null nil interface{}
+
+You are free to add new one with function `AddConstant`:
+
+```go
+ AddConstant("c", NumericNode("speed of light in vacuum", 299_792_458))
+```
+
+#### Examples
+
+
+Using `true` in path
+
+[Playground](https://play.golang.org/p/h0oFLaE11Tn)
+
+```go
+package main
+
+import (
+ "fmt"
+
+ "github.com/spyzhov/ajson"
+)
+
+func main() {
+ json := []byte(`{"foo": [true, null, false, 1, "bar", true, 1e3], "bar": [true, "baz", false]}`)
+ result, _ := ajson.JSONPath(json, `$..[?(@ == true)]`)
+ fmt.Printf("Count of `true` values: %d", len(result))
+}
+```
+Output:
+```
+Count of `true` values: 3
+```
+
+
+Using `null` in eval
+
+[Playground](https://play.golang.org/p/wpqh1Fw5vWE)
+
+```go
+package main
+
+import (
+ "fmt"
+
+ "github.com/spyzhov/ajson"
+)
+
+func main() {
+ json := []byte(`{"foo": [true, null, false, 1, "bar", true, 1e3], "bar": [true, "baz", false]}`)
+ result, _ := ajson.JSONPath(json, `$..[?(@ == true)]`)
+ fmt.Printf("Count of `true` values: %d", len(result))
+}
+```
+Output:
+```
+Count of `true` values: 3
+```
+
+
+### Supported operations
+
+Package has several predefined operators.
+
+[Operator precedence](https://golang.org/ref/spec#Operator_precedence)
+
+ Precedence Operator
+ 6 **
+ 5 * / % << >> & &^
+ 4 + - | ^
+ 3 == != < <= > >= =~
+ 2 &&
+ 1 ||
+
+[Arithmetic operators](https://golang.org/ref/spec#Arithmetic_operators)
+
+ ** power integers, floats
+ + sum integers, floats, strings
+ - difference integers, floats
+ * product integers, floats
+ / quotient integers, floats
+ % remainder integers
+
+ & bitwise AND integers
+ | bitwise OR integers
+ ^ bitwise XOR integers
+ &^ bit clear (AND NOT) integers
+
+ << left shift integer << unsigned integer
+ >> right shift integer >> unsigned integer
+
+ == equals any
+ != not equals any
+ < less any
+ <= less or equals any
+ > larger any
+ >= larger or equals any
+ =~ equals regex string strings
+
+You are free to add new one with function `AddOperation`:
+
+```go
+ AddOperation("<>", 3, false, func(left *ajson.Node, right *ajson.Node) (node *ajson.Node, err error) {
+ result, err := left.Eq(right)
+ if err != nil {
+ return nil, err
+ }
+ return BoolNode("neq", !result), nil
+ })
+```
+
+#### Examples
+
+
+Using `regex` operator
+
+[Playground](https://play.golang.org/p/Lm_F4OGTMWl)
+
+```go
+package main
+
+import (
+ "fmt"
+
+ "github.com/spyzhov/ajson"
+)
+
+func main() {
+ json := []byte(`[{"name":"Foo","mail":"foo@example.com"},{"name":"bar","mail":"bar@example.org"}]`)
+ result, err := ajson.JSONPath(json, `$.[?(@.mail =~ '.+@example\\.com')]`)
+ if err != nil {
+ panic(err)
+ }
+ fmt.Printf("JSON: %s", result[0].Source())
+ // Output:
+ // JSON: {"name":"Foo","mail":"foo@example.com"}
+}
+
+```
+Output:
+```
+JSON: {"name":"Foo","mail":"foo@example.com"}
+```
+
+
+### Supported functions
+
+Package has several predefined functions.
+
+ abs math.Abs integers, floats
+ acos math.Acos integers, floats
+ acosh math.Acosh integers, floats
+ asin math.Asin integers, floats
+ asinh math.Asinh integers, floats
+ atan math.Atan integers, floats
+ atanh math.Atanh integers, floats
+ avg Average array of integers or floats
+ b64decode b64 Decoding string
+ b64encode b64 Encoding string
+ b64encoden b64 Encoding (no padding) string
+ cbrt math.Cbrt integers, floats
+ ceil math.Ceil integers, floats
+ cos math.Cos integers, floats
+ cosh math.Cosh integers, floats
+ erf math.Erf integers, floats
+ erfc math.Erfc integers, floats
+ erfcinv math.Erfcinv integers, floats
+ erfinv math.Erfinv integers, floats
+ exp math.Exp integers, floats
+ exp2 math.Exp2 integers, floats
+ expm1 math.Expm1 integers, floats
+ factorial N! unsigned integer
+ first Get first element any
+ floor math.Floor integers, floats
+ gamma math.Gamma integers, floats
+ j0 math.J0 integers, floats
+ j1 math.J1 integers, floats
+ key Key of element string
+ last Get last element any
+ length Length of array array, string
+ log math.Log integers, floats
+ log10 math.Log10 integers, floats
+ log1p math.Log1p integers, floats
+ log2 math.Log2 integers, floats
+ logb math.Logb integers, floats
+ not not any
+ parent Get parent element any
+ pow10 math.Pow10 integer
+ rand N*rand.Float64 float
+ randint rand.Intn integer
+ root Get root element any
+ round math.Round integers, floats
+ roundtoeven math.RoundToEven integers, floats
+ sin math.Sin integers, floats
+ sinh math.Sinh integers, floats
+ size Count of elements array, object
+ sum Sum array of integers or floats
+ sqrt math.Sqrt integers, floats
+ tan math.Tan integers, floats
+ tanh math.Tanh integers, floats
+ trunc math.Trunc integers, floats
+ y0 math.Y0 integers, floats
+ y1 math.Y1 integers, floats
+
+You are free to add new one with function `AddFunction`:
+
+```go
+ AddFunction("trim", func(node *ajson.Node) (result *Node, err error) {
+ if node.IsString() {
+ return StringNode("trim", strings.TrimSpace(node.MustString())), nil
+ }
+ return
+ })
+```
+
+#### Examples
+
+
+Using `avg` for array
+
+[Playground](https://play.golang.org/p/cM66hTE-CX1)
+
+```go
+package main
+
+import (
+ "fmt"
+
+ "github.com/spyzhov/ajson"
+)
+
+func main() {
+ json := []byte(`{"prices": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]}`)
+ root, err := ajson.Unmarshal(json)
+ if err != nil {
+ panic(err)
+ }
+ result, err := ajson.Eval(root, `avg($.prices)`)
+ if err != nil {
+ panic(err)
+ }
+ fmt.Printf("Avg price: %0.1f", result.MustNumeric())
+ // Output:
+ // Avg price: 5.5
+}
+```
+Output:
+```
+Avg price: 5.5
+```
+
+
+# Examples
+
+Calculating `AVG(price)` when object is heterogeneous.
+
+```json
+{
+ "store": {
+ "book": [
+ {
+ "category": "reference",
+ "author": "Nigel Rees",
+ "title": "Sayings of the Century",
+ "price": 8.95
+ },
+ {
+ "category": "fiction",
+ "author": "Evelyn Waugh",
+ "title": "Sword of Honour",
+ "price": 12.99
+ },
+ {
+ "category": "fiction",
+ "author": "Herman Melville",
+ "title": "Moby Dick",
+ "isbn": "0-553-21311-3",
+ "price": 8.99
+ },
+ {
+ "category": "fiction",
+ "author": "J. R. R. Tolkien",
+ "title": "The Lord of the Rings",
+ "isbn": "0-395-19395-8",
+ "price": 22.99
+ }
+ ],
+ "bicycle": {
+ "color": "red",
+ "price": 19.95
+ },
+ "tools": null
+ }
+}
+```
+
+## Unmarshal
+
+[Playground](https://play.golang.org/p/xny93dzjZCK)
+```go
+package main
+
+import (
+ "fmt"
+ "github.com/spyzhov/ajson"
+)
+
+func main() {
+ data := []byte(`{"store": {"book": [
+{"category": "reference", "author": "Nigel Rees", "title": "Sayings of the Century", "price": 8.95},
+{"category": "fiction", "author": "Evelyn Waugh", "title": "Sword of Honour", "price": 12.99},
+{"category": "fiction", "author": "Herman Melville", "title": "Moby Dick", "isbn": "0-553-21311-3", "price": 8.99},
+{"category": "fiction", "author": "J. R. R. Tolkien", "title": "The Lord of the Rings", "isbn": "0-395-19395-8", "price": 22.99}],
+"bicycle": {"color": "red", "price": 19.95}, "tools": null}}`)
+
+ root, err := ajson.Unmarshal(data)
+ if err != nil {
+ panic(err)
+ }
+
+ store := root.MustKey("store").MustObject()
+
+ var prices float64
+ size := 0
+ for _, objects := range store {
+ if objects.IsArray() && objects.Size() > 0 {
+ size += objects.Size()
+ for _, object := range objects.MustArray() {
+ prices += object.MustKey("price").MustNumeric()
+ }
+ } else if objects.IsObject() && objects.HasKey("price") {
+ size++
+ prices += objects.MustKey("price").MustNumeric()
+ }
+ }
+
+ if size > 0 {
+ fmt.Println("AVG price:", prices/float64(size))
+ } else {
+ fmt.Println("AVG price:", 0)
+ }
+}
+```
+
+## JSONPath:
+
+[Playground](https://play.golang.org/p/7twZHOd6dbT)
+```go
+package main
+
+import (
+ "fmt"
+ "github.com/spyzhov/ajson"
+)
+
+func main() {
+ data := []byte(`{"store": {"book": [
+{"category": "reference", "author": "Nigel Rees", "title": "Sayings of the Century", "price": 8.95},
+{"category": "fiction", "author": "Evelyn Waugh", "title": "Sword of Honour", "price": 12.99},
+{"category": "fiction", "author": "Herman Melville", "title": "Moby Dick", "isbn": "0-553-21311-3", "price": 8.99},
+{"category": "fiction", "author": "J. R. R. Tolkien", "title": "The Lord of the Rings", "isbn": "0-395-19395-8", "price": 22.99}],
+"bicycle": {"color": "red", "price": 19.95}, "tools": null}}`)
+
+ nodes, err := ajson.JSONPath(data, "$..price")
+ if err != nil {
+ panic(err)
+ }
+
+ var prices float64
+ size := len(nodes)
+ for _, node := range nodes {
+ prices += node.MustNumeric()
+ }
+
+ if size > 0 {
+ fmt.Println("AVG price:", prices/float64(size))
+ } else {
+ fmt.Println("AVG price:", 0)
+ }
+}
+```
+
+## Eval
+
+[Playground](https://play.golang.org/p/lTXnlRU3sgR)
+```go
+package main
+
+import (
+ "fmt"
+ "github.com/spyzhov/ajson"
+)
+
+func main() {
+ json := []byte(`{"store": {"book": [
+{"category": "reference", "author": "Nigel Rees", "title": "Sayings of the Century", "price": 8.95},
+{"category": "fiction", "author": "Evelyn Waugh", "title": "Sword of Honour", "price": 12.99},
+{"category": "fiction", "author": "Herman Melville", "title": "Moby Dick", "isbn": "0-553-21311-3", "price": 8.99},
+{"category": "fiction", "author": "J. R. R. Tolkien", "title": "The Lord of the Rings", "isbn": "0-395-19395-8", "price": 22.99}],
+"bicycle": {"color": "red", "price": 19.95}, "tools": null}}`)
+ root, err := ajson.Unmarshal(json)
+ if err != nil {
+ panic(err)
+ }
+ result, err := ajson.Eval(root, "avg($..price)")
+ if err != nil {
+ panic(err)
+ }
+ fmt.Println("AVG price:", result.MustNumeric())
+}
+```
+
+## Marshal
+
+[Playground](https://play.golang.org/p/i4gXXcA2VLU)
+```go
+package main
+
+import (
+ "fmt"
+ "github.com/spyzhov/ajson"
+)
+
+func main() {
+ json := []byte(`{"store": {"book": [
+{"category": "reference", "author": "Nigel Rees", "title": "Sayings of the Century", "price": 8.95},
+{"category": "fiction", "author": "Evelyn Waugh", "title": "Sword of Honour", "price": 12.99},
+{"category": "fiction", "author": "Herman Melville", "title": "Moby Dick", "isbn": "0-553-21311-3", "price": 8.99},
+{"category": "fiction", "author": "J. R. R. Tolkien", "title": "The Lord of the Rings", "isbn": "0-395-19395-8", "price": 22.99}],
+"bicycle": {"color": "red", "price": 19.95}, "tools": null}}`)
+ root := ajson.Must(ajson.Unmarshal(json))
+ result := ajson.Must(ajson.Eval(root, "avg($..price)"))
+ err := root.AppendObject("price(avg)", result)
+ if err != nil {
+ panic(err)
+ }
+ marshalled, err := ajson.Marshal(root)
+ if err != nil {
+ panic(err)
+ }
+ fmt.Printf("%s", marshalled)
+}
+```
+
+# Benchmarks
+
+Current package is comparable with `encoding/json` package.
+
+Test data:
+```json
+{ "store": {
+ "book": [
+ { "category": "reference",
+ "author": "Nigel Rees",
+ "title": "Sayings of the Century",
+ "price": 8.95
+ },
+ { "category": "fiction",
+ "author": "Evelyn Waugh",
+ "title": "Sword of Honour",
+ "price": 12.99
+ },
+ { "category": "fiction",
+ "author": "Herman Melville",
+ "title": "Moby Dick",
+ "isbn": "0-553-21311-3",
+ "price": 8.99
+ },
+ { "category": "fiction",
+ "author": "J. R. R. Tolkien",
+ "title": "The Lord of the Rings",
+ "isbn": "0-395-19395-8",
+ "price": 22.99
+ }
+ ],
+ "bicycle": {
+ "color": "red",
+ "price": 19.95
+ }
+ }
+}
+```
+JSONPath: `$.store..price`
+
+```
+$ go test -bench=. -cpu=1 -benchmem
+goos: linux
+goarch: amd64
+pkg: github.com/spyzhov/ajson
+BenchmarkUnmarshal_AJSON 138032 8762 ns/op 5344 B/op 95 allocs/op
+BenchmarkUnmarshal_JSON 117423 10502 ns/op 968 B/op 31 allocs/op
+BenchmarkJSONPath_all_prices 80908 14394 ns/op 7128 B/op 153 allocs/op
+```
+
+# License
+
+MIT licensed. See the [LICENSE](LICENSE) file for details.
diff --git a/vendor/github.com/spyzhov/ajson/buffer.go b/vendor/github.com/spyzhov/ajson/buffer.go
new file mode 100644
index 0000000..e332877
--- /dev/null
+++ b/vendor/github.com/spyzhov/ajson/buffer.go
@@ -0,0 +1,830 @@
+package ajson
+
+import (
+ "io"
+ "strings"
+
+ . "github.com/spyzhov/ajson/internal"
+)
+
+type buffer struct {
+ data []byte
+ length int
+ index int
+
+ last States
+ state States
+ class Classes
+}
+
+const __ = -1
+
+const (
+ quotes byte = '"'
+ quote byte = '\''
+ coma byte = ','
+ colon byte = ':'
+ backslash byte = '\\'
+ skipS byte = ' '
+ skipN byte = '\n'
+ skipR byte = '\r'
+ skipT byte = '\t'
+ bracketL byte = '['
+ bracketR byte = ']'
+ bracesL byte = '{'
+ bracesR byte = '}'
+ parenthesesL byte = '('
+ parenthesesR byte = ')'
+ dollar byte = '$'
+ at byte = '@'
+ dot byte = '.'
+ asterisk byte = '*'
+ plus byte = '+'
+ minus byte = '-'
+ //division byte = '/'
+ //exclamation byte = '!'
+ //caret byte = '^'
+ //signL byte = '<'
+ //signG byte = '>'
+ //signE byte = '='
+ //ampersand byte = '&'
+ //pipe byte = '|'
+ question byte = '?'
+)
+
+type (
+ rpn []string
+ tokens []string
+)
+
+var (
+ _null = []byte("null")
+ _true = []byte("true")
+ _false = []byte("false")
+)
+
+func newBuffer(body []byte) (b *buffer) {
+ b = &buffer{
+ length: len(body),
+ data: body,
+ last: GO,
+ state: GO,
+ }
+ return
+}
+
+func (b *buffer) current() (c byte, err error) {
+ if b.index < b.length {
+ return b.data[b.index], nil
+ }
+ return 0, io.EOF
+}
+
+func (b *buffer) next() (c byte, err error) {
+ err = b.step()
+ if err != nil {
+ return 0, err
+ }
+ return b.data[b.index], nil
+}
+
+func (b *buffer) slice(delta uint) ([]byte, error) {
+ if b.index+int(delta) > b.length {
+ return nil, io.EOF
+ }
+ return b.data[b.index : b.index+int(delta)], nil
+}
+
+func (b *buffer) move(delta int) error {
+ if b.index+delta >= b.length {
+ return io.EOF
+ }
+ if b.index+delta >= 0 {
+ b.index += delta
+ }
+ return nil
+}
+
+func (b *buffer) reset() {
+ b.last = GO
+}
+
+func (b *buffer) first() (c byte, err error) {
+ for ; b.index < b.length; b.index++ {
+ c = b.data[b.index]
+ if !(c == skipS || c == skipR || c == skipN || c == skipT) {
+ return c, nil
+ }
+ }
+ return 0, io.EOF
+}
+
+func (b *buffer) backslash() (result bool) {
+ for i := b.index - 1; i >= 0; i-- {
+ if b.data[i] == backslash {
+ result = !result
+ } else {
+ break
+ }
+ }
+ return
+}
+
+func (b *buffer) skip(s byte) error {
+ for ; b.index < b.length; b.index++ {
+ if b.data[b.index] == s && !b.backslash() {
+ return nil
+ }
+ }
+ return io.EOF
+}
+
+func (b *buffer) skipAny(s map[byte]bool) error {
+ for ; b.index < b.length; b.index++ {
+ if s[b.data[b.index]] && !b.backslash() {
+ return nil
+ }
+ }
+ return io.EOF
+}
+
+// if token is true - skip error from StateTransitionTable, just stop on unknown state
+func (b *buffer) numeric(token bool) error {
+ if token {
+ b.last = GO
+ }
+ for ; b.index < b.length; b.index++ {
+ b.class = b.getClasses(quotes)
+ if b.class == __ {
+ return b.errorSymbol()
+ }
+ b.state = StateTransitionTable[b.last][b.class]
+ if b.state == __ {
+ if token {
+ break
+ }
+ return b.errorSymbol()
+ }
+ if b.state < __ {
+ return nil
+ }
+ if b.state < MI || b.state > E3 {
+ return nil
+ }
+ b.last = b.state
+ }
+ if b.last != ZE && b.last != IN && b.last != FR && b.last != E3 {
+ return b.errorSymbol()
+ }
+ return nil
+}
+
+func (b *buffer) getClasses(search byte) Classes {
+ if b.data[b.index] >= 128 {
+ return C_ETC
+ }
+ if search == quote {
+ return QuoteAsciiClasses[b.data[b.index]]
+ }
+ return AsciiClasses[b.data[b.index]]
+}
+
+func (b *buffer) getState() States {
+ b.last = b.state
+ b.class = b.getClasses(quotes)
+ if b.class == __ {
+ return __
+ }
+ b.state = StateTransitionTable[b.last][b.class]
+ return b.state
+}
+
+func (b *buffer) string(search byte, token bool) error {
+ if token {
+ b.last = GO
+ }
+ for ; b.index < b.length; b.index++ {
+ b.class = b.getClasses(search)
+
+ if b.class == __ {
+ return b.errorSymbol()
+ }
+ b.state = StateTransitionTable[b.last][b.class]
+ if b.state == __ {
+ return b.errorSymbol()
+ }
+ if b.state < __ {
+ return nil
+ }
+ b.last = b.state
+ }
+ return b.errorSymbol()
+}
+
+func (b *buffer) null() error {
+ return b.word(_null)
+}
+
+func (b *buffer) true() error {
+ return b.word(_true)
+}
+
+func (b *buffer) false() error {
+ return b.word(_false)
+}
+
+func (b *buffer) word(word []byte) error {
+ var c byte
+ max := len(word)
+ index := 0
+ for ; b.index < b.length; b.index++ {
+ c = b.data[b.index]
+ // if c != word[index] && c != (word[index]-32) {
+ if c != word[index] {
+ return errorSymbol(b)
+ }
+ index++
+ if index >= max {
+ break
+ }
+ }
+ if index != max {
+ return errorEOF(b)
+ }
+ return nil
+}
+
+func (b *buffer) step() error {
+ if b.index+1 < b.length {
+ b.index++
+ return nil
+ }
+ return io.EOF
+}
+
+// reads until the end of the token e.g.: `@.length`, `@['foo'].bar[(@.length - 1)].baz`
+func (b *buffer) token() (err error) {
+ var (
+ c byte
+ stack = make([]byte, 0)
+ first = b.index
+ start int
+ find bool
+ )
+tokenLoop:
+ for ; b.index < b.length; b.index++ {
+ c = b.data[b.index]
+ switch {
+ case c == quotes:
+ fallthrough
+ case c == quote:
+ find = true
+ err = b.step()
+ if err != nil {
+ return b.errorEOF()
+ }
+ err = b.skip(c)
+ if err == io.EOF {
+ return b.errorEOF()
+ }
+ case c == bracketL:
+ find = true
+ stack = append(stack, c)
+ case c == bracketR:
+ find = true
+ if len(stack) == 0 {
+ if first == b.index {
+ return b.errorSymbol()
+ }
+ break tokenLoop
+ }
+ if stack[len(stack)-1] != bracketL {
+ return b.errorSymbol()
+ }
+ stack = stack[:len(stack)-1]
+ case c == parenthesesL:
+ find = true
+ stack = append(stack, c)
+ case c == parenthesesR:
+ find = true
+ if len(stack) == 0 {
+ if first == b.index {
+ return b.errorSymbol()
+ }
+ break tokenLoop
+ }
+ if stack[len(stack)-1] != parenthesesL {
+ return b.errorSymbol()
+ }
+ stack = stack[:len(stack)-1]
+ case c == dot || c == at || c == dollar || c == question || c == asterisk || (c >= 'A' && c <= 'z') || (c >= '0' && c <= '9'): // standard token name
+ find = true
+ continue
+ case len(stack) != 0:
+ find = true
+ continue
+ case c == minus || c == plus:
+ if !find {
+ find = true
+ start = b.index
+ err = b.numeric(true)
+ if err == nil || err == io.EOF {
+ b.index--
+ continue
+ }
+ b.index = start
+ }
+ fallthrough
+ default:
+ break tokenLoop
+ }
+ }
+ if len(stack) != 0 {
+ return b.errorEOF()
+ }
+ if first == b.index {
+ return b.step()
+ }
+ if b.index >= b.length {
+ return io.EOF
+ }
+ return nil
+}
+
+// Builder for `Reverse Polish notation`
+func (b *buffer) rpn() (result rpn, err error) {
+ var (
+ c byte
+ start int
+ temp string
+ current string
+ found bool
+ variable bool
+ stack = make([]string, 0)
+ )
+ for {
+ b.reset()
+ c, err = b.first()
+ if err != nil {
+ break
+ }
+ switch true {
+ case priorityChar[c]: // operations
+ if variable {
+ variable = false
+ current = b.operation()
+
+ if current == "" {
+ return nil, b.errorSymbol()
+ }
+
+ for len(stack) > 0 {
+ temp = stack[len(stack)-1]
+ found = false
+ if temp[0] >= 'A' && temp[0] <= 'z' { // function
+ found = true
+ } else if priority[temp] != 0 { // operation
+ if priority[temp] > priority[current] {
+ found = true
+ } else if priority[temp] == priority[current] && !rightOp[temp] {
+ found = true
+ }
+ }
+
+ if found {
+ stack = stack[:len(stack)-1]
+ result = append(result, temp)
+ } else {
+ break
+ }
+ }
+ stack = append(stack, current)
+ break
+ }
+ if c != minus && c != plus {
+ return nil, b.errorSymbol()
+ }
+ fallthrough // for numbers like `-1e6`
+ case (c >= '0' && c <= '9') || c == '.': // numbers
+ variable = true
+ start = b.index
+ err = b.numeric(true)
+ if err != nil {
+ return nil, err
+ }
+ current = string(b.data[start:b.index])
+ result = append(result, current)
+ b.index--
+ case c == quotes: // string
+ fallthrough
+ case c == quote: // string
+ variable = true
+ start = b.index
+ err = b.string(c, true)
+ if err != nil {
+ return nil, b.errorEOF()
+ }
+ current = string(b.data[start : b.index+1])
+ result = append(result, current)
+ case c == dollar || c == at: // variable : like @.length , $.expensive, etc.
+ variable = true
+ start = b.index
+ err = b.token()
+ if err != nil {
+ if err != io.EOF {
+ return nil, err
+ }
+ }
+ current = string(b.data[start:b.index])
+ result = append(result, current)
+ if err != nil {
+ //nolint:ineffassign
+ err = nil
+ } else {
+ b.index--
+ }
+ case c == parenthesesL: // (
+ variable = false
+ current = string(c)
+ stack = append(stack, current)
+ case c == parenthesesR: // )
+ variable = true
+ found = false
+ for len(stack) > 0 {
+ temp = stack[len(stack)-1]
+ stack = stack[:len(stack)-1]
+ if temp == "(" {
+ found = true
+ break
+ }
+ result = append(result, temp)
+ }
+ if !found { // have no parenthesesL
+ return nil, errorRequest("formula has no left parentheses")
+ }
+ default: // prefix functions or etc.
+ start = b.index
+ variable = true
+ for ; b.index < b.length; b.index++ {
+ c = b.data[b.index]
+ if c == parenthesesL { // function detection, example: sin(...), round(...), etc.
+ variable = false
+ break
+ }
+ if c < 'A' || c > 'z' {
+ if !(c >= '0' && c <= '9') && c != '_' { // constants detection, example: true, false, null, PI, e, etc.
+ break
+ }
+ }
+ }
+ current = strings.ToLower(string(b.data[start:b.index]))
+ b.index--
+ if !variable {
+ if _, found = functions[current]; !found {
+ return nil, errorRequest("wrong formula, '%s' is not a function", current)
+ }
+ stack = append(stack, current)
+ } else {
+ if _, found = constants[current]; !found {
+ return nil, errorRequest("wrong formula, '%s' is not a constant", current)
+ }
+ result = append(result, current)
+ }
+ }
+ err = b.step()
+ if err != nil {
+ break
+ }
+ }
+ if err == io.EOF {
+ err = nil // only io.EOF can be here
+ }
+
+ for len(stack) > 0 {
+ temp = stack[len(stack)-1]
+ _, ok := functions[temp]
+ if priority[temp] == 0 && !ok { // operations only
+ return nil, errorRequest("wrong formula, '%s' is not an operation or function", temp)
+ }
+ result = append(result, temp)
+ stack = stack[:len(stack)-1]
+ }
+
+ if len(result) == 0 {
+ return nil, b.errorEOF()
+ }
+
+ return
+}
+
+func (b *buffer) tokenize() (result tokens, err error) {
+ var (
+ c byte
+ start int
+ current string
+ variable bool
+ )
+ for {
+ b.reset()
+ c, err = b.first()
+ if err != nil {
+ break
+ }
+ switch true {
+ case priorityChar[c]: // operations
+ if variable || (c != minus && c != plus) {
+ variable = false
+ current = b.operation()
+
+ if current == "" {
+ return nil, b.errorSymbol()
+ }
+
+ result = append(result, current)
+ break
+ }
+ fallthrough // for numbers like `-1e6`
+ case (c >= '0' && c <= '9') || c == dot: // numbers
+ variable = true
+ start = b.index
+ err = b.numeric(true)
+ if err != nil && err != io.EOF {
+ if c == dot {
+ //nolint:ineffassign
+ err = nil
+ result = append(result, ".")
+ b.index = start
+ break
+ }
+ return nil, err
+ }
+ current = string(b.data[start:b.index])
+ result = append(result, current)
+ b.index--
+ case c == quotes: // string
+ fallthrough
+ case c == quote: // string
+ variable = true
+ start = b.index
+ err = b.string(c, true)
+ if err != nil {
+ return nil, b.errorEOF()
+ }
+ current = string(b.data[start : b.index+1])
+ result = append(result, current)
+ case c == dollar || c == at: // variable : like @.length , $.expensive, etc.
+ variable = true
+ start = b.index
+ err = b.token()
+ if err != nil {
+ if err != io.EOF {
+ return nil, err
+ }
+ }
+ current = string(b.data[start:b.index])
+ result = append(result, current)
+ if err != nil {
+ //nolint:ineffassign
+ err = nil
+ } else {
+ b.index--
+ }
+ case c == parenthesesL: // (
+ variable = false
+ current = string(c)
+ result = append(result, current)
+ case c == parenthesesR: // )
+ variable = true
+ current = string(c)
+ result = append(result, current)
+ default: // prefix functions or etc.
+ start = b.index
+ variable = true
+ for ; b.index < b.length; b.index++ {
+ c = b.data[b.index]
+ if c == parenthesesL { // function detection, example: sin(...), round(...), etc.
+ variable = false
+ break
+ }
+ if c < 'A' || c > 'z' {
+ if !(c >= '0' && c <= '9') && c != '_' { // constants detection, example: true, false, null, PI, e, etc.
+ break
+ }
+ }
+ }
+ if start == b.index {
+ err = b.step()
+ if err != nil {
+ //nolint:ineffassign
+ err = nil
+ current = strings.ToLower(string(b.data[start : b.index+1]))
+ } else {
+ current = strings.ToLower(string(b.data[start:b.index]))
+ b.index--
+ }
+ } else {
+ current = strings.ToLower(string(b.data[start:b.index]))
+ b.index--
+ }
+ result = append(result, current)
+ }
+ err = b.step()
+ if err != nil {
+ break
+ }
+ }
+
+ if err == io.EOF {
+ err = nil
+ }
+
+ return
+}
+
+func (b *buffer) operation() string {
+ current := ""
+
+ // Read the complete operation into the variable `current`: `+`, `!=`, `<=>`
+ // fixme: add additional order for comparison
+
+ for _, operation := range comparisonOperationsOrder() {
+ if bytes, ok := b.slice(uint(len(operation))); ok == nil {
+ if string(bytes) == operation {
+ current = operation
+ _ = b.move(len(operation) - 1) // error can't occupy here because of b.slice result
+ break
+ }
+ }
+ }
+ return current
+}
+
+func (b *buffer) errorEOF() error {
+ return errorEOF(b)
+}
+
+func (b *buffer) errorSymbol() error {
+ return errorSymbol(b)
+}
+
+func _floats(left, right *Node) (lnum, rnum float64, err error) {
+ lnum, err = left.GetNumeric()
+ if err != nil {
+ return
+ }
+ rnum, err = right.GetNumeric()
+ return
+}
+
+func _ints(left, right *Node) (lnum, rnum int, err error) {
+ lnum, err = left.getInteger()
+ if err != nil {
+ return
+ }
+ rnum, err = right.getInteger()
+ return
+}
+
+func _bools(left, right *Node) (lnum, rnum bool, err error) {
+ lnum, err = left.GetBool()
+ if err != nil {
+ return
+ }
+ rnum, err = right.GetBool()
+ return
+}
+
+func _strings(left, right *Node) (lnum, rnum string, err error) {
+ lnum, err = left.GetString()
+ if err != nil {
+ return
+ }
+ rnum, err = right.GetString()
+ return
+}
+
+func _arrays(left, right *Node) (lnum, rnum []*Node, err error) {
+ lnum, err = left.GetArray()
+ if err != nil {
+ return
+ }
+ rnum, err = right.GetArray()
+ return
+}
+
+func _objects(left, right *Node) (lnum, rnum map[string]*Node, err error) {
+ lnum, err = left.GetObject()
+ if err != nil {
+ return
+ }
+ rnum, err = right.GetObject()
+ return
+}
+
+func boolean(node *Node) (bool, error) {
+ switch node.Type() {
+ case Bool:
+ return node.GetBool()
+ case Numeric:
+ res, err := node.GetNumeric()
+ return res != 0, err
+ case String:
+ res, err := node.GetString()
+ return res != "", err
+ case Null:
+ return false, nil
+ case Array:
+ fallthrough
+ case Object:
+ return !node.Empty(), nil
+ }
+ return false, nil
+}
+
+func tokenize(cmd string) (result tokens, err error) {
+ buf := newBuffer([]byte(cmd))
+ return buf.tokenize()
+}
+
+func (t tokens) exists(find string) bool {
+ for _, s := range t {
+ if s == find {
+ return true
+ }
+ }
+ return false
+}
+
+func (t tokens) count(find string) int {
+ i := 0
+ for _, s := range t {
+ if s == find {
+ i++
+ }
+ }
+ return i
+}
+
+func (t tokens) slice(find string) []string {
+ n := len(t)
+ result := make([]string, 0, t.count(find))
+ from := 0
+ for i := 0; i < n; i++ {
+ if t[i] == find {
+ result = append(result, strings.Join(t[from:i], ""))
+ from = i + 1
+ }
+ }
+ result = append(result, strings.Join(t[from:n], ""))
+ return result
+}
+
+func str(key string) (string, bool) {
+ bString := []byte(key)
+ from := len(bString)
+ if from > 1 && (bString[0] == quotes && bString[from-1] == quotes) {
+ return unquote(bString, quotes)
+ }
+ if from > 1 && (bString[0] == quote && bString[from-1] == quote) {
+ return unquote(bString, quote)
+ }
+ return key, true
+ // todo quote string and unquote it:
+ // {
+ // bString = append([]byte{quotes}, bString...)
+ // bString = append(bString, quotes)
+ // }
+ // 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/vendor/github.com/spyzhov/ajson/decode.go b/vendor/github.com/spyzhov/ajson/decode.go
new file mode 100644
index 0000000..8d9a994
--- /dev/null
+++ b/vendor/github.com/spyzhov/ajson/decode.go
@@ -0,0 +1,231 @@
+package ajson
+
+import (
+ . "github.com/spyzhov/ajson/internal"
+)
+
+// List of action codes.
+// Copy from `internal/state.go:144`
+const (
+ cl States = -2 /* colon */
+ cm States = -3 /* comma */
+ //qt States = -4 /* quote */
+ bo States = -5 /* bracket open */
+ co States = -6 /* curly br. open */
+ bc States = -7 /* bracket close */
+ cc States = -8 /* curly br. close */
+ ec States = -9 /* curly br. empty */
+)
+
+// Unmarshal parses the JSON-encoded data and return the root node of struct.
+//
+// Doesn't calculate values, just type of stored value. It will store link to the data, on all life long.
+func Unmarshal(data []byte) (root *Node, err error) {
+ buf := newBuffer(data)
+ var (
+ state States
+ key *string
+ current *Node
+ useKey = func() **string {
+ tmp := cptrs(key)
+ key = nil
+ return &tmp
+ }
+ )
+
+ _, err = buf.first()
+ if err != nil {
+ return nil, buf.errorEOF()
+ }
+
+ for {
+ state = buf.getState()
+ if state == __ {
+ return nil, buf.errorSymbol()
+ }
+
+ if state >= GO {
+ // region Change State
+ switch buf.state {
+ case ST:
+ if current != nil && current.IsObject() && key == nil {
+ // Detected: Key
+ key, err = getString(buf)
+ buf.state = CO
+ } else {
+ // Detected: String
+ current, err = newNode(current, buf, String, useKey())
+ if err != nil {
+ break
+ }
+ err = buf.string(quotes, false)
+ current.borders[1] = buf.index + 1
+ buf.state = OK
+ if current.parent != nil {
+ current = current.parent
+ }
+ }
+ case MI, ZE, IN:
+ current, err = newNode(current, buf, Numeric, useKey())
+ if err != nil {
+ break
+ }
+ err = buf.numeric(false)
+ current.borders[1] = buf.index
+ buf.index -= 1
+ buf.state = OK
+ if current.parent != nil {
+ current = current.parent
+ }
+ case T1, F1:
+ current, err = newNode(current, buf, Bool, useKey())
+ if err != nil {
+ break
+ }
+ if buf.state == T1 {
+ err = buf.true()
+ } else {
+ err = buf.false()
+ }
+ current.borders[1] = buf.index + 1
+ buf.state = OK
+ if current.parent != nil {
+ current = current.parent
+ }
+ case N1:
+ current, err = newNode(current, buf, Null, useKey())
+ if err != nil {
+ break
+ }
+ err = buf.null()
+ current.borders[1] = buf.index + 1
+ buf.state = OK
+ if current.parent != nil {
+ current = current.parent
+ }
+ }
+ // endregion Change State
+ } else {
+ // region Action
+ switch state {
+ case ec: /* empty } */
+ if key != nil {
+ err = buf.errorSymbol()
+ }
+ fallthrough
+ case cc: /* } */
+ if current != nil && current.IsObject() && !current.ready() {
+ current.borders[1] = buf.index + 1
+ if current.parent != nil {
+ current = current.parent
+ }
+ } else {
+ err = buf.errorSymbol()
+ }
+ buf.state = OK
+ case bc: /* ] */
+ if current != nil && current.IsArray() && !current.ready() {
+ current.borders[1] = buf.index + 1
+ if current.parent != nil {
+ current = current.parent
+ }
+ } else {
+ err = buf.errorSymbol()
+ }
+ buf.state = OK
+ case co: /* { */
+ current, err = newNode(current, buf, Object, useKey())
+ buf.state = OB
+ case bo: /* [ */
+ current, err = newNode(current, buf, Array, useKey())
+ buf.state = AR
+ case cm: /* , */
+ if current == nil {
+ return nil, buf.errorSymbol()
+ }
+ if current.IsObject() {
+ buf.state = KE
+ } else if current.IsArray() {
+ buf.state = VA
+ } else {
+ err = buf.errorSymbol()
+ }
+ case cl: /* : */
+ if current == nil || !current.IsObject() || key == nil {
+ err = buf.errorSymbol()
+ } else {
+ buf.state = VA
+ }
+ default: /* syntax error */
+ err = buf.errorSymbol()
+ }
+ // endregion Action
+ }
+ if err != nil {
+ return
+ }
+ if buf.step() != nil {
+ break
+ }
+ if _, err = buf.first(); err != nil {
+ err = nil
+ break
+ }
+ }
+
+ if current == nil || buf.state != OK {
+ err = buf.errorEOF()
+ } else {
+ root = current.root()
+ if !root.ready() {
+ err = buf.errorEOF()
+ root = nil
+ }
+ }
+
+ return
+}
+
+// UnmarshalSafe do the same thing as Unmarshal, but copy data to the local variable, to make it editable.
+func UnmarshalSafe(data []byte) (root *Node, err error) {
+ var safe []byte
+ safe = append(safe, data...)
+ return Unmarshal(safe)
+}
+
+// Must returns a Node if there was no error. Else - panic with error as the value.
+func Must(root *Node, err error) *Node {
+ if err != nil {
+ panic(err)
+ }
+ return root
+}
+
+func getString(b *buffer) (*string, error) {
+ start := b.index
+ err := b.string(quotes, false)
+ if err != nil {
+ return nil, err
+ }
+ value, ok := unquote(b.data[start:b.index+1], quotes)
+ if !ok {
+ return nil, errorSymbol(b)
+ }
+ return &value, nil
+}
+
+func cptrs(cpy *string) *string {
+ if cpy == nil {
+ return nil
+ }
+ val := *cpy
+ return &val
+}
+
+func cptri(cpy *int) *int {
+ if cpy == nil {
+ return nil
+ }
+ val := *cpy
+ return &val
+}
diff --git a/vendor/github.com/spyzhov/ajson/doc.go b/vendor/github.com/spyzhov/ajson/doc.go
new file mode 100644
index 0000000..6c5d019
--- /dev/null
+++ b/vendor/github.com/spyzhov/ajson/doc.go
@@ -0,0 +1,163 @@
+// Package ajson implements decoding of JSON as defined in RFC 7159 without predefined mapping to a struct of golang, with support of JSONPath.
+//
+// All JSON structs reflects to a custom struct of Node, witch can be presented by it type and value.
+//
+// Method Unmarshal will scan all the byte slice to create a root node of JSON structure, with all it behaviors.
+//
+// Each Node has it's own type and calculated value, which will be calculated on demand.
+// Calculated value saves in atomic.Value, so it's thread safe.
+//
+// Method JSONPath will returns slice of founded elements in current JSON data, by it's JSONPath.
+//
+// JSONPath selection described at http://goessner.net/articles/JsonPath/
+//
+// JSONPath expressions always refer to a JSON structure in the same way as XPath expression are used in combination with an XML document. Since a JSON structure is usually anonymous and doesn't necessarily have a "root member object" JSONPath assumes the abstract name $ assigned to the outer level object.
+//
+// JSONPath expressions can use the dot–notation
+//
+// $.store.book[0].title
+//
+// or the bracket–notation
+//
+// $['store']['book'][0]['title']
+//
+// for input pathes. Internal or output pathes will always be converted to the more general bracket–notation.
+//
+// JSONPath allows the wildcard symbol * for member names and array indices. It borrows the descendant operator '..' from E4X and the array slice syntax proposal [start:end:step] from ECMASCRIPT 4.
+//
+// Expressions of the underlying scripting language () can be used as an alternative to explicit names or indices as in
+//
+// $.store.book[(@.length-1)].title
+//
+// using the symbol '@' for the current object. Filter expressions are supported via the syntax ?() as in
+//
+// $.store.book[?(@.price < 10)].title
+//
+// Here is a complete overview and a side by side comparison of the JSONPath syntax elements with its XPath counterparts.
+//
+// $ the root object/element
+// @ the current object/element
+// . or [] child operator
+// .. recursive descent. JSONPath borrows this syntax from E4X.
+// * wildcard. All objects/elements regardless their names.
+// [] subscript operator. XPath uses it to iterate over element collections and for predicates. In Javascript and JSON it is the native array operator.
+// [,] Union operator in XPath results in a combination of node sets. JSONPath allows alternate names or array indices as a set.
+// [start:end:step] array slice operator borrowed from ES4.
+// ?() applies a filter (script) expression.
+// () script expression, using the underlying script engine.
+//
+//
+// JSONPath Script engine
+//
+// Predefined constant
+//
+// Package has several predefined constants. You are free to add new one with AddConstant
+//
+// e math.E float64
+// pi math.Pi float64
+// phi math.Phi float64
+//
+// sqrt2 math.Sqrt2 float64
+// sqrte math.SqrtE float64
+// sqrtpi math.SqrtPi float64
+// sqrtphi math.SqrtPhi float64
+//
+// ln2 math.Ln2 float64
+// log2e math.Log2E float64
+// ln10 math.Ln10 float64
+// log10e math.Log10E float64
+//
+// true true bool
+// false false bool
+// null nil interface{}
+//
+// Supported operations
+//
+// Package has several predefined operators. You are free to add new one with AddOperator
+//
+// Operator precedence: https://golang.org/ref/spec#Operator_precedence
+//
+// Precedence Operator
+// 6 **
+// 5 * / % << >> & &^
+// 4 + - | ^
+// 3 == != < <= > >= =~
+// 2 &&
+// 1 ||
+//
+// Arithmetic operators: https://golang.org/ref/spec#Arithmetic_operators
+//
+// ** power integers, floats
+// + sum integers, floats, strings
+// - difference integers, floats
+// * product integers, floats
+// / quotient integers, floats
+// % remainder integers
+//
+// & bitwise AND integers
+// | bitwise OR integers
+// ^ bitwise XOR integers
+// &^ bit clear (AND NOT) integers
+//
+// << left shift integer << unsigned integer
+// >> right shift integer >> unsigned integer
+//
+// == equals any
+// != not equals any
+// < less any
+// <= less or equals any
+// > larger any
+// >= larger or equals any
+// =~ equals regex string strings
+//
+// Supported functions
+//
+// Package has several predefined functions. You are free to add new one with AddFunction
+//
+// abs math.Abs integers, floats
+// acos math.Acos integers, floats
+// acosh math.Acosh integers, floats
+// asin math.Asin integers, floats
+// asinh math.Asinh integers, floats
+// atan math.Atan integers, floats
+// atanh math.Atanh integers, floats
+// avg Average array of integers or floats
+// b64decode b64 Decoding string
+// b64encode b64 Encoding string
+// b64encoden b64 Encoding (no padding) string
+// cbrt math.Cbrt integers, floats
+// ceil math.Ceil integers, floats
+// cos math.Cos integers, floats
+// cosh math.Cosh integers, floats
+// erf math.Erf integers, floats
+// erfc math.Erfc integers, floats
+// erfcinv math.Erfcinv integers, floats
+// erfinv math.Erfinv integers, floats
+// exp math.Exp integers, floats
+// exp2 math.Exp2 integers, floats
+// expm1 math.Expm1 integers, floats
+// factorial N! unsigned integer
+// floor math.Floor integers, floats
+// gamma math.Gamma integers, floats
+// j0 math.J0 integers, floats
+// j1 math.J1 integers, floats
+// length len array
+// log math.Log integers, floats
+// log10 math.Log10 integers, floats
+// log1p math.Log1p integers, floats
+// log2 math.Log2 integers, floats
+// logb math.Logb integers, floats
+// not not any
+// pow10 math.Pow10 integer
+// round math.Round integers, floats
+// roundtoeven math.RoundToEven integers, floats
+// sin math.Sin integers, floats
+// sinh math.Sinh integers, floats
+// sqrt math.Sqrt integers, floats
+// tan math.Tan integers, floats
+// tanh math.Tanh integers, floats
+// trunc math.Trunc integers, floats
+// y0 math.Y0 integers, floats
+// y1 math.Y1 integers, floats
+//
+package ajson
diff --git a/vendor/github.com/spyzhov/ajson/encode.go b/vendor/github.com/spyzhov/ajson/encode.go
new file mode 100644
index 0000000..9dfd6f2
--- /dev/null
+++ b/vendor/github.com/spyzhov/ajson/encode.go
@@ -0,0 +1,90 @@
+package ajson
+
+import (
+ "strconv"
+)
+
+// Marshal returns slice of bytes, marshaled from current value
+func Marshal(node *Node) (result []byte, err error) {
+ result = make([]byte, 0)
+ var (
+ sValue string
+ bValue bool
+ nValue float64
+ oValue []byte
+ )
+
+ if node == nil {
+ return nil, errorUnparsed()
+ } else if node.dirty {
+ switch node._type {
+ case Null:
+ result = append(result, _null...)
+ case Numeric:
+ nValue, err = node.GetNumeric()
+ if err != nil {
+ return nil, err
+ }
+ result = append(result, strconv.FormatFloat(nValue, 'g', -1, 64)...)
+ case String:
+ sValue, err = node.GetString()
+ if err != nil {
+ return nil, err
+ }
+ result = append(result, quotes)
+ result = append(result, quoteString(sValue, true)...)
+ result = append(result, quotes)
+ case Bool:
+ bValue, err = node.GetBool()
+ if err != nil {
+ return nil, err
+ } else if bValue {
+ result = append(result, _true...)
+ } else {
+ result = append(result, _false...)
+ }
+ case Array:
+ result = append(result, bracketL)
+ for i := 0; i < len(node.children); i++ {
+ if i != 0 {
+ result = append(result, coma)
+ }
+ child, ok := node.children[strconv.Itoa(i)]
+ if !ok {
+ return nil, errorRequest("wrong length of array")
+ }
+ oValue, err = Marshal(child)
+ if err != nil {
+ return nil, err
+ }
+ result = append(result, oValue...)
+ }
+ result = append(result, bracketR)
+ case Object:
+ result = append(result, bracesL)
+ bValue = false
+ for key, child := range node.children {
+ if bValue {
+ result = append(result, coma)
+ } else {
+ bValue = true
+ }
+ result = append(result, quotes)
+ result = append(result, quoteString(key, true)...)
+ result = append(result, quotes, colon)
+ oValue, err = Marshal(child)
+ if err != nil {
+ return nil, err
+ }
+ result = append(result, oValue...)
+ }
+ result = append(result, bracesR)
+ }
+ } else if node.ready() {
+ result = append(result, node.Source()...)
+ } else {
+ return nil, errorUnparsed()
+ }
+
+ return
+}
diff --git a/vendor/github.com/spyzhov/ajson/errors.go b/vendor/github.com/spyzhov/ajson/errors.go
new file mode 100644
index 0000000..523d0ae
--- /dev/null
+++ b/vendor/github.com/spyzhov/ajson/errors.go
@@ -0,0 +1,102 @@
+package ajson
+
+import "fmt"
+
+// Error is common struct to provide internal errors
+type Error struct {
+ Type ErrorType
+ Index int
+ Char byte
+ Message string
+ Value interface{}
+}
+
+// ErrorType is container for reflection type of error
+type ErrorType int
+
+const (
+ // WrongSymbol means that system found symbol than not allowed to be
+ WrongSymbol ErrorType = iota
+ // UnexpectedEOF means that data ended, leaving the node undone
+ UnexpectedEOF
+ // WrongType means that wrong type requested
+ WrongType
+ // WrongRequest means that wrong range requested
+ WrongRequest
+ // Unparsed means that json structure wasn't parsed yet
+ Unparsed
+ // UnsupportedType means that wrong type was given
+ UnsupportedType
+)
+
+func errorSymbol(b *buffer) error {
+ symbol, err := b.current()
+ if err != nil {
+ symbol = 0
+ }
+ return Error{
+ Type: WrongSymbol,
+ Index: b.index,
+ Char: symbol,
+ }
+}
+
+func errorAt(index int, symbol byte) error {
+ return Error{
+ Type: WrongSymbol,
+ Index: index,
+ Char: symbol,
+ }
+}
+
+func errorEOF(b *buffer) error {
+ return Error{
+ Type: UnexpectedEOF,
+ Index: b.index,
+ }
+}
+
+func errorType() error {
+ return Error{
+ Type: WrongType,
+ }
+}
+
+func unsupportedType(value interface{}) error {
+ return Error{
+ Type: UnsupportedType,
+ Value: value,
+ }
+}
+
+func errorUnparsed() error {
+ return Error{
+ Type: Unparsed,
+ }
+}
+
+func errorRequest(format string, args ...interface{}) error {
+ return Error{
+ Type: WrongRequest,
+ Message: fmt.Sprintf(format, args...),
+ }
+}
+
+// Error interface implementation
+func (err Error) Error() string {
+ switch err.Type {
+ case WrongSymbol:
+ return fmt.Sprintf("wrong symbol '%s' at %d", []byte{err.Char}, err.Index)
+ case UnexpectedEOF:
+ 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:
+ return fmt.Sprintf("wrong request: %s", err.Message)
+ }
+ return fmt.Sprintf("unknown error: '%s' at %d", []byte{err.Char}, err.Index)
+}
diff --git a/vendor/github.com/spyzhov/ajson/internal/state.go b/vendor/github.com/spyzhov/ajson/internal/state.go
new file mode 100644
index 0000000..16bc2fc
--- /dev/null
+++ b/vendor/github.com/spyzhov/ajson/internal/state.go
@@ -0,0 +1,196 @@
+/*
+Copy from https://github.com/freddierice/php_source/blob/467ed5d6edff72219afd3e644516f131118ef48e/ext/json/JSON_parser.c
+Base code: Copyright (c) 2005 JSON.org
+*/
+package internal
+
+type (
+ States int8
+ Classes int8
+)
+
+const __ = -1
+
+// enum classes
+const (
+ C_SPACE Classes = iota /* space */
+ C_WHITE /* other whitespace */
+ C_LCURB /* { */
+ C_RCURB /* } */
+ C_LSQRB /* [ */
+ C_RSQRB /* ] */
+ C_COLON /* : */
+ C_COMMA /* , */
+ C_QUOTE /* " */
+ C_BACKS /* \ */
+ C_SLASH /* / */
+ C_PLUS /* + */
+ C_MINUS /* - */
+ C_POINT /* . */
+ C_ZERO /* 0 */
+ C_DIGIT /* 123456789 */
+ C_LOW_A /* a */
+ C_LOW_B /* b */
+ C_LOW_C /* c */
+ C_LOW_D /* d */
+ C_LOW_E /* e */
+ C_LOW_F /* f */
+ C_LOW_L /* l */
+ C_LOW_N /* n */
+ C_LOW_R /* r */
+ C_LOW_S /* s */
+ C_LOW_T /* t */
+ C_LOW_U /* u */
+ C_ABCDF /* ABCDF */
+ C_E /* E */
+ C_ETC /* everything else */
+)
+
+// AsciiClasses array maps the 128 ASCII characters into character classes.
+var AsciiClasses = [128]Classes{
+ /*
+ This array maps the 128 ASCII characters into character classes.
+ The remaining Unicode characters should be mapped to C_ETC.
+ Non-whitespace control characters are errors.
+ */
+ __, __, __, __, __, __, __, __,
+ __, C_WHITE, C_WHITE, __, __, C_WHITE, __, __,
+ __, __, __, __, __, __, __, __,
+ __, __, __, __, __, __, __, __,
+
+ C_SPACE, C_ETC, C_QUOTE, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC,
+ C_ETC, C_ETC, C_ETC, C_PLUS, C_COMMA, C_MINUS, C_POINT, C_SLASH,
+ C_ZERO, C_DIGIT, C_DIGIT, C_DIGIT, C_DIGIT, C_DIGIT, C_DIGIT, C_DIGIT,
+ C_DIGIT, C_DIGIT, C_COLON, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC,
+
+ C_ETC, C_ABCDF, C_ABCDF, C_ABCDF, C_ABCDF, C_E, C_ABCDF, C_ETC,
+ C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC,
+ C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC,
+ C_ETC, C_ETC, C_ETC, C_LSQRB, C_BACKS, C_RSQRB, C_ETC, C_ETC,
+
+ C_ETC, C_LOW_A, C_LOW_B, C_LOW_C, C_LOW_D, C_LOW_E, C_LOW_F, C_ETC,
+ C_ETC, C_ETC, C_ETC, C_ETC, C_LOW_L, C_ETC, C_LOW_N, C_ETC,
+ C_ETC, C_ETC, C_LOW_R, C_LOW_S, C_LOW_T, C_LOW_U, C_ETC, C_ETC,
+ C_ETC, C_ETC, C_ETC, C_LCURB, C_ETC, C_RCURB, C_ETC, C_ETC,
+}
+
+// QuoteAsciiClasses is a HACK for single quote from AsciiClasses
+var QuoteAsciiClasses = [128]Classes{
+ /*
+ This array maps the 128 ASCII characters into character classes.
+ The remaining Unicode characters should be mapped to C_ETC.
+ Non-whitespace control characters are errors.
+ */
+ __, __, __, __, __, __, __, __,
+ __, C_WHITE, C_WHITE, __, __, C_WHITE, __, __,
+ __, __, __, __, __, __, __, __,
+ __, __, __, __, __, __, __, __,
+
+ C_SPACE, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_QUOTE,
+ C_ETC, C_ETC, C_ETC, C_PLUS, C_COMMA, C_MINUS, C_POINT, C_SLASH,
+ C_ZERO, C_DIGIT, C_DIGIT, C_DIGIT, C_DIGIT, C_DIGIT, C_DIGIT, C_DIGIT,
+ C_DIGIT, C_DIGIT, C_COLON, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC,
+
+ C_ETC, C_ABCDF, C_ABCDF, C_ABCDF, C_ABCDF, C_E, C_ABCDF, C_ETC,
+ C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC,
+ C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC,
+ C_ETC, C_ETC, C_ETC, C_LSQRB, C_BACKS, C_RSQRB, C_ETC, C_ETC,
+
+ C_ETC, C_LOW_A, C_LOW_B, C_LOW_C, C_LOW_D, C_LOW_E, C_LOW_F, C_ETC,
+ C_ETC, C_ETC, C_ETC, C_ETC, C_LOW_L, C_ETC, C_LOW_N, C_ETC,
+ C_ETC, C_ETC, C_LOW_R, C_LOW_S, C_LOW_T, C_LOW_U, C_ETC, C_ETC,
+ C_ETC, C_ETC, C_ETC, C_LCURB, C_ETC, C_RCURB, C_ETC, C_ETC,
+}
+
+/*
+The state codes.
+*/
+const (
+ GO States = iota /* start */
+ OK /* ok */
+ OB /* object */
+ KE /* key */
+ CO /* colon */
+ VA /* value */
+ AR /* array */
+ ST /* string */
+ ES /* escape */
+ U1 /* u1 */
+ U2 /* u2 */
+ U3 /* u3 */
+ U4 /* u4 */
+ MI /* minus */
+ ZE /* zero */
+ IN /* integer */
+ DT /* dot */
+ FR /* fraction */
+ E1 /* e */
+ E2 /* ex */
+ E3 /* exp */
+ T1 /* tr */
+ T2 /* tru */
+ T3 /* true */
+ F1 /* fa */
+ F2 /* fal */
+ F3 /* fals */
+ F4 /* false */
+ N1 /* nu */
+ N2 /* nul */
+ N3 /* null */
+)
+
+// List of action codes
+const (
+ cl States = -2 /* colon */
+ cm States = -3 /* comma */
+ qt States = -4 /* quote */
+ bo States = -5 /* bracket open */
+ co States = -6 /* curly br. open */
+ bc States = -7 /* bracket close */
+ cc States = -8 /* curly br. close */
+ ec States = -9 /* curly br. empty */
+)
+
+// StateTransitionTable is the state transition table takes the current state and the current symbol, and returns either
+// a new state or an action. An action is represented as a negative number. A JSON text is accepted if at the end of the
+// text the state is OK and if the mode is DONE.
+var StateTransitionTable = [31][31]States{
+ /*
+ The state transition table takes the current state and the current symbol,
+ and returns either a new state or an action. An action is represented as a
+ negative number. A JSON text is accepted if at the end of the text the
+ state is OK and if the mode is DONE.
+ white 1-9 ABCDF etc
+ space | { } [ ] : , " \ / + - . 0 | a b c d e f l n r s t u | E |*/
+ /*start GO*/ {GO, GO, co, __, bo, __, __, __, ST, __, __, __, MI, __, ZE, IN, __, __, __, __, __, F1, __, N1, __, __, T1, __, __, __, __},
+ /*ok OK*/ {OK, OK, __, cc, __, bc, __, cm, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __},
+ /*object OB*/ {OB, OB, __, ec, __, __, __, __, ST, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __},
+ /*key KE*/ {KE, KE, __, __, __, __, __, __, ST, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __},
+ /*colon CO*/ {CO, CO, __, __, __, __, cl, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __},
+ /*value VA*/ {VA, VA, co, __, bo, __, __, __, ST, __, __, __, MI, __, ZE, IN, __, __, __, __, __, F1, __, N1, __, __, T1, __, __, __, __},
+ /*array AR*/ {AR, AR, co, __, bo, bc, __, __, ST, __, __, __, MI, __, ZE, IN, __, __, __, __, __, F1, __, N1, __, __, T1, __, __, __, __},
+ /*string ST*/ {ST, __, ST, ST, ST, ST, ST, ST, qt, ES, ST, ST, ST, ST, ST, ST, ST, ST, ST, ST, ST, ST, ST, ST, ST, ST, ST, ST, ST, ST, ST},
+ /*escape ES*/ {__, __, __, __, __, __, __, __, ST, ST, ST, __, __, __, __, __, __, ST, __, __, __, ST, __, ST, ST, __, ST, U1, __, __, __},
+ /*u1 U1*/ {__, __, __, __, __, __, __, __, __, __, __, __, __, __, U2, U2, U2, U2, U2, U2, U2, U2, __, __, __, __, __, __, U2, U2, __},
+ /*u2 U2*/ {__, __, __, __, __, __, __, __, __, __, __, __, __, __, U3, U3, U3, U3, U3, U3, U3, U3, __, __, __, __, __, __, U3, U3, __},
+ /*u3 U3*/ {__, __, __, __, __, __, __, __, __, __, __, __, __, __, U4, U4, U4, U4, U4, U4, U4, U4, __, __, __, __, __, __, U4, U4, __},
+ /*u4 U4*/ {__, __, __, __, __, __, __, __, __, __, __, __, __, __, ST, ST, ST, ST, ST, ST, ST, ST, __, __, __, __, __, __, ST, ST, __},
+ /*minus MI*/ {__, __, __, __, __, __, __, __, __, __, __, __, __, __, ZE, IN, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __},
+ /*zero ZE*/ {OK, OK, __, cc, __, bc, __, cm, __, __, __, __, __, DT, __, __, __, __, __, __, E1, __, __, __, __, __, __, __, __, E1, __},
+ /*int IN*/ {OK, OK, __, cc, __, bc, __, cm, __, __, __, __, __, DT, IN, IN, __, __, __, __, E1, __, __, __, __, __, __, __, __, E1, __},
+ /*dot DT*/ {__, __, __, __, __, __, __, __, __, __, __, __, __, __, FR, FR, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __},
+ /*frac FR*/ {OK, OK, __, cc, __, bc, __, cm, __, __, __, __, __, __, FR, FR, __, __, __, __, E1, __, __, __, __, __, __, __, __, E1, __},
+ /*e E1*/ {__, __, __, __, __, __, __, __, __, __, __, E2, E2, __, E3, E3, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __},
+ /*ex E2*/ {__, __, __, __, __, __, __, __, __, __, __, __, __, __, E3, E3, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __},
+ /*exp E3*/ {OK, OK, __, cc, __, bc, __, cm, __, __, __, __, __, __, E3, E3, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __},
+ /*tr T1*/ {__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, T2, __, __, __, __, __, __},
+ /*tru T2*/ {__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, T3, __, __, __},
+ /*true T3*/ {__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, OK, __, __, __, __, __, __, __, __, __, __},
+ /*fa F1*/ {__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, F2, __, __, __, __, __, __, __, __, __, __, __, __, __, __},
+ /*fal F2*/ {__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, F3, __, __, __, __, __, __, __, __},
+ /*fals F3*/ {__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, F4, __, __, __, __, __},
+ /*false F4*/ {__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, OK, __, __, __, __, __, __, __, __, __, __},
+ /*nu N1*/ {__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, N2, __, __, __},
+ /*nul N2*/ {__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, N3, __, __, __, __, __, __, __, __},
+ /*null N3*/ {__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, OK, __, __, __, __, __, __, __, __},
+}
diff --git a/vendor/github.com/spyzhov/ajson/jsonpath.go b/vendor/github.com/spyzhov/ajson/jsonpath.go
new file mode 100644
index 0000000..055d8a4
--- /dev/null
+++ b/vendor/github.com/spyzhov/ajson/jsonpath.go
@@ -0,0 +1,710 @@
+package ajson
+
+import (
+ "io"
+ "math"
+ "strconv"
+ "strings"
+)
+
+// JSONPath returns slice of founded elements in current JSON data, by it's JSONPath.
+//
+// JSONPath described at http://goessner.net/articles/JsonPath/
+//
+// JSONPath expressions always refer to a JSON structure in the same way as XPath expression are used in combination with an XML document. Since a JSON structure is usually anonymous and doesn't necessarily have a "root member object" JSONPath assumes the abstract name $ assigned to the outer level object.
+//
+// JSONPath expressions can use the dot–notation
+//
+// $.store.book[0].title
+//
+// or the bracket–notation
+//
+// $['store']['book'][0]['title']
+//
+// for input pathes. Internal or output pathes will always be converted to the more general bracket–notation.
+//
+// JSONPath allows the wildcard symbol * for member names and array indices. It borrows the descendant operator '..' from E4X and the array slice syntax proposal [start:end:step] from ECMASCRIPT 4.
+//
+// Expressions of the underlying scripting language () can be used as an alternative to explicit names or indices as in
+//
+// $.store.book[(@.length-1)].title
+//
+// using the symbol '@' for the current object. Filter expressions are supported via the syntax ?() as in
+//
+// $.store.book[?(@.price < 10)].title
+//
+// Here is a complete overview and a side by side comparison of the JSONPath syntax elements with its XPath counterparts.
+//
+// $ the root object/element
+// @ the current object/element
+// . or [] child operator
+// .. recursive descent. JSONPath borrows this syntax from E4X.
+// * wildcard. All objects/elements regardless their names.
+// [] subscript operator. XPath uses it to iterate over element collections and for predicates. In Javascript and JSON it is the native array operator.
+// [,] Union operator in XPath results in a combination of node sets. JSONPath allows alternate names or array indices as a set.
+// [start:end:step] array slice operator borrowed from ES4.
+// ?() applies a filter (script) expression.
+// () script expression, using the underlying script engine.
+//
+// # JSONPath Script engine
+//
+// # Predefined constant
+//
+// Package has several predefined constants. You are free to add new one with AddConstant
+//
+// e math.E float64
+// pi math.Pi float64
+// phi math.Phi float64
+//
+// sqrt2 math.Sqrt2 float64
+// sqrte math.SqrtE float64
+// sqrtpi math.SqrtPi float64
+// sqrtphi math.SqrtPhi float64
+//
+// ln2 math.Ln2 float64
+// log2e math.Log2E float64
+// ln10 math.Ln10 float64
+// log10e math.Log10E float64
+//
+// true true bool
+// false false bool
+// null nil interface{}
+//
+// # Supported operations
+//
+// Package has several predefined operators. You are free to add new one with AddOperator
+//
+// Operator precedence: https://golang.org/ref/spec#Operator_precedence
+//
+// Precedence Operator
+// 6 **
+// 5 * / % << >> & &^
+// 4 + - | ^
+// 3 == != < <= > >= =~
+// 2 &&
+// 1 ||
+//
+// Arithmetic operators: https://golang.org/ref/spec#Arithmetic_operators
+//
+// ** power integers, floats
+// + sum integers, floats, strings
+// - difference integers, floats
+// * product integers, floats
+// / quotient integers, floats
+// % remainder integers
+//
+// & bitwise AND integers
+// | bitwise OR integers
+// ^ bitwise XOR integers
+// &^ bit clear (AND NOT) integers
+//
+// << left shift integer << unsigned integer
+// >> right shift integer >> unsigned integer
+//
+// == equals any
+// != not equals any
+// < less any
+// <= less or equals any
+// > larger any
+// >= larger or equals any
+// =~ equals regex string strings
+//
+// # Supported functions
+//
+// Package has several predefined functions. You are free to add new one with AddFunction
+//
+// abs math.Abs integers, floats
+// acos math.Acos integers, floats
+// acosh math.Acosh integers, floats
+// asin math.Asin integers, floats
+// asinh math.Asinh integers, floats
+// atan math.Atan integers, floats
+// atanh math.Atanh integers, floats
+// avg Average array of integers or floats
+// cbrt math.Cbrt integers, floats
+// ceil math.Ceil integers, floats
+// cos math.Cos integers, floats
+// cosh math.Cosh integers, floats
+// erf math.Erf integers, floats
+// erfc math.Erfc integers, floats
+// erfcinv math.Erfcinv integers, floats
+// erfinv math.Erfinv integers, floats
+// exp math.Exp integers, floats
+// exp2 math.Exp2 integers, floats
+// expm1 math.Expm1 integers, floats
+// factorial N! unsigned integer
+// floor math.Floor integers, floats
+// gamma math.Gamma integers, floats
+// j0 math.J0 integers, floats
+// j1 math.J1 integers, floats
+// length len array
+// log math.Log integers, floats
+// log10 math.Log10 integers, floats
+// log1p math.Log1p integers, floats
+// log2 math.Log2 integers, floats
+// logb math.Logb integers, floats
+// not not any
+// pow10 math.Pow10 integer
+// round math.Round integers, floats
+// roundtoeven math.RoundToEven integers, floats
+// sin math.Sin integers, floats
+// sinh math.Sinh integers, floats
+// sqrt math.Sqrt integers, floats
+// tan math.Tan integers, floats
+// tanh math.Tanh integers, floats
+// trunc math.Trunc integers, floats
+// y0 math.Y0 integers, floats
+// y1 math.Y1 integers, floats
+func JSONPath(data []byte, path string) (result []*Node, err error) {
+ commands, err := ParseJSONPath(path)
+ if err != nil {
+ return nil, err
+ }
+ node, err := Unmarshal(data)
+ if err != nil {
+ return nil, err
+ }
+ return ApplyJSONPath(node, commands)
+}
+
+// Paths returns calculated paths of underlying nodes
+func Paths(array []*Node) []string {
+ result := make([]string, 0, len(array))
+ for _, element := range array {
+ result = append(result, element.Path())
+ }
+ return result
+}
+
+func recursiveChildren(node *Node) (result []*Node) {
+ if node.isContainer() {
+ for _, element := range node.Inheritors() {
+ if element.isContainer() {
+ result = append(result, element)
+ }
+ }
+ }
+ temp := make([]*Node, 0, len(result))
+ temp = append(temp, result...)
+ for _, element := range result {
+ temp = append(temp, recursiveChildren(element)...)
+ }
+ return temp
+}
+
+// ParseJSONPath will parse current path and return all commands tobe run.
+// Example:
+//
+// result, _ := ParseJSONPath("$.store.book[?(@.price < 10)].title")
+// result == []string{"$", "store", "book", "?(@.price < 10)", "title"}
+func ParseJSONPath(path string) (result []string, err error) {
+ buf := newBuffer([]byte(path))
+ result = make([]string, 0)
+ const (
+ fQuote = 1 << 0
+ fQuotes = 1 << 1
+ )
+ var (
+ c byte
+ start, stop int
+ childEnd = map[byte]bool{dot: true, bracketL: true}
+ flag int
+ brackets int
+ )
+ for {
+ c, err = buf.current()
+ if err != nil {
+ break
+ }
+ parseSwitch:
+ switch true {
+ case c == dollar || c == at:
+ result = append(result, string(c))
+ case c == dot:
+ start = buf.index
+ c, err = buf.next()
+ if err == io.EOF {
+ //nolint:ineffassign
+ err = nil
+ break
+ }
+ if err != nil {
+ break
+ }
+ if c == dot {
+ result = append(result, "..")
+ buf.index--
+ break
+ }
+ err = buf.skipAny(childEnd)
+ stop = buf.index
+ if err == io.EOF {
+ err = nil
+ stop = buf.length
+ } else {
+ buf.index--
+ }
+ if err != nil {
+ break
+ }
+ if start+1 < stop {
+ result = append(result, string(buf.data[start+1:stop]))
+ }
+ case c == bracketL:
+ _, err = buf.next()
+ if err != nil {
+ return nil, buf.errorEOF()
+ }
+ brackets = 1
+ start = buf.index
+ for ; buf.index < buf.length; buf.index++ {
+ c = buf.data[buf.index]
+ switch c {
+ case quote:
+ if flag&fQuotes == 0 {
+ if flag&fQuote == 0 {
+ flag |= fQuote
+ } else if !buf.backslash() {
+ flag ^= fQuote
+ }
+ }
+ case quotes:
+ if flag&fQuote == 0 {
+ if flag&fQuotes == 0 {
+ flag |= fQuotes
+ } else if !buf.backslash() {
+ flag ^= fQuotes
+ }
+ }
+ case bracketL:
+ if flag == 0 && !buf.backslash() {
+ brackets++
+ }
+ case bracketR:
+ if flag == 0 && !buf.backslash() {
+ brackets--
+ }
+ if brackets == 0 {
+ result = append(result, string(buf.data[start:buf.index]))
+ break parseSwitch
+ }
+ }
+ }
+ return nil, buf.errorEOF()
+ default:
+ return nil, buf.errorSymbol()
+ }
+ err = buf.step()
+ if err != nil {
+ if err == io.EOF {
+ err = nil
+ }
+ break
+ }
+ }
+ return
+}
+
+// ApplyJSONPath function applies commands sequence parse from JSONPath.
+// Example:
+//
+// commands := []string{"$", "store", "book", "?(@.price < 10)", "title"}
+// 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
+ keys []string
+ ikeys [3]int
+ fkeys [3]float64
+ num int
+ key string
+ ok bool
+ value, temp *Node
+ float float64
+ tokens tokens
+ expr rpn
+ )
+ for i, cmd := range commands {
+ tokens, err = newBuffer([]byte(cmd)).tokenize()
+ if err != nil {
+ return
+ }
+ switch {
+ case cmd == "$": // root element
+ if i == 0 {
+ result = append(result, node.root())
+ }
+ case cmd == "@": // current element
+ if i == 0 {
+ result = append(result, node)
+ }
+ case cmd == "..": // recursive descent
+ temporary = make([]*Node, 0)
+ for _, element := range result {
+ temporary = append(temporary, recursiveChildren(element)...)
+ }
+ result = append(result, temporary...)
+ case cmd == "*": // wildcard
+ temporary = make([]*Node, 0)
+ for _, element := range result {
+ temporary = append(temporary, element.Inheritors()...)
+ }
+ result = temporary
+ case tokens.exists(":"): // array slice operator
+ if tokens.count(":") > 3 {
+ return nil, errorRequest("slice must contains no more than 2 colons, got '%s'", cmd)
+ }
+ keys = tokens.slice(":")
+
+ temporary = make([]*Node, 0)
+ for _, element := range result {
+ if element.IsArray() && element.Size() > 0 {
+ if fkeys[0], err = getNumberIndex(element, keys[0], math.NaN()); err != nil {
+ return nil, errorRequest("wrong request: %s", cmd)
+ }
+ if fkeys[1], err = getNumberIndex(element, keys[1], math.NaN()); err != nil {
+ return nil, errorRequest("wrong request: %s", cmd)
+ }
+ if len(keys) < 3 {
+ fkeys[2] = 1
+ } else if fkeys[2], err = getNumberIndex(element, keys[2], 1); err != nil {
+ return nil, errorRequest("wrong request: %s", cmd)
+ }
+
+ ikeys[2] = int(fkeys[2])
+ if ikeys[2] == 0 {
+ return nil, errorRequest("wrong request: %s", cmd)
+ }
+
+ if math.IsNaN(fkeys[0]) {
+ if ikeys[2] > 0 {
+ ikeys[0] = 0
+ } else {
+ ikeys[0] = element.Size() - 1
+ }
+ } else {
+ ikeys[0] = getPositiveIndex(int(fkeys[0]), element.Size())
+ }
+ if math.IsNaN(fkeys[1]) {
+ if ikeys[2] > 0 {
+ ikeys[1] = element.Size()
+ } else {
+ ikeys[1] = -1
+ }
+ } else {
+ ikeys[1] = getPositiveIndex(int(fkeys[1]), element.Size())
+ }
+
+ if ikeys[2] > 0 {
+ if ikeys[0] < 0 {
+ ikeys[0] = 0
+ }
+ if ikeys[1] > element.Size() {
+ ikeys[1] = element.Size()
+ }
+
+ for i := ikeys[0]; i < ikeys[1]; i += ikeys[2] {
+ value, ok := element.children[strconv.Itoa(i)]
+ if ok {
+ temporary = append(temporary, value)
+ }
+ }
+ } else if ikeys[2] < 0 {
+ if ikeys[0] > element.Size() {
+ ikeys[0] = element.Size()
+ }
+ if ikeys[1] < -1 {
+ ikeys[1] = -1
+ }
+
+ for i := ikeys[0]; i > ikeys[1]; i += ikeys[2] {
+ value, ok := element.children[strconv.Itoa(i)]
+ if ok {
+ temporary = append(temporary, value)
+ }
+ }
+ }
+ }
+ }
+ result = temporary
+ case strings.HasPrefix(cmd, "?(") && strings.HasSuffix(cmd, ")"): // applies a filter (script) expression
+ expr, err = newBuffer([]byte(cmd[2 : len(cmd)-1])).rpn()
+ if err != nil {
+ return nil, errorRequest("wrong request: %s", cmd)
+ }
+ temporary = make([]*Node, 0)
+ for _, element := range result {
+ if element.isContainer() {
+ for _, temp = range element.Inheritors() {
+ value, err = eval(temp, expr, cmd)
+ if err != nil {
+ return nil, errorRequest("wrong request: %s", cmd)
+ }
+ if value != nil {
+ ok, err = boolean(value)
+ if err != nil || !ok {
+ continue
+ }
+ temporary = append(temporary, temp)
+ }
+ }
+ }
+ }
+ result = temporary
+ case strings.HasPrefix(cmd, "(") && strings.HasSuffix(cmd, ")"): // script expression, using the underlying script engine
+ expr, err = newBuffer([]byte(cmd[1 : len(cmd)-1])).rpn()
+ if err != nil {
+ return nil, errorRequest("wrong request: %s", cmd)
+ }
+ temporary = make([]*Node, 0)
+ for _, element := range result {
+ if !element.isContainer() {
+ continue
+ }
+ temp, err = eval(element, expr, cmd)
+ if err != nil {
+ return nil, errorRequest("wrong request: %s", cmd)
+ }
+ if temp != nil {
+ value = nil
+ switch temp.Type() {
+ case String:
+ key, err = temp.GetString()
+ if err != nil {
+ return nil, errorRequest("wrong type convert: %s", err.Error())
+ }
+ value = element.children[key]
+ case Numeric:
+ num, err = temp.getInteger()
+ if err == nil { // INTEGER
+ if num < 0 {
+ key = strconv.Itoa(element.Size() - num)
+ } else {
+ key = strconv.Itoa(num)
+ }
+ } else {
+ float, err = temp.GetNumeric()
+ if err != nil {
+ return nil, errorRequest("wrong type convert: %s", err.Error())
+ }
+ key = strconv.FormatFloat(float, 'g', -1, 64)
+ }
+ value = element.children[key]
+ case Bool:
+ ok, err = temp.GetBool()
+ if err != nil {
+ return nil, errorRequest("wrong type convert: %s", err.Error())
+ }
+ if ok {
+ temporary = append(temporary, element.Inheritors()...)
+ }
+ continue
+ // case Array: // get all keys from element via array values
+ }
+ if value != nil {
+ temporary = append(temporary, value)
+ }
+ }
+ }
+ result = temporary
+ default: // try to get by key & Union
+ if tokens.exists(",") {
+ keys = tokens.slice(",")
+ if len(keys) == 0 {
+ return nil, errorRequest("wrong request: %s", cmd)
+ }
+ } else {
+ keys = []string{cmd}
+ }
+
+ temporary = make([]*Node, 0)
+ for _, key = range keys { // fixme
+ for _, element := range result {
+ if element.IsArray() {
+ if key == "length" || key == "'length'" || key == "\"length\"" {
+ value, err = functions["length"](element)
+ if err != nil {
+ return
+ }
+ ok = true
+ } else if strings.HasPrefix(key, "(") && strings.HasSuffix(key, ")") {
+ fkeys[0], err = getNumberIndex(element, key, math.NaN())
+ if err != nil {
+ return nil, err
+ }
+ if math.IsNaN(fkeys[0]) {
+ return nil, errorRequest("wrong request: %s", cmd)
+ }
+ if element.Size() == 0 {
+ ok = false
+ } else {
+ num = getPositiveIndex(int(fkeys[0]), element.Size())
+ key = strconv.Itoa(num)
+ value, ok = element.children[key]
+ }
+ } else {
+ key, _ = str(key)
+ num, err = strconv.Atoi(key)
+ if err != nil || element.Size() == 0 {
+ ok = false
+ err = nil
+ } else {
+ num = getPositiveIndex(num, element.Size())
+ key = strconv.Itoa(num)
+ value, ok = element.children[key]
+ }
+ }
+
+ } else if element.IsObject() {
+ key, _ = str(key)
+ value, ok = element.children[key]
+ }
+ if ok {
+ temporary = append(temporary, value)
+ ok = false
+ }
+ }
+ }
+ result = temporary
+ }
+ }
+ return
+}
+
+// Eval evaluate expression `@.price == 19.95 && @.color == 'red'` to the result value i.e. Bool(true), Numeric(3.14), etc.
+func Eval(node *Node, cmd string) (result *Node, err error) {
+ calc, err := newBuffer([]byte(cmd)).rpn()
+ if err != nil {
+ return nil, err
+ }
+ return eval(node, calc, cmd)
+}
+
+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
+ temp *Node
+ fn Function
+ op Operation
+ ok bool
+ size int
+ commands []string
+ bstr []byte
+ )
+ for _, exp := range expression {
+ size = len(stack)
+ if fn, ok = functions[exp]; ok {
+ if size < 1 {
+ return nil, errorRequest("wrong request: %s", cmd)
+ }
+ stack[size-1], err = fn(stack[size-1])
+ if err != nil {
+ return
+ }
+ } else if op, ok = operations[exp]; ok {
+ if size < 2 {
+ return nil, errorRequest("wrong request: %s", cmd)
+ }
+ stack[size-2], err = op(stack[size-2], stack[size-1])
+ if err != nil {
+ return
+ }
+ stack = stack[:size-1]
+ } else if len(exp) > 0 {
+ if exp[0] == dollar || exp[0] == at {
+ commands, err = ParseJSONPath(exp)
+ if err != nil {
+ return
+ }
+ slice, err = ApplyJSONPath(node, commands)
+ if err != nil {
+ return
+ }
+ if len(slice) > 1 { // array given
+ stack = append(stack, ArrayNode("", slice))
+ } else if len(slice) == 1 {
+ stack = append(stack, slice[0])
+ } else { // no data found
+ stack = append(stack, nil)
+ }
+ } else if constant, ok := constants[strings.ToLower(exp)]; ok {
+ stack = append(stack, constant)
+ } else {
+ bstr = []byte(exp)
+ size = len(bstr)
+ if size >= 2 && bstr[0] == quote && bstr[size-1] == quote {
+ if sstr, ok := unquote(bstr, quote); ok {
+ temp = StringNode("", sstr)
+ } else {
+ err = errorRequest("wrong request: %s", cmd)
+ }
+ } else {
+ temp, err = Unmarshal(bstr)
+ }
+ if err != nil {
+ return
+ }
+ stack = append(stack, temp)
+ }
+ } else {
+ stack = append(stack, valueNode(nil, "", String, ""))
+ }
+ }
+ if len(stack) == 1 {
+ if stack[0] == nil {
+ return NullNode(""), nil
+ }
+ return stack[0], nil
+ }
+ if len(stack) == 0 {
+ return NullNode(""), nil
+ }
+ return nil, errorRequest("wrong request: %s", cmd)
+}
+
+func getNumberIndex(element *Node, input string, Default float64) (result float64, err error) {
+ var integer int
+ if input == "" {
+ result = Default
+ } else if input == "(@.length)" {
+ result = float64(element.Size())
+ } else if strings.HasPrefix(input, "(") && strings.HasSuffix(input, ")") {
+ var expr rpn
+ var temp *Node
+ expr, err = newBuffer([]byte(input[1 : len(input)-1])).rpn()
+ if err != nil {
+ return 0, err
+ }
+ temp, err = eval(element, expr, input)
+ if err != nil {
+ return
+ }
+ integer, err = temp.getInteger()
+ if err != nil {
+ return
+ }
+ result = float64(integer)
+ } else {
+ integer, err = strconv.Atoi(input)
+ if err != nil {
+ return 0, err
+ }
+ result = float64(integer)
+ }
+ return
+}
+
+func getPositiveIndex(index int, count int) int {
+ if index < 0 {
+ index += count
+ }
+ return index
+}
diff --git a/vendor/github.com/spyzhov/ajson/math.go b/vendor/github.com/spyzhov/ajson/math.go
new file mode 100644
index 0000000..7087000
--- /dev/null
+++ b/vendor/github.com/spyzhov/ajson/math.go
@@ -0,0 +1,649 @@
+package ajson
+
+import (
+ "encoding/base64"
+ "math"
+ "math/rand"
+ "regexp"
+ "sort"
+ "strings"
+)
+
+// Function - internal left function of JSONPath
+type Function func(node *Node) (result *Node, err error)
+
+// Operation - internal script operation of JSONPath
+type Operation func(left *Node, right *Node) (result *Node, err error)
+
+var (
+ // Operator precedence
+ // From https://golang.org/ref/spec#Operator_precedence
+ //
+ // Precedence Operator
+ // 5 * / % << >> & &^
+ // 4 + - | ^
+ // 3 == != < <= > >= =~
+ // 2 &&
+ // 1 ||
+ //
+ // Arithmetic operators
+ // From https://golang.org/ref/spec#Arithmetic_operators
+ //
+ // + sum integers, floats, complex values, strings
+ // - difference integers, floats, complex values
+ // * product integers, floats, complex values
+ // / quotient integers, floats, complex values
+ // % remainder integers
+ //
+ // & bitwise AND integers
+ // | bitwise OR integers
+ // ^ bitwise XOR integers
+ // &^ bit clear (AND NOT) integers
+ //
+ // << left shift integer << unsigned integer
+ // >> right shift integer >> unsigned integer
+ //
+ // == equals any
+ // != not equals any
+ // < less any
+ // <= less or equals any
+ // > larger any
+ // >= larger or equals any
+ // =~ equals regex string strings
+ //
+ priority = map[string]uint8{
+ "**": 6, // additional: power
+ "*": 5,
+ "/": 5,
+ "%": 5,
+ "<<": 5,
+ ">>": 5,
+ "&": 5,
+ "&^": 5,
+ "+": 4,
+ "-": 4,
+ "|": 4,
+ "^": 4,
+ "==": 3,
+ "!=": 3,
+ "<": 3,
+ "<=": 3,
+ ">": 3,
+ ">=": 3,
+ "=~": 3,
+ "&&": 2,
+ "||": 1,
+ }
+ priorityChar = map[byte]bool{
+ '*': true,
+ '/': true,
+ '%': true,
+ '<': true,
+ '>': true,
+ '&': true,
+ '|': true,
+ '^': true,
+ '+': true,
+ '-': true,
+ '=': true,
+ '!': true,
+ }
+
+ rightOp = map[string]bool{
+ "**": true,
+ }
+
+ operations = map[string]Operation{
+ "**": func(left *Node, right *Node) (result *Node, err error) {
+ lnum, rnum, err := _floats(left, right)
+ if err != nil {
+ return
+ }
+ return valueNode(nil, "power", Numeric, math.Pow(lnum, rnum)), nil
+ },
+ "*": func(left *Node, right *Node) (result *Node, err error) {
+ lnum, rnum, err := _floats(left, right)
+ if err != nil {
+ return
+ }
+ return valueNode(nil, "multiply", Numeric, float64(lnum*rnum)), nil
+ },
+ "/": func(left *Node, right *Node) (result *Node, err error) {
+ lnum, rnum, err := _floats(left, right)
+ if err != nil {
+ return
+ }
+ if rnum == 0 {
+ return nil, errorRequest("division by zero")
+ }
+ return valueNode(nil, "division", Numeric, float64(lnum/rnum)), nil
+ },
+ "%": func(left *Node, right *Node) (result *Node, err error) {
+ lnum, err := left.getInteger()
+ if err != nil {
+ return
+ }
+ rnum, err := right.getInteger()
+ if err != nil {
+ return
+ }
+ return valueNode(nil, "remainder", Numeric, float64(lnum%rnum)), nil
+ },
+ "<<": func(left *Node, right *Node) (result *Node, err error) {
+ lnum, err := left.getInteger()
+ if err != nil {
+ return
+ }
+ rnum, err := right.getUInteger()
+ if err != nil {
+ return
+ }
+ return valueNode(nil, "left shift", Numeric, float64(lnum<>": func(left *Node, right *Node) (result *Node, err error) {
+ lnum, err := left.getInteger()
+ if err != nil {
+ return
+ }
+ rnum, err := right.getUInteger()
+ if err != nil {
+ return
+ }
+ return valueNode(nil, "right shift", Numeric, float64(lnum>>rnum)), nil
+ },
+ "&": func(left *Node, right *Node) (result *Node, err error) {
+ lnum, rnum, err := _ints(left, right)
+ if err != nil {
+ return
+ }
+ return valueNode(nil, "bitwise AND", Numeric, float64(lnum&rnum)), nil
+ },
+ "&^": func(left *Node, right *Node) (result *Node, err error) {
+ lnum, rnum, err := _ints(left, right)
+ if err != nil {
+ return
+ }
+ return valueNode(nil, "bit clear (AND NOT)", Numeric, float64(lnum&^rnum)), nil
+ },
+ "+": func(left *Node, right *Node) (result *Node, err error) {
+ if left.IsString() {
+ lnum, rnum, err := _strings(left, right)
+ if err != nil {
+ return nil, err
+ }
+ return valueNode(nil, "sum", String, lnum+rnum), nil
+ }
+ lnum, rnum, err := _floats(left, right)
+ if err != nil {
+ return nil, err
+ }
+ return valueNode(nil, "sum", Numeric, float64(lnum+rnum)), nil
+ },
+ "-": func(left *Node, right *Node) (result *Node, err error) {
+ lnum, rnum, err := _floats(left, right)
+ if err != nil {
+ return
+ }
+ return valueNode(nil, "sub", Numeric, float64(lnum-rnum)), nil
+ },
+ "|": func(left *Node, right *Node) (result *Node, err error) {
+ lnum, rnum, err := _ints(left, right)
+ if err != nil {
+ return
+ }
+ return valueNode(nil, "bitwise OR", Numeric, float64(lnum|rnum)), nil
+ },
+ "^": func(left *Node, right *Node) (result *Node, err error) {
+ lnum, rnum, err := _ints(left, right)
+ if err != nil {
+ return nil, err
+ }
+ return valueNode(nil, "bitwise XOR", Numeric, float64(lnum^rnum)), nil
+ },
+ "==": func(left *Node, right *Node) (result *Node, err error) {
+ if left == nil || right == nil {
+ return valueNode(nil, "eq", Bool, false), nil
+ }
+ res, err := left.Eq(right)
+ if err != nil {
+ return nil, err
+ }
+ return valueNode(nil, "eq", Bool, res), nil
+ },
+ "!=": func(left *Node, right *Node) (result *Node, err error) {
+ if left == nil || right == nil {
+ return valueNode(nil, "neq", Bool, false), nil
+ }
+ res, err := left.Eq(right)
+ if err != nil {
+ return nil, err
+ }
+ return valueNode(nil, "neq", Bool, !res), nil
+ },
+ "=~": func(left *Node, right *Node) (node *Node, err error) {
+ pattern, err := right.GetString()
+ if err != nil {
+ return nil, err
+ }
+ val, err := left.GetString()
+ if err != nil {
+ return nil, err
+ }
+ res, err := regexp.MatchString(pattern, val)
+ if err != nil {
+ return nil, err
+ }
+ return valueNode(nil, "eq", Bool, res), nil
+ },
+ "<": func(left *Node, right *Node) (result *Node, err error) {
+ if left == nil || right == nil {
+ return valueNode(nil, "le", Bool, false), nil
+ }
+ res, err := left.Le(right)
+ if err != nil {
+ return nil, err
+ }
+ return valueNode(nil, "le", Bool, bool(res)), nil
+ },
+ "<=": func(left *Node, right *Node) (result *Node, err error) {
+ if left == nil || right == nil {
+ return valueNode(nil, "leq", Bool, false), nil
+ }
+ res, err := left.Leq(right)
+ if err != nil {
+ return nil, err
+ }
+ return valueNode(nil, "leq", Bool, bool(res)), nil
+ },
+ ">": func(left *Node, right *Node) (result *Node, err error) {
+ if left == nil || right == nil {
+ return valueNode(nil, "ge", Bool, false), nil
+ }
+ res, err := left.Ge(right)
+ if err != nil {
+ return nil, err
+ }
+ return valueNode(nil, "ge", Bool, bool(res)), nil
+ },
+ ">=": func(left *Node, right *Node) (result *Node, err error) {
+ if left == nil || right == nil {
+ return valueNode(nil, "geq", Bool, false), nil
+ }
+ res, err := left.Geq(right)
+ if err != nil {
+ return nil, err
+ }
+ return valueNode(nil, "geq", Bool, bool(res)), nil
+ },
+ "&&": func(left *Node, right *Node) (result *Node, err error) {
+ res := false
+ lval, err := boolean(left)
+ if err != nil {
+ return nil, err
+ }
+ if lval {
+ rval, err := boolean(right)
+ if err != nil {
+ return nil, err
+ }
+ res = rval
+ }
+ return valueNode(nil, "AND", Bool, bool(res)), nil
+ },
+ "||": func(left *Node, right *Node) (result *Node, err error) {
+ res := true
+ lval, err := boolean(left)
+ if err != nil {
+ return nil, err
+ }
+ if !lval {
+ rval, err := boolean(right)
+ if err != nil {
+ return nil, err
+ }
+ res = rval
+ }
+ return valueNode(nil, "OR", Bool, bool(res)), nil
+ },
+ }
+
+ randFunc = rand.Float64
+ randIntFunc = rand.Intn
+
+ functions = map[string]Function{
+ "abs": numericFunction("Abs", math.Abs),
+ "acos": numericFunction("Acos", math.Acos),
+ "acosh": numericFunction("Acosh", math.Acosh),
+ "asin": numericFunction("Asin", math.Asin),
+ "asinh": numericFunction("Asinh", math.Asinh),
+ "atan": numericFunction("Atan", math.Atan),
+ "atanh": numericFunction("Atanh", math.Atanh),
+ "cbrt": numericFunction("Cbrt", math.Cbrt),
+ "ceil": numericFunction("Ceil", math.Ceil),
+ "cos": numericFunction("Cos", math.Cos),
+ "cosh": numericFunction("Cosh", math.Cosh),
+ "erf": numericFunction("Erf", math.Erf),
+ "erfc": numericFunction("Erfc", math.Erfc),
+ "erfcinv": numericFunction("Erfcinv", math.Erfcinv),
+ "erfinv": numericFunction("Erfinv", math.Erfinv),
+ "exp": numericFunction("Exp", math.Exp),
+ "exp2": numericFunction("Exp2", math.Exp2),
+ "expm1": numericFunction("Expm1", math.Expm1),
+ "floor": numericFunction("Floor", math.Floor),
+ "gamma": numericFunction("Gamma", math.Gamma),
+ "j0": numericFunction("J0", math.J0),
+ "j1": numericFunction("J1", math.J1),
+ "log": numericFunction("Log", math.Log),
+ "log10": numericFunction("Log10", math.Log10),
+ "log1p": numericFunction("Log1p", math.Log1p),
+ "log2": numericFunction("Log2", math.Log2),
+ "logb": numericFunction("Logb", math.Logb),
+ "round": numericFunction("Round", math.Round),
+ "roundtoeven": numericFunction("RoundToEven", math.RoundToEven),
+ "sin": numericFunction("Sin", math.Sin),
+ "sinh": numericFunction("Sinh", math.Sinh),
+ "sqrt": numericFunction("Sqrt", math.Sqrt),
+ "tan": numericFunction("Tan", math.Tan),
+ "tanh": numericFunction("Tanh", math.Tanh),
+ "trunc": numericFunction("Trunc", math.Trunc),
+ "y0": numericFunction("Y0", math.Y0),
+ "y1": numericFunction("Y1", math.Y1),
+
+ "pow10": func(node *Node) (result *Node, err error) {
+ if node == nil {
+ return valueNode(nil, "Pow10", Numeric, 0), nil
+ }
+ num, err := node.getInteger()
+ if err != nil {
+ return
+ }
+ return valueNode(nil, "Pow10", Numeric, float64(math.Pow10(num))), nil
+ },
+ "length": func(node *Node) (result *Node, err error) {
+ if node == nil {
+ return valueNode(nil, "length", Numeric, float64(0)), nil
+ }
+ if node.IsArray() {
+ return valueNode(nil, "length", Numeric, float64(node.Size())), nil
+ }
+ if node.IsString() {
+ if res, err := node.GetString(); err != nil {
+ return nil, err
+ } else {
+ return valueNode(nil, "length", Numeric, float64(len(res))), nil
+ }
+ }
+ return valueNode(nil, "length", Numeric, float64(1)), nil
+ },
+ "size": func(node *Node) (result *Node, err error) {
+ return valueNode(nil, "size", Numeric, float64(node.Size())), nil
+ },
+ "factorial": func(node *Node) (result *Node, err error) {
+ if node == nil {
+ return valueNode(nil, "factorial", Numeric, 0), nil
+ }
+ num, err := node.getUInteger()
+ if err != nil {
+ return
+ }
+ return valueNode(nil, "factorial", Numeric, float64(mathFactorial(num))), nil
+ },
+ "avg": func(node *Node) (result *Node, err error) {
+ if node == nil {
+ return valueNode(nil, "avg", Null, nil), nil
+ }
+ if node.isContainer() {
+ sum := float64(0)
+ if node.Size() == 0 {
+ return valueNode(nil, "avg", Numeric, sum), nil
+ }
+ var value float64
+ for _, temp := range node.Inheritors() {
+ value, err = temp.GetNumeric()
+ if err != nil {
+ return nil, err
+ }
+ sum += value
+ }
+ return valueNode(nil, "avg", Numeric, sum/float64(node.Size())), nil
+ }
+ if node.IsNumeric() {
+ value, err := node.GetNumeric()
+ if err != nil {
+ return nil, err
+ }
+ return valueNode(nil, "avg", Numeric, value), nil
+ }
+ return valueNode(nil, "avg", Null, nil), nil
+ },
+ "b64decode": func(node *Node) (result *Node, err error) {
+ if node.IsString() {
+ if sourceString, err := node.GetString(); err != nil {
+ return nil, err
+ } else {
+ var result []byte
+ result, err = base64.StdEncoding.WithPadding(base64.StdPadding).DecodeString(sourceString)
+ if err != nil {
+ // then for NO_PAD encoded strings, if the first result with error
+ result, err = base64.StdEncoding.WithPadding(base64.NoPadding).DecodeString(sourceString)
+ }
+ if err != nil {
+ return nil, err
+ }
+ return valueNode(nil, "b64decode", String, string(result)), nil
+ }
+ }
+ return valueNode(nil, "b64decode", Null, nil), nil
+ },
+ "b64encoden": func(node *Node) (result *Node, err error) {
+ if node.IsString() {
+ if sourceString, err := node.GetString(); err != nil {
+ return nil, err
+ } else {
+ remainder := len(sourceString) % 3
+ size := len(sourceString) / 3 * 4
+ if remainder != 0 {
+ size += 1 + remainder
+ }
+ var result []byte = make([]byte, size)
+ base64.StdEncoding.WithPadding(base64.NoPadding).Encode(result, []byte(sourceString))
+ return valueNode(nil, "b64encoden", String, string(result)), nil
+ }
+ }
+ return valueNode(nil, "b64encoden", Null, nil), nil
+ },
+ "b64encode": func(node *Node) (result *Node, err error) {
+ if node.IsString() {
+ if sourceString, err := node.GetString(); err != nil {
+ return nil, err
+ } else {
+ remainder := len(sourceString) % 3
+ size := len(sourceString) / 3 * 4
+ if remainder != 0 {
+ size += 4
+ }
+ var result []byte = make([]byte, size)
+ base64.StdEncoding.WithPadding(base64.StdPadding).Encode(result, []byte(sourceString))
+ return valueNode(nil, "b64encode", String, string(result)), nil
+ }
+ }
+ return valueNode(nil, "b64encode", Null, nil), nil
+ },
+ "sum": func(node *Node) (result *Node, err error) {
+ if node == nil {
+ return valueNode(nil, "sum", Null, nil), nil
+ }
+ if node.isContainer() {
+ sum := float64(0)
+ if node.Size() == 0 {
+ return valueNode(nil, "sum", Numeric, sum), nil
+ }
+ var value float64
+ for _, temp := range node.Inheritors() {
+ value, err = temp.GetNumeric()
+ if err != nil {
+ return nil, err
+ }
+ sum += value
+ }
+ return valueNode(nil, "sum", Numeric, sum), nil
+ }
+ if node.IsNumeric() {
+ value, err := node.GetNumeric()
+ if err != nil {
+ return nil, err
+ }
+ return valueNode(nil, "sum", Numeric, value), nil
+ }
+ return valueNode(nil, "sum", Null, nil), nil
+ },
+ "not": func(node *Node) (result *Node, err error) {
+ if value, err := boolean(node); err != nil {
+ return nil, err
+ } else {
+ return valueNode(nil, "not", Bool, !value), nil
+ }
+ },
+ "rand": func(node *Node) (result *Node, err error) {
+ if node == nil {
+ return nil, errorType()
+ }
+ num, err := node.GetNumeric()
+ if err != nil {
+ return
+ }
+ return valueNode(nil, "Rand", Numeric, randFunc()*num), nil
+ },
+ "randint": func(node *Node) (result *Node, err error) {
+ if node == nil {
+ return nil, errorType()
+ }
+ num, err := node.getInteger()
+ if err != nil {
+ return
+ }
+ return valueNode(nil, "RandInt", Numeric, float64(randIntFunc(num))), nil
+ },
+ "last": func(node *Node) (result *Node, err error) {
+ if node.IsArray() {
+ array := node.Inheritors()
+ if len(array) > 0 {
+ return array[len(array)-1], nil
+ }
+ }
+ return valueNode(nil, "last", Null, nil), nil
+ },
+ "first": func(node *Node) (result *Node, err error) {
+ if node.IsArray() {
+ array := node.Inheritors()
+ if len(array) > 0 {
+ return array[0], nil
+ }
+ }
+ return valueNode(nil, "first", Null, nil), nil
+ },
+ "parent": func(node *Node) (result *Node, err error) {
+ if node == nil {
+ return valueNode(nil, "parent", Null, nil), nil
+ }
+ if node.parent != nil {
+ return node.parent, nil
+ }
+ return valueNode(nil, "parent", Null, nil), nil
+ },
+ "root": func(node *Node) (result *Node, err error) {
+ if node == nil {
+ return valueNode(nil, "root", Null, nil), nil
+ }
+ root := node.root()
+ if root != nil {
+ return root, nil
+ }
+ return valueNode(nil, "root", Null, nil), nil
+ },
+ "key": func(node *Node) (result *Node, err error) {
+ if node == nil {
+ return valueNode(nil, "key", Null, nil), nil
+ }
+ if node.parent != nil {
+ if node.parent.IsObject() {
+ return valueNode(nil, "key", String, node.Key()), nil
+ }
+ }
+ return valueNode(nil, "key", Null, nil), nil
+ },
+ }
+
+ constants = map[string]*Node{
+ "e": valueNode(nil, "e", Numeric, float64(math.E)),
+ "pi": valueNode(nil, "pi", Numeric, float64(math.Pi)),
+ "phi": valueNode(nil, "phi", Numeric, float64(math.Phi)),
+
+ "sqrt2": valueNode(nil, "sqrt2", Numeric, float64(math.Sqrt2)),
+ "sqrte": valueNode(nil, "sqrte", Numeric, float64(math.SqrtE)),
+ "sqrtpi": valueNode(nil, "sqrtpi", Numeric, float64(math.SqrtPi)),
+ "sqrtphi": valueNode(nil, "sqrtphi", Numeric, float64(math.SqrtPhi)),
+
+ "ln2": valueNode(nil, "ln2", Numeric, float64(math.Ln2)),
+ "log2e": valueNode(nil, "log2e", Numeric, float64(math.Log2E)),
+ "ln10": valueNode(nil, "ln10", Numeric, float64(math.Ln10)),
+ "log10e": valueNode(nil, "log10e", Numeric, float64(math.Log10E)),
+
+ "true": valueNode(nil, "true", Bool, true),
+ "false": valueNode(nil, "false", Bool, false),
+ "null": valueNode(nil, "null", Null, nil),
+ }
+)
+
+// AddFunction add a function for internal JSONPath script
+func AddFunction(alias string, function Function) {
+ functions[strings.ToLower(alias)] = function
+}
+
+// AddOperation add an operation for internal JSONPath script
+func AddOperation(alias string, prior uint8, right bool, operation Operation) {
+ alias = strings.ToLower(alias)
+ operations[alias] = operation
+ priority[alias] = prior
+ priorityChar[alias[0]] = true
+ if right {
+ rightOp[alias] = true
+ }
+}
+
+// AddConstant add a constant for internal JSONPath script
+func AddConstant(alias string, value *Node) {
+ constants[strings.ToLower(alias)] = value
+}
+
+func numericFunction(name string, fn func(float float64) float64) Function {
+ return func(node *Node) (result *Node, err error) {
+ if node.IsNumeric() {
+ num, err := node.GetNumeric()
+ if err != nil {
+ return nil, err
+ }
+ return valueNode(nil, name, Numeric, fn(num)), nil
+ }
+ return nil, errorRequest("function '%s' was called from non numeric node", name)
+ }
+}
+
+func mathFactorial(x uint) uint {
+ if x == 0 {
+ return 1
+ }
+ return x * mathFactorial(x-1)
+}
+
+func comparisonOperationsOrder() []string {
+ result := make([]string, 0, len(operations))
+ for operation := range operations {
+ result = append(result, operation)
+ }
+
+ sort.Slice(result, func(i, j int) bool {
+ return len(result[i]) > len(result[j])
+ })
+ return result
+}
diff --git a/vendor/github.com/spyzhov/ajson/node.go b/vendor/github.com/spyzhov/ajson/node.go
new file mode 100644
index 0000000..baca667
--- /dev/null
+++ b/vendor/github.com/spyzhov/ajson/node.go
@@ -0,0 +1,949 @@
+package ajson
+
+import (
+ "math"
+ "sort"
+ "strconv"
+ "sync/atomic"
+)
+
+// Node is a main struct, presents any type of JSON node.
+// Available types are:
+//
+// const (
+// Null NodeType = iota
+// Numeric
+// String
+// Bool
+// Array
+// Object
+// )
+//
+// Every type has its own methods to be called.
+// Every Node contains link to a byte data, parent and children, also calculated type of value, atomic value and internal information.
+type Node struct {
+ parent *Node
+ children map[string]*Node
+ key *string
+ index *int
+ _type NodeType
+ data *[]byte
+ borders [2]int
+ value atomic.Value
+ dirty bool
+}
+
+// NodeType is a kind of reflection of JSON type to a type of golang
+type NodeType int32
+
+// Reflections:
+//
+// Null = nil.(interface{})
+// Numeric = float64
+// String = string
+// Bool = bool
+// Array = []*Node
+// Object = map[string]*Node
+//
+const (
+ // Null is reflection of nil.(interface{})
+ Null NodeType = iota
+ // Numeric is reflection of float64
+ Numeric
+ // String is reflection of string
+ String
+ // Bool is reflection of bool
+ Bool
+ // Array is reflection of []*Node
+ Array
+ // Object is reflection of map[string]*Node
+ Object
+)
+
+// NullNode is constructor for Node with Null value
+func NullNode(key string) *Node {
+ return &Node{
+ _type: Null,
+ key: &key,
+ dirty: true,
+ }
+}
+
+// NumericNode is constructor for Node with a Numeric value
+func NumericNode(key string, value float64) (current *Node) {
+ current = &Node{
+ _type: Numeric,
+ key: &key,
+ dirty: true,
+ }
+ current.value.Store(value)
+ return
+}
+
+// StringNode is constructor for Node with a String value
+func StringNode(key string, value string) (current *Node) {
+ current = &Node{
+ _type: String,
+ key: &key,
+ dirty: true,
+ }
+ current.value.Store(value)
+ return
+}
+
+// BoolNode is constructor for Node with a Bool value
+func BoolNode(key string, value bool) (current *Node) {
+ current = &Node{
+ _type: Bool,
+ key: &key,
+ dirty: true,
+ }
+ current.value.Store(value)
+ return
+}
+
+// ArrayNode is constructor for Node with an Array value
+func ArrayNode(key string, value []*Node) (current *Node) {
+ current = &Node{
+ data: nil,
+ _type: Array,
+ key: &key,
+ dirty: true,
+ }
+ current.children = make(map[string]*Node, len(value))
+ if value != nil {
+ for i := range value {
+ var index = i
+ current.children[strconv.Itoa(index)] = value[index]
+ value[index].parent = current
+ value[index].index = &index
+ }
+ current.value.Store(value)
+ }
+ return
+}
+
+// ObjectNode is constructor for Node with an Object value
+func ObjectNode(key string, value map[string]*Node) (current *Node) {
+ current = &Node{
+ _type: Object,
+ key: &key,
+ children: value,
+ dirty: true,
+ }
+ if value != nil {
+ for key, val := range value {
+ vkey := key
+ val.parent = current
+ val.key = &vkey
+ }
+ current.value.Store(value)
+ } else {
+ current.children = make(map[string]*Node)
+ }
+ return
+}
+
+func newNode(parent *Node, buf *buffer, _type NodeType, key **string) (current *Node, err error) {
+ current = &Node{
+ parent: parent,
+ data: &buf.data,
+ borders: [2]int{buf.index, 0},
+ _type: _type,
+ key: *key,
+ dirty: false,
+ }
+ if _type == Object || _type == Array {
+ current.children = make(map[string]*Node)
+ }
+ if parent != nil {
+ if parent.IsArray() {
+ size := len(parent.children)
+ current.index = &size
+ parent.children[strconv.Itoa(size)] = current
+ } else if parent.IsObject() {
+ if *key == nil {
+ err = errorSymbol(buf)
+ } else {
+ parent.children[**key] = current
+ }
+ } else {
+ err = errorSymbol(buf)
+ }
+ }
+ return
+}
+
+func valueNode(parent *Node, key string, _type NodeType, value interface{}) (current *Node) {
+ current = &Node{
+ parent: parent,
+ data: nil,
+ borders: [2]int{0, 0},
+ _type: _type,
+ key: &key,
+ dirty: true,
+ }
+ if value != nil {
+ current.value.Store(value)
+ }
+ return
+}
+
+// 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 == nil {
+ return nil
+ }
+ if n.ready() && !n.dirty && n.data != nil {
+ return (*n.data)[n.borders[0]:n.borders[1]]
+ }
+ return nil
+}
+
+// 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, err := Marshal(n)
+ if err != nil {
+ return "Error: " + err.Error()
+ }
+ 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)
+ }
+ return
+}
+
+// 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
+}
+
+// Value is calculating and returns a value of current node.
+//
+// It returns nil, if current node type is Null.
+//
+// It returns float64, if current node type is Numeric.
+//
+// It returns string, if current node type is String.
+//
+// It returns bool, if current node type is Bool.
+//
+// It returns []*Node, if current node type is Array.
+//
+// It returns map[string]*Node, if current node type is Object.
+//
+// BUT! Current method doesn't calculate underlying nodes (use method Node.Unpack for that).
+//
+// 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()
+ case Numeric:
+ return n.GetNumeric()
+ case String:
+ return n.GetString()
+ case Bool:
+ return n.GetBool()
+ case Array:
+ return n.GetArray()
+ case Object:
+ return n.GetObject()
+ }
+ return nil, errorType()
+}
+
+func (n *Node) getValue() (value interface{}, err error) {
+ value = n.value.Load()
+ if value == nil {
+ switch n._type {
+ case Null:
+ return nil, nil
+ case Numeric:
+ value, err = strconv.ParseFloat(string(n.Source()), 64)
+ if err != nil {
+ return
+ }
+ n.value.Store(value)
+ case String:
+ var ok bool
+ value, ok = unquote(n.Source(), quotes)
+ if !ok {
+ return "", errorAt(n.borders[0], (*n.data)[n.borders[0]])
+ }
+ n.value.Store(value)
+ case Bool:
+ if len(n.Source()) == 0 {
+ return nil, errorUnparsed()
+ }
+ b := n.Source()[0]
+ value = b == 't' || b == 'T'
+ n.value.Store(value)
+ case Array:
+ children := make([]*Node, len(n.children))
+ for _, child := range n.children {
+ children[*child.index] = child
+ }
+ value = children
+ n.value.Store(value)
+ case Object:
+ result := make(map[string]*Node)
+ for key, child := range n.children {
+ result[key] = child
+ }
+ value = result
+ n.value.Store(value)
+ }
+ }
+ return
+}
+
+// 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()
+ }
+ return nil, nil
+}
+
+// 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()
+ }
+ iValue, err := n.getValue()
+ if err != nil {
+ return 0, err
+ }
+ value, ok := iValue.(float64)
+ if !ok {
+ return value, errorType()
+ }
+ return value, nil
+}
+
+// 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()
+ }
+ iValue, err := n.getValue()
+ if err != nil {
+ return "", err
+ }
+ value, ok := iValue.(string)
+ if !ok {
+ return value, errorType()
+ }
+ return value, nil
+}
+
+// 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()
+ }
+ iValue, err := n.getValue()
+ if err != nil {
+ return false, err
+ }
+ value, ok := iValue.(bool)
+ if !ok {
+ return value, errorType()
+ }
+ return value, nil
+}
+
+// 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()
+ }
+ iValue, err := n.getValue()
+ if err != nil {
+ return nil, err
+ }
+ value, ok := iValue.([]*Node)
+ if !ok {
+ return value, errorType()
+ }
+ return value, nil
+}
+
+// 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()
+ }
+ iValue, err := n.getValue()
+ if err != nil {
+ return nil, err
+ }
+ value, ok := iValue.(map[string]*Node)
+ if !ok {
+ return value, errorType()
+ }
+ return value, nil
+}
+
+// MustNull returns nil, if current type is Null, else: panic if error happened
+func (n *Node) MustNull() (value interface{}) {
+ value, err := n.GetNull()
+ if err != nil {
+ panic(err)
+ }
+ return
+}
+
+// MustNumeric returns float64, if current type is Numeric, else: panic if error happened
+func (n *Node) MustNumeric() (value float64) {
+ value, err := n.GetNumeric()
+ if err != nil {
+ panic(err)
+ }
+ return
+}
+
+// MustString returns string, if current type is String, else: panic if error happened
+func (n *Node) MustString() (value string) {
+ value, err := n.GetString()
+ if err != nil {
+ panic(err)
+ }
+ return
+}
+
+// MustBool returns bool, if current type is Bool, else: panic if error happened
+func (n *Node) MustBool() (value bool) {
+ value, err := n.GetBool()
+ if err != nil {
+ panic(err)
+ }
+ return
+}
+
+// MustArray returns []*Node, if current type is Array, else: panic if error happened
+func (n *Node) MustArray() (value []*Node) {
+ value, err := n.GetArray()
+ if err != nil {
+ panic(err)
+ }
+ return
+}
+
+// MustObject returns map[string]*Node, if current type is Object, else: panic if error happened
+func (n *Node) MustObject() (value map[string]*Node) {
+ value, err := n.GetObject()
+ if err != nil {
+ panic(err)
+ }
+ return
+}
+
+// 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
+ case Numeric:
+ value, err = n.Value()
+ if _, ok := value.(float64); !ok {
+ return nil, errorType()
+ }
+ case String:
+ value, err = n.Value()
+ if _, ok := value.(string); !ok {
+ return nil, errorType()
+ }
+ case Bool:
+ value, err = n.Value()
+ if _, ok := value.(bool); !ok {
+ return nil, errorType()
+ }
+ case Array:
+ children := make([]interface{}, len(n.children))
+ for _, child := range n.children {
+ val, err := child.Unpack()
+ if err != nil {
+ return nil, err
+ }
+ children[*child.index] = val
+ }
+ value = children
+ case Object:
+ result := make(map[string]interface{})
+ for key, child := range n.children {
+ result[key], err = child.Unpack()
+ if err != nil {
+ return nil, err
+ }
+ }
+ value = result
+ }
+ return
+}
+
+// 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()
+ }
+ if index < 0 {
+ index += len(n.children)
+ }
+ child, ok := n.children[strconv.Itoa(index)]
+ if !ok {
+ return nil, errorRequest("out of index %d", index)
+ }
+ return child, nil
+}
+
+// MustIndex will return child node of current array node. If current node is not Array, or index is unavailable, raise a panic
+func (n *Node) MustIndex(index int) (value *Node) {
+ value, err := n.GetIndex(index)
+ if err != nil {
+ panic(err)
+ }
+ return
+}
+
+// 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()
+ }
+ value, ok := n.children[key]
+ if !ok {
+ return nil, errorRequest("wrong key '%s'", key)
+ }
+ return value, nil
+}
+
+// MustKey will return child node of current object node. If current node is not Object, or key is unavailable, raise a panic
+func (n *Node) MustKey(key string) (value *Node) {
+ value, err := n.GetKey(key)
+ if err != nil {
+ panic(err)
+ }
+ return
+}
+
+// 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 {
+ return "$"
+ }
+ if n.key != nil {
+ return n.parent.Path() + "['" + n.Key() + "']"
+ }
+ return n.parent.Path() + "[" + strconv.Itoa(n.Index()) + "]"
+}
+
+// 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:
+ lnum, rnum, err := _bools(n, node)
+ if err != nil {
+ return false, err
+ }
+ result = lnum == rnum
+ case Numeric:
+ lnum, rnum, err := _floats(n, node)
+ if err != nil {
+ return false, err
+ }
+ result = lnum == rnum
+ case String:
+ lnum, rnum, err := _strings(n, node)
+ if err != nil {
+ return false, err
+ }
+ result = lnum == rnum
+ case Null:
+ // Null type always is the same
+ result = true
+ case Array:
+ lnum, rnum, err := _arrays(n, node)
+ if err != nil {
+ return false, err
+ }
+ if len(lnum) == len(rnum) {
+ result = true
+ for i := range lnum {
+ result, err = lnum[i].Eq(rnum[i])
+ if err != nil {
+ return false, err
+ }
+ if !result {
+ return false, err
+ }
+ }
+ }
+ case Object:
+ lnum, rnum, err := _objects(n, node)
+ if err != nil {
+ return false, err
+ }
+ if len(lnum) == len(rnum) {
+ result = true
+ for i := range lnum {
+ element, ok := rnum[i]
+ if !ok {
+ return false, nil
+ }
+ result, err = lnum[i].Eq(element)
+ if err != nil {
+ return false, err
+ }
+ if !result {
+ return false, err
+ }
+ }
+ }
+ }
+ }
+ return
+}
+
+// Neq check if nodes value are not the same
+func (n *Node) Neq(node *Node) (result bool, err error) {
+ result, err = n.Eq(node)
+ return !result, err
+}
+
+// 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:
+ lnum, rnum, err := _floats(n, node)
+ if err != nil {
+ return false, err
+ }
+ result = lnum < rnum
+ case String:
+ lnum, rnum, err := _strings(n, node)
+ if err != nil {
+ return false, err
+ }
+ result = lnum < rnum
+ default:
+ return false, errorType()
+ }
+ }
+ return
+}
+
+// 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:
+ lnum, rnum, err := _floats(n, node)
+ if err != nil {
+ return false, err
+ }
+ result = lnum <= rnum
+ case String:
+ lnum, rnum, err := _strings(n, node)
+ if err != nil {
+ return false, err
+ }
+ result = lnum <= rnum
+ default:
+ return false, errorType()
+ }
+ }
+ return
+}
+
+// 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:
+ lnum, rnum, err := _floats(n, node)
+ if err != nil {
+ return false, err
+ }
+ result = lnum > rnum
+ case String:
+ lnum, rnum, err := _strings(n, node)
+ if err != nil {
+ return false, err
+ }
+ result = lnum > rnum
+ default:
+ return false, errorType()
+ }
+ }
+ return
+}
+
+// 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:
+ lnum, rnum, err := _floats(n, node)
+ if err != nil {
+ return false, err
+ }
+ result = lnum >= rnum
+ case String:
+ lnum, rnum, err := _strings(n, node)
+ if err != nil {
+ return false, err
+ }
+ result = lnum >= rnum
+ default:
+ return false, errorType()
+ }
+ }
+ return
+}
+
+func (n *Node) ready() bool {
+ return n.borders[1] != 0
+}
+
+func (n *Node) isContainer() bool {
+ return n._type == Array || n._type == Object
+}
+
+func (n *Node) getInteger() (int, error) {
+ if !n.IsNumeric() {
+ return 0, errorType()
+ }
+ float, err := n.GetNumeric()
+ if err != nil {
+ return 0, err
+ }
+ if math.Mod(float, 1.0) != 0 {
+ return 0, errorRequest("node is not INT")
+ }
+ return int(float), nil
+}
+
+func (n *Node) getUInteger() (uint, error) {
+ result, err := n.getInteger()
+ if err != nil {
+ return 0, err
+ }
+ if result < 0 {
+ return 0, errorRequest("node is not UINT")
+ }
+ return uint(result), nil
+}
+
+// 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)
+ keys := n.Keys()
+ sort.Slice(keys, func(i, j int) bool {
+ return keys[i] < keys[j]
+ })
+ for i, key := range keys {
+ result[i] = n.children[key]
+ }
+ } else if n.IsArray() {
+ result = make([]*Node, size)
+ for _, element := range n.children {
+ result[*element.index] = element
+ }
+ }
+ return
+}
+
+// JSONPath evaluate path for current node
+func (n *Node) JSONPath(path string) (result []*Node, err error) {
+ commands, err := ParseJSONPath(path)
+ if err != nil {
+ return nil, err
+ }
+ return ApplyJSONPath(n, commands)
+}
+
+// root returns the root node
+func (n *Node) root() (node *Node) {
+ node = n
+ for node.parent != nil {
+ node = node.parent
+ }
+ return node
+}
diff --git a/vendor/github.com/spyzhov/ajson/node_mutations.go b/vendor/github.com/spyzhov/ajson/node_mutations.go
new file mode 100644
index 0000000..0d5bf72
--- /dev/null
+++ b/vendor/github.com/spyzhov/ajson/node_mutations.go
@@ -0,0 +1,378 @@
+package ajson
+
+import (
+ "strconv"
+ "sync/atomic"
+)
+
+// IsDirty is the flag that shows, was node changed or not
+func (n *Node) IsDirty() bool {
+ return n.dirty
+}
+
+// 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 updates current node value with Numeric value
+func (n *Node) SetNumeric(value float64) error {
+ return n.update(Numeric, value)
+}
+
+// SetString updates current node value with String value
+func (n *Node) SetString(value string) error {
+ return n.update(String, value)
+}
+
+// SetBool updates current node value with Bool value
+func (n *Node) SetBool(value bool) error {
+ return n.update(Bool, value)
+}
+
+// SetArray updates current node value with Array value
+func (n *Node) SetArray(value []*Node) error {
+ return n.update(Array, value)
+}
+
+// SetObject updates current node value with Object value
+func (n *Node) SetObject(value map[string]*Node) error {
+ return n.update(Object, value)
+}
+
+// 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()
+ }
+ for _, val := range value {
+ if err := n.appendNode(nil, val); err != nil {
+ return err
+ }
+ }
+ n.mark()
+ return nil
+}
+
+// AppendObject appends current Object node value with key:value
+func (n *Node) AppendObject(key string, value *Node) error {
+ if !n.IsObject() {
+ return errorType()
+ }
+ err := n.appendNode(&key, value)
+ if err != nil {
+ return err
+ }
+ n.mark()
+ return nil
+}
+
+// DeleteNode removes element child
+func (n *Node) DeleteNode(value *Node) error {
+ return n.remove(value)
+}
+
+// DeleteKey removes element from Object, by it's key
+func (n *Node) DeleteKey(key string) error {
+ node, err := n.GetKey(key)
+ if err != nil {
+ return err
+ }
+ return n.remove(node)
+}
+
+// PopKey removes element from Object, by it's key and return it
+func (n *Node) PopKey(key string) (node *Node, err error) {
+ node, err = n.GetKey(key)
+ if err != nil {
+ return
+ }
+ return node, n.remove(node)
+}
+
+// DeleteIndex removes element from Array, by it's index
+func (n *Node) DeleteIndex(index int) error {
+ node, err := n.GetIndex(index)
+ if err != nil {
+ return err
+ }
+ return n.remove(node)
+}
+
+// PopIndex removes element from Array, by it's index and return it
+func (n *Node) PopIndex(index int) (node *Node, err error) {
+ node, err = n.GetIndex(index)
+ if err != nil {
+ return
+ }
+ return node, n.remove(node)
+}
+
+// Delete removes element from parent. For root - do nothing.
+func (n *Node) Delete() error {
+ if n.parent == nil {
+ return nil
+ }
+ return n.parent.remove(n)
+}
+
+// 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.setReference(nil, nil, nil)
+ return node
+}
+
+func (n *Node) clone() *Node {
+ node := &Node{
+ parent: n.parent,
+ children: make(map[string]*Node, len(n.children)),
+ key: cptrs(n.key),
+ index: cptri(n.index),
+ _type: n._type,
+ data: n.data,
+ borders: n.borders,
+ value: n.value,
+ dirty: n.dirty,
+ }
+ for key, value := range n.children {
+ clone := value.clone()
+ clone.parent = node
+ node.children[key] = clone
+ }
+ return node
+}
+
+// update method updates stored value, with validations
+func (n *Node) update(_type NodeType, value interface{}) error {
+ // validate
+ err := n.validate(_type, value)
+ if err != nil {
+ return err
+ }
+ // update
+ n.mark()
+ n.clear()
+
+ atomic.StoreInt32((*int32)(&n._type), int32(_type))
+ n.value = atomic.Value{}
+ if value != nil {
+ switch _type {
+ case Array:
+ nodes := value.([]*Node)
+ n.children = make(map[string]*Node, len(nodes))
+ for _, node := range nodes {
+ tnode := node
+ if err = n.appendNode(nil, tnode); err != nil {
+ return err
+ }
+ }
+ case Object:
+ nodes := value.(map[string]*Node)
+ n.children = make(map[string]*Node, len(nodes))
+ for key, node := range nodes {
+ tkey := key
+ tnode := node
+ if err = n.appendNode(&tkey, tnode); err != nil {
+ return err
+ }
+ }
+ }
+ n.value.Store(value)
+ }
+ return nil
+}
+
+// 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 {
+ return errorType()
+ }
+ case Numeric:
+ if _, ok := value.(float64); !ok {
+ return errorType()
+ }
+ case String:
+ if _, ok := value.(string); !ok {
+ return errorType()
+ }
+ case Bool:
+ if _, ok := value.(bool); !ok {
+ return errorType()
+ }
+ case Array:
+ if value != nil {
+ if _, ok := value.([]*Node); !ok {
+ return errorType()
+ }
+ }
+ case Object:
+ if value != nil {
+ if _, ok := value.(map[string]*Node); !ok {
+ return errorType()
+ }
+ }
+ }
+ return nil
+}
+
+// remove method removes value from current container
+func (n *Node) remove(value *Node) error {
+ if !n.isContainer() {
+ return errorType()
+ }
+ if value.parent != n {
+ return errorRequest("wrong parent")
+ }
+ n.mark()
+ if n.IsArray() {
+ delete(n.children, strconv.Itoa(*value.index))
+ n.dropindex(*value.index)
+ } else {
+ delete(n.children, *value.key)
+ }
+ value.parent = nil
+ return nil
+}
+
+// dropindex: internal method to reindexing current array value
+func (n *Node) dropindex(index int) {
+ for i := index + 1; i <= len(n.children); i++ {
+ previous := i - 1
+ if current, ok := n.children[strconv.Itoa(i)]; ok {
+ current.index = &previous
+ n.children[strconv.Itoa(previous)] = current
+ }
+ delete(n.children, strconv.Itoa(i))
+ }
+}
+
+// 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.isParentOrSelfNode(value) {
+ return errorRequest("attempt to create infinite loop")
+ }
+ if value.parent != nil {
+ if err := value.parent.remove(value); err != nil {
+ return err
+ }
+ }
+ value.parent = n
+ value.key = key
+ if key != nil {
+ if old, ok := n.children[*key]; ok {
+ if old != value {
+ if err := n.remove(old); err != nil {
+ return err
+ }
+ }
+ }
+ n.children[*key] = value
+ } else {
+ index := len(n.children)
+ value.index = &index
+ n.children[strconv.Itoa(index)] = value
+ }
+ return nil
+}
+
+// mark node as dirty, with all parents (up the tree)
+func (n *Node) mark() {
+ node := n
+ for node != nil && !node.dirty {
+ node.dirty = true
+ node = node.parent
+ }
+}
+
+// clear current value of node
+func (n *Node) clear() {
+ n.data = nil
+ n.borders[1] = 0
+ for key := range n.children {
+ n.children[key].parent = nil
+ }
+ 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 {
+ 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 = cptri(index)
+}
diff --git a/vendor/github.com/spyzhov/ajson/quote.go b/vendor/github.com/spyzhov/ajson/quote.go
new file mode 100644
index 0000000..b5b9439
--- /dev/null
+++ b/vendor/github.com/spyzhov/ajson/quote.go
@@ -0,0 +1,296 @@
+package ajson
+
+import "unicode/utf8"
+
+// This file was copied from encoding/json library.
+// fixme: https://github.com/spyzhov/ajson/issues/13
+// (c) Golang: encoding/json/tables.go
+// (c) Golang: encoding/json/encode.go
+
+var hex = "0123456789abcdef"
+
+// safeSet holds the value true if the ASCII character with the given array
+// position can be represented inside a JSON string without any further
+// escaping.
+//
+// All values are true except for the ASCII control characters (0-31), the
+// double quote ("), and the backslash character ("\").
+var safeSet = [utf8.RuneSelf]bool{
+ ' ': true,
+ '!': true,
+ '"': false,
+ '#': true,
+ '$': true,
+ '%': true,
+ '&': true,
+ '\'': true,
+ '(': true,
+ ')': true,
+ '*': true,
+ '+': true,
+ ',': true,
+ '-': true,
+ '.': true,
+ '/': true,
+ '0': true,
+ '1': true,
+ '2': true,
+ '3': true,
+ '4': true,
+ '5': true,
+ '6': true,
+ '7': true,
+ '8': true,
+ '9': true,
+ ':': true,
+ ';': true,
+ '<': true,
+ '=': true,
+ '>': true,
+ '?': true,
+ '@': true,
+ 'A': true,
+ 'B': true,
+ 'C': true,
+ 'D': true,
+ 'E': true,
+ 'F': true,
+ 'G': true,
+ 'H': true,
+ 'I': true,
+ 'J': true,
+ 'K': true,
+ 'L': true,
+ 'M': true,
+ 'N': true,
+ 'O': true,
+ 'P': true,
+ 'Q': true,
+ 'R': true,
+ 'S': true,
+ 'T': true,
+ 'U': true,
+ 'V': true,
+ 'W': true,
+ 'X': true,
+ 'Y': true,
+ 'Z': true,
+ '[': true,
+ '\\': false,
+ ']': true,
+ '^': true,
+ '_': true,
+ '`': true,
+ 'a': true,
+ 'b': true,
+ 'c': true,
+ 'd': true,
+ 'e': true,
+ 'f': true,
+ 'g': true,
+ 'h': true,
+ 'i': true,
+ 'j': true,
+ 'k': true,
+ 'l': true,
+ 'm': true,
+ 'n': true,
+ 'o': true,
+ 'p': true,
+ 'q': true,
+ 'r': true,
+ 's': true,
+ 't': true,
+ 'u': true,
+ 'v': true,
+ 'w': true,
+ 'x': true,
+ 'y': true,
+ 'z': true,
+ '{': true,
+ '|': true,
+ '}': true,
+ '~': true,
+ '\u007f': true,
+}
+
+// htmlSafeSet holds the value true if the ASCII character with the given
+// array position can be safely represented inside a JSON string, embedded
+// inside of HTML