Skip to content

Commit b642560

Browse files
liggittk8s-publishing-bot
authored andcommitted
Restore string JSON encoding of cri-api KeyValue
Kubernetes-commit: b86d94a7cce8113b1e406efacfd3ae70dc7ebf88
1 parent ac5c86c commit b642560

2 files changed

Lines changed: 162 additions & 0 deletions

File tree

pkg/apis/runtime/v1/api_json.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
Copyright The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package v1
18+
19+
import "encoding/json"
20+
21+
// MarshalJSON() preserves pre-1.34 JSON encoding of value as a string (not base64),
22+
// stomping non-utf-8 data with the utf8 replacement character.
23+
func (k *KeyValue) MarshalJSON() ([]byte, error) {
24+
return json.Marshal(stringKeyValue{
25+
Key: k.GetKey(),
26+
Value: string(k.GetValue()),
27+
})
28+
}
29+
30+
// UnmarshalJSON preserves pre-1.34 JSON decoding of value as a string (not base64),
31+
// stomping non-utf-8 data with the utf8 replacement character.
32+
func (k *KeyValue) UnmarshalJSON(data []byte) error {
33+
v := stringKeyValue{}
34+
if err := json.Unmarshal(data, &v); err != nil {
35+
return err
36+
}
37+
k.Key = v.Key
38+
k.Value = []byte(v.Value)
39+
return nil
40+
}
41+
42+
// stringKeyValue matches the structure used to json-encode pre-1.34.
43+
// Non-UTF-8 characters in Value are coerced to the replacement character on encode/decode.
44+
type stringKeyValue struct {
45+
Key string `json:"key,omitempty"`
46+
Value string `json:"value,omitempty"`
47+
}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
/*
2+
Copyright The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package v1
18+
19+
import (
20+
"bytes"
21+
"encoding/json"
22+
"testing"
23+
)
24+
25+
func TestKeyValueCompat(t *testing.T) {
26+
testcases := []struct {
27+
name string
28+
envs []*KeyValue
29+
variantJSON string
30+
expectedJSON string
31+
expectedRoundTripped []*KeyValue
32+
}{
33+
{
34+
name: "null",
35+
envs: nil,
36+
expectedJSON: `null`,
37+
expectedRoundTripped: nil,
38+
},
39+
{
40+
name: "zero-length list",
41+
envs: []*KeyValue{},
42+
expectedJSON: `[]`,
43+
expectedRoundTripped: []*KeyValue{},
44+
},
45+
{
46+
name: "zero-value env",
47+
envs: []*KeyValue{{}},
48+
expectedJSON: `[{}]`,
49+
expectedRoundTripped: []*KeyValue{{}},
50+
},
51+
{
52+
name: "ascii env",
53+
envs: []*KeyValue{{Key: "key", Value: []byte("value")}},
54+
expectedJSON: `[{"key":"key","value":"value"}]`,
55+
expectedRoundTripped: []*KeyValue{{Key: "key", Value: []byte("value")}},
56+
},
57+
{
58+
name: "utf8 env",
59+
envs: []*KeyValue{{Key: "key", Value: []byte("Iñtërnâtiônàlizætiøn🐹")}},
60+
expectedJSON: `[{"key":"key","value":"Iñtërnâtiônàlizætiøn🐹"}]`,
61+
expectedRoundTripped: []*KeyValue{{Key: "key", Value: []byte("Iñtërnâtiônàlizætiøn🐹")}},
62+
},
63+
{
64+
name: "non-utf8 env",
65+
envs: []*KeyValue{{Key: "key", Value: []byte{'A', 0x80, 'Z'}}}, // invalid utf8 continuation byte (0x80)
66+
variantJSON: `[{"key":"key","value":"A` + "\x80" + `Z"}]`, // an alternate JSON input containing the invalid utf8 byte that should coerce to the same result
67+
expectedJSON: `[{"key":"key","value":"A\ufffdZ"}]`, // coerced to utf8 replacement character (\ufffd) on marshal
68+
expectedRoundTripped: []*KeyValue{{Key: "key", Value: []byte("A\ufffdZ")}}, // round-trips to replacement character (\ufffd) on unmarshal
69+
},
70+
}
71+
72+
for _, tc := range testcases {
73+
t.Run(tc.name, func(t *testing.T) {
74+
data, err := json.Marshal(tc.envs)
75+
if err != nil {
76+
t.Fatal(err)
77+
}
78+
79+
if string(data) != tc.expectedJSON {
80+
t.Fatalf("json differed:\nwant: %s\ngot: %s", tc.expectedJSON, string(data))
81+
}
82+
83+
verifyJSON(t, data, tc.expectedRoundTripped)
84+
if len(tc.variantJSON) > 0 {
85+
verifyJSON(t, []byte(tc.variantJSON), tc.expectedRoundTripped)
86+
}
87+
})
88+
}
89+
}
90+
91+
func verifyJSON(t *testing.T, data []byte, expectedRoundTripped []*KeyValue) {
92+
t.Helper()
93+
94+
var rt []*KeyValue
95+
if err := json.Unmarshal(data, &rt); err != nil {
96+
t.Fatal(err)
97+
}
98+
if (rt == nil) != (expectedRoundTripped == nil) {
99+
t.Fatalf("expected value (%#v) does not match actual round-tripped value (%#v) for nil", expectedRoundTripped, rt)
100+
}
101+
if rt == nil {
102+
return
103+
}
104+
if len(rt) != len(expectedRoundTripped) {
105+
t.Fatalf("length of expected value (%#v) does not match length of actual round-tripped value (%#v)", expectedRoundTripped, rt)
106+
}
107+
for i := range expectedRoundTripped {
108+
if want, got := expectedRoundTripped[i].Key, rt[i].Key; want != got {
109+
t.Fatalf("item[%d].key does not match: %s vs %s", i, want, got)
110+
}
111+
if want, got := expectedRoundTripped[i].Value, rt[i].Value; !bytes.Equal(want, got) {
112+
t.Fatalf("item[%d].value does not match: %v vs %v", i, want, got)
113+
}
114+
}
115+
}

0 commit comments

Comments
 (0)