-
Notifications
You must be signed in to change notification settings - Fork 41
/
javascript.go
129 lines (111 loc) · 3.25 KB
/
javascript.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
package builtin
import (
"time"
"github.com/fission/fission-workflows/pkg/types"
"github.com/fission/fission-workflows/pkg/types/typedvalues"
"github.com/robertkrimen/otto"
"github.com/sirupsen/logrus"
)
const (
Javascript = "javascript"
JavascriptInputExpr = "expr"
JavascriptInputArgs = "args"
execTimeout = time.Duration(100) * time.Millisecond
errTimeout = "javascript time out"
)
/*
FunctionJavascript allows you to create a task that evaluates an arbitrary JavaScript expression.
The implementation is similar to the inline evaluation of JavaScript in [expressions](./expressions.md) in inputs.
In that sense this implementations does not offer more functionality than inline expressions.
However, as it allows you to implement the entire task in JavaScript, this function is useful for prototyping and
stubbing particular functions.
**Specification**
**input** | required | types | description
----------------|----------|-------------------|--------------------------------------------------------
expr | yes | string | The JavaScript expression
args | no | * | The arguments that need to be present in the expression.
Note: the `expr` is of type `string` - not a `expression` - to prevent the workflow engine from evaluating the
expression prematurely.
**output** (*) The output of the expression.
**Example**
```yaml
# ...
JsExample:
run: javascript
inputs:
expr: "a ^ b"
args:
a: 42
b: 10
# ...
```
A complete example of this function can be found in the [fibonacci](../examples/misc/fibonacci.wf.yaml) example.
*/
type FunctionJavascript struct {
vm *otto.Otto
}
func NewFunctionJavascript() *FunctionJavascript {
return &FunctionJavascript{
vm: otto.New(),
}
}
func (fn *FunctionJavascript) Invoke(spec *types.TaskInvocationSpec) (*typedvalues.TypedValue, error) {
exprVal, err := ensureInput(spec.Inputs, JavascriptInputExpr, typedvalues.TypeString)
argsVal, _ := spec.Inputs[JavascriptInputArgs]
if err != nil {
return nil, err
}
expr, err := typedvalues.UnwrapString(exprVal)
if err != nil {
return nil, err
}
args, err := typedvalues.Unwrap(argsVal)
if err != nil {
return nil, err
}
logrus.WithField("taskID", spec.TaskId).
Infof("[internal://%s] args: %v | expr: %v", Javascript, args, expr)
result, err := fn.exec(expr, args)
if err != nil {
return nil, err
}
logrus.WithField("taskID", spec.TaskId).
Infof("[internal://%s] %v => %v", Javascript, expr, result)
return typedvalues.Wrap(result)
}
func (fn *FunctionJavascript) exec(expr string, args interface{}) (interface{}, error) {
defer func() {
if caught := recover(); caught != nil {
if errTimeout != caught {
panic(caught)
}
}
}()
scoped := fn.vm.Copy()
switch t := args.(type) {
case map[string]interface{}:
for key, arg := range t {
err := scoped.Set(key, arg)
if err != nil {
return nil, err
}
}
default:
err := scoped.Set("arg", t)
if err != nil {
return nil, err
}
}
go func() {
<-time.After(execTimeout)
scoped.Interrupt <- func() {
panic(errTimeout)
}
}()
jsResult, err := scoped.Run(expr)
if err != nil {
return nil, err
}
i, _ := jsResult.Export() // Err is always nil
return i, nil
}