From bae220d1bc460a60a906441b744a0859a718b791 Mon Sep 17 00:00:00 2001 From: Pieter Noordhuis Date: Fri, 5 Jan 2024 13:06:12 +0100 Subject: [PATCH] Consolidate functions to convert `dyn.Value` to native types (#1100) ## Changes The file `value.go` had a couple `AsZZZ` and `MustZZZ` functions. This change backfills missing versions and moves all of them to a separate file. ## Tests Tests pass; full coverage. --- libs/dyn/value.go | 50 ---------- libs/dyn/value_underlying.go | 139 ++++++++++++++++++++++++++ libs/dyn/value_underlying_test.go | 161 ++++++++++++++++++++++++++++++ 3 files changed, 300 insertions(+), 50 deletions(-) create mode 100644 libs/dyn/value_underlying.go create mode 100644 libs/dyn/value_underlying_test.go diff --git a/libs/dyn/value.go b/libs/dyn/value.go index 9ac738f9..e33e10cf 100644 --- a/libs/dyn/value.go +++ b/libs/dyn/value.go @@ -2,7 +2,6 @@ package dyn import ( "fmt" - "time" ) type Value struct { @@ -38,11 +37,6 @@ func NewValue(v any, loc Location) Value { } } -func (v Value) AsMap() (map[string]Value, bool) { - m, ok := v.v.(map[string]Value) - return m, ok -} - func (v Value) Kind() Kind { return v.k } @@ -131,47 +125,3 @@ func (v Value) MarkAnchor() Value { func (v Value) IsAnchor() bool { return v.anchor } - -func (v Value) MustMap() map[string]Value { - return v.v.(map[string]Value) -} - -func (v Value) MustSequence() []Value { - return v.v.([]Value) -} - -func (v Value) MustString() string { - return v.v.(string) -} - -func (v Value) MustBool() bool { - return v.v.(bool) -} - -func (v Value) MustInt() int64 { - switch vv := v.v.(type) { - case int: - return int64(vv) - case int32: - return int64(vv) - case int64: - return int64(vv) - default: - panic("not an int") - } -} - -func (v Value) MustFloat() float64 { - switch vv := v.v.(type) { - case float32: - return float64(vv) - case float64: - return float64(vv) - default: - panic("not a float") - } -} - -func (v Value) MustTime() time.Time { - return v.v.(time.Time) -} diff --git a/libs/dyn/value_underlying.go b/libs/dyn/value_underlying.go new file mode 100644 index 00000000..c8c50379 --- /dev/null +++ b/libs/dyn/value_underlying.go @@ -0,0 +1,139 @@ +package dyn + +import ( + "fmt" + "time" +) + +// AsMap returns the underlying map if this value is a map, +// the zero value and false otherwise. +func (v Value) AsMap() (map[string]Value, bool) { + vv, ok := v.v.(map[string]Value) + return vv, ok +} + +// MustMap returns the underlying map if this value is a map, +// panics otherwise. +func (v Value) MustMap() map[string]Value { + vv, ok := v.AsMap() + if !ok || v.k != KindMap { + panic(fmt.Sprintf("expected kind %s, got %s", KindMap, v.k)) + } + return vv +} + +// AsSequence returns the underlying sequence if this value is a sequence, +// the zero value and false otherwise. +func (v Value) AsSequence() ([]Value, bool) { + vv, ok := v.v.([]Value) + return vv, ok +} + +// MustSequence returns the underlying sequence if this value is a sequence, +// panics otherwise. +func (v Value) MustSequence() []Value { + vv, ok := v.AsSequence() + if !ok || v.k != KindSequence { + panic(fmt.Sprintf("expected kind %s, got %s", KindSequence, v.k)) + } + return vv +} + +// AsString returns the underlying string if this value is a string, +// the zero value and false otherwise. +func (v Value) AsString() (string, bool) { + vv, ok := v.v.(string) + return vv, ok +} + +// MustString returns the underlying string if this value is a string, +// panics otherwise. +func (v Value) MustString() string { + vv, ok := v.AsString() + if !ok || v.k != KindString { + panic(fmt.Sprintf("expected kind %s, got %s", KindString, v.k)) + } + return vv +} + +// AsBool returns the underlying bool if this value is a bool, +// the zero value and false otherwise. +func (v Value) AsBool() (bool, bool) { + vv, ok := v.v.(bool) + return vv, ok +} + +// MustBool returns the underlying bool if this value is a bool, +// panics otherwise. +func (v Value) MustBool() bool { + vv, ok := v.AsBool() + if !ok || v.k != KindBool { + panic(fmt.Sprintf("expected kind %s, got %s", KindBool, v.k)) + } + return vv +} + +// AsInt returns the underlying int if this value is an int, +// the zero value and false otherwise. +func (v Value) AsInt() (int64, bool) { + switch vv := v.v.(type) { + case int: + return int64(vv), true + case int32: + return int64(vv), true + case int64: + return int64(vv), true + default: + return 0, false + } +} + +// MustInt returns the underlying int if this value is an int, +// panics otherwise. +func (v Value) MustInt() int64 { + vv, ok := v.AsInt() + if !ok || v.k != KindInt { + panic(fmt.Sprintf("expected kind %s, got %s", KindInt, v.k)) + } + return vv +} + +// AsFloat returns the underlying float if this value is a float, +// the zero value and false otherwise. +func (v Value) AsFloat() (float64, bool) { + switch vv := v.v.(type) { + case float32: + return float64(vv), true + case float64: + return float64(vv), true + default: + return 0, false + } +} + +// MustFloat returns the underlying float if this value is a float, +// panics otherwise. +func (v Value) MustFloat() float64 { + vv, ok := v.AsFloat() + if !ok || v.k != KindFloat { + panic(fmt.Sprintf("expected kind %s, got %s", KindFloat, v.k)) + } + return vv +} + +// AsTime returns the underlying time if this value is a time, +// the zero value and false otherwise. +func (v Value) AsTime() (time.Time, bool) { + vv, ok := v.v.(time.Time) + return vv, ok +} + +// MustTime returns the underlying time if this value is a time, +// panics otherwise. +func (v Value) MustTime() time.Time { + vv, ok := v.AsTime() + if !ok || v.k != KindTime { + panic(fmt.Sprintf("expected kind %s, got %s", KindTime, v.k)) + } + return vv +} diff --git a/libs/dyn/value_underlying_test.go b/libs/dyn/value_underlying_test.go new file mode 100644 index 00000000..17cb9594 --- /dev/null +++ b/libs/dyn/value_underlying_test.go @@ -0,0 +1,161 @@ +package dyn_test + +import ( + "testing" + "time" + + "github.com/databricks/cli/libs/dyn" + "github.com/stretchr/testify/assert" +) + +func TestValueUnderlyingMap(t *testing.T) { + v := dyn.V( + map[string]dyn.Value{ + "key": dyn.NewValue("value", dyn.Location{File: "file", Line: 1, Column: 2}), + }, + ) + + vv1, ok := v.AsMap() + assert.True(t, ok) + + _, ok = dyn.NilValue.AsMap() + assert.False(t, ok) + + vv2 := v.MustMap() + assert.Equal(t, vv1, vv2) + + // Test panic. + assert.PanicsWithValue(t, "expected kind map, got nil", func() { + dyn.NilValue.MustMap() + }) +} + +func TestValueUnderlyingSequence(t *testing.T) { + v := dyn.V( + []dyn.Value{ + dyn.NewValue("value", dyn.Location{File: "file", Line: 1, Column: 2}), + }, + ) + + vv1, ok := v.AsSequence() + assert.True(t, ok) + + _, ok = dyn.NilValue.AsSequence() + assert.False(t, ok) + + vv2 := v.MustSequence() + assert.Equal(t, vv1, vv2) + + // Test panic. + assert.PanicsWithValue(t, "expected kind sequence, got nil", func() { + dyn.NilValue.MustSequence() + }) +} + +func TestValueUnderlyingString(t *testing.T) { + v := dyn.V("value") + + vv1, ok := v.AsString() + assert.True(t, ok) + + _, ok = dyn.NilValue.AsString() + assert.False(t, ok) + + vv2 := v.MustString() + assert.Equal(t, vv1, vv2) + + // Test panic. + assert.PanicsWithValue(t, "expected kind string, got nil", func() { + dyn.NilValue.MustString() + }) +} + +func TestValueUnderlyingBool(t *testing.T) { + v := dyn.V(true) + + vv1, ok := v.AsBool() + assert.True(t, ok) + + _, ok = dyn.NilValue.AsBool() + assert.False(t, ok) + + vv2 := v.MustBool() + assert.Equal(t, vv1, vv2) + + // Test panic. + assert.PanicsWithValue(t, "expected kind bool, got nil", func() { + dyn.NilValue.MustBool() + }) +} + +func TestValueUnderlyingInt(t *testing.T) { + v := dyn.V(int(1)) + + vv1, ok := v.AsInt() + assert.True(t, ok) + + _, ok = dyn.NilValue.AsInt() + assert.False(t, ok) + + vv2 := v.MustInt() + assert.Equal(t, vv1, vv2) + + // Test panic. + assert.PanicsWithValue(t, "expected kind int, got nil", func() { + dyn.NilValue.MustInt() + }) + + // Test int32 type specifically. + v = dyn.V(int32(1)) + vv1, ok = v.AsInt() + assert.True(t, ok) + assert.Equal(t, int64(1), vv1) + + // Test int64 type specifically. + v = dyn.V(int64(1)) + vv1, ok = v.AsInt() + assert.True(t, ok) + assert.Equal(t, int64(1), vv1) +} + +func TestValueUnderlyingFloat(t *testing.T) { + v := dyn.V(float32(1.0)) + + vv1, ok := v.AsFloat() + assert.True(t, ok) + + _, ok = dyn.NilValue.AsFloat() + assert.False(t, ok) + + vv2 := v.MustFloat() + assert.Equal(t, vv1, vv2) + + // Test panic. + assert.PanicsWithValue(t, "expected kind float, got nil", func() { + dyn.NilValue.MustFloat() + }) + + // Test float64 type specifically. + v = dyn.V(float64(1.0)) + vv1, ok = v.AsFloat() + assert.True(t, ok) + assert.Equal(t, float64(1.0), vv1) +} + +func TestValueUnderlyingTime(t *testing.T) { + v := dyn.V(time.Now()) + + vv1, ok := v.AsTime() + assert.True(t, ok) + + _, ok = dyn.NilValue.AsTime() + assert.False(t, ok) + + vv2 := v.MustTime() + assert.Equal(t, vv1, vv2) + + // Test panic. + assert.PanicsWithValue(t, "expected kind time, got nil", func() { + dyn.NilValue.MustTime() + }) +}