Skip to content

Commit 0eb2853

Browse files
authored
Merge pull request #90 from lavalamp/optimize
set serialization
2 parents e94e05b + b48d312 commit 0eb2853

File tree

113 files changed

+12427
-8
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

113 files changed

+12427
-8
lines changed

fieldpath/serialize-pe.go

+143
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
/*
2+
Copyright 2018 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 fieldpath
18+
19+
import (
20+
"errors"
21+
"fmt"
22+
"strconv"
23+
"strings"
24+
25+
jsoniter "github.com/json-iterator/go"
26+
"sigs.k8s.io/structured-merge-diff/value"
27+
)
28+
29+
var ErrUnknownPathElementType = errors.New("unknown path element type")
30+
31+
const (
32+
// Field indicates that the content of this path element is a field's name
33+
peField = "f"
34+
35+
// Value indicates that the content of this path element is a field's value
36+
peValue = "v"
37+
38+
// Index indicates that the content of this path element is an index in an array
39+
peIndex = "i"
40+
41+
// Key indicates that the content of this path element is a key value map
42+
peKey = "k"
43+
44+
// Separator separates the type of a path element from the contents
45+
peSeparator = ":"
46+
)
47+
48+
var (
49+
peFieldSepBytes = []byte(peField + peSeparator)
50+
peValueSepBytes = []byte(peValue + peSeparator)
51+
peIndexSepBytes = []byte(peIndex + peSeparator)
52+
peKeySepBytes = []byte(peKey + peSeparator)
53+
peSepBytes = []byte(peSeparator)
54+
)
55+
56+
// DeserializePathElement parses a serialized path element
57+
func DeserializePathElement(s string) (PathElement, error) {
58+
b := []byte(s)
59+
if len(b) < 2 {
60+
return PathElement{}, errors.New("key must be 2 characters long:")
61+
}
62+
typeSep, b := b[:2], b[2:]
63+
if typeSep[1] != peSepBytes[0] {
64+
return PathElement{}, fmt.Errorf("missing colon: %v", s)
65+
}
66+
switch typeSep[0] {
67+
case peFieldSepBytes[0]:
68+
// Slice s rather than convert b, to save on
69+
// allocations.
70+
str := s[2:]
71+
return PathElement{
72+
FieldName: &str,
73+
}, nil
74+
case peValueSepBytes[0]:
75+
iter := readPool.BorrowIterator(b)
76+
defer readPool.ReturnIterator(iter)
77+
v, err := value.ReadJSONIter(iter)
78+
if err != nil {
79+
return PathElement{}, err
80+
}
81+
return PathElement{Value: &v}, nil
82+
case peKeySepBytes[0]:
83+
iter := readPool.BorrowIterator(b)
84+
defer readPool.ReturnIterator(iter)
85+
v, err := value.ReadJSONIter(iter)
86+
if err != nil {
87+
return PathElement{}, err
88+
}
89+
if v.MapValue == nil {
90+
return PathElement{}, fmt.Errorf("expected key value pairs but got %#v", v)
91+
}
92+
return PathElement{Key: v.MapValue}, nil
93+
case peIndexSepBytes[0]:
94+
i, err := strconv.Atoi(s[2:])
95+
if err != nil {
96+
return PathElement{}, err
97+
}
98+
return PathElement{
99+
Index: &i,
100+
}, nil
101+
default:
102+
return PathElement{}, ErrUnknownPathElementType
103+
}
104+
}
105+
106+
var (
107+
readPool = jsoniter.NewIterator(jsoniter.ConfigCompatibleWithStandardLibrary).Pool()
108+
writePool = jsoniter.NewStream(jsoniter.ConfigCompatibleWithStandardLibrary, nil, 1024).Pool()
109+
)
110+
111+
// SerializePathElement serializes a path element
112+
func SerializePathElement(pe PathElement) (string, error) {
113+
buf := strings.Builder{}
114+
stream := writePool.BorrowStream(&buf)
115+
defer writePool.ReturnStream(stream)
116+
switch {
117+
case pe.FieldName != nil:
118+
if _, err := stream.Write(peFieldSepBytes); err != nil {
119+
return "", err
120+
}
121+
stream.WriteRaw(*pe.FieldName)
122+
case pe.Key != nil:
123+
if _, err := stream.Write(peKeySepBytes); err != nil {
124+
return "", err
125+
}
126+
v := value.Value{MapValue: pe.Key}
127+
v.WriteJSONStream(stream)
128+
case pe.Value != nil:
129+
if _, err := stream.Write(peValueSepBytes); err != nil {
130+
return "", err
131+
}
132+
pe.Value.WriteJSONStream(stream)
133+
case pe.Index != nil:
134+
if _, err := stream.Write(peIndexSepBytes); err != nil {
135+
return "", err
136+
}
137+
stream.WriteInt(*pe.Index)
138+
default:
139+
return "", errors.New("invalid PathElement")
140+
}
141+
err := stream.Flush()
142+
return buf.String(), err
143+
}

fieldpath/serialize-pe_test.go

+84
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/*
2+
Copyright 2018 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 fieldpath
18+
19+
import "testing"
20+
21+
func TestPathElementRoundTrip(t *testing.T) {
22+
tests := []string{
23+
`i:0`,
24+
`i:1234`,
25+
`f:`,
26+
`f:spec`,
27+
`f:more-complicated-string`,
28+
`k:{"name":"my-container"}`,
29+
`k:{"port":"8080","protocol":"TCP"}`,
30+
`k:{"optionalField":null}`,
31+
`k:{"jsonField":{"A":1,"B":null,"C":"D","E":{"F":"G"}}}`,
32+
`k:{"listField":["1","2","3"]}`,
33+
`v:null`,
34+
`v:"some-string"`,
35+
`v:1234`,
36+
`v:{"some":"json"}`,
37+
}
38+
39+
for _, test := range tests {
40+
t.Run(test, func(t *testing.T) {
41+
pe, err := DeserializePathElement(test)
42+
if err != nil {
43+
t.Fatalf("Failed to create path element: %v", err)
44+
}
45+
output, err := SerializePathElement(pe)
46+
if err != nil {
47+
t.Fatalf("Failed to create string from path element: %v", err)
48+
}
49+
if test != output {
50+
t.Fatalf("Expected round-trip:\ninput: %v\noutput: %v", test, output)
51+
}
52+
})
53+
}
54+
}
55+
56+
func TestPathElementIgnoreUnknown(t *testing.T) {
57+
_, err := DeserializePathElement("r:Hello")
58+
if err != ErrUnknownPathElementType {
59+
t.Fatalf("Unknown qualifiers must not return an invalid path element")
60+
}
61+
}
62+
63+
func TestDeserializePathElementError(t *testing.T) {
64+
tests := []string{
65+
``,
66+
`no-colon`,
67+
`i:index is not a number`,
68+
`i:1.23`,
69+
`i:`,
70+
`v:invalid json`,
71+
`v:`,
72+
`k:invalid json`,
73+
`k:{"name":invalid}`,
74+
}
75+
76+
for _, test := range tests {
77+
t.Run(test, func(t *testing.T) {
78+
pe, err := DeserializePathElement(test)
79+
if err == nil {
80+
t.Fatalf("Expected error, no error found. got: %#v, %s", pe, pe)
81+
}
82+
})
83+
}
84+
}

0 commit comments

Comments
 (0)