Skip to content

Commit

Permalink
for dataconv: fix StarString, add GoToStarlarkViaJSON (#119)
Browse files Browse the repository at this point in the history
* draft for fix

* renaming

* typed nil for starstring

* fix typed nil

* add test cases

* for go ver
  • Loading branch information
hyorigo authored Aug 12, 2024
1 parent 97c1c1f commit 6f89608
Show file tree
Hide file tree
Showing 5 changed files with 182 additions and 42 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ jobs:
1.18.10,
1.19.13,
1.20.14,
1.21.12,
1.22.5,
1.21.13,
1.22.6,
]
permissions:
contents: read
Expand Down
19 changes: 18 additions & 1 deletion dataconv/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -218,8 +218,11 @@ func TypeConvert(data interface{}) interface{} {
}
}

// StarString returns the string representation of a starlark.Value.
// StarString returns the string representation of a starlark.Value, i.e. converts Starlark values to Go strings.
func StarString(x starlark.Value) string {
if IsInterfaceNil(x) {
return emptyStr
}
switch v := x.(type) {
case starlark.String:
return v.GoString()
Expand All @@ -230,6 +233,20 @@ func StarString(x starlark.Value) string {
}
}

// GoToStarlarkViaJSON converts any Go value that can be marshaled into JSON into a corresponding Starlark value.
// It first marshals the Go value into a JSON string with the standard Go JSON package,
// then decodes this JSON string into a Starlark value with the official Starlark JSON module.
func GoToStarlarkViaJSON(v interface{}) (starlark.Value, error) {
if v == nil {
return starlark.None, nil
}
bs, err := json.Marshal(v)
if err != nil {
return starlark.None, err
}
return DecodeStarlarkJSON(bs)
}

// GetThreadContext returns the context of the given thread, or new context if not found.
func GetThreadContext(thread *starlark.Thread) context.Context {
if thread != nil {
Expand Down
133 changes: 133 additions & 0 deletions dataconv/helper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1102,6 +1102,16 @@ func TestStarString(t *testing.T) {
val starlark.Value
want string
}{
{
name: "nil",
val: nil,
want: "",
},
{
name: "typed nil",
val: (*starlark.List)(nil),
want: "",
},
{
name: "string",
val: starlark.String("hello"),
Expand Down Expand Up @@ -1147,6 +1157,129 @@ func TestStarString(t *testing.T) {
}
}

func TestGoToStarlarkViaJSON(t *testing.T) {
now := time.Now()
stime := starlark.String(now.Format(time.RFC3339Nano))

tests := []struct {
name string
input interface{}
want starlark.Value
wantErr bool
}{
{
name: "nil",
input: nil,
want: starlark.None,
},
{
name: "typed nil",
input: (*int)(nil),
want: starlark.None,
},
{
name: "bool true",
input: true,
want: starlark.Bool(true),
},
{
name: "bool false",
input: false,
want: starlark.Bool(false),
},
{
name: "int",
input: 42,
want: starlark.MakeInt(42),
},
{
name: "negative int",
input: -42,
want: starlark.MakeInt(-42),
},
{
name: "float",
input: 3.14,
want: starlark.Float(3.14),
},
{
name: "string",
input: "hello",
want: starlark.String("hello"),
},
{
name: "time",
input: now,
want: stime,
},
{
name: "map",
input: map[string]interface{}{"foo": 42},
want: func() *starlark.Dict {
d := starlark.NewDict(2)
d.SetKey(starlark.String("foo"), starlark.MakeInt(42))
return d
}(),
},
{
name: "slice",
input: []interface{}{1, 2, "three"},
want: starlark.NewList([]starlark.Value{
starlark.MakeInt(1),
starlark.MakeInt(2),
starlark.String("three"),
}),
},
{
name: "struct",
input: struct {
Name string `json:"name"`
Value int `json:"value"`
}{"test", 123},
want: func() *starlark.Dict {
d := starlark.NewDict(2)
d.SetKey(starlark.String("name"), starlark.String("test"))
d.SetKey(starlark.String("value"), starlark.MakeInt(123))
return d
}(),
},
{
name: "complex nested structure",
input: map[string]interface{}{"nested": map[string]interface{}{"key": "value", "list": []interface{}{1, 2, 3}}},
want: func() *starlark.Dict {
innerDict := starlark.NewDict(2)
innerDict.SetKey(starlark.String("key"), starlark.String("value"))
innerDict.SetKey(starlark.String("list"), starlark.NewList([]starlark.Value{
starlark.MakeInt(1),
starlark.MakeInt(2),
starlark.MakeInt(3),
}))
d := starlark.NewDict(1)
d.SetKey(starlark.String("nested"), innerDict)
return d
}(),
},
{
name: "invalid type",
input: make(chan int),
wantErr: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := GoToStarlarkViaJSON(tt.input)
if (err != nil) != tt.wantErr {
t.Errorf("GoToStarlarkViaJSON() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !tt.wantErr && !reflect.DeepEqual(got, tt.want) {
t.Errorf("GoToStarlarkViaJSON() got = %v, want %v", got, tt.want)
}
})
}
}

func TestGetThreadContext(t *testing.T) {
bkg := context.Background()
t.Run("nil thread", func(t *testing.T) {
Expand Down
27 changes: 9 additions & 18 deletions dataconv/marshal.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package dataconv
// Based on https://github.com/qri-io/starlib/tree/master/util with some modifications and additions

import (
"errors"
"fmt"
"time"

Expand Down Expand Up @@ -131,6 +132,14 @@ func Unmarshal(x starlark.Value) (val interface{}, err error) {
return jo, nil
}

// for typed nil or nil
if IsInterfaceNil(x) {
if x == nil {
return nil, errors.New("nil value")
}
return nil, fmt.Errorf("typed nil value: %T", x)
}

// switch on the type of the value (common types)
switch v := x.(type) {
case starlark.NoneType:
Expand Down Expand Up @@ -290,32 +299,14 @@ func Unmarshal(x starlark.Value) (val interface{}, err error) {
}
val = am
case *convert.GoSlice:
if IsInterfaceNil(v) {
err = fmt.Errorf("nil GoSlice")
return
}
val = v.Value().Interface()
case *convert.GoMap:
if IsInterfaceNil(v) {
err = fmt.Errorf("nil GoMap")
return
}
val = v.Value().Interface()
case *convert.GoStruct:
if IsInterfaceNil(v) {
err = fmt.Errorf("nil GoStruct")
return
}
val = v.Value().Interface()
case *convert.GoInterface:
if IsInterfaceNil(v) {
err = fmt.Errorf("nil GoInterface")
return
}
val = v.Value().Interface()
default:
//fmt.Println("errbadtype:", x.Type())
//err = fmt.Errorf("unrecognized starlark type: %s", x.Type())
err = fmt.Errorf("unrecognized starlark type: %T", x)
}
return
Expand Down
41 changes: 20 additions & 21 deletions dataconv/marshal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,10 +147,6 @@ func TestUnmarshal(t *testing.T) {
if err := intDict.SetKey(starlark.MakeInt(42*2), starlark.MakeInt(42)); err != nil {
t.Fatal(err)
}
nilDict := starlark.NewDict(1)
if err := nilDict.SetKey(starlark.String("foo"), nil); err != nil {
t.Fatal(err)
}
cycDict := starlark.NewDict(2)
if err := cycDict.SetKey(starlark.String("foo"), starlark.MakeInt(42)); err != nil {
t.Fatal(err)
Expand Down Expand Up @@ -214,21 +210,24 @@ func TestUnmarshal(t *testing.T) {
}{"Aloha", 100}

var (
nilGs *convert.GoSlice
nilGm *convert.GoMap
nilGst *convert.GoStruct
nilGif *convert.GoInterface
nilList *starlark.List
nilDict *starlark.Dict
nilGs *convert.GoSlice
nilGm *convert.GoMap
nilGst *convert.GoStruct
nilGif *convert.GoInterface
)
cases := []struct {
in starlark.Value
want interface{}
err string
}{
{nil, nil, "unrecognized starlark type: <nil>"},
{nilDict, nil, "unmarshaling starlark value: unrecognized starlark type: <nil>"},
{srtNil, nil, "unrecognized starlark type: <nil>"},
{starlark.NewList([]starlark.Value{starlark.MakeInt(42), nil}), nil, "unrecognized starlark type: <nil>"},
{starlark.Tuple([]starlark.Value{starlark.MakeInt(42), nil}), nil, "unrecognized starlark type: <nil>"},
{nil, nil, "nil value"},
{nilList, nil, "typed nil value: *starlark.List"},
{nilDict, nil, "typed nil value: *starlark.Dict"},
{srtNil, nil, "nil value"},
{starlark.NewList([]starlark.Value{starlark.MakeInt(42), nil}), nil, "nil value"},
{starlark.Tuple([]starlark.Value{starlark.MakeInt(42), nil}), nil, "nil value"},
{starlark.None, nil, ""},
{starlark.True, true, ""},
{starlark.String("foo"), "foo", ""},
Expand Down Expand Up @@ -271,14 +270,14 @@ func TestUnmarshal(t *testing.T) {
{convert.NewGoMap(map[string]int{"foo": 42}), map[string]int{"foo": 42}, ""},
{convert.NewStruct(gs), gs, ""},
{convert.MakeGoInterface("Hello, World!"), "Hello, World!", ""},
{(*convert.GoSlice)(nil), nil, "nil GoSlice"},
{nilGs, nil, "nil GoSlice"},
{(*convert.GoMap)(nil), nil, "nil GoMap"},
{nilGm, nil, "nil GoMap"},
{(*convert.GoStruct)(nil), nil, "nil GoStruct"},
{nilGst, nil, "nil GoStruct"},
{(*convert.GoInterface)(nil), nil, "nil GoInterface"},
{nilGif, nil, "nil GoInterface"},
{(*convert.GoSlice)(nil), nil, "typed nil value: *convert.GoSlice"},
{nilGs, nil, "typed nil value: *convert.GoSlice"},
{(*convert.GoMap)(nil), nil, "typed nil value: *convert.GoMap"},
{nilGm, nil, "typed nil value: *convert.GoMap"},
{(*convert.GoStruct)(nil), nil, "typed nil value: *convert.GoStruct"},
{nilGst, nil, "typed nil value: *convert.GoStruct"},
{(*convert.GoInterface)(nil), nil, "typed nil value: *convert.GoInterface"},
{nilGif, nil, "typed nil value: *convert.GoInterface"},
{msb, nil, "unrecognized starlark type: *starlark.Builtin"},
{sf, nil, "unrecognized starlark type: *starlark.Function"},
{sse, nil, "unrecognized starlark type: *starlark.Builtin"},
Expand Down

0 comments on commit 6f89608

Please sign in to comment.