-
Notifications
You must be signed in to change notification settings - Fork 19
/
flow.go
159 lines (125 loc) · 4.46 KB
/
flow.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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
package definition
import (
"encoding/json"
"fmt"
"github.com/nyaruka/goflow/flows"
"github.com/nyaruka/goflow/utils"
)
type flow struct {
uuid flows.FlowUUID
name string
language utils.Language
expireAfterMinutes int
translations flows.FlowTranslations
nodes []flows.Node
nodeMap map[flows.NodeUUID]flows.Node
}
func (f *flow) UUID() flows.FlowUUID { return f.uuid }
func (f *flow) Name() string { return f.name }
func (f *flow) Language() utils.Language { return f.language }
func (f *flow) ExpireAfterMinutes() int { return f.expireAfterMinutes }
func (f *flow) Nodes() []flows.Node { return f.nodes }
func (f *flow) Translations() flows.FlowTranslations { return f.translations }
func (f *flow) GetNode(uuid flows.NodeUUID) flows.Node { return f.nodeMap[uuid] }
// Validates that structurally we are sane. IE, all required fields are present and
// all exits with destinations point to valid endpoints.
func (f *flow) Validate(assets flows.SessionAssets) error {
var err error
for _, node := range f.nodes {
// validate all the node's actions
for _, action := range node.Actions() {
if err := action.Validate(assets); err != nil {
return fmt.Errorf("validation failed for action[uuid=%s, type=%s]: %v", action.UUID(), action.Type(), err)
}
}
}
return err
}
func (f *flow) Resolve(key string) interface{} {
switch key {
case "name":
return f.Name()
case "uuid":
return f.UUID()
}
return fmt.Errorf("No field '%s' on flow", key)
}
func (f *flow) Default() interface{} {
return f
}
// String returns the default string value for this flow, which is just our name
func (f *flow) String() string {
return f.name
}
func (f *flow) Reference() *flows.FlowReference {
return flows.NewFlowReference(f.uuid, f.name)
}
var _ utils.VariableResolver = (*flow)(nil)
//------------------------------------------------------------------------------------------
// JSON Encoding / Decoding
//------------------------------------------------------------------------------------------
type flowEnvelope struct {
UUID flows.FlowUUID `json:"uuid" validate:"required,uuid4"`
Name string `json:"name" validate:"required"`
Language utils.Language `json:"language"`
ExpireAfterMinutes int `json:"expire_after_minutes"`
Localization flowTranslations `json:"localization"`
Nodes []*node `json:"nodes"`
// only for writing out, optional
Metadata map[string]interface{} `json:"_ui,omitempty"`
}
// ReadFlow reads a single flow definition from the passed in byte array
func ReadFlow(data json.RawMessage) (flows.Flow, error) {
envelope := flowEnvelope{}
if err := utils.UnmarshalAndValidate(data, &envelope, "flow"); err != nil {
return nil, err
}
f := &flow{}
f.uuid = envelope.UUID
f.name = envelope.Name
f.language = envelope.Language
f.expireAfterMinutes = envelope.ExpireAfterMinutes
f.translations = &envelope.Localization
f.nodes = make([]flows.Node, len(envelope.Nodes))
f.nodeMap = make(map[flows.NodeUUID]flows.Node)
// for each node...
for n, node := range envelope.Nodes {
f.nodes[n] = node
// make sure we haven't seen this node before
if f.nodeMap[node.UUID()] != nil {
return nil, fmt.Errorf("duplicate node uuid: %s", node.UUID())
}
f.nodeMap[node.UUID()] = node
}
// go back through nodes and perform basic structural validation
for _, node := range f.nodes {
// check every exit has a valid destination
for _, exit := range node.Exits() {
if exit.DestinationNodeUUID() != "" && f.nodeMap[exit.DestinationNodeUUID()] == nil {
return nil, fmt.Errorf("destination %s of exit[uuid=%s] isn't a known node", exit.DestinationNodeUUID(), exit.UUID())
}
}
// and the router if there is one
if node.Router() != nil {
if err := node.Router().Validate(node.Exits()); err != nil {
return nil, fmt.Errorf("router is invalid on node[uuid=%s]: %v", node.UUID(), err)
}
}
}
return f, nil
}
func (f *flow) MarshalJSON() ([]byte, error) {
var fe = flowEnvelope{}
fe.UUID = f.uuid
fe.Name = f.name
fe.Language = f.language
fe.ExpireAfterMinutes = f.expireAfterMinutes
if f.translations != nil {
fe.Localization = *f.translations.(*flowTranslations)
}
fe.Nodes = make([]*node, len(f.nodes))
for i := range f.nodes {
fe.Nodes[i] = f.nodes[i].(*node)
}
return json.Marshal(&fe)
}