diff --git a/pkg/dataset/value.go b/pkg/dataset/value.go index 4fb4aea0..eaee64ed 100644 --- a/pkg/dataset/value.go +++ b/pkg/dataset/value.go @@ -45,6 +45,13 @@ func (t ValueType) ValueFrom(i interface{}) *Value { return &Value{v: *vv} } +func (vt ValueType) MustBeValue(i interface{}) *Value { + if v := vt.ValueFrom(i); v != nil { + return v + } + panic("invalid value") +} + type Value struct { v value.Value } @@ -81,6 +88,17 @@ func (v *Value) Interface() interface{} { return v.v.Interface() } +func (v *Value) Cast(vt ValueType) *Value { + if v == nil { + return nil + } + nv := v.v.Cast(value.Type(vt), nil) + if nv == nil { + return nil + } + return &Value{v: *nv} +} + func (v *Value) ValueBool() *bool { if v == nil { return nil diff --git a/pkg/dataset/value_optional.go b/pkg/dataset/value_optional.go index 61affc8c..ac422506 100644 --- a/pkg/dataset/value_optional.go +++ b/pkg/dataset/value_optional.go @@ -3,7 +3,7 @@ package dataset import "github.com/reearth/reearth-backend/pkg/value" type OptionalValue struct { - ov value.OptionalValue + ov value.Optional } func NewOptionalValue(t ValueType, v *Value) *OptionalValue { @@ -11,7 +11,7 @@ func NewOptionalValue(t ValueType, v *Value) *OptionalValue { if v != nil { vv = &v.v } - ov := value.NewOptionalValue(value.Type(t), vv) + ov := value.NewOptional(value.Type(t), vv) if ov == nil { return nil } @@ -22,7 +22,7 @@ func OptionalValueFrom(v *Value) *OptionalValue { if v == nil { return nil } - ov := value.OptionalValueFrom(&v.v) + ov := value.OptionalFrom(&v.v) if ov == nil { return nil } @@ -76,3 +76,14 @@ func (ov *OptionalValue) Clone() *OptionalValue { ov: *nov, } } + +func (ov *OptionalValue) Cast(t ValueType) *OptionalValue { + if ov == nil { + return nil + } + vv := ov.ov.Cast(value.Type(t), nil) + if vv == nil { + return nil + } + return &OptionalValue{ov: *vv} +} diff --git a/pkg/dataset/value_optional_test.go b/pkg/dataset/value_optional_test.go index 8767264a..b96decc0 100644 --- a/pkg/dataset/value_optional_test.go +++ b/pkg/dataset/value_optional_test.go @@ -23,14 +23,14 @@ func TestNewNilableValue(t *testing.T) { t: ValueTypeString, v: ValueTypeString.ValueFrom("foo"), }, - want: &OptionalValue{ov: *value.OptionalValueFrom(value.TypeString.ValueFrom("foo", nil))}, + want: &OptionalValue{ov: *value.OptionalFrom(value.TypeString.ValueFrom("foo", nil))}, }, { name: "nil value", args: args{ t: ValueTypeString, }, - want: &OptionalValue{ov: *value.NewOptionalValue(value.TypeString, nil)}, + want: &OptionalValue{ov: *value.NewOptional(value.TypeString, nil)}, }, { name: "invalid value", @@ -73,7 +73,7 @@ func TestOptionalValueFrom(t *testing.T) { args: args{ v: ValueTypeString.ValueFrom("foo"), }, - want: &OptionalValue{ov: *value.NewOptionalValue(value.TypeString, value.TypeString.ValueFrom("foo", nil))}, + want: &OptionalValue{ov: *value.NewOptional(value.TypeString, value.TypeString.ValueFrom("foo", nil))}, }, { name: "empty value", @@ -106,7 +106,7 @@ func TestOptionalValue_Type(t *testing.T) { }{ { name: "ok", - value: &OptionalValue{ov: *value.NewOptionalValue(value.TypeBool, nil)}, + value: &OptionalValue{ov: *value.NewOptional(value.TypeBool, nil)}, want: ValueTypeBool, }, { @@ -138,7 +138,7 @@ func TestOptionalValue_Value(t *testing.T) { }{ { name: "ok", - value: &OptionalValue{ov: *value.OptionalValueFrom(value.TypeString.ValueFrom("foobar", nil))}, + value: &OptionalValue{ov: *value.OptionalFrom(value.TypeString.ValueFrom("foobar", nil))}, want: ValueTypeString.ValueFrom("foobar"), }, { @@ -175,7 +175,7 @@ func TestOptionalValue_TypeAndValue(t *testing.T) { }{ { name: "ok", - value: &OptionalValue{ov: *value.OptionalValueFrom(value.TypeString.ValueFrom("foobar", nil))}, + value: &OptionalValue{ov: *value.OptionalFrom(value.TypeString.ValueFrom("foobar", nil))}, wantt: ValueTypeString, wantv: ValueTypeString.ValueFrom("foobar"), }, @@ -219,17 +219,17 @@ func TestOptionalValue_SetValue(t *testing.T) { }{ { name: "set", - value: &OptionalValue{ov: *value.OptionalValueFrom(value.TypeString.ValueFrom("foo", nil))}, + value: &OptionalValue{ov: *value.OptionalFrom(value.TypeString.ValueFrom("foo", nil))}, args: args{v: ValueTypeString.ValueFrom("foobar")}, }, { name: "set to nil", - value: &OptionalValue{ov: *value.NewOptionalValue(value.TypeString, nil)}, + value: &OptionalValue{ov: *value.NewOptional(value.TypeString, nil)}, args: args{v: ValueTypeString.ValueFrom("foobar")}, }, { name: "invalid value", - value: &OptionalValue{ov: *value.NewOptionalValue(value.TypeString, nil)}, + value: &OptionalValue{ov: *value.NewOptional(value.TypeString, nil)}, args: args{v: ValueTypeNumber.ValueFrom(1)}, invalid: true, }, @@ -279,7 +279,7 @@ func TestOptionalValue_Clone(t *testing.T) { { name: "ok", target: &OptionalValue{ - ov: *value.NewOptionalValue(value.TypeString, value.TypeString.ValueFrom("foo", nil)), + ov: *value.NewOptional(value.TypeString, value.TypeString.ValueFrom("foo", nil)), }, }, { @@ -301,3 +301,52 @@ func TestOptionalValue_Clone(t *testing.T) { }) } } + +func TestOptionalValue_Cast(t *testing.T) { + type args struct { + t ValueType + } + tests := []struct { + name string + target *OptionalValue + args args + want *OptionalValue + }{ + { + name: "diff type", + target: &OptionalValue{ov: *value.OptionalFrom(value.TypeNumber.ValueFrom(1.1, nil))}, + args: args{t: ValueTypeString}, + want: &OptionalValue{ov: *value.OptionalFrom(value.TypeString.ValueFrom("1.1", nil))}, + }, + { + name: "same type", + target: &OptionalValue{ov: *value.OptionalFrom(value.TypeNumber.ValueFrom(1.1, nil))}, + args: args{t: ValueTypeNumber}, + want: &OptionalValue{ov: *value.OptionalFrom(value.TypeNumber.ValueFrom(1.1, nil))}, + }, + { + name: "failed to cast", + target: &OptionalValue{ov: *value.OptionalFrom(value.TypeLatLng.ValueFrom(LatLng{Lat: 1, Lng: 2}, nil))}, + args: args{t: ValueTypeString}, + want: &OptionalValue{ov: *value.NewOptional(value.TypeString, nil)}, + }, + { + name: "empty", + target: &OptionalValue{}, + args: args{t: ValueTypeString}, + want: nil, + }, + { + name: "nil", + target: nil, + args: args{t: ValueTypeString}, + want: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.want, tt.target.Cast(tt.args.t)) + }) + } +} diff --git a/pkg/property/merged.go b/pkg/property/merged.go index 9b049c5b..6110b476 100644 --- a/pkg/property/merged.go +++ b/pkg/property/merged.go @@ -113,13 +113,6 @@ func Merge(o *Property, p *Property, linked *id.DatasetID) *Merged { return nil } - // copy id - var linked2 *id.DatasetID - if linked != nil { - linked3 := *linked - linked2 = &linked3 - } - var schema id.PropertySchemaID if p != nil { schema = p.Schema() @@ -131,8 +124,8 @@ func Merge(o *Property, p *Property, linked *id.DatasetID) *Merged { Original: o.IDRef(), Parent: p.IDRef(), Schema: schema, - Groups: mergeItems(o.Items(), p.Items(), linked2), - LinkedDataset: linked2, + Groups: mergeItems(o.Items(), p.Items(), linked.CopyRef()), + LinkedDataset: linked.CopyRef(), } } diff --git a/pkg/property/value.go b/pkg/property/value.go index 35404a98..a6ea52ce 100644 --- a/pkg/property/value.go +++ b/pkg/property/value.go @@ -2,7 +2,6 @@ package property import ( "net/url" - "strconv" "github.com/reearth/reearth-backend/pkg/value" ) @@ -49,6 +48,13 @@ func (vt ValueType) ValueFrom(i interface{}) *Value { return &Value{v: *v} } +func (vt ValueType) MustBeValue(i interface{}) *Value { + if v := vt.ValueFrom(i); v != nil { + return v + } + panic("invalid value") +} + type Value struct { v value.Value } @@ -89,6 +95,17 @@ func (v *Value) Interface() interface{} { return v.v.Interface() } +func (v *Value) Cast(vt ValueType) *Value { + if v == nil { + return nil + } + nv := v.v.Cast(value.Type(vt), types) + if nv == nil { + return nil + } + return &Value{v: *nv} +} + func (v *Value) ValueBool() *bool { if v == nil { return nil @@ -200,16 +217,12 @@ func (v *Value) ValuePolygon() *Polygon { } func ValueFromStringOrNumber(s string) *Value { - if vint, err := strconv.Atoi(s); err == nil { - return ValueTypeNumber.ValueFrom(vint) - } - - if vfloat64, err := strconv.ParseFloat(s, 64); err == nil { - return ValueTypeNumber.ValueFrom(vfloat64) + if s == "true" || s == "false" || s == "TRUE" || s == "FALSE" || s == "True" || s == "False" { + return ValueTypeBool.ValueFrom(s) } - if vbool, err := strconv.ParseBool(s); err == nil { - return ValueTypeBool.ValueFrom(vbool) + if v := ValueTypeNumber.ValueFrom(s); v != nil { + return v } return ValueTypeString.ValueFrom(s) diff --git a/pkg/property/value_dataset.go b/pkg/property/value_dataset.go index 3443ad0f..26130725 100644 --- a/pkg/property/value_dataset.go +++ b/pkg/property/value_dataset.go @@ -17,11 +17,11 @@ func NewValueAndDatasetValue(ty ValueType, d *dataset.Value, p *Value) *ValueAnd } if d != nil && ValueType(d.Type()) != ty { - d = nil + d = d.Cast(dataset.ValueType(ty)) } if p != nil && p.Type() != ty { - p = nil + p = p.Cast(ty) } return &ValueAndDatasetValue{ diff --git a/pkg/property/value_dataset_test.go b/pkg/property/value_dataset_test.go index ecaefd48..c3562a08 100644 --- a/pkg/property/value_dataset_test.go +++ b/pkg/property/value_dataset_test.go @@ -22,73 +22,86 @@ func TestNewValueAndDatasetValue(t *testing.T) { name: "ok", args: args{ ty: ValueTypeBool, - d: dataset.ValueTypeBool.ValueFrom(false), - p: ValueTypeBool.ValueFrom(true), + d: dataset.ValueTypeBool.MustBeValue(false), + p: ValueTypeBool.MustBeValue(true), }, want: &ValueAndDatasetValue{ t: ValueTypeBool, d: dataset.ValueTypeBool.ValueFrom(false), - p: ValueTypeBool.ValueFrom(true), + p: ValueTypeBool.MustBeValue(true), }, }, { - name: "invalid type", + name: "different types 1", args: args{ - ty: ValueType("foobar"), - d: dataset.ValueTypeBool.ValueFrom(false), - p: ValueTypeBool.ValueFrom(true), + ty: ValueTypeURL, + d: dataset.ValueTypeString.MustBeValue("https://reearth.io"), + p: nil, + }, + want: &ValueAndDatasetValue{ + t: ValueTypeURL, + d: dataset.ValueTypeURL.MustBeValue("https://reearth.io"), + p: nil, }, - want: nil, }, { - name: "invalid dataset value", + name: "different types 3", args: args{ ty: ValueTypeBool, - d: dataset.ValueTypeString.ValueFrom("false"), - p: ValueTypeBool.ValueFrom(true), + d: dataset.ValueTypeBool.MustBeValue(false), + p: ValueTypeString.MustBeValue("true"), }, want: &ValueAndDatasetValue{ t: ValueTypeBool, - d: nil, - p: ValueTypeBool.ValueFrom(true), + d: dataset.ValueTypeBool.ValueFrom(false), + p: ValueTypeBool.MustBeValue(true), }, }, { - name: "invalid property value", + name: "different types 2", args: args{ ty: ValueTypeBool, - d: dataset.ValueTypeBool.ValueFrom(false), - p: ValueTypeString.ValueFrom("true"), + d: dataset.ValueTypeString.ValueFrom("false"), + p: ValueTypeBool.MustBeValue(true), }, want: &ValueAndDatasetValue{ t: ValueTypeBool, d: dataset.ValueTypeBool.ValueFrom(false), - p: nil, + p: ValueTypeBool.MustBeValue(true), }, }, + { + name: "invalid type", + args: args{ + ty: ValueType("foobar"), + d: dataset.ValueTypeBool.ValueFrom(false), + p: ValueTypeBool.MustBeValue(true), + }, + want: nil, + }, { name: "nil dataset value", args: args{ ty: ValueTypeBool, d: nil, - p: ValueTypeBool.ValueFrom(false), + p: ValueTypeBool.MustBeValue(false), }, want: &ValueAndDatasetValue{ t: ValueTypeBool, d: nil, - p: ValueTypeBool.ValueFrom(false), + p: ValueTypeBool.MustBeValue(false), }, }, { name: "nil property value", args: args{ ty: ValueTypeBool, - d: dataset.ValueTypeBool.ValueFrom(false), + d: dataset.ValueTypeBool.MustBeValue(false), p: nil, }, want: &ValueAndDatasetValue{ t: ValueTypeBool, - d: dataset.ValueTypeBool.ValueFrom(false), + d: dataset.ValueTypeBool.MustBeValue(false), p: nil, }, }, @@ -254,26 +267,26 @@ func TestValueAndDatasetValue_Value(t *testing.T) { name: "dataset only", target: &ValueAndDatasetValue{ t: ValueTypeString, - d: dataset.ValueTypeString.ValueFrom("foo"), + d: dataset.ValueTypeString.MustBeValue("foo"), }, - want: ValueTypeString.ValueFrom("foo"), + want: ValueTypeString.MustBeValue("foo"), }, { name: "property only", target: &ValueAndDatasetValue{ t: ValueTypeString, - p: ValueTypeString.ValueFrom("bar"), + p: ValueTypeString.MustBeValue("bar"), }, - want: ValueTypeString.ValueFrom("bar"), + want: ValueTypeString.MustBeValue("bar"), }, { name: "dataset and property", target: &ValueAndDatasetValue{ t: ValueTypeString, - d: dataset.ValueTypeString.ValueFrom("foo"), - p: ValueTypeString.ValueFrom("bar"), + d: dataset.ValueTypeString.MustBeValue("foo"), + p: ValueTypeString.MustBeValue("bar"), }, - want: ValueTypeString.ValueFrom("foo"), + want: ValueTypeString.MustBeValue("foo"), }, { name: "empty", diff --git a/pkg/property/value_optional.go b/pkg/property/value_optional.go index 6e01c84b..6b862ad6 100644 --- a/pkg/property/value_optional.go +++ b/pkg/property/value_optional.go @@ -3,7 +3,7 @@ package property import "github.com/reearth/reearth-backend/pkg/value" type OptionalValue struct { - ov value.OptionalValue + ov value.Optional } func NewOptionalValue(t ValueType, v *Value) *OptionalValue { @@ -11,7 +11,7 @@ func NewOptionalValue(t ValueType, v *Value) *OptionalValue { if v != nil { vv = &v.v } - ov := value.NewOptionalValue(value.Type(t), vv) + ov := value.NewOptional(value.Type(t), vv) if ov == nil { return nil } @@ -22,7 +22,7 @@ func OptionalValueFrom(v *Value) *OptionalValue { if v == nil { return nil } - ov := value.OptionalValueFrom(&v.v) + ov := value.OptionalFrom(&v.v) if ov == nil { return nil } @@ -76,3 +76,14 @@ func (ov *OptionalValue) SetValue(v *Value) { ov.ov.SetValue(&v.v) } } + +func (ov *OptionalValue) Cast(t ValueType) *OptionalValue { + if ov == nil { + return nil + } + vv := ov.ov.Cast(value.Type(t), types) + if vv == nil { + return nil + } + return &OptionalValue{ov: *vv} +} diff --git a/pkg/property/value_optional_test.go b/pkg/property/value_optional_test.go index 61e35b02..f73f4970 100644 --- a/pkg/property/value_optional_test.go +++ b/pkg/property/value_optional_test.go @@ -23,14 +23,14 @@ func TestNewNilableValue(t *testing.T) { t: ValueTypeString, v: ValueTypeString.ValueFrom("foo"), }, - want: &OptionalValue{ov: *value.OptionalValueFrom(value.TypeString.ValueFrom("foo", types))}, + want: &OptionalValue{ov: *value.OptionalFrom(value.TypeString.ValueFrom("foo", types))}, }, { name: "nil value", args: args{ t: ValueTypeString, }, - want: &OptionalValue{ov: *value.NewOptionalValue(value.TypeString, nil)}, + want: &OptionalValue{ov: *value.NewOptional(value.TypeString, nil)}, }, { name: "invalid value", @@ -73,7 +73,7 @@ func TestOptionalValueFrom(t *testing.T) { args: args{ v: ValueTypeString.ValueFrom("foo"), }, - want: &OptionalValue{ov: *value.NewOptionalValue(value.TypeString, value.TypeString.ValueFrom("foo", types))}, + want: &OptionalValue{ov: *value.NewOptional(value.TypeString, value.TypeString.ValueFrom("foo", types))}, }, { name: "empty value", @@ -106,7 +106,7 @@ func TestOptionalValue_Type(t *testing.T) { }{ { name: "ok", - value: &OptionalValue{ov: *value.NewOptionalValue(value.TypeBool, nil)}, + value: &OptionalValue{ov: *value.NewOptional(value.TypeBool, nil)}, want: ValueTypeBool, }, { @@ -138,7 +138,7 @@ func TestOptionalValue_Value(t *testing.T) { }{ { name: "ok", - value: &OptionalValue{ov: *value.OptionalValueFrom(value.TypeString.ValueFrom("foobar", types))}, + value: &OptionalValue{ov: *value.OptionalFrom(value.TypeString.ValueFrom("foobar", types))}, want: ValueTypeString.ValueFrom("foobar"), }, { @@ -175,7 +175,7 @@ func TestOptionalValue_TypeAndValue(t *testing.T) { }{ { name: "ok", - value: &OptionalValue{ov: *value.OptionalValueFrom(value.TypeString.ValueFrom("foobar", types))}, + value: &OptionalValue{ov: *value.OptionalFrom(value.TypeString.ValueFrom("foobar", types))}, wantt: ValueTypeString, wantv: ValueTypeString.ValueFrom("foobar"), }, @@ -219,17 +219,17 @@ func TestOptionalValue_SetValue(t *testing.T) { }{ { name: "set", - value: &OptionalValue{ov: *value.OptionalValueFrom(value.TypeString.ValueFrom("foo", types))}, + value: &OptionalValue{ov: *value.OptionalFrom(value.TypeString.ValueFrom("foo", types))}, args: args{v: ValueTypeString.ValueFrom("foobar")}, }, { name: "set to nil", - value: &OptionalValue{ov: *value.NewOptionalValue(value.TypeString, nil)}, + value: &OptionalValue{ov: *value.NewOptional(value.TypeString, nil)}, args: args{v: ValueTypeString.ValueFrom("foobar")}, }, { name: "invalid value", - value: &OptionalValue{ov: *value.NewOptionalValue(value.TypeString, nil)}, + value: &OptionalValue{ov: *value.NewOptional(value.TypeString, nil)}, args: args{v: ValueTypeNumber.ValueFrom(1)}, invalid: true, }, @@ -279,7 +279,7 @@ func TestOptionalValue_Clone(t *testing.T) { { name: "ok", target: &OptionalValue{ - ov: *value.NewOptionalValue(value.TypeString, value.TypeString.ValueFrom("foo", types)), + ov: *value.NewOptional(value.TypeString, value.TypeString.ValueFrom("foo", types)), }, }, { @@ -301,3 +301,52 @@ func TestOptionalValue_Clone(t *testing.T) { }) } } + +func TestOptionalValue_Cast(t *testing.T) { + type args struct { + t ValueType + } + tests := []struct { + name string + target *OptionalValue + args args + want *OptionalValue + }{ + { + name: "diff type", + target: &OptionalValue{ov: *value.OptionalFrom(value.TypeNumber.ValueFrom(1.1, types))}, + args: args{t: ValueTypeString}, + want: &OptionalValue{ov: *value.OptionalFrom(value.TypeString.ValueFrom("1.1", types))}, + }, + { + name: "same type", + target: &OptionalValue{ov: *value.OptionalFrom(value.TypeNumber.ValueFrom(1.1, types))}, + args: args{t: ValueTypeNumber}, + want: &OptionalValue{ov: *value.OptionalFrom(value.TypeNumber.ValueFrom(1.1, types))}, + }, + { + name: "failed to cast", + target: &OptionalValue{ov: *value.OptionalFrom(value.TypeLatLng.ValueFrom(LatLng{Lat: 1, Lng: 2}, types))}, + args: args{t: ValueTypeString}, + want: &OptionalValue{ov: *value.NewOptional(value.TypeString, nil)}, + }, + { + name: "empty", + target: &OptionalValue{}, + args: args{t: ValueTypeString}, + want: nil, + }, + { + name: "nil", + target: nil, + args: args{t: ValueTypeString}, + want: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.want, tt.target.Cast(tt.args.t)) + }) + } +} diff --git a/pkg/property/value_test.go b/pkg/property/value_test.go index a51528a3..236033bf 100644 --- a/pkg/property/value_test.go +++ b/pkg/property/value_test.go @@ -4,9 +4,223 @@ import ( "testing" "github.com/reearth/reearth-backend/pkg/dataset" + "github.com/reearth/reearth-backend/pkg/value" "github.com/stretchr/testify/assert" ) +func TestValue_IsEmpty(t *testing.T) { + tests := []struct { + name string + value *Value + want bool + }{ + { + name: "empty", + want: true, + }, + { + name: "nil", + want: true, + }, + { + name: "non-empty", + value: ValueTypeString.ValueFrom("foo"), + want: false, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + assert.Equal(t, tt.want, tt.value.IsEmpty()) + }) + } +} + +func TestValue_Clone(t *testing.T) { + tests := []struct { + name string + value *Value + want *Value + }{ + { + name: "ok", + value: ValueTypeString.ValueFrom("foo"), + want: &Value{ + v: *value.TypeString.ValueFrom("foo", types), + }, + }, + { + name: "nil", + value: nil, + want: nil, + }, + { + name: "empty", + value: &Value{}, + want: nil, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + assert.Equal(t, tt.want, tt.value.Clone()) + }) + } +} + +func TestValue_Value(t *testing.T) { + tests := []struct { + name string + value *Value + want interface{} + }{ + { + name: "ok", + value: ValueTypeString.ValueFrom("foo"), + want: "foo", + }, + { + name: "empty", + value: &Value{}, + }, + { + name: "nil", + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + if tt.want == nil { + assert.Nil(t, tt.value.Value()) + } else { + assert.Equal(t, tt.want, tt.value.Value()) + } + }) + } +} + +func TestValue_Type(t *testing.T) { + tests := []struct { + name string + value *Value + want ValueType + }{ + { + name: "ok", + value: ValueTypeString.ValueFrom("foo"), + want: ValueTypeString, + }, + { + name: "empty", + value: &Value{}, + want: ValueTypeUnknown, + }, + { + name: "nil", + want: ValueTypeUnknown, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + assert.Equal(t, tt.want, tt.value.Type()) + }) + } +} + +func TestValue_Interface(t *testing.T) { + tests := []struct { + name string + value *Value + want interface{} + }{ + { + name: "string", + value: ValueTypeString.ValueFrom("foo"), + want: "foo", + }, + { + name: "empty", + value: &Value{}, + want: nil, + }, + { + name: "nil", + value: nil, + want: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.want, tt.value.Interface()) + }) + } +} + +func TestValue_Cast(t *testing.T) { + type args struct { + t ValueType + } + tests := []struct { + name string + target *Value + args args + want *Value + }{ + { + name: "diff type", + target: ValueTypeNumber.ValueFrom(1.1), + args: args{t: ValueTypeString}, + want: ValueTypeString.ValueFrom("1.1"), + }, + { + name: "same type", + target: ValueTypeNumber.ValueFrom(1.1), + args: args{t: ValueTypeNumber}, + want: ValueTypeNumber.ValueFrom(1.1), + }, + { + name: "failed to cast", + target: ValueTypeLatLng.ValueFrom(LatLng{Lat: 1, Lng: 2}), + args: args{t: ValueTypeString}, + want: nil, + }, + { + name: "invalid type", + target: ValueTypeNumber.ValueFrom(1.1), + args: args{t: ValueTypeUnknown}, + want: nil, + }, + { + name: "empty", + target: &Value{}, + args: args{t: ValueTypeString}, + want: nil, + }, + { + name: "nil", + target: nil, + args: args{t: ValueTypeString}, + want: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.want, tt.target.Cast(tt.args.t)) + }) + } +} + func TestValueFromDataset(t *testing.T) { testCases := []struct { Name string @@ -62,3 +276,51 @@ func TestValueFromDataset(t *testing.T) { }) } } + +func TestValueFromStringOrNumber(t *testing.T) { + type args struct { + s string + } + tests := []struct { + name string + args args + want *Value + }{ + { + name: "string", + args: args{"aax"}, + want: ValueTypeString.ValueFrom("aax"), + }, + { + name: "number positive int", + args: args{"1023"}, + want: ValueTypeNumber.ValueFrom(1023), + }, + { + name: "number negative int", + args: args{"-1"}, + want: ValueTypeNumber.ValueFrom(-1), + }, + { + name: "number float", + args: args{"1.14"}, + want: ValueTypeNumber.ValueFrom(1.14), + }, + { + name: "bool true", + args: args{"true"}, + want: ValueTypeBool.ValueFrom(true), + }, + { + name: "bool false", + args: args{"false"}, + want: ValueTypeBool.ValueFrom(false), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.want, ValueFromStringOrNumber(tt.args.s)) + }) + } +} diff --git a/pkg/value/bool.go b/pkg/value/bool.go index 538e3f5f..70a31278 100644 --- a/pkg/value/bool.go +++ b/pkg/value/bool.go @@ -1,15 +1,29 @@ package value +import "strconv" + var TypeBool Type = "bool" type propertyBool struct{} func (*propertyBool) I2V(i interface{}) (interface{}, bool) { - if v, ok := i.(bool); ok { + switch v := i.(type) { + case bool: return v, true - } - if v, ok := i.(*bool); ok && v != nil { - return *v, true + case string: + if b, err := strconv.ParseBool(v); err == nil { + return b, true + } + case *bool: + if v != nil { + return *v, true + } + case *string: + if v != nil { + if b, err := strconv.ParseBool(*v); err == nil { + return b, true + } + } } return nil, false } diff --git a/pkg/value/bool_test.go b/pkg/value/bool_test.go new file mode 100644 index 00000000..4b96481f --- /dev/null +++ b/pkg/value/bool_test.go @@ -0,0 +1,61 @@ +package value + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_propertyBool_I2V(t *testing.T) { + tr := true + fa := false + trs1 := "true" + trs2 := "TRUE" + trs3 := "True" + trs4 := "T" + trs5 := "t" + trs6 := "1" + fas1 := "false" + fas2 := "FALSE" + fas3 := "False" + fas4 := "F" + fas5 := "f" + fas6 := "0" + + tests := []struct { + name string + args []interface{} + want1 interface{} + want2 bool + }{ + { + name: "true", + args: []interface{}{tr, trs1, trs2, trs3, trs4, trs5, trs6, &tr, &trs1, &trs2, &trs3, &trs4, &trs5, &trs6}, + want1: true, + want2: true, + }, + { + name: "false", + args: []interface{}{fa, fas1, fas2, fas3, fas4, fas5, fas6, &fa, &fas1, &fas2, &fas3, &fas4, &fas5, &fas6}, + want1: false, + want2: true, + }, + { + name: "nil", + args: []interface{}{"foo", (*bool)(nil), (*string)(nil), nil}, + want1: nil, + want2: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + p := &propertyBool{} + for i, v := range tt.args { + got1, got2 := p.I2V(v) + assert.Equal(t, tt.want1, got1, "test %d", i) + assert.Equal(t, tt.want2, got2, "test %d", i) + } + }) + } +} diff --git a/pkg/value/latlng.go b/pkg/value/latlng.go index 7612eb54..2bf97ab9 100644 --- a/pkg/value/latlng.go +++ b/pkg/value/latlng.go @@ -22,14 +22,21 @@ var TypeLatLng Type = "latlng" type propertyLatLng struct{} func (*propertyLatLng) I2V(i interface{}) (interface{}, bool) { - if v, ok := i.(LatLng); ok { + switch v := i.(type) { + case LatLng: return v, true - } else if v, ok := i.(*LatLng); ok { + case LatLngHeight: + return LatLng{Lat: v.Lat, Lng: v.Lng}, true + case *LatLng: if v != nil { return *v, true } - return nil, false + case *LatLngHeight: + if v != nil { + return LatLng{Lat: v.Lat, Lng: v.Lng}, true + } } + v := LatLng{} if err := mapstructure.Decode(i, &v); err != nil { return nil, false diff --git a/pkg/value/latlngheight.go b/pkg/value/latlngheight.go index 173f3875..f2120899 100644 --- a/pkg/value/latlngheight.go +++ b/pkg/value/latlngheight.go @@ -24,15 +24,19 @@ var TypeLatLngHeight Type = "latlngheight" type propertyLatLngHeight struct{} func (*propertyLatLngHeight) I2V(i interface{}) (interface{}, bool) { - if v, ok := i.(LatLngHeight); ok { + switch v := i.(type) { + case LatLngHeight: return v, true - } - - if v, ok := i.(*LatLngHeight); ok { + case LatLng: + return LatLngHeight{Lat: v.Lat, Lng: v.Lng, Height: 0}, true + case *LatLngHeight: + if v != nil { + return *v, true + } + case *LatLng: if v != nil { - return *v, false + return LatLngHeight{Lat: v.Lat, Lng: v.Lng, Height: 0}, true } - return nil, false } v := LatLngHeight{} diff --git a/pkg/value/number.go b/pkg/value/number.go index 275e5325..53ce8bfe 100644 --- a/pkg/value/number.go +++ b/pkg/value/number.go @@ -1,6 +1,9 @@ package value -import "encoding/json" +import ( + "encoding/json" + "strconv" +) var TypeNumber Type = "number" @@ -38,6 +41,10 @@ func (*propertyNumber) I2V(i interface{}) (interface{}, bool) { if f, err := v.Float64(); err == nil { return f, true } + case string: + if vfloat64, err := strconv.ParseFloat(v, 64); err == nil { + return vfloat64, true + } case *float64: if v != nil { return *v, true @@ -96,6 +103,12 @@ func (*propertyNumber) I2V(i interface{}) (interface{}, bool) { return f, true } } + case *string: + if v != nil { + if vfloat64, err := strconv.ParseFloat(*v, 64); err == nil { + return vfloat64, true + } + } } return nil, false } diff --git a/pkg/value/number_test.go b/pkg/value/number_test.go new file mode 100644 index 00000000..7ca44da5 --- /dev/null +++ b/pkg/value/number_test.go @@ -0,0 +1,99 @@ +package value + +import ( + "encoding/json" + "math" + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_propertyNumber_I2V(t *testing.T) { + z1 := 0 + z2 := 0.0 + z3 := "0" + z4 := json.Number("0") + z5 := json.Number("-0") + n1 := 1.12 + n2 := "1.12" + n3 := json.Number("1.12") + nn1 := -0.11 + nn2 := "-0.11" + nn3 := json.Number("-0.11") + nan1 := math.NaN() + nan2 := json.Number("NaN") + inf1 := math.Inf(0) + inf2 := json.Number("Infinity") + infn1 := math.Inf(-1) + infn2 := json.Number("-Infinity") + + tests := []struct { + name string + args []interface{} + want1 interface{} + want2 bool + }{ + { + name: "zero", + args: []interface{}{z1, z2, z3, z4, z5, &z1, &z2, &z3, &z4, &z5}, + want1: 0.0, + want2: true, + }, + { + name: "float", + args: []interface{}{n1, n2, n3, &n1, &n2, &n3}, + want1: 1.12, + want2: true, + }, + { + name: "negative float", + args: []interface{}{nn1, nn2, nn3, &nn1, &nn2, &nn3}, + want1: -0.11, + want2: true, + }, + { + name: "nan", + args: []interface{}{nan1, nan2}, + want1: math.NaN(), + want2: true, + }, + { + name: "inf", + args: []interface{}{inf1, inf2}, + want1: math.Inf(0), + want2: true, + }, + { + name: "negative inf", + args: []interface{}{infn1, infn2}, + want1: math.Inf(-1), + want2: true, + }, + { + name: "nil", + args: []interface{}{"foo", (*float64)(nil), (*string)(nil), (*int)(nil), (*json.Number)(nil), nil}, + want1: nil, + want2: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + p := &propertyNumber{} + for i, v := range tt.args { + got1, got2 := p.I2V(v) + if f, ok := tt.want1.(float64); ok { + if math.IsNaN(f) { + assert.True(t, math.IsNaN(tt.want1.(float64))) + } else { + assert.Equal(t, tt.want1, got1, "test %d", i) + } + } else { + assert.Equal(t, tt.want1, got1, "test %d", i) + } + + assert.Equal(t, tt.want2, got2, "test %d", i) + } + }) + } +} diff --git a/pkg/value/optional.go b/pkg/value/optional.go index 2b0b230e..8bf0b084 100644 --- a/pkg/value/optional.go +++ b/pkg/value/optional.go @@ -1,61 +1,74 @@ package value -type OptionalValue struct { +type Optional struct { t Type v *Value } -func NewOptionalValue(t Type, v *Value) *OptionalValue { +func NewOptional(t Type, v *Value) *Optional { if t == TypeUnknown || (v != nil && v.Type() != t) { return nil } - return &OptionalValue{ + return &Optional{ t: t, v: v, } } -func OptionalValueFrom(v *Value) *OptionalValue { +func OptionalFrom(v *Value) *Optional { if v.Type() == TypeUnknown { return nil } - return &OptionalValue{ + return &Optional{ t: v.Type(), v: v, } } -func (ov *OptionalValue) Type() Type { +func (ov *Optional) Type() Type { if ov == nil { return TypeUnknown } return ov.t } -func (ov *OptionalValue) Value() *Value { +func (ov *Optional) Value() *Value { if ov == nil || ov.t == TypeUnknown || ov.v == nil { return nil } return ov.v.Clone() } -func (ov *OptionalValue) TypeAndValue() (Type, *Value) { +func (ov *Optional) TypeAndValue() (Type, *Value) { return ov.Type(), ov.Value() } -func (ov *OptionalValue) SetValue(v *Value) { +func (ov *Optional) SetValue(v *Value) { if ov == nil || ov.t == TypeUnknown || (v != nil && ov.t != v.Type()) { return } ov.v = v.Clone() } -func (ov *OptionalValue) Clone() *OptionalValue { +func (ov *Optional) Clone() *Optional { if ov == nil { return nil } - return &OptionalValue{ + return &Optional{ t: ov.t, v: ov.v.Clone(), } } + +// Cast tries to convert the value to the new type and generates a new Optional. +func (ov *Optional) Cast(t Type, p TypePropertyMap) *Optional { + if ov == nil || ov.t == TypeUnknown { + return nil + } + if ov.v == nil { + return NewOptional(t, nil) + } + + nv := ov.v.Cast(t, p) + return NewOptional(t, nv) +} diff --git a/pkg/value/optional_test.go b/pkg/value/optional_test.go index 19e0f601..70eec855 100644 --- a/pkg/value/optional_test.go +++ b/pkg/value/optional_test.go @@ -6,7 +6,7 @@ import ( "github.com/stretchr/testify/assert" ) -func TestNewOptionalValue(t *testing.T) { +func TestNewOptional(t *testing.T) { type args struct { t Type v *Value @@ -14,7 +14,7 @@ func TestNewOptionalValue(t *testing.T) { tests := []struct { name string args args - want *OptionalValue + want *Optional }{ { name: "default type", @@ -22,7 +22,7 @@ func TestNewOptionalValue(t *testing.T) { t: TypeString, v: TypeString.ValueFrom("foo", nil), }, - want: &OptionalValue{t: TypeString, v: TypeString.ValueFrom("foo", nil)}, + want: &Optional{t: TypeString, v: TypeString.ValueFrom("foo", nil)}, }, { name: "custom type", @@ -30,14 +30,14 @@ func TestNewOptionalValue(t *testing.T) { t: Type("foo"), v: &Value{t: Type("foo")}, }, - want: &OptionalValue{t: Type("foo"), v: &Value{t: Type("foo")}}, + want: &Optional{t: Type("foo"), v: &Value{t: Type("foo")}}, }, { name: "nil value", args: args{ t: Type("foo"), }, - want: &OptionalValue{t: Type("foo"), v: nil}, + want: &Optional{t: Type("foo"), v: nil}, }, { name: "invalid value", @@ -61,33 +61,33 @@ func TestNewOptionalValue(t *testing.T) { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() - assert.Equal(t, tt.want, NewOptionalValue(tt.args.t, tt.args.v)) + assert.Equal(t, tt.want, NewOptional(tt.args.t, tt.args.v)) }) } } -func TestOptionalValueFrom(t *testing.T) { +func TestOptionalFrom(t *testing.T) { type args struct { v *Value } tests := []struct { name string args args - want *OptionalValue + want *Optional }{ { name: "default type", args: args{ v: TypeString.ValueFrom("foo", nil), }, - want: &OptionalValue{t: TypeString, v: TypeString.ValueFrom("foo", nil)}, + want: &Optional{t: TypeString, v: TypeString.ValueFrom("foo", nil)}, }, { name: "custom type", args: args{ v: &Value{t: Type("foo")}, }, - want: &OptionalValue{t: Type("foo"), v: &Value{t: Type("foo")}}, + want: &Optional{t: Type("foo"), v: &Value{t: Type("foo")}}, }, { name: "invalid value", @@ -107,25 +107,25 @@ func TestOptionalValueFrom(t *testing.T) { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() - assert.Equal(t, tt.want, OptionalValueFrom(tt.args.v)) + assert.Equal(t, tt.want, OptionalFrom(tt.args.v)) }) } } -func TestOptionalValue_Type(t *testing.T) { +func TestOptional_Type(t *testing.T) { tests := []struct { name string - value *OptionalValue + value *Optional want Type }{ { name: "ok", - value: &OptionalValue{t: Type("foo")}, + value: &Optional{t: Type("foo")}, want: Type("foo"), }, { name: "empty", - value: &OptionalValue{}, + value: &Optional{}, want: TypeUnknown, }, { @@ -144,20 +144,20 @@ func TestOptionalValue_Type(t *testing.T) { } } -func TestOptionalValue_Value(t *testing.T) { +func TestOptional_Value(t *testing.T) { tests := []struct { name string - value *OptionalValue + value *Optional want *Value }{ { name: "ok", - value: &OptionalValue{t: TypeString, v: &Value{t: TypeString, v: "foobar"}}, + value: &Optional{t: TypeString, v: &Value{t: TypeString, v: "foobar"}}, want: &Value{t: TypeString, v: "foobar"}, }, { name: "empty", - value: &OptionalValue{}, + value: &Optional{}, want: nil, }, { @@ -180,22 +180,22 @@ func TestOptionalValue_Value(t *testing.T) { } } -func TestOptionalValue_TypeAndValue(t *testing.T) { +func TestOptional_TypeAndValue(t *testing.T) { tests := []struct { name string - value *OptionalValue + value *Optional wantt Type wantv *Value }{ { name: "ok", - value: &OptionalValue{t: TypeString, v: &Value{t: TypeString, v: "foobar"}}, + value: &Optional{t: TypeString, v: &Value{t: TypeString, v: "foobar"}}, wantt: TypeString, wantv: &Value{t: TypeString, v: "foobar"}, }, { name: "empty", - value: &OptionalValue{}, + value: &Optional{}, wantt: TypeUnknown, wantv: nil, }, @@ -221,19 +221,19 @@ func TestOptionalValue_TypeAndValue(t *testing.T) { } } -func TestOptionalValue_SetValue(t *testing.T) { +func TestOptional_SetValue(t *testing.T) { type args struct { v *Value } tests := []struct { name string - value *OptionalValue + value *Optional args args invalid bool }{ { name: "set", - value: &OptionalValue{ + value: &Optional{ t: TypeString, v: &Value{t: TypeString, v: "foobar"}, }, @@ -241,14 +241,14 @@ func TestOptionalValue_SetValue(t *testing.T) { }, { name: "set to nil", - value: &OptionalValue{ + value: &Optional{ t: TypeString, }, args: args{v: &Value{t: TypeString, v: "bar"}}, }, { name: "invalid value", - value: &OptionalValue{ + value: &Optional{ t: TypeNumber, v: &Value{t: TypeNumber, v: 1}, }, @@ -257,14 +257,14 @@ func TestOptionalValue_SetValue(t *testing.T) { }, { name: "nil value", - value: &OptionalValue{ + value: &Optional{ t: TypeNumber, v: &Value{t: TypeNumber, v: 1}, }, }, { name: "empty", - value: &OptionalValue{}, + value: &Optional{}, args: args{v: &Value{t: TypeString, v: "bar"}}, invalid: true, }, @@ -299,18 +299,18 @@ func TestOptionalValue_SetValue(t *testing.T) { } } -func TestOptionalValue_Clone(t *testing.T) { +func TestOptional_Clone(t *testing.T) { tests := []struct { name string - target *OptionalValue + target *Optional }{ { name: "ok", - target: &OptionalValue{t: TypeString, v: TypeString.ValueFrom("foo", nil)}, + target: &Optional{t: TypeString, v: TypeString.ValueFrom("foo", nil)}, }, { name: "empty", - target: &OptionalValue{}, + target: &Optional{}, }, { name: "nil", @@ -328,3 +328,59 @@ func TestOptionalValue_Clone(t *testing.T) { }) } } + +func TestOptional_Cast(t *testing.T) { + type args struct { + t Type + p TypePropertyMap + } + tests := []struct { + name string + target *Optional + args args + want *Optional + }{ + { + name: "diff type", + target: &Optional{t: TypeNumber, v: TypeNumber.ValueFrom(1.1, nil)}, + args: args{t: TypeString}, + want: &Optional{t: TypeString, v: TypeString.ValueFrom("1.1", nil)}, + }, + { + name: "same type", + target: &Optional{t: TypeNumber, v: TypeNumber.ValueFrom(1.1, nil)}, + args: args{t: TypeNumber}, + want: &Optional{t: TypeNumber, v: TypeNumber.ValueFrom(1.1, nil)}, + }, + { + name: "nil value", + target: &Optional{t: TypeNumber}, + args: args{t: TypeString}, + want: &Optional{t: TypeString}, + }, + { + name: "failed to cast", + target: &Optional{t: TypeLatLng, v: TypeLatLng.ValueFrom(LatLng{Lat: 1, Lng: 2}, nil)}, + args: args{t: TypeString}, + want: &Optional{t: TypeString}, + }, + { + name: "empty", + target: &Optional{}, + args: args{t: TypeString}, + want: nil, + }, + { + name: "nil", + target: nil, + args: args{t: TypeString}, + want: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.want, tt.target.Cast(tt.args.t, tt.args.p)) + }) + } +} diff --git a/pkg/value/string.go b/pkg/value/string.go index 3979d0cb..03bcdd94 100644 --- a/pkg/value/string.go +++ b/pkg/value/string.go @@ -1,5 +1,10 @@ package value +import ( + "fmt" + "strconv" +) + var TypeString Type = "string" type propertyString struct{} @@ -7,9 +12,14 @@ type propertyString struct{} func (*propertyString) I2V(i interface{}) (interface{}, bool) { if v, ok := i.(string); ok { return v, true - } - if v, ok := i.(*string); ok { + } else if v, ok := i.(*string); ok && v != nil { return *v, true + } else if v, ok := i.(float64); ok { + return strconv.FormatFloat(v, 'f', -1, 64), true + } else if v, ok := i.(*float64); ok && v != nil { + return strconv.FormatFloat(*v, 'f', -1, 64), true + } else if v, ok := i.(fmt.Stringer); ok && v != nil { + return v.String(), true } return nil, false } diff --git a/pkg/value/string_test.go b/pkg/value/string_test.go new file mode 100644 index 00000000..d39b3ccc --- /dev/null +++ b/pkg/value/string_test.go @@ -0,0 +1,57 @@ +package value + +import ( + "net/url" + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_propertyString_I2V(t *testing.T) { + s := "foobar" + n := 1.12 + u, _ := url.Parse("https://reearth.io") + + tests := []struct { + name string + args []interface{} + want1 interface{} + want2 bool + }{ + { + name: "string", + args: []interface{}{s, &s}, + want1: "foobar", + want2: true, + }, + { + name: "number", + args: []interface{}{n, &n}, + want1: "1.12", + want2: true, + }, + { + name: "url", + args: []interface{}{u}, + want1: "https://reearth.io", + want2: true, + }, + { + name: "nil", + args: []interface{}{(*string)(nil), nil}, + want1: nil, + want2: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + p := &propertyString{} + for i, v := range tt.args { + got1, got2 := p.I2V(v) + assert.Equal(t, tt.want1, got1, "test %d", i) + assert.Equal(t, tt.want2, got2, "test %d", i) + } + }) + } +} diff --git a/pkg/value/url.go b/pkg/value/url.go index 0745a793..07f2a5c6 100644 --- a/pkg/value/url.go +++ b/pkg/value/url.go @@ -24,6 +24,12 @@ func (*propertyURL) I2V(i interface{}) (interface{}, bool) { } } + if v, ok := i.(*string); ok && v != nil { + if u, err := url.Parse(*v); err == nil { + return u, true + } + } + return nil, false } diff --git a/pkg/value/value.go b/pkg/value/value.go index 79cea656..aac797f1 100644 --- a/pkg/value/value.go +++ b/pkg/value/value.go @@ -80,3 +80,13 @@ func (v *Value) Validate() bool { func (v *Value) MarshalJSON() ([]byte, error) { return json.Marshal(v.Interface()) } + +func (v *Value) Cast(t Type, p TypePropertyMap) *Value { + if v == nil || v.t == TypeUnknown { + return nil + } + if v.t == t { + return v.Clone() + } + return t.ValueFrom(v.v, p) +} diff --git a/pkg/value/value_test.go b/pkg/value/value_test.go index b71a78fb..34c47320 100644 --- a/pkg/value/value_test.go +++ b/pkg/value/value_test.go @@ -262,3 +262,59 @@ func TestValue_Interface(t *testing.T) { }) } } + +func TestValue_Cast(t *testing.T) { + type args struct { + t Type + p TypePropertyMap + } + tests := []struct { + name string + target *Value + args args + want *Value + }{ + { + name: "diff type", + target: &Value{t: TypeNumber, v: 1.1}, + args: args{t: TypeString}, + want: &Value{t: TypeString, v: "1.1"}, + }, + { + name: "same type", + target: &Value{t: TypeNumber, v: 1.1}, + args: args{t: TypeNumber}, + want: &Value{t: TypeNumber, v: 1.1}, + }, + { + name: "failed to cast", + target: &Value{t: TypeLatLng, v: LatLng{Lat: 1, Lng: 2}}, + args: args{t: TypeString}, + want: nil, + }, + { + name: "invalid value", + target: &Value{t: TypeNumber}, + args: args{t: TypeString}, + want: nil, + }, + { + name: "empty", + target: &Value{}, + args: args{t: TypeString}, + want: nil, + }, + { + name: "nil", + target: nil, + args: args{t: TypeString}, + want: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.want, tt.target.Cast(tt.args.t, tt.args.p)) + }) + } +}