wip2
This commit is contained in:
@@ -11,97 +11,79 @@ import (
|
||||
|
||||
type Builtin struct {
|
||||
Name string
|
||||
Function any
|
||||
Function func(*goja.Runtime) func(goja.FunctionCall) goja.Value
|
||||
Definition string
|
||||
}
|
||||
|
||||
type EmptyArgs struct{}
|
||||
|
||||
var (
|
||||
builtinRegistry = make(map[string]Builtin)
|
||||
registryMutex sync.RWMutex
|
||||
customConverters = make(map[reflect.Type]func(*goja.Runtime, reflect.Value) goja.Value)
|
||||
builtinRegistry = make(map[string]Builtin)
|
||||
registryMutex sync.RWMutex
|
||||
)
|
||||
|
||||
func RegisterBuiltin(name string, fn any) {
|
||||
fnValue := reflect.ValueOf(fn)
|
||||
fnType := fnValue.Type()
|
||||
func RegisterBuiltin[T any, R any](name string, fn any) {
|
||||
var zeroT T
|
||||
tType := reflect.TypeOf(zeroT)
|
||||
|
||||
wrapper := createGenericWrapper(fnValue, fnType)
|
||||
definition := generateTypeScriptDefinition(name, fnType)
|
||||
if tType.Kind() != reflect.Struct {
|
||||
panic(fmt.Sprintf("builtin %s: argument must be a struct type, got %v", name, tType))
|
||||
}
|
||||
|
||||
fnType := reflect.TypeOf(fn)
|
||||
wrapper := createWrapper[T](fn, fnType)
|
||||
|
||||
registryMutex.Lock()
|
||||
builtinRegistry[name] = Builtin{
|
||||
Name: name,
|
||||
Function: wrapper,
|
||||
Definition: definition,
|
||||
Definition: generateTypeScriptDefinition(name, tType, fnType),
|
||||
}
|
||||
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 {
|
||||
return func(vm *goja.Runtime) any {
|
||||
func createWrapper[T any](fn any, fnType reflect.Type) func(*goja.Runtime) func(goja.FunctionCall) goja.Value {
|
||||
return func(vm *goja.Runtime) func(goja.FunctionCall) goja.Value {
|
||||
return func(call goja.FunctionCall) goja.Value {
|
||||
args := make([]reflect.Value, fnType.NumIn())
|
||||
var args T
|
||||
argsValue := reflect.ValueOf(&args).Elem()
|
||||
|
||||
for i := 0; i < fnType.NumIn(); i++ {
|
||||
argType := fnType.In(i)
|
||||
var jsArg goja.Value
|
||||
|
||||
if i < len(call.Arguments) {
|
||||
jsArg = call.Arguments[i]
|
||||
} else {
|
||||
jsArg = goja.Undefined()
|
||||
}
|
||||
for i := 0; i < argsValue.NumField() && i < len(call.Arguments); i++ {
|
||||
jsArg := call.Arguments[i]
|
||||
field := argsValue.Field(i)
|
||||
|
||||
if goja.IsUndefined(jsArg) || goja.IsNull(jsArg) {
|
||||
if argType.Kind() == reflect.Map {
|
||||
args[i] = reflect.MakeMap(argType)
|
||||
continue
|
||||
}
|
||||
if argType.Kind() == reflect.Interface {
|
||||
args[i] = reflect.Zero(argType)
|
||||
if field.Kind() == reflect.Pointer {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
converted, err := convertJSValueToGo(vm, jsArg, argType)
|
||||
converted, err := convertJSValueToGo(vm, jsArg, field.Type())
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("argument %d: %v", i, err))
|
||||
panic(fmt.Sprintf("argument %d (%s): %v", i, getFieldName(argsValue.Type().Field(i)), err))
|
||||
}
|
||||
|
||||
if converted != nil {
|
||||
field.Set(reflect.ValueOf(converted))
|
||||
}
|
||||
args[i] = reflect.ValueOf(converted)
|
||||
}
|
||||
|
||||
results := fnValue.Call(args)
|
||||
if defaults, ok := any(args).(interface{ Defaults() T }); ok {
|
||||
args = defaults.Defaults()
|
||||
}
|
||||
|
||||
fnValue := reflect.ValueOf(fn)
|
||||
firstParamType := fnType.In(0)
|
||||
argValue := reflect.ValueOf(args).Convert(firstParamType)
|
||||
results := fnValue.Call([]reflect.Value{argValue})
|
||||
|
||||
if len(results) == 0 {
|
||||
return goja.Undefined()
|
||||
}
|
||||
|
||||
lastResult := results[len(results)-1]
|
||||
if lastResult.Type().Implements(reflect.TypeOf((*error)(nil)).Elem()) {
|
||||
if !lastResult.IsNil() {
|
||||
panic(fmt.Sprintf("error: %v", lastResult.Interface()))
|
||||
if err, isError := results[len(results)-1].Interface().(error); isError {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if len(results) == 1 {
|
||||
return goja.Undefined()
|
||||
@@ -115,8 +97,15 @@ func createGenericWrapper(fnValue reflect.Value, fnType reflect.Type) any {
|
||||
}
|
||||
|
||||
func convertJSValueToGo(vm *goja.Runtime, jsValue goja.Value, targetType reflect.Type) (any, error) {
|
||||
if goja.IsUndefined(jsValue) || goja.IsNull(jsValue) {
|
||||
if targetType.Kind() == reflect.Interface || targetType.Kind() == reflect.Pointer {
|
||||
if goja.IsNull(jsValue) {
|
||||
if targetType.Kind() == reflect.Pointer || targetType.Kind() == reflect.Map {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, fmt.Errorf("cannot convert null/undefined to %v", targetType)
|
||||
}
|
||||
|
||||
if goja.IsUndefined(jsValue) {
|
||||
if targetType.Kind() == reflect.Pointer || targetType.Kind() == reflect.Map {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, fmt.Errorf("cannot convert null/undefined to %v", targetType)
|
||||
@@ -154,20 +143,88 @@ func convertJSValueToGo(vm *goja.Runtime, jsValue goja.Value, targetType reflect
|
||||
return jsValue.Export(), nil
|
||||
|
||||
case reflect.Map:
|
||||
if targetType.Key().Kind() == reflect.String && targetType.Elem().Kind() == reflect.Interface {
|
||||
if goja.IsUndefined(jsValue) || goja.IsNull(jsValue) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if targetType.Key().Kind() == reflect.String {
|
||||
obj := jsValue.ToObject(vm)
|
||||
if obj == nil {
|
||||
return nil, fmt.Errorf("not an object")
|
||||
}
|
||||
|
||||
result := make(map[string]any)
|
||||
for _, key := range obj.Keys() {
|
||||
result[key] = obj.Get(key).Export()
|
||||
if targetType.Elem().Kind() == reflect.Interface {
|
||||
result := make(map[string]any)
|
||||
for _, key := range obj.Keys() {
|
||||
result[key] = obj.Get(key).Export()
|
||||
}
|
||||
return result, nil
|
||||
} else if targetType.Elem().Kind() == reflect.String {
|
||||
result := make(map[string]string)
|
||||
for _, key := range obj.Keys() {
|
||||
v := obj.Get(key)
|
||||
result[key] = v.String()
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
return nil, fmt.Errorf("unsupported map type: %v", targetType)
|
||||
|
||||
case reflect.Struct:
|
||||
obj := jsValue.ToObject(vm)
|
||||
if obj == nil {
|
||||
return nil, fmt.Errorf("not an object")
|
||||
}
|
||||
|
||||
result := reflect.New(targetType).Elem()
|
||||
for i := 0; i < targetType.NumField(); i++ {
|
||||
field := targetType.Field(i)
|
||||
fieldName := getFieldName(field)
|
||||
|
||||
jsField := obj.Get(fieldName)
|
||||
|
||||
var err error
|
||||
var converted any
|
||||
func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
// goja.Value was zero - treat as undefined
|
||||
err = nil
|
||||
converted = nil
|
||||
}
|
||||
}()
|
||||
converted, err = convertJSValueToGo(vm, jsField, field.Type)
|
||||
}()
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("field %s: %v", fieldName, err)
|
||||
}
|
||||
|
||||
if converted == nil {
|
||||
if field.Type.Kind() == reflect.Pointer || field.Type.Kind() == reflect.Map {
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
result.Field(i).Set(reflect.ValueOf(converted))
|
||||
}
|
||||
}
|
||||
return result.Interface(), nil
|
||||
|
||||
case reflect.Pointer:
|
||||
if goja.IsNull(jsValue) || goja.IsUndefined(jsValue) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
elemType := targetType.Elem()
|
||||
converted, err := convertJSValueToGo(vm, jsValue, elemType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ptr := reflect.New(elemType)
|
||||
ptr.Elem().Set(reflect.ValueOf(converted))
|
||||
return ptr.Interface(), nil
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported type: %v", targetType)
|
||||
}
|
||||
@@ -175,26 +232,6 @@ 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:
|
||||
@@ -213,7 +250,7 @@ func convertGoValueToJS(vm *goja.Runtime, goValue reflect.Value) goja.Value {
|
||||
case map[string]any:
|
||||
obj := vm.NewObject()
|
||||
for key, val := range v {
|
||||
_ = obj.Set(key, val)
|
||||
_ = obj.Set(key, convertGoValueToJS(vm, reflect.ValueOf(val)))
|
||||
}
|
||||
return obj
|
||||
|
||||
@@ -225,56 +262,76 @@ func convertGoValueToJS(vm *goja.Runtime, goValue reflect.Value) goja.Value {
|
||||
return vm.ToValue(arr)
|
||||
|
||||
default:
|
||||
if goValue.Kind() == reflect.Pointer {
|
||||
if goValue.IsNil() {
|
||||
return goja.Null()
|
||||
}
|
||||
return convertGoValueToJS(vm, goValue.Elem())
|
||||
}
|
||||
|
||||
if goValue.Kind() == reflect.Struct {
|
||||
obj := vm.NewObject()
|
||||
for i := 0; i < goValue.NumField(); i++ {
|
||||
field := goValue.Type().Field(i)
|
||||
fieldName := getFieldName(field)
|
||||
_ = obj.Set(fieldName, convertGoValueToJS(vm, goValue.Field(i)))
|
||||
}
|
||||
return obj
|
||||
}
|
||||
|
||||
return vm.ToValue(v)
|
||||
}
|
||||
}
|
||||
|
||||
func generateTypeScriptDefinition(name string, fnType reflect.Type) string {
|
||||
if fnType.Kind() != reflect.Func {
|
||||
func getFieldName(field reflect.StructField) string {
|
||||
jsonTag := field.Tag.Get("json")
|
||||
if jsonTag != "" && jsonTag != "-" {
|
||||
name, _, _ := strings.Cut(jsonTag, ",")
|
||||
return name
|
||||
}
|
||||
return field.Name
|
||||
}
|
||||
|
||||
func generateTypeScriptDefinition(name string, argsType reflect.Type, fnType reflect.Type) string {
|
||||
if argsType.Kind() != reflect.Struct {
|
||||
return ""
|
||||
}
|
||||
|
||||
var params []string
|
||||
for i := 0; i < fnType.NumIn(); i++ {
|
||||
paramName := fmt.Sprintf("arg%d", i)
|
||||
if fnType.In(i).Kind() == reflect.Pointer {
|
||||
ptrType := fnType.In(i).Elem()
|
||||
if ptrType.Kind() == reflect.Struct {
|
||||
if s, ok := extractStructParamName(ptrType); ok {
|
||||
paramName = s
|
||||
}
|
||||
}
|
||||
}
|
||||
params = append(params, fmt.Sprintf("%s: %s", paramName, goTypeToTSType(fnType.In(i))))
|
||||
for i := 0; i < argsType.NumField(); i++ {
|
||||
field := argsType.Field(i)
|
||||
fieldName := getFieldName(field)
|
||||
goType := field.Type
|
||||
|
||||
tsType := goTypeToTSType(goType, goType.Kind() == reflect.Pointer)
|
||||
params = append(params, fmt.Sprintf("%s: %s", fieldName, tsType))
|
||||
}
|
||||
|
||||
returnSignature := "void"
|
||||
if fnType.NumOut() > 0 {
|
||||
returnSignature := "any"
|
||||
if fnType.Kind() == reflect.Func && fnType.NumOut() > 0 {
|
||||
lastIndex := fnType.NumOut() - 1
|
||||
lastType := fnType.Out(lastIndex)
|
||||
|
||||
if lastType.Implements(reflect.TypeOf((*error)(nil)).Elem()) {
|
||||
if fnType.NumOut() > 1 {
|
||||
returnSignature = goTypeToTSType(fnType.Out(0))
|
||||
} else {
|
||||
returnSignature = "void"
|
||||
returnSignature = goTypeToTSType(fnType.Out(0), false)
|
||||
}
|
||||
} else {
|
||||
returnSignature = goTypeToTSType(lastType)
|
||||
returnSignature = goTypeToTSType(lastType, false)
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Sprintf("declare function %s(%s): %s;", name, strings.Join(params, ", "), returnSignature)
|
||||
}
|
||||
|
||||
func extractStructParamName(structType reflect.Type) (string, bool) {
|
||||
if structType.Name() != "" {
|
||||
return strings.ToLower(structType.Name()), true
|
||||
func goTypeToTSType(t reflect.Type, isPointer bool) string {
|
||||
if isPointer {
|
||||
if t.Kind() == reflect.Pointer {
|
||||
return goTypeToTSType(t.Elem(), false) + " | null"
|
||||
}
|
||||
return goTypeToTSType(t, false) + " | null"
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
func goTypeToTSType(t reflect.Type) string {
|
||||
switch t.Kind() {
|
||||
case reflect.String:
|
||||
return "string"
|
||||
@@ -286,17 +343,30 @@ func goTypeToTSType(t reflect.Type) string {
|
||||
return "number"
|
||||
case reflect.Bool:
|
||||
return "boolean"
|
||||
case reflect.Interface, reflect.Pointer:
|
||||
case reflect.Interface:
|
||||
return "any"
|
||||
case reflect.Slice:
|
||||
return fmt.Sprintf("%s[]", goTypeToTSType(t.Elem()))
|
||||
return fmt.Sprintf("%s[]", goTypeToTSType(t.Elem(), false))
|
||||
case reflect.Map:
|
||||
if t.Key().Kind() == reflect.String && t.Elem().Kind() == reflect.Interface {
|
||||
return "Record<string, any>"
|
||||
}
|
||||
return "Record<string, any>"
|
||||
case reflect.Struct:
|
||||
return "any"
|
||||
fields := make([]string, 0, t.NumField())
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
field := t.Field(i)
|
||||
name := getFieldName(field)
|
||||
tsType := goTypeToTSType(field.Type, field.Type.Kind() == reflect.Pointer)
|
||||
if field.Type.Kind() == reflect.Pointer {
|
||||
tsType = strings.TrimSuffix(tsType, " | null")
|
||||
tsType += "?"
|
||||
} else if strings.Contains(field.Tag.Get("json"), ",omitempty") {
|
||||
tsType += "?"
|
||||
}
|
||||
fields = append(fields, fmt.Sprintf("%s: %s", name, tsType))
|
||||
}
|
||||
return fmt.Sprintf("{ %s }", strings.Join(fields, "; "))
|
||||
default:
|
||||
return "any"
|
||||
}
|
||||
@@ -318,10 +388,6 @@ func RegisterBuiltins(vm *goja.Runtime) {
|
||||
defer registryMutex.RUnlock()
|
||||
|
||||
for name, builtin := range builtinRegistry {
|
||||
if wrapperFactory, ok := builtin.Function.(func(*goja.Runtime) any); ok {
|
||||
_ = vm.Set(name, wrapperFactory(vm))
|
||||
} else {
|
||||
_ = vm.Set(name, builtin.Function)
|
||||
}
|
||||
_ = vm.Set(name, builtin.Function(vm))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,26 +3,66 @@ package standard
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"maps"
|
||||
"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
|
||||
type FetchArgs struct {
|
||||
URL string `json:"url"`
|
||||
Options *FetchOptions `json:"options"`
|
||||
}
|
||||
|
||||
func Fetch(url string, options map[string]any) (*FetchResult, error) {
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
type FetchOptions struct {
|
||||
Method string `json:"method"`
|
||||
Headers *map[string]string `json:"headers"`
|
||||
}
|
||||
|
||||
func (o *FetchOptions) Defaults() *FetchOptions {
|
||||
if o.Method == "" {
|
||||
o.Method = "GET"
|
||||
}
|
||||
return o
|
||||
}
|
||||
|
||||
type FetchResult struct {
|
||||
OK bool `json:"ok"`
|
||||
Status int `json:"status"`
|
||||
Body string `json:"body"`
|
||||
Headers map[string]string `json:"headers"`
|
||||
}
|
||||
|
||||
type AddArgs struct {
|
||||
A int `json:"a"`
|
||||
B int `json:"b"`
|
||||
}
|
||||
|
||||
type GreetArgs struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
func Fetch(args FetchArgs) (*FetchResult, error) {
|
||||
method := "GET"
|
||||
headers := make(map[string]string)
|
||||
|
||||
if args.Options != nil {
|
||||
method = args.Options.Method
|
||||
if args.Options.Headers != nil {
|
||||
maps.Copy(headers, *args.Options.Headers)
|
||||
}
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(method, args.URL, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create request: %w", err)
|
||||
}
|
||||
|
||||
for k, v := range headers {
|
||||
req.Header.Set(k, v)
|
||||
}
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to fetch: %w", err)
|
||||
@@ -36,12 +76,12 @@ func Fetch(url string, options map[string]any) (*FetchResult, error) {
|
||||
return nil, fmt.Errorf("failed to read body: %w", err)
|
||||
}
|
||||
|
||||
headers := make(map[string]string)
|
||||
resultHeaders := 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
|
||||
resultHeaders[key] = val
|
||||
resultHeaders[strings.ToLower(key)] = val
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,44 +89,20 @@ func Fetch(url string, options map[string]any) (*FetchResult, error) {
|
||||
OK: resp.StatusCode >= 200 && resp.StatusCode < 300,
|
||||
Status: resp.StatusCode,
|
||||
Body: string(body),
|
||||
Headers: headers,
|
||||
Headers: resultHeaders,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func convertFetchResult(vm *goja.Runtime, result *FetchResult) goja.Value {
|
||||
if result == nil {
|
||||
return goja.Null()
|
||||
}
|
||||
func add(args AddArgs) int {
|
||||
return args.A + args.B
|
||||
}
|
||||
|
||||
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 greet(args GreetArgs) string {
|
||||
return fmt.Sprintf("Hello, %s!", args.Name)
|
||||
}
|
||||
|
||||
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)
|
||||
})
|
||||
builtin.RegisterBuiltin[FetchArgs, *FetchResult]("fetch", Fetch)
|
||||
builtin.RegisterBuiltin[AddArgs, int]("add", add)
|
||||
builtin.RegisterBuiltin[GreetArgs, string]("greet", greet)
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ func TestFetch(t *testing.T) {
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
result, err := Fetch(server.URL, nil)
|
||||
result, err := Fetch(FetchArgs{URL: server.URL})
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.True(t, result.OK)
|
||||
@@ -32,7 +32,7 @@ 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)
|
||||
result, err := Fetch(FetchArgs{URL: "https://httpbin.org/get"})
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.True(t, result.OK)
|
||||
@@ -42,7 +42,7 @@ func TestFetchHTTPBin(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFetchWith404(t *testing.T) {
|
||||
result, err := Fetch("https://httpbin.org/status/404", nil)
|
||||
result, err := Fetch(FetchArgs{URL: "https://httpbin.org/status/404"})
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.False(t, result.OK)
|
||||
@@ -50,7 +50,58 @@ func TestFetchWith404(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFetchWithInvalidURL(t *testing.T) {
|
||||
_, err := Fetch("http://this-domain-does-not-exist-12345.com", nil)
|
||||
_, err := Fetch(FetchArgs{URL: "http://this-domain-does-not-exist-12345.com"})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "failed to fetch")
|
||||
}
|
||||
|
||||
func TestFetchWithHeaders(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
assert.Equal(t, "Bearer test-token", r.Header.Get("Authorization"))
|
||||
assert.Equal(t, "GET", r.Method)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, _ = w.Write([]byte(`ok`))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
headers := map[string]string{
|
||||
"Authorization": "Bearer test-token",
|
||||
}
|
||||
options := &FetchOptions{
|
||||
Method: "GET",
|
||||
Headers: &headers,
|
||||
}
|
||||
result, err := Fetch(FetchArgs{URL: server.URL, Options: options})
|
||||
require.NoError(t, err)
|
||||
assert.True(t, result.OK)
|
||||
}
|
||||
|
||||
func TestFetchDefaults(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
assert.Equal(t, "GET", r.Method, "default method should be GET")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, _ = w.Write([]byte(`ok`))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
options := &FetchOptions{}
|
||||
result, err := Fetch(FetchArgs{URL: server.URL, Options: options})
|
||||
require.NoError(t, err)
|
||||
assert.True(t, result.OK)
|
||||
}
|
||||
|
||||
func TestAdd(t *testing.T) {
|
||||
result := add(AddArgs{A: 5, B: 10})
|
||||
assert.Equal(t, 15, result)
|
||||
|
||||
result = add(AddArgs{A: -3, B: 7})
|
||||
assert.Equal(t, 4, result)
|
||||
}
|
||||
|
||||
func TestGreet(t *testing.T) {
|
||||
result := greet(GreetArgs{Name: "World"})
|
||||
assert.Equal(t, "Hello, World!", result)
|
||||
|
||||
result = greet(GreetArgs{Name: "Alice"})
|
||||
assert.Equal(t, "Hello, Alice!", result)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user