@onlineapps/cookbook-core 1.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -0
- package/package.json +1 -1
- package/schemas/cookbook.relaxed.schema.json +0 -0
- package/schemas/cookbook.schema.json +0 -0
- package/schemas/cookbook.strict.schema.json +0 -0
- package/schemas/cookbook.v2.schema.json +728 -0
- package/src/index.js +7 -3
- package/src/parser/cookbookParser.js +0 -0
- package/src/parser/cookbookValidator.js +1 -1
- package/src/parser/cookbookValidatorV2.js +339 -0
- package/src/parser.js +0 -0
- package/src/referenceValidator.js +0 -0
- package/src/schema.js +0 -0
- package/src/validator.js +9 -4
package/README.md
CHANGED
|
File without changes
|
package/package.json
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,728 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://onlineapps.com/schemas/cookbook/v2.1.0/cookbook.schema.json",
|
|
4
|
+
"title": "Cookbook Workflow Schema v2.1",
|
|
5
|
+
"description": "Schema for defining workflow orchestration with snake_case convention",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": ["version", "steps"],
|
|
8
|
+
"additionalProperties": false,
|
|
9
|
+
"properties": {
|
|
10
|
+
"version": {
|
|
11
|
+
"type": "string",
|
|
12
|
+
"pattern": "^2\\.\\d+\\.\\d+$",
|
|
13
|
+
"description": "Schema version (must start with 2.x.x)"
|
|
14
|
+
},
|
|
15
|
+
"name": {
|
|
16
|
+
"type": "string",
|
|
17
|
+
"description": "Human-readable workflow name"
|
|
18
|
+
},
|
|
19
|
+
"description": {
|
|
20
|
+
"type": "string",
|
|
21
|
+
"description": "Workflow description"
|
|
22
|
+
},
|
|
23
|
+
"config": {
|
|
24
|
+
"$ref": "#/definitions/Config",
|
|
25
|
+
"description": "Global configuration and defaults"
|
|
26
|
+
},
|
|
27
|
+
"api_input": {
|
|
28
|
+
"type": "object",
|
|
29
|
+
"description": "Expected input parameters for the workflow",
|
|
30
|
+
"additionalProperties": true
|
|
31
|
+
},
|
|
32
|
+
"steps": {
|
|
33
|
+
"type": "array",
|
|
34
|
+
"minItems": 1,
|
|
35
|
+
"items": {
|
|
36
|
+
"$ref": "#/definitions/Step"
|
|
37
|
+
},
|
|
38
|
+
"description": "Array of workflow steps"
|
|
39
|
+
},
|
|
40
|
+
"delivery": {
|
|
41
|
+
"$ref": "#/definitions/DeliveryConfig",
|
|
42
|
+
"description": "Delivery strategy and destinations for workflow output"
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"definitions": {
|
|
46
|
+
"Config": {
|
|
47
|
+
"type": "object",
|
|
48
|
+
"additionalProperties": false,
|
|
49
|
+
"properties": {
|
|
50
|
+
"default_timeout_ms": {
|
|
51
|
+
"type": "integer",
|
|
52
|
+
"minimum": 1,
|
|
53
|
+
"description": "Default timeout for all steps in milliseconds"
|
|
54
|
+
},
|
|
55
|
+
"default_retry": {
|
|
56
|
+
"$ref": "#/definitions/RetryConfig",
|
|
57
|
+
"description": "Default retry configuration for all steps"
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
"Step": {
|
|
62
|
+
"type": "object",
|
|
63
|
+
"required": ["step_id", "type"],
|
|
64
|
+
"oneOf": [
|
|
65
|
+
{ "$ref": "#/definitions/TaskStep" },
|
|
66
|
+
{ "$ref": "#/definitions/ForeachStep" },
|
|
67
|
+
{ "$ref": "#/definitions/ForkJoinStep" },
|
|
68
|
+
{ "$ref": "#/definitions/SwitchStep" },
|
|
69
|
+
{ "$ref": "#/definitions/SubWorkflowStep" },
|
|
70
|
+
{ "$ref": "#/definitions/WaitStep" },
|
|
71
|
+
{ "$ref": "#/definitions/DispatchStep" }
|
|
72
|
+
]
|
|
73
|
+
},
|
|
74
|
+
"TaskStep": {
|
|
75
|
+
"type": "object",
|
|
76
|
+
"required": ["step_id", "type", "service", "operation"],
|
|
77
|
+
"additionalProperties": false,
|
|
78
|
+
"properties": {
|
|
79
|
+
"step_id": {
|
|
80
|
+
"type": "string",
|
|
81
|
+
"pattern": "^[a-zA-Z0-9_]+$",
|
|
82
|
+
"description": "Unique identifier for the step"
|
|
83
|
+
},
|
|
84
|
+
"type": {
|
|
85
|
+
"const": "task",
|
|
86
|
+
"description": "Step type"
|
|
87
|
+
},
|
|
88
|
+
"name": {
|
|
89
|
+
"type": "string",
|
|
90
|
+
"description": "Human-readable step name"
|
|
91
|
+
},
|
|
92
|
+
"service": {
|
|
93
|
+
"type": "string",
|
|
94
|
+
"pattern": "^[a-z0-9-]+$",
|
|
95
|
+
"description": "Target microservice name"
|
|
96
|
+
},
|
|
97
|
+
"operation": {
|
|
98
|
+
"type": "string",
|
|
99
|
+
"description": "Operation ID from OpenAPI spec (required for task steps)"
|
|
100
|
+
},
|
|
101
|
+
"input": {
|
|
102
|
+
"type": "object",
|
|
103
|
+
"additionalProperties": true,
|
|
104
|
+
"description": "Input parameters for the operation"
|
|
105
|
+
},
|
|
106
|
+
"output": {
|
|
107
|
+
"type": "object",
|
|
108
|
+
"additionalProperties": true,
|
|
109
|
+
"description": "Output mapping to workflow context"
|
|
110
|
+
},
|
|
111
|
+
"timeout_ms": {
|
|
112
|
+
"type": "integer",
|
|
113
|
+
"minimum": 1,
|
|
114
|
+
"description": "Timeout in milliseconds (overrides default)"
|
|
115
|
+
},
|
|
116
|
+
"retry": {
|
|
117
|
+
"$ref": "#/definitions/RetryConfig",
|
|
118
|
+
"description": "Retry configuration (overrides default)"
|
|
119
|
+
},
|
|
120
|
+
"on_error": {
|
|
121
|
+
"$ref": "#/definitions/ErrorHandler",
|
|
122
|
+
"description": "Error handling configuration"
|
|
123
|
+
},
|
|
124
|
+
"compensate": {
|
|
125
|
+
"$ref": "#/definitions/CompensationStep",
|
|
126
|
+
"description": "Rollback/compensation configuration"
|
|
127
|
+
},
|
|
128
|
+
"depends_on": {
|
|
129
|
+
"type": "array",
|
|
130
|
+
"items": {
|
|
131
|
+
"type": "string"
|
|
132
|
+
},
|
|
133
|
+
"description": "Array of step_ids this step depends on"
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
"ForeachStep": {
|
|
138
|
+
"type": "object",
|
|
139
|
+
"required": ["step_id", "type", "iterator", "steps"],
|
|
140
|
+
"additionalProperties": false,
|
|
141
|
+
"properties": {
|
|
142
|
+
"step_id": {
|
|
143
|
+
"type": "string",
|
|
144
|
+
"pattern": "^[a-zA-Z0-9_]+$"
|
|
145
|
+
},
|
|
146
|
+
"type": {
|
|
147
|
+
"const": "foreach"
|
|
148
|
+
},
|
|
149
|
+
"name": {
|
|
150
|
+
"type": "string"
|
|
151
|
+
},
|
|
152
|
+
"iterator": {
|
|
153
|
+
"type": "string",
|
|
154
|
+
"pattern": "^\\$\\{.+\\}$",
|
|
155
|
+
"description": "Reference to array to iterate over"
|
|
156
|
+
},
|
|
157
|
+
"steps": {
|
|
158
|
+
"type": "array",
|
|
159
|
+
"minItems": 1,
|
|
160
|
+
"items": {
|
|
161
|
+
"$ref": "#/definitions/Step"
|
|
162
|
+
},
|
|
163
|
+
"description": "Steps to execute for each item"
|
|
164
|
+
},
|
|
165
|
+
"output": {
|
|
166
|
+
"type": "object",
|
|
167
|
+
"additionalProperties": true,
|
|
168
|
+
"description": "How to aggregate results"
|
|
169
|
+
},
|
|
170
|
+
"max_concurrency": {
|
|
171
|
+
"type": "integer",
|
|
172
|
+
"minimum": 1,
|
|
173
|
+
"description": "Maximum parallel executions"
|
|
174
|
+
},
|
|
175
|
+
"on_error": {
|
|
176
|
+
"$ref": "#/definitions/ErrorHandler"
|
|
177
|
+
},
|
|
178
|
+
"depends_on": {
|
|
179
|
+
"type": "array",
|
|
180
|
+
"items": {
|
|
181
|
+
"type": "string"
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
},
|
|
186
|
+
"ForkJoinStep": {
|
|
187
|
+
"type": "object",
|
|
188
|
+
"required": ["step_id", "type", "branches", "join"],
|
|
189
|
+
"additionalProperties": false,
|
|
190
|
+
"properties": {
|
|
191
|
+
"step_id": {
|
|
192
|
+
"type": "string",
|
|
193
|
+
"pattern": "^[a-zA-Z0-9_]+$"
|
|
194
|
+
},
|
|
195
|
+
"type": {
|
|
196
|
+
"const": "fork_join"
|
|
197
|
+
},
|
|
198
|
+
"name": {
|
|
199
|
+
"type": "string"
|
|
200
|
+
},
|
|
201
|
+
"branches": {
|
|
202
|
+
"type": "array",
|
|
203
|
+
"minItems": 2,
|
|
204
|
+
"items": {
|
|
205
|
+
"type": "object",
|
|
206
|
+
"required": ["branch_id", "steps"],
|
|
207
|
+
"properties": {
|
|
208
|
+
"branch_id": {
|
|
209
|
+
"type": "string"
|
|
210
|
+
},
|
|
211
|
+
"steps": {
|
|
212
|
+
"type": "array",
|
|
213
|
+
"minItems": 1,
|
|
214
|
+
"items": {
|
|
215
|
+
"$ref": "#/definitions/Step"
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
},
|
|
220
|
+
"description": "Parallel branches to execute"
|
|
221
|
+
},
|
|
222
|
+
"join": {
|
|
223
|
+
"type": "object",
|
|
224
|
+
"required": ["strategy"],
|
|
225
|
+
"properties": {
|
|
226
|
+
"strategy": {
|
|
227
|
+
"enum": ["all", "first", "race", "merge"],
|
|
228
|
+
"description": "How to join branch results"
|
|
229
|
+
},
|
|
230
|
+
"timeout_ms": {
|
|
231
|
+
"type": "integer",
|
|
232
|
+
"minimum": 1
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
},
|
|
236
|
+
"output": {
|
|
237
|
+
"type": "object",
|
|
238
|
+
"additionalProperties": true
|
|
239
|
+
},
|
|
240
|
+
"on_error": {
|
|
241
|
+
"$ref": "#/definitions/ErrorHandler"
|
|
242
|
+
},
|
|
243
|
+
"depends_on": {
|
|
244
|
+
"type": "array",
|
|
245
|
+
"items": {
|
|
246
|
+
"type": "string"
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
},
|
|
251
|
+
"SwitchStep": {
|
|
252
|
+
"type": "object",
|
|
253
|
+
"required": ["step_id", "type", "expression", "cases", "default"],
|
|
254
|
+
"additionalProperties": false,
|
|
255
|
+
"properties": {
|
|
256
|
+
"step_id": {
|
|
257
|
+
"type": "string",
|
|
258
|
+
"pattern": "^[a-zA-Z0-9_]+$"
|
|
259
|
+
},
|
|
260
|
+
"type": {
|
|
261
|
+
"const": "switch"
|
|
262
|
+
},
|
|
263
|
+
"name": {
|
|
264
|
+
"type": "string"
|
|
265
|
+
},
|
|
266
|
+
"expression": {
|
|
267
|
+
"type": "string",
|
|
268
|
+
"pattern": "^\\$\\{.+\\}$",
|
|
269
|
+
"description": "Expression to evaluate for switch"
|
|
270
|
+
},
|
|
271
|
+
"cases": {
|
|
272
|
+
"type": "object",
|
|
273
|
+
"additionalProperties": {
|
|
274
|
+
"type": "object",
|
|
275
|
+
"required": ["steps"],
|
|
276
|
+
"properties": {
|
|
277
|
+
"steps": {
|
|
278
|
+
"type": "array",
|
|
279
|
+
"minItems": 1,
|
|
280
|
+
"items": {
|
|
281
|
+
"$ref": "#/definitions/Step"
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
},
|
|
286
|
+
"description": "Case branches"
|
|
287
|
+
},
|
|
288
|
+
"default": {
|
|
289
|
+
"type": "object",
|
|
290
|
+
"required": ["steps"],
|
|
291
|
+
"properties": {
|
|
292
|
+
"steps": {
|
|
293
|
+
"type": "array",
|
|
294
|
+
"minItems": 1,
|
|
295
|
+
"items": {
|
|
296
|
+
"$ref": "#/definitions/Step"
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
},
|
|
300
|
+
"description": "Default branch (required)"
|
|
301
|
+
},
|
|
302
|
+
"on_error": {
|
|
303
|
+
"$ref": "#/definitions/ErrorHandler"
|
|
304
|
+
},
|
|
305
|
+
"depends_on": {
|
|
306
|
+
"type": "array",
|
|
307
|
+
"items": {
|
|
308
|
+
"type": "string"
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
},
|
|
313
|
+
"SubWorkflowStep": {
|
|
314
|
+
"type": "object",
|
|
315
|
+
"required": ["step_id", "type", "workflow_id"],
|
|
316
|
+
"additionalProperties": false,
|
|
317
|
+
"properties": {
|
|
318
|
+
"step_id": {
|
|
319
|
+
"type": "string",
|
|
320
|
+
"pattern": "^[a-zA-Z0-9_]+$"
|
|
321
|
+
},
|
|
322
|
+
"type": {
|
|
323
|
+
"const": "sub_workflow"
|
|
324
|
+
},
|
|
325
|
+
"name": {
|
|
326
|
+
"type": "string"
|
|
327
|
+
},
|
|
328
|
+
"workflow_id": {
|
|
329
|
+
"type": "string",
|
|
330
|
+
"description": "ID of workflow to execute"
|
|
331
|
+
},
|
|
332
|
+
"parameters": {
|
|
333
|
+
"type": "object",
|
|
334
|
+
"additionalProperties": true,
|
|
335
|
+
"description": "Parameters to pass to sub-workflow"
|
|
336
|
+
},
|
|
337
|
+
"output": {
|
|
338
|
+
"type": "object",
|
|
339
|
+
"additionalProperties": true
|
|
340
|
+
},
|
|
341
|
+
"timeout_ms": {
|
|
342
|
+
"type": "integer",
|
|
343
|
+
"minimum": 1
|
|
344
|
+
},
|
|
345
|
+
"on_error": {
|
|
346
|
+
"$ref": "#/definitions/ErrorHandler"
|
|
347
|
+
},
|
|
348
|
+
"depends_on": {
|
|
349
|
+
"type": "array",
|
|
350
|
+
"items": {
|
|
351
|
+
"type": "string"
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
},
|
|
356
|
+
"WaitStep": {
|
|
357
|
+
"type": "object",
|
|
358
|
+
"required": ["step_id", "type", "duration_ms"],
|
|
359
|
+
"additionalProperties": false,
|
|
360
|
+
"properties": {
|
|
361
|
+
"step_id": {
|
|
362
|
+
"type": "string",
|
|
363
|
+
"pattern": "^[a-zA-Z0-9_]+$"
|
|
364
|
+
},
|
|
365
|
+
"type": {
|
|
366
|
+
"const": "wait"
|
|
367
|
+
},
|
|
368
|
+
"name": {
|
|
369
|
+
"type": "string"
|
|
370
|
+
},
|
|
371
|
+
"duration_ms": {
|
|
372
|
+
"type": "integer",
|
|
373
|
+
"minimum": 1,
|
|
374
|
+
"description": "Wait duration in milliseconds"
|
|
375
|
+
},
|
|
376
|
+
"message": {
|
|
377
|
+
"type": "string",
|
|
378
|
+
"description": "Optional message explaining the wait"
|
|
379
|
+
},
|
|
380
|
+
"depends_on": {
|
|
381
|
+
"type": "array",
|
|
382
|
+
"items": {
|
|
383
|
+
"type": "string"
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
},
|
|
388
|
+
"DispatchStep": {
|
|
389
|
+
"type": "object",
|
|
390
|
+
"required": ["step_id", "type", "method", "target"],
|
|
391
|
+
"additionalProperties": false,
|
|
392
|
+
"properties": {
|
|
393
|
+
"step_id": {
|
|
394
|
+
"type": "string",
|
|
395
|
+
"pattern": "^[a-zA-Z0-9_]+$"
|
|
396
|
+
},
|
|
397
|
+
"type": {
|
|
398
|
+
"const": "dispatch"
|
|
399
|
+
},
|
|
400
|
+
"name": {
|
|
401
|
+
"type": "string"
|
|
402
|
+
},
|
|
403
|
+
"method": {
|
|
404
|
+
"enum": ["webhook", "http", "grpc", "kafka", "sqs"],
|
|
405
|
+
"description": "Dispatch method"
|
|
406
|
+
},
|
|
407
|
+
"target": {
|
|
408
|
+
"type": "string",
|
|
409
|
+
"description": "Target URL or address"
|
|
410
|
+
},
|
|
411
|
+
"input": {
|
|
412
|
+
"type": "object",
|
|
413
|
+
"additionalProperties": true
|
|
414
|
+
},
|
|
415
|
+
"headers": {
|
|
416
|
+
"type": "object",
|
|
417
|
+
"additionalProperties": {
|
|
418
|
+
"type": "string"
|
|
419
|
+
}
|
|
420
|
+
},
|
|
421
|
+
"timeout_ms": {
|
|
422
|
+
"type": "integer",
|
|
423
|
+
"minimum": 1
|
|
424
|
+
},
|
|
425
|
+
"retry": {
|
|
426
|
+
"$ref": "#/definitions/RetryConfig"
|
|
427
|
+
},
|
|
428
|
+
"on_error": {
|
|
429
|
+
"$ref": "#/definitions/ErrorHandler"
|
|
430
|
+
},
|
|
431
|
+
"depends_on": {
|
|
432
|
+
"type": "array",
|
|
433
|
+
"items": {
|
|
434
|
+
"type": "string"
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
},
|
|
439
|
+
"RetryConfig": {
|
|
440
|
+
"type": "object",
|
|
441
|
+
"additionalProperties": false,
|
|
442
|
+
"properties": {
|
|
443
|
+
"max_attempts": {
|
|
444
|
+
"type": "integer",
|
|
445
|
+
"minimum": 1,
|
|
446
|
+
"description": "Maximum retry attempts"
|
|
447
|
+
},
|
|
448
|
+
"delay_ms": {
|
|
449
|
+
"type": "integer",
|
|
450
|
+
"minimum": 0,
|
|
451
|
+
"description": "Delay between retries in milliseconds"
|
|
452
|
+
},
|
|
453
|
+
"backoff": {
|
|
454
|
+
"enum": ["linear", "exponential"],
|
|
455
|
+
"description": "Backoff strategy"
|
|
456
|
+
},
|
|
457
|
+
"max_delay_ms": {
|
|
458
|
+
"type": "integer",
|
|
459
|
+
"minimum": 1,
|
|
460
|
+
"description": "Maximum delay for exponential backoff"
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
},
|
|
464
|
+
"ErrorHandler": {
|
|
465
|
+
"type": "object",
|
|
466
|
+
"additionalProperties": false,
|
|
467
|
+
"properties": {
|
|
468
|
+
"strategy": {
|
|
469
|
+
"enum": ["retry", "fail", "continue", "compensate"],
|
|
470
|
+
"description": "Error handling strategy"
|
|
471
|
+
},
|
|
472
|
+
"retry": {
|
|
473
|
+
"$ref": "#/definitions/RetryConfig"
|
|
474
|
+
},
|
|
475
|
+
"catch": {
|
|
476
|
+
"type": "array",
|
|
477
|
+
"items": {
|
|
478
|
+
"type": "object",
|
|
479
|
+
"required": ["error_type", "handler_step"],
|
|
480
|
+
"properties": {
|
|
481
|
+
"error_type": {
|
|
482
|
+
"type": "string",
|
|
483
|
+
"description": "Type of error to catch"
|
|
484
|
+
},
|
|
485
|
+
"handler_step": {
|
|
486
|
+
"type": "string",
|
|
487
|
+
"description": "Step to execute on this error"
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
},
|
|
491
|
+
"description": "Specific error handlers"
|
|
492
|
+
},
|
|
493
|
+
"finally": {
|
|
494
|
+
"type": "string",
|
|
495
|
+
"description": "Step to always execute"
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
},
|
|
499
|
+
"CompensationStep": {
|
|
500
|
+
"type": "object",
|
|
501
|
+
"required": ["step_id", "service", "operation"],
|
|
502
|
+
"additionalProperties": false,
|
|
503
|
+
"properties": {
|
|
504
|
+
"step_id": {
|
|
505
|
+
"type": "string",
|
|
506
|
+
"pattern": "^[a-zA-Z0-9_]+$",
|
|
507
|
+
"description": "ID for compensation step"
|
|
508
|
+
},
|
|
509
|
+
"service": {
|
|
510
|
+
"type": "string",
|
|
511
|
+
"pattern": "^[a-z0-9-]+$"
|
|
512
|
+
},
|
|
513
|
+
"operation": {
|
|
514
|
+
"type": "string"
|
|
515
|
+
},
|
|
516
|
+
"input": {
|
|
517
|
+
"type": "object",
|
|
518
|
+
"additionalProperties": true
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
},
|
|
522
|
+
"DeliveryConfig": {
|
|
523
|
+
"type": "object",
|
|
524
|
+
"required": ["handler"],
|
|
525
|
+
"additionalProperties": false,
|
|
526
|
+
"properties": {
|
|
527
|
+
"handler": {
|
|
528
|
+
"enum": ["dispatcher", "service_step", "none"],
|
|
529
|
+
"description": "Delivery handler selection"
|
|
530
|
+
},
|
|
531
|
+
"allow_skip": {
|
|
532
|
+
"type": "boolean",
|
|
533
|
+
"default": false,
|
|
534
|
+
"description": "Allow dispatcher to skip delivery without failing workflow"
|
|
535
|
+
},
|
|
536
|
+
"delivery_step": {
|
|
537
|
+
"type": "string",
|
|
538
|
+
"pattern": "^[a-zA-Z0-9_]+$",
|
|
539
|
+
"description": "Step ID that performs delivery when handler=service_step"
|
|
540
|
+
},
|
|
541
|
+
"destinations": {
|
|
542
|
+
"type": "array",
|
|
543
|
+
"items": {
|
|
544
|
+
"$ref": "#/definitions/DeliveryDestination"
|
|
545
|
+
},
|
|
546
|
+
"default": []
|
|
547
|
+
},
|
|
548
|
+
"output": {
|
|
549
|
+
"$ref": "#/definitions/DeliveryOutput"
|
|
550
|
+
}
|
|
551
|
+
},
|
|
552
|
+
"allOf": [
|
|
553
|
+
{
|
|
554
|
+
"if": {
|
|
555
|
+
"properties": {
|
|
556
|
+
"handler": { "const": "dispatcher" }
|
|
557
|
+
}
|
|
558
|
+
},
|
|
559
|
+
"then": {
|
|
560
|
+
"required": ["destinations", "output"],
|
|
561
|
+
"properties": {
|
|
562
|
+
"destinations": {
|
|
563
|
+
"minItems": 1
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
},
|
|
568
|
+
{
|
|
569
|
+
"if": {
|
|
570
|
+
"properties": {
|
|
571
|
+
"handler": { "const": "service_step" }
|
|
572
|
+
}
|
|
573
|
+
},
|
|
574
|
+
"then": {
|
|
575
|
+
"required": ["delivery_step", "output"]
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
]
|
|
579
|
+
},
|
|
580
|
+
"DeliveryDestination": {
|
|
581
|
+
"type": "object",
|
|
582
|
+
"required": ["type"],
|
|
583
|
+
"properties": {
|
|
584
|
+
"type": {
|
|
585
|
+
"enum": ["webhook", "websocket", "public_url"],
|
|
586
|
+
"description": "Delivery channel type"
|
|
587
|
+
},
|
|
588
|
+
"name": {
|
|
589
|
+
"type": "string",
|
|
590
|
+
"pattern": "^[a-zA-Z0-9._-]+$",
|
|
591
|
+
"description": "Optional identifier for monitoring"
|
|
592
|
+
},
|
|
593
|
+
"enabled": {
|
|
594
|
+
"type": "boolean",
|
|
595
|
+
"default": true
|
|
596
|
+
},
|
|
597
|
+
"retry": {
|
|
598
|
+
"$ref": "#/definitions/RetryConfig"
|
|
599
|
+
}
|
|
600
|
+
},
|
|
601
|
+
"allOf": [
|
|
602
|
+
{
|
|
603
|
+
"if": {
|
|
604
|
+
"properties": {
|
|
605
|
+
"type": { "const": "webhook" }
|
|
606
|
+
}
|
|
607
|
+
},
|
|
608
|
+
"then": {
|
|
609
|
+
"$ref": "#/definitions/DeliveryWebhookOptions"
|
|
610
|
+
}
|
|
611
|
+
},
|
|
612
|
+
{
|
|
613
|
+
"if": {
|
|
614
|
+
"properties": {
|
|
615
|
+
"type": { "const": "websocket" }
|
|
616
|
+
}
|
|
617
|
+
},
|
|
618
|
+
"then": {
|
|
619
|
+
"$ref": "#/definitions/DeliveryWebsocketOptions"
|
|
620
|
+
}
|
|
621
|
+
},
|
|
622
|
+
{
|
|
623
|
+
"if": {
|
|
624
|
+
"properties": {
|
|
625
|
+
"type": { "const": "public_url" }
|
|
626
|
+
}
|
|
627
|
+
},
|
|
628
|
+
"then": {
|
|
629
|
+
"$ref": "#/definitions/DeliveryPublicUrlOptions"
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
]
|
|
633
|
+
},
|
|
634
|
+
"DeliveryWebhookOptions": {
|
|
635
|
+
"type": "object",
|
|
636
|
+
"required": ["url", "method"],
|
|
637
|
+
"properties": {
|
|
638
|
+
"url": {
|
|
639
|
+
"type": "string",
|
|
640
|
+
"format": "uri",
|
|
641
|
+
"description": "Target webhook URL"
|
|
642
|
+
},
|
|
643
|
+
"method": {
|
|
644
|
+
"type": "string",
|
|
645
|
+
"enum": ["GET", "POST", "PUT", "PATCH", "DELETE"]
|
|
646
|
+
},
|
|
647
|
+
"headers": {
|
|
648
|
+
"type": "object",
|
|
649
|
+
"additionalProperties": {
|
|
650
|
+
"type": "string"
|
|
651
|
+
}
|
|
652
|
+
},
|
|
653
|
+
"body_template": {
|
|
654
|
+
"type": ["object", "array"],
|
|
655
|
+
"description": "Optional custom payload template"
|
|
656
|
+
},
|
|
657
|
+
"timeout_ms": {
|
|
658
|
+
"type": "integer",
|
|
659
|
+
"minimum": 100,
|
|
660
|
+
"description": "Request timeout"
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
},
|
|
664
|
+
"DeliveryWebsocketOptions": {
|
|
665
|
+
"type": "object",
|
|
666
|
+
"properties": {
|
|
667
|
+
"client_id": {
|
|
668
|
+
"type": "string",
|
|
669
|
+
"description": "Client session identifier"
|
|
670
|
+
},
|
|
671
|
+
"tenant_id": {
|
|
672
|
+
"type": "string",
|
|
673
|
+
"description": "Tenant/topic identifier"
|
|
674
|
+
},
|
|
675
|
+
"events": {
|
|
676
|
+
"type": "array",
|
|
677
|
+
"items": {
|
|
678
|
+
"type": "string"
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
},
|
|
682
|
+
"anyOf": [
|
|
683
|
+
{ "required": ["client_id"] },
|
|
684
|
+
{ "required": ["tenant_id"] }
|
|
685
|
+
]
|
|
686
|
+
},
|
|
687
|
+
"DeliveryPublicUrlOptions": {
|
|
688
|
+
"type": "object",
|
|
689
|
+
"required": ["path"],
|
|
690
|
+
"properties": {
|
|
691
|
+
"path": {
|
|
692
|
+
"type": "string",
|
|
693
|
+
"pattern": "^[a-zA-Z0-9._\\-/\\${}]+$",
|
|
694
|
+
"description": "Storage key / relative path"
|
|
695
|
+
},
|
|
696
|
+
"ttl_seconds": {
|
|
697
|
+
"type": "integer",
|
|
698
|
+
"minimum": 60,
|
|
699
|
+
"description": "Time to live for generated URL"
|
|
700
|
+
},
|
|
701
|
+
"access": {
|
|
702
|
+
"type": "string",
|
|
703
|
+
"enum": ["public", "signed"],
|
|
704
|
+
"default": "signed"
|
|
705
|
+
},
|
|
706
|
+
"max_downloads": {
|
|
707
|
+
"type": "integer",
|
|
708
|
+
"minimum": 1
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
},
|
|
712
|
+
"DeliveryOutput": {
|
|
713
|
+
"type": "object",
|
|
714
|
+
"propertyNames": {
|
|
715
|
+
"pattern": "^[a-zA-Z0-9_]+$"
|
|
716
|
+
},
|
|
717
|
+
"additionalProperties": {
|
|
718
|
+
"type": [
|
|
719
|
+
"string",
|
|
720
|
+
"number",
|
|
721
|
+
"boolean",
|
|
722
|
+
"object",
|
|
723
|
+
"array"
|
|
724
|
+
]
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
}
|
package/src/index.js
CHANGED
|
@@ -16,7 +16,9 @@ const {
|
|
|
16
16
|
const {
|
|
17
17
|
validateCookbook,
|
|
18
18
|
validateStep,
|
|
19
|
-
CookbookValidationError
|
|
19
|
+
CookbookValidationError,
|
|
20
|
+
detectVersion,
|
|
21
|
+
loadSchema: loadValidatorSchema
|
|
20
22
|
} = require('./validator');
|
|
21
23
|
|
|
22
24
|
const {
|
|
@@ -40,6 +42,7 @@ module.exports = {
|
|
|
40
42
|
// Validator functions
|
|
41
43
|
validateCookbook,
|
|
42
44
|
validateStep,
|
|
45
|
+
detectVersion,
|
|
43
46
|
|
|
44
47
|
// Reference validation
|
|
45
48
|
validateAllReferences,
|
|
@@ -50,11 +53,12 @@ module.exports = {
|
|
|
50
53
|
// Schema access
|
|
51
54
|
CookbookSchema,
|
|
52
55
|
loadSchema,
|
|
56
|
+
loadValidatorSchema,
|
|
53
57
|
|
|
54
58
|
// Error classes
|
|
55
59
|
CookbookValidationError,
|
|
56
60
|
|
|
57
61
|
// Utility exports
|
|
58
|
-
VERSION: '
|
|
59
|
-
SCHEMA_VERSION: '1.0
|
|
62
|
+
VERSION: '2.0.0',
|
|
63
|
+
SCHEMA_VERSION: '2.1.0'
|
|
60
64
|
};
|
|
File without changes
|
|
@@ -281,7 +281,7 @@ function validateStep(step, context, mode = 'default') {
|
|
|
281
281
|
}
|
|
282
282
|
}
|
|
283
283
|
|
|
284
|
-
allowedProps = new Set([...commonAllowed, 'name', 'service', 'action', 'endpoint', 'input', 'output', 'depends_on', 'retry', 'timeoutMs']);
|
|
284
|
+
allowedProps = new Set([...commonAllowed, 'name', 'service', 'action', 'endpoint', 'operation', 'input', 'output', 'depends_on', 'retry', 'timeoutMs', 'on_error']);
|
|
285
285
|
break;
|
|
286
286
|
}
|
|
287
287
|
case 'foreach': {
|
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview
|
|
3
|
+
* Cookbook Validator v2.0 - Single Source of Truth through JSON Schema
|
|
4
|
+
*
|
|
5
|
+
* This validator uses ONLY JSON Schema (v2.0) for validation.
|
|
6
|
+
* No duplicate manual validation logic.
|
|
7
|
+
*
|
|
8
|
+
* Features:
|
|
9
|
+
* - Full JSON Schema draft-07 validation via AJV
|
|
10
|
+
* - Support for v1.0 backwards compatibility
|
|
11
|
+
* - Automatic migration suggestions for v1.0 cookbooks
|
|
12
|
+
* - Comprehensive error messages with paths
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const Ajv = require('ajv');
|
|
16
|
+
const addFormats = require('ajv-formats');
|
|
17
|
+
const path = require('path');
|
|
18
|
+
const fs = require('fs');
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Custom Error class for validation errors
|
|
22
|
+
*/
|
|
23
|
+
class CookbookValidationError extends Error {
|
|
24
|
+
constructor(message, errors = null) {
|
|
25
|
+
super(message);
|
|
26
|
+
this.name = 'CookbookValidationError';
|
|
27
|
+
this.validationErrors = errors;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Load schema file with version detection
|
|
33
|
+
*/
|
|
34
|
+
function loadSchema(version = '2.0') {
|
|
35
|
+
const schemaMap = {
|
|
36
|
+
'1.0': 'cookbook.schema.json', // Legacy v1.0 schema
|
|
37
|
+
'2.0': 'cookbook.v2.schema.json' // New v2.0 schema with snake_case
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const filename = schemaMap[version.split('.')[0] + '.0'] || schemaMap['2.0'];
|
|
41
|
+
const schemaPath = path.join(__dirname, '../../schemas', filename);
|
|
42
|
+
|
|
43
|
+
if (!fs.existsSync(schemaPath)) {
|
|
44
|
+
throw new CookbookValidationError(`Schema file not found: ${filename}`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return JSON.parse(fs.readFileSync(schemaPath, 'utf-8'));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Create AJV validator instance with custom error messages
|
|
52
|
+
*/
|
|
53
|
+
function createValidator(schema) {
|
|
54
|
+
const ajv = new Ajv({
|
|
55
|
+
allErrors: true,
|
|
56
|
+
strict: false, // Needed for 'condition' property
|
|
57
|
+
verbose: true
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
addFormats(ajv);
|
|
61
|
+
|
|
62
|
+
// Add custom error messages
|
|
63
|
+
ajv.addKeyword({
|
|
64
|
+
keyword: 'errorMessage',
|
|
65
|
+
schemaType: 'string',
|
|
66
|
+
compile: (schemaVal) => {
|
|
67
|
+
return function validate(data, dataPath) {
|
|
68
|
+
validate.errors = [{
|
|
69
|
+
keyword: 'errorMessage',
|
|
70
|
+
message: schemaVal,
|
|
71
|
+
params: {}
|
|
72
|
+
}];
|
|
73
|
+
return true;
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
return ajv.compile(schema);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Format AJV errors into readable messages
|
|
83
|
+
*/
|
|
84
|
+
function formatErrors(errors) {
|
|
85
|
+
if (!errors || errors.length === 0) return 'Unknown validation error';
|
|
86
|
+
|
|
87
|
+
return errors.map(err => {
|
|
88
|
+
const path = err.instancePath || err.dataPath || '/';
|
|
89
|
+
const field = path.split('/').pop() || 'root';
|
|
90
|
+
|
|
91
|
+
switch (err.keyword) {
|
|
92
|
+
case 'required':
|
|
93
|
+
return `Missing required field '${err.params.missingProperty}' at ${path}`;
|
|
94
|
+
|
|
95
|
+
case 'additionalProperties':
|
|
96
|
+
return `Unexpected field '${err.params.additionalProperty}' at ${path}`;
|
|
97
|
+
|
|
98
|
+
case 'enum':
|
|
99
|
+
return `Invalid value for '${field}' at ${path}. Allowed: ${err.params.allowedValues.join(', ')}`;
|
|
100
|
+
|
|
101
|
+
case 'type':
|
|
102
|
+
return `Invalid type for '${field}' at ${path}. Expected ${err.params.type}, got ${typeof err.data}`;
|
|
103
|
+
|
|
104
|
+
case 'pattern':
|
|
105
|
+
return `Invalid format for '${field}' at ${path}. Must match pattern: ${err.params.pattern}`;
|
|
106
|
+
|
|
107
|
+
case 'const':
|
|
108
|
+
return `Field '${field}' at ${path} must be exactly '${err.params.allowedValue}'`;
|
|
109
|
+
|
|
110
|
+
default:
|
|
111
|
+
return err.message || `Validation error at ${path}`;
|
|
112
|
+
}
|
|
113
|
+
}).join('\n');
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Detect cookbook version and suggest migration if needed
|
|
118
|
+
*/
|
|
119
|
+
function detectVersion(cookbook) {
|
|
120
|
+
// Check version field first
|
|
121
|
+
if (cookbook.version) {
|
|
122
|
+
const major = cookbook.version.split('.')[0];
|
|
123
|
+
if (major === '1') return '1.0';
|
|
124
|
+
if (major === '2') return '2.0';
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Auto-detect based on field names
|
|
128
|
+
const hasV1Fields = cookbook.steps && cookbook.steps.some(step =>
|
|
129
|
+
'id' in step && !('step_id' in step)
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
const hasV2Fields = cookbook.steps && cookbook.steps.some(step =>
|
|
133
|
+
'step_id' in step && !('id' in step)
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
if (hasV1Fields) return '1.0';
|
|
137
|
+
if (hasV2Fields) return '2.0';
|
|
138
|
+
|
|
139
|
+
// Default to v2.0 for new cookbooks
|
|
140
|
+
return '2.0';
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Check if migration is needed and provide suggestions
|
|
145
|
+
*/
|
|
146
|
+
function checkMigration(cookbook, detectedVersion) {
|
|
147
|
+
if (detectedVersion === '1.0') {
|
|
148
|
+
const suggestions = [];
|
|
149
|
+
|
|
150
|
+
// Check for camelCase fields that should be snake_case
|
|
151
|
+
if (cookbook.steps) {
|
|
152
|
+
cookbook.steps.forEach((step, idx) => {
|
|
153
|
+
if ('id' in step) {
|
|
154
|
+
suggestions.push(`- Step ${idx}: Rename 'id' to 'step_id'`);
|
|
155
|
+
}
|
|
156
|
+
if ('timeoutMs' in step) {
|
|
157
|
+
suggestions.push(`- Step ${idx}: Rename 'timeoutMs' to 'timeout_ms'`);
|
|
158
|
+
}
|
|
159
|
+
const retry = step.retry || {};
|
|
160
|
+
if ('maxAttempts' in retry) {
|
|
161
|
+
suggestions.push(`- Step ${idx}: Rename 'retry.maxAttempts' to 'retry.max_attempts'`);
|
|
162
|
+
}
|
|
163
|
+
if ('delayMs' in retry) {
|
|
164
|
+
suggestions.push(`- Step ${idx}: Rename 'retry.delayMs' to 'retry.delay_ms'`);
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (suggestions.length > 0) {
|
|
170
|
+
console.warn('\n⚠️ Migration suggestions for v2.0:');
|
|
171
|
+
console.warn(suggestions.join('\n'));
|
|
172
|
+
console.warn('\nRun migration script: npm run migrate:cookbook v1-to-v2 <input> <output>\n');
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Main validation function
|
|
179
|
+
*
|
|
180
|
+
* @param {object} cookbook - The cookbook object to validate
|
|
181
|
+
* @param {object} options - Validation options
|
|
182
|
+
* @param {string} options.version - Force specific version validation
|
|
183
|
+
* @param {boolean} options.strict - Use strict validation (default: true)
|
|
184
|
+
* @param {boolean} options.warnOnly - Only warn, don't throw (default: false)
|
|
185
|
+
* @returns {boolean} True if valid, throws error otherwise
|
|
186
|
+
*/
|
|
187
|
+
function validateCookbook(cookbook, options = {}) {
|
|
188
|
+
const {
|
|
189
|
+
version = null,
|
|
190
|
+
strict = true,
|
|
191
|
+
warnOnly = false
|
|
192
|
+
} = options;
|
|
193
|
+
|
|
194
|
+
// Detect version if not specified
|
|
195
|
+
const cookbookVersion = version || detectVersion(cookbook);
|
|
196
|
+
|
|
197
|
+
// Check if migration needed
|
|
198
|
+
checkMigration(cookbook, cookbookVersion);
|
|
199
|
+
|
|
200
|
+
// Load appropriate schema
|
|
201
|
+
const schema = loadSchema(cookbookVersion);
|
|
202
|
+
|
|
203
|
+
// Create validator
|
|
204
|
+
const validate = createValidator(schema);
|
|
205
|
+
|
|
206
|
+
// Validate
|
|
207
|
+
const isValid = validate(cookbook);
|
|
208
|
+
|
|
209
|
+
if (!isValid) {
|
|
210
|
+
const errorMessage = formatErrors(validate.errors);
|
|
211
|
+
|
|
212
|
+
if (warnOnly) {
|
|
213
|
+
console.warn('⚠️ Cookbook validation warnings:\n' + errorMessage);
|
|
214
|
+
return false;
|
|
215
|
+
} else {
|
|
216
|
+
throw new CookbookValidationError(
|
|
217
|
+
'Cookbook validation failed:\n' + errorMessage,
|
|
218
|
+
validate.errors
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Additional semantic validation (still using schema as source of truth)
|
|
224
|
+
validateSemantics(cookbook, cookbookVersion);
|
|
225
|
+
|
|
226
|
+
return true;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Semantic validation for things JSON Schema can't easily express
|
|
231
|
+
*/
|
|
232
|
+
function validateSemantics(cookbook, version) {
|
|
233
|
+
const stepIds = new Set();
|
|
234
|
+
|
|
235
|
+
// Check for duplicate step_ids (or ids in v1)
|
|
236
|
+
const idField = version === '1.0' ? 'id' : 'step_id';
|
|
237
|
+
|
|
238
|
+
function checkStep(step, path) {
|
|
239
|
+
const stepId = step[idField];
|
|
240
|
+
if (stepId) {
|
|
241
|
+
if (stepIds.has(stepId)) {
|
|
242
|
+
throw new CookbookValidationError(
|
|
243
|
+
`Duplicate ${idField} '${stepId}' found at ${path}`
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
stepIds.add(stepId);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Check nested steps
|
|
250
|
+
if (step.steps) {
|
|
251
|
+
step.steps.forEach((nested, idx) =>
|
|
252
|
+
checkStep(nested, `${path}.steps[${idx}]`)
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
if (step.body) {
|
|
256
|
+
step.body.forEach((nested, idx) =>
|
|
257
|
+
checkStep(nested, `${path}.body[${idx}]`)
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
if (step.branches) {
|
|
261
|
+
step.branches.forEach((branch, idx) => {
|
|
262
|
+
if (branch.steps) {
|
|
263
|
+
branch.steps.forEach((nested, jdx) =>
|
|
264
|
+
checkStep(nested, `${path}.branches[${idx}].steps[${jdx}]`)
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
if (step.cases) {
|
|
270
|
+
Object.keys(step.cases).forEach(caseKey => {
|
|
271
|
+
const caseVal = step.cases[caseKey];
|
|
272
|
+
if (caseVal.steps) {
|
|
273
|
+
caseVal.steps.forEach((nested, idx) =>
|
|
274
|
+
checkStep(nested, `${path}.cases.${caseKey}.steps[${idx}]`)
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Check all steps
|
|
282
|
+
if (cookbook.steps) {
|
|
283
|
+
cookbook.steps.forEach((step, idx) =>
|
|
284
|
+
checkStep(step, `steps[${idx}]`)
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Check dependencies exist
|
|
289
|
+
if (version === '2.0' && cookbook.steps) {
|
|
290
|
+
cookbook.steps.forEach((step, idx) => {
|
|
291
|
+
if (step.depends_on) {
|
|
292
|
+
step.depends_on.forEach(dep => {
|
|
293
|
+
if (!stepIds.has(dep)) {
|
|
294
|
+
throw new CookbookValidationError(
|
|
295
|
+
`Step ${idx} depends on non-existent step_id '${dep}'`
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Validate a single step (for testing purposes)
|
|
306
|
+
*/
|
|
307
|
+
function validateStep(step, version = '2.0') {
|
|
308
|
+
const schema = loadSchema(version);
|
|
309
|
+
const stepSchema = schema.definitions?.Step || schema.definitions?.TaskStep;
|
|
310
|
+
|
|
311
|
+
if (!stepSchema) {
|
|
312
|
+
throw new CookbookValidationError('Step schema definition not found');
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const validate = createValidator({
|
|
316
|
+
...stepSchema,
|
|
317
|
+
$schema: schema.$schema
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
const isValid = validate(step);
|
|
321
|
+
|
|
322
|
+
if (!isValid) {
|
|
323
|
+
throw new CookbookValidationError(
|
|
324
|
+
'Step validation failed:\n' + formatErrors(validate.errors),
|
|
325
|
+
validate.errors
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
return true;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Export functions
|
|
333
|
+
module.exports = {
|
|
334
|
+
validateCookbook,
|
|
335
|
+
validateStep,
|
|
336
|
+
CookbookValidationError,
|
|
337
|
+
detectVersion,
|
|
338
|
+
loadSchema
|
|
339
|
+
};
|
package/src/parser.js
CHANGED
|
File without changes
|
|
File without changes
|
package/src/schema.js
CHANGED
|
File without changes
|
package/src/validator.js
CHANGED
|
@@ -1,17 +1,22 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Re-export validator functions from the parser module
|
|
4
|
+
* Re-export validator functions from the v2.0 parser module
|
|
5
|
+
* This now uses the v2.0 validator as the primary validator
|
|
5
6
|
*/
|
|
6
7
|
|
|
7
8
|
const {
|
|
8
9
|
validateCookbook,
|
|
9
10
|
validateStep,
|
|
10
|
-
CookbookValidationError
|
|
11
|
-
|
|
11
|
+
CookbookValidationError,
|
|
12
|
+
detectVersion,
|
|
13
|
+
loadSchema
|
|
14
|
+
} = require('./parser/cookbookValidatorV2');
|
|
12
15
|
|
|
13
16
|
module.exports = {
|
|
14
17
|
validateCookbook,
|
|
15
18
|
validateStep,
|
|
16
|
-
CookbookValidationError
|
|
19
|
+
CookbookValidationError,
|
|
20
|
+
detectVersion,
|
|
21
|
+
loadSchema
|
|
17
22
|
};
|