This is an automated email from the git hooks/post-receive script.
git pushed a commit to branch main
in repository ego.
View the commit online.
commit 99b586177f934d7390c70d6cdbda57071b762f7d
Author: [email protected] <[email protected]>
AuthorDate: Tue Mar 31 11:44:41 2026 -0600
feat(efl): add ValueMarshal/ValueUnmarshal for scalar types
Implement ValueMarshal(any) and ValueUnmarshal(*Value, any) with
scalar type dispatch via reflection. Supports int, string, float64,
bool, and all sized integer variants. Container and struct support
follows in subsequent commits.
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
---
efl/value_marshal.go | 206 ++++++++++++++++++++++++++++++++++++++++++++++
efl/value_marshal_test.go | 171 ++++++++++++++++++++++++++++++++++++++
2 files changed, 377 insertions(+)
diff --git a/efl/value_marshal.go b/efl/value_marshal.go
new file mode 100644
index 0000000..74c57f8
--- /dev/null
+++ b/efl/value_marshal.go
@@ -0,0 +1,206 @@
+package efl
+
+import (
+ "errors"
+ "fmt"
+ "reflect"
+)
+
+// ValueMarshal converts any Go scalar value to a *Value.
+//
+// Supported input types: bool, string, all integer and float variants, and
+// *Value (pass-through). Slice, map, and struct inputs return a "not yet
+// implemented" error. nil returns an error.
+func ValueMarshal(v any) (*Value, error) {
+ if v == nil {
+ return nil, errors.New("efl: ValueMarshal: nil input")
+ }
+
+ // Pass-through: if the caller already has a *Value, return it directly.
+ if val, ok := v.(*Value); ok {
+ return val, nil
+ }
+
+ switch u := v.(type) {
+ case int:
+ return NewValue[int](u)
+ case int8:
+ // int8 is not in ScalarType; promote to int.
+ return NewValue[int](int(u))
+ case int16:
+ return NewValue[int16](u)
+ case int32:
+ return NewValue[int32](u)
+ case int64:
+ return NewValue[int64](u)
+ case uint:
+ return NewValue[uint](u)
+ case uint8:
+ // uint8 == byte; route through the byte branch so Kind() == ValueKindByte.
+ return NewValue[byte](u)
+ case uint16:
+ return NewValue[uint16](u)
+ case uint32:
+ return NewValue[uint32](u)
+ case uint64:
+ return NewValue[uint64](u)
+ case float32:
+ return NewValue[float32](u)
+ case float64:
+ return NewValue[float64](u)
+ case bool:
+ return NewValue[bool](u)
+ case string:
+ return NewValue[string](u)
+ default:
+ // Use reflection only to produce helpful error messages for composite
+ // types that we plan to support later.
+ kind := reflect.TypeOf(v).Kind()
+ switch kind {
+ case reflect.Slice:
+ return nil, errors.New("efl: ValueMarshal: slice: not yet implemented")
+ case reflect.Map:
+ return nil, errors.New("efl: ValueMarshal: map: not yet implemented")
+ case reflect.Struct:
+ return nil, errors.New("efl: ValueMarshal: struct: not yet implemented")
+ default:
+ return nil, fmt.Errorf("efl: ValueMarshal: unsupported type %T", v)
+ }
+ }
+}
+
+// ValueUnmarshal reads the scalar stored in val and writes it into the value
+// pointed to by dst. dst must be a non-nil pointer to one of the supported
+// scalar types (bool, string, int family, uint family, float family).
+//
+// The unmarshal uses the destination type to determine which ValueGet variant
+// to call; if the stored type cannot be coerced by Eina, an error is returned.
+func ValueUnmarshal(val *Value, dst any) error {
+ if dst == nil {
+ return errors.New("efl: ValueUnmarshal: dst is nil")
+ }
+
+ rv := reflect.ValueOf(dst)
+ if rv.Kind() != reflect.Pointer {
+ return fmt.Errorf("efl: ValueUnmarshal: dst must be a pointer, got %T", dst)
+ }
+ if rv.IsNil() {
+ return errors.New("efl: ValueUnmarshal: dst pointer is nil")
+ }
+
+ elem := rv.Elem()
+
+ switch elem.Kind() {
+ case reflect.Int:
+ got, err := ValueGet[int](val)
+ if err != nil {
+ return fmt.Errorf("efl: ValueUnmarshal into int: %w", err)
+ }
+ elem.SetInt(int64(got))
+
+ case reflect.Int8:
+ got, err := ValueGet[int](val)
+ if err != nil {
+ return fmt.Errorf("efl: ValueUnmarshal into int8: %w", err)
+ }
+ elem.SetInt(int64(got))
+
+ case reflect.Int16:
+ got, err := ValueGet[int16](val)
+ if err != nil {
+ return fmt.Errorf("efl: ValueUnmarshal into int16: %w", err)
+ }
+ elem.SetInt(int64(got))
+
+ case reflect.Int32:
+ got, err := ValueGet[int32](val)
+ if err != nil {
+ return fmt.Errorf("efl: ValueUnmarshal into int32: %w", err)
+ }
+ elem.SetInt(int64(got))
+
+ case reflect.Int64:
+ got, err := ValueGet[int64](val)
+ if err != nil {
+ return fmt.Errorf("efl: ValueUnmarshal into int64: %w", err)
+ }
+ elem.SetInt(got)
+
+ case reflect.Uint:
+ got, err := ValueGet[uint](val)
+ if err != nil {
+ return fmt.Errorf("efl: ValueUnmarshal into uint: %w", err)
+ }
+ elem.SetUint(uint64(got))
+
+ case reflect.Uint8:
+ got, err := ValueGet[byte](val)
+ if err != nil {
+ return fmt.Errorf("efl: ValueUnmarshal into uint8: %w", err)
+ }
+ elem.SetUint(uint64(got))
+
+ case reflect.Uint16:
+ got, err := ValueGet[uint16](val)
+ if err != nil {
+ return fmt.Errorf("efl: ValueUnmarshal into uint16: %w", err)
+ }
+ elem.SetUint(uint64(got))
+
+ case reflect.Uint32:
+ got, err := ValueGet[uint32](val)
+ if err != nil {
+ return fmt.Errorf("efl: ValueUnmarshal into uint32: %w", err)
+ }
+ elem.SetUint(uint64(got))
+
+ case reflect.Uint64:
+ got, err := ValueGet[uint64](val)
+ if err != nil {
+ return fmt.Errorf("efl: ValueUnmarshal into uint64: %w", err)
+ }
+ elem.SetUint(got)
+
+ case reflect.Float32:
+ got, err := ValueGet[float32](val)
+ if err != nil {
+ return fmt.Errorf("efl: ValueUnmarshal into float32: %w", err)
+ }
+ elem.SetFloat(float64(got))
+
+ case reflect.Float64:
+ got, err := ValueGet[float64](val)
+ if err != nil {
+ return fmt.Errorf("efl: ValueUnmarshal into float64: %w", err)
+ }
+ elem.SetFloat(got)
+
+ case reflect.Bool:
+ got, err := ValueGet[bool](val)
+ if err != nil {
+ return fmt.Errorf("efl: ValueUnmarshal into bool: %w", err)
+ }
+ elem.SetBool(got)
+
+ case reflect.String:
+ got, err := ValueGet[string](val)
+ if err != nil {
+ return fmt.Errorf("efl: ValueUnmarshal into string: %w", err)
+ }
+ elem.SetString(got)
+
+ case reflect.Slice:
+ return errors.New("efl: ValueUnmarshal: slice: not yet implemented")
+
+ case reflect.Map:
+ return errors.New("efl: ValueUnmarshal: map: not yet implemented")
+
+ case reflect.Struct:
+ return errors.New("efl: ValueUnmarshal: struct: not yet implemented")
+
+ default:
+ return fmt.Errorf("efl: ValueUnmarshal: unsupported destination type %T", dst)
+ }
+
+ return nil
+}
diff --git a/efl/value_marshal_test.go b/efl/value_marshal_test.go
new file mode 100644
index 0000000..75c6cc2
--- /dev/null
+++ b/efl/value_marshal_test.go
@@ -0,0 +1,171 @@
+package efl
+
+import (
+ "math"
+ "testing"
+)
+
+// TestMarshalInt verifies that an int is marshalled to a Value with
+// ValueKindInt and that the value survives an unmarshal round-trip.
+func TestMarshalInt(t *testing.T) {
+ val, err := ValueMarshal(42)
+ if err != nil {
+ t.Fatalf("ValueMarshal(42): %v", err)
+ }
+ defer val.Free()
+
+ if got := val.Kind(); got != ValueKindInt {
+ t.Errorf("Kind: want ValueKindInt, got %v", got)
+ }
+
+ var out int
+ if err := ValueUnmarshal(val, &out); err != nil {
+ t.Fatalf("ValueUnmarshal into *int: %v", err)
+ }
+ if out != 42 {
+ t.Errorf("round-trip: want 42, got %d", out)
+ }
+}
+
+// TestMarshalString verifies string marshalling and round-trip.
+func TestMarshalString(t *testing.T) {
+ const input = "hello"
+ val, err := ValueMarshal(input)
+ if err != nil {
+ t.Fatalf("ValueMarshal(%q): %v", input, err)
+ }
+ defer val.Free()
+
+ if got := val.Kind(); got != ValueKindString {
+ t.Errorf("Kind: want ValueKindString, got %v", got)
+ }
+
+ var out string
+ if err := ValueUnmarshal(val, &out); err != nil {
+ t.Fatalf("ValueUnmarshal into *string: %v", err)
+ }
+ if out != input {
+ t.Errorf("round-trip: want %q, got %q", input, out)
+ }
+}
+
+// TestMarshalFloat64 verifies float64 marshalling and round-trip.
+func TestMarshalFloat64(t *testing.T) {
+ const input = 3.14
+ val, err := ValueMarshal(input)
+ if err != nil {
+ t.Fatalf("ValueMarshal(%v): %v", input, err)
+ }
+ defer val.Free()
+
+ if got := val.Kind(); got != ValueKindFloat64 {
+ t.Errorf("Kind: want ValueKindFloat64, got %v", got)
+ }
+
+ var out float64
+ if err := ValueUnmarshal(val, &out); err != nil {
+ t.Fatalf("ValueUnmarshal into *float64: %v", err)
+ }
+ if math.Abs(out-input) > 1e-10 {
+ t.Errorf("round-trip: want ~%v, got %v", input, out)
+ }
+}
+
+// TestMarshalBool verifies bool marshalling and round-trip.
+func TestMarshalBool(t *testing.T) {
+ val, err := ValueMarshal(true)
+ if err != nil {
+ t.Fatalf("ValueMarshal(true): %v", err)
+ }
+ defer val.Free()
+
+ if got := val.Kind(); got != ValueKindBool {
+ t.Errorf("Kind: want ValueKindBool, got %v", got)
+ }
+
+ var out bool
+ if err := ValueUnmarshal(val, &out); err != nil {
+ t.Fatalf("ValueUnmarshal into *bool: %v", err)
+ }
+ if !out {
+ t.Error("round-trip: want true, got false")
+ }
+}
+
+// TestMarshalValuePassthrough verifies that marshalling an existing *Value
+// returns the same pointer without wrapping or copying.
+func TestMarshalValuePassthrough(t *testing.T) {
+ original, err := NewValue[int](7)
+ if err != nil {
+ t.Fatalf("NewValue[int]: %v", err)
+ }
+ defer original.Free()
+
+ result, err := ValueMarshal(original)
+ if err != nil {
+ t.Fatalf("ValueMarshal(*Value): %v", err)
+ }
+ if result != original {
+ t.Error("passthrough: expected same pointer, got a different one")
+ }
+}
+
+// TestMarshalNil verifies that passing nil to ValueMarshal returns an error.
+func TestMarshalNil(t *testing.T) {
+ _, err := ValueMarshal(nil)
+ if err == nil {
+ t.Fatal("expected error for nil input, got nil")
+ }
+}
+
+// TestUnmarshalInt marshals an int and unmarshals it back into *int.
+func TestUnmarshalInt(t *testing.T) {
+ val, err := ValueMarshal(99)
+ if err != nil {
+ t.Fatalf("ValueMarshal(99): %v", err)
+ }
+ defer val.Free()
+
+ var out int
+ if err := ValueUnmarshal(val, &out); err != nil {
+ t.Fatalf("ValueUnmarshal: %v", err)
+ }
+ if out != 99 {
+ t.Errorf("want 99, got %d", out)
+ }
+}
+
+// TestUnmarshalString marshals a string and unmarshals it back into *string.
+func TestUnmarshalString(t *testing.T) {
+ const want = "unmarshal me"
+ val, err := ValueMarshal(want)
+ if err != nil {
+ t.Fatalf("ValueMarshal: %v", err)
+ }
+ defer val.Free()
+
+ var out string
+ if err := ValueUnmarshal(val, &out); err != nil {
+ t.Fatalf("ValueUnmarshal: %v", err)
+ }
+ if out != want {
+ t.Errorf("want %q, got %q", want, out)
+ }
+}
+
+// TestUnmarshalBool marshals a bool and unmarshals it back into *bool.
+func TestUnmarshalBool(t *testing.T) {
+ val, err := ValueMarshal(false)
+ if err != nil {
+ t.Fatalf("ValueMarshal(false): %v", err)
+ }
+ defer val.Free()
+
+ out := true // set to true so we can confirm it was overwritten with false
+ if err := ValueUnmarshal(val, &out); err != nil {
+ t.Fatalf("ValueUnmarshal: %v", err)
+ }
+ if out {
+ t.Error("want false, got true")
+ }
+}
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.