Skip to content

Commit c53528d

Browse files
authored
Merge pull request #280 from bytecodealliance/ydnar/enum-json
cm, wit/bindgen: add JSON marshaling for enum types
2 parents 7ef22df + ca608fc commit c53528d

File tree

12 files changed

+322
-39
lines changed

12 files changed

+322
-39
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
77
### Added
88

99
- Initial support for Component Model [async](https://github.com/WebAssembly/component-model/blob/main/design/mvp/Async.md) types `stream`, `future`, and `error-context`.
10+
- Initial support for JSON serialization of WIT `list`, `enum`, and `record` types.
1011
- [`wasm-tools`](https://crates.io/crates/wasm-tools) is now vendored as a WebAssembly module, executed using [Wazero](https://wazero.io/). This allows package `wit` and `wit-bindgen-go` to run on any supported platform without needing to separately install `wasm-tools`.
1112

1213
### Changed

cm/CHANGELOG.md

+6-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
77
### Added
88

99
- Initial support for Component Model [async](https://github.com/WebAssembly/component-model/blob/main/design/mvp/Async.md) types `stream`, `future`, and `error-context`.
10-
- Initial support for JSON serialization of WIT types, starting with `list` and `record`.
10+
- Initial support for JSON serialization of WIT `list`, `enum`, and `record` types.
11+
- Added `cm.CaseUnmarshaler` helper for text and JSON unmarshaling of `enum` and `variant` types.
12+
13+
### Changed
14+
15+
- Breaking: package `cm`: removed `bool` from `Discriminant` type constraint. It was not used by code generation.
1116

1217
## [v0.1.0] — 2024-12-14
1318

cm/case.go

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package cm
2+
3+
import "errors"
4+
5+
// CaseUnmarshaler returns an function that can unmarshal text into
6+
// [variant] or [enum] case T.
7+
//
8+
// [enum]: https://component-model.bytecodealliance.org/design/wit.html#enums
9+
// [variant]: https://component-model.bytecodealliance.org/design/wit.html#variants
10+
func CaseUnmarshaler[T ~uint8 | ~uint16 | ~uint32](cases []string) func(v *T, text []byte) error {
11+
if len(cases) <= linearScanThreshold {
12+
return func(v *T, text []byte) error {
13+
if len(text) == 0 {
14+
return errEmpty
15+
}
16+
s := string(text)
17+
for i := 0; i < len(cases); i++ {
18+
if cases[i] == s {
19+
*v = T(i)
20+
return nil
21+
}
22+
}
23+
return errNoMatchingCase
24+
}
25+
}
26+
27+
m := make(map[string]T, len(cases))
28+
for i, v := range cases {
29+
m[v] = T(i)
30+
}
31+
32+
return func(v *T, text []byte) error {
33+
if len(text) == 0 {
34+
return errEmpty
35+
}
36+
s := string(text)
37+
c, ok := m[s]
38+
if !ok {
39+
return errNoMatchingCase
40+
}
41+
*v = c
42+
return nil
43+
}
44+
}
45+
46+
const linearScanThreshold = 16
47+
48+
var (
49+
errEmpty = errors.New("empty text")
50+
errNoMatchingCase = errors.New("no matching case")
51+
)

cm/case_test.go

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package cm
2+
3+
import (
4+
"strings"
5+
"testing"
6+
)
7+
8+
func TestCaseUnmarshaler(t *testing.T) {
9+
tests := []struct {
10+
name string
11+
cases []string
12+
}{
13+
{"nil", nil},
14+
{"empty slice", []string{}},
15+
{"a b c", strings.SplitAfter("abc", "")},
16+
{"a b c d e f g", strings.SplitAfter("abcdefg", "")},
17+
}
18+
for _, tt := range tests {
19+
t.Run(tt.name, func(t *testing.T) {
20+
f := CaseUnmarshaler[uint8](tt.cases)
21+
for want, c := range tt.cases {
22+
var got uint8
23+
err := f(&got, []byte(c))
24+
if err != nil {
25+
t.Error(err)
26+
return
27+
}
28+
if got != uint8(want) {
29+
t.Errorf("f(%q): got %d, expected %d", c, got, want)
30+
}
31+
}
32+
})
33+
}
34+
}

cm/variant.go

+2-3
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,9 @@ package cm
33
import "unsafe"
44

55
// Discriminant is the set of types that can represent the tag or discriminator of a variant.
6-
// Use bool for 2-case variant types, result<T>, or option<T> types, uint8 where there are 256 or
7-
// fewer cases, uint16 for up to 65,536 cases, or uint32 for anything greater.
6+
// Use uint8 where there are 256 or fewer cases, uint16 for up to 65,536 cases, or uint32 for anything greater.
87
type Discriminant interface {
9-
bool | uint8 | uint16 | uint32
8+
uint8 | uint16 | uint32
109
}
1110

1211
// Variant represents a loosely-typed Component Model variant.

cm/variant_test.go

+12-12
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,18 @@ func TestVariantLayout(t *testing.T) {
1717
size uintptr
1818
offset uintptr
1919
}{
20-
{"variant { string; string }", Variant[bool, string, string]{}, sizePlusAlignOf[string](), ptrSize},
21-
{"variant { bool; string }", Variant[bool, string, bool]{}, sizePlusAlignOf[string](), ptrSize},
22-
{"variant { string; _ }", Variant[bool, string, string]{}, sizePlusAlignOf[string](), ptrSize},
23-
{"variant { _; _ }", Variant[bool, string, struct{}]{}, sizePlusAlignOf[string](), ptrSize},
24-
{"variant { u64; u64 }", Variant[bool, uint64, uint64]{}, 16, alignOf[uint64]()},
25-
{"variant { u32; u64 }", Variant[bool, uint64, uint32]{}, 16, alignOf[uint64]()},
26-
{"variant { u64; u32 }", Variant[bool, uint64, uint32]{}, 16, alignOf[uint64]()},
27-
{"variant { u8; u64 }", Variant[bool, uint64, uint8]{}, 16, alignOf[uint64]()},
28-
{"variant { u64; u8 }", Variant[bool, uint64, uint8]{}, 16, alignOf[uint64]()},
29-
{"variant { u8; u32 }", Variant[bool, uint32, uint8]{}, 8, alignOf[uint32]()},
30-
{"variant { u32; u8 }", Variant[bool, uint32, uint8]{}, 8, alignOf[uint32]()},
31-
{"variant { [9]u8, u64 }", Variant[bool, [9]byte, uint64]{}, 24, alignOf[uint64]()},
20+
{"variant { string; string }", Variant[uint8, string, string]{}, sizePlusAlignOf[string](), ptrSize},
21+
{"variant { bool; string }", Variant[uint8, string, bool]{}, sizePlusAlignOf[string](), ptrSize},
22+
{"variant { string; _ }", Variant[uint8, string, string]{}, sizePlusAlignOf[string](), ptrSize},
23+
{"variant { _; _ }", Variant[uint8, string, struct{}]{}, sizePlusAlignOf[string](), ptrSize},
24+
{"variant { u64; u64 }", Variant[uint8, uint64, uint64]{}, 16, alignOf[uint64]()},
25+
{"variant { u32; u64 }", Variant[uint8, uint64, uint32]{}, 16, alignOf[uint64]()},
26+
{"variant { u64; u32 }", Variant[uint8, uint64, uint32]{}, 16, alignOf[uint64]()},
27+
{"variant { u8; u64 }", Variant[uint8, uint64, uint8]{}, 16, alignOf[uint64]()},
28+
{"variant { u64; u8 }", Variant[uint8, uint64, uint8]{}, 16, alignOf[uint64]()},
29+
{"variant { u8; u32 }", Variant[uint8, uint32, uint8]{}, 8, alignOf[uint32]()},
30+
{"variant { u32; u8 }", Variant[uint8, uint32, uint8]{}, 8, alignOf[uint32]()},
31+
{"variant { [9]u8, u64 }", Variant[uint8, [9]byte, uint64]{}, 24, alignOf[uint64]()},
3232
}
3333

3434
for _, tt := range tests {

tests/generated/wasi/filesystem/v0.2.0/types/types.wit.go

+47-8
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/generated/wasi/io/v0.2.0/streams/streams.wit.go

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/generated/wasi/sockets/v0.2.0/network/network.wit.go

+34-8
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)