From ce4ca9820a1e47b2622a679cdd5aa8eb0f2df113 Mon Sep 17 00:00:00 2001 From: Brooks Townsend Date: Fri, 7 Mar 2025 12:24:38 -0500 Subject: [PATCH 1/4] cm: implement JSON [un]marshaling for Option types Signed-off-by: Brooks Townsend --- cm/option.go | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/cm/option.go b/cm/option.go index cf7024aa..b00dac88 100644 --- a/cm/option.go +++ b/cm/option.go @@ -1,5 +1,7 @@ package cm +import "encoding/json" + // Option represents a Component Model [option] type. // // [option]: https://component-model.bytecodealliance.org/design/wit.html#options @@ -57,3 +59,25 @@ func (o option[T]) Value() T { } return o.some } + +// MarshalJSON implements the json.Marshaler interface. +func (o Option[T]) MarshalJSON() ([]byte, error) { + if !o.isSome { + return json.Marshal(nil) + } + return json.Marshal(o.Some()) +} + +// UnmarshalJSON implements the json.Unmarshaler interface. +func (o *Option[T]) UnmarshalJSON(data []byte) error { + if string(data) == "null" { + *o = None[T]() + return nil + } + var v T + if err := json.Unmarshal(data, &v); err != nil { + return err + } + *o = Some(v) + return nil +} From e755c070e5c75f30dd0815630b8d3ee95dd40a3a Mon Sep 17 00:00:00 2001 From: Brooks Townsend Date: Fri, 7 Mar 2025 12:24:52 -0500 Subject: [PATCH 2/4] cm: test JSON [un]marshaling for Option types Signed-off-by: Brooks Townsend --- cm/option_test.go | 62 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 61 insertions(+), 1 deletion(-) diff --git a/cm/option_test.go b/cm/option_test.go index 1b21669c..ad173d88 100644 --- a/cm/option_test.go +++ b/cm/option_test.go @@ -1,6 +1,9 @@ package cm -import "testing" +import ( + "encoding/json" + "testing" +) func TestOption(t *testing.T) { o1 := None[string]() @@ -65,3 +68,60 @@ func TestOption(t *testing.T) { t.Errorf("Value: %v, expected %v", got, want) } } + +func TestOptionMarshalJSON(t *testing.T) { + type TestStruct struct { + Field Option[string] `json:"field"` + } + + // Test marshaling None + ts1 := TestStruct{Field: None[string]()} + data1, err := json.Marshal(ts1) + if err != nil { + t.Fatalf("json.Marshal failed: %v", err) + } + expected1 := `{"field":null}` + if string(data1) != expected1 { + t.Errorf("json.Marshal: got %s, expected %s", data1, expected1) + } + + // Test marshaling Some + ts2 := TestStruct{Field: Some("hello")} + data2, err := json.Marshal(ts2) + if err != nil { + t.Fatalf("json.Marshal failed: %v", err) + } + expected2 := `{"field":"hello"}` + if string(data2) != expected2 { + t.Errorf("json.Marshal: got %s, expected %s", data2, expected2) + } +} + +func TestOptionUnmarshalJSON(t *testing.T) { + type TestStruct struct { + Field Option[string] `json:"field"` + } + + // Test unmarshaling None + data1 := []byte(`{"field":null}`) + var ts1 TestStruct + if err := json.Unmarshal(data1, &ts1); err != nil { + t.Fatalf("json.Unmarshal failed: %v", err) + } + if got, want := ts1.Field.None(), true; got != want { + t.Errorf("ts1.Field.None: %t, expected %t", got, want) + } + + // Test unmarshaling Some + data2 := []byte(`{"field":"hello"}`) + var ts2 TestStruct + if err := json.Unmarshal(data2, &ts2); err != nil { + t.Fatalf("json.Unmarshal failed: %v", err) + } + if got, want := ts2.Field.isSome, true; got != want { + t.Errorf("ts2.Field.Some: %t, expected %t", got, want) + } + if got, want := ts2.Field.Value(), "hello"; got != want { + t.Errorf("ts2.Field.Value: %v, expected %v", got, want) + } +} From 450b98cc0ef1033ee9cc9899245fb1453bb97258 Mon Sep 17 00:00:00 2001 From: Brooks Townsend Date: Mon, 10 Mar 2025 09:58:54 -0400 Subject: [PATCH 3/4] cm: implement JSON [un]marshaling for option types Signed-off-by: Brooks Townsend --- cm/option.go | 26 +++++++++++++++++++++----- cm/option_test.go | 18 ++++++++++++++---- 2 files changed, 35 insertions(+), 9 deletions(-) diff --git a/cm/option.go b/cm/option.go index b00dac88..041cdbe4 100644 --- a/cm/option.go +++ b/cm/option.go @@ -60,15 +60,12 @@ func (o option[T]) Value() T { return o.some } -// MarshalJSON implements the json.Marshaler interface. +// MarshalJSON implements the json.Marshaler interface for the public option type. func (o Option[T]) MarshalJSON() ([]byte, error) { - if !o.isSome { - return json.Marshal(nil) - } return json.Marshal(o.Some()) } -// UnmarshalJSON implements the json.Unmarshaler interface. +// UnmarshalJSON implements the json.Unmarshaler interface for the public option type. func (o *Option[T]) UnmarshalJSON(data []byte) error { if string(data) == "null" { *o = None[T]() @@ -81,3 +78,22 @@ func (o *Option[T]) UnmarshalJSON(data []byte) error { *o = Some(v) return nil } + +// MarshalJSON implements the json.Marshaler interface for the internal option type. +func (o option[T]) MarshalJSON() ([]byte, error) { + return json.Marshal(o.Some()) +} + +// UnmarshalJSON implements the json.Unmarshaler interface for the internal option type. +func (o *option[T]) UnmarshalJSON(data []byte) error { + if string(data) == "null" { + o = &option[T]{isSome: false} + return nil + } + var v T + if err := json.Unmarshal(data, &v); err != nil { + return err + } + o = &option[T]{isSome: true, some: v} + return nil +} diff --git a/cm/option_test.go b/cm/option_test.go index ad173d88..2ee10d54 100644 --- a/cm/option_test.go +++ b/cm/option_test.go @@ -112,16 +112,26 @@ func TestOptionUnmarshalJSON(t *testing.T) { t.Errorf("ts1.Field.None: %t, expected %t", got, want) } - // Test unmarshaling Some - data2 := []byte(`{"field":"hello"}`) + // Test unmarshaling None (not present) + data2 := []byte(`{}`) var ts2 TestStruct if err := json.Unmarshal(data2, &ts2); err != nil { t.Fatalf("json.Unmarshal failed: %v", err) } - if got, want := ts2.Field.isSome, true; got != want { + if got, want := ts2.Field.None(), true; got != want { + t.Errorf("ts1.Field.None: %t, expected %t", got, want) + } + + // Test unmarshaling Some + data3 := []byte(`{"field":"hello"}`) + var ts3 TestStruct + if err := json.Unmarshal(data3, &ts3); err != nil { + t.Fatalf("json.Unmarshal failed: %v", err) + } + if got, want := ts3.Field.isSome, true; got != want { t.Errorf("ts2.Field.Some: %t, expected %t", got, want) } - if got, want := ts2.Field.Value(), "hello"; got != want { + if got, want := ts3.Field.Value(), "hello"; got != want { t.Errorf("ts2.Field.Value: %v, expected %v", got, want) } } From c7d2e569a8f732a8a4f61b4510900cfb6972c207 Mon Sep 17 00:00:00 2001 From: Brooks Townsend Date: Tue, 11 Mar 2025 16:38:32 -0400 Subject: [PATCH 4/4] cm: test JSON [un]marshaling for named and nested options Signed-off-by: Brooks Townsend --- cm/option_test.go | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/cm/option_test.go b/cm/option_test.go index 2ee10d54..354178a0 100644 --- a/cm/option_test.go +++ b/cm/option_test.go @@ -95,6 +95,32 @@ func TestOptionMarshalJSON(t *testing.T) { if string(data2) != expected2 { t.Errorf("json.Marshal: got %s, expected %s", data2, expected2) } + + // Test marshaling custom option type + type OptionalI32 Option[int32] + ts3 := OptionalI32(Some(int32(42))) + data3, err := json.Marshal(ts3) + if err != nil { + t.Fatalf("json.Marshal failed: %v", err) + } + expected3 := `42` + if string(data3) != expected3 { + t.Errorf("json.Marshal: got %s, expected %s", data3, expected3) + } + + // Test marshaling nested option type + type NestedStruct struct { + Field Option[Option[int32]] `json:"field"` + } + ts4 := NestedStruct{Field: Some(Some(int32(42)))} + data4, err := json.Marshal(ts4) + if err != nil { + t.Fatalf("json.Marshal failed: %v", err) + } + expected4 := `{"field":42}` + if string(data4) != expected4 { + t.Errorf("json.Marshal: got %s, expected %s", data4, expected4) + } } func TestOptionUnmarshalJSON(t *testing.T) { @@ -134,4 +160,24 @@ func TestOptionUnmarshalJSON(t *testing.T) { if got, want := ts3.Field.Value(), "hello"; got != want { t.Errorf("ts2.Field.Value: %v, expected %v", got, want) } + + // Test unmarshaling nested option type + type NestedStruct struct { + Field Option[Option[int32]] `json:"field"` + } + data5 := []byte(`{"field":42}`) + var ns NestedStruct + if err := json.Unmarshal(data5, &ns); err != nil { + t.Fatalf("json.Unmarshal failed: %v", err) + } + if got, want := ns.Field.isSome, true; got != want { + t.Errorf("ns.Field.Some: %t, expected %t", got, want) + } + if got, want := ns.Field.Value(), Some(int32(42)); got != want { + t.Errorf("ns.Field.Value: %v, expected %v", got, want) + } + // Deref the inner option to get the value + if got, want := ns.Field.Value().Value(), int32(42); got != want { + t.Errorf("ns.Field.Value.Value: %v, expected %v", got, want) + } }