Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
K
k3s
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Jacklull
k3s
Commits
3f69112e
Commit
3f69112e
authored
Feb 25, 2015
by
Brian Grant
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #4766 from mikedanese/remove-util-merge
Remove pkg/util/merge and replace with evanphx/json-patch
parents
e152c671
88e1ab3c
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
1411 additions
and
131 deletions
+1411
-131
Godeps.json
Godeps/Godeps.json
+8
-4
.travis.yml
.../_workspace/src/github.com/evanphx/json-patch/.travis.yml
+14
-0
LICENSE
Godeps/_workspace/src/github.com/evanphx/json-patch/LICENSE
+25
-0
README.md
...ps/_workspace/src/github.com/evanphx/json-patch/README.md
+29
-0
merge.go
Godeps/_workspace/src/github.com/evanphx/json-patch/merge.go
+280
-0
merge_test.go
...workspace/src/github.com/evanphx/json-patch/merge_test.go
+322
-0
patch.go
Godeps/_workspace/src/github.com/evanphx/json-patch/patch.go
+496
-0
patch_test.go
...workspace/src/github.com/evanphx/json-patch/patch_test.go
+234
-0
helpers.go
pkg/kubectl/cmd/util/helpers.go
+3
-2
merge.go
pkg/util/merge/merge.go
+0
-57
merge_test.go
pkg/util/merge/merge_test.go
+0
-68
No files found.
Godeps/Godeps.json
View file @
3f69112e
...
...
@@ -117,6 +117,10 @@
"Rev"
:
"692a50017a7049b26cf7ea4ccfc0d8c77369a793"
},
{
"ImportPath"
:
"github.com/evanphx/json-patch"
,
"Rev"
:
"7dd4489c2eb6073e5a9d7746c3274c5b5f0387df"
},
{
"ImportPath"
:
"github.com/fsouza/go-dockerclient"
,
"Rev"
:
"d19717788084716e4adff0515be6289aa04bec46"
},
...
...
@@ -156,15 +160,15 @@
"Rev"
:
"6633656539c1639d9d78127b7d47c622b5d7b6dc"
},
{
"ImportPath"
:
"github.com/matttproud/golang_protobuf_extensions/ext"
,
"Rev"
:
"7a864a042e844af638df17ebbabf8183dace556a"
},
{
"ImportPath"
:
"github.com/kr/pty"
,
"Comment"
:
"release.r56-25-g05017fc"
,
"Rev"
:
"05017fcccf23c823bfdea560dcc958a136e54fb7"
},
{
"ImportPath"
:
"github.com/matttproud/golang_protobuf_extensions/ext"
,
"Rev"
:
"7a864a042e844af638df17ebbabf8183dace556a"
},
{
"ImportPath"
:
"github.com/miekg/dns"
,
"Rev"
:
"3f504e8dabd5d562e997d19ce0200aa41973e1b2"
},
...
...
Godeps/_workspace/src/github.com/evanphx/json-patch/.travis.yml
0 → 100644
View file @
3f69112e
language
:
go
go
:
-
1.4
-
1.3
install
:
-
if ! go get code.google.com/p/go.tools/cmd/cover; then go get golang.org/x/tools/cmd/cover; fi
script
:
-
go test -cover ./...
notifications
:
email
:
false
Godeps/_workspace/src/github.com/evanphx/json-patch/LICENSE
0 → 100644
View file @
3f69112e
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.
Godeps/_workspace/src/github.com/evanphx/json-patch/README.md
0 → 100644
View file @
3f69112e
## JSON-Patch
Provides the abiilty to modify and test a JSON according to a
[
RFC6902 JSON patch
](
http://tools.ietf.org/html/rfc6902
)
and
[
RFC7386 JSON Merge Patch
](
https://tools.ietf.org/html/rfc7386
)
.
*Version*
:
**1.0**
[

](http://godoc.org/github.com/evanphx/json-patch)
[

](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)`
Godeps/_workspace/src/github.com/evanphx/json-patch/merge.go
0 → 100644
View file @
3f69112e
package
jsonpatch
import
(
"encoding/json"
"fmt"
"reflect"
)
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
{
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
{
var
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
}
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
}
Godeps/_workspace/src/github.com/evanphx/json-patch/merge_test.go
0 → 100644
View file @
3f69112e
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
)
}
}
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'
\n
got:
\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))
}
*/
}
Godeps/_workspace/src/github.com/evanphx/json-patch/patch.go
0 → 100644
View file @
3f69112e
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
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
*
n
.
raw
,
nil
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
*
partialDoc
,
path
string
)
(
container
,
string
)
{
doc
:=
container
(
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
(
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
,
key
}
func
(
d
*
partialDoc
)
set
(
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
{
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
}
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
}
return
(
*
d
)[
idx
],
nil
}
func
(
d
*
partialArray
)
remove
(
key
string
)
error
{
idx
,
err
:=
strconv
.
Atoi
(
key
)
if
err
!=
nil
{
return
err
}
cur
:=
*
d
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
*
partialDoc
,
op
operation
)
error
{
path
:=
op
.
path
()
con
,
key
:=
findObject
(
doc
,
path
)
if
con
==
nil
{
return
fmt
.
Errorf
(
"Missing container: %s"
,
path
)
}
con
.
set
(
key
,
op
.
value
())
return
nil
}
func
(
p
Patch
)
remove
(
doc
*
partialDoc
,
op
operation
)
error
{
path
:=
op
.
path
()
con
,
key
:=
findObject
(
doc
,
path
)
return
con
.
remove
(
key
)
}
func
(
p
Patch
)
replace
(
doc
*
partialDoc
,
op
operation
)
error
{
path
:=
op
.
path
()
con
,
key
:=
findObject
(
doc
,
path
)
con
.
set
(
key
,
op
.
value
())
return
nil
}
func
(
p
Patch
)
move
(
doc
*
partialDoc
,
op
operation
)
error
{
from
:=
op
.
from
()
con
,
key
:=
findObject
(
doc
,
from
)
val
,
err
:=
con
.
get
(
key
)
if
err
!=
nil
{
return
err
}
con
.
remove
(
key
)
path
:=
op
.
path
()
con
,
key
=
findObject
(
doc
,
path
)
con
.
set
(
key
,
val
)
return
nil
}
func
(
p
Patch
)
test
(
doc
*
partialDoc
,
op
operation
)
error
{
path
:=
op
.
path
()
con
,
key
:=
findObject
(
doc
,
path
)
val
,
err
:=
con
.
get
(
key
)
if
err
!=
nil
{
return
err
}
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
)
{
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
}
}
return
json
.
Marshal
(
pd
)
}
Godeps/_workspace/src/github.com/evanphx/json-patch/patch_test.go
0 → 100644
View file @
3f69112e
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
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 } }`
,
},
}
type
BadCase
struct
{
doc
,
patch
string
}
var
MutationTestCases
=
[]
BadCase
{
{
`{ "foo": "bar", "qux": { "baz": 1, "bar": null } }`
,
`[ { "op": "remove", "path": "/qux/bar" } ]`
,
},
}
var
BadCases
=
[]
BadCase
{
{
`{ "foo": "bar" }`
,
`[ { "op": "add", "path": "/baz/bat", "value": "qux" } ]`
,
},
}
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\n
Actual:
\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\n
Patched:
\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"
,
},
}
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
)
}
}
}
}
pkg/kubectl/cmd/util/helpers.go
View file @
3f69112e
...
...
@@ -28,7 +28,8 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util/merge"
"github.com/evanphx/json-patch"
"github.com/golang/glog"
"github.com/spf13/cobra"
)
...
...
@@ -167,7 +168,7 @@ func Merge(dst runtime.Object, fragment, kind string) (runtime.Object, error) {
if
err
!=
nil
{
return
nil
,
err
}
patched
,
err
:=
merge
.
MergeJSON
(
target
,
[]
byte
(
fragment
))
patched
,
err
:=
jsonpatch
.
MergePatch
(
target
,
[]
byte
(
fragment
))
if
err
!=
nil
{
return
nil
,
err
}
...
...
pkg/util/merge/merge.go
deleted
100644 → 0
View file @
e152c671
/*
Copyright 2014 Google Inc. All rights reserved.
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
merge
import
(
"encoding/json"
)
// MergeJSON merges JSON according to RFC7386
// (see https://tools.ietf.org/html/rfc7386)
func
MergeJSON
(
dst
,
src
[]
byte
)
([]
byte
,
error
)
{
var
target
interface
{}
if
err
:=
json
.
Unmarshal
(
dst
,
&
target
);
err
!=
nil
{
return
nil
,
err
}
var
patch
interface
{}
if
err
:=
json
.
Unmarshal
(
src
,
&
patch
);
err
!=
nil
{
return
nil
,
err
}
return
json
.
Marshal
(
MergePatch
(
target
,
patch
))
}
// MergePatch is an implementation of MergePatch described in RFC7386 that operates on
// json marshalled into empty interface{} by encoding/json.Unmarshal()
// (see https://tools.ietf.org/html/rfc7386#section-2)
func
MergePatch
(
target
,
patch
interface
{})
interface
{}
{
if
patchObject
,
isPatchObject
:=
patch
.
(
map
[
string
]
interface
{});
isPatchObject
{
targetObject
:=
make
(
map
[
string
]
interface
{})
if
m
,
isTargetObject
:=
target
.
(
map
[
string
]
interface
{});
isTargetObject
{
targetObject
=
m
}
for
name
,
value
:=
range
patchObject
{
if
_
,
found
:=
targetObject
[
name
];
value
==
nil
&&
found
{
delete
(
targetObject
,
name
)
}
else
{
targetObject
[
name
]
=
MergePatch
(
targetObject
[
name
],
value
)
}
}
return
targetObject
}
else
{
return
patch
}
}
pkg/util/merge/merge_test.go
deleted
100644 → 0
View file @
e152c671
/*
Copyright 2014 Google Inc. All rights reserved.
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
merge
import
(
"testing"
)
func
TestMergeJSON
(
t
*
testing
.
T
)
{
tests
:=
[]
struct
{
target
string
patch
string
expected
string
}{
{
target
:
`{}`
,
patch
:
`{}`
,
expected
:
`{}`
},
{
target
:
`{"a":"b","c":{"d":"e","f":"g"}}`
,
patch
:
`{}`
,
expected
:
`{"a":"b","c":{"d":"e","f":"g"}}`
},
//update
{
target
:
`{"a":"b","c":"d"}`
,
patch
:
`{"a":"z"}`
,
expected
:
`{"a":"z","c":"d"}`
},
//remove key
{
target
:
`{"f":"g"}`
,
patch
:
`{"f":null}`
,
expected
:
`{}`
},
//inner update
{
target
:
`{"c":{"d":"e","f":"g"}}`
,
patch
:
`{"c":{"f":"z"}}`
,
expected
:
`{"c":{"d":"e","f":"z"}}`
},
//inner remove
{
target
:
`{"c":{"d":"e","f":"g"}}`
,
patch
:
`{"c":{"f":null}}`
,
expected
:
`{"c":{"d":"e"}}`
},
//complex update and remove
{
target
:
`{"a":"b","c":{"d":"e","f":"g"}}`
,
patch
:
`{"a":"z","c":{"f":null}}`
,
expected
:
`{"a":"z","c":{"d":"e"}}`
},
// test cases from https://tools.ietf.org/html/rfc7386#appendix-A slightly adapted to correspond to go's
// encoding/json conventions
{
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","c":null}}`
},
{
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","c":null}`
},
{
target
:
`{}`
,
patch
:
`{"a":{"bb":{"ccc":null}}}`
,
expected
:
`{"a":{"bb":{"ccc":null}}}`
},
}
for
i
,
test
:=
range
tests
{
out
,
err
:=
MergeJSON
([]
byte
(
test
.
target
),
[]
byte
(
test
.
patch
))
if
err
!=
nil
{
t
.
Errorf
(
"case %v, unexpected error: %v"
,
i
,
err
)
}
if
string
(
out
)
!=
test
.
expected
{
t
.
Errorf
(
"case %v, expected:
\n
%v
\n
saw:
\n
%v
\n
"
,
i
,
test
.
expected
,
string
(
out
))
}
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment