Skip to content

Commit 04460e4

Browse files
committed
Use Tooling API To Execute Anonymous Apex
When getting context variables use the Tooling API to execute anonymous apex. Because /tooling/executeAnonymous doesn't return the debug log like the SOAP version, we use another approach to get the context variables. We throw an exception at the end of execution, setting the exception message to the serialized context.
1 parent 4058ef4 commit 04460e4

3 files changed

Lines changed: 37 additions & 28 deletions

File tree

apex.go

Lines changed: 34 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
package batch
22

33
import (
4-
"bytes"
54
"encoding/json"
65
"fmt"
76
force "github.com/ForceCLI/force/lib"
8-
"github.com/benhoyt/goawk/interp"
97
anon "github.com/octoberswimmer/batchforce/apex"
108
"strings"
119
)
@@ -20,36 +18,50 @@ func (e *Execution) getApexContext() (map[string]any, error) {
2018
for _, v := range apexVars {
2119
lines = append(lines, fmt.Sprintf(`b_f_c_t_x.put('%s', %s);`, v, v))
2220
}
23-
lines = append(lines, `System.debug(JSON.serialize(b_f_c_t_x));`)
21+
// Throw an exception with the JSON data to retrieve it via Tooling API
22+
lines = append(lines, `throw new System.HandledException('BATCHFORCE_CONTEXT:' + JSON.serialize(b_f_c_t_x));`)
2423
apex = apex + strings.Join(lines, "\n")
2524

26-
// retrieve underlying *force.Force for Partner API calls
25+
// retrieve underlying *force.Force
2726
fs, ok := e.session().(*force.Force)
2827
if !ok {
2928
return nil, fmt.Errorf("session is not a *force.Force")
3029
}
31-
debugLog, err := fs.Partner.ExecuteAnonymous(apex)
30+
31+
// Use Tooling API for ExecuteAnonymous (works in WASM)
32+
result, err := fs.ExecuteAnonymousTooling(apex)
3233
if err != nil {
33-
return nil, err
34+
return nil, fmt.Errorf("failed to execute anonymous apex: %v", err)
3435
}
35-
val, err := varFromDebugLog(debugLog)
36-
if err != nil {
37-
return nil, err
36+
37+
// Check if we got our expected exception with the context data
38+
if !result.Success && result.ExceptionMessage != "" {
39+
// Look for our marker in the exception message (may have exception type prefix)
40+
marker := "BATCHFORCE_CONTEXT:"
41+
idx := strings.Index(result.ExceptionMessage, marker)
42+
if idx != -1 {
43+
// Extract the JSON from the exception message
44+
jsonStr := result.ExceptionMessage[idx+len(marker):]
45+
var n map[string]any
46+
err = json.Unmarshal([]byte(jsonStr), &n)
47+
if err != nil {
48+
return nil, fmt.Errorf("failed to parse context JSON: %v", err)
49+
}
50+
return n, nil
51+
}
52+
// This is a real exception, not our context data
53+
return nil, fmt.Errorf("execution error: %s", result.ExceptionMessage)
3854
}
39-
var n map[string]any
40-
err = json.Unmarshal(val, &n)
41-
if err != nil {
42-
return nil, err
55+
56+
// If no exception was thrown (no variables to extract), return empty map
57+
if result.Success {
58+
return map[string]any{}, nil
4359
}
44-
return n, nil
45-
}
4660

47-
func varFromDebugLog(log string) ([]byte, error) {
48-
input := strings.NewReader(log)
49-
output := new(bytes.Buffer)
50-
err := interp.Exec(`$2~/USER_DEBUG/ { var = $5 } END { print var }`, "|", input, output)
51-
if err != nil {
52-
return nil, err
61+
// Handle compilation errors
62+
if result.CompileProblem != "" {
63+
return nil, fmt.Errorf("compilation error: %s", result.CompileProblem)
5364
}
54-
return output.Bytes(), nil
65+
66+
return nil, fmt.Errorf("execution failed")
5567
}

go.mod

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,8 @@ go 1.24
55
toolchain go1.24.4
66

77
require (
8-
github.com/ForceCLI/force v1.1.0
8+
github.com/ForceCLI/force v1.1.1
99
github.com/antlr4-go/antlr/v4 v4.13.1
10-
github.com/benhoyt/goawk v1.21.0
1110
github.com/charmbracelet/glamour v0.10.0
1211
github.com/clbanning/mxj v1.8.4
1312
github.com/expr-lang/expr v1.17.5

go.sum

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
github.com/ForceCLI/config v0.0.0-20230217143549-9149d42a3c99 h1:H2axnitaP3Dw+tocMHPQHjM2wJ/+grF8sOIQGaJeEsg=
22
github.com/ForceCLI/config v0.0.0-20230217143549-9149d42a3c99/go.mod h1:WHFXv3VIHldTnYGmWAXAxsu4O754A9Zakq4DedI8PSA=
3-
github.com/ForceCLI/force v1.1.0 h1:e+KhyNeZF3r98YrvE32Bv7bQOCeg7lj1uiihcs3axVE=
4-
github.com/ForceCLI/force v1.1.0/go.mod h1:S+csSNhBOHrRsRV7PMKhmPP7N4SBLYJMrQzEc/OHLTs=
3+
github.com/ForceCLI/force v1.1.1 h1:Ias+7kCOPFOBNSOTYAaQn4R9RzWPPNKsQOeJ1BY2H1Y=
4+
github.com/ForceCLI/force v1.1.1/go.mod h1:S+csSNhBOHrRsRV7PMKhmPP7N4SBLYJMrQzEc/OHLTs=
55
github.com/ForceCLI/inflect v0.0.0-20130829110746-cc00b5ad7a6a h1:mMd54YgLoeupNpbph3KdwvF58O0lZ72RQaJ2cFPOFDE=
66
github.com/ForceCLI/inflect v0.0.0-20130829110746-cc00b5ad7a6a/go.mod h1:DGKmCfb9oo5BivGO+szHk2ZvlqPDTlW4AYVpRBIVbms=
77
github.com/ViViDboarder/gotifier v0.0.0-20140619195515-0f19f3d7c54c h1:qLWjxZGLdzxp0Gc4Sf6f4w15D+wNKZ28HhkV9y5cAhw=
@@ -20,8 +20,6 @@ github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWp
2020
github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA=
2121
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
2222
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
23-
github.com/benhoyt/goawk v1.21.0 h1:GASuhJXHMFZ/2TJBPh+2Ah3kclVGNvGjt+uh3ajMdLk=
24-
github.com/benhoyt/goawk v1.21.0/go.mod h1:UG1Ld6CjkkHhoyQmErQGSTwmavsTqFnCDYsLSJbovqU=
2523
github.com/boljen/go-bitmap v0.0.0-20151001105940-23cd2fb0ce7d h1:zsO4lp+bjv5XvPTF58Vq+qgmZEYZttJK+CWtSZhKenI=
2624
github.com/boljen/go-bitmap v0.0.0-20151001105940-23cd2fb0ce7d/go.mod h1:f1iKL6ZhUWvbk7PdWVmOaak10o86cqMUYEmn1CZNGEI=
2725
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs=

0 commit comments

Comments
 (0)