Skip to content

Commit acfda13

Browse files
Merge pull request #127838 from benluddy/unstructured-nested-number-as-float64
Add NestedNumberAsFloat64 unstructured field accessor. Kubernetes-commit: aa09157014750871407aca4217f4ecf07bc64b7c
2 parents c463db1 + f0c9bac commit acfda13

File tree

2 files changed

+94
-0
lines changed

2 files changed

+94
-0
lines changed

pkg/apis/meta/v1/unstructured/helpers.go

+22
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,28 @@ func NestedInt64(obj map[string]interface{}, fields ...string) (int64, bool, err
125125
return i, true, nil
126126
}
127127

128+
// NestedNumberAsFloat64 returns the float64 value of a nested field. If the field's value is a
129+
// float64, it is returned. If the field's value is an int64 that can be losslessly converted to
130+
// float64, it will be converted and returned. Returns false if value is not found and an error if
131+
// not a float64 or an int64 that can be accurately represented as a float64.
132+
func NestedNumberAsFloat64(obj map[string]interface{}, fields ...string) (float64, bool, error) {
133+
val, found, err := NestedFieldNoCopy(obj, fields...)
134+
if !found || err != nil {
135+
return 0, found, err
136+
}
137+
switch x := val.(type) {
138+
case int64:
139+
if x != int64(float64(x)) {
140+
return 0, false, fmt.Errorf("%v accessor error: int64 value %v cannot be losslessly converted to float64", jsonPath(fields), x)
141+
}
142+
return float64(x), true, nil
143+
case float64:
144+
return x, true, nil
145+
default:
146+
return 0, false, fmt.Errorf("%v accessor error: %v is of the type %T, expected float64 or int64", jsonPath(fields), val, val)
147+
}
148+
}
149+
128150
// NestedStringSlice returns a copy of []string value of a nested field.
129151
// Returns false if value is not found and an error if not a []interface{} or contains non-string items in the slice.
130152
func NestedStringSlice(obj map[string]interface{}, fields ...string) ([]string, bool, error) {

pkg/apis/meta/v1/unstructured/helpers_test.go

+72
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package unstructured
1818

1919
import (
2020
"io/ioutil"
21+
"math"
2122
"sync"
2223
"testing"
2324

@@ -225,3 +226,74 @@ func TestSetNestedMap(t *testing.T) {
225226
assert.Len(t, obj["x"].(map[string]interface{})["z"], 1)
226227
assert.Equal(t, "bar", obj["x"].(map[string]interface{})["z"].(map[string]interface{})["b"])
227228
}
229+
230+
func TestNestedNumberAsFloat64(t *testing.T) {
231+
for _, tc := range []struct {
232+
name string
233+
obj map[string]interface{}
234+
path []string
235+
wantFloat64 float64
236+
wantBool bool
237+
wantErrMessage string
238+
}{
239+
{
240+
name: "not found",
241+
obj: nil,
242+
path: []string{"missing"},
243+
wantFloat64: 0,
244+
wantBool: false,
245+
wantErrMessage: "",
246+
},
247+
{
248+
name: "found float64",
249+
obj: map[string]interface{}{"value": float64(42)},
250+
path: []string{"value"},
251+
wantFloat64: 42,
252+
wantBool: true,
253+
wantErrMessage: "",
254+
},
255+
{
256+
name: "found unexpected type bool",
257+
obj: map[string]interface{}{"value": true},
258+
path: []string{"value"},
259+
wantFloat64: 0,
260+
wantBool: false,
261+
wantErrMessage: ".value accessor error: true is of the type bool, expected float64 or int64",
262+
},
263+
{
264+
name: "found int64",
265+
obj: map[string]interface{}{"value": int64(42)},
266+
path: []string{"value"},
267+
wantFloat64: 42,
268+
wantBool: true,
269+
wantErrMessage: "",
270+
},
271+
{
272+
name: "found int64 not representable as float64",
273+
obj: map[string]interface{}{"value": int64(math.MaxInt64)},
274+
path: []string{"value"},
275+
wantFloat64: 0,
276+
wantBool: false,
277+
wantErrMessage: ".value accessor error: int64 value 9223372036854775807 cannot be losslessly converted to float64",
278+
},
279+
} {
280+
t.Run(tc.name, func(t *testing.T) {
281+
gotFloat64, gotBool, gotErr := NestedNumberAsFloat64(tc.obj, tc.path...)
282+
if gotFloat64 != tc.wantFloat64 {
283+
t.Errorf("got %v, wanted %v", gotFloat64, tc.wantFloat64)
284+
}
285+
if gotBool != tc.wantBool {
286+
t.Errorf("got %t, wanted %t", gotBool, tc.wantBool)
287+
}
288+
if tc.wantErrMessage != "" {
289+
if gotErr == nil {
290+
t.Errorf("got nil error, wanted %s", tc.wantErrMessage)
291+
} else if gotErrMessage := gotErr.Error(); gotErrMessage != tc.wantErrMessage {
292+
t.Errorf("wanted error %q, got: %v", gotErrMessage, tc.wantErrMessage)
293+
}
294+
} else if gotErr != nil {
295+
t.Errorf("wanted nil error, got %v", gotErr)
296+
}
297+
})
298+
}
299+
}

0 commit comments

Comments
 (0)