diff --git a/go.mod b/go.mod index 34f1f155d56..4e0e55439b6 100644 --- a/go.mod +++ b/go.mod @@ -498,7 +498,6 @@ require ( github.com/stoewer/go-strcase v1.3.0 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.1 // indirect - github.com/tidwall/sjson v1.2.5 github.com/yosida95/uritemplate/v3 v3.0.2 // indirect golang.org/x/exp/typeparams v0.0.0-20250210185358-939b2ce775ac // indirect google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 // indirect diff --git a/go.sum b/go.sum index 51aba643b49..288f97387dc 100644 --- a/go.sum +++ b/go.sum @@ -1027,7 +1027,6 @@ github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3 h1:f+jULpR github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3/go.mod h1:ON8b8w4BN/kE1EOhwT0o+d62W65a6aPw1nouo9LMgyY= github.com/tetafro/godot v1.5.0 h1:aNwfVI4I3+gdxjMgYPus9eHmoBeJIbnajOyqZYStzuw= github.com/tetafro/godot v1.5.0/go.mod h1:2oVxTBSftRTh4+MVfUaUXR6bn2GDXCaMcOG4Dk3rfio= -github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= @@ -1036,8 +1035,6 @@ github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhV github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= -github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= -github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= github.com/timakin/bodyclose v0.0.0-20241017074812-ed6a65f985e3 h1:y4mJRFlM6fUyPhoXuFg/Yu02fg/nIPFMOY8tOqppoFg= github.com/timakin/bodyclose v0.0.0-20241017074812-ed6a65f985e3/go.mod h1:mkjARE7Yr8qU23YcGMSALbIxTQ9r9QBVahQOBRfU460= github.com/timonwong/loggercheck v0.10.1 h1:uVZYClxQFpw55eh+PIoqM7uAOHMrhVcDoWDery9R8Lg= diff --git a/pkg/installmanager/installmanager.go b/pkg/installmanager/installmanager.go index dc37cc017e8..4d80ce7221b 100644 --- a/pkg/installmanager/installmanager.go +++ b/pkg/installmanager/installmanager.go @@ -21,7 +21,6 @@ import ( log "github.com/sirupsen/logrus" "github.com/spf13/cobra" "github.com/tidwall/gjson" - "github.com/tidwall/sjson" "sigs.k8s.io/yaml" corev1 "k8s.io/api/core/v1" @@ -1032,16 +1031,13 @@ func patchLabelMachineSetManifest(manifestBytes []byte, poolsByType map[string]* } logger.Infof("Matching machineset %s of type %s with pool %s", msetName, msetType, poolName) - manifestBytesJson, err = sjson.SetBytes(manifestBytesJson, `metadata.labels.hive\.openshift\.io/managed`, "true") - if err != nil { - logger.WithError(err).Error("error applying hive managed label patch") - return nil, err - } - - // Add a pool-name label to the MachineSet containing the pool.Spec.Name which is equivalent to the value of the machine.openshift.io/cluster-api-machineset label - manifestBytesJson, err = sjson.SetBytes(manifestBytesJson, `metadata.labels.hive\.openshift\.io/machine-pool`, msetType) + // Set hive labels on the MachineSet metadata + manifestBytesJson, err = setJSONLabels(manifestBytesJson, map[string]string{ + "hive.openshift.io/managed": "true", + "hive.openshift.io/machine-pool": msetType, + }) if err != nil { - logger.WithError(err).Error("error applying machine pool label patch") + logger.WithError(err).Error("error setting labels on manifest") return nil, err } @@ -1059,6 +1055,28 @@ func patchLabelMachineSetManifest(manifestBytes []byte, poolsByType map[string]* } +// setJSONLabels sets the given labels on a JSON-encoded Kubernetes resource's metadata. +func setJSONLabels(jsonData []byte, newLabels map[string]string) ([]byte, error) { + var manifest map[string]interface{} + if err := json.Unmarshal(jsonData, &manifest); err != nil { + return nil, fmt.Errorf("error unmarshalling manifest JSON: %w", err) + } + metadata, _ := manifest["metadata"].(map[string]interface{}) + if metadata == nil { + metadata = map[string]interface{}{} + manifest["metadata"] = metadata + } + labels, _ := metadata["labels"].(map[string]interface{}) + if labels == nil { + labels = map[string]interface{}{} + metadata["labels"] = labels + } + for k, v := range newLabels { + labels[k] = v + } + return json.Marshal(manifest) +} + // patchWorkerMachineSetManifest accepts a yaml manifest as []byte and patches the manifest to include an additional // security group filter if that manifest is the definition of a worker MachineSet. The security group is obtained from // an annotation (constants.ExtraWorkerSecurityGroupAnnotation) on the provided MachinePool and is the value of the diff --git a/pkg/installmanager/installmanager_test.go b/pkg/installmanager/installmanager_test.go index e8837f88a75..2f44bd7eb87 100644 --- a/pkg/installmanager/installmanager_test.go +++ b/pkg/installmanager/installmanager_test.go @@ -1461,6 +1461,80 @@ data: } } +func Test_setJSONLabels(t *testing.T) { + cases := []struct { + name string + input string + labels map[string]string + wantErr bool + expectLabels map[string]string + expectName string + }{ + { + name: "add labels to object with no metadata", + input: `{"apiVersion":"machine.openshift.io/v1beta1","kind":"MachineSet"}`, + labels: map[string]string{ + "hive.openshift.io/managed": "true", + }, + expectLabels: map[string]string{ + "hive.openshift.io/managed": "true", + }, + }, + { + name: "add labels preserving existing metadata and labels", + input: `{"apiVersion":"machine.openshift.io/v1beta1","kind":"MachineSet","metadata":{"name":"test-ms","labels":{"existing":"label"}}}`, + labels: map[string]string{ + "hive.openshift.io/managed": "true", + "hive.openshift.io/machine-pool": "worker", + }, + expectName: "test-ms", + expectLabels: map[string]string{ + "existing": "label", + "hive.openshift.io/managed": "true", + "hive.openshift.io/machine-pool": "worker", + }, + }, + { + name: "add labels to object with metadata but no labels", + input: `{"apiVersion":"machine.openshift.io/v1beta1","kind":"MachineSet","metadata":{"name":"test-ms"}}`, + labels: map[string]string{ + "foo": "bar", + }, + expectName: "test-ms", + expectLabels: map[string]string{ + "foo": "bar", + }, + }, + { + name: "invalid json", + input: `not json`, + labels: map[string]string{"a": "b"}, + wantErr: true, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + output, err := setJSONLabels([]byte(tc.input), tc.labels) + if tc.wantErr { + assert.Error(t, err) + return + } + require.NoError(t, err) + + var ms machineapi.MachineSet + require.NoError(t, json.Unmarshal(output, &ms), "output should unmarshal into a MachineSet") + + if tc.expectName != "" { + assert.Equal(t, tc.expectName, ms.Name) + } + for k, v := range tc.expectLabels { + assert.Equal(t, v, ms.Labels[k], "label %s", k) + } + }) + } +} + func Test_scrubMetadataJSON(t *testing.T) { cases := []struct { initial string diff --git a/vendor/github.com/tidwall/sjson/LICENSE b/vendor/github.com/tidwall/sjson/LICENSE deleted file mode 100644 index 89593c7c848..00000000000 --- a/vendor/github.com/tidwall/sjson/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2016 Josh Baker - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - diff --git a/vendor/github.com/tidwall/sjson/README.md b/vendor/github.com/tidwall/sjson/README.md deleted file mode 100644 index 4598424efa0..00000000000 --- a/vendor/github.com/tidwall/sjson/README.md +++ /dev/null @@ -1,278 +0,0 @@ -

-SJSON -
-GoDoc -

- -

set a json value quickly

- -SJSON is a Go package that provides a [very fast](#performance) and simple way to set a value in a json document. -For quickly retrieving json values check out [GJSON](https://github.com/tidwall/gjson). - -For a command line interface check out [JJ](https://github.com/tidwall/jj). - -Getting Started -=============== - -Installing ----------- - -To start using SJSON, install Go and run `go get`: - -```sh -$ go get -u github.com/tidwall/sjson -``` - -This will retrieve the library. - -Set a value ------------ -Set sets the value for the specified path. -A path is in dot syntax, such as "name.last" or "age". -This function expects that the json is well-formed and validated. -Invalid json will not panic, but it may return back unexpected results. -Invalid paths may return an error. - -```go -package main - -import "github.com/tidwall/sjson" - -const json = `{"name":{"first":"Janet","last":"Prichard"},"age":47}` - -func main() { - value, _ := sjson.Set(json, "name.last", "Anderson") - println(value) -} -``` - -This will print: - -```json -{"name":{"first":"Janet","last":"Anderson"},"age":47} -``` - -Path syntax ------------ - -A path is a series of keys separated by a dot. -The dot and colon characters can be escaped with ``\``. - -```json -{ - "name": {"first": "Tom", "last": "Anderson"}, - "age":37, - "children": ["Sara","Alex","Jack"], - "fav.movie": "Deer Hunter", - "friends": [ - {"first": "James", "last": "Murphy"}, - {"first": "Roger", "last": "Craig"} - ] -} -``` -``` -"name.last" >> "Anderson" -"age" >> 37 -"children.1" >> "Alex" -"friends.1.last" >> "Craig" -``` - -The `-1` key can be used to append a value to an existing array: - -``` -"children.-1" >> appends a new value to the end of the children array -``` - -Normally number keys are used to modify arrays, but it's possible to force a numeric object key by using the colon character: - -```json -{ - "users":{ - "2313":{"name":"Sara"}, - "7839":{"name":"Andy"} - } -} -``` - -A colon path would look like: - -``` -"users.:2313.name" >> "Sara" -``` - -Supported types ---------------- - -Pretty much any type is supported: - -```go -sjson.Set(`{"key":true}`, "key", nil) -sjson.Set(`{"key":true}`, "key", false) -sjson.Set(`{"key":true}`, "key", 1) -sjson.Set(`{"key":true}`, "key", 10.5) -sjson.Set(`{"key":true}`, "key", "hello") -sjson.Set(`{"key":true}`, "key", []string{"hello", "world"}) -sjson.Set(`{"key":true}`, "key", map[string]interface{}{"hello":"world"}) -``` - -When a type is not recognized, SJSON will fallback to the `encoding/json` Marshaller. - - -Examples --------- - -Set a value from empty document: -```go -value, _ := sjson.Set("", "name", "Tom") -println(value) - -// Output: -// {"name":"Tom"} -``` - -Set a nested value from empty document: -```go -value, _ := sjson.Set("", "name.last", "Anderson") -println(value) - -// Output: -// {"name":{"last":"Anderson"}} -``` - -Set a new value: -```go -value, _ := sjson.Set(`{"name":{"last":"Anderson"}}`, "name.first", "Sara") -println(value) - -// Output: -// {"name":{"first":"Sara","last":"Anderson"}} -``` - -Update an existing value: -```go -value, _ := sjson.Set(`{"name":{"last":"Anderson"}}`, "name.last", "Smith") -println(value) - -// Output: -// {"name":{"last":"Smith"}} -``` - -Set a new array value: -```go -value, _ := sjson.Set(`{"friends":["Andy","Carol"]}`, "friends.2", "Sara") -println(value) - -// Output: -// {"friends":["Andy","Carol","Sara"] -``` - -Append an array value by using the `-1` key in a path: -```go -value, _ := sjson.Set(`{"friends":["Andy","Carol"]}`, "friends.-1", "Sara") -println(value) - -// Output: -// {"friends":["Andy","Carol","Sara"] -``` - -Append an array value that is past the end: -```go -value, _ := sjson.Set(`{"friends":["Andy","Carol"]}`, "friends.4", "Sara") -println(value) - -// Output: -// {"friends":["Andy","Carol",null,null,"Sara"] -``` - -Delete a value: -```go -value, _ := sjson.Delete(`{"name":{"first":"Sara","last":"Anderson"}}`, "name.first") -println(value) - -// Output: -// {"name":{"last":"Anderson"}} -``` - -Delete an array value: -```go -value, _ := sjson.Delete(`{"friends":["Andy","Carol"]}`, "friends.1") -println(value) - -// Output: -// {"friends":["Andy"]} -``` - -Delete the last array value: -```go -value, _ := sjson.Delete(`{"friends":["Andy","Carol"]}`, "friends.-1") -println(value) - -// Output: -// {"friends":["Andy"]} -``` - -## Performance - -Benchmarks of SJSON alongside [encoding/json](https://golang.org/pkg/encoding/json/), -[ffjson](https://github.com/pquerna/ffjson), -[EasyJSON](https://github.com/mailru/easyjson), -and [Gabs](https://github.com/Jeffail/gabs) - -``` -Benchmark_SJSON-8 3000000 805 ns/op 1077 B/op 3 allocs/op -Benchmark_SJSON_ReplaceInPlace-8 3000000 449 ns/op 0 B/op 0 allocs/op -Benchmark_JSON_Map-8 300000 21236 ns/op 6392 B/op 150 allocs/op -Benchmark_JSON_Struct-8 300000 14691 ns/op 1789 B/op 24 allocs/op -Benchmark_Gabs-8 300000 21311 ns/op 6752 B/op 150 allocs/op -Benchmark_FFJSON-8 300000 17673 ns/op 3589 B/op 47 allocs/op -Benchmark_EasyJSON-8 1500000 3119 ns/op 1061 B/op 13 allocs/op -``` - -JSON document used: - -```json -{ - "widget": { - "debug": "on", - "window": { - "title": "Sample Konfabulator Widget", - "name": "main_window", - "width": 500, - "height": 500 - }, - "image": { - "src": "Images/Sun.png", - "hOffset": 250, - "vOffset": 250, - "alignment": "center" - }, - "text": { - "data": "Click Here", - "size": 36, - "style": "bold", - "vOffset": 100, - "alignment": "center", - "onMouseUp": "sun1.opacity = (sun1.opacity / 100) * 90;" - } - } -} -``` - -Each operation was rotated though one of the following search paths: - -``` -widget.window.name -widget.image.hOffset -widget.text.onMouseUp -``` - -*These benchmarks were run on a MacBook Pro 15" 2.8 GHz Intel Core i7 using Go 1.7 and can be be found [here](https://github.com/tidwall/sjson-benchmarks)*. - -## Contact -Josh Baker [@tidwall](http://twitter.com/tidwall) - -## License - -SJSON source code is available under the MIT [License](/LICENSE). diff --git a/vendor/github.com/tidwall/sjson/logo.png b/vendor/github.com/tidwall/sjson/logo.png deleted file mode 100644 index b5aa257b6b5..00000000000 Binary files a/vendor/github.com/tidwall/sjson/logo.png and /dev/null differ diff --git a/vendor/github.com/tidwall/sjson/sjson.go b/vendor/github.com/tidwall/sjson/sjson.go deleted file mode 100644 index a55eef3fdb8..00000000000 --- a/vendor/github.com/tidwall/sjson/sjson.go +++ /dev/null @@ -1,737 +0,0 @@ -// Package sjson provides setting json values. -package sjson - -import ( - jsongo "encoding/json" - "sort" - "strconv" - "unsafe" - - "github.com/tidwall/gjson" -) - -type errorType struct { - msg string -} - -func (err *errorType) Error() string { - return err.msg -} - -// Options represents additional options for the Set and Delete functions. -type Options struct { - // Optimistic is a hint that the value likely exists which - // allows for the sjson to perform a fast-track search and replace. - Optimistic bool - // ReplaceInPlace is a hint to replace the input json rather than - // allocate a new json byte slice. When this field is specified - // the input json will not longer be valid and it should not be used - // In the case when the destination slice doesn't have enough free - // bytes to replace the data in place, a new bytes slice will be - // created under the hood. - // The Optimistic flag must be set to true and the input must be a - // byte slice in order to use this field. - ReplaceInPlace bool -} - -type pathResult struct { - part string // current key part - gpart string // gjson get part - path string // remaining path - force bool // force a string key - more bool // there is more path to parse -} - -func isSimpleChar(ch byte) bool { - switch ch { - case '|', '#', '@', '*', '?': - return false - default: - return true - } -} - -func parsePath(path string) (res pathResult, simple bool) { - var r pathResult - if len(path) > 0 && path[0] == ':' { - r.force = true - path = path[1:] - } - for i := 0; i < len(path); i++ { - if path[i] == '.' { - r.part = path[:i] - r.gpart = path[:i] - r.path = path[i+1:] - r.more = true - return r, true - } - if !isSimpleChar(path[i]) { - return r, false - } - if path[i] == '\\' { - // go into escape mode. this is a slower path that - // strips off the escape character from the part. - epart := []byte(path[:i]) - gpart := []byte(path[:i+1]) - i++ - if i < len(path) { - epart = append(epart, path[i]) - gpart = append(gpart, path[i]) - i++ - for ; i < len(path); i++ { - if path[i] == '\\' { - gpart = append(gpart, '\\') - i++ - if i < len(path) { - epart = append(epart, path[i]) - gpart = append(gpart, path[i]) - } - continue - } else if path[i] == '.' { - r.part = string(epart) - r.gpart = string(gpart) - r.path = path[i+1:] - r.more = true - return r, true - } else if !isSimpleChar(path[i]) { - return r, false - } - epart = append(epart, path[i]) - gpart = append(gpart, path[i]) - } - } - // append the last part - r.part = string(epart) - r.gpart = string(gpart) - return r, true - } - } - r.part = path - r.gpart = path - return r, true -} - -func mustMarshalString(s string) bool { - for i := 0; i < len(s); i++ { - if s[i] < ' ' || s[i] > 0x7f || s[i] == '"' || s[i] == '\\' { - return true - } - } - return false -} - -// appendStringify makes a json string and appends to buf. -func appendStringify(buf []byte, s string) []byte { - if mustMarshalString(s) { - b, _ := jsongo.Marshal(s) - return append(buf, b...) - } - buf = append(buf, '"') - buf = append(buf, s...) - buf = append(buf, '"') - return buf -} - -// appendBuild builds a json block from a json path. -func appendBuild(buf []byte, array bool, paths []pathResult, raw string, - stringify bool) []byte { - if !array { - buf = appendStringify(buf, paths[0].part) - buf = append(buf, ':') - } - if len(paths) > 1 { - n, numeric := atoui(paths[1]) - if numeric || (!paths[1].force && paths[1].part == "-1") { - buf = append(buf, '[') - buf = appendRepeat(buf, "null,", n) - buf = appendBuild(buf, true, paths[1:], raw, stringify) - buf = append(buf, ']') - } else { - buf = append(buf, '{') - buf = appendBuild(buf, false, paths[1:], raw, stringify) - buf = append(buf, '}') - } - } else { - if stringify { - buf = appendStringify(buf, raw) - } else { - buf = append(buf, raw...) - } - } - return buf -} - -// atoui does a rip conversion of string -> unigned int. -func atoui(r pathResult) (n int, ok bool) { - if r.force { - return 0, false - } - for i := 0; i < len(r.part); i++ { - if r.part[i] < '0' || r.part[i] > '9' { - return 0, false - } - n = n*10 + int(r.part[i]-'0') - } - return n, true -} - -// appendRepeat repeats string "n" times and appends to buf. -func appendRepeat(buf []byte, s string, n int) []byte { - for i := 0; i < n; i++ { - buf = append(buf, s...) - } - return buf -} - -// trim does a rip trim -func trim(s string) string { - for len(s) > 0 { - if s[0] <= ' ' { - s = s[1:] - continue - } - break - } - for len(s) > 0 { - if s[len(s)-1] <= ' ' { - s = s[:len(s)-1] - continue - } - break - } - return s -} - -// deleteTailItem deletes the previous key or comma. -func deleteTailItem(buf []byte) ([]byte, bool) { -loop: - for i := len(buf) - 1; i >= 0; i-- { - // look for either a ',',':','[' - switch buf[i] { - case '[': - return buf, true - case ',': - return buf[:i], false - case ':': - // delete tail string - i-- - for ; i >= 0; i-- { - if buf[i] == '"' { - i-- - for ; i >= 0; i-- { - if buf[i] == '"' { - i-- - if i >= 0 && buf[i] == '\\' { - i-- - continue - } - for ; i >= 0; i-- { - // look for either a ',','{' - switch buf[i] { - case '{': - return buf[:i+1], true - case ',': - return buf[:i], false - } - } - } - } - break - } - } - break loop - } - } - return buf, false -} - -var errNoChange = &errorType{"no change"} - -func appendRawPaths(buf []byte, jstr string, paths []pathResult, raw string, - stringify, del bool) ([]byte, error) { - var err error - var res gjson.Result - var found bool - if del { - if paths[0].part == "-1" && !paths[0].force { - res = gjson.Get(jstr, "#") - if res.Int() > 0 { - res = gjson.Get(jstr, strconv.FormatInt(int64(res.Int()-1), 10)) - found = true - } - } - } - if !found { - res = gjson.Get(jstr, paths[0].gpart) - } - if res.Index > 0 { - if len(paths) > 1 { - buf = append(buf, jstr[:res.Index]...) - buf, err = appendRawPaths(buf, res.Raw, paths[1:], raw, - stringify, del) - if err != nil { - return nil, err - } - buf = append(buf, jstr[res.Index+len(res.Raw):]...) - return buf, nil - } - buf = append(buf, jstr[:res.Index]...) - var exidx int // additional forward stripping - if del { - var delNextComma bool - buf, delNextComma = deleteTailItem(buf) - if delNextComma { - i, j := res.Index+len(res.Raw), 0 - for ; i < len(jstr); i, j = i+1, j+1 { - if jstr[i] <= ' ' { - continue - } - if jstr[i] == ',' { - exidx = j + 1 - } - break - } - } - } else { - if stringify { - buf = appendStringify(buf, raw) - } else { - buf = append(buf, raw...) - } - } - buf = append(buf, jstr[res.Index+len(res.Raw)+exidx:]...) - return buf, nil - } - if del { - return nil, errNoChange - } - n, numeric := atoui(paths[0]) - isempty := true - for i := 0; i < len(jstr); i++ { - if jstr[i] > ' ' { - isempty = false - break - } - } - if isempty { - if numeric { - jstr = "[]" - } else { - jstr = "{}" - } - } - jsres := gjson.Parse(jstr) - if jsres.Type != gjson.JSON { - if numeric { - jstr = "[]" - } else { - jstr = "{}" - } - jsres = gjson.Parse(jstr) - } - var comma bool - for i := 1; i < len(jsres.Raw); i++ { - if jsres.Raw[i] <= ' ' { - continue - } - if jsres.Raw[i] == '}' || jsres.Raw[i] == ']' { - break - } - comma = true - break - } - switch jsres.Raw[0] { - default: - return nil, &errorType{"json must be an object or array"} - case '{': - end := len(jsres.Raw) - 1 - for ; end > 0; end-- { - if jsres.Raw[end] == '}' { - break - } - } - buf = append(buf, jsres.Raw[:end]...) - if comma { - buf = append(buf, ',') - } - buf = appendBuild(buf, false, paths, raw, stringify) - buf = append(buf, '}') - return buf, nil - case '[': - var appendit bool - if !numeric { - if paths[0].part == "-1" && !paths[0].force { - appendit = true - } else { - return nil, &errorType{ - "cannot set array element for non-numeric key '" + - paths[0].part + "'"} - } - } - if appendit { - njson := trim(jsres.Raw) - if njson[len(njson)-1] == ']' { - njson = njson[:len(njson)-1] - } - buf = append(buf, njson...) - if comma { - buf = append(buf, ',') - } - - buf = appendBuild(buf, true, paths, raw, stringify) - buf = append(buf, ']') - return buf, nil - } - buf = append(buf, '[') - ress := jsres.Array() - for i := 0; i < len(ress); i++ { - if i > 0 { - buf = append(buf, ',') - } - buf = append(buf, ress[i].Raw...) - } - if len(ress) == 0 { - buf = appendRepeat(buf, "null,", n-len(ress)) - } else { - buf = appendRepeat(buf, ",null", n-len(ress)) - if comma { - buf = append(buf, ',') - } - } - buf = appendBuild(buf, true, paths, raw, stringify) - buf = append(buf, ']') - return buf, nil - } -} - -func isOptimisticPath(path string) bool { - for i := 0; i < len(path); i++ { - if path[i] < '.' || path[i] > 'z' { - return false - } - if path[i] > '9' && path[i] < 'A' { - return false - } - if path[i] > 'z' { - return false - } - } - return true -} - -// Set sets a json value for the specified path. -// A path is in dot syntax, such as "name.last" or "age". -// This function expects that the json is well-formed, and does not validate. -// Invalid json will not panic, but it may return back unexpected results. -// An error is returned if the path is not valid. -// -// A path is a series of keys separated by a dot. -// -// { -// "name": {"first": "Tom", "last": "Anderson"}, -// "age":37, -// "children": ["Sara","Alex","Jack"], -// "friends": [ -// {"first": "James", "last": "Murphy"}, -// {"first": "Roger", "last": "Craig"} -// ] -// } -// "name.last" >> "Anderson" -// "age" >> 37 -// "children.1" >> "Alex" -// -func Set(json, path string, value interface{}) (string, error) { - return SetOptions(json, path, value, nil) -} - -// SetBytes sets a json value for the specified path. -// If working with bytes, this method preferred over -// Set(string(data), path, value) -func SetBytes(json []byte, path string, value interface{}) ([]byte, error) { - return SetBytesOptions(json, path, value, nil) -} - -// SetRaw sets a raw json value for the specified path. -// This function works the same as Set except that the value is set as a -// raw block of json. This allows for setting premarshalled json objects. -func SetRaw(json, path, value string) (string, error) { - return SetRawOptions(json, path, value, nil) -} - -// SetRawOptions sets a raw json value for the specified path with options. -// This furnction works the same as SetOptions except that the value is set -// as a raw block of json. This allows for setting premarshalled json objects. -func SetRawOptions(json, path, value string, opts *Options) (string, error) { - var optimistic bool - if opts != nil { - optimistic = opts.Optimistic - } - res, err := set(json, path, value, false, false, optimistic, false) - if err == errNoChange { - return json, nil - } - return string(res), err -} - -// SetRawBytes sets a raw json value for the specified path. -// If working with bytes, this method preferred over -// SetRaw(string(data), path, value) -func SetRawBytes(json []byte, path string, value []byte) ([]byte, error) { - return SetRawBytesOptions(json, path, value, nil) -} - -type dtype struct{} - -// Delete deletes a value from json for the specified path. -func Delete(json, path string) (string, error) { - return Set(json, path, dtype{}) -} - -// DeleteBytes deletes a value from json for the specified path. -func DeleteBytes(json []byte, path string) ([]byte, error) { - return SetBytes(json, path, dtype{}) -} - -type stringHeader struct { - data unsafe.Pointer - len int -} - -type sliceHeader struct { - data unsafe.Pointer - len int - cap int -} - -func set(jstr, path, raw string, - stringify, del, optimistic, inplace bool) ([]byte, error) { - if path == "" { - return []byte(jstr), &errorType{"path cannot be empty"} - } - if !del && optimistic && isOptimisticPath(path) { - res := gjson.Get(jstr, path) - if res.Exists() && res.Index > 0 { - sz := len(jstr) - len(res.Raw) + len(raw) - if stringify { - sz += 2 - } - if inplace && sz <= len(jstr) { - if !stringify || !mustMarshalString(raw) { - jsonh := *(*stringHeader)(unsafe.Pointer(&jstr)) - jsonbh := sliceHeader{ - data: jsonh.data, len: jsonh.len, cap: jsonh.len} - jbytes := *(*[]byte)(unsafe.Pointer(&jsonbh)) - if stringify { - jbytes[res.Index] = '"' - copy(jbytes[res.Index+1:], []byte(raw)) - jbytes[res.Index+1+len(raw)] = '"' - copy(jbytes[res.Index+1+len(raw)+1:], - jbytes[res.Index+len(res.Raw):]) - } else { - copy(jbytes[res.Index:], []byte(raw)) - copy(jbytes[res.Index+len(raw):], - jbytes[res.Index+len(res.Raw):]) - } - return jbytes[:sz], nil - } - return []byte(jstr), nil - } - buf := make([]byte, 0, sz) - buf = append(buf, jstr[:res.Index]...) - if stringify { - buf = appendStringify(buf, raw) - } else { - buf = append(buf, raw...) - } - buf = append(buf, jstr[res.Index+len(res.Raw):]...) - return buf, nil - } - } - var paths []pathResult - r, simple := parsePath(path) - if simple { - paths = append(paths, r) - for r.more { - r, simple = parsePath(r.path) - if !simple { - break - } - paths = append(paths, r) - } - } - if !simple { - if del { - return []byte(jstr), - &errorType{"cannot delete value from a complex path"} - } - return setComplexPath(jstr, path, raw, stringify) - } - njson, err := appendRawPaths(nil, jstr, paths, raw, stringify, del) - if err != nil { - return []byte(jstr), err - } - return njson, nil -} - -func setComplexPath(jstr, path, raw string, stringify bool) ([]byte, error) { - res := gjson.Get(jstr, path) - if !res.Exists() || !(res.Index != 0 || len(res.Indexes) != 0) { - return []byte(jstr), errNoChange - } - if res.Index != 0 { - njson := []byte(jstr[:res.Index]) - if stringify { - njson = appendStringify(njson, raw) - } else { - njson = append(njson, raw...) - } - njson = append(njson, jstr[res.Index+len(res.Raw):]...) - jstr = string(njson) - } - if len(res.Indexes) > 0 { - type val struct { - index int - res gjson.Result - } - vals := make([]val, 0, len(res.Indexes)) - res.ForEach(func(_, vres gjson.Result) bool { - vals = append(vals, val{res: vres}) - return true - }) - if len(res.Indexes) != len(vals) { - return []byte(jstr), errNoChange - } - for i := 0; i < len(res.Indexes); i++ { - vals[i].index = res.Indexes[i] - } - sort.SliceStable(vals, func(i, j int) bool { - return vals[i].index > vals[j].index - }) - for _, val := range vals { - vres := val.res - index := val.index - njson := []byte(jstr[:index]) - if stringify { - njson = appendStringify(njson, raw) - } else { - njson = append(njson, raw...) - } - njson = append(njson, jstr[index+len(vres.Raw):]...) - jstr = string(njson) - } - } - return []byte(jstr), nil -} - -// SetOptions sets a json value for the specified path with options. -// A path is in dot syntax, such as "name.last" or "age". -// This function expects that the json is well-formed, and does not validate. -// Invalid json will not panic, but it may return back unexpected results. -// An error is returned if the path is not valid. -func SetOptions(json, path string, value interface{}, - opts *Options) (string, error) { - if opts != nil { - if opts.ReplaceInPlace { - // it's not safe to replace bytes in-place for strings - // copy the Options and set options.ReplaceInPlace to false. - nopts := *opts - opts = &nopts - opts.ReplaceInPlace = false - } - } - jsonh := *(*stringHeader)(unsafe.Pointer(&json)) - jsonbh := sliceHeader{data: jsonh.data, len: jsonh.len, cap: jsonh.len} - jsonb := *(*[]byte)(unsafe.Pointer(&jsonbh)) - res, err := SetBytesOptions(jsonb, path, value, opts) - return string(res), err -} - -// SetBytesOptions sets a json value for the specified path with options. -// If working with bytes, this method preferred over -// SetOptions(string(data), path, value) -func SetBytesOptions(json []byte, path string, value interface{}, - opts *Options) ([]byte, error) { - var optimistic, inplace bool - if opts != nil { - optimistic = opts.Optimistic - inplace = opts.ReplaceInPlace - } - jstr := *(*string)(unsafe.Pointer(&json)) - var res []byte - var err error - switch v := value.(type) { - default: - b, merr := jsongo.Marshal(value) - if merr != nil { - return nil, merr - } - raw := *(*string)(unsafe.Pointer(&b)) - res, err = set(jstr, path, raw, false, false, optimistic, inplace) - case dtype: - res, err = set(jstr, path, "", false, true, optimistic, inplace) - case string: - res, err = set(jstr, path, v, true, false, optimistic, inplace) - case []byte: - raw := *(*string)(unsafe.Pointer(&v)) - res, err = set(jstr, path, raw, true, false, optimistic, inplace) - case bool: - if v { - res, err = set(jstr, path, "true", false, false, optimistic, inplace) - } else { - res, err = set(jstr, path, "false", false, false, optimistic, inplace) - } - case int8: - res, err = set(jstr, path, strconv.FormatInt(int64(v), 10), - false, false, optimistic, inplace) - case int16: - res, err = set(jstr, path, strconv.FormatInt(int64(v), 10), - false, false, optimistic, inplace) - case int32: - res, err = set(jstr, path, strconv.FormatInt(int64(v), 10), - false, false, optimistic, inplace) - case int64: - res, err = set(jstr, path, strconv.FormatInt(int64(v), 10), - false, false, optimistic, inplace) - case uint8: - res, err = set(jstr, path, strconv.FormatUint(uint64(v), 10), - false, false, optimistic, inplace) - case uint16: - res, err = set(jstr, path, strconv.FormatUint(uint64(v), 10), - false, false, optimistic, inplace) - case uint32: - res, err = set(jstr, path, strconv.FormatUint(uint64(v), 10), - false, false, optimistic, inplace) - case uint64: - res, err = set(jstr, path, strconv.FormatUint(uint64(v), 10), - false, false, optimistic, inplace) - case float32: - res, err = set(jstr, path, strconv.FormatFloat(float64(v), 'f', -1, 64), - false, false, optimistic, inplace) - case float64: - res, err = set(jstr, path, strconv.FormatFloat(float64(v), 'f', -1, 64), - false, false, optimistic, inplace) - } - if err == errNoChange { - return json, nil - } - return res, err -} - -// SetRawBytesOptions sets a raw json value for the specified path with options. -// If working with bytes, this method preferred over -// SetRawOptions(string(data), path, value, opts) -func SetRawBytesOptions(json []byte, path string, value []byte, - opts *Options) ([]byte, error) { - jstr := *(*string)(unsafe.Pointer(&json)) - vstr := *(*string)(unsafe.Pointer(&value)) - var optimistic, inplace bool - if opts != nil { - optimistic = opts.Optimistic - inplace = opts.ReplaceInPlace - } - res, err := set(jstr, path, vstr, false, false, optimistic, inplace) - if err == errNoChange { - return json, nil - } - return res, err -} diff --git a/vendor/modules.txt b/vendor/modules.txt index 7ddac52c7bd..d74c255b221 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -2373,9 +2373,6 @@ github.com/tidwall/match # github.com/tidwall/pretty v1.2.1 ## explicit; go 1.16 github.com/tidwall/pretty -# github.com/tidwall/sjson v1.2.5 -## explicit; go 1.14 -github.com/tidwall/sjson # github.com/timakin/bodyclose v0.0.0-20241017074812-ed6a65f985e3 ## explicit; go 1.12 github.com/timakin/bodyclose/passes/bodyclose