@grafana/openapi-to-k6 0.1.2 → 0.2.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/.github/workflows/publish.yaml +2 -0
- package/.github/workflows/tests.yaml +14 -6
- package/README.md +49 -14
- package/dist/cli.js +18 -2
- package/dist/constants.js +8 -2
- package/dist/{generator.js → generator/index.js} +7 -13
- package/dist/{k6SdkClient.js → generator/k6Client.js} +148 -161
- package/examples/basic_schema/single/k6-script.sample.ts +11 -0
- package/examples/basic_schema/single/simpleAPI.ts +87 -0
- package/examples/basic_schema/split/k6-script.sample.ts +11 -0
- package/examples/basic_schema/split/simpleAPI.schemas.ts +9 -0
- package/examples/basic_schema/split/simpleAPI.ts +85 -0
- package/examples/basic_schema/tags/default.ts +85 -0
- package/examples/basic_schema/tags/k6-script.sample.ts +11 -0
- package/examples/basic_schema/tags/simpleAPI.schemas.ts +9 -0
- package/examples/form_data_schema/single/formDataAPI.ts +115 -0
- package/examples/form_data_schema/{k6-script.sample.ts → single/k6-script.sample.ts} +2 -2
- package/examples/form_data_schema/split/formDataAPI.schemas.ts +24 -0
- package/examples/form_data_schema/split/formDataAPI.ts +98 -0
- package/examples/form_data_schema/split/k6-script.sample.ts +11 -0
- package/examples/form_data_schema/tags/default.ts +98 -0
- package/examples/form_data_schema/tags/formDataAPI.schemas.ts +24 -0
- package/examples/form_data_schema/tags/k6-script.sample.ts +11 -0
- package/examples/form_url_encoded_data_schema/single/formURLEncodedAPI.ts +112 -0
- package/examples/form_url_encoded_data_schema/{k6-script.sample.ts → single/k6-script.sample.ts} +2 -2
- package/examples/form_url_encoded_data_schema/split/formURLEncodedAPI.schemas.ts +24 -0
- package/examples/form_url_encoded_data_schema/split/formURLEncodedAPI.ts +98 -0
- package/examples/form_url_encoded_data_schema/split/k6-script.sample.ts +11 -0
- package/examples/form_url_encoded_data_schema/tags/default.ts +98 -0
- package/examples/form_url_encoded_data_schema/tags/formURLEncodedAPI.schemas.ts +24 -0
- package/examples/form_url_encoded_data_schema/tags/k6-script.sample.ts +11 -0
- package/examples/form_url_encoded_data_with_query_params_schema/single/formURLEncodedAPIWithQueryParameters.ts +128 -0
- package/examples/form_url_encoded_data_with_query_params_schema/{k6-script.sample.ts → single/k6-script.sample.ts} +2 -2
- package/examples/form_url_encoded_data_with_query_params_schema/split/formURLEncodedAPIWithQueryParameters.schemas.ts +35 -0
- package/examples/form_url_encoded_data_with_query_params_schema/split/formURLEncodedAPIWithQueryParameters.ts +104 -0
- package/examples/form_url_encoded_data_with_query_params_schema/split/k6-script.sample.ts +14 -0
- package/examples/form_url_encoded_data_with_query_params_schema/tags/default.ts +104 -0
- package/examples/form_url_encoded_data_with_query_params_schema/tags/formURLEncodedAPIWithQueryParameters.schemas.ts +35 -0
- package/examples/form_url_encoded_data_with_query_params_schema/tags/k6-script.sample.ts +14 -0
- package/examples/get_request_with_path_parameters_schema/{k6-script.sample.ts → single/k6-script.sample.ts} +2 -2
- package/examples/get_request_with_path_parameters_schema/single/simpleAPI.ts +94 -0
- package/examples/get_request_with_path_parameters_schema/split/k6-script.sample.ts +11 -0
- package/examples/get_request_with_path_parameters_schema/split/simpleAPI.schemas.ts +12 -0
- package/examples/get_request_with_path_parameters_schema/split/simpleAPI.ts +90 -0
- package/examples/get_request_with_path_parameters_schema/tags/default.ts +90 -0
- package/examples/get_request_with_path_parameters_schema/tags/k6-script.sample.ts +11 -0
- package/examples/get_request_with_path_parameters_schema/tags/simpleAPI.schemas.ts +12 -0
- package/examples/headers_schema/single/headerDemoAPI.ts +202 -0
- package/examples/headers_schema/{k6-script.sample.ts → single/k6-script.sample.ts} +2 -2
- package/examples/headers_schema/split/headerDemoAPI.schemas.ts +32 -0
- package/examples/headers_schema/split/headerDemoAPI.ts +184 -0
- package/examples/headers_schema/split/k6-script.sample.ts +25 -0
- package/examples/headers_schema/tags/default.ts +182 -0
- package/examples/headers_schema/tags/headerDemoAPI.schemas.ts +32 -0
- package/examples/headers_schema/tags/k6-script.sample.ts +25 -0
- package/examples/no_title_schema/{k6-script.sample.ts → single/k6-script.sample.ts} +2 -2
- package/examples/no_title_schema/single/k6Client.ts +87 -0
- package/examples/{basic_schema → no_title_schema/split}/k6-script.sample.ts +2 -2
- package/examples/no_title_schema/split/k6Client.schemas.ts +9 -0
- package/examples/no_title_schema/split/k6Client.ts +85 -0
- package/examples/no_title_schema/tags/default.ts +85 -0
- package/examples/no_title_schema/tags/k6-script.sample.ts +11 -0
- package/examples/no_title_schema/tags/k6Client.schemas.ts +9 -0
- package/examples/post_request_with_query_params/single/exampleAPI.ts +126 -0
- package/examples/post_request_with_query_params/{k6-script.sample.ts → single/k6-script.sample.ts} +2 -2
- package/examples/post_request_with_query_params/split/exampleAPI.schemas.ts +33 -0
- package/examples/post_request_with_query_params/split/exampleAPI.ts +105 -0
- package/examples/post_request_with_query_params/split/k6-script.sample.ts +14 -0
- package/examples/post_request_with_query_params/tags/default.ts +105 -0
- package/examples/post_request_with_query_params/tags/exampleAPI.schemas.ts +33 -0
- package/examples/post_request_with_query_params/tags/k6-script.sample.ts +14 -0
- package/examples/query_params_schema/single/exampleAPI.ts +120 -0
- package/examples/query_params_schema/{k6-script.sample.ts → single/k6-script.sample.ts} +2 -2
- package/examples/query_params_schema/split/exampleAPI.schemas.ts +37 -0
- package/examples/query_params_schema/split/exampleAPI.ts +94 -0
- package/examples/query_params_schema/split/k6-script.sample.ts +11 -0
- package/examples/query_params_schema/tags/default.ts +94 -0
- package/examples/query_params_schema/tags/exampleAPI.schemas.ts +37 -0
- package/examples/query_params_schema/tags/k6-script.sample.ts +11 -0
- package/examples/simple_post_request_schema/{exampleAPI.ts → single/exampleAPI.ts} +49 -49
- package/examples/simple_post_request_schema/{k6-script.sample.ts → single/k6-script.sample.ts} +2 -2
- package/examples/simple_post_request_schema/split/exampleAPI.schemas.ts +47 -0
- package/examples/simple_post_request_schema/split/exampleAPI.ts +99 -0
- package/examples/simple_post_request_schema/split/k6-script.sample.ts +13 -0
- package/examples/simple_post_request_schema/tags/default.ts +99 -0
- package/examples/simple_post_request_schema/tags/exampleAPI.schemas.ts +47 -0
- package/examples/simple_post_request_schema/tags/k6-script.sample.ts +13 -0
- package/images/openapi-to-k6.png +0 -0
- package/package.json +2 -2
- package/src/cli.ts +28 -2
- package/src/constants.ts +7 -1
- package/src/{generator.ts → generator/index.ts} +8 -21
- package/src/{k6SdkClient.ts → generator/k6Client.ts} +174 -222
- package/src/type.d.ts +3 -4
- package/tests/e2e/schema.json +8 -0
- package/tests/e2e/{k6Script.ts → single/k6Script.ts} +7 -2
- package/tests/e2e/split/k6Script.ts +82 -0
- package/tests/e2e/tags/k6Script.ts +106 -0
- package/tests/functional-tests/fixtures/schemas/basic_schema.json +1 -4
- package/tests/functional-tests/fixtures/schemas/form_data_schema.json +4 -4
- package/tests/functional-tests/fixtures/schemas/form_url_encoded_data_schema.json +3 -3
- package/tests/functional-tests/fixtures/schemas/form_url_encoded_data_with_query_params_schema.json +2 -2
- package/tests/functional-tests/fixtures/schemas/get_request_with_path_parameters_schema.json +2 -2
- package/tests/functional-tests/fixtures/schemas/headers_schema.json +7 -8
- package/tests/functional-tests/fixtures/schemas/no_title_schema.json +2 -5
- package/tests/functional-tests/fixtures/schemas/post_request_with_query_params.json +3 -4
- package/tests/functional-tests/fixtures/schemas/query_params_schema.json +3 -3
- package/tests/functional-tests/fixtures/schemas/simple_post_request_schema.json +3 -5
- package/tests/functional-tests/generator.test.ts +46 -5
- package/examples/basic_schema/simpleAPI.ts +0 -87
- package/examples/form_data_schema/formDataAPI.ts +0 -115
- package/examples/form_url_encoded_data_schema/formURLEncodedAPI.ts +0 -114
- package/examples/form_url_encoded_data_with_query_params_schema/formURLEncodedAPIWithQueryParameters.ts +0 -130
- package/examples/get_request_with_path_parameters_schema/simpleAPI.ts +0 -94
- package/examples/headers_schema/headerDemoAPI.ts +0 -196
- package/examples/no_title_schema/K6Client.ts +0 -86
- package/examples/post_request_with_query_params/exampleAPI.ts +0 -124
- package/examples/query_params_schema/exampleAPI.ts +0 -118
- package/examples/update_examples.sh +0 -21
|
@@ -12,7 +12,9 @@ jobs:
|
|
|
12
12
|
- uses: actions/checkout@v4
|
|
13
13
|
- uses: actions/setup-node@v4
|
|
14
14
|
with:
|
|
15
|
-
node-version: '21
|
|
15
|
+
node-version: '21'
|
|
16
|
+
cache: 'npm'
|
|
17
|
+
cache-dependency-path: 'package-lock.json'
|
|
16
18
|
- run: npm ci
|
|
17
19
|
- run: npm run lint
|
|
18
20
|
- run: npm test
|
|
@@ -22,10 +24,15 @@ jobs:
|
|
|
22
24
|
- uses: actions/checkout@v4
|
|
23
25
|
- uses: actions/setup-node@v4
|
|
24
26
|
with:
|
|
25
|
-
node-version: '21
|
|
27
|
+
node-version: '21'
|
|
28
|
+
cache: 'npm'
|
|
29
|
+
cache-dependency-path: 'package-lock.json'
|
|
26
30
|
- run: npm ci
|
|
27
31
|
- name: Generate SDK for E2E tests
|
|
28
|
-
run:
|
|
32
|
+
run: |
|
|
33
|
+
npm run dev -- ./tests/e2e/schema.json ./tests/e2e/single/sdk.ts --disable-analytics --mode single
|
|
34
|
+
npm run dev -- ./tests/e2e/schema.json ./tests/e2e/split/sdk.ts --disable-analytics --mode split
|
|
35
|
+
npm run dev -- ./tests/e2e/schema.json ./tests/e2e/tags/sdk.ts --disable-analytics --mode tags
|
|
29
36
|
- name: Start Mockoon CLI
|
|
30
37
|
uses: mockoon/cli-action@v2
|
|
31
38
|
with:
|
|
@@ -35,17 +42,18 @@ jobs:
|
|
|
35
42
|
- uses: grafana/setup-k6-action@v1
|
|
36
43
|
- uses: grafana/run-k6-action@v1
|
|
37
44
|
with:
|
|
38
|
-
path: './tests/e2e
|
|
45
|
+
path: './tests/e2e/*/k6Script.ts'
|
|
39
46
|
flags: '--compatibility-mode=experimental_enhanced'
|
|
40
47
|
inspect-flags: '--compatibility-mode=experimental_enhanced'
|
|
41
48
|
run-examples:
|
|
42
49
|
runs-on: ubuntu-latest
|
|
43
|
-
needs: [test, e2e-test]
|
|
44
50
|
steps:
|
|
45
51
|
- uses: actions/checkout@v4
|
|
46
52
|
- uses: actions/setup-node@v4
|
|
47
53
|
with:
|
|
48
|
-
node-version: '21
|
|
54
|
+
node-version: '21'
|
|
55
|
+
cache: 'npm'
|
|
56
|
+
cache-dependency-path: 'package-lock.json'
|
|
49
57
|
- run: npm ci
|
|
50
58
|
- name: Update examples
|
|
51
59
|
run: npm run update-examples
|
package/README.md
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
<
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
<img
|
|
4
|
+
src="./images/openapi-to-k6.png"
|
|
5
|
+
width="600"
|
|
6
|
+
style="pointer-events: none;" />
|
|
7
|
+
<br />
|
|
8
|
+
<hr/>
|
|
9
|
+
<p align="center">⚠️</p>
|
|
10
|
+
<b>This tool is currently in the experimental stage. Expect bugs, incomplete features, and breaking changes as development progresses. Use at your own risk, and please report any issues or feedback to help us improve.</b>
|
|
11
|
+
<hr/>
|
|
12
|
+
</div>
|
|
8
13
|
|
|
9
14
|
## Overview
|
|
10
15
|
|
|
@@ -17,16 +22,46 @@ OpenAPI documentation.
|
|
|
17
22
|
|
|
18
23
|
Along with the client, it also generates a sample k6 script as an example of how to use the client.
|
|
19
24
|
|
|
20
|
-
|
|
21
|
-
|
|
25
|
+
The generated client exports a class with methods for each endpoint in the OpenAPI specification. You can create
|
|
26
|
+
a instance of the class and use the methods to call the endpoints.
|
|
22
27
|
|
|
23
|
-
To take a look at a few examples of how the generated client and sample script looks, check out the [examples](./examples) directory.
|
|
28
|
+
To take a look at a few examples of how the generated client looks and sample script looks, check out the [examples](./examples) directory.
|
|
24
29
|
|
|
25
|
-
Note: Optional usage analytics are gathered to make the tool better. To disable this, use the option
|
|
26
|
-
`--disable-analytics` or set an environment variable `DISABLE_ANALYTICS=true`.
|
|
27
30
|
|
|
28
31
|
## Getting started
|
|
29
32
|
|
|
33
|
+
1. Install the tool globally via
|
|
34
|
+
|
|
35
|
+
```shell
|
|
36
|
+
npm install -g @grafana/openapi-to-k6
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
2. To start using the tool either give path to your OpenAPI schema file or provide a URL to your Open
|
|
40
|
+
API schema and the output path where you want to generate the client files.
|
|
41
|
+
|
|
42
|
+
```shell
|
|
43
|
+
openapi-to-k6 <path-to-openapi-schema | url-to-openapi-schema> <output path>
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
This will the generate a TypeScript client and a sample k6 script in the corresponding directory.
|
|
47
|
+
|
|
48
|
+
### Options
|
|
49
|
+
|
|
50
|
+
Following are some of the configuration options supported by the tool.
|
|
51
|
+
|
|
52
|
+
1. `--mode` or ` -m`: Specify the mode to use for generating the client. Following are available options:
|
|
53
|
+
1. `single`: This is the default mode used is nothing is specified. It generated the TypeScript client as a single file with all the types and implementation in a single file.
|
|
54
|
+
2. `split`: This mode splits the types and implementation into separate files.
|
|
55
|
+
3. `tags`: This modes splits your OpenAPI schema based on the tags and generates a separate client for each tag. If a route has no tag set, it will be available in `default.ts` file.
|
|
56
|
+
|
|
57
|
+
To check how the output looks for each mode, check out the [examples](./examples) directory.
|
|
58
|
+
2. `--disable-analytics`: Disable anonymous usage analytics reporting which helping making the tool better. You can also set an environment variable `DISABLE_ANALYTICS=true` to disable the analytics.
|
|
59
|
+
3. `--disable-sample-script`: Disable the generation of sample k6 script.
|
|
60
|
+
4. `--verbose` or ` -v` : Enable verbose logging to see more detailed logging output.
|
|
61
|
+
5. `--help` or ` -h` : Show help message.
|
|
62
|
+
|
|
63
|
+
## Developing locally
|
|
64
|
+
|
|
30
65
|
1. Clone the repository
|
|
31
66
|
|
|
32
67
|
```shell
|
|
@@ -69,7 +104,7 @@ cd tests/e2e/
|
|
|
69
104
|
This will run the mock server in a docker container in background.
|
|
70
105
|
|
|
71
106
|
```shell
|
|
72
|
-
docker run -
|
|
107
|
+
docker run -v ./schema.json:/tmp/schema.json -p 3000:3000 mockoon/cli:latest -d /tmp/schema.json
|
|
73
108
|
```
|
|
74
109
|
|
|
75
110
|
3. Assuming you have already followed previous steps and have the environment set up, you can generate the SDK by using
|
|
@@ -90,4 +125,4 @@ k6 run --compatibility-mode=experimental_enhanced ./K6Script.ts
|
|
|
90
125
|
2. Install the compiled package locally by using `npm install .` or `npm install -g .`.
|
|
91
126
|
3. Use the CLI `k6-sdkgen <path-to-openapi-schema> <output path>`
|
|
92
127
|
|
|
93
|
-
Special
|
|
128
|
+
Special mention for the the open-source library [Orval](https://orval.dev/) which is used for the generation of the TypeScript client.
|
package/dist/cli.js
CHANGED
|
@@ -16,13 +16,26 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
16
16
|
const chalk_1 = __importDefault(require("chalk"));
|
|
17
17
|
const commander_1 = require("commander");
|
|
18
18
|
const analytics_1 = require("./analytics");
|
|
19
|
+
const constants_1 = require("./constants");
|
|
19
20
|
const generator_1 = __importDefault(require("./generator"));
|
|
20
21
|
const helper_1 = require("./helper");
|
|
21
22
|
const logger_1 = require("./logger");
|
|
22
23
|
const program = new commander_1.Command();
|
|
23
24
|
const packageDetails = (0, helper_1.getPackageDetails)();
|
|
25
|
+
/**
|
|
26
|
+
* Validate that the mode argument is one of the supported modes.
|
|
27
|
+
*
|
|
28
|
+
* @param {string} value - The mode value to validate
|
|
29
|
+
* @return {Mode} - The validated mode value
|
|
30
|
+
*/
|
|
31
|
+
function validateMode(value) {
|
|
32
|
+
if (!Object.values(constants_1.Mode).includes(value)) {
|
|
33
|
+
throw new commander_1.InvalidArgumentError(`Supported modes: ${Object.values(constants_1.Mode).join(', ')}`);
|
|
34
|
+
}
|
|
35
|
+
return value;
|
|
36
|
+
}
|
|
24
37
|
function generateSDK(_a) {
|
|
25
|
-
return __awaiter(this, arguments, void 0, function* ({ openApiPath, outputDir, shouldGenerateSampleK6Script, analyticsData, }) {
|
|
38
|
+
return __awaiter(this, arguments, void 0, function* ({ openApiPath, outputDir, shouldGenerateSampleK6Script, analyticsData, mode, }) {
|
|
26
39
|
logger_1.logger.logMessage('Generating TypeScript client for k6...\n');
|
|
27
40
|
logger_1.logger.logMessage(`OpenAPI schema: ${openApiPath}`);
|
|
28
41
|
logger_1.logger.logMessage(`Output: ${outputDir}\n`);
|
|
@@ -31,6 +44,7 @@ function generateSDK(_a) {
|
|
|
31
44
|
outputDir,
|
|
32
45
|
shouldGenerateSampleK6Script,
|
|
33
46
|
analyticsData,
|
|
47
|
+
mode,
|
|
34
48
|
});
|
|
35
49
|
if (shouldGenerateSampleK6Script) {
|
|
36
50
|
logger_1.logger.logMessage(`TypeScript client and sample k6 script generated successfully.`, chalk_1.default.green);
|
|
@@ -46,8 +60,9 @@ program
|
|
|
46
60
|
.version(packageDetails.version)
|
|
47
61
|
.argument('<openApiPath>', 'Path or URL for the OpenAPI schema file')
|
|
48
62
|
.argument('<outputDir>', 'Directory where the SDK should be generated')
|
|
63
|
+
.option('-m, --mode <string>', `mode to use for generating the client. Valid values - ${Object.values(constants_1.Mode).join(', ')}`, validateMode, constants_1.Mode.SINGLE)
|
|
49
64
|
.option('-v, --verbose', 'enable verbose mode to show debug logs')
|
|
50
|
-
.option('--
|
|
65
|
+
.option('--disable-sample-script', 'disable generating sample k6 script')
|
|
51
66
|
.option('--disable-analytics', 'disable anonymous usage data collection')
|
|
52
67
|
.action((openApiPath, outputDir, options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
53
68
|
let analyticsData;
|
|
@@ -75,6 +90,7 @@ program
|
|
|
75
90
|
outputDir,
|
|
76
91
|
shouldGenerateSampleK6Script: !shouldDisableSampleScript,
|
|
77
92
|
analyticsData,
|
|
93
|
+
mode: options.mode,
|
|
78
94
|
});
|
|
79
95
|
}
|
|
80
96
|
catch (error) {
|
package/dist/constants.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.K6_SCRIPT_TEMPLATE = exports.SAMPLE_K6_SCRIPT_FILE_NAME = exports.DEFAULT_SCHEMA_TITLE = void 0;
|
|
3
|
+
exports.Mode = exports.K6_SCRIPT_TEMPLATE = exports.SAMPLE_K6_SCRIPT_FILE_NAME = exports.DEFAULT_SCHEMA_TITLE = void 0;
|
|
4
4
|
exports.DEFAULT_SCHEMA_TITLE = 'K6Client';
|
|
5
5
|
exports.SAMPLE_K6_SCRIPT_FILE_NAME = 'k6-script.sample.ts';
|
|
6
6
|
exports.K6_SCRIPT_TEMPLATE = `
|
|
7
7
|
import { {{clientFunctionName}} } from '{{clientPath}}'
|
|
8
8
|
|
|
9
9
|
const baseUrl = '<BASE_URL>';
|
|
10
|
-
const client = {{clientFunctionName}}({ baseUrl })
|
|
10
|
+
const client = new {{clientFunctionName}}({ baseUrl })
|
|
11
11
|
|
|
12
12
|
export default function () {
|
|
13
13
|
{{#each clientFunctionsList}}
|
|
@@ -19,3 +19,9 @@ export default function () {
|
|
|
19
19
|
{{/each}}
|
|
20
20
|
}
|
|
21
21
|
`;
|
|
22
|
+
var Mode;
|
|
23
|
+
(function (Mode) {
|
|
24
|
+
Mode["SINGLE"] = "single";
|
|
25
|
+
Mode["SPLIT"] = "split";
|
|
26
|
+
Mode["TAGS"] = "tags";
|
|
27
|
+
})(Mode || (exports.Mode = Mode = {}));
|
|
@@ -15,10 +15,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
15
15
|
const fs_1 = __importDefault(require("fs"));
|
|
16
16
|
const orval_1 = __importDefault(require("orval"));
|
|
17
17
|
const path_1 = __importDefault(require("path"));
|
|
18
|
-
const constants_1 = require("
|
|
19
|
-
const helper_1 = require("
|
|
20
|
-
const
|
|
21
|
-
const
|
|
18
|
+
const constants_1 = require("../constants");
|
|
19
|
+
const helper_1 = require("../helper");
|
|
20
|
+
const logger_1 = require("../logger");
|
|
21
|
+
const k6Client_1 = require("./k6Client");
|
|
22
22
|
const outputOverrider = helper_1.OutputOverrider.getInstance();
|
|
23
23
|
const packageDetails = (0, helper_1.getPackageDetails)();
|
|
24
24
|
const generatedFileHeaderGenerator = (info) => {
|
|
@@ -44,10 +44,7 @@ const afterAllFilesWriteHandler = (filePaths) => __awaiter(void 0, void 0, void
|
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
});
|
|
47
|
-
exports.default = (_a) => __awaiter(void 0, [_a], void 0, function* ({ openApiPath, outputDir, shouldGenerateSampleK6Script, analyticsData, }) {
|
|
48
|
-
const schemaDetails = {
|
|
49
|
-
title: '',
|
|
50
|
-
};
|
|
47
|
+
exports.default = (_a) => __awaiter(void 0, [_a], void 0, function* ({ openApiPath, outputDir, shouldGenerateSampleK6Script, analyticsData, mode, }) {
|
|
51
48
|
/**
|
|
52
49
|
* Note!
|
|
53
50
|
* 1. override.requestOptions is not supported for the custom K6 client
|
|
@@ -58,8 +55,8 @@ exports.default = (_a) => __awaiter(void 0, [_a], void 0, function* ({ openApiPa
|
|
|
58
55
|
input: openApiPath,
|
|
59
56
|
output: {
|
|
60
57
|
target: outputDir,
|
|
61
|
-
mode:
|
|
62
|
-
client: () => (0,
|
|
58
|
+
mode: mode,
|
|
59
|
+
client: () => (0, k6Client_1.getK6ClientBuilder)(shouldGenerateSampleK6Script, analyticsData),
|
|
63
60
|
override: {
|
|
64
61
|
header: generatedFileHeaderGenerator,
|
|
65
62
|
},
|
|
@@ -70,7 +67,4 @@ exports.default = (_a) => __awaiter(void 0, [_a], void 0, function* ({ openApiPa
|
|
|
70
67
|
},
|
|
71
68
|
});
|
|
72
69
|
}));
|
|
73
|
-
if (!schemaDetails.title) {
|
|
74
|
-
logger_1.logger.warning('Could not find schema title in the OpenAPI spec. Please provide a `title` in the schema in `info` block to generate proper file names');
|
|
75
|
-
}
|
|
76
70
|
});
|
|
@@ -12,17 +12,119 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
12
12
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
13
|
};
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
-
exports.generateFooter = exports.generateK6Header = exports.generateTitle = exports.getK6Dependencies = void 0;
|
|
16
15
|
exports.getK6ClientBuilder = getK6ClientBuilder;
|
|
17
16
|
const core_1 = require("@orval/core");
|
|
18
17
|
const handlebars_1 = __importDefault(require("handlebars"));
|
|
19
18
|
const path_1 = __importDefault(require("path"));
|
|
20
|
-
const constants_1 = require("
|
|
21
|
-
const helper_1 = require("
|
|
22
|
-
const logger_1 = require("
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
19
|
+
const constants_1 = require("../constants");
|
|
20
|
+
const helper_1 = require("../helper");
|
|
21
|
+
const logger_1 = require("../logger");
|
|
22
|
+
/**
|
|
23
|
+
* In case the supplied schema does not have a title set, it will set the default title to ensure
|
|
24
|
+
* proper client generation
|
|
25
|
+
*
|
|
26
|
+
* @param context - The context object containing the schema details
|
|
27
|
+
*/
|
|
28
|
+
function _setDefaultSchemaTitle(context) {
|
|
29
|
+
const schemaDetails = context.specs[context.specKey];
|
|
30
|
+
if (schemaDetails && !schemaDetails.info.title) {
|
|
31
|
+
schemaDetails.info.title = constants_1.DEFAULT_SCHEMA_TITLE;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function _generateResponseTypeDefinition(response) {
|
|
35
|
+
let responseDataType = '';
|
|
36
|
+
if (response.definition.success &&
|
|
37
|
+
!['any', 'unknown'].includes(response.definition.success)) {
|
|
38
|
+
responseDataType += response.definition.success;
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
responseDataType += 'ResponseBody';
|
|
42
|
+
}
|
|
43
|
+
return `{
|
|
44
|
+
response: Response
|
|
45
|
+
data: ${responseDataType}
|
|
46
|
+
}`;
|
|
47
|
+
}
|
|
48
|
+
function _getRequestParametersMergerFunctionImplementation() {
|
|
49
|
+
return `/**
|
|
50
|
+
* Merges the provided request parameters with default parameters for the client.
|
|
51
|
+
*
|
|
52
|
+
* @param {Params} requestParameters - The parameters provided specifically for the request
|
|
53
|
+
* @param {Params} commonRequestParameters - Common parameters for all requests
|
|
54
|
+
* @returns {Params} - The merged parameters
|
|
55
|
+
*/
|
|
56
|
+
private _mergeRequestParameters (requestParameters?: Params, commonRequestParameters?: Params): Params {
|
|
57
|
+
return {
|
|
58
|
+
...commonRequestParameters, // Default to common parameters
|
|
59
|
+
...requestParameters, // Override with request-specific parameters
|
|
60
|
+
headers: {
|
|
61
|
+
...commonRequestParameters?.headers || {}, // Ensure headers are defined
|
|
62
|
+
...requestParameters?.headers || {},
|
|
63
|
+
},
|
|
64
|
+
cookies: {
|
|
65
|
+
...commonRequestParameters?.cookies || {}, // Ensure cookies are defined
|
|
66
|
+
...requestParameters?.cookies || {},
|
|
67
|
+
},
|
|
68
|
+
tags: {
|
|
69
|
+
...commonRequestParameters?.tags || {}, // Ensure tags are defined
|
|
70
|
+
...requestParameters?.tags || {},
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
};`;
|
|
74
|
+
}
|
|
75
|
+
const _getRequestParamsValue = ({ response, queryParams, headers, body, }) => {
|
|
76
|
+
if (!queryParams && !headers && !response.isBlob && !body.contentType) {
|
|
77
|
+
// No parameters to merge, return the request parameters directly
|
|
78
|
+
return 'mergedRequestParameters';
|
|
79
|
+
}
|
|
80
|
+
let value = '\n ...mergedRequestParameters,';
|
|
81
|
+
if (response.isBlob) {
|
|
82
|
+
value += `\n responseType: 'binary',`;
|
|
83
|
+
}
|
|
84
|
+
// Expand the headers
|
|
85
|
+
if (body.contentType || headers) {
|
|
86
|
+
let headersValue = `\n headers: {`;
|
|
87
|
+
if (body.contentType) {
|
|
88
|
+
if (body.formData) {
|
|
89
|
+
headersValue += `\n'Content-Type': '${body.contentType}; boundary=' + formData.boundary,`;
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
headersValue += `\n'Content-Type': '${body.contentType}',`;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if (headers) {
|
|
96
|
+
headersValue += `\n// In the schema, headers can be of any type like number but k6 accepts only strings as headers, hence converting all headers to string`;
|
|
97
|
+
headersValue += `\n...Object.fromEntries(Object.entries(headers || {}).map(([key, value]) => [key, String(value)])),`;
|
|
98
|
+
}
|
|
99
|
+
headersValue += `\n...mergedRequestParameters?.headers},`;
|
|
100
|
+
value += headersValue;
|
|
101
|
+
}
|
|
102
|
+
return `{${value}}`;
|
|
103
|
+
};
|
|
104
|
+
const _getK6RequestOptions = (verbOptions) => {
|
|
105
|
+
const { body, headers, queryParams, response, verb } = verbOptions;
|
|
106
|
+
let fetchBodyOption = 'undefined';
|
|
107
|
+
if (body.formData) {
|
|
108
|
+
// Use the FormData.body() method to get the body of the request
|
|
109
|
+
fetchBodyOption = 'formData.body()';
|
|
110
|
+
}
|
|
111
|
+
else if (body.formUrlEncoded || body.implementation) {
|
|
112
|
+
fetchBodyOption = `JSON.stringify(${body.implementation})`;
|
|
113
|
+
}
|
|
114
|
+
// Generate the params input for the call
|
|
115
|
+
const requestParametersValue = _getRequestParamsValue({
|
|
116
|
+
response,
|
|
117
|
+
body,
|
|
118
|
+
headers: headers === null || headers === void 0 ? void 0 : headers.schema,
|
|
119
|
+
queryParams: queryParams === null || queryParams === void 0 ? void 0 : queryParams.schema,
|
|
120
|
+
});
|
|
121
|
+
// Sample output
|
|
122
|
+
// 'GET', 'http://test.com/route', <body>, <options>
|
|
123
|
+
return `"${verb.toUpperCase()}",
|
|
124
|
+
url.toString(),
|
|
125
|
+
${fetchBodyOption},
|
|
126
|
+
${requestParametersValue}`;
|
|
127
|
+
};
|
|
26
128
|
const getK6Dependencies = () => [
|
|
27
129
|
{
|
|
28
130
|
exports: [
|
|
@@ -67,32 +169,8 @@ const getK6Dependencies = () => [
|
|
|
67
169
|
dependency: 'https://jslib.k6.io/formdata/0.0.2/index.js',
|
|
68
170
|
},
|
|
69
171
|
];
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
const specData = Object.values(context.specs);
|
|
73
|
-
let schemaTitle;
|
|
74
|
-
if (specData[0]) {
|
|
75
|
-
schemaTitle = specData[0].info.title;
|
|
76
|
-
}
|
|
77
|
-
schemaTitle !== null && schemaTitle !== void 0 ? schemaTitle : (schemaTitle = constants_1.DEFAULT_SCHEMA_TITLE);
|
|
78
|
-
return schemaTitle;
|
|
79
|
-
}
|
|
80
|
-
function _generateResponseTypeName(operationName) {
|
|
81
|
-
return `${(0, core_1.pascal)(operationName)}Response`;
|
|
82
|
-
}
|
|
83
|
-
function _generateResponseTypeDefinition(operationName, response) {
|
|
84
|
-
const typeName = _generateResponseTypeName(operationName);
|
|
85
|
-
let responseDataType = '';
|
|
86
|
-
if (response.definition.success) {
|
|
87
|
-
responseDataType += response.definition.success + ' | ';
|
|
88
|
-
}
|
|
89
|
-
responseDataType += 'ResponseBody';
|
|
90
|
-
return `export type ${typeName} = {
|
|
91
|
-
response: Response
|
|
92
|
-
data: ${responseDataType}
|
|
93
|
-
};`;
|
|
94
|
-
}
|
|
95
|
-
const generateK6Implementation = ({ headers, queryParams, operationName, response, body, props, verb, override, formData, formUrlEncoded, paramsSerializer, }, { route }, analyticsData) => {
|
|
172
|
+
const generateK6Implementation = (verbOptions, { route }, analyticsData) => {
|
|
173
|
+
const { queryParams, operationName, response, body, props, verb, formData, formUrlEncoded, } = verbOptions;
|
|
96
174
|
if (analyticsData) {
|
|
97
175
|
analyticsData.generatedRequestsCount[verb] += 1;
|
|
98
176
|
}
|
|
@@ -103,33 +181,21 @@ const generateK6Implementation = ({ headers, queryParams, operationName, respons
|
|
|
103
181
|
isFormData: true,
|
|
104
182
|
isFormUrlEncoded: false,
|
|
105
183
|
});
|
|
106
|
-
|
|
107
|
-
returnTypesToWrite.set(operationName, _generateResponseTypeDefinition(operationName, response));
|
|
108
|
-
let url = `cleanBaseUrl + \`${route}\``;
|
|
184
|
+
let url = `this.cleanBaseUrl + \`${route}\``;
|
|
109
185
|
if (queryParams) {
|
|
110
186
|
url += '+`?${new URLSearchParams(params).toString()}`';
|
|
111
187
|
}
|
|
112
188
|
const urlGeneration = `const url = new URL(${url});`;
|
|
113
|
-
const options =
|
|
114
|
-
|
|
115
|
-
body,
|
|
116
|
-
headers,
|
|
117
|
-
queryParams,
|
|
118
|
-
response,
|
|
119
|
-
verb,
|
|
120
|
-
requestOptions: override === null || override === void 0 ? void 0 : override.requestOptions,
|
|
121
|
-
paramsSerializer,
|
|
122
|
-
paramsSerializerOptions: override === null || override === void 0 ? void 0 : override.paramsSerializerOptions,
|
|
123
|
-
});
|
|
124
|
-
return `const ${operationName} = (\n ${(0, core_1.toObjectString)(props, 'implementation')} requestParameters?: Params): ${_generateResponseTypeName(operationName)} => {${bodyForm}
|
|
189
|
+
const options = _getK6RequestOptions(verbOptions);
|
|
190
|
+
return `${operationName}(\n ${(0, core_1.toObjectString)(props, 'implementation')} requestParameters?: Params): ${_generateResponseTypeDefinition(response)} {\n${bodyForm}
|
|
125
191
|
${urlGeneration}
|
|
126
|
-
const mergedRequestParameters = _mergeRequestParameters(requestParameters || {},
|
|
192
|
+
const mergedRequestParameters = this._mergeRequestParameters(requestParameters || {}, this.commonRequestParameters);
|
|
127
193
|
const response = http.request(${options});
|
|
128
194
|
let data;
|
|
129
195
|
|
|
130
196
|
try {
|
|
131
197
|
data = response.json();
|
|
132
|
-
} catch
|
|
198
|
+
} catch {
|
|
133
199
|
data = response.body;
|
|
134
200
|
}
|
|
135
201
|
return {
|
|
@@ -139,120 +205,41 @@ const generateK6Implementation = ({ headers, queryParams, operationName, respons
|
|
|
139
205
|
}
|
|
140
206
|
`;
|
|
141
207
|
};
|
|
142
|
-
const getParamsInputValue = ({ response, queryParams, headers, body, }) => {
|
|
143
|
-
if (!queryParams && !headers && !response.isBlob && !body.contentType) {
|
|
144
|
-
// No parameters to merge, return the request parameters directly
|
|
145
|
-
return 'mergedRequestParameters';
|
|
146
|
-
}
|
|
147
|
-
let value = '\n ...mergedRequestParameters,';
|
|
148
|
-
if (response.isBlob) {
|
|
149
|
-
value += `\n responseType: 'binary',`;
|
|
150
|
-
}
|
|
151
|
-
// Expand the headers
|
|
152
|
-
if (body.contentType || headers) {
|
|
153
|
-
let headersValue = `\n headers: {`;
|
|
154
|
-
if (body.contentType) {
|
|
155
|
-
if (body.formData) {
|
|
156
|
-
headersValue += `\n'Content-Type': '${body.contentType}; boundary=' + formData.boundary,`;
|
|
157
|
-
}
|
|
158
|
-
else {
|
|
159
|
-
headersValue += `\n'Content-Type': '${body.contentType}',`;
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
if (headers) {
|
|
163
|
-
headersValue += `\n...headers,`;
|
|
164
|
-
}
|
|
165
|
-
headersValue += `\n...mergedRequestParameters?.headers},`;
|
|
166
|
-
value += headersValue;
|
|
167
|
-
}
|
|
168
|
-
return `{${value}}`;
|
|
169
|
-
};
|
|
170
|
-
const getK6RequestOptions = (options) => {
|
|
171
|
-
const { body, headers, queryParams, response, verb } = options;
|
|
172
|
-
let fetchBodyOption = 'undefined';
|
|
173
|
-
if (body.formData) {
|
|
174
|
-
// Use the FormData.body() method to get the body of the request
|
|
175
|
-
fetchBodyOption = 'formData.body()';
|
|
176
|
-
}
|
|
177
|
-
else if (body.formUrlEncoded || body.implementation) {
|
|
178
|
-
fetchBodyOption = `JSON.stringify(${body.implementation})`;
|
|
179
|
-
}
|
|
180
|
-
// Generate the params input for the call
|
|
181
|
-
const paramsValue = getParamsInputValue({
|
|
182
|
-
response,
|
|
183
|
-
body,
|
|
184
|
-
headers: headers === null || headers === void 0 ? void 0 : headers.schema,
|
|
185
|
-
queryParams: queryParams === null || queryParams === void 0 ? void 0 : queryParams.schema,
|
|
186
|
-
});
|
|
187
|
-
// Sample output
|
|
188
|
-
// 'GET', 'http://test.com/route', <body>, <options>
|
|
189
|
-
return `"${verb.toUpperCase()}",
|
|
190
|
-
url.toString(),
|
|
191
|
-
${fetchBodyOption},
|
|
192
|
-
${paramsValue}`;
|
|
193
|
-
};
|
|
194
|
-
function _getRequestParametersMergerFunctionImplementation() {
|
|
195
|
-
return `/**
|
|
196
|
-
* Merges the provided request parameters with default parameters for the client.
|
|
197
|
-
*
|
|
198
|
-
* @param {Params} requestParameters - The parameters provided specifically for the request
|
|
199
|
-
* @param {Params} commonRequestParameters - Common parameters for all requests
|
|
200
|
-
* @returns {Params} - The merged parameters
|
|
201
|
-
*/
|
|
202
|
-
const _mergeRequestParameters = (requestParameters?: Params, commonRequestParameters?: Params): Params => {
|
|
203
|
-
return {
|
|
204
|
-
...commonRequestParameters, // Default to common parameters
|
|
205
|
-
...requestParameters, // Override with request-specific parameters
|
|
206
|
-
headers: {
|
|
207
|
-
...commonRequestParameters?.headers || {}, // Ensure headers are defined
|
|
208
|
-
...requestParameters?.headers || {},
|
|
209
|
-
},
|
|
210
|
-
cookies: {
|
|
211
|
-
...commonRequestParameters?.cookies || {}, // Ensure cookies are defined
|
|
212
|
-
...requestParameters?.cookies || {},
|
|
213
|
-
},
|
|
214
|
-
tags: {
|
|
215
|
-
...commonRequestParameters?.tags || {}, // Ensure tags are defined
|
|
216
|
-
...requestParameters?.tags || {},
|
|
217
|
-
},
|
|
218
|
-
};
|
|
219
|
-
};`;
|
|
220
|
-
}
|
|
221
208
|
const generateTitle = (title) => {
|
|
222
209
|
const sanTitle = (0, core_1.sanitize)(title || constants_1.DEFAULT_SCHEMA_TITLE);
|
|
223
|
-
return
|
|
210
|
+
return `${(0, core_1.pascal)(sanTitle)}Client`;
|
|
224
211
|
};
|
|
225
|
-
exports.generateTitle = generateTitle;
|
|
226
212
|
const generateK6Header = ({ title }) => {
|
|
227
|
-
const clientOptionsTypeName = `${(0, core_1.pascal)(title)}Options`;
|
|
228
213
|
return `
|
|
229
|
-
|
|
214
|
+
/**
|
|
215
|
+
* This is the base client to use for interacting with the API.
|
|
216
|
+
*/
|
|
217
|
+
export class ${title} {
|
|
218
|
+
private cleanBaseUrl: string;
|
|
219
|
+
private commonRequestParameters: Params;
|
|
220
|
+
|
|
221
|
+
constructor (clientOptions: {
|
|
230
222
|
baseUrl: string,
|
|
231
223
|
commonRequestParameters?: Params
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
*/
|
|
237
|
-
export const ${title} = (clientOptions: ${clientOptionsTypeName}) => {\n
|
|
238
|
-
const cleanBaseUrl = clientOptions.baseUrl.replace(/\\/+$/, '');\n`;
|
|
224
|
+
}) {
|
|
225
|
+
this.cleanBaseUrl = clientOptions.baseUrl.replace(/\\/+$/, '');\n
|
|
226
|
+
}\n
|
|
227
|
+
`;
|
|
239
228
|
};
|
|
240
|
-
|
|
241
|
-
const generateFooter = ({ operationNames }) => {
|
|
242
|
-
let footer = '';
|
|
243
|
-
footer += `return {${operationNames.join(',')}}};\n\n`;
|
|
244
|
-
operationNames.forEach((operationName) => {
|
|
245
|
-
if (returnTypesToWrite.has(operationName)) {
|
|
246
|
-
footer += returnTypesToWrite.get(operationName) + '\n';
|
|
247
|
-
}
|
|
248
|
-
});
|
|
229
|
+
const generateFooter = () => {
|
|
249
230
|
// Add function definition for merging request parameters
|
|
250
|
-
footer
|
|
231
|
+
const footer = `
|
|
232
|
+
|
|
233
|
+
${_getRequestParametersMergerFunctionImplementation()}
|
|
234
|
+
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
`;
|
|
251
238
|
return footer;
|
|
252
239
|
};
|
|
253
|
-
exports.generateFooter = generateFooter;
|
|
254
240
|
const k6ScriptBuilder = (verbOptions, output, context) => __awaiter(void 0, void 0, void 0, function* () {
|
|
255
|
-
|
|
241
|
+
var _a;
|
|
242
|
+
const schemaTitle = ((_a = context.specs[context.specKey]) === null || _a === void 0 ? void 0 : _a.info.title) || constants_1.DEFAULT_SCHEMA_TITLE;
|
|
256
243
|
const { path: pathOfGeneratedClient, filename, extension, } = yield (0, helper_1.getGeneratedClientPath)(output.target, schemaTitle);
|
|
257
244
|
const directoryPath = (0, helper_1.getDirectoryForPath)(pathOfGeneratedClient);
|
|
258
245
|
const generateScriptPath = path_1.default.join(directoryPath, constants_1.SAMPLE_K6_SCRIPT_FILE_NAME);
|
|
@@ -275,7 +262,7 @@ const k6ScriptBuilder = (verbOptions, output, context) => __awaiter(void 0, void
|
|
|
275
262
|
});
|
|
276
263
|
}
|
|
277
264
|
const scriptContentData = {
|
|
278
|
-
clientFunctionName:
|
|
265
|
+
clientFunctionName: generateTitle(schemaTitle),
|
|
279
266
|
clientPath: `./${filename}${extension}`,
|
|
280
267
|
clientFunctionsList,
|
|
281
268
|
};
|
|
@@ -287,11 +274,11 @@ const k6ScriptBuilder = (verbOptions, output, context) => __awaiter(void 0, void
|
|
|
287
274
|
},
|
|
288
275
|
];
|
|
289
276
|
});
|
|
290
|
-
function getK6Client(
|
|
277
|
+
function getK6Client(analyticsData) {
|
|
291
278
|
return function (verbOptions, options) {
|
|
279
|
+
_setDefaultSchemaTitle(options.context);
|
|
292
280
|
const imports = (0, core_1.generateVerbImports)(verbOptions);
|
|
293
281
|
const implementation = generateK6Implementation(verbOptions, options, analyticsData);
|
|
294
|
-
schemaDetails.title = getSchemaTitleFromContext(options.context);
|
|
295
282
|
const specData = Object.values(options.context.specs);
|
|
296
283
|
if (specData[0]) {
|
|
297
284
|
if (analyticsData) {
|
|
@@ -301,13 +288,13 @@ function getK6Client(schemaDetails, analyticsData) {
|
|
|
301
288
|
return { implementation, imports };
|
|
302
289
|
};
|
|
303
290
|
}
|
|
304
|
-
function getK6ClientBuilder(
|
|
291
|
+
function getK6ClientBuilder(shouldGenerateSampleK6Script, analyticsData) {
|
|
305
292
|
return {
|
|
306
|
-
client: getK6Client(
|
|
307
|
-
header:
|
|
308
|
-
dependencies:
|
|
309
|
-
footer:
|
|
310
|
-
title:
|
|
293
|
+
client: getK6Client(analyticsData),
|
|
294
|
+
header: generateK6Header,
|
|
295
|
+
dependencies: getK6Dependencies,
|
|
296
|
+
footer: generateFooter,
|
|
297
|
+
title: generateTitle,
|
|
311
298
|
extraFiles: shouldGenerateSampleK6Script ? k6ScriptBuilder : undefined,
|
|
312
299
|
};
|
|
313
300
|
}
|