Skip to content

Commit bd443e1

Browse files
committed
Replace unflattening algorithm.
Signed-off-by: Felix Fontein <felix@fontein.de>
1 parent de31847 commit bd443e1

2 files changed

Lines changed: 142 additions & 88 deletions

File tree

stores/flatten.go

Lines changed: 100 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ package stores
22

33
import (
44
"fmt"
5-
"reflect"
6-
"slices"
5+
"math"
6+
"sort"
77
"strconv"
88
"strings"
99

@@ -69,98 +69,131 @@ func tokenize(path string) []token {
6969
return tokens
7070
}
7171

72-
func descendMap(branch sops.TreeBranch, key string) (sops.TreeBranch, reflect.Value) {
73-
for idx, elt := range branch {
74-
if elt.Key == key {
75-
return branch, reflect.ValueOf(&branch[idx].Value)
72+
type node struct {
73+
value *interface{}
74+
subkeys map[string]*node
75+
indices map[int]*node
76+
}
77+
78+
func place(value *interface{}, path []token, root *node) error {
79+
for _, token := range path {
80+
var next *node
81+
var ok bool
82+
switch t := token.(type) {
83+
case mapToken:
84+
if root.subkeys == nil {
85+
root.subkeys = make(map[string]*node)
86+
}
87+
next, ok = root.subkeys[t.key]
88+
if !ok {
89+
next = &node{}
90+
root.subkeys[t.key] = next
91+
}
92+
case listToken:
93+
if root.indices == nil {
94+
root.indices = make(map[int]*node)
95+
}
96+
next, ok = root.indices[t.position]
97+
if !ok {
98+
next = &node{}
99+
root.indices[t.position] = next
100+
}
76101
}
102+
root = next
77103
}
78-
newBranch := append(branch, sops.TreeItem{
79-
Key: key,
80-
Value: nil,
81-
})
82-
return newBranch, reflect.ValueOf(&newBranch[len(newBranch)-1].Value)
104+
if root.value != nil {
105+
return fmt.Errorf("Duplicate value")
106+
}
107+
root.value = value
108+
return nil
83109
}
84110

85-
func descendList(array []interface{}, index int) ([]interface{}, reflect.Value) {
86-
if index >= len(array) {
87-
if index >= cap(array) {
88-
array = slices.Grow(array, index+1-cap(array))
89-
}
90-
array = array[:index+1]
111+
func b2i(value bool) int {
112+
if value {
113+
return 1
91114
}
92-
return array, reflect.ValueOf(&array[index])
115+
return 0
93116
}
94117

95-
func descend(value reflect.Value, t token) (reflect.Value, error) {
96-
interfaceType := reflect.TypeOf((*interface{})(nil))
97-
switch currToken := t.(type) {
98-
case mapToken:
99-
// This special case is only needed for the root
100-
treeBranchPtrType := reflect.TypeOf((*sops.TreeBranch)(nil))
101-
if value.Type() == treeBranchPtrType {
102-
v := *value.Interface().(*sops.TreeBranch)
103-
v, nextVal := descendMap(v, currToken.key)
104-
reflect.Indirect(value).Set(reflect.ValueOf(v))
105-
return nextVal, nil
118+
func convert(root *node) (interface{}, error) {
119+
hasValue := root.value != nil
120+
hasSubkey := len(root.subkeys) > 0
121+
hasIndex := len(root.indices) > 0
122+
if b2i(hasValue)+b2i(hasSubkey)+b2i(hasIndex) > 1 {
123+
return nil, fmt.Errorf("Type mismatch")
124+
}
125+
if hasValue {
126+
return *root.value, nil
127+
}
128+
if hasSubkey {
129+
keys := make([]string, len(root.subkeys))
130+
index := 0
131+
for k := range root.subkeys {
132+
keys[index] = k
133+
index += 1
106134
}
107-
if value.Type() == interfaceType {
108-
val := reflect.Indirect(value)
109-
if val.IsNil() {
110-
v, nextVal := descendMap(nil, currToken.key)
111-
val.Set(reflect.ValueOf(v))
112-
return nextVal, nil
135+
sort.Strings(keys)
136+
result := make(sops.TreeBranch, len(keys))
137+
for index, key := range keys {
138+
value, err := convert(root.subkeys[key])
139+
if err != nil {
140+
return nil, err
113141
}
114-
if v, ok := val.Interface().(sops.TreeBranch); ok {
115-
v, nextVal := descendMap(v, currToken.key)
116-
val.Set(reflect.ValueOf(v))
117-
return nextVal, nil
142+
result[index] = sops.TreeItem{
143+
Key: key,
144+
Value: value,
118145
}
119146
}
120-
return reflect.Value{}, fmt.Errorf("Type mismatch: can only use string key for map")
121-
case listToken:
122-
if value.Type() == interfaceType {
123-
val := reflect.Indirect(value)
124-
if val.IsNil() {
125-
v, nextVal := descendList(nil, currToken.position)
126-
val.Set(reflect.ValueOf(v))
127-
return nextVal, nil
128-
}
129-
if v, ok := val.Interface().([]interface{}); ok {
130-
v, nextVal := descendList(v, currToken.position)
131-
val.Set(reflect.ValueOf(v))
132-
return nextVal, nil
133-
}
147+
return result, nil
148+
}
149+
minValue := math.MaxInt
150+
maxValue := math.MinInt
151+
for k := range root.indices {
152+
if k < minValue {
153+
minValue = k
154+
}
155+
if k > maxValue {
156+
maxValue = k
157+
}
158+
}
159+
if minValue != 0 || maxValue+1 != len(root.indices) {
160+
return nil, fmt.Errorf("Incomplete list")
161+
}
162+
result := make([]interface{}, maxValue+1)
163+
for k, v := range root.indices {
164+
value, err := convert(v)
165+
if err != nil {
166+
return nil, err
134167
}
135-
return reflect.Value{}, fmt.Errorf("Type mismatch: can only use integer key for list")
136-
default:
137-
return reflect.Value{}, fmt.Errorf("Internal error: unknown token %q", t)
168+
result[k] = value
138169
}
170+
return result, nil
139171
}
140172

141173
func unflattenTreeBranch(branch sops.TreeBranch) (sops.TreeBranch, error) {
142-
var result sops.TreeBranch
174+
root := &node{}
143175
for _, item := range branch {
144176
if _, ok := item.Key.(sops.Comment); ok {
145177
continue
146178
}
147179
if key, ok := item.Key.(string); ok {
148-
current := reflect.ValueOf(&result)
149180
tokens := tokenize(key)
150-
for _, token := range tokens {
151-
var err error
152-
current, err = descend(current, token)
153-
if err != nil {
154-
return nil, fmt.Errorf("Error while unflattening %q: %w", key, err)
155-
}
181+
err := place(&item.Value, tokens, root)
182+
if err != nil {
183+
return nil, fmt.Errorf("Error while unflattening %q: %w", key, err)
156184
}
157-
reflect.Indirect(current).Set(reflect.ValueOf(item.Value))
158-
continue
159185
} else {
160186
return nil, fmt.Errorf("Found non-string key %q when unflattening", item.Key)
161187
}
162188
}
163-
return result, nil
189+
result, err := convert(root)
190+
if err != nil {
191+
return nil, fmt.Errorf("Error while unflattening: %w", err)
192+
}
193+
if tb, ok := result.(sops.TreeBranch); ok {
194+
return tb, nil
195+
}
196+
return nil, fmt.Errorf("Internal error: cannot find root")
164197
}
165198

166199
////////////////////////////////////////////////////////////////////////////////////////////////////////////////

stores/flatten_test.go

Lines changed: 42 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -83,41 +83,41 @@ func TestUnflattenTreeBranch(t *testing.T) {
8383
Value: []interface{}{
8484
"foo",
8585
sops.TreeBranch{
86-
sops.TreeItem{
87-
Key: "foo",
88-
Value: "bar",
89-
},
9086
sops.TreeItem{
9187
Key: "baz",
9288
Value: "bam",
9389
},
90+
sops.TreeItem{
91+
Key: "foo",
92+
Value: "bar",
93+
},
9494
},
9595
},
9696
},
9797
sops.TreeItem{
9898
Key: "key3",
9999
Value: sops.TreeBranch{
100-
sops.TreeItem{
101-
Key: "foo",
102-
Value: "bar",
103-
},
104100
sops.TreeItem{
105101
Key: "baz",
106102
Value: "bam",
107103
},
104+
sops.TreeItem{
105+
Key: "foo",
106+
Value: "bar",
107+
},
108108
},
109109
},
110110
sops.TreeItem{
111111
Key: "key4",
112112
Value: sops.TreeBranch{
113-
sops.TreeItem{
114-
Key: "foo",
115-
Value: "bar",
116-
},
117113
sops.TreeItem{
118114
Key: "baz",
119115
Value: "bam",
120116
},
117+
sops.TreeItem{
118+
Key: "foo",
119+
Value: "bar",
120+
},
121121
},
122122
},
123123
}
@@ -132,12 +132,22 @@ func TestUnflattenTreeBranch(t *testing.T) {
132132
Value: "bar",
133133
},
134134
}
135-
expectedOutputDupe = sops.TreeBranch{
135+
136+
inputSkip1 = sops.TreeBranch{
136137
sops.TreeItem{
137-
Key: "key1",
138-
Value: []interface{}{
139-
"bar",
140-
},
138+
Key: "key1__list_0",
139+
Value: "foo",
140+
},
141+
sops.TreeItem{
142+
Key: "key1__list_999999999999",
143+
Value: "bar",
144+
},
145+
}
146+
147+
inputSkip2 = sops.TreeBranch{
148+
sops.TreeItem{
149+
Key: "key1__list_1",
150+
Value: "foo",
141151
},
142152
}
143153

@@ -169,17 +179,28 @@ func TestUnflattenTreeBranch(t *testing.T) {
169179
assert.Equal(t, output, expectedOutput)
170180

171181
output, err = unflattenTreeBranch(inputDupe)
172-
assert.Nil(t, err)
173-
assert.Equal(t, output, expectedOutputDupe)
182+
assert.NotNil(t, err)
183+
assert.Equal(t, "Error while unflattening \"key1__list_0\": Duplicate value", err.Error())
184+
assert.Nil(t, output)
185+
186+
output, err = unflattenTreeBranch(inputSkip1)
187+
assert.NotNil(t, err)
188+
assert.Equal(t, "Error while unflattening: Incomplete list", err.Error())
189+
assert.Nil(t, output)
190+
191+
output, err = unflattenTreeBranch(inputSkip2)
192+
assert.NotNil(t, err)
193+
assert.Equal(t, "Error while unflattening: Incomplete list", err.Error())
194+
assert.Nil(t, output)
174195

175196
output, err = unflattenTreeBranch(inputCollision1)
176197
assert.NotNil(t, err)
177-
assert.Equal(t, "Error while unflattening \"key1__map_foo\": Type mismatch: can only use string key for map", err.Error())
198+
assert.Equal(t, "Error while unflattening: Type mismatch", err.Error())
178199
assert.Nil(t, output)
179200

180201
output, err = unflattenTreeBranch(inputCollision2)
181202
assert.NotNil(t, err)
182-
assert.Equal(t, "Error while unflattening \"key1__list_0\": Type mismatch: can only use integer key for list", err.Error())
203+
assert.Equal(t, "Error while unflattening: Type mismatch", err.Error())
183204
assert.Nil(t, output)
184205
}
185206

0 commit comments

Comments
 (0)