Skip to content

Commit

Permalink
Merge pull request #280 from bytecodealliance/ydnar/enum-json
Browse files Browse the repository at this point in the history
cm, wit/bindgen: add JSON marshaling for enum types
  • Loading branch information
ydnar authored Jan 27, 2025
2 parents 7ef22df + ca608fc commit c53528d
Show file tree
Hide file tree
Showing 12 changed files with 322 additions and 39 deletions.
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

0 comments on commit c53528d

Please sign in to comment.