starlib
starlib copied to clipboard
(proposal) Marshal\Unmarshal custom type via starlarkstruct.Struct
It is proposal and dirty code to illustrate the concept
NOTE: factory object saved in to starlarakstruct.Struct in constructor for unmarshal (no need to global store custom types). Similar to database.Valuer
Implement custom type via starlarkstruct.Struct Added two interfaces
type Unmarshaler interface {
UnmarshalStarlark(starlark.Value) error
}
type Marshaler interface {
MarshalStarlark() (starlark.Value, error)
}
Added new cases for util.Marahsl
and util.Unmarshal
// Unmarshal
case *starlarkstruct.Struct:
if _var, ok := v.Constructor().(Unmarshaler); ok {
err = _var.UnmarshalStarlark(x)
if err != nil {
err = errors.Wrapf(err, "failed marshal %q to Starlark object", v.Constructor().Type())
return
}
val = _var
} else {
err = fmt.Errorf("constructor object from *starlarkstruct.Struct not supported Marshaler to starlark object: %s", v.Constructor().Type())
}
// Marshal
case Marshaler:
v, err = x.MarshalStarlark()
Example custom type
type customType struct {
Foo int64
}
func (t *customType) UnmarshalStarlark(v starlark.Value) error {
// asserts
if v.Type() != "struct" {
return fmt.Errorf("not expected top level type, want struct, got %q", v.Type())
}
if _, ok := v.(*starlarkstruct.Struct).Constructor().(*customType); !ok {
return fmt.Errorf("not expected construct type got %T, want %T", v.(*starlarkstruct.Struct).Constructor(), t)
}
// TODO: refactoring transform data
mustInt64 := func(sv starlark.Value) int64 {
i, _ := sv.(starlark.Int).Int64()
return i
}
data := starlark.StringDict{}
v.(*starlarkstruct.Struct).ToStringDict(data)
*t = customType{
Foo: mustInt64(data["foo"]),
}
return nil
}
func (t *customType) MarshalStarlark() (starlark.Value, error) {
v := starlarkstruct.FromStringDict(&customType{}, starlark.StringDict{
"foo": starlark.MakeInt64(t.Foo),
})
return v, nil
}
func (c customType) String() string {
return "customType"
}
func (c customType) Type() string { return "test.customType" }
func (customType) Freeze() {}
func (c customType) Truth() starlark.Bool {
return starlark.True
}
func (c customType) Hash() (uint32, error) {
return 0, fmt.Errorf("unhashable: %s", c.Type())
}
var _ Unmarshaler = (*customType)(nil)
var _ Marshaler = (*customType)(nil)
var _ starlark.Value = (*customType)(nil)
Tests
func TestLifeCycle(t *testing.T) {
t.Run("once", func(t *testing.T) {
// golang value
goVal := &customType{42}
// starlark value
slVal, err := Marshal(goVal)
assert.NoError(t, err)
assert.IsType(t, &starlarkstruct.Struct{}, slVal)
gotGoVal, err := Unmarshal(slVal)
assert.NoError(t, err)
log.Println(slVal.String())
assert.EqualValues(t, goVal, gotGoVal)
})
t.Run("asDictValue", func(t *testing.T) {
// golang value
goVal := map[string]interface{}{
"foo": &customType{42},
}
// starlark value
slVal, err := Marshal(goVal)
assert.NoError(t, err)
wantSlVal := starlark.NewDict(1)
assert.IsType(t, wantSlVal, slVal)
wantSlVal.SetKey(starlark.String("foo"), func() starlark.Value { v, _ := Marshal(&customType{42}); return v }())
assert.EqualValues(t, wantSlVal, slVal)
gotGoVal, err := Unmarshal(slVal)
assert.NoError(t, err)
log.Println(slVal.String())
assert.EqualValues(t, goVal, gotGoVal)
})
t.Run("asListValue", func(t *testing.T) {
// golang value
goVal := []interface{}{
&customType{42},
&customType{42},
}
// starlark value
slVal, err := Marshal(goVal)
assert.NoError(t, err)
wantSlVal := starlark.NewList(nil)
wantSlVal.Append(func() starlark.Value { v, _ := Marshal(&customType{42}); return v }())
wantSlVal.Append(func() starlark.Value { v, _ := Marshal(&customType{42}); return v }())
assert.IsType(t, wantSlVal, slVal)
assert.EqualValues(t, wantSlVal, slVal)
gotGoVal, err := Unmarshal(slVal)
assert.NoError(t, err)
log.Println(slVal.String())
assert.EqualValues(t, goVal, gotGoVal)
})
}
https://github.com/qri-io/starlib/pull/34 ref
@gebv @b5 FYI Starlark recently merged the Unpacker
interface: https://github.com/google/starlark-go/pull/272
I need to solve the Custom Type problem in my application soon, so planning to look into both UnpackArgs
for things other than Starlark function arguments and/or continue using util.Unmarshal
. Will report back if I learn anything that can be contributed here.
Strange that google/starlark-go
has no generic interface for Marshal
. Packer
would make a lot of sense 😄. Any others addressed this problem?