arazzo-runner 0.0.11 → 0.0.13
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 +18 -0
- package/package.json +2 -1
- package/src/Arazzo.js +46 -31
- package/src/Document.js +28 -2
- package/src/Expression.js +24 -0
- package/src/OpenAPI.js +0 -1
- package/src/Runner.js +14 -1
package/README.md
CHANGED
|
@@ -66,6 +66,24 @@ jq --arg password "$secret_password" '.workflowId1.password = $password' input.j
|
|
|
66
66
|
|
|
67
67
|
Obviously, if you have a lot of secret variables that need adding as inputs, then you might need to write a script that can alter the `input.json` file for you within your CI/CD runner.
|
|
68
68
|
|
|
69
|
+
### Config file
|
|
70
|
+
|
|
71
|
+
Arazzo Runner allows for a config file to keep hold of secrets to get OpenAPI/Arazzo documents held on paths behind apikeys:
|
|
72
|
+
|
|
73
|
+
```js
|
|
74
|
+
"use strict";
|
|
75
|
+
|
|
76
|
+
module.exports = {
|
|
77
|
+
documentKey: {
|
|
78
|
+
key: process.env.APIKey,
|
|
79
|
+
in: "header|query", // either in the header or the query params
|
|
80
|
+
name: "apiKey",
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
When downloading OpenAPI/Arazzo documents, it will use the documentKey to access those files. This file should be stored in `./options/config.js`
|
|
86
|
+
|
|
69
87
|
## OpenAPI Servers
|
|
70
88
|
|
|
71
89
|
OpenAPI Documents allow you to specify servers at the root, [path](https://spec.openapis.org/oas/latest.html#path-item-object) and [operation](https://spec.openapis.org/oas/latest.html#operation-object) level. They allow you to specify multiple servers, however the OpenAPI specification is opinionated that all servers specified in a Document should return the same thing and this Arazzo Runner will follow this opinion and only attempt one of the specified servers.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "arazzo-runner",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.13",
|
|
4
4
|
"description": "A runner to run through Arazzo Document workflows",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
"@swaggerexpert/arazzo-runtime-expression": "^1.0.1",
|
|
41
41
|
"@swaggerexpert/json-pointer": "^2.10.2",
|
|
42
42
|
"ajv": "^8.17.1",
|
|
43
|
+
"fast-xml-parser": "^5.3.3",
|
|
43
44
|
"jsonpath": "^1.1.1",
|
|
44
45
|
"openapi-params": "^0.0.5",
|
|
45
46
|
"openapi-server-url-templating": "^1.3.0",
|
package/src/Arazzo.js
CHANGED
|
@@ -348,9 +348,9 @@ class Arazzo extends Document {
|
|
|
348
348
|
} else {
|
|
349
349
|
this.logger.verbose("Using fetch call");
|
|
350
350
|
|
|
351
|
-
this.logger.verbose(`url: ${url}`);
|
|
352
|
-
this.logger.verbose(`method: ${options.method}`);
|
|
353
|
-
this.logger.verbose("headers:");
|
|
351
|
+
this.logger.verbose(`request url: ${url}`);
|
|
352
|
+
this.logger.verbose(`request method: ${options.method}`);
|
|
353
|
+
this.logger.verbose("request headers:");
|
|
354
354
|
for (const [key, value] of options.headers.entries()) {
|
|
355
355
|
this.logger.verbose(`${key}: ${value}`);
|
|
356
356
|
}
|
|
@@ -360,6 +360,12 @@ class Arazzo extends Document {
|
|
|
360
360
|
response = await fetch(url, options);
|
|
361
361
|
}
|
|
362
362
|
|
|
363
|
+
this.logger.verbose(`received StatusCode: ${response.status}`);
|
|
364
|
+
this.logger.verbose(`received headers:`);
|
|
365
|
+
for (const [key, value] of response.headers.entries()) {
|
|
366
|
+
this.logger.verbose(`${key}: ${value}`);
|
|
367
|
+
}
|
|
368
|
+
|
|
363
369
|
if (response.headers.has("retry-after")) {
|
|
364
370
|
const retryAfter = parseRetryAfter(response.headers.get("retry-after"));
|
|
365
371
|
if (retryAfter !== null) {
|
|
@@ -504,32 +510,48 @@ class Arazzo extends Document {
|
|
|
504
510
|
* @param {*} response
|
|
505
511
|
*/
|
|
506
512
|
async dealWithResponse(response) {
|
|
513
|
+
if (
|
|
514
|
+
response.headers.has("Content-Type") &&
|
|
515
|
+
response.headers.get("Content-Type") === "application/json"
|
|
516
|
+
) {
|
|
517
|
+
const json = await response?.json().catch((err) => {
|
|
518
|
+
this.logger.error(
|
|
519
|
+
`Error trying to resolve ${this.step.stepId} outputs`,
|
|
520
|
+
);
|
|
521
|
+
throw new Error(err);
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
this.expression.addToContext("response.body", json);
|
|
525
|
+
} else {
|
|
526
|
+
const body = await response.body;
|
|
527
|
+
|
|
528
|
+
this.expression.addToContext("response.body", body);
|
|
529
|
+
}
|
|
530
|
+
|
|
507
531
|
this.doNotProcessStep = false;
|
|
508
532
|
this.alreadyProcessingOnFailure = false;
|
|
509
533
|
|
|
510
534
|
if (this.step.successCriteria) {
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
if (this.
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
this.logger.notice("Retries stopped");
|
|
520
|
-
}
|
|
535
|
+
const passedSuccessCriteria = this.hasPassedSuccessCriteria();
|
|
536
|
+
|
|
537
|
+
if (passedSuccessCriteria) {
|
|
538
|
+
this.logger.success("All criteria checks passed");
|
|
539
|
+
if (this.currentRetryRule) {
|
|
540
|
+
if (this.retryContext.doNotDeleteRetryLimits) {
|
|
541
|
+
this.retryLimits[this.currentRetryRule] = 0;
|
|
542
|
+
this.logger.notice("Retries stopped");
|
|
521
543
|
}
|
|
544
|
+
}
|
|
522
545
|
|
|
523
|
-
|
|
546
|
+
await this.dealWithPassedRule(response);
|
|
547
|
+
} else {
|
|
548
|
+
this.logger.error("Not all criteria checks passed");
|
|
549
|
+
if (this.step.onFailure) {
|
|
550
|
+
await this.dealWithFailedRule();
|
|
524
551
|
} else {
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
} else {
|
|
529
|
-
throw new Error(
|
|
530
|
-
`${this.step.stepId} step of the ${this.workflow.workflowId} workflow failed the successCriteria`,
|
|
531
|
-
);
|
|
532
|
-
}
|
|
552
|
+
throw new Error(
|
|
553
|
+
`${this.step.stepId} step of the ${this.workflow.workflowId} workflow failed the successCriteria`,
|
|
554
|
+
);
|
|
533
555
|
}
|
|
534
556
|
}
|
|
535
557
|
} else {
|
|
@@ -843,14 +865,7 @@ class Arazzo extends Document {
|
|
|
843
865
|
* @private
|
|
844
866
|
* @param {*} response
|
|
845
867
|
*/
|
|
846
|
-
async dealWithStepOutputs(
|
|
847
|
-
const json = await response?.json().catch((err) => {
|
|
848
|
-
this.logger.error(`Error trying to resolve ${this.step.stepId} outputs`);
|
|
849
|
-
throw new Error(err);
|
|
850
|
-
});
|
|
851
|
-
|
|
852
|
-
this.expression.addToContext("response.body", json);
|
|
853
|
-
|
|
868
|
+
async dealWithStepOutputs() {
|
|
854
869
|
const outputs = {};
|
|
855
870
|
for (const key in this.step.outputs) {
|
|
856
871
|
const value = this.expression.resolveExpression(this.step.outputs[key]);
|
|
@@ -1067,7 +1082,7 @@ class Arazzo extends Document {
|
|
|
1067
1082
|
this.sourceDescription.type,
|
|
1068
1083
|
this.sourceDescription.url,
|
|
1069
1084
|
this.sourceDescription.name,
|
|
1070
|
-
{ logger: this.logger },
|
|
1085
|
+
{ logger: this.logger, config: this.config },
|
|
1071
1086
|
);
|
|
1072
1087
|
|
|
1073
1088
|
Object.assign(this.loadedSourceDescriptions, {
|
package/src/Document.js
CHANGED
|
@@ -15,14 +15,40 @@ const fsp = require("node:fs/promises");
|
|
|
15
15
|
const path = require("node:path");
|
|
16
16
|
|
|
17
17
|
class Document {
|
|
18
|
-
constructor(url, name, { logger,
|
|
18
|
+
constructor(url, name, { logger, config }) {
|
|
19
19
|
this.url = url;
|
|
20
20
|
this.name = name;
|
|
21
21
|
this.logger = logger;
|
|
22
|
+
this.config = config;
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
async loadDocument() {
|
|
25
|
-
|
|
26
|
+
let headers = new Headers();
|
|
27
|
+
let fetchURL = new URL(this.url);
|
|
28
|
+
|
|
29
|
+
if (this.config) {
|
|
30
|
+
if (this.config?.documentKey) {
|
|
31
|
+
if (this.config.documentKey.in === "header") {
|
|
32
|
+
headers.append(
|
|
33
|
+
this.config.documentKey.name,
|
|
34
|
+
this.config.documentKey.key,
|
|
35
|
+
);
|
|
36
|
+
} else {
|
|
37
|
+
fetchURL.searchParams.append(
|
|
38
|
+
this.config.documentKey.name,
|
|
39
|
+
this.config.documentKey.key,
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
this.logger.verbose(`fetching ${this.type} document from: ${fetchURL}`);
|
|
46
|
+
this.logger.verbose("fetch headers:");
|
|
47
|
+
for (const [key, value] of headers.entries()) {
|
|
48
|
+
this.logger.verbose(`${key}: ${value}`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const response = await fetch(fetchURL, { headers: headers });
|
|
26
52
|
|
|
27
53
|
if (!response.ok) {
|
|
28
54
|
throw new Error(`Error fetching document from ${this.url}`);
|
package/src/Expression.js
CHANGED
|
@@ -242,6 +242,18 @@ class Expression {
|
|
|
242
242
|
// return expression;
|
|
243
243
|
// }
|
|
244
244
|
|
|
245
|
+
/**
|
|
246
|
+
* @private
|
|
247
|
+
* @param {*} value
|
|
248
|
+
* @returns {boolean}
|
|
249
|
+
*/
|
|
250
|
+
isXML(value) {
|
|
251
|
+
return (
|
|
252
|
+
typeof value === "string" &&
|
|
253
|
+
(value.trim().startsWith("<?xml") || value.trim().startsWith("<"))
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
|
|
245
257
|
/**
|
|
246
258
|
* Resolves a runtime expression to its value in the context
|
|
247
259
|
* @public
|
|
@@ -470,6 +482,18 @@ class Expression {
|
|
|
470
482
|
throw new Error(`Context path '${normalised}' not found`);
|
|
471
483
|
}
|
|
472
484
|
|
|
485
|
+
// NEW: Check if contextData is XML
|
|
486
|
+
if (this.isXML(contextData)) {
|
|
487
|
+
const { XMLParser } = require("fast-xml-parser");
|
|
488
|
+
const parser = new XMLParser({
|
|
489
|
+
ignoreAttributes: false,
|
|
490
|
+
attributeNamePrefix: "@_",
|
|
491
|
+
});
|
|
492
|
+
const jsonData = parser.parse(contextData);
|
|
493
|
+
return evaluate(jsonData, pointer);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// Regular JSON pointer evaluation
|
|
473
497
|
try {
|
|
474
498
|
return evaluate(contextData, pointer);
|
|
475
499
|
} catch (err) {
|
package/src/OpenAPI.js
CHANGED
package/src/Runner.js
CHANGED
|
@@ -5,10 +5,23 @@ const Input = require("./Input");
|
|
|
5
5
|
|
|
6
6
|
const Logger = require("./Logger");
|
|
7
7
|
|
|
8
|
+
const path = require("node:path");
|
|
9
|
+
|
|
8
10
|
class Runner {
|
|
9
11
|
constructor(options, logger) {
|
|
10
12
|
const verboseLogging = options?.verbose || false;
|
|
11
13
|
this.logger = logger || new Logger(verboseLogging);
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
this.logger.verbose(
|
|
17
|
+
`Loading config from ${path.resolve("./options", "config")}`,
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
this.config = require(path.resolve("./options", "config.js"));
|
|
21
|
+
} catch (err) {
|
|
22
|
+
this.logger.verbose("No config found");
|
|
23
|
+
this.config = {};
|
|
24
|
+
}
|
|
12
25
|
}
|
|
13
26
|
|
|
14
27
|
/**
|
|
@@ -23,7 +36,7 @@ class Runner {
|
|
|
23
36
|
"arazzo",
|
|
24
37
|
arazzoUrl,
|
|
25
38
|
"arazzo",
|
|
26
|
-
{ logger: this.logger },
|
|
39
|
+
{ logger: this.logger, config: this.config },
|
|
27
40
|
);
|
|
28
41
|
|
|
29
42
|
if (!this.isUrl(arazzoUrl)) {
|