From 392b175abfa19fe9566dd7908fec69cd45144b77 Mon Sep 17 00:00:00 2001 From: Chan Wen Xu Date: Sat, 20 Mar 2021 18:56:02 +0800 Subject: [PATCH] feat: Implement a type-safe Object struct --- wasm/go.mod | 3 ++ wasm/object.go | 142 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 145 insertions(+) create mode 100644 wasm/go.mod create mode 100644 wasm/object.go diff --git a/wasm/go.mod b/wasm/go.mod new file mode 100644 index 0000000..31e956b --- /dev/null +++ b/wasm/go.mod @@ -0,0 +1,3 @@ +module gitea.teamortix.com/Team-Ortix/go-mod-wasm/wasm + +go 1.16 diff --git a/wasm/object.go b/wasm/object.go new file mode 100644 index 0000000..f1c3fe5 --- /dev/null +++ b/wasm/object.go @@ -0,0 +1,142 @@ +package wasm + +import ( + "fmt" + "syscall/js" +) + +// TypeMismatchError is returned when a function is called with a js.Value that has the incorrect type. +type TypeMismatchError struct { + Expected js.Type + Actual js.Type +} + +func (e TypeMismatchError) Error() string { + return fmt.Sprintf("expected %v type, got %v type instead", e.Expected, e.Actual) +} + +// Global returns the global object as a Object. +// If the global object is not an object, it panics. +func Global() Object { + global, err := NewObject(js.Global()) + if err != nil { + panic(err) + } + return global +} + +// Object is a statically typed Object instance of js.Value. +// It should be instantiated with NewObject where it is checked for type instead of directly. +// Calling methods on a zero Object is undefined behaviour. +type Object struct { + value js.Value +} + +// NewObject instantiates a new Object with the provided js.Value. +// If the js.Value is not an Object, it returns a TypeMismatchError. +func NewObject(raw js.Value) (Object, error) { + if raw.Type() != js.TypeObject { + return Object{}, TypeMismatchError{ + Expected: js.TypeObject, + Actual: raw.Type(), + } + } + + return Object{raw}, nil +} + +// Get recursively gets the Object's properties, returning a TypeMismatchError if it encounters a non-object while +// descending through the object. +func (o Object) Get(path ...string) (js.Value, error) { + current := o.value + for _, v := range path { + if current.Type() != js.TypeObject { + return js.Value{}, TypeMismatchError{ + Expected: js.TypeObject, + Actual: current.Type(), + } + } + + current = current.Get(v) + } + return current, nil +} + +// Expect is a helper function that calls Get and checks the type of the final result. +// It returns a TypeMismatchError if a non-object is encountered while descending the path or the final type does not +// match with the provided expected type. +func (o Object) Expect(expectedType js.Type, path ...string) (js.Value, error) { + value, err := o.Get(path...) + if err != nil { + return js.Value{}, err + } + + if value.Type() != expectedType { + return js.Value{}, TypeMismatchError{ + Expected: expectedType, + Actual: value.Type(), + } + } + + return value, nil +} + +// Delete removes property p from the object. +func (o Object) Delete(p string) { + o.value.Delete(p) +} + +// Equal checks if the object is equal to another value. +// It is equivalent to JS's === operator. +func (o Object) Equal(v js.Value) bool { + return o.value.Equal(v) +} + +// Index indexes into the object. +func (o Object) Index(i int) js.Value { + return o.value.Index(i) +} + +// InstanceOf implements the instanceof operator in JavaScript. +// If t is not a constructor, this function returns false. +func (o Object) InstanceOf(t js.Value) bool { + if t.Type() != js.TypeFunction { + return false + } + return o.value.InstanceOf(t) +} + +// JSValue implements the js.Wrapper interface. +func (o Object) JSValue() js.Value { + return o.value +} + +// Length returns the "length" property of the object. +func (o Object) Length() int { + return o.value.Length() +} + +// Set sets the property p to the value of js.ValueOf(x). +func (o Object) Set(p string, x interface{}) { + o.value.Set(p, x) +} + +// SetIndex sets the index i to the value of js.ValueOf(x). +func (o Object) SetIndex(i int, x interface{}) { + o.value.SetIndex(i, x) +} + +// String returns the object marshalled as a JSON string for debugging purposes. +func (o Object) String() string { + stringify, err := Global().Expect(js.TypeFunction, "JSON", "stringify") + if err != nil { + panic(err) + } + + jsonStr := stringify.Invoke(o) + if jsonStr.Type() != js.TypeString { + panic("JSON.stringify returned a " + jsonStr.Type().String()) + } + + return jsonStr.String() +}