From aa19b48df06630d47af40812d4908030a7386740 Mon Sep 17 00:00:00 2001 From: Pieter Noordhuis Date: Thu, 4 Jan 2024 19:52:22 +0100 Subject: [PATCH 1/2] Consolidate functions to convert `dyn.Value` to native types --- libs/dyn/value.go | 50 ---------- libs/dyn/value_underlying.go | 127 +++++++++++++++++++++++ libs/dyn/value_underlying_test.go | 161 ++++++++++++++++++++++++++++++ 3 files changed, 288 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 9ac738f9cb..e33e10cffa 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 0000000000..e885f558b4 --- /dev/null +++ b/libs/dyn/value_underlying.go @@ -0,0 +1,127 @@ +package dyn + +import ( + "fmt" + "time" +) + +// panicOnTypeMismatch is a helper function for the MustZZZ functions in this file. +// We rather panic with a descriptive error message than a generic one. +func panicOnTypeMismatch[T any](v Value, vv T, ok bool, k Kind) T { + if !ok || v.k != k { + panic(fmt.Sprintf("expected kind %s, got %s", k, v.k)) + } + return vv +} + +// 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() + return panicOnTypeMismatch(v, vv, ok, KindMap) +} + +// 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() + return panicOnTypeMismatch(v, vv, ok, KindSequence) +} + +// 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() + return panicOnTypeMismatch(v, vv, ok, KindString) +} + +// 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() + return panicOnTypeMismatch(v, vv, ok, KindBool) +} + +// 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() + return panicOnTypeMismatch(v, vv, ok, KindInt) +} + +// 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() + return panicOnTypeMismatch(v, vv, ok, KindFloat) +} + +// 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() + return panicOnTypeMismatch(v, vv, ok, KindTime) +} diff --git a/libs/dyn/value_underlying_test.go b/libs/dyn/value_underlying_test.go new file mode 100644 index 0000000000..17cb959418 --- /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() + }) +} From 021386139c8fd5aac2abe14e849b3fa346f3f2d8 Mon Sep 17 00:00:00 2001 From: Pieter Noordhuis Date: Thu, 4 Jan 2024 19:59:42 +0100 Subject: [PATCH 2/2] Inline --- libs/dyn/value_underlying.go | 44 +++++++++++++++++++++++------------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/libs/dyn/value_underlying.go b/libs/dyn/value_underlying.go index e885f558b4..c8c5037900 100644 --- a/libs/dyn/value_underlying.go +++ b/libs/dyn/value_underlying.go @@ -5,15 +5,6 @@ import ( "time" ) -// panicOnTypeMismatch is a helper function for the MustZZZ functions in this file. -// We rather panic with a descriptive error message than a generic one. -func panicOnTypeMismatch[T any](v Value, vv T, ok bool, k Kind) T { - if !ok || v.k != k { - panic(fmt.Sprintf("expected kind %s, got %s", k, v.k)) - } - return vv -} - // 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) { @@ -25,7 +16,10 @@ func (v Value) AsMap() (map[string]Value, bool) { // panics otherwise. func (v Value) MustMap() map[string]Value { vv, ok := v.AsMap() - return panicOnTypeMismatch(v, vv, ok, KindMap) + 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, @@ -39,7 +33,10 @@ func (v Value) AsSequence() ([]Value, bool) { // panics otherwise. func (v Value) MustSequence() []Value { vv, ok := v.AsSequence() - return panicOnTypeMismatch(v, vv, ok, KindSequence) + 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, @@ -53,7 +50,10 @@ func (v Value) AsString() (string, bool) { // panics otherwise. func (v Value) MustString() string { vv, ok := v.AsString() - return panicOnTypeMismatch(v, vv, ok, KindString) + 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, @@ -67,7 +67,10 @@ func (v Value) AsBool() (bool, bool) { // panics otherwise. func (v Value) MustBool() bool { vv, ok := v.AsBool() - return panicOnTypeMismatch(v, vv, ok, KindBool) + 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, @@ -89,7 +92,10 @@ func (v Value) AsInt() (int64, bool) { // panics otherwise. func (v Value) MustInt() int64 { vv, ok := v.AsInt() - return panicOnTypeMismatch(v, vv, ok, KindInt) + 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, @@ -109,7 +115,10 @@ func (v Value) AsFloat() (float64, bool) { // panics otherwise. func (v Value) MustFloat() float64 { vv, ok := v.AsFloat() - return panicOnTypeMismatch(v, vv, ok, KindFloat) + 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, @@ -123,5 +132,8 @@ func (v Value) AsTime() (time.Time, bool) { // panics otherwise. func (v Value) MustTime() time.Time { vv, ok := v.AsTime() - return panicOnTypeMismatch(v, vv, ok, KindTime) + if !ok || v.k != KindTime { + panic(fmt.Sprintf("expected kind %s, got %s", KindTime, v.k)) + } + return vv }