diff --git a/example/server/main.go b/example/server/main.go index a192ea9..e4c4ccf 100644 --- a/example/server/main.go +++ b/example/server/main.go @@ -2,12 +2,6 @@ package example import ( "encoding/json" - "math/rand" -) - -const ( - _IDChars string = "01234567890abcdef" - _DefaultIDlen int = 8 ) type User struct { @@ -18,8 +12,8 @@ type User struct { var ( Users []*User = []*User{ - {ID: generateID(), Name: "eco", Age: 30}, - {ID: generateID(), Name: "any", Age: 29}, + {ID: "a4fb4201", Name: "eco", Age: 30}, + {ID: "43bd1a0d", Name: "any", Age: 29}, } ) @@ -44,12 +38,3 @@ func MarshalDiscardError(i interface{}) []byte { enc, _ := json.Marshal(i) return enc } - -func generateID() string { - idArr := make([]byte, _DefaultIDlen) - for i := range idArr { - index := rand.Intn(len(_IDChars)) - idArr[i] = _IDChars[index] - } - return string(idArr) -} diff --git a/example/server/test_case.go b/example/server/test_case.go index 81a9fdd..15a28be 100644 --- a/example/server/test_case.go +++ b/example/server/test_case.go @@ -1,13 +1,14 @@ package example import ( + "errors" "net/http" "github.com/ecoshub/taste/server" ) var ( - TestUser *User = &User{ID: "718c9a02", Name: "john", Age: 20} + TestUser []byte = []byte(`{"id":"718c9a02","name":"john","age":20}`) ) var ( @@ -19,8 +20,8 @@ var ( Path: "/api/v1/version", }, Expect: &server.Expect{ - Status: http.StatusOK, - BodyString: "v1.0.0", + Status: http.StatusOK, + Body: []byte("v1.0.0"), }, }, { @@ -31,7 +32,12 @@ var ( }, Expect: &server.Expect{ Status: http.StatusOK, - Body: MarshalDiscardError(Users), + Body: []byte(` + [ + {"id":"a4fb4201","name":"eco","age":30}, + {"id":"43bd1a0d","name":"any","age":29} + ]`), + Error: errors.New("type expectation failed. expected type: 'int', got type: 'string', path: '[0 id]'"), }, }, { @@ -53,7 +59,7 @@ var ( }, Expect: &server.Expect{ Status: http.StatusOK, - Body: MarshalDiscardError(Users[0]), + Body: []byte(`{"id":"a4fb4201","name":"eco","age":30}`), }, }, { @@ -64,7 +70,7 @@ var ( }, Expect: &server.Expect{ Status: http.StatusOK, - Body: MarshalDiscardError(Users[1]), + Body: []byte(`{"id":"43bd1a0d","name":"any","age":29}`), Header: http.Header{ "Content-Type": []string{"application/json; charset=utf-8"}, }, @@ -75,11 +81,16 @@ var ( Request: &server.Request{ Method: http.MethodPost, Path: "/api/v1/user/new", - Body: MarshalDiscardError(TestUser), + Body: []byte(`{"id":"718c9a02","name":"john","age":20}`), }, Expect: &server.Expect{ Status: http.StatusOK, - Body: MarshalDiscardError(append(Users, TestUser)), + Body: []byte(` + [ + {"id":"a4fb4201","name":"eco","age":30}, + {"id":"43bd1a0d","name":"any","age":29}, + {"id":"718c9a02","name":"john","age":20} + ]`), Header: http.Header{ "Content-Type": []string{"application/json; charset=utf-8"}, }, diff --git a/go.mod b/go.mod index 68f2495..7d777d2 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,7 @@ module github.com/ecoshub/taste go 1.16 -require github.com/gin-gonic/gin v1.8.1 +require ( + github.com/ecoshub/jin v0.0.0-20220630143555-793085706848 + github.com/gin-gonic/gin v1.8.1 +) diff --git a/go.sum b/go.sum index 6560f7f..16277eb 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,8 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/ecoshub/jin v0.0.0-20220630143555-793085706848 h1:3bYBxwUprgL61kqd+L2mg+IDZRkockHBn5f66nqYlw8= +github.com/ecoshub/jin v0.0.0-20220630143555-793085706848/go.mod h1:M5bdVepcqwxZZ6tCE0HQxTytn6DYYnzFL/zPKKwMW+4= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.8.1 h1:4+fr/el88TOO3ewCmQr8cx/CtZ/umlIRIs5M4NTNjf8= diff --git a/server/case.go b/server/case.go index 06a8afd..a32d0eb 100644 --- a/server/case.go +++ b/server/case.go @@ -31,4 +31,5 @@ type Expect struct { Body []byte BodyString string Header http.Header + Error error } diff --git a/server/run.go b/server/run.go index c641217..2634518 100644 --- a/server/run.go +++ b/server/run.go @@ -51,13 +51,18 @@ func run(sc *Tester, c *Case, t *testing.T) { } defer resp.Body.Close() - if c.Expect.Body == nil { - if c.Expect.BodyString != "" { - utils.CheckEqual(t, "response-body", string(body), c.Expect.BodyString) + err = utils.Validate(c.Expect.Body, body) + // got error + if err != nil { + // expecting error + if c.Expect.Error != nil { + utils.CheckEqual(t, "error", err, c.Expect.Error) + return } - } else { - utils.CheckEqual(t, "response-body", string(body), string(c.Expect.Body)) + // utils.Fail(t, "", c.Expect.Body, body) + t.Fatalf("err: %v. expected: %s, got: %s", err, c.Expect.Body, body) } + } func resolveBody(r *Request) *bytes.Buffer { diff --git a/test/validation/validation_test.go b/test/validation/validation_test.go new file mode 100644 index 0000000..043a206 --- /dev/null +++ b/test/validation/validation_test.go @@ -0,0 +1,324 @@ +package validation + +import ( + "errors" + "testing" + + "github.com/ecoshub/taste/unit" + "github.com/ecoshub/taste/utils" +) + +var ( + scenario = []*unit.Case{ + { + Name: "validation_fuzzy_object", + Func: unit.Func(utils.Validate( + []byte(``), + []byte(``), + )), + Expect: unit.Returns(nil), + }, + { + Name: "validation_fuzzy_object", + Func: unit.Func(utils.Validate( + []byte(`{}`), + []byte(`{}`), + )), + Expect: unit.Returns(nil), + }, + { + Name: "validation_fuzzy_object", + Func: unit.Func(utils.Validate( + []byte(`{}`), + []byte(``), + )), + Expect: unit.Returns(nil), + }, + { + Name: "validation_fuzzy_object", + Func: unit.Func(utils.Validate( + []byte(`{"name":"eco","age":30,"items":["cellphone","table"]}`), + []byte(`{"name":"eco","age":30,"items":["cellphone","table","chair"]}`), + )), + Expect: unit.Returns(errors.New("unexpected path. path: [items 2]")), + }, + { + Name: "validation_fuzzy_object", + Func: unit.Func(utils.Validate( + []byte(`{"name":"eco"}`), + []byte(`{"name":"test"}`), + )), + Expect: unit.Returns(errors.New("value expectation failed. expected value: 'eco', got value: 'test', path: '[name]'")), + }, + { + Name: "validation_fuzzy_object", + Func: unit.Func(utils.Validate( + []byte(`{"name":"eco"}`), + []byte(`{"name":"eco"}`), + )), + Expect: unit.Returns(nil), + }, + { + Name: "validation_fuzzy_object", + Func: unit.Func(utils.Validate( + []byte(`{"name":"eco","age":30}`), + []byte(`{"name":"eco"}`), + )), + Expect: unit.Returns(errors.New("field is required. field: 'age'")), + }, + { + Name: "validation_fuzzy_object", + Func: unit.Func(utils.Validate( + []byte(`{"name":"eco","age":30}`), + []byte(`{"name":"eco","age":"30"}`), + )), + Expect: unit.Returns(errors.New("type expectation failed. expected type: 'int', got type: 'string', path: '[age]'")), + }, + { + Name: "validation_fuzzy_object", + Func: unit.Func(utils.Validate( + []byte(`{"name|string":"eco","age|int":30}`), + []byte(`{"name":"eco","age":"30"}`), + )), + Expect: unit.Returns(errors.New("type expectation failed. expected type: 'int', got type: 'string', path: '[age]'")), + }, + { + Name: "validation_fuzzy_object", + Func: unit.Func(utils.Validate( + []byte(`{"name|string":"eco","age|int":30}`), + []byte(`{"name":"eco","age":30.0}`), + )), + Expect: unit.Returns(errors.New("type expectation failed. expected type: 'int', got type: 'float', path: '[age]'")), + }, + { + Name: "validation_fuzzy_object", + Func: unit.Func(utils.Validate( + []byte(`{"name|string":"eco","age|int":30}`), + []byte(`{"name":"eco","age":30}`), + )), + Expect: unit.Returns(nil), + }, + { + Name: "validation_fuzzy_object", + Func: unit.Func(utils.Validate( + []byte(`{"name|string":"eco","age|int":30}`), + []byte(`{"name": "eco", "age" :30}`), + )), + Expect: unit.Returns(nil), + }, + { + Name: "validation_fuzzy_object", + Func: unit.Func(utils.Validate( + []byte(`{"name|string":"eco","age|int":30}`), + []byte(`{"name": "eco", "age" :30, "extra":true}`), + )), + Expect: unit.Returns(errors.New("unexpected path. path: [extra]")), + }, + { + Name: "validation_fuzzy_object", + Func: unit.Func(utils.Validate( + []byte(`{"name|string":"eco","age|int":30}`), + []byte(`{"age" :30, "name": "eco"}`), + )), + Expect: unit.Returns(nil), + }, + { + Name: "validation_fuzzy_object", + Func: unit.Func(utils.Validate( + []byte(`{"name|string":"*","age|int":"*"}`), + []byte(`{"age" :30, "name": "eco"}`), + )), + Expect: unit.Returns(nil), + }, + { + Name: "validation_fuzzy_object", + Func: unit.Func(utils.Validate( + []byte(`{"name|string":"*","age|int":"*"}`), + []byte(`{"age" :"emre", "name": 30}`), + )), + Expect: unit.Returns(errors.New("type expectation failed. expected type: 'string', got type: 'int', path: '[name]'")), + }, + { + Name: "validation_fuzzy_object", + Func: unit.Func(utils.Validate( + []byte(`{"name|string":"*","age|int":"*"}`), + []byte(`{"age":72,"name":"test"}`), + )), + Expect: unit.Returns(nil), + }, + { + Name: "validation_fuzzy_object", + Func: unit.Func(utils.Validate( + []byte(`{"name|string":"*","age|int":"30", "*employed|boolean":"*"}`), + []byte(`{"age":30,"name":"eco"}`), + )), + Expect: unit.Returns(nil), + }, + { + Name: "validation_fuzzy_object", + Func: unit.Func(utils.Validate( + []byte(`{"name|string":"*","age|int":"30", "*employed|boolean":"*"}`), + []byte(`{"age":30,"name":"eco","employed":"yes"}`), + )), + Expect: unit.Returns(errors.New("type expectation failed. expected type: 'boolean', got type: 'string', path: '[employed]'")), + }, + { + Name: "validation_fuzzy_object", + Func: unit.Func(utils.Validate( + []byte(`{"name|string":"*","age|int":"30", "*employed|boolean":"*"}`), + []byte(`{"age":30,"name":"eco","employed":false}`), + )), + Expect: unit.Returns(nil), + }, + { + Name: "validation_fuzzy_array", + Func: unit.Func(utils.Validate( + []byte(`[]`), + []byte(`[]`), + )), + Expect: unit.Returns(nil), + }, + { + Name: "validation_fuzzy_array", + Func: unit.Func(utils.Validate( + []byte(`[]`), + []byte(``), + )), + Expect: unit.Returns(nil), + }, + { + Name: "validation_fuzzy_array", + Func: unit.Func(utils.Validate( + []byte(``), + []byte(`[]`), + )), + Expect: unit.Returns(nil), + }, + { + Name: "validation_fuzzy_array", + Func: unit.Func(utils.Validate( + []byte(`{"items|array":[ + {"name|string":"*","age|int":30} + ]}`), + []byte(`{"items":[ + {"name":"eco1","age":"30"} + ]}`), + )), + Expect: unit.Returns(errors.New("type expectation failed. expected type: 'int', got type: 'string', path: '[items 0 age]'")), + }, + { + Name: "validation_fuzzy_array", + Func: unit.Func(utils.Validate( + []byte(`{"items|array":[ + {"name|string":"*","age|int":30} + ]}`), + []byte(`{"items":[ + {"name":"eco1","age":30} + ]}`), + )), + Expect: unit.Returns(nil), + }, + { + Name: "validation_fuzzy_array", + Func: unit.Func(utils.Validate( + []byte(`{"items|array":[ + {"name|string":"*","age|int":"*"} + ]}`), + []byte(`{"items":[ + {"name":"eco1","age":31}, + {"name":"eco2","age":32}, + {"name":"eco3","age":33} + ]}`), + )), + Expect: unit.Returns(errors.New("unexpected path. path: [items 1 name]")), + }, + { + Name: "validation_fuzzy_array", + Func: unit.Func(utils.Validate( + []byte(`{"items|array":[ + {"name|string":"*","age|int":"*","games|array":[ + "headball2", + "*" + ]} + ]}`), + []byte(`{"items":[ + {"name":"eco1","age":31,"games":"test"} + ]}`), + )), + Expect: unit.Returns(errors.New("type expectation failed. expected type: 'array', got type: 'string', path: '[items 0 games]'")), + }, + { + Name: "validation_fuzzy_array", + Func: unit.Func(utils.Validate( + []byte(`{"items|array":[ + {"name|string":"*","age|int":"*","games|array":[ + "headball2", + "*" + ]} + ]}`), + []byte(`{"items":[ + {"name":"eco1","age":31,"games":[]} + ]}`), + )), + Expect: unit.Returns(errors.New("error: array is empty error_code: 02. path: [items 0 games 0]")), + }, + { + Name: "validation_fuzzy_array", + Func: unit.Func(utils.Validate( + []byte(`{"items|array":[ + {"name|string":"*","age|int":"*","games|array":[ + "headball2", + "*" + ]} + ]}`), + []byte(`{"items":[ + {"name":"eco1","age":31,"games":[ + "headball2" + ]} + ]}`), + )), + Expect: unit.Returns(errors.New("error: index out of range error_code: 07. path: [items 0 games 1]")), + }, + { + Name: "validation_fuzzy_array", + Func: unit.Func(utils.Validate( + []byte(`{"items|array":[ + {"name|string":"*","age|int":"*","games|array":[ + "headball2", + "*" + ]} + ]}`), + []byte(`{"items":[ + {"name":"eco1","age":31,"games":[ + "headball2", + "eco" + ]} + ]}`), + )), + Expect: unit.Returns(nil), + }, + { + Name: "validation_fuzzy_array", + Func: unit.Func(utils.Validate( + []byte(`{"items|array":[ + {"name|string":"*","age|int":"*","games|array":[ + "headball2", + "*" + ]} + ]}`), + []byte(`{"items":[ + {"name":"eco1","age":31,"games":[ + "headball2", + "eco", + "eco" + ]} + ]}`), + )), + Expect: unit.Returns(errors.New("unexpected path. path: [items 0 games 2]")), + }, + } +) + +func TestValidation(t *testing.T) { + unit.Test(t, scenario) +} diff --git a/unit/case.go b/unit/case.go index 9f0c71f..90cf1ee 100644 --- a/unit/case.go +++ b/unit/case.go @@ -9,20 +9,35 @@ import ( type scenario []*Case type Case struct { - Name string - Func []interface{} - Expect []interface{} + Name string + Func []interface{} + Expect []interface{} + OnlyRunThis bool } func Func(i ...interface{}) []interface{} { return i } func Returns(i ...interface{}) []interface{} { return i } func Test(t *testing.T, scenario scenario) { + c, ok := hasOnlyRunMe(scenario) + if ok { + c.Test(t) + return + } for _, c := range scenario { c.Test(t) } } +func hasOnlyRunMe(scenario scenario) (*Case, bool) { + for _, c := range scenario { + if c.OnlyRunThis { + return c, true + } + } + return nil, false +} + func (c *Case) Test(t *testing.T) { t.Run(c.Name, func(t *testing.T) { if c.Func == nil { diff --git a/utils/check.go b/utils/check.go index ba5fafc..dbfeccb 100644 --- a/utils/check.go +++ b/utils/check.go @@ -15,23 +15,22 @@ func CheckEqualWithNilError(t *testing.T, field string, err error, got, expect i } func CheckNotEqual(t *testing.T, field string, got, expect interface{}) { - LogChecking(t, "Equal", field, got, expect) - if reflect.DeepEqual(got, expect) { + if check(t, field, got, expect) { FailNot(t, field, got, expect) } - LogDone(t, "Equal", field) } func CheckEqual(t *testing.T, field string, got, expect interface{}) { - LogChecking(t, "Equal", field, got, expect) - if !reflect.DeepEqual(got, expect) { + if !check(t, field, got, expect) { Fail(t, field, got, expect) } - LogDone(t, "Equal", field) +} + +func check(t *testing.T, field string, got, expect interface{}) bool { + return reflect.DeepEqual(got, expect) } func CheckExpectError(t *testing.T, field string, got, expect error) { - LogChecking(t, "Error expect", field, got, expect) gotErrMsg := "" if got != nil { gotErrMsg = got.Error() @@ -46,8 +45,6 @@ func CheckExpectError(t *testing.T, field string, got, expect error) { Fail(t, field, got, expect) return } - - LogDone(t, "Error expect", field) } func FailNot(t *testing.T, field string, got, expect interface{}) { @@ -57,17 +54,3 @@ func FailNot(t *testing.T, field string, got, expect interface{}) { func Fail(t *testing.T, field string, got, expect interface{}) { t.Fatalf("'%v' unexpected result. got: '%v'(%T), expected: '%v'(%T)", field, got, got, expect, expect) } - -func LogChecking(t *testing.T, functionName, field string, got, expect interface{}) { - if !TestLogEnable { - return - } - t.Logf("\t> checking func: %v field: %v. got: %v, expected: %v\n", functionName, field, got, expect) -} - -func LogDone(t *testing.T, functionName, field string) { - if !TestLogEnable { - return - } - t.Logf("\t> done func: %v field: %v.\n", functionName, field) -} diff --git a/utils/validation.go b/utils/validation.go new file mode 100644 index 0000000..137f062 --- /dev/null +++ b/utils/validation.go @@ -0,0 +1,284 @@ +package utils + +import ( + "fmt" + "strconv" + "strings" + + "github.com/ecoshub/jin" +) + +// const ( +// wildcardSymbol string = "*" +// pathSeparator string = ":" +// ) + +var ( + ErrStringTypeExpectation string = "type expectation failed. expected type: '%s', got type: '%s', path: '%s'" + ErrStringValueExpectation string = "value expectation failed. expected value: '%s', got value: '%s', path: '%s'" + ErrStringMissingType string = "field must define a type. filed: '%s'" + ErrStringRequiredField string = "field is required. field: '%s'" +) + +type expect struct { + _field string + _value string + _type string + _required bool + _isWildcard bool +} + +func Validate(expect, got []byte) error { + if string(expect) == string(got) { + return nil + } + if len(expect) == 0 { + return nil + } + ok, err := jin.IsEmpty(expect) + if err != nil { + return err + } + if ok { + return nil + } + pathExpect := []string{} + pathReal := []string{} + pathsReal := make([][]string, 0, 8) + pathsExpected := make([][]string, 0, 8) + err = tree(expect, pathExpect, pathReal, func(pathExpect []string, pathReal []string) (bool, error) { + if len(pathReal) == 0 { + return true, nil + } + newPathReal := make([]string, len(pathReal)) + newPathExpected := make([]string, len(pathExpect)) + for i := range newPathReal { + newPathReal[i] = pathReal[i] + newPathExpected[i] = pathExpect[i] + } + pathsExpected = append(pathsExpected, newPathExpected) + pathsReal = append(pathsReal, newPathReal) + return true, nil + }) + if err != nil { + return err + } + err = compare(expect, got, pathsExpected, pathsReal) + if err != nil { + return err + } + jin.Walk(got, func(_ string, _ []byte, path []string) (bool, error) { + pathString := pathToPathString(path) + exists := false + for _, realPath := range pathsReal { + realPathString := pathToPathString(realPath) + if pathString == realPathString { + exists = true + break + } + } + if !exists { + err = fmt.Errorf("unexpected path. path: %v", path) + return false, err + } + return true, nil + }) + if err != nil { + return err + } + return nil +} + +func pathToPathString(p []string) string { + return strings.Join(p, ":") +} + +func compare(expect, got []byte, pathsExpected, pathsReal [][]string) error { + for i := range pathsExpected { + realPath := pathsReal[i] + expectedPath := pathsExpected[i] + realKey := getLastPath(realPath) + expectedKey := getLastPath(expectedPath) + + expectedValue, err := jin.GetString(expect, expectedPath...) + if err != nil { + return fmt.Errorf("fatal parsing error. %v", err) + } + + e, err := resolve(expectedKey, expectedValue, expectedPath) + if err != nil { + return err + } + + realValue, err := jin.GetString(got, realPath...) + exists := err == nil + if !e._required { + if !exists { + continue + } + } + + _type, err := jin.GetType(expect, expectedPath...) + if err != nil { + return fmt.Errorf("fatal parsing error. %v", err) + } + + if err != nil { + if jin.ErrEqual(err, jin.ErrCodeKeyNotFound) { + if e._required { + return fmt.Errorf(ErrStringRequiredField, e._field) + } + } else { + return fmt.Errorf("%s. path: %v", err, realPath) + } + } + + realType, err := jin.GetType(got, realPath...) + if err != nil { + if jin.ErrEqual(err, jin.ErrCodeKeyNotFound) { + if e._required { + return fmt.Errorf(ErrStringRequiredField, e._field) + } + } else { + return fmt.Errorf("%s. path: %v", err, realPath) + } + } + + if realType == jin.TypeNumber { + realType = processNumberType(realValue) + } + + if _type == jin.TypeNumber { + _type = processNumberType(realValue) + } + + if realKey != e._field { + return fmt.Errorf("fatal parsing error. keys are not same. key: '%v', key: '%v'", realKey, expectedKey) + } + + if e._type != "" { + if realType != e._type { + return fmt.Errorf(ErrStringTypeExpectation, e._type, realType, realPath) + } + } else { + if _type != realType { + return fmt.Errorf(ErrStringTypeExpectation, _type, realType, realPath) + } + } + + if !(realType == "array" || realType == "object") { + if !e._isWildcard { + if realValue != e._value { + return fmt.Errorf(ErrStringValueExpectation, e._value, realValue, realPath) + } + } + } + } + return nil +} + +func processNumberType(value string) string { + _, err := strconv.ParseInt(value, 10, 64) + if err == nil { + return "int" + } + _, err = strconv.ParseFloat(value, 64) + if err == nil { + return "float" + } + return value +} + +func resolve(key, value string, path []string) (*expect, error) { + e := &expect{} + // strip '*' prefix if exists, and set the required field + if strings.HasPrefix(key, "*") { + key = key[1:] + e._required = false + } else { + e._required = true + } + tokens := strings.Split(key, "|") + switch len(tokens) { + case 0: + return nil, fmt.Errorf(ErrStringMissingType, path) + case 1: + e._field = tokens[0] + default: + e._field = tokens[0] + e._type = tokens[1] + } + if value == "*" { + e._isWildcard = true + } + e._value = value + return e, nil +} + +func tree(body []byte, pathExpect []string, pathReal []string, f func(pathExpect []string, pathReal []string) (bool, error)) error { + if len(body) == 0 { + return nil + } + + t, err := jin.GetType(body) + if err != nil { + return err + } + + keepRunning, err := f(pathExpect, pathReal) + if err != nil { + return err + } + + if !keepRunning { + return nil + } + + switch t { + case jin.TypeObject: + err = jin.IterateKeyValue(body, func(kb, vb []byte) (bool, error) { + key := string(kb) + tok := strings.Split(key, "|") + pathExpect = append(pathExpect, key) + key = strings.TrimPrefix(tok[0], "*") + pathReal = append(pathReal, key) + err = tree(vb, pathExpect, pathReal, f) + if err != nil { + return false, err + } + pathExpect = pathExpect[:len(pathExpect)-1] + pathReal = pathReal[:len(pathReal)-1] + return true, nil + }) + if err != nil { + return err + } + case jin.TypeArray: + index := 0 + err = jin.IterateArray(body, func(val []byte) (bool, error) { + indexString := strconv.Itoa(index) + pathExpect = append(pathExpect, indexString) + pathReal = append(pathReal, indexString) + err = tree(val, pathExpect, pathReal, f) + if err != nil { + fmt.Println("err", err) + return false, err + } + pathExpect = pathExpect[:len(pathExpect)-1] + pathReal = pathReal[:len(pathReal)-1] + index++ + return true, nil + }) + if err != nil { + return err + } + } + return nil +} + +func getLastPath(path []string) string { + if len(path) == 1 { + return path[0] + } + return path[len(path)-1] +}