Skip to content

Commit 6f89608

Browse files
authored
for dataconv: fix StarString, add GoToStarlarkViaJSON (#119)
* draft for fix * renaming * typed nil for starstring * fix typed nil * add test cases * for go ver
1 parent 97c1c1f commit 6f89608

File tree

5 files changed

+182
-42
lines changed

5 files changed

+182
-42
lines changed

.github/workflows/build.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ jobs:
3232
1.18.10,
3333
1.19.13,
3434
1.20.14,
35-
1.21.12,
36-
1.22.5,
35+
1.21.13,
36+
1.22.6,
3737
]
3838
permissions:
3939
contents: read

dataconv/helper.go

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,8 +218,11 @@ func TypeConvert(data interface{}) interface{} {
218218
}
219219
}
220220

221-
// StarString returns the string representation of a starlark.Value.
221+
// StarString returns the string representation of a starlark.Value, i.e. converts Starlark values to Go strings.
222222
func StarString(x starlark.Value) string {
223+
if IsInterfaceNil(x) {
224+
return emptyStr
225+
}
223226
switch v := x.(type) {
224227
case starlark.String:
225228
return v.GoString()
@@ -230,6 +233,20 @@ func StarString(x starlark.Value) string {
230233
}
231234
}
232235

236+
// GoToStarlarkViaJSON converts any Go value that can be marshaled into JSON into a corresponding Starlark value.
237+
// It first marshals the Go value into a JSON string with the standard Go JSON package,
238+
// then decodes this JSON string into a Starlark value with the official Starlark JSON module.
239+
func GoToStarlarkViaJSON(v interface{}) (starlark.Value, error) {
240+
if v == nil {
241+
return starlark.None, nil
242+
}
243+
bs, err := json.Marshal(v)
244+
if err != nil {
245+
return starlark.None, err
246+
}
247+
return DecodeStarlarkJSON(bs)
248+
}
249+
233250
// GetThreadContext returns the context of the given thread, or new context if not found.
234251
func GetThreadContext(thread *starlark.Thread) context.Context {
235252
if thread != nil {

dataconv/helper_test.go

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1102,6 +1102,16 @@ func TestStarString(t *testing.T) {
11021102
val starlark.Value
11031103
want string
11041104
}{
1105+
{
1106+
name: "nil",
1107+
val: nil,
1108+
want: "",
1109+
},
1110+
{
1111+
name: "typed nil",
1112+
val: (*starlark.List)(nil),
1113+
want: "",
1114+
},
11051115
{
11061116
name: "string",
11071117
val: starlark.String("hello"),
@@ -1147,6 +1157,129 @@ func TestStarString(t *testing.T) {
11471157
}
11481158
}
11491159

1160+
func TestGoToStarlarkViaJSON(t *testing.T) {
1161+
now := time.Now()
1162+
stime := starlark.String(now.Format(time.RFC3339Nano))
1163+
1164+
tests := []struct {
1165+
name string
1166+
input interface{}
1167+
want starlark.Value
1168+
wantErr bool
1169+
}{
1170+
{
1171+
name: "nil",
1172+
input: nil,
1173+
want: starlark.None,
1174+
},
1175+
{
1176+
name: "typed nil",
1177+
input: (*int)(nil),
1178+
want: starlark.None,
1179+
},
1180+
{
1181+
name: "bool true",
1182+
input: true,
1183+
want: starlark.Bool(true),
1184+
},
1185+
{
1186+
name: "bool false",
1187+
input: false,
1188+
want: starlark.Bool(false),
1189+
},
1190+
{
1191+
name: "int",
1192+
input: 42,
1193+
want: starlark.MakeInt(42),
1194+
},
1195+
{
1196+
name: "negative int",
1197+
input: -42,
1198+
want: starlark.MakeInt(-42),
1199+
},
1200+
{
1201+
name: "float",
1202+
input: 3.14,
1203+
want: starlark.Float(3.14),
1204+
},
1205+
{
1206+
name: "string",
1207+
input: "hello",
1208+
want: starlark.String("hello"),
1209+
},
1210+
{
1211+
name: "time",
1212+
input: now,
1213+
want: stime,
1214+
},
1215+
{
1216+
name: "map",
1217+
input: map[string]interface{}{"foo": 42},
1218+
want: func() *starlark.Dict {
1219+
d := starlark.NewDict(2)
1220+
d.SetKey(starlark.String("foo"), starlark.MakeInt(42))
1221+
return d
1222+
}(),
1223+
},
1224+
{
1225+
name: "slice",
1226+
input: []interface{}{1, 2, "three"},
1227+
want: starlark.NewList([]starlark.Value{
1228+
starlark.MakeInt(1),
1229+
starlark.MakeInt(2),
1230+
starlark.String("three"),
1231+
}),
1232+
},
1233+
{
1234+
name: "struct",
1235+
input: struct {
1236+
Name string `json:"name"`
1237+
Value int `json:"value"`
1238+
}{"test", 123},
1239+
want: func() *starlark.Dict {
1240+
d := starlark.NewDict(2)
1241+
d.SetKey(starlark.String("name"), starlark.String("test"))
1242+
d.SetKey(starlark.String("value"), starlark.MakeInt(123))
1243+
return d
1244+
}(),
1245+
},
1246+
{
1247+
name: "complex nested structure",
1248+
input: map[string]interface{}{"nested": map[string]interface{}{"key": "value", "list": []interface{}{1, 2, 3}}},
1249+
want: func() *starlark.Dict {
1250+
innerDict := starlark.NewDict(2)
1251+
innerDict.SetKey(starlark.String("key"), starlark.String("value"))
1252+
innerDict.SetKey(starlark.String("list"), starlark.NewList([]starlark.Value{
1253+
starlark.MakeInt(1),
1254+
starlark.MakeInt(2),
1255+
starlark.MakeInt(3),
1256+
}))
1257+
d := starlark.NewDict(1)
1258+
d.SetKey(starlark.String("nested"), innerDict)
1259+
return d
1260+
}(),
1261+
},
1262+
{
1263+
name: "invalid type",
1264+
input: make(chan int),
1265+
wantErr: true,
1266+
},
1267+
}
1268+
1269+
for _, tt := range tests {
1270+
t.Run(tt.name, func(t *testing.T) {
1271+
got, err := GoToStarlarkViaJSON(tt.input)
1272+
if (err != nil) != tt.wantErr {
1273+
t.Errorf("GoToStarlarkViaJSON() error = %v, wantErr %v", err, tt.wantErr)
1274+
return
1275+
}
1276+
if !tt.wantErr && !reflect.DeepEqual(got, tt.want) {
1277+
t.Errorf("GoToStarlarkViaJSON() got = %v, want %v", got, tt.want)
1278+
}
1279+
})
1280+
}
1281+
}
1282+
11501283
func TestGetThreadContext(t *testing.T) {
11511284
bkg := context.Background()
11521285
t.Run("nil thread", func(t *testing.T) {

dataconv/marshal.go

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package dataconv
33
// Based on https://github.com/qri-io/starlib/tree/master/util with some modifications and additions
44

55
import (
6+
"errors"
67
"fmt"
78
"time"
89

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

135+
// for typed nil or nil
136+
if IsInterfaceNil(x) {
137+
if x == nil {
138+
return nil, errors.New("nil value")
139+
}
140+
return nil, fmt.Errorf("typed nil value: %T", x)
141+
}
142+
134143
// switch on the type of the value (common types)
135144
switch v := x.(type) {
136145
case starlark.NoneType:
@@ -290,32 +299,14 @@ func Unmarshal(x starlark.Value) (val interface{}, err error) {
290299
}
291300
val = am
292301
case *convert.GoSlice:
293-
if IsInterfaceNil(v) {
294-
err = fmt.Errorf("nil GoSlice")
295-
return
296-
}
297302
val = v.Value().Interface()
298303
case *convert.GoMap:
299-
if IsInterfaceNil(v) {
300-
err = fmt.Errorf("nil GoMap")
301-
return
302-
}
303304
val = v.Value().Interface()
304305
case *convert.GoStruct:
305-
if IsInterfaceNil(v) {
306-
err = fmt.Errorf("nil GoStruct")
307-
return
308-
}
309306
val = v.Value().Interface()
310307
case *convert.GoInterface:
311-
if IsInterfaceNil(v) {
312-
err = fmt.Errorf("nil GoInterface")
313-
return
314-
}
315308
val = v.Value().Interface()
316309
default:
317-
//fmt.Println("errbadtype:", x.Type())
318-
//err = fmt.Errorf("unrecognized starlark type: %s", x.Type())
319310
err = fmt.Errorf("unrecognized starlark type: %T", x)
320311
}
321312
return

dataconv/marshal_test.go

Lines changed: 20 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -147,10 +147,6 @@ func TestUnmarshal(t *testing.T) {
147147
if err := intDict.SetKey(starlark.MakeInt(42*2), starlark.MakeInt(42)); err != nil {
148148
t.Fatal(err)
149149
}
150-
nilDict := starlark.NewDict(1)
151-
if err := nilDict.SetKey(starlark.String("foo"), nil); err != nil {
152-
t.Fatal(err)
153-
}
154150
cycDict := starlark.NewDict(2)
155151
if err := cycDict.SetKey(starlark.String("foo"), starlark.MakeInt(42)); err != nil {
156152
t.Fatal(err)
@@ -214,21 +210,24 @@ func TestUnmarshal(t *testing.T) {
214210
}{"Aloha", 100}
215211

216212
var (
217-
nilGs *convert.GoSlice
218-
nilGm *convert.GoMap
219-
nilGst *convert.GoStruct
220-
nilGif *convert.GoInterface
213+
nilList *starlark.List
214+
nilDict *starlark.Dict
215+
nilGs *convert.GoSlice
216+
nilGm *convert.GoMap
217+
nilGst *convert.GoStruct
218+
nilGif *convert.GoInterface
221219
)
222220
cases := []struct {
223221
in starlark.Value
224222
want interface{}
225223
err string
226224
}{
227-
{nil, nil, "unrecognized starlark type: <nil>"},
228-
{nilDict, nil, "unmarshaling starlark value: unrecognized starlark type: <nil>"},
229-
{srtNil, nil, "unrecognized starlark type: <nil>"},
230-
{starlark.NewList([]starlark.Value{starlark.MakeInt(42), nil}), nil, "unrecognized starlark type: <nil>"},
231-
{starlark.Tuple([]starlark.Value{starlark.MakeInt(42), nil}), nil, "unrecognized starlark type: <nil>"},
225+
{nil, nil, "nil value"},
226+
{nilList, nil, "typed nil value: *starlark.List"},
227+
{nilDict, nil, "typed nil value: *starlark.Dict"},
228+
{srtNil, nil, "nil value"},
229+
{starlark.NewList([]starlark.Value{starlark.MakeInt(42), nil}), nil, "nil value"},
230+
{starlark.Tuple([]starlark.Value{starlark.MakeInt(42), nil}), nil, "nil value"},
232231
{starlark.None, nil, ""},
233232
{starlark.True, true, ""},
234233
{starlark.String("foo"), "foo", ""},
@@ -271,14 +270,14 @@ func TestUnmarshal(t *testing.T) {
271270
{convert.NewGoMap(map[string]int{"foo": 42}), map[string]int{"foo": 42}, ""},
272271
{convert.NewStruct(gs), gs, ""},
273272
{convert.MakeGoInterface("Hello, World!"), "Hello, World!", ""},
274-
{(*convert.GoSlice)(nil), nil, "nil GoSlice"},
275-
{nilGs, nil, "nil GoSlice"},
276-
{(*convert.GoMap)(nil), nil, "nil GoMap"},
277-
{nilGm, nil, "nil GoMap"},
278-
{(*convert.GoStruct)(nil), nil, "nil GoStruct"},
279-
{nilGst, nil, "nil GoStruct"},
280-
{(*convert.GoInterface)(nil), nil, "nil GoInterface"},
281-
{nilGif, nil, "nil GoInterface"},
273+
{(*convert.GoSlice)(nil), nil, "typed nil value: *convert.GoSlice"},
274+
{nilGs, nil, "typed nil value: *convert.GoSlice"},
275+
{(*convert.GoMap)(nil), nil, "typed nil value: *convert.GoMap"},
276+
{nilGm, nil, "typed nil value: *convert.GoMap"},
277+
{(*convert.GoStruct)(nil), nil, "typed nil value: *convert.GoStruct"},
278+
{nilGst, nil, "typed nil value: *convert.GoStruct"},
279+
{(*convert.GoInterface)(nil), nil, "typed nil value: *convert.GoInterface"},
280+
{nilGif, nil, "typed nil value: *convert.GoInterface"},
282281
{msb, nil, "unrecognized starlark type: *starlark.Builtin"},
283282
{sf, nil, "unrecognized starlark type: *starlark.Function"},
284283
{sse, nil, "unrecognized starlark type: *starlark.Builtin"},

0 commit comments

Comments
 (0)