httpsnippet-client-api 5.0.0-beta.1 → 5.0.0-beta.2
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 +5 -5
- package/dist/index.d.ts +10 -0
- package/dist/index.js +272 -0
- package/package.json +20 -14
- package/src/index.ts +308 -0
- package/tsconfig.json +12 -0
- package/src/index.js +0 -288
package/README.md
CHANGED
|
@@ -6,7 +6,6 @@ An [HTTPSnippet](https://npm.im/httpsnippet) client for generating snippets for
|
|
|
6
6
|
|
|
7
7
|
[](https://readme.io)
|
|
8
8
|
|
|
9
|
-
|
|
10
9
|
## Installation
|
|
11
10
|
|
|
12
11
|
```sh
|
|
@@ -16,10 +15,10 @@ npm install --save httpsnippet-client-api
|
|
|
16
15
|
## Usage
|
|
17
16
|
|
|
18
17
|
```js
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
import { HTTPSnippet, addTargetClient } from 'httpsnippet';
|
|
19
|
+
import client = require('httpsnippet-client-api');
|
|
21
20
|
|
|
22
|
-
|
|
21
|
+
addTargetClient('node', client);
|
|
23
22
|
|
|
24
23
|
const har = {
|
|
25
24
|
"log": {
|
|
@@ -59,7 +58,8 @@ Results in the following:
|
|
|
59
58
|
const sdk = require('api')('https://api.example.com/openapi.json');
|
|
60
59
|
|
|
61
60
|
sdk.auth('a5a220e');
|
|
62
|
-
sdk
|
|
61
|
+
sdk
|
|
62
|
+
.put('/apiKey')
|
|
63
63
|
.then(res => console.log(res))
|
|
64
64
|
.catch(err => console.error(err));
|
|
65
65
|
```
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { OASDocument } from 'oas/@types/rmoas.types';
|
|
2
|
+
import type { Client } from '@readme/httpsnippet/dist/targets/targets';
|
|
3
|
+
export interface APIOptions {
|
|
4
|
+
apiDefinition: OASDocument;
|
|
5
|
+
apiDefinitionUri: string;
|
|
6
|
+
indent?: string | false;
|
|
7
|
+
escapeBrackets?: boolean;
|
|
8
|
+
}
|
|
9
|
+
declare const client: Client<APIOptions>;
|
|
10
|
+
export default client;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __assign = (this && this.__assign) || function () {
|
|
3
|
+
__assign = Object.assign || function(t) {
|
|
4
|
+
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
5
|
+
s = arguments[i];
|
|
6
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
|
7
|
+
t[p] = s[p];
|
|
8
|
+
}
|
|
9
|
+
return t;
|
|
10
|
+
};
|
|
11
|
+
return __assign.apply(this, arguments);
|
|
12
|
+
};
|
|
13
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
14
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
15
|
+
};
|
|
16
|
+
exports.__esModule = true;
|
|
17
|
+
var stringify_object_1 = __importDefault(require("stringify-object"));
|
|
18
|
+
var code_builder_1 = require("@readme/httpsnippet/dist/helpers/code-builder");
|
|
19
|
+
var content_type_1 = __importDefault(require("content-type"));
|
|
20
|
+
var oas_1 = __importDefault(require("oas"));
|
|
21
|
+
function stringify(obj, opts) {
|
|
22
|
+
if (opts === void 0) { opts = {}; }
|
|
23
|
+
return (0, stringify_object_1["default"])(obj, __assign({ indent: ' ' }, opts));
|
|
24
|
+
}
|
|
25
|
+
function buildAuthSnippet(authKey) {
|
|
26
|
+
// Auth key will be an array for Basic auth cases.
|
|
27
|
+
if (Array.isArray(authKey)) {
|
|
28
|
+
var auth_1 = [];
|
|
29
|
+
authKey.forEach(function (token, i) {
|
|
30
|
+
// If the token part is the last part of the key and it's empty, don't add it to the snippet.
|
|
31
|
+
if (token.length === 0 && authKey.length > 1 && i === authKey.length - 1) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
auth_1.push("'".concat(token.replace(/'/g, "\\'"), "'"));
|
|
35
|
+
});
|
|
36
|
+
return "sdk.auth(".concat(auth_1.join(', '), ");");
|
|
37
|
+
}
|
|
38
|
+
return "sdk.auth('".concat(authKey.replace(/'/g, "\\'"), "');");
|
|
39
|
+
}
|
|
40
|
+
function getAuthSources(operation) {
|
|
41
|
+
var matchers = {
|
|
42
|
+
header: {},
|
|
43
|
+
query: [],
|
|
44
|
+
cookie: []
|
|
45
|
+
};
|
|
46
|
+
if (operation.getSecurity().length === 0) {
|
|
47
|
+
return matchers;
|
|
48
|
+
}
|
|
49
|
+
var security = operation.prepareSecurity();
|
|
50
|
+
Object.keys(security).forEach(function (id) {
|
|
51
|
+
security[id].forEach(function (scheme) {
|
|
52
|
+
if (scheme.type === 'http') {
|
|
53
|
+
if (scheme.scheme === 'basic') {
|
|
54
|
+
matchers.header.authorization = 'Basic';
|
|
55
|
+
}
|
|
56
|
+
else if (scheme.scheme === 'bearer') {
|
|
57
|
+
matchers.header.authorization = 'Bearer';
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
else if (scheme.type === 'oauth2') {
|
|
61
|
+
matchers.header.authorization = 'Bearer';
|
|
62
|
+
}
|
|
63
|
+
else if (scheme.type === 'apiKey') {
|
|
64
|
+
if (scheme["in"] === 'query') {
|
|
65
|
+
matchers.query.push(scheme.name);
|
|
66
|
+
}
|
|
67
|
+
else if (scheme["in"] === 'header') {
|
|
68
|
+
// The way that this asterisk header matcher works is that since this `apiKey` goes in a
|
|
69
|
+
// named header (`scheme.name`) because the header is the key, we're matching against the
|
|
70
|
+
// entire header -- counter to the way that the HTTP basic matcher above works where we
|
|
71
|
+
// match and extract the API key from everything after `Basic ` in the `Authorization`
|
|
72
|
+
// header.
|
|
73
|
+
matchers.header[scheme.name.toLowerCase()] = '*';
|
|
74
|
+
}
|
|
75
|
+
else if (scheme["in"] === 'cookie') {
|
|
76
|
+
matchers.cookie.push(scheme.name);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
return matchers;
|
|
82
|
+
}
|
|
83
|
+
var client = {
|
|
84
|
+
info: {
|
|
85
|
+
key: 'api',
|
|
86
|
+
title: 'API',
|
|
87
|
+
link: 'https://npm.im/api',
|
|
88
|
+
description: 'Automatic SDK generation from an OpenAPI definition.'
|
|
89
|
+
},
|
|
90
|
+
convert: function (source, options) {
|
|
91
|
+
var opts = __assign({}, options);
|
|
92
|
+
if (!('apiDefinitionUri' in opts)) {
|
|
93
|
+
throw new Error('This HTTP Snippet client must have an `apiDefinitionUri` option supplied to it.');
|
|
94
|
+
}
|
|
95
|
+
else if (!('apiDefinition' in opts)) {
|
|
96
|
+
throw new Error('This HTTP Snippet client must have an `apiDefinition` option supplied to it.');
|
|
97
|
+
}
|
|
98
|
+
var method = source.method.toLowerCase();
|
|
99
|
+
var oas = new oas_1["default"](opts.apiDefinition);
|
|
100
|
+
var apiDefinition = oas.getDefinition();
|
|
101
|
+
var foundOperation = oas.findOperation(source.url, method);
|
|
102
|
+
if (!foundOperation) {
|
|
103
|
+
throw new Error("Unable to locate a matching operation in the supplied `apiDefinition` for: ".concat(source.method, " ").concat(source.url));
|
|
104
|
+
}
|
|
105
|
+
var operationSlugs = foundOperation.url.slugs;
|
|
106
|
+
var operation = oas.operation(foundOperation.url.nonNormalizedPath, method);
|
|
107
|
+
var path = operation.path;
|
|
108
|
+
var authData = [];
|
|
109
|
+
var authSources = getAuthSources(operation);
|
|
110
|
+
var _a = new code_builder_1.CodeBuilder({ indent: opts.indent || ' ' }), blank = _a.blank, push = _a.push, join = _a.join;
|
|
111
|
+
// const code = new CodeBuilder(opts.indent);
|
|
112
|
+
push("const sdk = require('api')('".concat(opts.apiDefinitionUri, "');"));
|
|
113
|
+
blank();
|
|
114
|
+
// If we have multiple servers configured and our source URL differs from the stock URL that we
|
|
115
|
+
// receive from our `oas` library then the URL either has server variables contained in it (that
|
|
116
|
+
// don't match the defaults), or the OAS offers alternate server URLs and we should expose that
|
|
117
|
+
// in the generated snippet.
|
|
118
|
+
var configData = [];
|
|
119
|
+
if ((apiDefinition.servers || []).length > 1) {
|
|
120
|
+
var stockUrl = oas.url();
|
|
121
|
+
var baseUrl = source.url.replace(path, '');
|
|
122
|
+
if (baseUrl !== stockUrl) {
|
|
123
|
+
var serverVars = oas.splitVariables(baseUrl);
|
|
124
|
+
var serverUrl = serverVars ? oas.url(serverVars.selected, serverVars.variables) : baseUrl;
|
|
125
|
+
configData.push("sdk.server('".concat(serverUrl, "');"));
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
var metadata = {};
|
|
129
|
+
Object.keys(source.queryObj).forEach(function (param) {
|
|
130
|
+
if (authSources.query.includes(param)) {
|
|
131
|
+
authData.push(buildAuthSnippet(source.queryObj[param]));
|
|
132
|
+
// If this query param is part of an auth source then we don't want it doubled up in the
|
|
133
|
+
// snippet.
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
metadata[param] = source.queryObj[param];
|
|
137
|
+
});
|
|
138
|
+
Object.keys(source.cookiesObj).forEach(function (cookie) {
|
|
139
|
+
if (authSources.cookie.includes(cookie)) {
|
|
140
|
+
authData.push(buildAuthSnippet(source.cookiesObj[cookie]));
|
|
141
|
+
// If this cookie is part of an auth source then we don't want it doubled up.
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
// Note that we may have the potential to overlap any cookie that also shares the name as
|
|
145
|
+
// another metadata parameter. This problem is currently inherent to `api` and not this
|
|
146
|
+
// snippet generator.
|
|
147
|
+
metadata[cookie] = source.cookiesObj[cookie];
|
|
148
|
+
});
|
|
149
|
+
// If we have path parameters present, we should only add them in if we have an `operationId` as
|
|
150
|
+
// we don't want metadata to duplicate what we'll be setting the path in the snippet to.
|
|
151
|
+
if (operation.hasOperationId()) {
|
|
152
|
+
Array.from(Object.entries(operationSlugs)).forEach(function (_a) {
|
|
153
|
+
var param = _a[0], value = _a[1];
|
|
154
|
+
// The keys in `operationSlugs` will always be prefixed with a `:` in the `oas` library so
|
|
155
|
+
// we can safely do this substring here without asserting this context.
|
|
156
|
+
metadata[param.substring(1)] = value;
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
if (Object.keys(source.headersObj).length) {
|
|
160
|
+
var headers_1 = source.headersObj;
|
|
161
|
+
Object.keys(headers_1).forEach(function (header) {
|
|
162
|
+
// Headers in HTTPSnippet are case-insensitive so we need to add in some special handling to
|
|
163
|
+
// make sure we're able to match them properly.
|
|
164
|
+
var headerLc = header.toLowerCase();
|
|
165
|
+
if (headerLc in authSources.header) {
|
|
166
|
+
// If this header has been set up as an authentication header, let's remove it and add it
|
|
167
|
+
// into our auth data so we can build up an `.auth()` snippet for the SDK.
|
|
168
|
+
var authScheme = authSources.header[headerLc];
|
|
169
|
+
if (authScheme === '*') {
|
|
170
|
+
authData.push(buildAuthSnippet(headers_1[header]));
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
// @ts-expect-error `headers[header]` is typed improperly in HTTPSnippet.
|
|
174
|
+
var authKey = headers_1[header].replace("".concat(authSources.header[headerLc], " "), '');
|
|
175
|
+
if (authScheme.toLowerCase() === 'basic') {
|
|
176
|
+
authKey = Buffer.from(authKey, 'base64').toString('ascii');
|
|
177
|
+
authKey = authKey.split(':');
|
|
178
|
+
}
|
|
179
|
+
authData.push(buildAuthSnippet(authKey));
|
|
180
|
+
}
|
|
181
|
+
delete headers_1[header];
|
|
182
|
+
}
|
|
183
|
+
else if (headerLc === 'content-type') {
|
|
184
|
+
// `Content-Type` headers are automatically added within the SDK so we can filter them out
|
|
185
|
+
// if they don't have parameters attached to them.
|
|
186
|
+
// @ts-expect-error `headers[header]` is typed improperly in HTTPSnippet.
|
|
187
|
+
var parsedContentType = content_type_1["default"].parse(headers_1[header]);
|
|
188
|
+
if (!Object.keys(parsedContentType.parameters).length) {
|
|
189
|
+
delete headers_1[header];
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
else if (headerLc === 'accept') {
|
|
193
|
+
// If the `Accept` header here is not the default or first `Accept` header for the
|
|
194
|
+
// operations' request body then we should add it otherwise we can let the SDK handle it
|
|
195
|
+
// itself.
|
|
196
|
+
if (headers_1[header] === operation.getContentType()) {
|
|
197
|
+
delete headers_1[header];
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
if (Object.keys(headers_1).length > 0) {
|
|
202
|
+
metadata = Object.assign(metadata, headers_1);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
var body;
|
|
206
|
+
switch (source.postData.mimeType) {
|
|
207
|
+
case 'application/x-www-form-urlencoded':
|
|
208
|
+
body = source.postData.paramsObj;
|
|
209
|
+
break;
|
|
210
|
+
case 'application/json':
|
|
211
|
+
if (source.postData.jsonObj) {
|
|
212
|
+
body = source.postData.jsonObj;
|
|
213
|
+
}
|
|
214
|
+
break;
|
|
215
|
+
case 'multipart/form-data':
|
|
216
|
+
if (source.postData.params) {
|
|
217
|
+
body = {};
|
|
218
|
+
// If there's a `Content-Type` header present in the metadata, but it's for the
|
|
219
|
+
// `multipart/form-data` request then dump it off the snippet. We shouldn't offload that
|
|
220
|
+
// unnecessary bloat of multipart boundaries to the user, instead letting the SDK handle it
|
|
221
|
+
// automatically.
|
|
222
|
+
if ('content-type' in metadata && metadata['content-type'].indexOf('multipart/form-data') === 0) {
|
|
223
|
+
delete metadata['content-type'];
|
|
224
|
+
}
|
|
225
|
+
source.postData.params.forEach(function (param) {
|
|
226
|
+
if (param.fileName) {
|
|
227
|
+
body[param.name] = param.fileName;
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
body[param.name] = param.value;
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
break;
|
|
235
|
+
default:
|
|
236
|
+
if (source.postData.text) {
|
|
237
|
+
body = source.postData.text;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
var args = [];
|
|
241
|
+
var accessor = method;
|
|
242
|
+
if (operation.hasOperationId()) {
|
|
243
|
+
accessor = operation.getOperationId({ camelCase: true });
|
|
244
|
+
}
|
|
245
|
+
else {
|
|
246
|
+
// Since we're not using an operationId as our primary accessor we need to take the current
|
|
247
|
+
// operation that we're working with and transpile back our path parameters on top of it.
|
|
248
|
+
var slugs = Object.fromEntries(Object.keys(operationSlugs).map(function (slug) { return [slug.replace(/:(.*)/, '$1'), operationSlugs[slug]]; }));
|
|
249
|
+
args.push("'".concat(decodeURIComponent(oas.replaceUrl(path, slugs)), "'"));
|
|
250
|
+
}
|
|
251
|
+
// If we're going to be rendering out body params and metadata we should cut their character
|
|
252
|
+
// limit in half because we'll be rendering them in their own lines.
|
|
253
|
+
var inlineCharacterLimit = typeof body !== 'undefined' && Object.keys(metadata).length > 0 ? 40 : 80;
|
|
254
|
+
if (typeof body !== 'undefined') {
|
|
255
|
+
args.push(stringify(body, { inlineCharacterLimit: inlineCharacterLimit }));
|
|
256
|
+
}
|
|
257
|
+
if (Object.keys(metadata).length > 0) {
|
|
258
|
+
args.push(stringify(metadata, { inlineCharacterLimit: inlineCharacterLimit }));
|
|
259
|
+
}
|
|
260
|
+
if (authData.length) {
|
|
261
|
+
push(authData.join('\n'));
|
|
262
|
+
}
|
|
263
|
+
if (configData.length) {
|
|
264
|
+
push(configData.join('\n'));
|
|
265
|
+
}
|
|
266
|
+
push("sdk.".concat(accessor, "(").concat(args.join(', '), ")"));
|
|
267
|
+
push('.then(res => console.log(res))', 1);
|
|
268
|
+
push('.catch(err => console.error(err));', 1);
|
|
269
|
+
return join();
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
exports["default"] = client;
|
package/package.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "httpsnippet-client-api",
|
|
3
|
-
"version": "5.0.0-beta.
|
|
3
|
+
"version": "5.0.0-beta.2",
|
|
4
4
|
"description": "An HTTPSnippet client for generating snippets for the `api` module.",
|
|
5
|
-
"main": "
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"types": "./dist/index.d.ts",
|
|
6
7
|
"scripts": {
|
|
7
|
-
"
|
|
8
|
-
"
|
|
9
|
-
"
|
|
10
|
-
"test": "nyc mocha \"test/**/*.test.
|
|
11
|
-
"test:watch": "nyc mocha \"test/**/*.test.js\" --watch"
|
|
8
|
+
"build": "tsc",
|
|
9
|
+
"prebuild": "rm -rf dist/",
|
|
10
|
+
"prepack": "npm run build",
|
|
11
|
+
"test": "nyc mocha \"test/**/*.test.ts\""
|
|
12
12
|
},
|
|
13
13
|
"repository": {
|
|
14
14
|
"type": "git",
|
|
@@ -28,24 +28,30 @@
|
|
|
28
28
|
"stringify-object": "^3.3.0"
|
|
29
29
|
},
|
|
30
30
|
"peerDependencies": {
|
|
31
|
-
"@readme/httpsnippet": "^
|
|
31
|
+
"@readme/httpsnippet": "^4.0.3",
|
|
32
32
|
"oas": "^18.0.1"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
|
-
"@readme/eslint-config": "^8.7.3",
|
|
36
35
|
"@readme/oas-examples": "^5.4.1",
|
|
37
36
|
"@readme/openapi-parser": "^2.2.0",
|
|
38
|
-
"
|
|
37
|
+
"@types/content-type": "^1.1.5",
|
|
38
|
+
"@types/stringify-object": "^4.0.1",
|
|
39
|
+
"api": "^5.0.0-beta.2",
|
|
39
40
|
"chai": "^4.3.6",
|
|
40
|
-
"eslint": "^8.14.0",
|
|
41
41
|
"fetch-mock": "^9.11.0",
|
|
42
42
|
"isomorphic-fetch": "^3.0.0",
|
|
43
43
|
"mocha": "^10.0.0",
|
|
44
44
|
"nyc": "^15.1.0",
|
|
45
|
-
"prettier": "^2.6.2",
|
|
46
45
|
"sinon": "^14.0.0",
|
|
47
|
-
"sinon-chai": "^3.7.0"
|
|
46
|
+
"sinon-chai": "^3.7.0",
|
|
47
|
+
"typescript": "^4.7.4"
|
|
48
48
|
},
|
|
49
49
|
"prettier": "@readme/eslint-config/prettier",
|
|
50
|
-
"
|
|
50
|
+
"nyc": {
|
|
51
|
+
"exclude": [
|
|
52
|
+
"dist/",
|
|
53
|
+
"test/"
|
|
54
|
+
]
|
|
55
|
+
},
|
|
56
|
+
"gitHead": "aa738b1bf46b447afed38507d3fc49acbbad6309"
|
|
51
57
|
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
import type { Operation } from 'oas';
|
|
2
|
+
import type { HttpMethods, OASDocument } from 'oas/@types/rmoas.types';
|
|
3
|
+
import type { Client } from '@readme/httpsnippet/dist/targets/targets';
|
|
4
|
+
import stringifyObject from 'stringify-object';
|
|
5
|
+
import { CodeBuilder } from '@readme/httpsnippet/dist/helpers/code-builder';
|
|
6
|
+
import contentType from 'content-type';
|
|
7
|
+
import Oas from 'oas';
|
|
8
|
+
|
|
9
|
+
// This should really be an exported type in `oas`.
|
|
10
|
+
type SecurityType = 'Basic' | 'Bearer' | 'Query' | 'Header' | 'Cookie' | 'OAuth2' | 'http' | 'apiKey';
|
|
11
|
+
|
|
12
|
+
function stringify(obj: any, opts = {}) {
|
|
13
|
+
return stringifyObject(obj, { indent: ' ', ...opts });
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function buildAuthSnippet(authKey: string | string[]) {
|
|
17
|
+
// Auth key will be an array for Basic auth cases.
|
|
18
|
+
if (Array.isArray(authKey)) {
|
|
19
|
+
const auth: string[] = [];
|
|
20
|
+
authKey.forEach((token, i) => {
|
|
21
|
+
// If the token part is the last part of the key and it's empty, don't add it to the snippet.
|
|
22
|
+
if (token.length === 0 && authKey.length > 1 && i === authKey.length - 1) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
auth.push(`'${token.replace(/'/g, "\\'")}'`);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
return `sdk.auth(${auth.join(', ')});`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return `sdk.auth('${authKey.replace(/'/g, "\\'")}');`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function getAuthSources(operation: Operation) {
|
|
36
|
+
const matchers: { header: Record<string, string>; query: string[]; cookie: string[] } = {
|
|
37
|
+
header: {},
|
|
38
|
+
query: [],
|
|
39
|
+
cookie: [],
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
if (operation.getSecurity().length === 0) {
|
|
43
|
+
return matchers;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const security = operation.prepareSecurity();
|
|
47
|
+
Object.keys(security).forEach((id: SecurityType) => {
|
|
48
|
+
security[id].forEach(scheme => {
|
|
49
|
+
if (scheme.type === 'http') {
|
|
50
|
+
if (scheme.scheme === 'basic') {
|
|
51
|
+
matchers.header.authorization = 'Basic';
|
|
52
|
+
} else if (scheme.scheme === 'bearer') {
|
|
53
|
+
matchers.header.authorization = 'Bearer';
|
|
54
|
+
}
|
|
55
|
+
} else if (scheme.type === 'oauth2') {
|
|
56
|
+
matchers.header.authorization = 'Bearer';
|
|
57
|
+
} else if (scheme.type === 'apiKey') {
|
|
58
|
+
if (scheme.in === 'query') {
|
|
59
|
+
matchers.query.push(scheme.name);
|
|
60
|
+
} else if (scheme.in === 'header') {
|
|
61
|
+
// The way that this asterisk header matcher works is that since this `apiKey` goes in a
|
|
62
|
+
// named header (`scheme.name`) because the header is the key, we're matching against the
|
|
63
|
+
// entire header -- counter to the way that the HTTP basic matcher above works where we
|
|
64
|
+
// match and extract the API key from everything after `Basic ` in the `Authorization`
|
|
65
|
+
// header.
|
|
66
|
+
matchers.header[scheme.name.toLowerCase()] = '*';
|
|
67
|
+
} else if (scheme.in === 'cookie') {
|
|
68
|
+
matchers.cookie.push(scheme.name);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
return matchers;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface APIOptions {
|
|
78
|
+
apiDefinition: OASDocument;
|
|
79
|
+
apiDefinitionUri: string;
|
|
80
|
+
indent?: string | false;
|
|
81
|
+
escapeBrackets?: boolean;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const client: Client<APIOptions> = {
|
|
85
|
+
info: {
|
|
86
|
+
key: 'api',
|
|
87
|
+
title: 'API',
|
|
88
|
+
link: 'https://npm.im/api',
|
|
89
|
+
description: 'Automatic SDK generation from an OpenAPI definition.',
|
|
90
|
+
},
|
|
91
|
+
convert: (source, options) => {
|
|
92
|
+
const opts = {
|
|
93
|
+
...options,
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
if (!('apiDefinitionUri' in opts)) {
|
|
97
|
+
throw new Error('This HTTP Snippet client must have an `apiDefinitionUri` option supplied to it.');
|
|
98
|
+
} else if (!('apiDefinition' in opts)) {
|
|
99
|
+
throw new Error('This HTTP Snippet client must have an `apiDefinition` option supplied to it.');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const method = source.method.toLowerCase() as HttpMethods;
|
|
103
|
+
const oas = new Oas(opts.apiDefinition);
|
|
104
|
+
const apiDefinition = oas.getDefinition();
|
|
105
|
+
const foundOperation = oas.findOperation(source.url, method);
|
|
106
|
+
if (!foundOperation) {
|
|
107
|
+
throw new Error(
|
|
108
|
+
`Unable to locate a matching operation in the supplied \`apiDefinition\` for: ${source.method} ${source.url}`
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const operationSlugs = foundOperation.url.slugs;
|
|
113
|
+
const operation = oas.operation(foundOperation.url.nonNormalizedPath, method);
|
|
114
|
+
const path = operation.path;
|
|
115
|
+
const authData: string[] = [];
|
|
116
|
+
const authSources = getAuthSources(operation);
|
|
117
|
+
|
|
118
|
+
const { blank, push, join } = new CodeBuilder({ indent: opts.indent || ' ' });
|
|
119
|
+
// const code = new CodeBuilder(opts.indent);
|
|
120
|
+
|
|
121
|
+
push(`const sdk = require('api')('${opts.apiDefinitionUri}');`);
|
|
122
|
+
blank();
|
|
123
|
+
|
|
124
|
+
// If we have multiple servers configured and our source URL differs from the stock URL that we
|
|
125
|
+
// receive from our `oas` library then the URL either has server variables contained in it (that
|
|
126
|
+
// don't match the defaults), or the OAS offers alternate server URLs and we should expose that
|
|
127
|
+
// in the generated snippet.
|
|
128
|
+
const configData = [];
|
|
129
|
+
if ((apiDefinition.servers || []).length > 1) {
|
|
130
|
+
const stockUrl = oas.url();
|
|
131
|
+
const baseUrl = source.url.replace(path, '');
|
|
132
|
+
if (baseUrl !== stockUrl) {
|
|
133
|
+
const serverVars = oas.splitVariables(baseUrl);
|
|
134
|
+
const serverUrl = serverVars ? oas.url(serverVars.selected, serverVars.variables) : baseUrl;
|
|
135
|
+
|
|
136
|
+
configData.push(`sdk.server('${serverUrl}');`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
let metadata: Record<string, string | string[]> = {};
|
|
141
|
+
Object.keys(source.queryObj).forEach(param => {
|
|
142
|
+
if (authSources.query.includes(param)) {
|
|
143
|
+
authData.push(buildAuthSnippet(source.queryObj[param]));
|
|
144
|
+
|
|
145
|
+
// If this query param is part of an auth source then we don't want it doubled up in the
|
|
146
|
+
// snippet.
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
metadata[param] = source.queryObj[param];
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
Object.keys(source.cookiesObj).forEach(cookie => {
|
|
154
|
+
if (authSources.cookie.includes(cookie)) {
|
|
155
|
+
authData.push(buildAuthSnippet(source.cookiesObj[cookie]));
|
|
156
|
+
|
|
157
|
+
// If this cookie is part of an auth source then we don't want it doubled up.
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Note that we may have the potential to overlap any cookie that also shares the name as
|
|
162
|
+
// another metadata parameter. This problem is currently inherent to `api` and not this
|
|
163
|
+
// snippet generator.
|
|
164
|
+
metadata[cookie] = source.cookiesObj[cookie];
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
// If we have path parameters present, we should only add them in if we have an `operationId` as
|
|
168
|
+
// we don't want metadata to duplicate what we'll be setting the path in the snippet to.
|
|
169
|
+
if (operation.hasOperationId()) {
|
|
170
|
+
Array.from(Object.entries(operationSlugs)).forEach(([param, value]) => {
|
|
171
|
+
// The keys in `operationSlugs` will always be prefixed with a `:` in the `oas` library so
|
|
172
|
+
// we can safely do this substring here without asserting this context.
|
|
173
|
+
metadata[param.substring(1)] = value;
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (Object.keys(source.headersObj).length) {
|
|
178
|
+
const headers = source.headersObj;
|
|
179
|
+
|
|
180
|
+
Object.keys(headers).forEach(header => {
|
|
181
|
+
// Headers in HTTPSnippet are case-insensitive so we need to add in some special handling to
|
|
182
|
+
// make sure we're able to match them properly.
|
|
183
|
+
const headerLc = header.toLowerCase();
|
|
184
|
+
|
|
185
|
+
if (headerLc in authSources.header) {
|
|
186
|
+
// If this header has been set up as an authentication header, let's remove it and add it
|
|
187
|
+
// into our auth data so we can build up an `.auth()` snippet for the SDK.
|
|
188
|
+
const authScheme = authSources.header[headerLc];
|
|
189
|
+
if (authScheme === '*') {
|
|
190
|
+
authData.push(buildAuthSnippet(headers[header]));
|
|
191
|
+
} else {
|
|
192
|
+
// @ts-expect-error `headers[header]` is typed improperly in HTTPSnippet.
|
|
193
|
+
let authKey = headers[header].replace(`${authSources.header[headerLc]} `, '');
|
|
194
|
+
if (authScheme.toLowerCase() === 'basic') {
|
|
195
|
+
authKey = Buffer.from(authKey, 'base64').toString('ascii');
|
|
196
|
+
authKey = authKey.split(':');
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
authData.push(buildAuthSnippet(authKey));
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
delete headers[header];
|
|
203
|
+
} else if (headerLc === 'content-type') {
|
|
204
|
+
// `Content-Type` headers are automatically added within the SDK so we can filter them out
|
|
205
|
+
// if they don't have parameters attached to them.
|
|
206
|
+
// @ts-expect-error `headers[header]` is typed improperly in HTTPSnippet.
|
|
207
|
+
const parsedContentType = contentType.parse(headers[header]);
|
|
208
|
+
if (!Object.keys(parsedContentType.parameters).length) {
|
|
209
|
+
delete headers[header];
|
|
210
|
+
}
|
|
211
|
+
} else if (headerLc === 'accept') {
|
|
212
|
+
// If the `Accept` header here is not the default or first `Accept` header for the
|
|
213
|
+
// operations' request body then we should add it otherwise we can let the SDK handle it
|
|
214
|
+
// itself.
|
|
215
|
+
if (headers[header] === operation.getContentType()) {
|
|
216
|
+
delete headers[header];
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
if (Object.keys(headers).length > 0) {
|
|
222
|
+
metadata = Object.assign(metadata, headers);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
let body: any;
|
|
227
|
+
switch (source.postData.mimeType) {
|
|
228
|
+
case 'application/x-www-form-urlencoded':
|
|
229
|
+
body = source.postData.paramsObj;
|
|
230
|
+
break;
|
|
231
|
+
|
|
232
|
+
case 'application/json':
|
|
233
|
+
if (source.postData.jsonObj) {
|
|
234
|
+
body = source.postData.jsonObj;
|
|
235
|
+
}
|
|
236
|
+
break;
|
|
237
|
+
|
|
238
|
+
case 'multipart/form-data':
|
|
239
|
+
if (source.postData.params) {
|
|
240
|
+
body = {};
|
|
241
|
+
|
|
242
|
+
// If there's a `Content-Type` header present in the metadata, but it's for the
|
|
243
|
+
// `multipart/form-data` request then dump it off the snippet. We shouldn't offload that
|
|
244
|
+
// unnecessary bloat of multipart boundaries to the user, instead letting the SDK handle it
|
|
245
|
+
// automatically.
|
|
246
|
+
if ('content-type' in metadata && metadata['content-type'].indexOf('multipart/form-data') === 0) {
|
|
247
|
+
delete metadata['content-type'];
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
source.postData.params.forEach(function (param) {
|
|
251
|
+
if (param.fileName) {
|
|
252
|
+
body[param.name] = param.fileName;
|
|
253
|
+
} else {
|
|
254
|
+
body[param.name] = param.value;
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
break;
|
|
259
|
+
|
|
260
|
+
default:
|
|
261
|
+
if (source.postData.text) {
|
|
262
|
+
body = source.postData.text;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const args = [];
|
|
267
|
+
|
|
268
|
+
let accessor: string = method;
|
|
269
|
+
if (operation.hasOperationId()) {
|
|
270
|
+
accessor = operation.getOperationId({ camelCase: true });
|
|
271
|
+
} else {
|
|
272
|
+
// Since we're not using an operationId as our primary accessor we need to take the current
|
|
273
|
+
// operation that we're working with and transpile back our path parameters on top of it.
|
|
274
|
+
const slugs = Object.fromEntries(
|
|
275
|
+
Object.keys(operationSlugs).map(slug => [slug.replace(/:(.*)/, '$1'), operationSlugs[slug]])
|
|
276
|
+
);
|
|
277
|
+
|
|
278
|
+
args.push(`'${decodeURIComponent(oas.replaceUrl(path, slugs))}'`);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// If we're going to be rendering out body params and metadata we should cut their character
|
|
282
|
+
// limit in half because we'll be rendering them in their own lines.
|
|
283
|
+
const inlineCharacterLimit = typeof body !== 'undefined' && Object.keys(metadata).length > 0 ? 40 : 80;
|
|
284
|
+
if (typeof body !== 'undefined') {
|
|
285
|
+
args.push(stringify(body, { inlineCharacterLimit }));
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (Object.keys(metadata).length > 0) {
|
|
289
|
+
args.push(stringify(metadata, { inlineCharacterLimit }));
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (authData.length) {
|
|
293
|
+
push(authData.join('\n'));
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (configData.length) {
|
|
297
|
+
push(configData.join('\n'));
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
push(`sdk.${accessor}(${args.join(', ')})`);
|
|
301
|
+
push('.then(res => console.log(res))', 1);
|
|
302
|
+
push('.catch(err => console.error(err));', 1);
|
|
303
|
+
|
|
304
|
+
return join();
|
|
305
|
+
},
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
export default client;
|
package/tsconfig.json
ADDED
package/src/index.js
DELETED
|
@@ -1,288 +0,0 @@
|
|
|
1
|
-
const stringifyObject = require('stringify-object');
|
|
2
|
-
const CodeBuilder = require('@readme/httpsnippet/src/helpers/code-builder');
|
|
3
|
-
const contentType = require('content-type');
|
|
4
|
-
const Oas = require('oas').default;
|
|
5
|
-
|
|
6
|
-
function stringify(obj, opts = {}) {
|
|
7
|
-
return stringifyObject(obj, { indent: ' ', ...opts });
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
function buildAuthSnippet(authKey) {
|
|
11
|
-
// Auth key will be an array for Basic auth cases.
|
|
12
|
-
if (Array.isArray(authKey)) {
|
|
13
|
-
const auth = [];
|
|
14
|
-
authKey.forEach((token, i) => {
|
|
15
|
-
// If the token part is the last part of the key and it's empty, don't add it to the snippet.
|
|
16
|
-
if (token.length === 0 && authKey.length > 1 && i === authKey.length - 1) {
|
|
17
|
-
return;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
auth.push(`'${token.replace(/'/g, "\\'")}'`);
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
return `sdk.auth(${auth.join(', ')});`;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
return `sdk.auth('${authKey.replace(/'/g, "\\'")}');`;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function getAuthSources(operation) {
|
|
30
|
-
const matchers = {
|
|
31
|
-
header: [],
|
|
32
|
-
query: [],
|
|
33
|
-
cookie: [],
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
if (operation.getSecurity().length === 0) {
|
|
37
|
-
return matchers;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const security = operation.prepareSecurity();
|
|
41
|
-
Object.keys(security).forEach(id => {
|
|
42
|
-
security[id].forEach(scheme => {
|
|
43
|
-
if (scheme.type === 'http') {
|
|
44
|
-
if (scheme.scheme === 'basic') {
|
|
45
|
-
matchers.header.authorization = 'Basic';
|
|
46
|
-
} else if (scheme.scheme === 'bearer') {
|
|
47
|
-
matchers.header.authorization = 'Bearer';
|
|
48
|
-
}
|
|
49
|
-
} else if (scheme.type === 'oauth2') {
|
|
50
|
-
matchers.header.authorization = 'Bearer';
|
|
51
|
-
} else if (scheme.type === 'apiKey') {
|
|
52
|
-
if (scheme.in === 'query') {
|
|
53
|
-
matchers.query.push(scheme.name);
|
|
54
|
-
} else if (scheme.in === 'header') {
|
|
55
|
-
// The way that this asterisk header matcher works is that since this `apiKey` goes in a
|
|
56
|
-
// named header (`scheme.name`) because the header is the key, we're matching against the
|
|
57
|
-
// entire header -- counter to the way that the HTTP basic matcher above works where we
|
|
58
|
-
// match and extract the API key from everything after `Basic ` in the `Authorization`
|
|
59
|
-
// header.
|
|
60
|
-
matchers.header[scheme.name.toLowerCase()] = '*';
|
|
61
|
-
} else if (scheme.in === 'cookie') {
|
|
62
|
-
matchers.cookie.push(scheme.name);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
});
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
return matchers;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
module.exports = function (source, options) {
|
|
72
|
-
const opts = { indent: ' ', ...options };
|
|
73
|
-
|
|
74
|
-
if (!('apiDefinitionUri' in opts)) {
|
|
75
|
-
throw new Error('This HTTP Snippet client must have an `apiDefinitionUri` option supplied to it.');
|
|
76
|
-
} else if (!('apiDefinition' in opts)) {
|
|
77
|
-
throw new Error('This HTTP Snippet client must have an `apiDefinition` option supplied to it.');
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const method = source.method.toLowerCase();
|
|
81
|
-
const oas = new Oas(opts.apiDefinition);
|
|
82
|
-
const apiDefinition = oas.getDefinition();
|
|
83
|
-
const foundOperation = oas.findOperation(source.url, method);
|
|
84
|
-
if (!foundOperation) {
|
|
85
|
-
throw new Error(
|
|
86
|
-
`Unable to locate a matching operation in the supplied \`apiDefinition\` for: ${source.method} ${source.url}`
|
|
87
|
-
);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const operationSlugs = foundOperation.url.slugs;
|
|
91
|
-
const operation = oas.operation(foundOperation.url.nonNormalizedPath, method);
|
|
92
|
-
const path = operation.path;
|
|
93
|
-
const authData = [];
|
|
94
|
-
const authSources = getAuthSources(operation);
|
|
95
|
-
|
|
96
|
-
const code = new CodeBuilder(opts.indent);
|
|
97
|
-
|
|
98
|
-
code.push(`const sdk = require('api')('${opts.apiDefinitionUri}');`);
|
|
99
|
-
code.blank();
|
|
100
|
-
|
|
101
|
-
// If we have multiple servers configured and our source URL differs from the stock URL that we
|
|
102
|
-
// receive from our `oas` library then the URL either has server variables contained in it (that
|
|
103
|
-
// don't match the defaults), or the OAS offers alternate server URLs and we should expose that
|
|
104
|
-
// in the generated snippet.
|
|
105
|
-
const configData = [];
|
|
106
|
-
if ((apiDefinition.servers || []).length > 1) {
|
|
107
|
-
const stockUrl = oas.url();
|
|
108
|
-
const baseUrl = source.url.replace(path, '');
|
|
109
|
-
if (baseUrl !== stockUrl) {
|
|
110
|
-
const serverVars = oas.splitVariables(baseUrl);
|
|
111
|
-
const serverUrl = serverVars ? oas.url(serverVars.selected, serverVars.variables) : baseUrl;
|
|
112
|
-
|
|
113
|
-
configData.push(`sdk.server('${serverUrl}');`);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
let metadata = {};
|
|
118
|
-
Object.keys(source.queryObj).forEach(param => {
|
|
119
|
-
if (authSources.query.includes(param)) {
|
|
120
|
-
authData.push(buildAuthSnippet(source.queryObj[param]));
|
|
121
|
-
|
|
122
|
-
// If this query param is part of an auth source then we don't want it doubled up in the
|
|
123
|
-
// snippet.
|
|
124
|
-
return;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
metadata[param] = source.queryObj[param];
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
Object.keys(source.cookiesObj).forEach(cookie => {
|
|
131
|
-
if (authSources.cookie.includes(cookie)) {
|
|
132
|
-
authData.push(buildAuthSnippet(source.cookiesObj[cookie]));
|
|
133
|
-
|
|
134
|
-
// If this cookie is part of an auth source then we don't want it doubled up.
|
|
135
|
-
return;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// Note that we may have the potential to overlap any cookie that also shares the name as
|
|
139
|
-
// another metadata parameter. This problem is currently inherent to `api` and not this
|
|
140
|
-
// snippet generator.
|
|
141
|
-
metadata[cookie] = source.cookiesObj[cookie];
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
// If we have path parameters present, we should only add them in if we have an `operationId` as
|
|
145
|
-
// we don't want metadata to duplicate what we'll be setting the path in the snippet to.
|
|
146
|
-
if (operation.hasOperationId()) {
|
|
147
|
-
Array.from(Object.entries(operationSlugs)).forEach(([param, value]) => {
|
|
148
|
-
// The keys in `operationSlugs` will always be prefixed with a `:` in the `oas` library so
|
|
149
|
-
// we can safely do this substring here without asserting this context.
|
|
150
|
-
metadata[param.substring(1)] = value;
|
|
151
|
-
});
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
if (Object.keys(source.headersObj).length) {
|
|
155
|
-
const headers = source.headersObj;
|
|
156
|
-
|
|
157
|
-
Object.keys(headers).forEach(header => {
|
|
158
|
-
// Headers in HTTPSnippet are case-insensitive so we need to add in some special handling to
|
|
159
|
-
// make sure we're able to match them properly.
|
|
160
|
-
const headerLc = header.toLowerCase();
|
|
161
|
-
|
|
162
|
-
if (headerLc in authSources.header) {
|
|
163
|
-
// If this header has been set up as an authentication header, let's remove it and add it
|
|
164
|
-
// into our auth data so we can build up an `.auth()` snippet for the SDK.
|
|
165
|
-
const authScheme = authSources.header[headerLc];
|
|
166
|
-
if (authScheme === '*') {
|
|
167
|
-
authData.push(buildAuthSnippet(headers[header]));
|
|
168
|
-
} else {
|
|
169
|
-
let authKey = headers[header].replace(`${authSources.header[headerLc]} `, '');
|
|
170
|
-
if (authScheme.toLowerCase() === 'basic') {
|
|
171
|
-
authKey = Buffer.from(authKey, 'base64').toString('ascii');
|
|
172
|
-
authKey = authKey.split(':');
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
authData.push(buildAuthSnippet(authKey));
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
delete headers[header];
|
|
179
|
-
} else if (headerLc === 'content-type') {
|
|
180
|
-
// `Content-Type` headers are automatically added within the SDK so we can filter them out
|
|
181
|
-
// if they don't have parameters attached to them.
|
|
182
|
-
const parsedContentType = contentType.parse(headers[header]);
|
|
183
|
-
if (!Object.keys(parsedContentType.parameters).length) {
|
|
184
|
-
delete headers[header];
|
|
185
|
-
}
|
|
186
|
-
} else if (headerLc === 'accept') {
|
|
187
|
-
// If the `Accept` header here is not the default or first `Accept` header for the
|
|
188
|
-
// operations' request body then we should add it otherwise we can let the SDK handle it
|
|
189
|
-
// itself.
|
|
190
|
-
if (headers[header] === operation.getContentType()) {
|
|
191
|
-
delete headers[header];
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
if (Object.keys(headers).length > 0) {
|
|
197
|
-
metadata = Object.assign(metadata, headers);
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
let body;
|
|
202
|
-
switch (source.postData.mimeType) {
|
|
203
|
-
case 'application/x-www-form-urlencoded':
|
|
204
|
-
body = source.postData.paramsObj;
|
|
205
|
-
break;
|
|
206
|
-
|
|
207
|
-
case 'application/json':
|
|
208
|
-
if (source.postData.jsonObj) {
|
|
209
|
-
body = source.postData.jsonObj;
|
|
210
|
-
}
|
|
211
|
-
break;
|
|
212
|
-
|
|
213
|
-
case 'multipart/form-data':
|
|
214
|
-
if (source.postData.params) {
|
|
215
|
-
body = {};
|
|
216
|
-
|
|
217
|
-
// If there's a `Content-Type` header present in the metadata, but it's for the
|
|
218
|
-
// `multipart/form-data` request then dump it off the snippet. We shouldn't offload that
|
|
219
|
-
// unnecessary bloat of multipart boundaries to the user, instead letting the SDK handle it
|
|
220
|
-
// automatically.
|
|
221
|
-
if ('content-type' in metadata && metadata['content-type'].indexOf('multipart/form-data') === 0) {
|
|
222
|
-
delete metadata['content-type'];
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
source.postData.params.forEach(function (param) {
|
|
226
|
-
if (param.fileName) {
|
|
227
|
-
body[param.name] = param.fileName;
|
|
228
|
-
} else {
|
|
229
|
-
body[param.name] = param.value;
|
|
230
|
-
}
|
|
231
|
-
});
|
|
232
|
-
}
|
|
233
|
-
break;
|
|
234
|
-
|
|
235
|
-
default:
|
|
236
|
-
if (source.postData.text) {
|
|
237
|
-
body = source.postData.text;
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
const args = [];
|
|
242
|
-
|
|
243
|
-
let accessor = method;
|
|
244
|
-
if (operation.hasOperationId()) {
|
|
245
|
-
accessor = operation.getOperationId({ camelCase: true });
|
|
246
|
-
} else {
|
|
247
|
-
// Since we're not using an operationId as our primary accessor we need to take the current
|
|
248
|
-
// operation that we're working with and transpile back our path parameters on top of it.
|
|
249
|
-
const slugs = Object.fromEntries(
|
|
250
|
-
Object.keys(operationSlugs).map(slug => [slug.replace(/:(.*)/, '$1'), operationSlugs[slug]])
|
|
251
|
-
);
|
|
252
|
-
|
|
253
|
-
args.push(`'${decodeURIComponent(oas.replaceUrl(path, slugs))}'`);
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
// If we're going to be rendering out body params and metadata we should cut their character
|
|
257
|
-
// limit in half because we'll be rendering them in their own lines.
|
|
258
|
-
const inlineCharacterLimit = typeof body !== 'undefined' && Object.keys(metadata).length > 0 ? 40 : 80;
|
|
259
|
-
if (typeof body !== 'undefined') {
|
|
260
|
-
args.push(stringify(body, { inlineCharacterLimit }));
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
if (Object.keys(metadata).length > 0) {
|
|
264
|
-
args.push(stringify(metadata, { inlineCharacterLimit }));
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
if (authData.length) {
|
|
268
|
-
code.push(authData.join('\n'));
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
if (configData.length) {
|
|
272
|
-
code.push(configData.join('\n'));
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
code
|
|
276
|
-
.push(`sdk.${accessor}(${args.join(', ')})`)
|
|
277
|
-
.push(1, '.then(res => console.log(res))')
|
|
278
|
-
.push(1, '.catch(err => console.error(err));');
|
|
279
|
-
|
|
280
|
-
return code.join();
|
|
281
|
-
};
|
|
282
|
-
|
|
283
|
-
module.exports.info = {
|
|
284
|
-
key: 'api',
|
|
285
|
-
title: 'API',
|
|
286
|
-
link: 'https://npm.im/api',
|
|
287
|
-
description: 'Automatic SDK generation from an OpenAPI definition.',
|
|
288
|
-
};
|