vendor github.com/evanphx/json-patch

This commit is contained in:
Mengqi Yu 2018-03-05 18:24:32 -08:00
parent ce6b8f6927
commit b62a81c7ca
10 changed files with 1931 additions and 1 deletions

8
Gopkg.lock generated
View File

@ -25,6 +25,12 @@
revision = "5741799b275a3c4a5a9623a993576d7545cf7b5c"
version = "v2.4.0"
[[projects]]
branch = "master"
name = "github.com/evanphx/json-patch"
packages = ["."]
revision = "944e07253867aacae43c04b2e6a239005443f33a"
[[projects]]
name = "github.com/ghodss/yaml"
packages = ["."]
@ -226,6 +232,6 @@
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "a778052416a71e5aca31256e71794eda11fed3c896b231ca36a045770abad827"
inputs-digest = "b4cf8af8194603b9195b180d2d425c6eb0b9b2f2d82493383b14c4d02d9dffff"
solver-name = "gps-cdcl"
solver-version = 1

16
vendor/github.com/evanphx/json-patch/.travis.yml generated vendored Normal file
View File

@ -0,0 +1,16 @@
language: go
go:
- 1.8
- 1.7
install:
- if ! go get code.google.com/p/go.tools/cmd/cover; then go get golang.org/x/tools/cmd/cover; fi
- go get github.com/jessevdk/go-flags
script:
- go get
- go test -cover ./...
notifications:
email: false

25
vendor/github.com/evanphx/json-patch/LICENSE generated vendored Normal file
View File

@ -0,0 +1,25 @@
Copyright (c) 2014, Evan Phoenix
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the Evan Phoenix nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

29
vendor/github.com/evanphx/json-patch/README.md generated vendored Normal file
View File

@ -0,0 +1,29 @@
## JSON-Patch
Provides the ability to modify and test a JSON according to a
[RFC6902 JSON patch](http://tools.ietf.org/html/rfc6902) and [RFC7396 JSON Merge Patch](https://tools.ietf.org/html/rfc7396).
*Version*: **1.0**
[![GoDoc](https://godoc.org/github.com/evanphx/json-patch?status.svg)](http://godoc.org/github.com/evanphx/json-patch)
[![Build Status](https://travis-ci.org/evanphx/json-patch.svg?branch=master)](https://travis-ci.org/evanphx/json-patch)
### API Usage
* Given a `[]byte`, obtain a Patch object
`obj, err := jsonpatch.DecodePatch(patch)`
* Apply the patch and get a new document back
`out, err := obj.Apply(doc)`
* Create a JSON Merge Patch document based on two json documents (a to b):
`mergeDoc, err := jsonpatch.CreateMergePatch(a, b)`
* Bonus API: compare documents for structural equality
`jsonpatch.Equal(doca, docb)`

View File

@ -0,0 +1,39 @@
package main
// Borrowed from Concourse: https://github.com/concourse/atc/blob/master/atccmd/file_flag.go
import (
"fmt"
"os"
"path/filepath"
)
// FileFlag is a flag for passing a path to a file on disk. The file is
// expected to be a file, not a directory, that actually exists.
type FileFlag string
// UnmarshalFlag implements go-flag's Unmarshaler interface
func (f *FileFlag) UnmarshalFlag(value string) error {
stat, err := os.Stat(value)
if err != nil {
return err
}
if stat.IsDir() {
return fmt.Errorf("path '%s' is a directory, not a file", value)
}
abs, err := filepath.Abs(value)
if err != nil {
return err
}
*f = FileFlag(abs)
return nil
}
// Path is the path to the file
func (f FileFlag) Path() string {
return string(f)
}

View File

@ -0,0 +1,56 @@
package main
import (
"fmt"
"io/ioutil"
"log"
"os"
jsonpatch "github.com/evanphx/json-patch"
flags "github.com/jessevdk/go-flags"
)
type opts struct {
PatchFilePaths []FileFlag `long:"patch-file" short:"p" value-name:"PATH" description:"Path to file with one or more operations"`
}
func main() {
var o opts
_, err := flags.Parse(&o)
if err != nil {
log.Fatalf("error: %s\n", err)
}
patches := make([]jsonpatch.Patch, len(o.PatchFilePaths))
for i, patchFilePath := range o.PatchFilePaths {
var bs []byte
bs, err = ioutil.ReadFile(patchFilePath.Path())
if err != nil {
log.Fatalf("error reading patch file: %s", err)
}
var patch jsonpatch.Patch
patch, err = jsonpatch.DecodePatch(bs)
if err != nil {
log.Fatalf("error decoding patch file: %s", err)
}
patches[i] = patch
}
doc, err := ioutil.ReadAll(os.Stdin)
if err != nil {
log.Fatalf("error reading from stdin: %s", err)
}
mdoc := doc
for _, patch := range patches {
mdoc, err = patch.Apply(mdoc)
if err != nil {
log.Fatalf("error applying patch: %s", err)
}
}
fmt.Printf("%s", mdoc)
}

305
vendor/github.com/evanphx/json-patch/merge.go generated vendored Normal file
View File

@ -0,0 +1,305 @@
package jsonpatch
import (
"encoding/json"
"fmt"
"reflect"
)
func merge(cur, patch *lazyNode, mergeMerge bool) *lazyNode {
curDoc, err := cur.intoDoc()
if err != nil {
pruneNulls(patch)
return patch
}
patchDoc, err := patch.intoDoc()
if err != nil {
return patch
}
mergeDocs(curDoc, patchDoc, mergeMerge)
return cur
}
func mergeDocs(doc, patch *partialDoc, mergeMerge bool) {
for k, v := range *patch {
if v == nil {
if mergeMerge {
(*doc)[k] = nil
} else {
delete(*doc, k)
}
} else {
cur, ok := (*doc)[k]
if !ok || cur == nil {
pruneNulls(v)
(*doc)[k] = v
} else {
(*doc)[k] = merge(cur, v, mergeMerge)
}
}
}
}
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")
// MergeMergePatches merges two merge patches together, such that
// applying this resulting merged merge patch to a document yields the same
// as merging each merge patch to the document in succession.
func MergeMergePatches(patch1Data, patch2Data []byte) ([]byte, error) {
return doMergePatch(patch1Data, patch2Data, true)
}
// MergePatch merges the patchData into the docData.
func MergePatch(docData, patchData []byte) ([]byte, error) {
return doMergePatch(docData, patchData, false)
}
func doMergePatch(docData, patchData []byte, mergeMerge bool) ([]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 {
if mergeMerge {
doc = patch
} else {
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, mergeMerge)
}
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 nil:
// Both nil, fine.
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 {
av, ok := a[key]
// value was added
if !ok {
into[key] = bv
continue
}
// If types have changed, replace completely
if reflect.TypeOf(av) != reflect.TypeOf(bv) {
into[key] = 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[key] = dst
}
case string, float64, bool:
if !matchesValue(av, bv) {
into[key] = bv
}
case []interface{}:
bt := bv.([]interface{})
if !matchesArray(at, bt) {
into[key] = bv
}
case nil:
switch bv.(type) {
case nil:
// Both nil, fine.
default:
into[key] = 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
}

471
vendor/github.com/evanphx/json-patch/merge_test.go generated vendored Normal file
View File

@ -0,0 +1,471 @@
package jsonpatch
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 TestCreateMergePatchNil(t *testing.T) {
doc := `{ "title": "hello", "nested": {"one": 1, "two": [{"one":null}, {"two":null}, {"three":null}]} }`
pat := doc
exp := `{}`
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 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 TestMergeReplaceKeyNotEscape(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/escaped": 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 TestMergePatchReplaceKeyNotEscaping(t *testing.T) {
doc := `{ "obj": { "title/escaped": "hello" } }`
pat := `{ "obj": { "title/escaped": "goodbye" } }`
exp := `{ "obj": { "title/escaped": "goodbye" } }`
res := mergePatch(doc, pat)
if !compareJSON(exp, res) {
t.Fatalf("Key was not replaced")
}
}
func TestMergeMergePatches(t *testing.T) {
cases := []struct {
demonstrates string
p1 string
p2 string
exp string
}{
{
demonstrates: "simple patches are merged normally",
p1: `{"add1": 1}`,
p2: `{"add2": 2}`,
exp: `{"add1": 1, "add2": 2}`,
},
{
demonstrates: "nulls are kept",
p1: `{"del1": null}`,
p2: `{"del2": null}`,
exp: `{"del1": null, "del2": null}`,
},
{
demonstrates: "a key added then deleted is kept deleted",
p1: `{"add_then_delete": "atd"}`,
p2: `{"add_then_delete": null}`,
exp: `{"add_then_delete": null}`,
},
{
demonstrates: "a key deleted then added is kept added",
p1: `{"delete_then_add": null}`,
p2: `{"delete_then_add": "dta"}`,
exp: `{"delete_then_add": "dta"}`,
},
{
demonstrates: "object overrides array",
p1: `[]`,
p2: `{"del": null, "add": "a"}`,
exp: `{"del": null, "add": "a"}`,
},
{
demonstrates: "array overrides object",
p1: `{"del": null, "add": "a"}`,
p2: `[]`,
exp: `[]`,
},
}
for _, c := range cases {
out, err := MergeMergePatches([]byte(c.p1), []byte(c.p2))
if err != nil {
panic(err)
}
if !compareJSON(c.exp, string(out)) {
t.Logf("Error while trying to demonstrate: %v", c.demonstrates)
t.Logf("Got %v", string(out))
t.Logf("Expected %v", c.exp)
t.Fatalf("Merged merge patch is incorrect")
}
}
}

643
vendor/github.com/evanphx/json-patch/patch.go generated vendored Normal file
View File

@ -0,0 +1,643 @@
package jsonpatch
import (
"bytes"
"encoding/json"
"fmt"
"strconv"
"strings"
)
const (
eRaw = iota
eDoc
eAry
)
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, "/")
if len(split) < 2 {
return nil, ""
}
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
if idx < 0 {
idx *= -1
if idx > len(ary) {
return fmt.Errorf("Unable to access invalid index: %d", idx)
}
idx = len(ary) - idx
}
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)
}
val, ok := con.get(key)
if val == nil || ok != nil {
return fmt.Errorf("jsonpatch replace operation does not apply: doc is missing key: %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
}
return fmt.Errorf("Testing value %s failed", path)
}
if val.equal(op.value()) {
return nil
}
return fmt.Errorf("Testing value %s failed", path)
}
func (p Patch) copy(doc *container, op operation) error {
from := op.from()
con, key := findObject(doc, from)
if con == nil {
return fmt.Errorf("jsonpatch copy operation does not apply: doc is missing from path: %s", from)
}
val, err := con.get(key)
if err != nil {
return err
}
path := op.path()
con, key = findObject(doc, path)
if con == nil {
return fmt.Errorf("jsonpatch copy operation does not apply: doc is missing destination path: %s", path)
}
return con.set(key, val)
}
// 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] == '[' {
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)
case "copy":
err = p.copy(&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)
}
// 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 (
rfc6901Decoder = strings.NewReplacer("~1", "/", "~0", "~")
)
func decodePatchKey(k string) string {
return rfc6901Decoder.Replace(k)
}

340
vendor/github.com/evanphx/json-patch/patch_test.go generated vendored Normal file
View File

@ -0,0 +1,340 @@
package jsonpatch
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 objA, objB map[string]interface{}
json.Unmarshal([]byte(a), &objA)
json.Unmarshal([]byte(b), &objB)
// fmt.Printf("Comparing %#v\nagainst %#v\n", objA, objB)
return reflect.DeepEqual(objA, objB)
}
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" ] }`,
},
{
`{ "foo": [ "bar", "baz" ] }`,
`[
{ "op": "add", "path": "/foo/-1", "value": "qux" }
]`,
`{ "foo": [ "bar", "baz", "qux" ] }`,
},
{
`{ "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"]}]`,
},
{
`[ {"foo": ["bar","qux","baz"], "bar": ["qux","baz"]}]`,
`[ { "op": "copy", "from": "/0/foo/0", "path": "/0/bar/0"}]`,
`[ {"foo": ["bar","qux","baz"], "bar": ["bar", "baz"]}]`,
},
{
`[ {"foo": ["bar","qux","baz"], "bar": ["qux","baz"]}]`,
`[ { "op": "copy", "from": "/0/foo/0", "path": "/0/bar"}]`,
`[ {"foo": ["bar","qux","baz"], "bar": ["bar", "qux", "baz"]}]`,
},
{
`[ { "foo": {"bar": ["qux","baz"]}, "baz": {"qux": "bum"}}]`,
`[ { "op": "copy", "from": "/0/foo/bar", "path": "/0/baz/bar"}]`,
`[ { "baz": {"bar": ["qux","baz"], "qux":"bum"}, "foo": {"bar": ["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" } ]`,
},
{
`{ "foo": "bar" }`,
`[ { "op": "add", "pathz": "/baz", "value": "qux" } ]`,
},
{
`{ "foo": "bar" }`,
`[ { "op": "add", "path": "", "value": "qux" } ]`,
},
{
`{ "foo": ["bar","baz"]}`,
`[ { "op": "replace", "path": "/foo/2", "value": "bum"}]`,
},
{
`{ "foo": ["bar","baz"]}`,
`[ { "op": "add", "path": "/foo/-4", "value": "bum"}]`,
},
{
`{ "name":{ "foo": "bat", "qux": "bum"}}`,
`[ { "op": "replace", "path": "/foo/bar", "value":"baz"}]`,
},
}
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)
}
}
}
}