Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cm, wit/bindgen: add JSON marshaling for enum types #280

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
Open
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
### Added

- Initial support for Component Model [async](https://github.com/WebAssembly/component-model/blob/main/design/mvp/Async.md) types `stream`, `future`, and `error-context`.
- Initial support for JSON serialization of WIT `list`, `enum`, and `record` types.
- [`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`.

### Changed
Expand Down
7 changes: 6 additions & 1 deletion cm/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
### Added

- Initial support for Component Model [async](https://github.com/WebAssembly/component-model/blob/main/design/mvp/Async.md) types `stream`, `future`, and `error-context`.
- Initial support for JSON serialization of WIT types, starting with `list` and `record`.
- Initial support for JSON serialization of WIT `list`, `enum`, and `record` types.
- Added `cm.CaseUnmarshaler` helper for text and JSON unmarshaling of `enum` and `variant` types.

### Changed

- Breaking: package `cm`: removed `bool` from `Discriminant` type constraint. It was not used by code generation.

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

Expand Down
51 changes: 51 additions & 0 deletions cm/case.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package cm

import "errors"

// CaseUnmarshaler returns an function that can unmarshal text into
// [variant] or [enum] case T.
//
// [enum]: https://component-model.bytecodealliance.org/design/wit.html#enums
// [variant]: https://component-model.bytecodealliance.org/design/wit.html#variants
func CaseUnmarshaler[T ~uint8 | ~uint16 | ~uint32](cases []string) func(v *T, text []byte) error {
if len(cases) <= linearScanThreshold {
return func(v *T, text []byte) error {
if len(text) == 0 {
return errEmpty
}
s := string(text)
for i := 0; i < len(cases); i++ {
if cases[i] == s {
*v = T(i)
return nil
}
}
return errNoMatchingCase
}
}

m := make(map[string]T, len(cases))
for i, v := range cases {
m[v] = T(i)
}

return func(v *T, text []byte) error {
if len(text) == 0 {
return errEmpty
}
s := string(text)
c, ok := m[s]
if !ok {
return errNoMatchingCase
}
*v = c
return nil
}
}

const linearScanThreshold = 16

var (
errEmpty = errors.New("empty text")
errNoMatchingCase = errors.New("no matching case")
)
34 changes: 34 additions & 0 deletions cm/case_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package cm

import (
"strings"
"testing"
)

func TestCaseUnmarshaler(t *testing.T) {
tests := []struct {
name string
cases []string
}{
{"nil", nil},
{"empty slice", []string{}},
{"a b c", strings.SplitAfter("abc", "")},
{"a b c d e f g", strings.SplitAfter("abcdefg", "")},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
f := CaseUnmarshaler[uint8](tt.cases)
for want, c := range tt.cases {
var got uint8
err := f(&got, []byte(c))
if err != nil {
t.Error(err)
return
}
if got != uint8(want) {
t.Errorf("f(%q): got %d, expected %d", c, got, want)
}
}
})
}
}
5 changes: 2 additions & 3 deletions cm/variant.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,9 @@ package cm
import "unsafe"

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

// Variant represents a loosely-typed Component Model variant.
Expand Down
24 changes: 12 additions & 12 deletions cm/variant_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,18 @@ func TestVariantLayout(t *testing.T) {
size uintptr
offset uintptr
}{
{"variant { string; string }", Variant[bool, string, string]{}, sizePlusAlignOf[string](), ptrSize},
{"variant { bool; string }", Variant[bool, string, bool]{}, sizePlusAlignOf[string](), ptrSize},
{"variant { string; _ }", Variant[bool, string, string]{}, sizePlusAlignOf[string](), ptrSize},
{"variant { _; _ }", Variant[bool, string, struct{}]{}, sizePlusAlignOf[string](), ptrSize},
{"variant { u64; u64 }", Variant[bool, uint64, uint64]{}, 16, alignOf[uint64]()},
{"variant { u32; u64 }", Variant[bool, uint64, uint32]{}, 16, alignOf[uint64]()},
{"variant { u64; u32 }", Variant[bool, uint64, uint32]{}, 16, alignOf[uint64]()},
{"variant { u8; u64 }", Variant[bool, uint64, uint8]{}, 16, alignOf[uint64]()},
{"variant { u64; u8 }", Variant[bool, uint64, uint8]{}, 16, alignOf[uint64]()},
{"variant { u8; u32 }", Variant[bool, uint32, uint8]{}, 8, alignOf[uint32]()},
{"variant { u32; u8 }", Variant[bool, uint32, uint8]{}, 8, alignOf[uint32]()},
{"variant { [9]u8, u64 }", Variant[bool, [9]byte, uint64]{}, 24, alignOf[uint64]()},
{"variant { string; string }", Variant[uint8, string, string]{}, sizePlusAlignOf[string](), ptrSize},
{"variant { bool; string }", Variant[uint8, string, bool]{}, sizePlusAlignOf[string](), ptrSize},
{"variant { string; _ }", Variant[uint8, string, string]{}, sizePlusAlignOf[string](), ptrSize},
{"variant { _; _ }", Variant[uint8, string, struct{}]{}, sizePlusAlignOf[string](), ptrSize},
{"variant { u64; u64 }", Variant[uint8, uint64, uint64]{}, 16, alignOf[uint64]()},
{"variant { u32; u64 }", Variant[uint8, uint64, uint32]{}, 16, alignOf[uint64]()},
{"variant { u64; u32 }", Variant[uint8, uint64, uint32]{}, 16, alignOf[uint64]()},
{"variant { u8; u64 }", Variant[uint8, uint64, uint8]{}, 16, alignOf[uint64]()},
{"variant { u64; u8 }", Variant[uint8, uint64, uint8]{}, 16, alignOf[uint64]()},
{"variant { u8; u32 }", Variant[uint8, uint32, uint8]{}, 8, alignOf[uint32]()},
{"variant { u32; u8 }", Variant[uint8, uint32, uint8]{}, 8, alignOf[uint32]()},
{"variant { [9]u8, u64 }", Variant[uint8, [9]byte, uint64]{}, 24, alignOf[uint64]()},
}

for _, tt := range tests {
Expand Down
55 changes: 47 additions & 8 deletions tests/generated/wasi/filesystem/v0.2.0/types/types.wit.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions tests/generated/wasi/io/v0.2.0/streams/streams.wit.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

42 changes: 34 additions & 8 deletions tests/generated/wasi/sockets/v0.2.0/network/network.wit.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading