diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c22dc2..6be8407 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -57,6 +57,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 reads and construct only via `NewExecutableUnit(...)`. Mid-flight mutation of `DataProvider` (and the other fields) is no longer possible from outside the package. Closes [#91](https://github.com/robbyt/go-polyscript/issues/91). +- **Breaking:** Tightened the `platform.EvaluatorResponse` interface: + - `GetScriptExeID() string` → `ScriptExeID() string` (dropped `Get` prefix). + - `GetExecTime() string` → `ExecTime() time.Duration` (no longer stringified + at the boundary). + - Added `AsMap() (map[string]any, error)` returning the result as a typed + map. On type mismatch, the returned error wraps a per-engine sentinel + (`engines/{extism,risor,starlark}/evaluator.ErrAsMapTypeMismatch`) and + includes the actual Go type via `"%T"`, so callers can use + `errors.Is(err, evaluator.ErrAsMapTypeMismatch)` and still read the + concrete type from `err.Error()`. Replaces the + `result.Interface().(map[string]any)` ceremony at 40+ call sites across + the test suite. + Closes [#86](https://github.com/robbyt/go-polyscript/issues/86). ### Deprecated - The twelve legacy top-level constructors (`FromRisorFile`, diff --git a/engines/extism/evaluator/errors.go b/engines/extism/evaluator/errors.go new file mode 100644 index 0000000..a152e09 --- /dev/null +++ b/engines/extism/evaluator/errors.go @@ -0,0 +1,9 @@ +package evaluator + +import "errors" + +// ErrAsMapTypeMismatch is returned by (*execResult).AsMap when the underlying +// value is not a map[string]any. Wrapped with the actual Go type via "%T", so +// callers can use errors.Is(err, ErrAsMapTypeMismatch) and still read the +// concrete type from err.Error(). +var ErrAsMapTypeMismatch = errors.New("extism: AsMap expected map[string]any") diff --git a/engines/extism/evaluator/evaluator_test.go b/engines/extism/evaluator/evaluator_test.go index cc57600..272be7a 100644 --- a/engines/extism/evaluator/evaluator_test.go +++ b/engines/extism/evaluator/evaluator_test.go @@ -188,8 +188,8 @@ func TestEvaluator_Evaluate(t *testing.T) { require.NotNil(t, response) // Verify the response - resultMap, ok := response.Interface().(map[string]any) - require.True(t, ok, "Expected map response") + resultMap, err := response.AsMap() + require.NoError(t, err, "Expected map response") require.Contains(t, resultMap, "result") require.Equal(t, "success", resultMap["result"]) require.Contains(t, resultMap, "value") diff --git a/engines/extism/evaluator/response.go b/engines/extism/evaluator/response.go index 2498d59..1ba932e 100644 --- a/engines/extism/evaluator/response.go +++ b/engines/extism/evaluator/response.go @@ -39,7 +39,7 @@ func newEvalResult( func (r *execResult) String() string { return fmt.Sprintf( "execResult{Type: %s, Value: %v, ExecTime: %s, ScriptExeID: %s}", - r.Type(), r.value, r.GetExecTime(), r.GetScriptExeID(), + r.Type(), r.value, r.ExecTime(), r.ScriptExeID(), ) } @@ -66,12 +66,20 @@ func (r *execResult) Type() data.Types { } } -func (r *execResult) GetScriptExeID() string { +func (r *execResult) ScriptExeID() string { return r.scriptExeID } -func (r *execResult) GetExecTime() string { - return r.execTime.String() +func (r *execResult) ExecTime() time.Duration { + return r.execTime +} + +func (r *execResult) AsMap() (map[string]any, error) { + m, ok := r.value.(map[string]any) + if !ok { + return nil, fmt.Errorf("%w, got %T", ErrAsMapTypeMismatch, r.value) + } + return m, nil } func (r *execResult) Inspect() string { diff --git a/engines/extism/evaluator/response_test.go b/engines/extism/evaluator/response_test.go index 9a01e0a..7e50404 100644 --- a/engines/extism/evaluator/response_test.go +++ b/engines/extism/evaluator/response_test.go @@ -333,11 +333,11 @@ func TestResponseMethods(t *testing.T) { handler := slog.NewTextHandler(os.Stdout, nil) result := newEvalResult(handler, tt.value, tt.execTime, tt.versionID) - // Test GetScriptExeID - assert.Equal(t, tt.versionID, result.GetScriptExeID()) + // Test ScriptExeID + assert.Equal(t, tt.versionID, result.ScriptExeID()) - // Test GetExecTime - assert.Equal(t, tt.execTime.String(), result.GetExecTime()) + // Test ExecTime + assert.Equal(t, tt.execTime, result.ExecTime()) }) } }) @@ -387,3 +387,34 @@ func TestResponseMethods(t *testing.T) { } }) } + +func TestExecResultAsMap(t *testing.T) { + t.Parallel() + handler := slog.NewTextHandler(os.Stdout, nil) + + t.Run("success", func(t *testing.T) { + m := map[string]any{"k": "v", "n": 42} + result := newEvalResult(handler, m, time.Millisecond, "id-ok") + got, err := result.AsMap() + require.NoError(t, err) + assert.Equal(t, m, got) + }) + + t.Run("error on string", func(t *testing.T) { + result := newEvalResult(handler, "not a map", time.Millisecond, "id-bad") + got, err := result.AsMap() + require.Error(t, err) + assert.Nil(t, got) + require.ErrorIs(t, err, ErrAsMapTypeMismatch) + assert.Contains(t, err.Error(), "got string") + }) + + t.Run("error on nil", func(t *testing.T) { + result := newEvalResult(handler, nil, time.Millisecond, "id-nil") + got, err := result.AsMap() + require.Error(t, err) + assert.Nil(t, got) + require.ErrorIs(t, err, ErrAsMapTypeMismatch) + assert.Contains(t, err.Error(), "got ") + }) +} diff --git a/engines/extism/new_test.go b/engines/extism/new_test.go index 3c79287..8d52de2 100644 --- a/engines/extism/new_test.go +++ b/engines/extism/new_test.go @@ -185,8 +185,8 @@ func TestFromExtismLoader_RunsEndToEnd(t *testing.T) { res, err := eval.Eval(t.Context()) require.NoError(t, err) - got, ok := res.Interface().(map[string]any) - require.True(t, ok) + got, err := res.AsMap() + require.NoError(t, err) assert.Equal(t, "Hello, World!", got["greeting"]) } diff --git a/engines/integration_test.go b/engines/integration_test.go index 681a491..2f28393 100644 --- a/engines/integration_test.go +++ b/engines/integration_test.go @@ -82,8 +82,8 @@ let tags = ctx["tags"] require.NoError(t, err) // Verify results - resultMap, ok := result.Interface().(map[string]any) - require.True(t, ok) + resultMap, err := result.AsMap() + require.NoError(t, err) assert.Equal(t, "risor", resultMap["engine"]) assert.Equal(t, "Integration Test", resultMap["name"]) @@ -135,8 +135,8 @@ _ = result require.NoError(t, err) // Verify results - resultMap, ok := result.Interface().(map[string]any) - require.True(t, ok) + resultMap, err := result.AsMap() + require.NoError(t, err) assert.Equal(t, "starlark", resultMap["engine"]) assert.Equal(t, "Integration Test", resultMap["name"]) @@ -175,8 +175,8 @@ _ = result require.NoError(t, err) // Verify results - the greet function returns {"greeting": "Hello, !"} - resultMap, ok := result.Interface().(map[string]any) - require.True(t, ok) + resultMap, err := result.AsMap() + require.NoError(t, err) greeting, exists := resultMap["greeting"] require.True(t, exists) @@ -239,8 +239,8 @@ let action = ctx["action"] require.NoError(t, err) // Verify both static and dynamic data are accessible - resultMap, ok := result.Interface().(map[string]any) - require.True(t, ok) + resultMap, err := result.AsMap() + require.NoError(t, err) assert.Equal(t, "TestApp", resultMap["app"]) // Static data assert.Equal(t, "user123", resultMap["user"]) // Dynamic data @@ -282,8 +282,8 @@ _ = result require.NoError(t, err) // Verify both static and dynamic data are accessible - resultMap, ok := result.Interface().(map[string]any) - require.True(t, ok) + resultMap, err := result.AsMap() + require.NoError(t, err) assert.Equal(t, "TestApp", resultMap["app"]) // Static data assert.Equal(t, "user123", resultMap["user"]) // Dynamic data @@ -323,8 +323,8 @@ _ = result require.NoError(t, err) // Verify the WASM module processed the data - resultMap, ok := result.Interface().(map[string]any) - require.True(t, ok) + resultMap, err := result.AsMap() + require.NoError(t, err) greeting, exists := resultMap["greeting"] require.True(t, exists) @@ -364,8 +364,8 @@ let debug = ctx["config"]["debug"] result, err := evaluator.Eval(t.Context()) require.NoError(t, err) - resultMap, ok := result.Interface().(map[string]any) - require.True(t, ok) + resultMap, err := result.AsMap() + require.NoError(t, err) assert.Equal(t, "World", resultMap["name"]) assert.Equal(t, true, resultMap["debug"]) @@ -390,8 +390,8 @@ _ = result result, err := evaluator.Eval(t.Context()) require.NoError(t, err) - resultMap, ok := result.Interface().(map[string]any) - require.True(t, ok) + resultMap, err := result.AsMap() + require.NoError(t, err) assert.Equal(t, "World", resultMap["name"]) assert.Equal(t, true, resultMap["debug"]) @@ -416,8 +416,8 @@ _ = result result, err := evaluator.Eval(t.Context()) require.NoError(t, err) - resultMap, ok := result.Interface().(map[string]any) - require.True(t, ok) + resultMap, err := result.AsMap() + require.NoError(t, err) // The greet function returns {"greeting": "Hello, !"} greeting, exists := resultMap["greeting"] @@ -476,8 +476,8 @@ let user_data = ctx["user_data"] require.NoError(t, err) // Verify - resultMap, ok := result.Interface().(map[string]any) - require.True(t, ok) + resultMap, err := result.AsMap() + require.NoError(t, err) config, exists := resultMap["config"].(map[string]any) require.True(t, exists) @@ -534,8 +534,8 @@ _ = result require.NoError(t, err) // Verify - resultMap, ok := result.Interface().(map[string]any) - require.True(t, ok) + resultMap, err := result.AsMap() + require.NoError(t, err) config, exists := resultMap["config"].(map[string]any) require.True(t, exists) @@ -576,8 +576,8 @@ _ = result require.NoError(t, err) // Verify - resultMap, ok := result.Interface().(map[string]any) - require.True(t, ok) + resultMap, err := result.AsMap() + require.NoError(t, err) greeting, exists := resultMap["greeting"] require.True(t, exists) @@ -624,8 +624,8 @@ let max_retries = ctx["constants"]["max_retries"] require.NoError(t, err) // Verify - resultMap, ok := result.Interface().(map[string]any) - require.True(t, ok) + resultMap, err := result.AsMap() + require.NoError(t, err) assert.Equal(t, "TestApp", resultMap["app_name"]) assert.Equal(t, "1.0.0", resultMap["version"]) @@ -661,8 +661,8 @@ _ = result require.NoError(t, err) // Verify - resultMap, ok := result.Interface().(map[string]any) - require.True(t, ok) + resultMap, err := result.AsMap() + require.NoError(t, err) assert.Equal(t, "TestApp", resultMap["app_name"]) assert.Equal(t, "1.0.0", resultMap["version"]) @@ -692,8 +692,8 @@ _ = result require.NoError(t, err) // Verify - resultMap, ok := result.Interface().(map[string]any) - require.True(t, ok) + resultMap, err := result.AsMap() + require.NoError(t, err) greeting, exists := resultMap["greeting"] require.True(t, exists) @@ -752,8 +752,8 @@ let request_id = ctx["request_id"] require.NoError(t, err) // Verify both static and dynamic data are accessible - resultMap, ok := result.Interface().(map[string]any) - require.True(t, ok) + resultMap, err := result.AsMap() + require.NoError(t, err) assert.Equal(t, "TestApp", resultMap["app_name"]) // Static data assert.Equal(t, "1.0.0", resultMap["version"]) // Static data @@ -799,8 +799,8 @@ _ = result require.NoError(t, err) // Verify both static and dynamic data are accessible - resultMap, ok := result.Interface().(map[string]any) - require.True(t, ok) + resultMap, err := result.AsMap() + require.NoError(t, err) assert.Equal(t, "TestApp", resultMap["app_name"]) // Static data assert.Equal(t, "1.0.0", resultMap["version"]) // Static data @@ -845,8 +845,8 @@ _ = result require.NoError(t, err) // Verify dynamic data overrode static data - resultMap, ok := result.Interface().(map[string]any) - require.True(t, ok) + resultMap, err := result.AsMap() + require.NoError(t, err) greeting, exists := resultMap["greeting"] require.True(t, exists) @@ -914,8 +914,8 @@ let content_type = ctx["request"]["Headers"]["Content-Type"][0] require.NoError(t, err) // Verify HTTP request data is accessible - resultMap, ok := result.Interface().(map[string]any) - require.True(t, ok) + resultMap, err := result.AsMap() + require.NoError(t, err) assert.Equal(t, "POST", resultMap["method"]) assert.Equal(t, "/api/test", resultMap["path"]) @@ -963,8 +963,8 @@ _ = result require.NoError(t, err) // Verify HTTP request data is accessible - resultMap, ok := result.Interface().(map[string]any) - require.True(t, ok) + resultMap, err := result.AsMap() + require.NoError(t, err) assert.Equal(t, "POST", resultMap["method"]) assert.Equal(t, "/api/test", resultMap["path"]) @@ -998,8 +998,8 @@ _ = result require.NoError(t, err) // Verify the WASM module processed the request data - resultMap, ok := result.Interface().(map[string]any) - require.True(t, ok) + resultMap, err := result.AsMap() + require.NoError(t, err) greeting, exists := resultMap["greeting"] require.True(t, exists) @@ -1048,8 +1048,8 @@ let config_data = ctx["config"] require.NoError(t, err) // Verify explicit key wrapping worked - resultMap, ok := result.Interface().(map[string]any) - require.True(t, ok) + resultMap, err := result.AsMap() + require.NoError(t, err) assert.Equal(t, true, resultMap["has_request"]) assert.Equal(t, true, resultMap["has_user"]) diff --git a/engines/risor/evaluator/errors.go b/engines/risor/evaluator/errors.go new file mode 100644 index 0000000..0fb081b --- /dev/null +++ b/engines/risor/evaluator/errors.go @@ -0,0 +1,9 @@ +package evaluator + +import "errors" + +// ErrAsMapTypeMismatch is returned by (*execResult).AsMap when the underlying +// value is not a map[string]any. Wrapped with the actual Go type via "%T", so +// callers can use errors.Is(err, ErrAsMapTypeMismatch) and still read the +// concrete type from err.Error(). +var ErrAsMapTypeMismatch = errors.New("risor: AsMap expected map[string]any") diff --git a/engines/risor/evaluator/response.go b/engines/risor/evaluator/response.go index ce0d773..ad5db8c 100644 --- a/engines/risor/evaluator/response.go +++ b/engines/risor/evaluator/response.go @@ -40,17 +40,26 @@ func newEvalResult( func (r *execResult) String() string { return fmt.Sprintf( "ExecResult{Type: %s, Value: %v, ExecTime: %s, ScriptExeID: %s}", - r.Type(), r.Object, r.GetExecTime(), r.GetScriptExeID()) + r.Type(), r.Object, r.ExecTime(), r.ScriptExeID()) } func (r *execResult) Type() data.Types { return data.Types(r.Object.Type()) } -func (r *execResult) GetScriptExeID() string { +func (r *execResult) ScriptExeID() string { return r.scriptExeID } -func (r *execResult) GetExecTime() string { - return r.execTime.String() +func (r *execResult) ExecTime() time.Duration { + return r.execTime +} + +func (r *execResult) AsMap() (map[string]any, error) { + v := r.Interface() + m, ok := v.(map[string]any) + if !ok { + return nil, fmt.Errorf("%w, got %T", ErrAsMapTypeMismatch, v) + } + return m, nil } diff --git a/engines/risor/evaluator/response_test.go b/engines/risor/evaluator/response_test.go index a81997c..5046a62 100644 --- a/engines/risor/evaluator/response_test.go +++ b/engines/risor/evaluator/response_test.go @@ -121,8 +121,8 @@ func TestResponseMethods(t *testing.T) { require.Implements(t, (*platform.EvaluatorResponse)(nil), result) // Verify metadata methods - assert.Equal(t, tt.execTime.String(), result.GetExecTime()) - assert.Equal(t, tt.versionID, result.GetScriptExeID()) + assert.Equal(t, tt.execTime, result.ExecTime()) + assert.Equal(t, tt.versionID, result.ScriptExeID()) }) } }) @@ -347,12 +347,49 @@ func TestResponseMethods(t *testing.T) { result := newEvalResult(handler, mockObj, tt.execTime, tt.versionID) - // Test GetScriptExeID - assert.Equal(t, tt.versionID, result.GetScriptExeID()) + // Test ScriptExeID + assert.Equal(t, tt.versionID, result.ScriptExeID()) - // Test GetExecTime - assert.Equal(t, tt.execTime.String(), result.GetExecTime()) + // Test ExecTime + assert.Equal(t, tt.execTime, result.ExecTime()) }) } }) } + +func TestExecResultAsMap(t *testing.T) { + t.Parallel() + handler := slog.NewTextHandler(os.Stdout, nil) + + t.Run("success", func(t *testing.T) { + m := map[string]any{"k": "v", "n": 42} + mockObj := new(RisorObjectMock) + mockObj.On("Interface").Return(m) + result := newEvalResult(handler, mockObj, time.Millisecond, "id-ok") + got, err := result.AsMap() + require.NoError(t, err) + assert.Equal(t, m, got) + }) + + t.Run("error on string", func(t *testing.T) { + mockObj := new(RisorObjectMock) + mockObj.On("Interface").Return("not a map") + result := newEvalResult(handler, mockObj, time.Millisecond, "id-bad") + got, err := result.AsMap() + require.Error(t, err) + assert.Nil(t, got) + require.ErrorIs(t, err, ErrAsMapTypeMismatch) + assert.Contains(t, err.Error(), "got string") + }) + + t.Run("error on int", func(t *testing.T) { + mockObj := new(RisorObjectMock) + mockObj.On("Interface").Return(42) + result := newEvalResult(handler, mockObj, time.Millisecond, "id-int") + got, err := result.AsMap() + require.Error(t, err) + assert.Nil(t, got) + require.ErrorIs(t, err, ErrAsMapTypeMismatch) + assert.Contains(t, err.Error(), "got int") + }) +} diff --git a/engines/starlark/evaluator/errors.go b/engines/starlark/evaluator/errors.go new file mode 100644 index 0000000..b58f6d4 --- /dev/null +++ b/engines/starlark/evaluator/errors.go @@ -0,0 +1,9 @@ +package evaluator + +import "errors" + +// ErrAsMapTypeMismatch is returned by (*execResult).AsMap when the underlying +// value is not a map[string]any. Wrapped with the actual Go type via "%T", so +// callers can use errors.Is(err, ErrAsMapTypeMismatch) and still read the +// concrete type from err.Error(). +var ErrAsMapTypeMismatch = errors.New("starlark: AsMap expected map[string]any") diff --git a/engines/starlark/evaluator/response.go b/engines/starlark/evaluator/response.go index 4f5ef5e..23a1a72 100644 --- a/engines/starlark/evaluator/response.go +++ b/engines/starlark/evaluator/response.go @@ -44,7 +44,7 @@ func newEvalResult( func (r *execResult) String() string { return fmt.Sprintf( "ExecResult{Type: %s, Value: %v, ExecTime: %s, ScriptExeID: %s}", - r.Type(), r.Value, r.GetExecTime(), r.GetScriptExeID()) + r.Type(), r.Value, r.ExecTime(), r.ScriptExeID()) } func (r *execResult) Type() data.Types { @@ -76,12 +76,21 @@ func (r *execResult) Type() data.Types { } } -func (r *execResult) GetScriptExeID() string { +func (r *execResult) ScriptExeID() string { return r.scriptExeID } -func (r *execResult) GetExecTime() string { - return r.execTime.String() +func (r *execResult) ExecTime() time.Duration { + return r.execTime +} + +func (r *execResult) AsMap() (map[string]any, error) { + v := r.Interface() + m, ok := v.(map[string]any) + if !ok { + return nil, fmt.Errorf("%w, got %T", ErrAsMapTypeMismatch, v) + } + return m, nil } func (r *execResult) Inspect() string { diff --git a/engines/starlark/evaluator/response_test.go b/engines/starlark/evaluator/response_test.go index 0861238..aa0b10b 100644 --- a/engines/starlark/evaluator/response_test.go +++ b/engines/starlark/evaluator/response_test.go @@ -80,9 +80,9 @@ func TestResponseMethods(t *testing.T) { require.NotNil(t, result) require.Equal(t, mockVal, result.Value) require.Equal(t, tt.execTime, result.execTime) - assert.Equal(t, tt.execTime.String(), result.GetExecTime()) + assert.Equal(t, tt.execTime, result.ExecTime()) require.Equal(t, tt.versionID, result.scriptExeID) - require.Equal(t, tt.versionID, result.GetScriptExeID()) + require.Equal(t, tt.versionID, result.ScriptExeID()) require.Implements(t, (*platform.EvaluatorResponse)(nil), result) mockVal.AssertExpectations(t) @@ -239,11 +239,11 @@ func TestResponseMethods(t *testing.T) { handler := slog.NewTextHandler(os.Stdout, nil) result := newEvalResult(handler, mockVal, tt.execTime, tt.scriptID) - // Test GetScriptExeID - assert.Equal(t, tt.scriptID, result.GetScriptExeID()) + // Test ScriptExeID + assert.Equal(t, tt.scriptID, result.ScriptExeID()) - // Test GetExecTime - assert.Equal(t, tt.execTime.String(), result.GetExecTime()) + // Test ExecTime + assert.Equal(t, tt.execTime, result.ExecTime()) }) } }) @@ -291,3 +291,39 @@ func TestResponseMethods(t *testing.T) { } }) } + +func TestExecResultAsMap(t *testing.T) { + t.Parallel() + handler := slog.NewTextHandler(os.Stdout, nil) + + t.Run("success", func(t *testing.T) { + dict := starlark.NewDict(2) + require.NoError(t, dict.SetKey(starlark.String("greeting"), starlark.String("hi"))) + require.NoError(t, dict.SetKey(starlark.String("n"), starlark.MakeInt(42))) + + result := newEvalResult(handler, dict, time.Millisecond, "id-ok") + got, err := result.AsMap() + require.NoError(t, err) + assert.Equal(t, "hi", got["greeting"]) + assert.Equal(t, int64(42), got["n"]) + }) + + t.Run("error on string", func(t *testing.T) { + result := newEvalResult(handler, starlark.String("not a map"), time.Millisecond, "id-bad") + got, err := result.AsMap() + require.Error(t, err) + assert.Nil(t, got) + require.ErrorIs(t, err, ErrAsMapTypeMismatch) + assert.Contains(t, err.Error(), "got string") + }) + + t.Run("error on int", func(t *testing.T) { + result := newEvalResult(handler, starlark.MakeInt(123), time.Millisecond, "id-int") + got, err := result.AsMap() + require.Error(t, err) + assert.Nil(t, got) + require.ErrorIs(t, err, ErrAsMapTypeMismatch) + // starlark int converts to int64; the AsMap error message reflects the Go type. + assert.Contains(t, err.Error(), "got int64") + }) +} diff --git a/platform/evaluatorResponse.go b/platform/evaluatorResponse.go index b460b80..0e5d52d 100644 --- a/platform/evaluatorResponse.go +++ b/platform/evaluatorResponse.go @@ -1,6 +1,10 @@ package platform -import "github.com/robbyt/go-polyscript/platform/data" +import ( + "time" + + "github.com/robbyt/go-polyscript/platform/data" +) // EvaluatorResponse is based on the risor object.Object interface, but with some features removed type EvaluatorResponse interface { @@ -13,9 +17,14 @@ type EvaluatorResponse interface { // Interface converts the given object to a native Go value. Interface() any - // GetScriptExeID returns the ID of the script that generated the object. - GetScriptExeID() string + // ScriptExeID returns the ID of the script that generated the object. + ScriptExeID() string + + // ExecTime returns the time it took to execute the script. + ExecTime() time.Duration - // GetExecTime returns the time it took to execute the script - GetExecTime() string + // AsMap returns the result as a map[string]any. Returns an error of the + // form "AsMap: expected map[string]any, got " when the underlying value + // has a different shape. + AsMap() (map[string]any, error) } diff --git a/platform/evaluatorResponse_test.go b/platform/evaluatorResponse_test.go index 2385f70..1c55645 100644 --- a/platform/evaluatorResponse_test.go +++ b/platform/evaluatorResponse_test.go @@ -2,6 +2,7 @@ package platform_test import ( "testing" + "time" "github.com/robbyt/go-polyscript/platform/data" "github.com/stretchr/testify/assert" @@ -93,13 +94,13 @@ func TestEvaluatorResponseInterface(t *testing.T) { // Test script ID and execution time methods t.Run("Script metadata methods", func(t *testing.T) { - mockResponse.On("GetScriptExeID").Return("script-123").Once() - scriptID := mockResponse.GetScriptExeID() - assert.Equal(t, "script-123", scriptID, "GetScriptExeID() should return expected ID") + mockResponse.On("ScriptExeID").Return("script-123").Once() + scriptID := mockResponse.ScriptExeID() + assert.Equal(t, "script-123", scriptID, "ScriptExeID() should return expected ID") - mockResponse.On("GetExecTime").Return("42ms").Once() - execTime := mockResponse.GetExecTime() - assert.Equal(t, "42ms", execTime, "GetExecTime() should return expected time") + mockResponse.On("ExecTime").Return(42 * time.Millisecond).Once() + execTime := mockResponse.ExecTime() + assert.Equal(t, 42*time.Millisecond, execTime, "ExecTime() should return expected time") }) // Verify all expected assertions diff --git a/platform/evaluator_test.go b/platform/evaluator_test.go index 6975f7d..319cd77 100644 --- a/platform/evaluator_test.go +++ b/platform/evaluator_test.go @@ -8,6 +8,7 @@ import ( "net/http" "os" "testing" + "time" "github.com/robbyt/go-polyscript" "github.com/robbyt/go-polyscript/platform" @@ -58,8 +59,8 @@ func TestEvaluatorInterface(t *testing.T) { // Create a mock evaluator response mockResponse := new(mockEvaluatorResponse) mockResponse.On("Interface").Return("test result") - mockResponse.On("GetScriptExeID").Return("test-script-id") - mockResponse.On("GetExecTime").Return("10µs") + mockResponse.On("ScriptExeID").Return("test-script-id") + mockResponse.On("ExecTime").Return(10 * time.Microsecond) mockResponse.On("Type").Return(data.STRING) mockResponse.On("Inspect").Return("test result") @@ -89,10 +90,10 @@ func TestEvaluatorInterface(t *testing.T) { assert.Equal( t, "test-script-id", - response.GetScriptExeID(), - "GetScriptExeID() should return expected value", + response.ScriptExeID(), + "ScriptExeID() should return expected value", ) - assert.Equal(t, "10µs", response.GetExecTime(), "GetExecTime() should return expected value") + assert.Equal(t, 10*time.Microsecond, response.ExecTime(), "ExecTime() should return expected value") assert.Equal(t, data.STRING, response.Type(), "Type() should return expected value") assert.Equal(t, "test result", response.Inspect(), "Inspect() should return expected value") @@ -206,8 +207,8 @@ func TestEvaluatorWithPrepInterface(t *testing.T) { // Create a mock evaluator response mockResponse := new(mockEvaluatorResponse) mockResponse.On("Interface").Return("combined result") - mockResponse.On("GetScriptExeID").Return("test-script-id") - mockResponse.On("GetExecTime").Return("10µs") + mockResponse.On("ScriptExeID").Return("test-script-id") + mockResponse.On("ExecTime").Return(10 * time.Microsecond) mockResponse.On("Type").Return(data.STRING) mockResponse.On("Inspect").Return("combined result") diff --git a/platform/mocks_test.go b/platform/mocks_test.go index 8ac6e93..6b90925 100644 --- a/platform/mocks_test.go +++ b/platform/mocks_test.go @@ -2,6 +2,8 @@ package platform_test import ( "context" + "fmt" + "time" "github.com/robbyt/go-polyscript/platform" "github.com/robbyt/go-polyscript/platform/data" @@ -70,12 +72,29 @@ func (m *mockEvaluatorResponse) Interface() any { return args.Get(0) } -func (m *mockEvaluatorResponse) GetScriptExeID() string { +func (m *mockEvaluatorResponse) ScriptExeID() string { args := m.Called() return args.String(0) } -func (m *mockEvaluatorResponse) GetExecTime() string { +func (m *mockEvaluatorResponse) ExecTime() time.Duration { args := m.Called() - return args.String(0) + return args.Get(0).(time.Duration) +} + +func (m *mockEvaluatorResponse) AsMap() (map[string]any, error) { + args := m.Called() + err := args.Error(1) + got := args.Get(0) + if got == nil { + return nil, err + } + m0, ok := got.(map[string]any) + if !ok { + if err != nil { + return nil, err + } + return nil, fmt.Errorf("AsMap: expected map[string]any, got %T", got) + } + return m0, err } diff --git a/polyscript_extism_test.go b/polyscript_extism_test.go index c12e7ce..e2e65cf 100644 --- a/polyscript_extism_test.go +++ b/polyscript_extism_test.go @@ -130,8 +130,8 @@ func TestExtismDataIntegrationScenarios(t *testing.T) { require.NoError(t, err) require.NotNil(t, result) - resultMap, ok := result.Interface().(map[string]any) - require.True(t, ok, "Result should be a map") + resultMap, err := result.AsMap() + require.NoError(t, err, "Result should be a map") require.Contains(t, resultMap, "greeting") assert.Equal(t, "Hello, Test User!", resultMap["greeting"]) @@ -171,8 +171,8 @@ func TestExtismEvalEndToEnd(t *testing.T) { require.NoError(t, err) require.NotNil(t, result) - resultMap, ok := result.Interface().(map[string]any) - require.True(t, ok, "Result should be a map") + resultMap, err := result.AsMap() + require.NoError(t, err, "Result should be a map") require.Contains(t, resultMap, "greeting") assert.Equal(t, "Hello, Test User!", resultMap["greeting"]) } diff --git a/polyscript_mocks_test.go b/polyscript_mocks_test.go index 0fe6840..05586be 100644 --- a/polyscript_mocks_test.go +++ b/polyscript_mocks_test.go @@ -2,6 +2,8 @@ package polyscript_test import ( "context" + "fmt" + "time" "github.com/robbyt/go-polyscript/platform" "github.com/robbyt/go-polyscript/platform/data" @@ -70,12 +72,29 @@ func (m *mockEvaluatorResponse) Interface() any { return args.Get(0) } -func (m *mockEvaluatorResponse) GetScriptExeID() string { +func (m *mockEvaluatorResponse) ScriptExeID() string { args := m.Called() return args.String(0) } -func (m *mockEvaluatorResponse) GetExecTime() string { +func (m *mockEvaluatorResponse) ExecTime() time.Duration { args := m.Called() - return args.String(0) + return args.Get(0).(time.Duration) +} + +func (m *mockEvaluatorResponse) AsMap() (map[string]any, error) { + args := m.Called() + err := args.Error(1) + got := args.Get(0) + if got == nil { + return nil, err + } + m0, ok := got.(map[string]any) + if !ok { + if err != nil { + return nil, err + } + return nil, fmt.Errorf("AsMap: expected map[string]any, got %T", got) + } + return m0, err } diff --git a/polyscript_options_test.go b/polyscript_options_test.go index e109bb5..32f4d92 100644 --- a/polyscript_options_test.go +++ b/polyscript_options_test.go @@ -31,8 +31,8 @@ func TestNewRisor(t *testing.T) { result, err := eval.Eval(t.Context()) require.NoError(t, err) - got, ok := result.Interface().(map[string]any) - require.True(t, ok) + got, err := result.AsMap() + require.NoError(t, err) assert.Equal(t, "World", got["name"]) }) @@ -45,8 +45,8 @@ func TestNewRisor(t *testing.T) { require.NoError(t, err) result, err := eval.Eval(ctx) require.NoError(t, err) - got, ok := result.Interface().(map[string]any) - require.True(t, ok) + got, err := result.AsMap() + require.NoError(t, err) assert.Equal(t, "Robert", got["name"]) }) @@ -92,8 +92,8 @@ _ = result` result, err := eval.Eval(t.Context()) require.NoError(t, err) - got, ok := result.Interface().(map[string]any) - require.True(t, ok) + got, err := result.AsMap() + require.NoError(t, err) assert.Equal(t, "World", got["name"]) }) } @@ -111,8 +111,8 @@ func TestNewExtism(t *testing.T) { result, err := eval.Eval(t.Context()) require.NoError(t, err) - got, ok := result.Interface().(map[string]any) - require.True(t, ok) + got, err := result.AsMap() + require.NoError(t, err) assert.Equal(t, "Hello, World!", got["greeting"]) }) @@ -144,8 +144,8 @@ func TestNewExtism(t *testing.T) { result, err := eval.Eval(t.Context()) require.NoError(t, err) - got, ok := result.Interface().(map[string]any) - require.True(t, ok) + got, err := result.AsMap() + require.NoError(t, err) assert.Equal(t, "Hello, Test!", got["greeting"]) }) @@ -166,8 +166,8 @@ func TestNewExtism(t *testing.T) { result, err := eval.Eval(t.Context()) require.NoError(t, err, "cap=%d", n) - got, ok := result.Interface().(map[string]any) - require.True(t, ok, "cap=%d", n) + got, err := result.AsMap() + require.NoError(t, err, "cap=%d", n) assert.Equal(t, "Hello, Cap!", got["greeting"], "cap=%d", n) } }) @@ -237,8 +237,8 @@ func TestOptionsCompose(t *testing.T) { result, err := eval.Eval(t.Context()) require.NoError(t, err) - got, ok := result.Interface().(map[string]any) - require.True(t, ok) + got, err := result.AsMap() + require.NoError(t, err) assert.Equal(t, "second", got["k"]) }) } @@ -269,8 +269,8 @@ func TestFromBytesIsolatesCallerSlice(t *testing.T) { result, err := eval.Eval(t.Context()) require.NoError(t, err) - got, ok := result.Interface().(map[string]any) - require.True(t, ok) + got, err := result.AsMap() + require.NoError(t, err) assert.Equal(t, "Hello, World!", got["greeting"]) } diff --git a/polyscript_test.go b/polyscript_test.go index 13c3aef..9458c27 100644 --- a/polyscript_test.go +++ b/polyscript_test.go @@ -251,8 +251,8 @@ func TestEvalHelpers(t *testing.T) { require.NoError(t, err) require.NotNil(t, result) - resultMap, ok := result.Interface().(map[string]any) - require.True(t, ok) + resultMap, err := result.AsMap() + require.NoError(t, err) assert.Equal(t, "Hello, World!", resultMap["message"]) length := resultMap["length"] @@ -425,8 +425,8 @@ _ = result` result, err := eval.Eval(enrichedCtx) require.NoError(t, err) - resultMap, ok := result.Interface().(map[string]any) - require.True(t, ok) + resultMap, err := result.AsMap() + require.NoError(t, err) assert.Equal(t, "Hello, Risor User (v1.0.0)", resultMap["message"]) timeout := resultMap["timeout"] @@ -456,8 +456,8 @@ _ = result` result, err := eval.Eval(enrichedCtx) require.NoError(t, err) - resultMap, ok := result.Interface().(map[string]any) - require.True(t, ok) + resultMap, err := result.AsMap() + require.NoError(t, err) assert.Equal(t, "Hello, Starlark User (v1.0.0)", resultMap["message"]) starlarkTimeout := resultMap["timeout"] diff --git a/readme_test.go b/readme_test.go index 05f0a4b..8b4c5d7 100644 --- a/readme_test.go +++ b/readme_test.go @@ -40,8 +40,8 @@ func TestReadmeQuickStart(t *testing.T) { require.NoError(t, err, "Should evaluate successfully") require.NotNil(t, result, "Result should not be nil") - resultMap, ok := result.Interface().(map[string]any) - require.True(t, ok, "Result should be a map") + resultMap, err := result.AsMap() + require.NoError(t, err, "Result should be a map") assert.Equal(t, "Hello, World.", resultMap["greeting"], "Greeting should match") assert.Equal(t, int64(13), resultMap["length"], "Length should be 13") } @@ -74,8 +74,8 @@ func TestReadmeStaticProvider(t *testing.T) { result, err := evaluator.Eval(t.Context()) require.NoError(t, err, "Should evaluate successfully") - resultMap, ok := result.Interface().(map[string]any) - require.True(t, ok, "Result should be a map") + resultMap, err := result.AsMap() + require.NoError(t, err, "Result should be a map") assert.Equal(t, "Hello, cats!", resultMap["greeting"], "Greeting should match with excitement") } @@ -102,8 +102,8 @@ func TestReadmeContextProvider(t *testing.T) { result, err := evaluator.Eval(enrichedCtx) require.NoError(t, err, "Should evaluate successfully") - resultMap, ok := result.Interface().(map[string]any) - require.True(t, ok, "Result should be a map") + resultMap, err := result.AsMap() + require.NoError(t, err, "Result should be a map") assert.Equal(t, "Billie Jean", resultMap["name"], "Name should match") assert.Equal(t, true, resultMap["is_not_my_lover"], "Relationship status should be correct") } @@ -146,8 +146,8 @@ func TestReadmeCombiningStaticAndDynamic(t *testing.T) { result, err := evaluator.Eval(enrichedCtx) require.NoError(t, err, "Should evaluate with combined data") - resultMap, ok := result.Interface().(map[string]any) - require.True(t, ok, "Result should be a map") + resultMap, err := result.AsMap() + require.NoError(t, err, "Result should be a map") assert.Equal(t, "Hello, Robert!", resultMap["greeting"], "Should use runtime name over static") } @@ -175,8 +175,8 @@ _ = result result, err := evaluator.Eval(t.Context()) require.NoError(t, err, "Should evaluate Starlark script") - resultMap, ok := result.Interface().(map[string]any) - require.True(t, ok, "Result should be a map") + resultMap, err := result.AsMap() + require.NoError(t, err, "Result should be a map") assert.Equal(t, "Hello, World!", resultMap["greeting"], "Greeting should match") assert.Equal(t, int64(13), resultMap["length"], "Length should be 13") } @@ -195,8 +195,8 @@ func TestReadmeExtism(t *testing.T) { require.NoError(t, err, "Should evaluate WASM module") require.NotNil(t, result, "Result should not be nil") - resultMap, ok := result.Interface().(map[string]any) - require.True(t, ok, "Result should be a map") + resultMap, err := result.AsMap() + require.NoError(t, err, "Result should be a map") assert.Contains(t, resultMap, "greeting", "Result should contain greeting field") assert.Equal(t, "Hello, World!", resultMap["greeting"], "Greeting should match") }