Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions patch.go
Original file line number Diff line number Diff line change
Expand Up @@ -644,6 +644,27 @@ func (p Patch) remove(doc *container, op Operation) error {
return nil
}

func (p Patch) removeImpl(doc *container, op Operation, allowMissingPathOnRemove bool) error {
path, err := op.Path()
if err != nil {
return errors.Wrapf(ErrMissing, "remove operation failed to decode path")
}

con, key := findObject(doc, path)

if con == nil {
return errors.Wrapf(ErrMissing, "remove operation does not apply: doc is missing path: \"%s\"", path)
}

err = con.remove(key)
// return nil if patch to be removed not found
if err != nil && !allowMissingPathOnRemove {
return errors.Wrapf(err, "error in remove for path: '%s'", path)
}

return nil
}

func (p Patch) replace(doc *container, op Operation) error {
path, err := op.Path()
if err != nil {
Expand Down Expand Up @@ -871,6 +892,55 @@ func (p Patch) ApplyIndent(doc []byte, indent string) ([]byte, error) {
return json.Marshal(pd)
}

// functionality is same as ApplyIndent in addition it doesn't return error if object is not present on path
func (p Patch) ApplyIndentAllowRemove(doc []byte, indent string, allowMissingPathOnRemove bool) ([]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

var accumulatedCopySize int64

for _, op := range p {
switch op.Kind() {
case "add":
err = p.add(&pd, op)
case "remove":
err = p.removeImpl(&pd, op, allowMissingPathOnRemove)
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, &accumulatedCopySize)
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
Expand Down
174 changes: 174 additions & 0 deletions patch_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,38 @@ func applyPatch(doc, patch string) (string, error) {
return string(out), nil
}

func applyPatchImpl_allowRemove(doc, patch string) (string, error) {
obj, err := DecodePatch([]byte(patch))

if err != nil {
panic(err)
}

out, err := obj.ApplyIndentAllowRemove([]byte(doc), "", true)

if err != nil {
return "", err
}

return string(out), nil
}

func applyPatchImpl_dontAllowRemove(doc, patch string) (string, error) {
obj, err := DecodePatch([]byte(patch))

if err != nil {
panic(err)
}

out, err := obj.ApplyIndentAllowRemove([]byte(doc), "", false)

if err != nil {
return "", err
}

return string(out), nil
}

type Case struct {
doc, patch, result string
}
Expand Down Expand Up @@ -380,6 +412,9 @@ var Cases = []Case{
type BadCase struct {
doc, patch string
}
type BadCases_remove struct {
doc, patch string
}

var MutationTestCases = []BadCase{
{
Expand Down Expand Up @@ -480,6 +515,29 @@ var BadCases = []BadCase{
},
}

var BadCasesRemove = []BadCases_remove{
{
`{ "a": { "b": { "d": 1 } } }`,
`[ { "op": "remove", "path": "/a/b/c" } ]`,
},
{
`{ "a": { "b": [1] } }`,
`[ { "op": "remove", "path": "/a/b/1" } ]`,
},
{
`{ "foo": []}`,
`[ {"op": "remove", "path": "/foo/-"}]`,
},
{
`{ "foo": []}`,
`[ {"op": "remove", "path": "/foo/-1"}]`,
},
{
`{ "foo": ["bar"]}`,
`[ {"op": "remove", "path": "/foo/-2"}]`,
},
}

// This is not thread safe, so we cannot run patch tests in parallel.
func configureGlobals(accumulatedCopySizeLimit int64) func() {
oldAccumulatedCopySizeLimit := AccumulatedCopySizeLimit
Expand Down Expand Up @@ -539,6 +597,106 @@ func TestAllCases(t *testing.T) {
}
}

func TestAllCasesImpl_allowRemove(t *testing.T) {
defer configureGlobals(int64(100))()
for _, c := range ExprCases {
out, err := applyPatchImpl_allowRemove(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\nPatch:%s\n",
reformatJSON(c.result), reformatJSON(out), reformatJSON(c.patch))
}
}

for _, c := range Cases {
out, err := applyPatchImpl_allowRemove(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\nPatch:%s\n",
reformatJSON(c.result), reformatJSON(out), reformatJSON(c.patch))
}
}

for _, c := range MutationTestCases {
out, err := applyPatchImpl_allowRemove(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 BadCasesRemove {
_, err := applyPatchImpl_allowRemove(c.doc, c.patch)

if err != nil {
t.Errorf("Patch %q should have failed to apply but it did not", c.patch)
}
}
}

func TestAllCasesImpl_dontAllow(t *testing.T) {
defer configureGlobals(int64(100))()
for _, c := range ExprCases {
out, err := applyPatchImpl_dontAllowRemove(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\nPatch:%s\n",
reformatJSON(c.result), reformatJSON(out), reformatJSON(c.patch))
}
}

for _, c := range Cases {
out, err := applyPatchImpl_dontAllowRemove(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\nPatch:%s\n",
reformatJSON(c.result), reformatJSON(out), reformatJSON(c.patch))
}
}

for _, c := range MutationTestCases {
out, err := applyPatchImpl_dontAllowRemove(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 := applyPatchImpl_dontAllowRemove(c.doc, c.patch)

if err == nil {
t.Errorf("Patch %q should have failed to apply but it did not", c.patch)
}
}
}

type TestCase struct {
doc, patch string
result bool
Expand Down Expand Up @@ -636,3 +794,19 @@ func TestAllTest(t *testing.T) {
}
}
}

func TestAllTest1(t *testing.T) {
for _, c := range TestCases {
_, err := applyPatchImpl_allowRemove(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: test failed", c.failedPath)
if err.Error() != expected {
t.Errorf("Testing failed as expected but invalid message: expected [%s], got [%s]", expected, err)
}
}
}
}