|
1
|
|
|
var Objects = require('../Utility').Objects |
|
2
|
|
|
var TriggerType = require('./TriggerType').TriggerType |
|
3
|
|
|
|
|
4
|
|
|
var severityFactory = function (id, weight) { |
|
5
|
|
|
return { |
|
6
|
|
|
id: id, |
|
7
|
|
|
weight: weight |
|
8
|
|
|
} |
|
9
|
|
|
} |
|
10
|
|
|
|
|
11
|
|
|
/** |
|
12
|
|
|
* @enum |
|
13
|
|
|
* @readonly |
|
14
|
|
|
*/ |
|
15
|
|
|
var Severity = { |
|
16
|
|
|
None: severityFactory('None', 0), |
|
17
|
|
|
Minor: severityFactory('Minor', 1), |
|
18
|
|
|
Major: severityFactory('Major', 2), |
|
19
|
|
|
Fatal: severityFactory('Fatal', 3) |
|
20
|
|
|
} |
|
21
|
|
|
|
|
22
|
|
|
/** |
|
23
|
|
|
* @typedef {object} TViolation |
|
24
|
|
|
* |
|
25
|
|
|
* @property {string} message |
|
26
|
|
|
* @property {Severity} severity |
|
27
|
|
|
*/ |
|
28
|
|
|
|
|
29
|
|
|
/** |
|
30
|
|
|
* A simple wrapper for list of violations, grouped by path. |
|
31
|
|
|
* |
|
32
|
|
|
* @class |
|
33
|
|
|
* |
|
34
|
|
|
* @property {Object.<string, TViolation[]>} violations Violations grouped by |
|
35
|
|
|
* path |
|
36
|
|
|
* @property {Severity} severity |
|
37
|
|
|
*/ |
|
38
|
|
|
function ViolationSet () { |
|
39
|
|
|
var self = this |
|
40
|
|
|
var violations = {} |
|
41
|
|
|
var severity = Severity.None |
|
42
|
|
|
|
|
43
|
|
|
function updateSeverity (l) { |
|
44
|
|
|
severity = l.weight > severity.weight ? l : severity |
|
45
|
|
|
} |
|
46
|
|
|
|
|
47
|
|
|
/** |
|
48
|
|
|
* @param {string} path |
|
49
|
|
|
* @param {string} message |
|
50
|
|
|
* @param {Severity} severity |
|
51
|
|
|
* @return {ViolationSet} |
|
52
|
|
|
*/ |
|
53
|
|
|
this.push = function (path, message, severity) { |
|
54
|
|
|
violations[path] = violations[path] ? violations[path] : [] |
|
55
|
|
|
violations[path].push({message: message, severity: severity}) |
|
56
|
|
|
updateSeverity(severity) |
|
57
|
|
|
return self |
|
58
|
|
|
} |
|
59
|
|
|
|
|
60
|
|
|
this.merge = function (other) { |
|
61
|
|
|
Object.keys(other.violations).forEach(function (path) { |
|
62
|
|
|
other.violations[path].forEach(function (violation) { |
|
63
|
|
|
self.push(path, violation.message, violation.severity) |
|
64
|
|
|
}) |
|
65
|
|
|
}) |
|
66
|
|
|
return self |
|
67
|
|
|
} |
|
68
|
|
|
|
|
69
|
|
|
Object.defineProperties(this, { |
|
70
|
|
|
severity: {get: function () { return severity }}, |
|
71
|
|
|
violations: {get: function () { return violations }} |
|
72
|
|
|
}) |
|
73
|
|
|
} |
|
74
|
|
|
|
|
75
|
|
|
var Validator = { |
|
76
|
|
|
Severity: Severity, |
|
77
|
|
|
ViolationSet: ViolationSet, |
|
78
|
|
|
/** |
|
79
|
|
|
* @param {TScenarioInput} input |
|
80
|
|
|
* |
|
81
|
|
|
* @return ViolationSet |
|
82
|
|
|
*/ |
|
83
|
|
|
scenario: function (input) { |
|
84
|
|
|
var violations = new ViolationSet() |
|
85
|
|
|
var violation |
|
86
|
|
|
var handlers = ['deserializer', 'onError', 'onTermination'] |
|
87
|
|
|
handlers.forEach(function (handler) { |
|
88
|
|
|
var path = '$.' + handler |
|
89
|
|
|
if (!input[handler]) { |
|
90
|
|
|
var message = handler + ' handler is missing, default will be used' |
|
91
|
|
|
violations.push(path, message, Severity.Minor) |
|
92
|
|
|
} else { |
|
93
|
|
|
violations.merge(Validator.handler(input[handler], path)) |
|
94
|
|
|
} |
|
95
|
|
|
}) |
|
96
|
|
|
if (!TriggerType.find(input.trigger)) { |
|
97
|
|
|
violation = 'Scenario trigger type is not set or invalid' |
|
98
|
|
|
violations.push('$.trigger', violation, Severity.Fatal) |
|
99
|
|
|
} |
|
100
|
|
|
var states = input.states |
|
101
|
|
|
if (!Objects.isObject(states)) { |
|
102
|
|
|
violations.push('$.states', 'Expected to be an object', Severity.Fatal) |
|
103
|
|
|
states = {} |
|
104
|
|
|
} |
|
105
|
|
|
var terminalStates = 0 |
|
106
|
|
|
var entrypointStates = 0 |
|
107
|
|
|
Object.keys(states).forEach(function (id) { |
|
108
|
|
|
var state = states[id] |
|
109
|
|
|
violations.merge(Validator.state(state, '$.states.' + id)) |
|
110
|
|
|
entrypointStates += state && state.entrypoint ? 1 : 0 |
|
111
|
|
|
terminalStates += state && state.terminal ? 1 : 0 |
|
112
|
|
|
}) |
|
113
|
|
|
if (terminalStates === 0) { |
|
114
|
|
|
violations.push('$.states', 'No terminal state is defined', Severity.Fatal) |
|
115
|
|
|
} |
|
116
|
|
|
if (entrypointStates !== 1) { |
|
117
|
|
|
violation = entrypointStates + ' entrypoint states are defined ' + |
|
118
|
|
|
'(exactly one expected)' |
|
119
|
|
|
violations.push('$.states', violation, Severity.Fatal) |
|
120
|
|
|
} |
|
121
|
|
|
return violations |
|
122
|
|
|
}, |
|
123
|
|
|
/** |
|
124
|
|
|
* @param {TStateInput} state |
|
125
|
|
|
* @param {string} [path] |
|
126
|
|
|
* |
|
127
|
|
|
* @return {ViolationSet} |
|
128
|
|
|
*/ |
|
129
|
|
|
state: function (state, path) { |
|
130
|
|
|
path = path || '$' |
|
131
|
|
|
var violations = new ViolationSet() |
|
132
|
|
|
if (Objects.isFunction(state)) { |
|
133
|
|
|
return violations |
|
134
|
|
|
} |
|
135
|
|
|
if (!Objects.isObject(state)) { |
|
136
|
|
|
return violations.push(path, 'Is not an object', Severity.Fatal) |
|
137
|
|
|
} |
|
138
|
|
|
var handlers = {transition: Severity.Fatal, abort: Severity.Minor} |
|
139
|
|
|
Object.keys(handlers).forEach(function (key) { |
|
140
|
|
|
var prop = path + '.' + key |
|
141
|
|
|
if (!state[key]) { |
|
142
|
|
|
violations.push(prop, key + ' handler is missing', handlers[key]) |
|
143
|
|
|
} else { |
|
144
|
|
|
violations.merge(Validator.handler(state[key], prop)) |
|
145
|
|
|
} |
|
146
|
|
|
}) |
|
147
|
|
|
return violations |
|
148
|
|
|
}, |
|
149
|
|
|
handler: function (handler, path) { |
|
150
|
|
|
path = path || '$' |
|
151
|
|
|
var violations = new ViolationSet() |
|
152
|
|
|
if (!handler) { |
|
153
|
|
|
return violations |
|
154
|
|
|
} |
|
155
|
|
|
var callable = handler |
|
156
|
|
|
if (Objects.isObject(handler)) { |
|
157
|
|
|
violations.merge(Validator.handler(handler.onTimeout, path + '.onTimeout')) |
|
158
|
|
|
callable = handler.handler |
|
159
|
|
|
} |
|
160
|
|
|
if (!Objects.isFunction(callable)) { |
|
161
|
|
|
var message = 'Neither handler itself or handler.handler is a function' |
|
162
|
|
|
violations.push(path, message, Severity.Fatal) |
|
163
|
|
|
} |
|
164
|
|
|
return violations |
|
165
|
|
|
} |
|
166
|
|
|
} |
|
167
|
|
|
|
|
168
|
|
|
module.exports = { |
|
169
|
|
|
Validator: Validator |
|
170
|
|
|
} |
|
171
|
|
|
|