httpsnippet-client-api 4.3.0 → 5.0.0-beta.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/LICENSE +1 -1
- package/README.md +34 -12
- package/package.json +20 -17
- package/src/index.js +60 -69
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# httpsnippet-client-api
|
|
2
2
|
|
|
3
|
-
An
|
|
3
|
+
An [HTTPSnippet](https://npm.im/httpsnippet) client for generating snippets for the [api](https://npm.im/api) module.
|
|
4
4
|
|
|
5
5
|
[](https://npm.im/api) [](https://github.com/readmeio/api)
|
|
6
6
|
|
|
@@ -21,23 +21,45 @@ const client = require('httpsnippet-client-api');
|
|
|
21
21
|
|
|
22
22
|
HTTPSnippet.addTargetClient('node', client);
|
|
23
23
|
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
24
|
+
const har = {
|
|
25
|
+
"log": {
|
|
26
|
+
"entries": [
|
|
27
|
+
{
|
|
28
|
+
"request": {
|
|
29
|
+
"cookies": [],
|
|
30
|
+
"httpVersion": "HTTP/1.1",
|
|
31
|
+
"method": "PUT",
|
|
32
|
+
"headers": [
|
|
33
|
+
{
|
|
34
|
+
"name": "X-API-KEY",
|
|
35
|
+
"value": "a5a220e"
|
|
36
|
+
}
|
|
37
|
+
],
|
|
38
|
+
"url": "https://httpbin.org/apiKey"
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
]
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const snippet = new HTTPSnippet(har);
|
|
46
|
+
const code = snippet.convert('node', 'api', {
|
|
47
|
+
apiDefinitionUri: 'https://api.example.com/openapi.json'
|
|
48
|
+
apiDefinition: {
|
|
49
|
+
/* an OpenAPI definition object */
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
console.log(code);
|
|
33
54
|
```
|
|
34
55
|
|
|
35
56
|
Results in the following:
|
|
36
57
|
|
|
37
58
|
```js
|
|
38
|
-
const sdk = require('api')('https://example.com/openapi.json');
|
|
59
|
+
const sdk = require('api')('https://api.example.com/openapi.json');
|
|
39
60
|
|
|
40
|
-
sdk.
|
|
61
|
+
sdk.auth('a5a220e');
|
|
62
|
+
sdk.put('/apiKey')
|
|
41
63
|
.then(res => console.log(res))
|
|
42
64
|
.catch(err => console.error(err));
|
|
43
65
|
```
|
package/package.json
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "httpsnippet-client-api",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "An
|
|
3
|
+
"version": "5.0.0-beta.0",
|
|
4
|
+
"description": "An HTTPSnippet client for generating snippets for the `api` module.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"lint": "eslint .",
|
|
8
8
|
"pretest": "npm run lint",
|
|
9
9
|
"prettier": "prettier --list-different --write \"./**/**.js\"",
|
|
10
|
-
"test": "
|
|
10
|
+
"test": "nyc mocha \"test/**/*.test.js\"",
|
|
11
|
+
"test:watch": "nyc mocha \"test/**/*.test.js\" --watch"
|
|
11
12
|
},
|
|
12
13
|
"repository": {
|
|
13
14
|
"type": "git",
|
|
@@ -20,29 +21,31 @@
|
|
|
20
21
|
"author": "Jon Ursenbach <jon@readme.io>",
|
|
21
22
|
"license": "MIT",
|
|
22
23
|
"engines": {
|
|
23
|
-
"node": "
|
|
24
|
+
"node": ">=14"
|
|
24
25
|
},
|
|
25
26
|
"dependencies": {
|
|
26
27
|
"content-type": "^1.0.4",
|
|
27
|
-
"path-to-regexp": "^6.1.0",
|
|
28
28
|
"stringify-object": "^3.3.0"
|
|
29
29
|
},
|
|
30
30
|
"peerDependencies": {
|
|
31
31
|
"@readme/httpsnippet": "^3.0.0",
|
|
32
|
-
"oas": "^18.1
|
|
32
|
+
"oas": "^18.0.1"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
|
-
"@readme/eslint-config": "^8.
|
|
36
|
-
"@readme/oas-examples": "^4.
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"
|
|
35
|
+
"@readme/eslint-config": "^8.7.3",
|
|
36
|
+
"@readme/oas-examples": "^5.4.1",
|
|
37
|
+
"@readme/openapi-parser": "^2.2.0",
|
|
38
|
+
"api": "^5.0.0-beta.0",
|
|
39
|
+
"chai": "^4.3.6",
|
|
40
|
+
"eslint": "^8.14.0",
|
|
41
|
+
"fetch-mock": "^9.11.0",
|
|
42
|
+
"isomorphic-fetch": "^3.0.0",
|
|
43
|
+
"mocha": "^10.0.0",
|
|
44
|
+
"nyc": "^15.1.0",
|
|
45
|
+
"prettier": "^2.6.2",
|
|
46
|
+
"sinon": "^14.0.0",
|
|
47
|
+
"sinon-chai": "^3.7.0"
|
|
40
48
|
},
|
|
41
49
|
"prettier": "@readme/eslint-config/prettier",
|
|
42
|
-
"
|
|
43
|
-
"testPathIgnorePatterns": [
|
|
44
|
-
"__tests__/__fixtures__/"
|
|
45
|
-
]
|
|
46
|
-
},
|
|
47
|
-
"gitHead": "d69e58465d8eff63aec29693f70d085247afb7ef"
|
|
50
|
+
"gitHead": "d18f7e3a1c20edaf84d0c5c2e1a64714549a4ee6"
|
|
48
51
|
}
|
package/src/index.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
const { match } = require('path-to-regexp');
|
|
2
1
|
const stringifyObject = require('stringify-object');
|
|
3
2
|
const CodeBuilder = require('@readme/httpsnippet/src/helpers/code-builder');
|
|
4
3
|
const contentType = require('content-type');
|
|
@@ -21,7 +20,7 @@ function buildAuthSnippet(authKey) {
|
|
|
21
20
|
auth.push(`'${token.replace(/'/g, "\\'")}'`);
|
|
22
21
|
});
|
|
23
22
|
|
|
24
|
-
return `sdk.auth(${auth.join(', ')})
|
|
23
|
+
return `sdk.auth(${auth.join(', ')});`;
|
|
25
24
|
}
|
|
26
25
|
|
|
27
26
|
return `sdk.auth('${authKey.replace(/'/g, "\\'")}');`;
|
|
@@ -53,7 +52,12 @@ function getAuthSources(operation) {
|
|
|
53
52
|
if (scheme.in === 'query') {
|
|
54
53
|
matchers.query.push(scheme.name);
|
|
55
54
|
} else if (scheme.in === 'header') {
|
|
56
|
-
|
|
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()] = '*';
|
|
57
61
|
} else if (scheme.in === 'cookie') {
|
|
58
62
|
matchers.cookie.push(scheme.name);
|
|
59
63
|
}
|
|
@@ -64,21 +68,6 @@ function getAuthSources(operation) {
|
|
|
64
68
|
return matchers;
|
|
65
69
|
}
|
|
66
70
|
|
|
67
|
-
function getParamsInPath(operation, path) {
|
|
68
|
-
const cleanedPath = operation.path.replace(/{(.*?)}/g, ':$1');
|
|
69
|
-
const matchStatement = match(cleanedPath, { decode: decodeURIComponent });
|
|
70
|
-
const matchResult = matchStatement(path);
|
|
71
|
-
const slugs = {};
|
|
72
|
-
|
|
73
|
-
if (matchResult && Object.keys(matchResult.params).length) {
|
|
74
|
-
Object.keys(matchResult.params).forEach(param => {
|
|
75
|
-
slugs[`${param}`] = matchResult.params[param];
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
return slugs;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
71
|
module.exports = function (source, options) {
|
|
83
72
|
const opts = { indent: ' ', ...options };
|
|
84
73
|
|
|
@@ -109,9 +98,10 @@ module.exports = function (source, options) {
|
|
|
109
98
|
code.push(`const sdk = require('api')('${opts.apiDefinitionUri}');`);
|
|
110
99
|
code.blank();
|
|
111
100
|
|
|
112
|
-
// If we have multiple servers configured and our source URL differs from the stock URL that we
|
|
113
|
-
// `oas` library then the URL either has server variables contained in it (that
|
|
114
|
-
// OAS offers alternate server URLs and we should expose that
|
|
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.
|
|
115
105
|
const configData = [];
|
|
116
106
|
if ((apiDefinition.servers || []).length > 1) {
|
|
117
107
|
const stockUrl = oas.url();
|
|
@@ -125,46 +115,53 @@ module.exports = function (source, options) {
|
|
|
125
115
|
}
|
|
126
116
|
|
|
127
117
|
let metadata = {};
|
|
128
|
-
|
|
129
|
-
|
|
118
|
+
Object.keys(source.queryObj).forEach(param => {
|
|
119
|
+
if (authSources.query.includes(param)) {
|
|
120
|
+
authData.push(buildAuthSnippet(source.queryObj[param]));
|
|
130
121
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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
|
+
}
|
|
134
126
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
});
|
|
127
|
+
metadata[param] = source.queryObj[param];
|
|
128
|
+
});
|
|
138
129
|
|
|
139
|
-
|
|
140
|
-
|
|
130
|
+
Object.keys(source.cookiesObj).forEach(cookie => {
|
|
131
|
+
if (authSources.cookie.includes(cookie)) {
|
|
132
|
+
authData.push(buildAuthSnippet(source.cookiesObj[cookie]));
|
|
141
133
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
if ('operationId' in operation.schema) {
|
|
145
|
-
const pathParams = getParamsInPath(operation, operation.path);
|
|
146
|
-
if (Object.keys(pathParams).length) {
|
|
147
|
-
Object.keys(pathParams).forEach(param => {
|
|
148
|
-
if (`:${param}` in operationSlugs) {
|
|
149
|
-
metadata[param] = operationSlugs[`:${param}`];
|
|
150
|
-
} else {
|
|
151
|
-
metadata[param] = pathParams[param];
|
|
152
|
-
}
|
|
153
|
-
});
|
|
134
|
+
// If this cookie is part of an auth source then we don't want it doubled up.
|
|
135
|
+
return;
|
|
154
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
|
+
});
|
|
155
152
|
}
|
|
156
153
|
|
|
157
154
|
if (Object.keys(source.headersObj).length) {
|
|
158
155
|
const headers = source.headersObj;
|
|
159
156
|
|
|
160
157
|
Object.keys(headers).forEach(header => {
|
|
161
|
-
// Headers in HTTPSnippet are case-insensitive so we need to add in some special handling to
|
|
162
|
-
// to match them properly.
|
|
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.
|
|
163
160
|
const headerLc = header.toLowerCase();
|
|
164
161
|
|
|
165
162
|
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
|
-
// so we can build up an `.auth()` snippet for the SDK.
|
|
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.
|
|
168
165
|
const authScheme = authSources.header[headerLc];
|
|
169
166
|
if (authScheme === '*') {
|
|
170
167
|
authData.push(buildAuthSnippet(headers[header]));
|
|
@@ -180,15 +177,16 @@ module.exports = function (source, options) {
|
|
|
180
177
|
|
|
181
178
|
delete headers[header];
|
|
182
179
|
} else if (headerLc === 'content-type') {
|
|
183
|
-
// Content-Type headers are automatically added within the SDK so we can filter them out
|
|
184
|
-
// parameters attached to them.
|
|
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.
|
|
185
182
|
const parsedContentType = contentType.parse(headers[header]);
|
|
186
183
|
if (!Object.keys(parsedContentType.parameters).length) {
|
|
187
184
|
delete headers[header];
|
|
188
185
|
}
|
|
189
186
|
} else if (headerLc === 'accept') {
|
|
190
|
-
// If the
|
|
191
|
-
// should add it
|
|
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.
|
|
192
190
|
if (headers[header] === operation.getContentType()) {
|
|
193
191
|
delete headers[header];
|
|
194
192
|
}
|
|
@@ -216,9 +214,10 @@ module.exports = function (source, options) {
|
|
|
216
214
|
if (source.postData.params) {
|
|
217
215
|
body = {};
|
|
218
216
|
|
|
219
|
-
// If there's a `Content-Type` header present in the metadata, but it's for the
|
|
220
|
-
// request then dump it off the snippet. We shouldn't offload that
|
|
221
|
-
// user, instead letting the SDK handle it
|
|
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.
|
|
222
221
|
if ('content-type' in metadata && metadata['content-type'].indexOf('multipart/form-data') === 0) {
|
|
223
222
|
delete metadata['content-type'];
|
|
224
223
|
}
|
|
@@ -242,11 +241,11 @@ module.exports = function (source, options) {
|
|
|
242
241
|
const args = [];
|
|
243
242
|
|
|
244
243
|
let accessor = method;
|
|
245
|
-
if (
|
|
246
|
-
accessor = operation.
|
|
244
|
+
if (operation.hasOperationId()) {
|
|
245
|
+
accessor = operation.getOperationId({ camelCase: true });
|
|
247
246
|
} else {
|
|
248
|
-
// Since we're not using an operationId as our primary accessor we need to take the current
|
|
249
|
-
// working with and transpile back our path parameters on top of it.
|
|
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.
|
|
250
249
|
const slugs = Object.fromEntries(
|
|
251
250
|
Object.keys(operationSlugs).map(slug => [slug.replace(/:(.*)/, '$1'), operationSlugs[slug]])
|
|
252
251
|
);
|
|
@@ -254,16 +253,8 @@ module.exports = function (source, options) {
|
|
|
254
253
|
args.push(`'${decodeURIComponent(oas.replaceUrl(path, slugs))}'`);
|
|
255
254
|
}
|
|
256
255
|
|
|
257
|
-
// If
|
|
258
|
-
//
|
|
259
|
-
if (accessor.match(/[^a-zA-Z\d\s:]/)) {
|
|
260
|
-
accessor = `['${accessor}']`;
|
|
261
|
-
} else {
|
|
262
|
-
accessor = `.${accessor}`;
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
// If we're going to be rendering out body params and metadata we should cut their character limit in half because
|
|
266
|
-
// we'll be rendering them in their own lines.
|
|
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.
|
|
267
258
|
const inlineCharacterLimit = typeof body !== 'undefined' && Object.keys(metadata).length > 0 ? 40 : 80;
|
|
268
259
|
if (typeof body !== 'undefined') {
|
|
269
260
|
args.push(stringify(body, { inlineCharacterLimit }));
|
|
@@ -282,7 +273,7 @@ module.exports = function (source, options) {
|
|
|
282
273
|
}
|
|
283
274
|
|
|
284
275
|
code
|
|
285
|
-
.push(`sdk
|
|
276
|
+
.push(`sdk.${accessor}(${args.join(', ')})`)
|
|
286
277
|
.push(1, '.then(res => console.log(res))')
|
|
287
278
|
.push(1, '.catch(err => console.error(err));');
|
|
288
279
|
|