wip3
This commit is contained in:
@@ -2,8 +2,6 @@ package builtin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -18,8 +16,9 @@ type Builtin struct {
|
||||
}
|
||||
|
||||
var (
|
||||
builtinRegistry = make(map[string]Builtin)
|
||||
registryMutex sync.RWMutex
|
||||
builtinRegistry = make(map[string]Builtin)
|
||||
registryMutex sync.RWMutex
|
||||
customConverters = make(map[reflect.Type]func(*goja.Runtime, reflect.Value) goja.Value)
|
||||
)
|
||||
|
||||
func RegisterBuiltin(name string, fn any) {
|
||||
@@ -29,11 +28,35 @@ func RegisterBuiltin(name string, fn any) {
|
||||
wrapper := createGenericWrapper(fnValue, fnType)
|
||||
definition := generateTypeScriptDefinition(name, fnType)
|
||||
|
||||
registryMutex.Lock()
|
||||
builtinRegistry[name] = Builtin{
|
||||
Name: name,
|
||||
Function: wrapper,
|
||||
Definition: definition,
|
||||
}
|
||||
registryMutex.Unlock()
|
||||
}
|
||||
|
||||
func RegisterCustomConverter[T any](converter func(vm *goja.Runtime, value T) goja.Value) {
|
||||
var t T
|
||||
typeOf := reflect.TypeOf(t)
|
||||
|
||||
registryMutex.Lock()
|
||||
wrappedConverter := func(vm *goja.Runtime, value reflect.Value) goja.Value {
|
||||
return converter(vm, value.Interface().(T))
|
||||
}
|
||||
customConverters[typeOf] = wrappedConverter
|
||||
|
||||
if typeOf.Kind() == reflect.Pointer {
|
||||
elemType := typeOf.Elem()
|
||||
customConverters[elemType] = func(vm *goja.Runtime, value reflect.Value) goja.Value {
|
||||
if value.IsNil() {
|
||||
return goja.Null()
|
||||
}
|
||||
return converter(vm, value.Interface().(T))
|
||||
}
|
||||
}
|
||||
registryMutex.Unlock()
|
||||
}
|
||||
|
||||
func createGenericWrapper(fnValue reflect.Value, fnType reflect.Type) any {
|
||||
@@ -152,6 +175,26 @@ func convertJSValueToGo(vm *goja.Runtime, jsValue goja.Value, targetType reflect
|
||||
|
||||
func convertGoValueToJS(vm *goja.Runtime, goValue reflect.Value) goja.Value {
|
||||
value := goValue.Interface()
|
||||
valueType := goValue.Type()
|
||||
|
||||
registryMutex.RLock()
|
||||
converter, ok := customConverters[valueType]
|
||||
registryMutex.RUnlock()
|
||||
|
||||
if ok {
|
||||
return converter(vm, goValue)
|
||||
}
|
||||
|
||||
if goValue.Kind() == reflect.Pointer && !goValue.IsNil() {
|
||||
elemType := goValue.Type().Elem()
|
||||
registryMutex.RLock()
|
||||
converter, ok := customConverters[elemType]
|
||||
registryMutex.RUnlock()
|
||||
|
||||
if ok {
|
||||
return converter(vm, goValue.Elem())
|
||||
}
|
||||
}
|
||||
|
||||
switch v := value.(type) {
|
||||
case string, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64, bool:
|
||||
@@ -181,33 +224,6 @@ func convertGoValueToJS(vm *goja.Runtime, goValue reflect.Value) goja.Value {
|
||||
}
|
||||
return vm.ToValue(arr)
|
||||
|
||||
case FetchResult:
|
||||
obj := vm.NewObject()
|
||||
_ = obj.Set("ok", v.OK)
|
||||
_ = obj.Set("status", v.Status)
|
||||
_ = obj.Set("text", func() string {
|
||||
return v.Body
|
||||
})
|
||||
|
||||
headersObj := vm.NewObject()
|
||||
headers := v.Headers
|
||||
_ = headersObj.Set("get", func(c goja.FunctionCall) goja.Value {
|
||||
if len(c.Arguments) < 1 {
|
||||
return goja.Undefined()
|
||||
}
|
||||
key := c.Arguments[0].String()
|
||||
return vm.ToValue(headers[key])
|
||||
})
|
||||
_ = obj.Set("headers", headersObj)
|
||||
|
||||
return obj
|
||||
|
||||
case *FetchResult:
|
||||
if v == nil {
|
||||
return goja.Null()
|
||||
}
|
||||
return convertGoValueToJS(vm, reflect.ValueOf(*v))
|
||||
|
||||
default:
|
||||
return vm.ToValue(v)
|
||||
}
|
||||
@@ -280,9 +296,6 @@ func goTypeToTSType(t reflect.Type) string {
|
||||
}
|
||||
return "Record<string, any>"
|
||||
case reflect.Struct:
|
||||
if t.Name() == "FetchResult" {
|
||||
return "Response"
|
||||
}
|
||||
return "any"
|
||||
default:
|
||||
return "any"
|
||||
@@ -312,56 +325,3 @@ func RegisterBuiltins(vm *goja.Runtime) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type FetchResult struct {
|
||||
OK bool
|
||||
Status int
|
||||
Body string
|
||||
Headers map[string]string
|
||||
}
|
||||
|
||||
func Fetch(url string, options map[string]any) (*FetchResult, error) {
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create request: %w", err)
|
||||
}
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to fetch: %w", err)
|
||||
}
|
||||
defer func() {
|
||||
_ = resp.Body.Close()
|
||||
}()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read body: %w", err)
|
||||
}
|
||||
|
||||
headers := make(map[string]string)
|
||||
for key, values := range resp.Header {
|
||||
if len(values) > 0 {
|
||||
val := values[0]
|
||||
headers[key] = val
|
||||
headers[strings.ToLower(key)] = val
|
||||
}
|
||||
}
|
||||
|
||||
return &FetchResult{
|
||||
OK: resp.StatusCode >= 200 && resp.StatusCode < 300,
|
||||
Status: resp.StatusCode,
|
||||
Body: string(body),
|
||||
Headers: headers,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
RegisterBuiltin("fetch", Fetch)
|
||||
RegisterBuiltin("add", func(a, b int) int {
|
||||
return a + b
|
||||
})
|
||||
RegisterBuiltin("greet", func(name string) string {
|
||||
return fmt.Sprintf("Hello, %s!", name)
|
||||
})
|
||||
}
|
||||
@@ -7,7 +7,8 @@ import (
|
||||
|
||||
"github.com/dop251/goja"
|
||||
"github.com/evanw/esbuild/pkg/api"
|
||||
"reichard.io/poiesis/internal/builtin"
|
||||
"reichard.io/poiesis/internal/runtime/pkg/builtin"
|
||||
_ "reichard.io/poiesis/internal/runtime/standard"
|
||||
)
|
||||
|
||||
type Runtime struct {
|
||||
|
||||
92
internal/runtime/standard/fetch.go
Normal file
92
internal/runtime/standard/fetch.go
Normal file
@@ -0,0 +1,92 @@
|
||||
package standard
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/dop251/goja"
|
||||
"reichard.io/poiesis/internal/runtime/pkg/builtin"
|
||||
)
|
||||
|
||||
type FetchResult struct {
|
||||
OK bool
|
||||
Status int
|
||||
Body string
|
||||
Headers map[string]string
|
||||
}
|
||||
|
||||
func Fetch(url string, options map[string]any) (*FetchResult, error) {
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create request: %w", err)
|
||||
}
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to fetch: %w", err)
|
||||
}
|
||||
defer func() {
|
||||
_ = resp.Body.Close()
|
||||
}()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read body: %w", err)
|
||||
}
|
||||
|
||||
headers := make(map[string]string)
|
||||
for key, values := range resp.Header {
|
||||
if len(values) > 0 {
|
||||
val := values[0]
|
||||
headers[key] = val
|
||||
headers[strings.ToLower(key)] = val
|
||||
}
|
||||
}
|
||||
|
||||
return &FetchResult{
|
||||
OK: resp.StatusCode >= 200 && resp.StatusCode < 300,
|
||||
Status: resp.StatusCode,
|
||||
Body: string(body),
|
||||
Headers: headers,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func convertFetchResult(vm *goja.Runtime, result *FetchResult) goja.Value {
|
||||
if result == nil {
|
||||
return goja.Null()
|
||||
}
|
||||
|
||||
obj := vm.NewObject()
|
||||
_ = obj.Set("ok", result.OK)
|
||||
_ = obj.Set("status", result.Status)
|
||||
_ = obj.Set("text", func() string {
|
||||
return result.Body
|
||||
})
|
||||
|
||||
headersObj := vm.NewObject()
|
||||
headers := result.Headers
|
||||
_ = headersObj.Set("get", func(c goja.FunctionCall) goja.Value {
|
||||
if len(c.Arguments) < 1 {
|
||||
return goja.Undefined()
|
||||
}
|
||||
key := c.Arguments[0].String()
|
||||
return vm.ToValue(headers[key])
|
||||
})
|
||||
_ = obj.Set("headers", headersObj)
|
||||
|
||||
return obj
|
||||
}
|
||||
|
||||
func init() {
|
||||
builtin.RegisterCustomConverter(convertFetchResult)
|
||||
|
||||
builtin.RegisterBuiltin("fetch", Fetch)
|
||||
builtin.RegisterBuiltin("add", func(a, b int) int {
|
||||
return a + b
|
||||
})
|
||||
builtin.RegisterBuiltin("greet", func(name string) string {
|
||||
return fmt.Sprintf("Hello, %s!", name)
|
||||
})
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package builtin
|
||||
package standard
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
@@ -30,6 +30,8 @@ func TestFetch(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFetchHTTPBin(t *testing.T) {
|
||||
t.Skip("httpbin.org test is flaky")
|
||||
|
||||
result, err := Fetch("https://httpbin.org/get", nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
Reference in New Issue
Block a user