Merge pull request #127838 from benluddy/unstructured-nested-number-as-float64

Add NestedNumberAsFloat64 unstructured field accessor.
This commit is contained in:
Kubernetes Prow Robot 2024-10-08 18:12:21 +01:00 committed by GitHub
commit aa09157014
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 94 additions and 0 deletions

View File

@ -125,6 +125,28 @@ func NestedInt64(obj map[string]interface{}, fields ...string) (int64, bool, err
return i, true, nil
}
// NestedNumberAsFloat64 returns the float64 value of a nested field. If the field's value is a
// float64, it is returned. If the field's value is an int64 that can be losslessly converted to
// float64, it will be converted and returned. Returns false if value is not found and an error if
// not a float64 or an int64 that can be accurately represented as a float64.
func NestedNumberAsFloat64(obj map[string]interface{}, fields ...string) (float64, bool, error) {
val, found, err := NestedFieldNoCopy(obj, fields...)
if !found || err != nil {
return 0, found, err
}
switch x := val.(type) {
case int64:
if x != int64(float64(x)) {
return 0, false, fmt.Errorf("%v accessor error: int64 value %v cannot be losslessly converted to float64", jsonPath(fields), x)
}
return float64(x), true, nil
case float64:
return x, true, nil
default:
return 0, false, fmt.Errorf("%v accessor error: %v is of the type %T, expected float64 or int64", jsonPath(fields), val, val)
}
}
// NestedStringSlice returns a copy of []string value of a nested field.
// Returns false if value is not found and an error if not a []interface{} or contains non-string items in the slice.
func NestedStringSlice(obj map[string]interface{}, fields ...string) ([]string, bool, error) {

View File

@ -18,6 +18,7 @@ package unstructured
import (
"io/ioutil"
"math"
"sync"
"testing"
@ -225,3 +226,74 @@ func TestSetNestedMap(t *testing.T) {
assert.Len(t, obj["x"].(map[string]interface{})["z"], 1)
assert.Equal(t, "bar", obj["x"].(map[string]interface{})["z"].(map[string]interface{})["b"])
}
func TestNestedNumberAsFloat64(t *testing.T) {
for _, tc := range []struct {
name string
obj map[string]interface{}
path []string
wantFloat64 float64
wantBool bool
wantErrMessage string
}{
{
name: "not found",
obj: nil,
path: []string{"missing"},
wantFloat64: 0,
wantBool: false,
wantErrMessage: "",
},
{
name: "found float64",
obj: map[string]interface{}{"value": float64(42)},
path: []string{"value"},
wantFloat64: 42,
wantBool: true,
wantErrMessage: "",
},
{
name: "found unexpected type bool",
obj: map[string]interface{}{"value": true},
path: []string{"value"},
wantFloat64: 0,
wantBool: false,
wantErrMessage: ".value accessor error: true is of the type bool, expected float64 or int64",
},
{
name: "found int64",
obj: map[string]interface{}{"value": int64(42)},
path: []string{"value"},
wantFloat64: 42,
wantBool: true,
wantErrMessage: "",
},
{
name: "found int64 not representable as float64",
obj: map[string]interface{}{"value": int64(math.MaxInt64)},
path: []string{"value"},
wantFloat64: 0,
wantBool: false,
wantErrMessage: ".value accessor error: int64 value 9223372036854775807 cannot be losslessly converted to float64",
},
} {
t.Run(tc.name, func(t *testing.T) {
gotFloat64, gotBool, gotErr := NestedNumberAsFloat64(tc.obj, tc.path...)
if gotFloat64 != tc.wantFloat64 {
t.Errorf("got %v, wanted %v", gotFloat64, tc.wantFloat64)
}
if gotBool != tc.wantBool {
t.Errorf("got %t, wanted %t", gotBool, tc.wantBool)
}
if tc.wantErrMessage != "" {
if gotErr == nil {
t.Errorf("got nil error, wanted %s", tc.wantErrMessage)
} else if gotErrMessage := gotErr.Error(); gotErrMessage != tc.wantErrMessage {
t.Errorf("wanted error %q, got: %v", gotErrMessage, tc.wantErrMessage)
}
} else if gotErr != nil {
t.Errorf("wanted nil error, got %v", gotErr)
}
})
}
}