Skip to content

change PrepareContext to AddDataToContext, and change the data signature #27

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Apr 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 11 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ evaluator, _ := polyscript.FromRisorString(script, logHandler)

ctx := context.Background()
runtimeData := map[string]any{"name": "Billie Jean", "relationship": false}
enrichedCtx, _ := evaluator.PrepareContext(ctx, runtimeData)
enrichedCtx, _ := evaluator.AddDataToContext(ctx, runtimeData)

// Execute with the "enriched" context containing the link to the input data
result, _ := evaluator.Eval(enrichedCtx)
Expand All @@ -127,14 +127,14 @@ evaluator, _ := polyscript.FromRisorStringWithData(script, staticData, logHandle

// For each request, prepare dynamic data
requestData := map[string]any{"userId": 123}
enrichedCtx, _ := evaluator.PrepareContext(context.Background(), requestData)
enrichedCtx, _ := evaluator.AddDataToContext(context.Background(), requestData)

// Execute with both static and dynamic data available
result, _ := evaluator.Eval(enrichedCtx)

// In scripts, data can be accessed from both locations:
// appName := ctx["appName"] // Static data: "MyApp"
// userId := ctx["input_data"]["userId"] // Dynamic data: 123
// In scripts, all data is accessed from the context:
// appName := ctx["appName"] // From static data: "MyApp"
// userId := ctx["userId"] // From dynamic data: 123
```

## Architecture
Expand All @@ -151,11 +151,13 @@ go-polyscript is structured around a few key concepts:

### Note on Data Access Patterns

go-polyscript uses a unified `Provider` interface to supply data to scripts. The library has standardized on storing dynamic runtime data under the `input_data` key (previously `script_data`). For maximum compatibility, scripts should handle two data access patterns:
go-polyscript uses a two-layer approach for handling data:

1. Top-level access for static data: `ctx["config_value"]`
2. Nested access for dynamic data: `ctx["input_data"]["user_data"]`
3. HTTP request data access: `ctx["input_data"]["request"]["method"]` (request objects are always stored under input_data)
1. **Data Provider Layer**: The `Provider` interface (via `AddDataToContext`) handles storage mechanisms and general type conversions. This layer is pluggable, allowing data to be stored in various backends while maintaining a consistent API.

2. **Engine-Specific Layer**: Each engine's `Evaluator` implementation handles the engine-specific conversions between the stored data and the format required by that particular scripting engine.

This separation allows scripts to access data with consistent patterns regardless of the storage mechanism or script engine. For example, data you store with `{"config": value}` will be accessible in your scripts as `ctx["config"]`, with each engine handling the specific conversions needed for its runtime.

See the [Data Providers](#working-with-data-providers) section for more details.

Expand Down
8 changes: 4 additions & 4 deletions engines/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,12 @@ This package contains virtual machine implementations for executing scripts in v

4. **Data Preparation Stage**
- This phase is optional, and must happen prior to evaluation when runtime input data is used
- The `Evaluator` implements the `evaluationEvaluator` interface, which has a `PrepareContext` method
- The `PrepareContext` method takes a `context.Context` and a variadic list of `any`
- `PrepareContext` calls the `data.Provider` to convert and store the data, somewhere accessible to the Evaluator
- The `Evaluator` implements the `data.Setter` interface, which has an `AddDataToContext` method
- The `AddDataToContext` method takes a `context.Context` and a variadic list of `map[string]any`
- `AddDataToContext` calls the `data.Provider` to store the data, somewhere accessible to the Evaluator
- The conversion is fairly opinionated, and handled by the `data.Provider`
- For example, it converts an `http.Request` into a `map[string]any` using the schema in `helper.RequestToMap`
- The `PrepareContext` method returns a new context with the data stored or linked in it
- The `AddDataToContext` method returns a new context with the data stored or linked in it

5. **Execution Stage**
- When `Eval(ctx)` is called, the `data.Provider` first loads the input data into the VM
Expand Down
125 changes: 0 additions & 125 deletions engines/TESTING.md

This file was deleted.

2 changes: 1 addition & 1 deletion engines/benchmarks/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ For distributed architectures, separate data preparation from evaluation to impr

```go
// PREPARE DATA (can happen on system A)
enrichedCtx, _ := evaluator.PrepareContext(ctx, inputData)
enrichedCtx, _ := evaluator.AddDataToContext(ctx, inputData)

// EVALUATE (can happen on system B)
result, _ := evaluator.Eval(enrichedCtx)
Expand Down
8 changes: 4 additions & 4 deletions engines/benchmarks/benchmark_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,9 +166,9 @@ func BenchmarkDataProviders(b *testing.B) {

b.Run("CompositeProvider", func(b *testing.B) {
// For CompositeProvider use case, we can prepare the context separately
// We need a special script that accesses the name via input_data
// We access the name directly from the context
compositeScript := `
name := ctx["input_data"]["name"]
name := ctx["name"]
message := "Hello, " + name + "!"
{
"greeting": message,
Expand All @@ -186,8 +186,8 @@ func BenchmarkDataProviders(b *testing.B) {
}

ctx := context.Background()
// Use PrepareContext to add the dynamic part
ctx, err = evaluator.PrepareContext(ctx, inputData)
// Use AddDataToContext to add the dynamic part
ctx, err = evaluator.AddDataToContext(ctx, map[string]any{"name": "World"})
if err != nil {
b.Fatalf("Failed to prepare context: %v", err)
}
Expand Down
10 changes: 5 additions & 5 deletions engines/extism/evaluator/evaluator.go
Original file line number Diff line number Diff line change
Expand Up @@ -208,21 +208,21 @@ func (be *Evaluator) Eval(ctx context.Context) (platform.EvaluatorResponse, erro
return result, nil
}

// PrepareContext implements the EvalDataPreparer interface for Extism WebAssembly modules.
// AddDataToContext implements the data.Setter interface for Extism WebAssembly modules.
// It enriches the provided context with data for script evaluation, using the
// ExecutableUnit's DataProvider to store the data.
func (be *Evaluator) PrepareContext(
func (be *Evaluator) AddDataToContext(
ctx context.Context,
d ...any,
d ...map[string]any,
) (context.Context, error) {
logger := be.logger.WithGroup("PrepareContext")
logger := be.logger.WithGroup("AddDataToContext")

// Use the shared helper function for context preparation
if be.execUnit == nil || be.execUnit.GetDataProvider() == nil {
return ctx, fmt.Errorf("no data provider available")
}

return data.PrepareContextHelper(
return data.AddDataToContextHelper(
ctx,
logger,
be.execUnit.GetDataProvider(),
Expand Down
20 changes: 10 additions & 10 deletions engines/extism/evaluator/evaluator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ func (m *mockErrProvider) GetData(ctx context.Context) (map[string]any, error) {

func (m *mockErrProvider) AddDataToContext(
ctx context.Context,
data ...any,
data ...map[string]any,
) (context.Context, error) {
return ctx, m.err
}
Expand Down Expand Up @@ -607,14 +607,14 @@ func TestEvaluator_Evaluate(t *testing.T) {
})
}

// TestEvaluator_PrepareContext tests the PrepareContext method with various scenarios
func TestEvaluator_PrepareContext(t *testing.T) {
// TestEvaluator_AddDataToContext tests the AddDataToContext method with various scenarios
func TestEvaluator_AddDataToContext(t *testing.T) {
t.Parallel()

tests := []struct {
name string
setupExe func(t *testing.T) *script.ExecutableUnit
inputs []any
inputs []map[string]any
wantError bool
expectedErr string
}{
Expand All @@ -633,7 +633,7 @@ func TestEvaluator_PrepareContext(t *testing.T) {
Content: content,
}
},
inputs: []any{map[string]any{"test": "data"}},
inputs: []map[string]any{{"test": "data"}},
wantError: true,
expectedErr: "no data provider available",
},
Expand All @@ -652,7 +652,7 @@ func TestEvaluator_PrepareContext(t *testing.T) {
Content: content,
}
},
inputs: []any{map[string]any{"test": "data"}},
inputs: []map[string]any{{"test": "data"}},
wantError: false,
},
{
Expand All @@ -670,7 +670,7 @@ func TestEvaluator_PrepareContext(t *testing.T) {
Content: content,
}
},
inputs: []any{},
inputs: []map[string]any{{}},
wantError: false,
},
{
Expand All @@ -679,7 +679,7 @@ func TestEvaluator_PrepareContext(t *testing.T) {
t.Helper()
return nil
},
inputs: []any{map[string]any{"test": "data"}},
inputs: []map[string]any{{"test": "data"}},
wantError: true,
expectedErr: "no data provider available",
},
Expand All @@ -701,7 +701,7 @@ func TestEvaluator_PrepareContext(t *testing.T) {
Content: content,
}
},
inputs: []any{map[string]any{"test": "data"}},
inputs: []map[string]any{{"test": "data"}},
wantError: true,
expectedErr: "provider error",
},
Expand All @@ -714,7 +714,7 @@ func TestEvaluator_PrepareContext(t *testing.T) {
evaluator := New(handler, exe)

ctx := context.Background()
enrichedCtx, err := evaluator.PrepareContext(ctx, tt.inputs...)
enrichedCtx, err := evaluator.AddDataToContext(ctx, tt.inputs...)

// Check error expectations
if tt.wantError {
Expand Down
2 changes: 1 addition & 1 deletion engines/extism/new.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ func FromExtismLoader(
}

// FromExtismLoaderWithData creates an Extism evaluator with both static and dynamic data capabilities.
// To add runtime data, use the `PrepareContext` method on the evaluator to add data to the context.
// To add runtime data, use the `AddDataToContext` method on the evaluator to add data to the context.
//
// Input parameters:
// - l: loader implementation for loading the WASM content
Expand Down
7 changes: 5 additions & 2 deletions engines/mocks/evaluator.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,11 @@ func (m *Evaluator) Load(newVersion script.ExecutableUnit) error {
return args.Error(0)
}

// PrepareContext is a mock implementation of the PrepareContext method.
func (m *Evaluator) PrepareContext(ctx context.Context, d ...any) (context.Context, error) {
// AddDataToContext is a mock implementation of the AddDataToContext method.
func (m *Evaluator) AddDataToContext(
ctx context.Context,
d ...map[string]any,
) (context.Context, error) {
args := m.Called(ctx, d)
return args.Get(0).(context.Context), args.Error(1)
}
10 changes: 5 additions & 5 deletions engines/risor/evaluator/evaluator.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,21 +155,21 @@ func (be *Evaluator) Eval(ctx context.Context) (platform.EvaluatorResponse, erro
return result, nil
}

// PrepareContext implements the EvalDataPreparer interface for Risor scripts.
// AddDataToContext implements the data.Setter interface for Risor scripts.
// It enriches the provided context with data for script evaluation, using the
// ExecutableUnit's DataProvider to store the data.
func (be *Evaluator) PrepareContext(
func (be *Evaluator) AddDataToContext(
ctx context.Context,
d ...any,
d ...map[string]any,
) (context.Context, error) {
logger := be.logger.WithGroup("PrepareContext")
logger := be.logger.WithGroup("AddDataToContext")

// Use the shared helper function for context preparation
if be.execUnit == nil || be.execUnit.GetDataProvider() == nil {
return ctx, fmt.Errorf("no data provider available")
}

return data.PrepareContextHelper(
return data.AddDataToContextHelper(
ctx,
logger,
be.execUnit.GetDataProvider(),
Expand Down
Loading