@onlineapps/cookbook-core 1.0.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 +117 -0
- package/package.json +38 -0
- package/schemas/cookbook.relaxed.schema.json +351 -0
- package/schemas/cookbook.schema.json +482 -0
- package/schemas/cookbook.strict.schema.json +441 -0
- package/src/index.js +60 -0
- package/src/parser/cookbookParser.js +108 -0
- package/src/parser/cookbookValidator.js +569 -0
- package/src/parser.js +7 -0
- package/src/referenceValidator.js +317 -0
- package/src/schema.js +46 -0
- package/src/validator.js +17 -0
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://onlineapps.cz/schemas/cookbook-strict/v2.0.0",
|
|
4
|
+
"title": "Cookbook Schema - Strict Mode",
|
|
5
|
+
"description": "Strict validation schema for production environments with all safety checks enabled",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": ["steps", "version"],
|
|
8
|
+
"additionalProperties": false,
|
|
9
|
+
"properties": {
|
|
10
|
+
"name": {
|
|
11
|
+
"type": "string",
|
|
12
|
+
"minLength": 1,
|
|
13
|
+
"maxLength": 100,
|
|
14
|
+
"pattern": "^[a-zA-Z0-9-_]+$"
|
|
15
|
+
},
|
|
16
|
+
"version": {
|
|
17
|
+
"type": "string",
|
|
18
|
+
"pattern": "^\\d+\\.\\d+\\.\\d+$",
|
|
19
|
+
"description": "Semantic version of the cookbook schema"
|
|
20
|
+
},
|
|
21
|
+
"description": {
|
|
22
|
+
"type": "string",
|
|
23
|
+
"maxLength": 500
|
|
24
|
+
},
|
|
25
|
+
"api_input": {
|
|
26
|
+
"type": "object",
|
|
27
|
+
"description": "Initial input data for the workflow"
|
|
28
|
+
},
|
|
29
|
+
"metadata": {
|
|
30
|
+
"type": "object",
|
|
31
|
+
"description": "Additional metadata for debugging and tracking",
|
|
32
|
+
"properties": {
|
|
33
|
+
"created_at": {
|
|
34
|
+
"type": "string",
|
|
35
|
+
"format": "date-time"
|
|
36
|
+
},
|
|
37
|
+
"author": {
|
|
38
|
+
"type": "string"
|
|
39
|
+
},
|
|
40
|
+
"tags": {
|
|
41
|
+
"type": "array",
|
|
42
|
+
"items": {
|
|
43
|
+
"type": "string"
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
"steps": {
|
|
49
|
+
"type": "array",
|
|
50
|
+
"minItems": 1,
|
|
51
|
+
"maxItems": 1000,
|
|
52
|
+
"items": {
|
|
53
|
+
"$ref": "#/definitions/Step"
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
"definitions": {
|
|
58
|
+
"Step": {
|
|
59
|
+
"type": "object",
|
|
60
|
+
"required": ["id", "type"],
|
|
61
|
+
"properties": {
|
|
62
|
+
"id": {
|
|
63
|
+
"type": "string",
|
|
64
|
+
"minLength": 1,
|
|
65
|
+
"maxLength": 100,
|
|
66
|
+
"pattern": "^[a-zA-Z0-9-_]+$"
|
|
67
|
+
},
|
|
68
|
+
"type": {
|
|
69
|
+
"type": "string",
|
|
70
|
+
"enum": ["task", "foreach", "fork_join", "switch", "sub_workflow", "wait", "dispatch"]
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
"oneOf": [
|
|
74
|
+
{ "$ref": "#/definitions/TaskStep" },
|
|
75
|
+
{ "$ref": "#/definitions/ForeachStep" },
|
|
76
|
+
{ "$ref": "#/definitions/ForkJoinStep" },
|
|
77
|
+
{ "$ref": "#/definitions/SwitchStep" },
|
|
78
|
+
{ "$ref": "#/definitions/SubWorkflowStep" },
|
|
79
|
+
{ "$ref": "#/definitions/WaitStep" },
|
|
80
|
+
{ "$ref": "#/definitions/DispatchStep" }
|
|
81
|
+
]
|
|
82
|
+
},
|
|
83
|
+
"TaskStep": {
|
|
84
|
+
"type": "object",
|
|
85
|
+
"required": ["id", "type", "service"],
|
|
86
|
+
"additionalProperties": false,
|
|
87
|
+
"properties": {
|
|
88
|
+
"id": {
|
|
89
|
+
"type": "string",
|
|
90
|
+
"minLength": 1,
|
|
91
|
+
"maxLength": 100,
|
|
92
|
+
"pattern": "^[a-zA-Z0-9-_]+$"
|
|
93
|
+
},
|
|
94
|
+
"type": {
|
|
95
|
+
"type": "string",
|
|
96
|
+
"const": "task"
|
|
97
|
+
},
|
|
98
|
+
"name": {
|
|
99
|
+
"type": "string",
|
|
100
|
+
"minLength": 1,
|
|
101
|
+
"maxLength": 200
|
|
102
|
+
},
|
|
103
|
+
"service": {
|
|
104
|
+
"type": "string",
|
|
105
|
+
"minLength": 1,
|
|
106
|
+
"maxLength": 100,
|
|
107
|
+
"pattern": "^[a-zA-Z0-9-_]+$"
|
|
108
|
+
},
|
|
109
|
+
"operation": {
|
|
110
|
+
"type": "string",
|
|
111
|
+
"minLength": 1,
|
|
112
|
+
"maxLength": 100,
|
|
113
|
+
"description": "Operation identifier for the service to execute"
|
|
114
|
+
},
|
|
115
|
+
"action": {
|
|
116
|
+
"type": "string",
|
|
117
|
+
"enum": ["GET", "POST", "PUT", "DELETE", "PATCH"]
|
|
118
|
+
},
|
|
119
|
+
"endpoint": {
|
|
120
|
+
"type": "string",
|
|
121
|
+
"minLength": 1,
|
|
122
|
+
"maxLength": 500
|
|
123
|
+
},
|
|
124
|
+
"input": {
|
|
125
|
+
"type": "object",
|
|
126
|
+
"additionalProperties": {
|
|
127
|
+
"type": "string",
|
|
128
|
+
"minLength": 1
|
|
129
|
+
},
|
|
130
|
+
"description": "Optional input mapping"
|
|
131
|
+
},
|
|
132
|
+
"output": {
|
|
133
|
+
"type": "object",
|
|
134
|
+
"additionalProperties": {
|
|
135
|
+
"type": "string",
|
|
136
|
+
"minLength": 1
|
|
137
|
+
},
|
|
138
|
+
"description": "Optional output mapping"
|
|
139
|
+
},
|
|
140
|
+
"depends_on": {
|
|
141
|
+
"type": "array",
|
|
142
|
+
"items": {
|
|
143
|
+
"type": "string",
|
|
144
|
+
"minLength": 1,
|
|
145
|
+
"maxLength": 100
|
|
146
|
+
},
|
|
147
|
+
"maxItems": 100
|
|
148
|
+
},
|
|
149
|
+
"retry": {
|
|
150
|
+
"$ref": "#/definitions/Retry"
|
|
151
|
+
},
|
|
152
|
+
"timeoutMs": {
|
|
153
|
+
"type": "integer",
|
|
154
|
+
"minimum": 1,
|
|
155
|
+
"maximum": 3600000
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
},
|
|
159
|
+
"ForeachStep": {
|
|
160
|
+
"type": "object",
|
|
161
|
+
"required": ["id", "type", "iterator", "body"],
|
|
162
|
+
"additionalProperties": false,
|
|
163
|
+
"properties": {
|
|
164
|
+
"id": {
|
|
165
|
+
"type": "string",
|
|
166
|
+
"minLength": 1,
|
|
167
|
+
"maxLength": 100,
|
|
168
|
+
"pattern": "^[a-zA-Z0-9-_]+$"
|
|
169
|
+
},
|
|
170
|
+
"type": {
|
|
171
|
+
"type": "string",
|
|
172
|
+
"const": "foreach"
|
|
173
|
+
},
|
|
174
|
+
"name": {
|
|
175
|
+
"type": "string",
|
|
176
|
+
"minLength": 1,
|
|
177
|
+
"maxLength": 200
|
|
178
|
+
},
|
|
179
|
+
"iterator": {
|
|
180
|
+
"type": "string",
|
|
181
|
+
"minLength": 1,
|
|
182
|
+
"pattern": "^\\$"
|
|
183
|
+
},
|
|
184
|
+
"body": {
|
|
185
|
+
"type": "array",
|
|
186
|
+
"minItems": 1,
|
|
187
|
+
"maxItems": 100,
|
|
188
|
+
"items": {
|
|
189
|
+
"$ref": "#/definitions/Step"
|
|
190
|
+
}
|
|
191
|
+
},
|
|
192
|
+
"output": {
|
|
193
|
+
"type": "object",
|
|
194
|
+
"additionalProperties": {
|
|
195
|
+
"type": "string",
|
|
196
|
+
"minLength": 1
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
},
|
|
201
|
+
"ForkJoinStep": {
|
|
202
|
+
"type": "object",
|
|
203
|
+
"required": ["id", "type", "branches", "join"],
|
|
204
|
+
"additionalProperties": false,
|
|
205
|
+
"properties": {
|
|
206
|
+
"id": {
|
|
207
|
+
"type": "string",
|
|
208
|
+
"minLength": 1,
|
|
209
|
+
"maxLength": 100,
|
|
210
|
+
"pattern": "^[a-zA-Z0-9-_]+$"
|
|
211
|
+
},
|
|
212
|
+
"type": {
|
|
213
|
+
"type": "string",
|
|
214
|
+
"const": "fork_join"
|
|
215
|
+
},
|
|
216
|
+
"name": {
|
|
217
|
+
"type": "string",
|
|
218
|
+
"minLength": 1,
|
|
219
|
+
"maxLength": 200
|
|
220
|
+
},
|
|
221
|
+
"branches": {
|
|
222
|
+
"type": "array",
|
|
223
|
+
"minItems": 1,
|
|
224
|
+
"maxItems": 50,
|
|
225
|
+
"items": {
|
|
226
|
+
"$ref": "#/definitions/Step"
|
|
227
|
+
}
|
|
228
|
+
},
|
|
229
|
+
"join": {
|
|
230
|
+
"type": "object",
|
|
231
|
+
"required": ["strategy"],
|
|
232
|
+
"additionalProperties": false,
|
|
233
|
+
"properties": {
|
|
234
|
+
"strategy": {
|
|
235
|
+
"type": "string",
|
|
236
|
+
"enum": ["merge", "first", "last", "all", "custom"],
|
|
237
|
+
"description": "How to combine branch results"
|
|
238
|
+
},
|
|
239
|
+
"output": {
|
|
240
|
+
"type": "object",
|
|
241
|
+
"additionalProperties": {
|
|
242
|
+
"type": "string",
|
|
243
|
+
"minLength": 1
|
|
244
|
+
}
|
|
245
|
+
},
|
|
246
|
+
"customHandler": {
|
|
247
|
+
"type": "string",
|
|
248
|
+
"description": "Custom handler name when strategy is 'custom'"
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
},
|
|
254
|
+
"SwitchStep": {
|
|
255
|
+
"type": "object",
|
|
256
|
+
"required": ["id", "type", "expression", "cases", "default"],
|
|
257
|
+
"additionalProperties": false,
|
|
258
|
+
"properties": {
|
|
259
|
+
"id": {
|
|
260
|
+
"type": "string",
|
|
261
|
+
"minLength": 1,
|
|
262
|
+
"maxLength": 100,
|
|
263
|
+
"pattern": "^[a-zA-Z0-9-_]+$"
|
|
264
|
+
},
|
|
265
|
+
"type": {
|
|
266
|
+
"type": "string",
|
|
267
|
+
"const": "switch"
|
|
268
|
+
},
|
|
269
|
+
"name": {
|
|
270
|
+
"type": "string",
|
|
271
|
+
"minLength": 1,
|
|
272
|
+
"maxLength": 200
|
|
273
|
+
},
|
|
274
|
+
"expression": {
|
|
275
|
+
"type": "string",
|
|
276
|
+
"minLength": 1,
|
|
277
|
+
"pattern": "^\\$"
|
|
278
|
+
},
|
|
279
|
+
"cases": {
|
|
280
|
+
"type": "object",
|
|
281
|
+
"minProperties": 1,
|
|
282
|
+
"maxProperties": 100,
|
|
283
|
+
"additionalProperties": {
|
|
284
|
+
"$ref": "#/definitions/Step"
|
|
285
|
+
}
|
|
286
|
+
},
|
|
287
|
+
"default": {
|
|
288
|
+
"$ref": "#/definitions/Step"
|
|
289
|
+
},
|
|
290
|
+
"depends_on": {
|
|
291
|
+
"type": "array",
|
|
292
|
+
"items": {
|
|
293
|
+
"type": "string",
|
|
294
|
+
"minLength": 1,
|
|
295
|
+
"maxLength": 100
|
|
296
|
+
},
|
|
297
|
+
"maxItems": 100
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
},
|
|
301
|
+
"SubWorkflowStep": {
|
|
302
|
+
"type": "object",
|
|
303
|
+
"required": ["id", "type", "steps"],
|
|
304
|
+
"additionalProperties": false,
|
|
305
|
+
"properties": {
|
|
306
|
+
"id": {
|
|
307
|
+
"type": "string",
|
|
308
|
+
"minLength": 1,
|
|
309
|
+
"maxLength": 100,
|
|
310
|
+
"pattern": "^[a-zA-Z0-9-_]+$"
|
|
311
|
+
},
|
|
312
|
+
"type": {
|
|
313
|
+
"type": "string",
|
|
314
|
+
"const": "sub_workflow"
|
|
315
|
+
},
|
|
316
|
+
"name": {
|
|
317
|
+
"type": "string",
|
|
318
|
+
"minLength": 1,
|
|
319
|
+
"maxLength": 200
|
|
320
|
+
},
|
|
321
|
+
"input": {
|
|
322
|
+
"type": "object",
|
|
323
|
+
"additionalProperties": {
|
|
324
|
+
"type": "string",
|
|
325
|
+
"minLength": 1
|
|
326
|
+
},
|
|
327
|
+
"description": "Optional input for sub-workflow"
|
|
328
|
+
},
|
|
329
|
+
"steps": {
|
|
330
|
+
"type": "array",
|
|
331
|
+
"minItems": 1,
|
|
332
|
+
"maxItems": 100,
|
|
333
|
+
"items": {
|
|
334
|
+
"$ref": "#/definitions/Step"
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
},
|
|
339
|
+
"WaitStep": {
|
|
340
|
+
"type": "object",
|
|
341
|
+
"required": ["id", "type", "durationMs"],
|
|
342
|
+
"additionalProperties": false,
|
|
343
|
+
"properties": {
|
|
344
|
+
"id": {
|
|
345
|
+
"type": "string",
|
|
346
|
+
"minLength": 1,
|
|
347
|
+
"maxLength": 100,
|
|
348
|
+
"pattern": "^[a-zA-Z0-9-_]+$"
|
|
349
|
+
},
|
|
350
|
+
"type": {
|
|
351
|
+
"type": "string",
|
|
352
|
+
"const": "wait"
|
|
353
|
+
},
|
|
354
|
+
"name": {
|
|
355
|
+
"type": "string",
|
|
356
|
+
"minLength": 1,
|
|
357
|
+
"maxLength": 200
|
|
358
|
+
},
|
|
359
|
+
"durationMs": {
|
|
360
|
+
"type": "integer",
|
|
361
|
+
"minimum": 0,
|
|
362
|
+
"maximum": 86400000,
|
|
363
|
+
"description": "Wait duration in milliseconds (max 24 hours)"
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
},
|
|
367
|
+
"DispatchStep": {
|
|
368
|
+
"type": "object",
|
|
369
|
+
"required": ["id", "type", "method", "target"],
|
|
370
|
+
"additionalProperties": false,
|
|
371
|
+
"properties": {
|
|
372
|
+
"id": {
|
|
373
|
+
"type": "string",
|
|
374
|
+
"minLength": 1,
|
|
375
|
+
"maxLength": 100,
|
|
376
|
+
"pattern": "^[a-zA-Z0-9-_]+$"
|
|
377
|
+
},
|
|
378
|
+
"type": {
|
|
379
|
+
"type": "string",
|
|
380
|
+
"const": "dispatch"
|
|
381
|
+
},
|
|
382
|
+
"name": {
|
|
383
|
+
"type": "string",
|
|
384
|
+
"minLength": 1,
|
|
385
|
+
"maxLength": 200
|
|
386
|
+
},
|
|
387
|
+
"method": {
|
|
388
|
+
"type": "string",
|
|
389
|
+
"enum": ["webhook", "http", "grpc", "kafka", "sqs", "custom"],
|
|
390
|
+
"description": "Dispatch method to use"
|
|
391
|
+
},
|
|
392
|
+
"target": {
|
|
393
|
+
"type": "string",
|
|
394
|
+
"format": "uri",
|
|
395
|
+
"maxLength": 2000
|
|
396
|
+
},
|
|
397
|
+
"input": {
|
|
398
|
+
"type": "object",
|
|
399
|
+
"additionalProperties": {
|
|
400
|
+
"type": "string",
|
|
401
|
+
"minLength": 1
|
|
402
|
+
},
|
|
403
|
+
"description": "Optional dispatch payload"
|
|
404
|
+
},
|
|
405
|
+
"headers": {
|
|
406
|
+
"type": "object",
|
|
407
|
+
"additionalProperties": {
|
|
408
|
+
"type": "string"
|
|
409
|
+
},
|
|
410
|
+
"description": "Optional HTTP headers for webhook/http methods"
|
|
411
|
+
},
|
|
412
|
+
"retry": {
|
|
413
|
+
"$ref": "#/definitions/Retry"
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
},
|
|
417
|
+
"Retry": {
|
|
418
|
+
"type": "object",
|
|
419
|
+
"required": ["maxAttempts", "delayMs"],
|
|
420
|
+
"additionalProperties": false,
|
|
421
|
+
"properties": {
|
|
422
|
+
"maxAttempts": {
|
|
423
|
+
"type": "integer",
|
|
424
|
+
"minimum": 1,
|
|
425
|
+
"maximum": 10
|
|
426
|
+
},
|
|
427
|
+
"delayMs": {
|
|
428
|
+
"type": "integer",
|
|
429
|
+
"minimum": 0,
|
|
430
|
+
"maximum": 300000
|
|
431
|
+
},
|
|
432
|
+
"backoffMultiplier": {
|
|
433
|
+
"type": "number",
|
|
434
|
+
"minimum": 1.0,
|
|
435
|
+
"maximum": 5.0,
|
|
436
|
+
"description": "Exponential backoff multiplier"
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @module @onlineapps/cookbook-core
|
|
5
|
+
*
|
|
6
|
+
* Core cookbook parsing and validation library.
|
|
7
|
+
* Lightweight foundation for workflow definitions - no heavy dependencies.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const {
|
|
11
|
+
parseCookbookFromFile,
|
|
12
|
+
parseCookbookFromFileSync,
|
|
13
|
+
parseCookbookFromObject
|
|
14
|
+
} = require('./parser');
|
|
15
|
+
|
|
16
|
+
const {
|
|
17
|
+
validateCookbook,
|
|
18
|
+
validateStep,
|
|
19
|
+
CookbookValidationError
|
|
20
|
+
} = require('./validator');
|
|
21
|
+
|
|
22
|
+
const {
|
|
23
|
+
CookbookSchema,
|
|
24
|
+
loadSchema
|
|
25
|
+
} = require('./schema');
|
|
26
|
+
|
|
27
|
+
const {
|
|
28
|
+
validateAllReferences,
|
|
29
|
+
validateDependsOnReferences,
|
|
30
|
+
validateVariableReferences,
|
|
31
|
+
validateErrorHandlerReferences
|
|
32
|
+
} = require('./referenceValidator');
|
|
33
|
+
|
|
34
|
+
module.exports = {
|
|
35
|
+
// Parser functions
|
|
36
|
+
parseCookbookFromFile,
|
|
37
|
+
parseCookbookFromFileSync,
|
|
38
|
+
parseCookbookFromObject,
|
|
39
|
+
|
|
40
|
+
// Validator functions
|
|
41
|
+
validateCookbook,
|
|
42
|
+
validateStep,
|
|
43
|
+
|
|
44
|
+
// Reference validation
|
|
45
|
+
validateAllReferences,
|
|
46
|
+
validateDependsOnReferences,
|
|
47
|
+
validateVariableReferences,
|
|
48
|
+
validateErrorHandlerReferences,
|
|
49
|
+
|
|
50
|
+
// Schema access
|
|
51
|
+
CookbookSchema,
|
|
52
|
+
loadSchema,
|
|
53
|
+
|
|
54
|
+
// Error classes
|
|
55
|
+
CookbookValidationError,
|
|
56
|
+
|
|
57
|
+
// Utility exports
|
|
58
|
+
VERSION: '1.0.0',
|
|
59
|
+
SCHEMA_VERSION: '1.0.0'
|
|
60
|
+
};
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @fileoverview
|
|
5
|
+
* Cookbook Parser: loads and parses a JSON cookbook, then validates its structure.
|
|
6
|
+
* Exposes functions to parse from file or from raw JavaScript object.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
const { validateCookbook, CookbookValidationError } = require('./cookbookValidator');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Read file contents as UTF-8 string (sync version).
|
|
15
|
+
* @param {string} filePath - Path to the cookbook JSON file.
|
|
16
|
+
* @returns {string} Raw file contents.
|
|
17
|
+
* @throws {Error} If file cannot be read.
|
|
18
|
+
*/
|
|
19
|
+
function _readFileSync(filePath) {
|
|
20
|
+
const absolutePath = path.resolve(filePath);
|
|
21
|
+
return fs.readFileSync(absolutePath, 'utf-8');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Read file contents as UTF-8 string (async version).
|
|
26
|
+
* @param {string} filePath - Path to the cookbook JSON file.
|
|
27
|
+
* @returns {Promise<string>} Raw file contents.
|
|
28
|
+
* @throws {Error} If file cannot be read.
|
|
29
|
+
*/
|
|
30
|
+
async function _readFileAsync(filePath) {
|
|
31
|
+
const absolutePath = path.resolve(filePath);
|
|
32
|
+
const fsPromises = fs.promises || require('fs').promises;
|
|
33
|
+
return await fsPromises.readFile(absolutePath, 'utf-8');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Parse a JSON string into an object.
|
|
38
|
+
* @param {string} jsonString - Raw JSON string.
|
|
39
|
+
* @param {string} context - Context label for error messages.
|
|
40
|
+
* @returns {object} Parsed JavaScript object.
|
|
41
|
+
* @throws {SyntaxError} If JSON is invalid.
|
|
42
|
+
*/
|
|
43
|
+
function _parseJson(jsonString, context) {
|
|
44
|
+
try {
|
|
45
|
+
return JSON.parse(jsonString);
|
|
46
|
+
} catch (err) {
|
|
47
|
+
const message = `Failed to parse JSON for ${context}: ${err.message}`;
|
|
48
|
+
const syntaxError = new SyntaxError(message);
|
|
49
|
+
syntaxError.originalError = err;
|
|
50
|
+
throw syntaxError;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Parse and validate a cookbook from a JavaScript object.
|
|
56
|
+
* @param {object} rawObject - JavaScript object representing the cookbook.
|
|
57
|
+
* @returns {object} Validated cookbook object (deep-copied).
|
|
58
|
+
* @throws {CookbookValidationError} If validation fails.
|
|
59
|
+
*/
|
|
60
|
+
function parseCookbookFromObject(rawObject) {
|
|
61
|
+
// Defensive copy to prevent mutation of original
|
|
62
|
+
const cookbook = JSON.parse(JSON.stringify(rawObject));
|
|
63
|
+
|
|
64
|
+
// Validate structure and nested steps
|
|
65
|
+
validateCookbook(cookbook);
|
|
66
|
+
|
|
67
|
+
return cookbook;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Parse and validate a cookbook from a JSON file (sync version).
|
|
72
|
+
* @param {string} filePath - Path to the JSON file containing the cookbook.
|
|
73
|
+
* @returns {object} Validated cookbook object.
|
|
74
|
+
* @throws {Error|SyntaxError|CookbookValidationError} If file read, parse, or validation fails.
|
|
75
|
+
*/
|
|
76
|
+
function parseCookbookFromFileSync(filePath) {
|
|
77
|
+
// Read raw contents
|
|
78
|
+
const raw = _readFileSync(filePath);
|
|
79
|
+
|
|
80
|
+
// Parse JSON into object
|
|
81
|
+
const parsed = _parseJson(raw, `file '${filePath}'`);
|
|
82
|
+
|
|
83
|
+
// Validate and return
|
|
84
|
+
return parseCookbookFromObject(parsed);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Parse and validate a cookbook from a JSON file (async version).
|
|
89
|
+
* @param {string} filePath - Path to the JSON file containing the cookbook.
|
|
90
|
+
* @returns {Promise<object>} Validated cookbook object.
|
|
91
|
+
* @throws {Error|SyntaxError|CookbookValidationError} If file read, parse, or validation fails.
|
|
92
|
+
*/
|
|
93
|
+
async function parseCookbookFromFile(filePath) {
|
|
94
|
+
// Read raw contents
|
|
95
|
+
const raw = await _readFileAsync(filePath);
|
|
96
|
+
|
|
97
|
+
// Parse JSON into object
|
|
98
|
+
const parsed = _parseJson(raw, `file '${filePath}'`);
|
|
99
|
+
|
|
100
|
+
// Validate and return
|
|
101
|
+
return parseCookbookFromObject(parsed);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
module.exports = {
|
|
105
|
+
parseCookbookFromFile,
|
|
106
|
+
parseCookbookFromFileSync,
|
|
107
|
+
parseCookbookFromObject,
|
|
108
|
+
};
|