diff --git a/go.mod b/go.mod index 01bd914cb0..ff2af06a19 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,6 @@ require ( github.com/grpc-ecosystem/grpc-gateway/v2 v2.17.1 github.com/hashicorp/golang-lru v0.5.1 github.com/heptiolabs/healthcheck v0.0.0-20171201210846-da5fdee475fb - github.com/mattbaird/jsonpatch v0.0.0-20230413205102-771768614e91 github.com/mennanov/fmutils v0.2.0 github.com/mitchellh/hashstructure/v2 v2.0.2 github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 @@ -33,6 +32,7 @@ require ( golang.org/x/oauth2 v0.12.0 golang.org/x/time v0.3.0 golang.org/x/tools v0.13.0 + gomodules.xyz/jsonpatch/v2 v2.4.0 google.golang.org/api v0.138.0 google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d google.golang.org/grpc v1.57.1 diff --git a/go.sum b/go.sum index 890951daf1..fade33f7df 100644 --- a/go.sum +++ b/go.sum @@ -330,8 +330,6 @@ github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/mattbaird/jsonpatch v0.0.0-20230413205102-771768614e91 h1:JnZSkFP1/GLwKCEuuWVhsacvbDQIVa5BRwAwd+9k2Vw= -github.com/mattbaird/jsonpatch v0.0.0-20230413205102-771768614e91/go.mod h1:M1qoD/MqPgTZIk0EWKB38wE28ACRfVcn+cU08jyArI0= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= @@ -749,6 +747,8 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= +gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= google.golang.org/api v0.0.0-20180603000442-8e296ef26005/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= diff --git a/pkg/apis/agones/v1/gameserver.go b/pkg/apis/agones/v1/gameserver.go index bafedde564..68a819fdd3 100644 --- a/pkg/apis/agones/v1/gameserver.go +++ b/pkg/apis/agones/v1/gameserver.go @@ -25,8 +25,8 @@ import ( "agones.dev/agones/pkg/apis/agones" "agones.dev/agones/pkg/util/apiserver" "agones.dev/agones/pkg/util/runtime" - "github.com/mattbaird/jsonpatch" "github.com/pkg/errors" + "gomodules.xyz/jsonpatch/v2" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" apimachineryvalidation "k8s.io/apimachinery/pkg/api/validation" diff --git a/pkg/fleetautoscalers/controller.go b/pkg/fleetautoscalers/controller.go index e319e411fb..bfbfb5e4ed 100644 --- a/pkg/fleetautoscalers/controller.go +++ b/pkg/fleetautoscalers/controller.go @@ -37,9 +37,9 @@ import ( "agones.dev/agones/pkg/util/webhooks" "agones.dev/agones/pkg/util/workerqueue" "github.com/heptiolabs/healthcheck" - "github.com/mattbaird/jsonpatch" "github.com/pkg/errors" "github.com/sirupsen/logrus" + "gomodules.xyz/jsonpatch/v2" admissionv1 "k8s.io/api/admission/v1" corev1 "k8s.io/api/core/v1" extclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" diff --git a/pkg/fleetautoscalers/controller_test.go b/pkg/fleetautoscalers/controller_test.go index 6f3b6bcd66..2dbb45bf0c 100644 --- a/pkg/fleetautoscalers/controller_test.go +++ b/pkg/fleetautoscalers/controller_test.go @@ -31,10 +31,10 @@ import ( agtesting "agones.dev/agones/pkg/testing" "agones.dev/agones/pkg/util/webhooks" "github.com/heptiolabs/healthcheck" - "github.com/mattbaird/jsonpatch" "github.com/pkg/errors" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "gomodules.xyz/jsonpatch/v2" admissionv1 "k8s.io/api/admission/v1" admregv1 "k8s.io/api/admissionregistration/v1" corev1 "k8s.io/api/core/v1" diff --git a/pkg/fleets/controller.go b/pkg/fleets/controller.go index 37f7f227a3..a13779fe55 100644 --- a/pkg/fleets/controller.go +++ b/pkg/fleets/controller.go @@ -33,9 +33,9 @@ import ( "agones.dev/agones/pkg/util/workerqueue" "github.com/google/go-cmp/cmp" "github.com/heptiolabs/healthcheck" - "github.com/mattbaird/jsonpatch" "github.com/pkg/errors" "github.com/sirupsen/logrus" + "gomodules.xyz/jsonpatch/v2" admissionv1 "k8s.io/api/admission/v1" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" diff --git a/pkg/fleets/controller_test.go b/pkg/fleets/controller_test.go index ae1bfba0da..62563af3e3 100644 --- a/pkg/fleets/controller_test.go +++ b/pkg/fleets/controller_test.go @@ -33,11 +33,11 @@ import ( utilruntime "agones.dev/agones/pkg/util/runtime" "agones.dev/agones/pkg/util/webhooks" "github.com/heptiolabs/healthcheck" - "github.com/mattbaird/jsonpatch" "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "gomodules.xyz/jsonpatch/v2" admissionv1 "k8s.io/api/admission/v1" appsv1 "k8s.io/api/apps/v1" autoscalingv1 "k8s.io/api/autoscaling/v1" diff --git a/pkg/gameservers/controller.go b/pkg/gameservers/controller.go index 6d2cc164a2..0e6ffa9b24 100644 --- a/pkg/gameservers/controller.go +++ b/pkg/gameservers/controller.go @@ -36,9 +36,9 @@ import ( "agones.dev/agones/pkg/util/webhooks" "agones.dev/agones/pkg/util/workerqueue" "github.com/heptiolabs/healthcheck" - "github.com/mattbaird/jsonpatch" "github.com/pkg/errors" "github.com/sirupsen/logrus" + "gomodules.xyz/jsonpatch/v2" admissionv1 "k8s.io/api/admission/v1" corev1 "k8s.io/api/core/v1" extclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" diff --git a/pkg/gameservers/controller_test.go b/pkg/gameservers/controller_test.go index 0f9f016fbe..1e8dfc0dc3 100644 --- a/pkg/gameservers/controller_test.go +++ b/pkg/gameservers/controller_test.go @@ -30,10 +30,10 @@ import ( agtesting "agones.dev/agones/pkg/testing" "agones.dev/agones/pkg/util/webhooks" "github.com/heptiolabs/healthcheck" - "github.com/mattbaird/jsonpatch" "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "gomodules.xyz/jsonpatch/v2" admissionv1 "k8s.io/api/admission/v1" corev1 "k8s.io/api/core/v1" k8serrors "k8s.io/apimachinery/pkg/api/errors" diff --git a/vendor/github.com/mattbaird/jsonpatch/.gitignore b/vendor/github.com/mattbaird/jsonpatch/.gitignore deleted file mode 100644 index 540a8d20be..0000000000 --- a/vendor/github.com/mattbaird/jsonpatch/.gitignore +++ /dev/null @@ -1,24 +0,0 @@ -# Compiled Object files, Static and Dynamic libs (Shared Objects) -*.o -*.a -*.so -.idea -# Folders -_obj -_test - -# Architecture specific extensions/prefixes -*.[568vq] -[568vq].out - -*.cgo1.go -*.cgo2.c -_cgo_defun.c -_cgo_gotypes.go -_cgo_export.* - -_testmain.go - -*.exe -*.test -*.prof diff --git a/vendor/github.com/mattbaird/jsonpatch/README.md b/vendor/github.com/mattbaird/jsonpatch/README.md deleted file mode 100644 index 54f34a2bc4..0000000000 --- a/vendor/github.com/mattbaird/jsonpatch/README.md +++ /dev/null @@ -1,46 +0,0 @@ -# jsonpatch -As per http://jsonpatch.com/ JSON Patch is specified in RFC 6902 from the IETF. - -JSON Patch allows you to generate JSON that describes changes you want to make to a document, so you don't have to send the whole doc. JSON Patch format is supported by HTTP PATCH method, allowing for standards based partial updates via REST APIs. - -```bash -go get github.com/mattbaird/jsonpatch -``` - -I tried some of the other "jsonpatch" go implementations, but none of them could diff two json documents and -generate format like jsonpatch.com specifies. Here's an example of the patch format: - -```json -[ - { "op": "replace", "path": "/baz", "value": "boo" }, - { "op": "add", "path": "/hello", "value": ["world"] }, - { "op": "remove", "path": "/foo"} -] - -``` -The API is super simple -#example -```go -package main - -import ( - "fmt" - "github.com/mattbaird/jsonpatch" -) - -var simpleA = `{"a":100, "b":200, "c":"hello"}` -var simpleB = `{"a":100, "b":200, "c":"goodbye"}` - -func main() { - patch, e := jsonpatch.CreatePatch([]byte(simpleA), []byte(simpleB)) - if e != nil { - fmt.Printf("Error creating JSON patch:%v", e) - return - } - for _, operation := range patch { - fmt.Printf("%s\n", operation.Json()) - } -} -``` - -This code needs more tests, as it's a highly recursive, type-fiddly monster. It's not a lot of code, but it has to deal with a lot of complexity. diff --git a/vendor/github.com/mattbaird/jsonpatch/jsonpatch.go b/vendor/github.com/mattbaird/jsonpatch/jsonpatch.go deleted file mode 100644 index 5b46d9282c..0000000000 --- a/vendor/github.com/mattbaird/jsonpatch/jsonpatch.go +++ /dev/null @@ -1,273 +0,0 @@ -package jsonpatch - -import ( - "bytes" - "encoding/json" - "fmt" - "reflect" - "strings" -) - -var errBadJSONDoc = fmt.Errorf("Invalid JSON Document") - -type JsonPatchOperation struct { - Operation string `json:"op"` - Path string `json:"path"` - Value interface{} `json:"value,omitempty"` -} - -func (j *JsonPatchOperation) Json() string { - b, _ := json.Marshal(j) - return string(b) -} - -func (j *JsonPatchOperation) MarshalJSON() ([]byte, error) { - var b bytes.Buffer - b.WriteString("{") - b.WriteString(fmt.Sprintf(`"op":"%s"`, j.Operation)) - b.WriteString(fmt.Sprintf(`,"path":"%s"`, j.Path)) - // Consider omitting Value for non-nullable operations. - if j.Value != nil || j.Operation == "replace" || j.Operation == "add" { - v, err := json.Marshal(j.Value) - if err != nil { - return nil, err - } - b.WriteString(`,"value":`) - b.Write(v) - } - b.WriteString("}") - return b.Bytes(), nil -} - -type ByPath []JsonPatchOperation - -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(operation, path string, value interface{}) JsonPatchOperation { - return JsonPatchOperation{Operation: operation, 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 JsonPatchOperations -// -// An error will be returned if any of the two documents are invalid. -func CreatePatch(a, b []byte) ([]JsonPatchOperation, error) { - var aI interface{} - var bI interface{} - - err := json.Unmarshal(a, &aI) - if err != nil { - return nil, errBadJSONDoc - } - err = json.Unmarshal(b, &bI) - if err != nil { - return nil, errBadJSONDoc - } - - return handleValues(aI, bI, "", []JsonPatchOperation{}) -} - -// 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 -} - -// 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 '~'. -// TODO decode support: -// var rfc6901Decoder = strings.NewReplacer("~1", "/", "~0", "~") - -var rfc6901Encoder = strings.NewReplacer("~", "~0", "/", "~1") - -func makePath(path string, newPart interface{}) string { - key := rfc6901Encoder.Replace(fmt.Sprintf("%v", newPart)) - if path == "" { - return "/" + key - } - if strings.HasSuffix(path, "/") { - return path + key - } - return path + "/" + key -} - -// diff returns the (recursive) difference between a and b as an array of JsonPatchOperations. -func diff(a, b map[string]interface{}, path string, patch []JsonPatchOperation) ([]JsonPatchOperation, 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 []JsonPatchOperation) ([]JsonPatchOperation, 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, ok := bv.([]interface{}) - if !ok { - // array replaced by non-array - patch = append(patch, NewPatch("replace", p, bv)) - } else if len(at) != len(bt) { - // arrays are not the same length - 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 -} - -// compareArray generates remove and add operations for `av` and `bv`. -func compareArray(av, bv []interface{}, p string) []JsonPatchOperation { - retval := []JsonPatchOperation{} - - // Find elements that need to be removed - processArray(av, bv, func(i int, value interface{}) { - retval = append(retval, NewPatch("remove", makePath(p, i), nil)) - }) - reversed := make([]JsonPatchOperation, len(retval)) - for i := 0; i < len(retval); i++ { - reversed[len(retval)-1-i] = retval[i] - } - retval = reversed - // Find elements that need to be added. - // NOTE we pass in `bv` then `av` so that processArray can find the missing elements. - processArray(bv, av, func(i int, value interface{}) { - retval = append(retval, NewPatch("add", makePath(p, i), value)) - }) - - return retval -} - -// processArray processes `av` and `bv` calling `applyOp` whenever a value is absent. -// It keeps track of which indexes have already had `applyOp` called for and automatically skips them so you can process duplicate objects correctly. -func processArray(av, bv []interface{}, applyOp func(i int, value interface{})) { - foundIndexes := make(map[int]struct{}, len(av)) - reverseFoundIndexes := make(map[int]struct{}, len(av)) - for i, v := range av { - for i2, v2 := range bv { - if _, ok := reverseFoundIndexes[i2]; ok { - // We already found this index. - continue - } - if reflect.DeepEqual(v, v2) { - // Mark this index as found since it matches exactly. - foundIndexes[i] = struct{}{} - reverseFoundIndexes[i2] = struct{}{} - break - } - } - if _, ok := foundIndexes[i]; !ok { - applyOp(i, v) - } - } -} diff --git a/vendor/github.com/mattbaird/jsonpatch/LICENSE b/vendor/gomodules.xyz/jsonpatch/v2/LICENSE similarity index 100% rename from vendor/github.com/mattbaird/jsonpatch/LICENSE rename to vendor/gomodules.xyz/jsonpatch/v2/LICENSE diff --git a/vendor/gomodules.xyz/jsonpatch/v2/jsonpatch.go b/vendor/gomodules.xyz/jsonpatch/v2/jsonpatch.go new file mode 100644 index 0000000000..0d7823b3cd --- /dev/null +++ b/vendor/gomodules.xyz/jsonpatch/v2/jsonpatch.go @@ -0,0 +1,246 @@ +package jsonpatch + +import ( + "bytes" + "encoding/json" + "fmt" + "reflect" + "strings" +) + +var errBadJSONDoc = fmt.Errorf("invalid JSON Document") + +type JsonPatchOperation = Operation + +type Operation struct { + Operation string `json:"op"` + Path string `json:"path"` + Value interface{} `json:"value,omitempty"` +} + +func (j *Operation) Json() string { + b, _ := json.Marshal(j) + return string(b) +} + +func (j *Operation) MarshalJSON() ([]byte, error) { + // Ensure for add and replace we emit `value: null` + if j.Value == nil && (j.Operation == "replace" || j.Operation == "add") { + return json.Marshal(struct { + Operation string `json:"op"` + Path string `json:"path"` + Value interface{} `json:"value"` + }{ + Operation: j.Operation, + Path: j.Path, + }) + } + // otherwise just marshal normally. We cannot literally do json.Marshal(j) as it would be recursively + // calling this function. + return json.Marshal(struct { + Operation string `json:"op"` + Path string `json:"path"` + Value interface{} `json:"value,omitempty"` + }{ + Operation: j.Operation, + Path: j.Path, + Value: j.Value, + }) +} + +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 NewOperation(op, path string, value interface{}) Operation { + return Operation{Operation: 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 JsonPatchOperations +// +// An error will be returned if any of the two documents are invalid. +func CreatePatch(a, b []byte) ([]Operation, error) { + if bytes.Equal(a, b) { + return []Operation{}, nil + } + var aI interface{} + var bI interface{} + err := json.Unmarshal(a, &aI) + if err != nil { + return nil, errBadJSONDoc + } + err = json.Unmarshal(b, &bI) + if err != nil { + return nil, errBadJSONDoc + } + return handleValues(aI, bI, "", []Operation{}) +} + +// 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, ok := bv.(string) + if ok && bt == at { + return true + } + case float64: + bt, ok := bv.(float64) + if ok && bt == at { + return true + } + case bool: + bt, ok := bv.(bool) + if ok && bt == at { + return true + } + case map[string]interface{}: + bt, ok := bv.(map[string]interface{}) + if !ok { + 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 + case []interface{}: + bt, ok := bv.([]interface{}) + if !ok { + return false + } + 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 +} + +// 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 '~'. +// TODO decode support: +// var rfc6901Decoder = strings.NewReplacer("~1", "/", "~0", "~") + +var rfc6901Encoder = strings.NewReplacer("~", "~0", "/", "~1") + +func makePath(path string, newPart interface{}) string { + key := rfc6901Encoder.Replace(fmt.Sprintf("%v", newPart)) + if path == "" { + return "/" + key + } + return path + "/" + key +} + +// diff returns the (recursive) difference between a and b as an array of JsonPatchOperations. +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, NewOperation("add", 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, NewOperation("remove", p, nil)) + } + } + return patch, nil +} + +func handleValues(av, bv interface{}, p string, patch []Operation) ([]Operation, error) { + { + at := reflect.TypeOf(av) + bt := reflect.TypeOf(bv) + if at == nil && bt == nil { + // do nothing + return patch, nil + } else if at != bt { + // If types have changed, replace completely (preserves null in destination) + return append(patch, NewOperation("replace", p, bv)), nil + } + } + + 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, NewOperation("replace", p, bv)) + } + case []interface{}: + bt := bv.([]interface{}) + n := min(len(at), len(bt)) + for i := len(at) - 1; i >= n; i-- { + patch = append(patch, NewOperation("remove", makePath(p, i), nil)) + } + for i := n; i < len(bt); i++ { + patch = append(patch, NewOperation("add", makePath(p, i), bt[i])) + } + for i := 0; i < n; i++ { + var err error + patch, err = handleValues(at[i], bt[i], makePath(p, i), patch) + if err != nil { + return nil, err + } + } + default: + panic(fmt.Sprintf("Unknown type:%T ", av)) + } + return patch, nil +} + +func min(x int, y int) int { + if y < x { + return y + } + return x +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 4a14ea202e..d4c13c836d 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -256,9 +256,6 @@ github.com/magiconair/properties github.com/mailru/easyjson/buffer github.com/mailru/easyjson/jlexer github.com/mailru/easyjson/jwriter -# github.com/mattbaird/jsonpatch v0.0.0-20230413205102-771768614e91 -## explicit -github.com/mattbaird/jsonpatch # github.com/matttproud/golang_protobuf_extensions v1.0.4 ## explicit; go 1.9 github.com/matttproud/golang_protobuf_extensions/pbutil @@ -436,6 +433,9 @@ golang.org/x/tools/internal/gocommand golang.org/x/tools/internal/gopathwalk golang.org/x/tools/internal/imports golang.org/x/tools/internal/typeparams +# gomodules.xyz/jsonpatch/v2 v2.4.0 +## explicit; go 1.20 +gomodules.xyz/jsonpatch/v2 # google.golang.org/api v0.138.0 ## explicit; go 1.19 google.golang.org/api/googleapi