diff --git a/util/json/json.go b/util/json/json.go new file mode 100755 index 00000000..5abe9906 --- /dev/null +++ b/util/json/json.go @@ -0,0 +1,247 @@ +// Copyright © 2016 Abcum Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package json + +import ( + "encoding/json" + "fmt" + "reflect" + "strings" +) + +type Operation struct { + Op string `json:"op"` + Path string `json:"path"` + Value interface{} `json:"value,omitempty"` +} + +type ByPath []Operation + +func (a ByPath) Len() int { return len(a) } +func (a ByPath) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a ByPath) Less(i, j int) bool { return a[i].Path < a[j].Path } + +func NewPatch(op, path string, value interface{}) Operation { + return Operation{Op: op, Path: path, Value: value} +} + +// CreatePatch creates a patch as specified in http://jsonpatch.com/ +// +// 'a' is original, 'b' is the modified document. Both are to be given as json encoded content. +// The function will return an array of Operations +// +// An error will be returned if any of the two documents are invalid. +func Diff(a, b interface{}) ([]Operation, error) { + + va, oka := a.([]byte) + vb, okb := b.([]byte) + + if oka && okb { + ia := map[string]interface{}{} + ib := map[string]interface{}{} + err := json.Unmarshal(va, &ia) + if err != nil { + return nil, err + } + err = json.Unmarshal(vb, &ib) + if err != nil { + return nil, err + } + return diff(ia, ib, "", []Operation{}) + } + + ma, oka := a.(map[string]interface{}) + mb, okb := b.(map[string]interface{}) + + if oka && okb { + return diff(ma, mb, "", []Operation{}) + } + + return nil, fmt.Errorf("Invalid input format") + +} + +// Returns true if the values matches (must be json types) +// The types of the values must match, otherwise it will always return false +// If two map[string]interface{} are given, all elements must match. +func matchesValue(av, bv interface{}) bool { + if reflect.TypeOf(av) != reflect.TypeOf(bv) { + return false + } + switch at := av.(type) { + case string: + bt := bv.(string) + if bt == at { + return true + } + case float64: + bt := bv.(float64) + if bt == at { + return true + } + case bool: + bt := bv.(bool) + if bt == at { + return true + } + case map[string]interface{}: + bt := bv.(map[string]interface{}) + for key := range at { + if !matchesValue(at[key], bt[key]) { + return false + } + } + for key := range bt { + if !matchesValue(at[key], bt[key]) { + return false + } + } + return true + case []interface{}: + bt := bv.([]interface{}) + if len(bt) != len(at) { + return false + } + for key := range at { + if !matchesValue(at[key], bt[key]) { + return false + } + } + for key := range bt { + if !matchesValue(at[key], bt[key]) { + return false + } + } + return true + } + return false +} + +func makePath(path string, newPart interface{}) string { + if path == "" { + return fmt.Sprintf("/%v", newPart) + } else { + if strings.HasSuffix(path, "/") { + path = path + fmt.Sprintf("%v", newPart) + } else { + path = path + fmt.Sprintf("/%v", newPart) + } + } + return path +} + +// diff returns the (recursive) difference between a and b as an array of Operations. +func diff(a, b map[string]interface{}, path string, patch []Operation) ([]Operation, error) { + for key, bv := range b { + p := makePath(path, key) + av, ok := a[key] + // value was added + if !ok { + patch = append(patch, NewPatch("add", p, bv)) + continue + } + // If types have changed, replace completely + if reflect.TypeOf(av) != reflect.TypeOf(bv) { + patch = append(patch, NewPatch("replace", p, bv)) + continue + } + // Types are the same, compare values + var err error + patch, err = handleValues(av, bv, p, patch) + if err != nil { + return nil, err + } + } + // Now add all deleted values as nil + for key := range a { + _, found := b[key] + if !found { + p := makePath(path, key) + + patch = append(patch, NewPatch("remove", p, nil)) + } + } + return patch, nil +} + +func handleValues(av, bv interface{}, p string, patch []Operation) ([]Operation, error) { + var err error + switch at := av.(type) { + case map[string]interface{}: + bt := bv.(map[string]interface{}) + patch, err = diff(at, bt, p, patch) + if err != nil { + return nil, err + } + case string, float64, bool: + if !matchesValue(av, bv) { + patch = append(patch, NewPatch("replace", p, bv)) + } + case []interface{}: + bt := bv.([]interface{}) + if len(at) != len(bt) { + // arrays are not the same + patch = append(patch, compareArray(at, bt, p)...) + + } else { + for i, _ := range bt { + patch, err = handleValues(at[i], bt[i], makePath(p, i), patch) + if err != nil { + return nil, err + } + } + } + case nil: + switch bv.(type) { + case nil: + // Both nil, fine. + default: + patch = append(patch, NewPatch("add", p, bv)) + } + default: + panic(fmt.Sprintf("Unknown type:%T ", av)) + } + return patch, nil +} + +func compareArray(av, bv []interface{}, p string) []Operation { + retval := []Operation{} + // var err error + for i, v := range av { + found := false + for _, v2 := range bv { + if reflect.DeepEqual(v, v2) { + found = true + } + } + if !found { + retval = append(retval, NewPatch("remove", makePath(p, i), nil)) + } + } + + for i, v := range bv { + found := false + for _, v2 := range av { + if reflect.DeepEqual(v, v2) { + found = true + } + } + if !found { + retval = append(retval, NewPatch("add", makePath(p, i), v)) + } + } + + return retval +} diff --git a/util/json/merge.go b/util/json/merge.go new file mode 100755 index 00000000..e4a5c2fd --- /dev/null +++ b/util/json/merge.go @@ -0,0 +1,321 @@ +// Copyright © 2016 Abcum Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package json + +/*import ( + "encoding/json" + "fmt" + "reflect" + "strings" +) + +func merge(cur, patch *lazyNode) *lazyNode { + curDoc, err := cur.intoDoc() + + if err != nil { + pruneNulls(patch) + return patch + } + + patchDoc, err := patch.intoDoc() + + if err != nil { + return patch + } + + mergeDocs(curDoc, patchDoc) + + return cur +} + +func mergeDocs(doc, patch *partialDoc) { + for k, v := range *patch { + k := decodePatchKey(k) + if v == nil { + delete(*doc, k) + } else { + cur, ok := (*doc)[k] + + if !ok || cur == nil { + pruneNulls(v) + (*doc)[k] = v + } else { + (*doc)[k] = merge(cur, v) + } + } + } +} + +func pruneNulls(n *lazyNode) { + sub, err := n.intoDoc() + + if err == nil { + pruneDocNulls(sub) + } else { + ary, err := n.intoAry() + + if err == nil { + pruneAryNulls(ary) + } + } +} + +func pruneDocNulls(doc *partialDoc) *partialDoc { + for k, v := range *doc { + if v == nil { + delete(*doc, k) + } else { + pruneNulls(v) + } + } + + return doc +} + +func pruneAryNulls(ary *partialArray) *partialArray { + newAry := []*lazyNode{} + + for _, v := range *ary { + if v != nil { + pruneNulls(v) + newAry = append(newAry, v) + } + } + + *ary = newAry + + return ary +} + +var errBadJSONDoc = fmt.Errorf("Invalid JSON Document") +var errBadJSONPatch = fmt.Errorf("Invalid JSON Patch") + +// MergePatch merges the patchData into the docData. +func MergePatch(docData, patchData []byte) ([]byte, error) { + doc := &partialDoc{} + + docErr := json.Unmarshal(docData, doc) + + patch := &partialDoc{} + + patchErr := json.Unmarshal(patchData, patch) + + if _, ok := docErr.(*json.SyntaxError); ok { + return nil, errBadJSONDoc + } + + if _, ok := patchErr.(*json.SyntaxError); ok { + return nil, errBadJSONPatch + } + + if docErr == nil && *doc == nil { + return nil, errBadJSONDoc + } + + if patchErr == nil && *patch == nil { + return nil, errBadJSONPatch + } + + if docErr != nil || patchErr != nil { + // Not an error, just not a doc, so we turn straight into the patch + if patchErr == nil { + doc = pruneDocNulls(patch) + } else { + patchAry := &partialArray{} + patchErr = json.Unmarshal(patchData, patchAry) + + if patchErr != nil { + return nil, errBadJSONPatch + } + + pruneAryNulls(patchAry) + + out, patchErr := json.Marshal(patchAry) + + if patchErr != nil { + return nil, errBadJSONPatch + } + + return out, nil + } + } else { + mergeDocs(doc, patch) + } + + return json.Marshal(doc) +} + +// CreateMergePatch creates a merge patch as specified in http://tools.ietf.org/html/draft-ietf-appsawg-json-merge-patch-07 +// +// 'a' is original, 'b' is the modified document. Both are to be given as json encoded content. +// The function will return a mergeable json document with differences from a to b. +// +// An error will be returned if any of the two documents are invalid. +func CreateMergePatch(a, b []byte) ([]byte, error) { + aI := map[string]interface{}{} + bI := map[string]interface{}{} + err := json.Unmarshal(a, &aI) + if err != nil { + return nil, errBadJSONDoc + } + err = json.Unmarshal(b, &bI) + if err != nil { + return nil, errBadJSONDoc + } + dest, err := getDiff(aI, bI) + if err != nil { + return nil, err + } + return json.Marshal(dest) +} + +// Returns true if the array matches (must be json types). +// As is idiomatic for go, an empty array is not the same as a nil array. +func matchesArray(a, b []interface{}) bool { + if len(a) != len(b) { + return false + } + if (a == nil && b != nil) || (a != nil && b == nil) { + return false + } + for i := range a { + if !matchesValue(a[i], b[i]) { + return false + } + } + return true +} + +// Returns true if the values matches (must be json types) +// The types of the values must match, otherwise it will always return false +// If two map[string]interface{} are given, all elements must match. +func matchesValue(av, bv interface{}) bool { + if reflect.TypeOf(av) != reflect.TypeOf(bv) { + return false + } + switch at := av.(type) { + case string: + bt := bv.(string) + if bt == at { + return true + } + case float64: + bt := bv.(float64) + if bt == at { + return true + } + case bool: + bt := bv.(bool) + if bt == at { + return true + } + case map[string]interface{}: + bt := bv.(map[string]interface{}) + for key := range at { + if !matchesValue(at[key], bt[key]) { + return false + } + } + for key := range bt { + if !matchesValue(at[key], bt[key]) { + return false + } + } + return true + case []interface{}: + bt := bv.([]interface{}) + return matchesArray(at, bt) + } + return false +} + +// getDiff returns the (recursive) difference between a and b as a map[string]interface{}. +func getDiff(a, b map[string]interface{}) (map[string]interface{}, error) { + into := map[string]interface{}{} + for key, bv := range b { + escapedKey := encodePatchKey(key) + av, ok := a[key] + // value was added + if !ok { + into[escapedKey] = bv + continue + } + // If types have changed, replace completely + if reflect.TypeOf(av) != reflect.TypeOf(bv) { + into[escapedKey] = bv + continue + } + // Types are the same, compare values + switch at := av.(type) { + case map[string]interface{}: + bt := bv.(map[string]interface{}) + dst := make(map[string]interface{}, len(bt)) + dst, err := getDiff(at, bt) + if err != nil { + return nil, err + } + if len(dst) > 0 { + into[escapedKey] = dst + } + case string, float64, bool: + if !matchesValue(av, bv) { + into[escapedKey] = bv + } + case []interface{}: + bt := bv.([]interface{}) + if !matchesArray(at, bt) { + into[escapedKey] = bv + } + case nil: + switch bv.(type) { + case nil: + // Both nil, fine. + default: + into[escapedKey] = bv + } + default: + panic(fmt.Sprintf("Unknown type:%T in key %s", av, key)) + } + } + // Now add all deleted values as nil + for key := range a { + _, found := b[key] + if !found { + into[key] = nil + } + } + return into, nil +} + +// From http://tools.ietf.org/html/rfc6901#section-4 : +// +// Evaluation of each reference token begins by decoding any escaped +// character sequence. This is performed by first transforming any +// occurrence of the sequence '~1' to '/', and then transforming any +// occurrence of the sequence '~0' to '~'. + +var ( + rfc6901Encoder = strings.NewReplacer("~", "~0", "/", "~1") + rfc6901Decoder = strings.NewReplacer("~1", "/", "~0", "~") +) + +func decodePatchKey(k string) string { + return rfc6901Decoder.Replace(k) +} + +func encodePatchKey(k string) string { + return rfc6901Encoder.Replace(k) +} +*/ diff --git a/util/json/merge_test.go b/util/json/merge_test.go new file mode 100755 index 00000000..5ea2b5cf --- /dev/null +++ b/util/json/merge_test.go @@ -0,0 +1,407 @@ +// Copyright © 2016 Abcum Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package json + +// import ( +// "strings" +// "testing" +// ) + +// func mergePatch(doc, patch string) string { +// out, err := MergePatch([]byte(doc), []byte(patch)) + +// if err != nil { +// panic(err) +// } + +// return string(out) +// } + +// func TestMergePatchReplaceKey(t *testing.T) { +// doc := `{ "title": "hello" }` +// pat := `{ "title": "goodbye" }` + +// res := mergePatch(doc, pat) + +// if !compareJSON(pat, res) { +// t.Fatalf("Key was not replaced") +// } +// } + +// func TestMergePatchIgnoresOtherValues(t *testing.T) { +// doc := `{ "title": "hello", "age": 18 }` +// pat := `{ "title": "goodbye" }` + +// res := mergePatch(doc, pat) + +// exp := `{ "title": "goodbye", "age": 18 }` + +// if !compareJSON(exp, res) { +// t.Fatalf("Key was not replaced") +// } +// } + +// func TestMergePatchNilDoc(t *testing.T) { +// doc := `{ "title": null }` +// pat := `{ "title": {"foo": "bar"} }` + +// res := mergePatch(doc, pat) + +// exp := `{ "title": {"foo": "bar"} }` + +// if !compareJSON(exp, res) { +// t.Fatalf("Key was not replaced") +// } +// } + +// func TestMergePatchRecursesIntoObjects(t *testing.T) { +// doc := `{ "person": { "title": "hello", "age": 18 } }` +// pat := `{ "person": { "title": "goodbye" } }` + +// res := mergePatch(doc, pat) + +// exp := `{ "person": { "title": "goodbye", "age": 18 } }` + +// if !compareJSON(exp, res) { +// t.Fatalf("Key was not replaced") +// } +// } + +// type nonObjectCases struct { +// doc, pat, res string +// } + +// func TestMergePatchReplacesNonObjectsWholesale(t *testing.T) { +// a1 := `[1]` +// a2 := `[2]` +// o1 := `{ "a": 1 }` +// o2 := `{ "a": 2 }` +// o3 := `{ "a": 1, "b": 1 }` +// o4 := `{ "a": 2, "b": 1 }` + +// cases := []nonObjectCases{ +// {a1, a2, a2}, +// {o1, a2, a2}, +// {a1, o1, o1}, +// {o3, o2, o4}, +// } + +// for _, c := range cases { +// act := mergePatch(c.doc, c.pat) + +// if !compareJSON(c.res, act) { +// t.Errorf("whole object replacement failed") +// } +// } +// } + +// func TestMergePatchReturnsErrorOnBadJSON(t *testing.T) { +// _, err := MergePatch([]byte(`[[[[`), []byte(`1`)) + +// if err == nil { +// t.Errorf("Did not return an error for bad json: %s", err) +// } + +// _, err = MergePatch([]byte(`1`), []byte(`[[[[`)) + +// if err == nil { +// t.Errorf("Did not return an error for bad json: %s", err) +// } +// } + +// func TestMergePatchReturnsEmptyArrayOnEmptyArray(t *testing.T) { +// doc := `{ "array": ["one", "two"] }` +// pat := `{ "array": [] }` + +// exp := `{ "array": [] }` + +// res, err := MergePatch([]byte(doc), []byte(pat)) + +// if err != nil { +// t.Errorf("Unexpected error: %s, %s", err, string(res)) +// } + +// if !compareJSON(exp, string(res)) { +// t.Fatalf("Emtpy array did not return not return as empty array") +// } +// } + +// var rfcTests = []struct { +// target string +// patch string +// expected string +// }{ +// // test cases from https://tools.ietf.org/html/rfc7386#appendix-A +// {target: `{"a":"b"}`, patch: `{"a":"c"}`, expected: `{"a":"c"}`}, +// {target: `{"a":"b"}`, patch: `{"b":"c"}`, expected: `{"a":"b","b":"c"}`}, +// {target: `{"a":"b"}`, patch: `{"a":null}`, expected: `{}`}, +// {target: `{"a":"b","b":"c"}`, patch: `{"a":null}`, expected: `{"b":"c"}`}, +// {target: `{"a":["b"]}`, patch: `{"a":"c"}`, expected: `{"a":"c"}`}, +// {target: `{"a":"c"}`, patch: `{"a":["b"]}`, expected: `{"a":["b"]}`}, +// {target: `{"a":{"b": "c"}}`, patch: `{"a": {"b": "d","c": null}}`, expected: `{"a":{"b":"d"}}`}, +// {target: `{"a":[{"b":"c"}]}`, patch: `{"a":[1]}`, expected: `{"a":[1]}`}, +// {target: `["a","b"]`, patch: `["c","d"]`, expected: `["c","d"]`}, +// {target: `{"a":"b"}`, patch: `["c"]`, expected: `["c"]`}, +// // {target: `{"a":"foo"}`, patch: `null`, expected: `null`}, +// // {target: `{"a":"foo"}`, patch: `"bar"`, expected: `"bar"`}, +// {target: `{"e":null}`, patch: `{"a":1}`, expected: `{"a":1,"e":null}`}, +// {target: `[1,2]`, patch: `{"a":"b","c":null}`, expected: `{"a":"b"}`}, +// {target: `{}`, patch: `{"a":{"bb":{"ccc":null}}}`, expected: `{"a":{"bb":{}}}`}, +// } + +// func TestMergePatchRFCCases(t *testing.T) { +// for i, c := range rfcTests { +// out := mergePatch(c.target, c.patch) + +// if !compareJSON(out, c.expected) { +// t.Errorf("case[%d], patch '%s' did not apply properly to '%s'. expected:\n'%s'\ngot:\n'%s'", i, c.patch, c.target, c.expected, out) +// } +// } +// } + +// var rfcFailTests = ` +// {"a":"foo"} | null +// {"a":"foo"} | "bar" +// ` + +// func TestMergePatchFailRFCCases(t *testing.T) { +// tests := strings.Split(rfcFailTests, "\n") + +// for _, c := range tests { +// if strings.TrimSpace(c) == "" { +// continue +// } + +// parts := strings.SplitN(c, "|", 2) + +// doc := strings.TrimSpace(parts[0]) +// pat := strings.TrimSpace(parts[1]) + +// out, err := MergePatch([]byte(doc), []byte(pat)) + +// if err != errBadJSONPatch { +// t.Errorf("error not returned properly: %s, %s", err, string(out)) +// } +// } + +// } + +// func TestMergeReplaceKey(t *testing.T) { +// doc := `{ "title": "hello", "nested": {"one": 1, "two": 2} }` +// pat := `{ "title": "goodbye", "nested": {"one": 2, "two": 2} }` + +// exp := `{ "title": "goodbye", "nested": {"one": 2} }` + +// res, err := CreateMergePatch([]byte(doc), []byte(pat)) + +// if err != nil { +// t.Errorf("Unexpected error: %s, %s", err, string(res)) +// } + +// if !compareJSON(exp, string(res)) { +// t.Fatalf("Key was not replaced") +// } +// } + +// func TestMergeGetArray(t *testing.T) { +// doc := `{ "title": "hello", "array": ["one", "two"], "notmatch": [1, 2, 3] }` +// pat := `{ "title": "hello", "array": ["one", "two", "three"], "notmatch": [1, 2, 3] }` + +// exp := `{ "array": ["one", "two", "three"] }` + +// res, err := CreateMergePatch([]byte(doc), []byte(pat)) + +// if err != nil { +// t.Errorf("Unexpected error: %s, %s", err, string(res)) +// } + +// if !compareJSON(exp, string(res)) { +// t.Fatalf("Array was not added") +// } +// } + +// func TestMergeGetObjArray(t *testing.T) { +// doc := `{ "title": "hello", "array": [{"banana": true}, {"evil": false}], "notmatch": [{"one":1}, {"two":2}, {"three":3}] }` +// pat := `{ "title": "hello", "array": [{"banana": false}, {"evil": true}], "notmatch": [{"one":1}, {"two":2}, {"three":3}] }` + +// exp := `{ "array": [{"banana": false}, {"evil": true}] }` + +// res, err := CreateMergePatch([]byte(doc), []byte(pat)) + +// if err != nil { +// t.Errorf("Unexpected error: %s, %s", err, string(res)) +// } + +// if !compareJSON(exp, string(res)) { +// t.Fatalf("Object array was not added") +// } +// } + +// func TestMergeDeleteKey(t *testing.T) { +// doc := `{ "title": "hello", "nested": {"one": 1, "two": 2} }` +// pat := `{ "title": "hello", "nested": {"one": 1} }` + +// exp := `{"nested":{"two":null}}` + +// res, err := CreateMergePatch([]byte(doc), []byte(pat)) + +// if err != nil { +// t.Errorf("Unexpected error: %s, %s", err, string(res)) +// } + +// // We cannot use "compareJSON", since Equals does not report a difference if the value is null +// if exp != string(res) { +// t.Fatalf("Key was not removed") +// } +// } + +// func TestMergeEmptyArray(t *testing.T) { +// doc := `{ "array": null }` +// pat := `{ "array": [] }` + +// exp := `{"array":[]}` + +// res, err := CreateMergePatch([]byte(doc), []byte(pat)) + +// if err != nil { +// t.Errorf("Unexpected error: %s, %s", err, string(res)) +// } + +// // We cannot use "compareJSON", since Equals does not report a difference if the value is null +// if exp != string(res) { +// t.Fatalf("Key was not removed") +// } +// } + +// func TestMergeObjArray(t *testing.T) { +// doc := `{ "array": [ {"a": {"b": 2}}, {"a": {"b": 3}} ]}` +// exp := `{}` + +// res, err := CreateMergePatch([]byte(doc), []byte(doc)) + +// if err != nil { +// t.Errorf("Unexpected error: %s, %s", err, string(res)) +// } + +// // We cannot use "compareJSON", since Equals does not report a difference if the value is null +// if exp != string(res) { +// t.Fatalf("Array was not empty, was " + string(res)) +// } +// } + +// func TestMergeComplexMatch(t *testing.T) { +// doc := `{"hello": "world","t": true ,"f": false, "n": null,"i": 123,"pi": 3.1416,"a": [1, 2, 3, 4], "nested": {"hello": "world","t": true ,"f": false, "n": null,"i": 123,"pi": 3.1416,"a": [1, 2, 3, 4]} }` +// empty := `{}` +// res, err := CreateMergePatch([]byte(doc), []byte(doc)) + +// if err != nil { +// t.Errorf("Unexpected error: %s, %s", err, string(res)) +// } + +// // We cannot use "compareJSON", since Equals does not report a difference if the value is null +// if empty != string(res) { +// t.Fatalf("Did not get empty result, was:%s", string(res)) +// } +// } + +// func TestMergeComplexAddAll(t *testing.T) { +// doc := `{"hello": "world","t": true ,"f": false, "n": null,"i": 123,"pi": 3.1416,"a": [1, 2, 3, 4], "nested": {"hello": "world","t": true ,"f": false, "n": null,"i": 123,"pi": 3.1416,"a": [1, 2, 3, 4]} }` +// empty := `{}` +// res, err := CreateMergePatch([]byte(empty), []byte(doc)) + +// if err != nil { +// t.Errorf("Unexpected error: %s, %s", err, string(res)) +// } + +// if !compareJSON(doc, string(res)) { +// t.Fatalf("Did not get everything as, it was:\n%s", string(res)) +// } +// } + +// func TestMergeComplexRemoveAll(t *testing.T) { +// doc := `{"hello": "world","t": true ,"f": false, "n": null,"i": 123,"pi": 3.1416,"a": [1, 2, 3, 4], "nested": {"hello": "world","t": true ,"f": false, "n": null,"i": 123,"pi": 3.1416,"a": [1, 2, 3, 4]} }` +// exp := `{"a":null,"f":null,"hello":null,"i":null,"n":null,"nested":null,"pi":null,"t":null}` +// empty := `{}` +// res, err := CreateMergePatch([]byte(doc), []byte(empty)) + +// if err != nil { +// t.Errorf("Unexpected error: %s, %s", err, string(res)) +// } + +// if exp != string(res) { +// t.Fatalf("Did not get result, was:%s", string(res)) +// } + +// // FIXME: Crashes if using compareJSON like this: +// /* +// if !compareJSON(doc, string(res)) { +// t.Fatalf("Did not get everything as, it was:\n%s", string(res)) +// } +// */ +// } + +// func TestMergeObjectWithInnerArray(t *testing.T) { +// stateString := `{ +// "OuterArray": [ +// { +// "InnerArray": [ +// { +// "StringAttr": "abc123" +// } +// ], +// "StringAttr": "def456" +// } +// ] +// }` + +// patch, err := CreateMergePatch([]byte(stateString), []byte(stateString)) +// if err != nil { +// t.Fatal(err) +// } + +// if string(patch) != "{}" { +// t.Fatalf("Patch should have been {} but was: %v", string(patch)) +// } +// } + +// func TestMergeReplaceKeyRequiringEscape(t *testing.T) { +// doc := `{ "title": "hello", "nested": {"title/escaped": 1, "two": 2} }` +// pat := `{ "title": "goodbye", "nested": {"title/escaped": 2, "two": 2} }` + +// exp := `{ "title": "goodbye", "nested": {"title~1escaped": 2} }` + +// res, err := CreateMergePatch([]byte(doc), []byte(pat)) + +// if err != nil { +// t.Errorf("Unexpected error: %s, %s", err, string(res)) +// } + +// if !compareJSON(exp, string(res)) { +// t.Log(string(res)) +// t.Fatalf("Key was not replaced") +// } +// } + +// func TestMergePatchReplaceKeyRequiringEscaping(t *testing.T) { +// doc := `{ "obj": { "title/escaped": "hello" } }` +// pat := `{ "obj": { "title~1escaped": "goodbye" } }` +// exp := `{ "obj": { "title/escaped": "goodbye" } }` + +// res := mergePatch(doc, pat) + +// if !compareJSON(exp, res) { +// t.Fatalf("Key was not replaced") +// } +// } diff --git a/util/json/patch.go b/util/json/patch.go new file mode 100755 index 00000000..91c0419d --- /dev/null +++ b/util/json/patch.go @@ -0,0 +1,601 @@ +// Copyright © 2016 Abcum Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package json + +/*import ( + "bytes" + "encoding/json" + "fmt" + "strconv" + "strings" +) + +const ( + eRaw = iota + eDoc + eAry +) + +const LeftBrace byte = 91 // []byte("[") + +type lazyNode struct { + raw *json.RawMessage + doc partialDoc + ary partialArray + which int +} + +type operation map[string]*json.RawMessage + +// Patch is an ordered collection of operations. +type Patch []operation + +type partialDoc map[string]*lazyNode +type partialArray []*lazyNode + +type container interface { + get(key string) (*lazyNode, error) + set(key string, val *lazyNode) error + add(key string, val *lazyNode) error + remove(key string) error +} + +func newLazyNode(raw *json.RawMessage) *lazyNode { + return &lazyNode{raw: raw, doc: nil, ary: nil, which: eRaw} +} + +func (n *lazyNode) MarshalJSON() ([]byte, error) { + switch n.which { + case eRaw: + return json.Marshal(n.raw) + case eDoc: + return json.Marshal(n.doc) + case eAry: + return json.Marshal(n.ary) + default: + return nil, fmt.Errorf("Unknown type") + } +} + +func (n *lazyNode) UnmarshalJSON(data []byte) error { + dest := make(json.RawMessage, len(data)) + copy(dest, data) + n.raw = &dest + n.which = eRaw + return nil +} + +func (n *lazyNode) intoDoc() (*partialDoc, error) { + if n.which == eDoc { + return &n.doc, nil + } + + err := json.Unmarshal(*n.raw, &n.doc) + + if err != nil { + return nil, err + } + + n.which = eDoc + return &n.doc, nil +} + +func (n *lazyNode) intoAry() (*partialArray, error) { + if n.which == eAry { + return &n.ary, nil + } + + err := json.Unmarshal(*n.raw, &n.ary) + + if err != nil { + return nil, err + } + + n.which = eAry + return &n.ary, nil +} + +func (n *lazyNode) compact() []byte { + buf := &bytes.Buffer{} + + err := json.Compact(buf, *n.raw) + + if err != nil { + return *n.raw + } + + return buf.Bytes() +} + +func (n *lazyNode) tryDoc() bool { + err := json.Unmarshal(*n.raw, &n.doc) + + if err != nil { + return false + } + + n.which = eDoc + return true +} + +func (n *lazyNode) tryAry() bool { + err := json.Unmarshal(*n.raw, &n.ary) + + if err != nil { + return false + } + + n.which = eAry + return true +} + +func (n *lazyNode) equal(o *lazyNode) bool { + if n.which == eRaw { + if !n.tryDoc() && !n.tryAry() { + if o.which != eRaw { + return false + } + + return bytes.Equal(n.compact(), o.compact()) + } + } + + if n.which == eDoc { + if o.which == eRaw { + if !o.tryDoc() { + return false + } + } + + if o.which != eDoc { + return false + } + + for k, v := range n.doc { + ov, ok := o.doc[k] + + if !ok { + return false + } + + if v == nil && ov == nil { + continue + } + + if !v.equal(ov) { + return false + } + } + + return true + } + + if o.which != eAry && !o.tryAry() { + return false + } + + if len(n.ary) != len(o.ary) { + return false + } + + for idx, val := range n.ary { + if !val.equal(o.ary[idx]) { + return false + } + } + + return true +} + +func (o operation) kind() string { + if obj, ok := o["op"]; ok { + var op string + + err := json.Unmarshal(*obj, &op) + + if err != nil { + return "unknown" + } + + return op + } + + return "unknown" +} + +func (o operation) path() string { + if obj, ok := o["path"]; ok { + var op string + + err := json.Unmarshal(*obj, &op) + + if err != nil { + return "unknown" + } + + return op + } + + return "unknown" +} + +func (o operation) from() string { + if obj, ok := o["from"]; ok { + var op string + + err := json.Unmarshal(*obj, &op) + + if err != nil { + return "unknown" + } + + return op + } + + return "unknown" +} + +func (o operation) value() *lazyNode { + if obj, ok := o["value"]; ok { + return newLazyNode(obj) + } + + return nil +} + +func isArray(buf []byte) bool { +Loop: + for _, c := range buf { + switch c { + case ' ': + case '\n': + case '\t': + continue + case '[': + return true + default: + break Loop + } + } + + return false +} + +func findObject(pd *container, path string) (container, string) { + doc := *pd + + split := strings.Split(path, "/") + + parts := split[1 : len(split)-1] + + key := split[len(split)-1] + + var err error + + for _, part := range parts { + + next, ok := doc.get(decodePatchKey(part)) + + if next == nil || ok != nil { + return nil, "" + } + + if isArray(*next.raw) { + doc, err = next.intoAry() + + if err != nil { + return nil, "" + } + } else { + doc, err = next.intoDoc() + + if err != nil { + return nil, "" + } + } + } + + return doc, decodePatchKey(key) +} + +func (d *partialDoc) set(key string, val *lazyNode) error { + (*d)[key] = val + return nil +} + +func (d *partialDoc) add(key string, val *lazyNode) error { + (*d)[key] = val + return nil +} + +func (d *partialDoc) get(key string) (*lazyNode, error) { + return (*d)[key], nil +} + +func (d *partialDoc) remove(key string) error { + _, ok := (*d)[key] + if !ok { + return fmt.Errorf("Unable to remove nonexistent key: %s", key) + } + + delete(*d, key) + return nil +} + +func (d *partialArray) set(key string, val *lazyNode) error { + if key == "-" { + *d = append(*d, val) + return nil + } + + idx, err := strconv.Atoi(key) + if err != nil { + return err + } + + sz := len(*d) + if idx+1 > sz { + sz = idx + 1 + } + + ary := make([]*lazyNode, sz) + + cur := *d + + copy(ary, cur) + + if idx >= len(ary) { + return fmt.Errorf("Unable to access invalid index: %d", idx) + } + + ary[idx] = val + + *d = ary + return nil +} + +func (d *partialArray) add(key string, val *lazyNode) error { + if key == "-" { + *d = append(*d, val) + return nil + } + + idx, err := strconv.Atoi(key) + if err != nil { + return err + } + + ary := make([]*lazyNode, len(*d)+1) + + cur := *d + + copy(ary[0:idx], cur[0:idx]) + ary[idx] = val + copy(ary[idx+1:], cur[idx:]) + + *d = ary + return nil +} + +func (d *partialArray) get(key string) (*lazyNode, error) { + idx, err := strconv.Atoi(key) + + if err != nil { + return nil, err + } + + if idx >= len(*d) { + return nil, fmt.Errorf("Unable to access invalid index: %d", idx) + } + + return (*d)[idx], nil +} + +func (d *partialArray) remove(key string) error { + idx, err := strconv.Atoi(key) + if err != nil { + return err + } + + cur := *d + + if idx >= len(cur) { + return fmt.Errorf("Unable to remove invalid index: %d", idx) + } + + ary := make([]*lazyNode, len(cur)-1) + + copy(ary[0:idx], cur[0:idx]) + copy(ary[idx:], cur[idx+1:]) + + *d = ary + return nil + +} + +func (p Patch) add(doc *container, op operation) error { + path := op.path() + + con, key := findObject(doc, path) + + if con == nil { + return fmt.Errorf("jsonpatch add operation does not apply: doc is missing path: %s", path) + } + + return con.add(key, op.value()) +} + +func (p Patch) remove(doc *container, op operation) error { + path := op.path() + + con, key := findObject(doc, path) + + if con == nil { + return fmt.Errorf("jsonpatch remove operation does not apply: doc is missing path: %s", path) + } + + return con.remove(key) +} + +func (p Patch) replace(doc *container, op operation) error { + path := op.path() + + con, key := findObject(doc, path) + + if con == nil { + return fmt.Errorf("jsonpatch replace operation does not apply: doc is missing path: %s", path) + } + + return con.set(key, op.value()) +} + +func (p Patch) move(doc *container, op operation) error { + from := op.from() + + con, key := findObject(doc, from) + + if con == nil { + return fmt.Errorf("jsonpatch move operation does not apply: doc is missing from path: %s", from) + } + + val, err := con.get(key) + if err != nil { + return err + } + + err = con.remove(key) + if err != nil { + return err + } + + path := op.path() + + con, key = findObject(doc, path) + + if con == nil { + return fmt.Errorf("jsonpatch move operation does not apply: doc is missing destination path: %s", path) + } + + return con.set(key, val) +} + +func (p Patch) test(doc *container, op operation) error { + path := op.path() + + con, key := findObject(doc, path) + + if con == nil { + return fmt.Errorf("jsonpatch test operation does not apply: is missing path: %s", path) + } + + val, err := con.get(key) + + if err != nil { + return err + } + + if val == nil { + if op.value().raw == nil { + return nil + } else { + return fmt.Errorf("Testing value %s failed", path) + } + } + + if val.equal(op.value()) { + return nil + } + + return fmt.Errorf("Testing value %s failed", path) +} + +// Equal indicates if 2 JSON documents have the same structural equality. +func Equal(a, b []byte) bool { + ra := make(json.RawMessage, len(a)) + copy(ra, a) + la := newLazyNode(&ra) + + rb := make(json.RawMessage, len(b)) + copy(rb, b) + lb := newLazyNode(&rb) + + return la.equal(lb) +} + +// DecodePatch decodes the passed JSON document as an RFC 6902 patch. +func DecodePatch(buf []byte) (Patch, error) { + var p Patch + + err := json.Unmarshal(buf, &p) + + if err != nil { + return nil, err + } + + return p, nil +} + +// Apply mutates a JSON document according to the patch, and returns the new +// document. +func (p Patch) Apply(doc []byte) ([]byte, error) { + return p.ApplyIndent(doc, "") +} + +// ApplyIndent mutates a JSON document according to the patch, and returns the new +// document indented. +func (p Patch) ApplyIndent(doc []byte, indent string) ([]byte, error) { + var pd container + if doc[0] == LeftBrace { + pd = &partialArray{} + } else { + pd = &partialDoc{} + } + + err := json.Unmarshal(doc, pd) + + if err != nil { + return nil, err + } + + err = nil + + for _, op := range p { + switch op.kind() { + case "add": + err = p.add(&pd, op) + case "remove": + err = p.remove(&pd, op) + case "replace": + err = p.replace(&pd, op) + case "move": + err = p.move(&pd, op) + case "test": + err = p.test(&pd, op) + default: + err = fmt.Errorf("Unexpected kind: %s", op.kind()) + } + + if err != nil { + return nil, err + } + } + + if indent != "" { + return json.MarshalIndent(pd, "", indent) + } + + return json.Marshal(pd) +} +*/ diff --git a/util/json/patch_test.go b/util/json/patch_test.go new file mode 100755 index 00000000..9bfc63f1 --- /dev/null +++ b/util/json/patch_test.go @@ -0,0 +1,311 @@ +// Copyright © 2016 Abcum Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package json + +// import ( +// "bytes" +// "encoding/json" +// "fmt" +// "reflect" +// "testing" +// ) + +// func reformatJSON(j string) string { +// buf := new(bytes.Buffer) + +// json.Indent(buf, []byte(j), "", " ") + +// return buf.String() +// } + +// func compareJSON(a, b string) bool { +// // return Equal([]byte(a), []byte(b)) + +// var obj_a, obj_b map[string]interface{} +// json.Unmarshal([]byte(a), &obj_a) +// json.Unmarshal([]byte(b), &obj_b) + +// // fmt.Printf("Comparing %#v\nagainst %#v\n", obj_a, obj_b) +// return reflect.DeepEqual(obj_a, obj_b) +// } + +// func applyPatch(doc, patch string) (string, error) { +// obj, err := DecodePatch([]byte(patch)) + +// if err != nil { +// panic(err) +// } + +// out, err := obj.Apply([]byte(doc)) + +// if err != nil { +// return "", err +// } + +// return string(out), nil +// } + +// type Case struct { +// doc, patch, result string +// } + +// var Cases = []Case{ +// { +// `{ "foo": "bar"}`, +// `[ +// { "op": "add", "path": "/baz", "value": "qux" } +// ]`, +// `{ +// "baz": "qux", +// "foo": "bar" +// }`, +// }, +// { +// `{ "foo": [ "bar", "baz" ] }`, +// `[ +// { "op": "add", "path": "/foo/1", "value": "qux" } +// ]`, +// `{ "foo": [ "bar", "qux", "baz" ] }`, +// }, +// { +// `{ "baz": "qux", "foo": "bar" }`, +// `[ { "op": "remove", "path": "/baz" } ]`, +// `{ "foo": "bar" }`, +// }, +// { +// `{ "foo": [ "bar", "qux", "baz" ] }`, +// `[ { "op": "remove", "path": "/foo/1" } ]`, +// `{ "foo": [ "bar", "baz" ] }`, +// }, +// { +// `{ "baz": "qux", "foo": "bar" }`, +// `[ { "op": "replace", "path": "/baz", "value": "boo" } ]`, +// `{ "baz": "boo", "foo": "bar" }`, +// }, +// { +// `{ +// "foo": { +// "bar": "baz", +// "waldo": "fred" +// }, +// "qux": { +// "corge": "grault" +// } +// }`, +// `[ { "op": "move", "from": "/foo/waldo", "path": "/qux/thud" } ]`, +// `{ +// "foo": { +// "bar": "baz" +// }, +// "qux": { +// "corge": "grault", +// "thud": "fred" +// } +// }`, +// }, +// { +// `{ "foo": [ "all", "grass", "cows", "eat" ] }`, +// `[ { "op": "move", "from": "/foo/1", "path": "/foo/3" } ]`, +// `{ "foo": [ "all", "cows", "eat", "grass" ] }`, +// }, +// { +// `{ "foo": "bar" }`, +// `[ { "op": "add", "path": "/child", "value": { "grandchild": { } } } ]`, +// `{ "foo": "bar", "child": { "grandchild": { } } }`, +// }, +// { +// `{ "foo": ["bar"] }`, +// `[ { "op": "add", "path": "/foo/-", "value": ["abc", "def"] } ]`, +// `{ "foo": ["bar", ["abc", "def"]] }`, +// }, +// { +// `{ "foo": "bar", "qux": { "baz": 1, "bar": null } }`, +// `[ { "op": "remove", "path": "/qux/bar" } ]`, +// `{ "foo": "bar", "qux": { "baz": 1 } }`, +// }, +// { +// `{ "foo": "bar" }`, +// `[ { "op": "add", "path": "/baz", "value": null } ]`, +// `{ "baz": null, "foo": "bar" }`, +// }, +// { +// `{ "foo": ["bar"]}`, +// `[ { "op": "replace", "path": "/foo/0", "value": "baz"}]`, +// `{ "foo": ["baz"]}`, +// }, +// { +// `{ "foo": ["bar","baz"]}`, +// `[ { "op": "replace", "path": "/foo/0", "value": "bum"}]`, +// `{ "foo": ["bum","baz"]}`, +// }, +// { +// `{ "foo": ["bar","qux","baz"]}`, +// `[ { "op": "replace", "path": "/foo/1", "value": "bum"}]`, +// `{ "foo": ["bar", "bum","baz"]}`, +// }, +// { +// `[ {"foo": ["bar","qux","baz"]}]`, +// `[ { "op": "replace", "path": "/0/foo/0", "value": "bum"}]`, +// `[ {"foo": ["bum","qux","baz"]}]`, +// }, +// } + +// type BadCase struct { +// doc, patch string +// } + +// var MutationTestCases = []BadCase{ +// { +// `{ "foo": "bar", "qux": { "baz": 1, "bar": null } }`, +// `[ { "op": "remove", "path": "/qux/bar" } ]`, +// }, +// { +// `{ "foo": "bar", "qux": { "baz": 1, "bar": null } }`, +// `[ { "op": "replace", "path": "/qux/baz", "value": null } ]`, +// }, +// } + +// var BadCases = []BadCase{ +// { +// `{ "foo": "bar" }`, +// `[ { "op": "add", "path": "/baz/bat", "value": "qux" } ]`, +// }, +// { +// `{ "a": { "b": { "d": 1 } } }`, +// `[ { "op": "remove", "path": "/a/b/c" } ]`, +// }, +// { +// `{ "a": { "b": { "d": 1 } } }`, +// `[ { "op": "move", "from": "/a/b/c", "path": "/a/b/e" } ]`, +// }, +// { +// `{ "a": { "b": [1] } }`, +// `[ { "op": "remove", "path": "/a/b/1" } ]`, +// }, +// { +// `{ "a": { "b": [1] } }`, +// `[ { "op": "move", "from": "/a/b/1", "path": "/a/b/2" } ]`, +// }, +// } + +// func TestAllCases(t *testing.T) { +// for _, c := range Cases { +// out, err := applyPatch(c.doc, c.patch) + +// if err != nil { +// t.Errorf("Unable to apply patch: %s", err) +// } + +// if !compareJSON(out, c.result) { +// t.Errorf("Patch did not apply. Expected:\n%s\n\nActual:\n%s", +// reformatJSON(c.result), reformatJSON(out)) +// } +// } + +// for _, c := range MutationTestCases { +// out, err := applyPatch(c.doc, c.patch) + +// if err != nil { +// t.Errorf("Unable to apply patch: %s", err) +// } + +// if compareJSON(out, c.doc) { +// t.Errorf("Patch did not apply. Original:\n%s\n\nPatched:\n%s", +// reformatJSON(c.doc), reformatJSON(out)) +// } +// } + +// for _, c := range BadCases { +// _, err := applyPatch(c.doc, c.patch) + +// if err == nil { +// t.Errorf("Patch should have failed to apply but it did not") +// } +// } +// } + +// type TestCase struct { +// doc, patch string +// result bool +// failedPath string +// } + +// var TestCases = []TestCase{ +// { +// `{ +// "baz": "qux", +// "foo": [ "a", 2, "c" ] +// }`, +// `[ +// { "op": "test", "path": "/baz", "value": "qux" }, +// { "op": "test", "path": "/foo/1", "value": 2 } +// ]`, +// true, +// "", +// }, +// { +// `{ "baz": "qux" }`, +// `[ { "op": "test", "path": "/baz", "value": "bar" } ]`, +// false, +// "/baz", +// }, +// { +// `{ +// "baz": "qux", +// "foo": ["a", 2, "c"] +// }`, +// `[ +// { "op": "test", "path": "/baz", "value": "qux" }, +// { "op": "test", "path": "/foo/1", "value": "c" } +// ]`, +// false, +// "/foo/1", +// }, +// { +// `{ "baz": "qux" }`, +// `[ { "op": "test", "path": "/foo", "value": 42 } ]`, +// false, +// "/foo", +// }, +// { +// `{ "baz": "qux" }`, +// `[ { "op": "test", "path": "/foo", "value": null } ]`, +// true, +// "", +// }, +// { +// `{ "baz/foo": "qux" }`, +// `[ { "op": "test", "path": "/baz~1foo", "value": "qux"} ]`, +// true, +// "", +// }, +// } + +// func TestAllTest(t *testing.T) { +// for _, c := range TestCases { +// _, err := applyPatch(c.doc, c.patch) + +// if c.result && err != nil { +// t.Errorf("Testing failed when it should have passed: %s", err) +// } else if !c.result && err == nil { +// t.Errorf("Testing passed when it should have faild: %s", err) +// } else if !c.result { +// expected := fmt.Sprintf("Testing value %s failed", c.failedPath) +// if err.Error() != expected { +// t.Errorf("Testing failed as expected but invalid message: expected [%s], got [%s]", expected, err) +// } +// } +// } +// }