source: node_modules/swagger-client/lib/http/index.js

main
Last change on this file was d24f17c, checked in by Aleksandar Panovski <apano77@…>, 15 months ago

Initial commit

  • Property mode set to 100644
File size: 14.8 KB
Line 
1"use strict";
2
3var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault").default;
4exports.__esModule = true;
5exports.default = http;
6exports.encodeFormOrQuery = encodeFormOrQuery;
7exports.isFile = isFile;
8exports.makeHttp = makeHttp;
9exports.mergeInQueryOrForm = mergeInQueryOrForm;
10exports.self = void 0;
11exports.serializeHeaders = serializeHeaders;
12exports.serializeRes = serializeRes;
13exports.shouldDownloadAsText = void 0;
14var _qs = _interopRequireDefault(require("qs"));
15var _jsYaml = _interopRequireDefault(require("js-yaml"));
16require("../helpers/fetch-polyfill.node.js");
17var _styleSerializer = require("../execute/oas3/style-serializer.js");
18// For testing
19const self = exports.self = {
20 serializeRes,
21 mergeInQueryOrForm
22};
23
24// Handles fetch-like syntax and the case where there is only one object passed-in
25// (which will have the URL as a property). Also serializes the response.
26async function http(url, request = {}) {
27 if (typeof url === 'object') {
28 request = url;
29 url = request.url;
30 }
31 request.headers = request.headers || {};
32
33 // Serializes query, for convenience
34 // Should be the last thing we do, as its hard to mutate the URL with
35 // the search string, but much easier to manipulate the req.query object
36 self.mergeInQueryOrForm(request);
37
38 // Newlines in header values cause weird error messages from `window.fetch`,
39 // so let's message them out.
40 // Context: https://stackoverflow.com/a/50709178
41 if (request.headers) {
42 Object.keys(request.headers).forEach(headerName => {
43 const value = request.headers[headerName];
44 if (typeof value === 'string') {
45 request.headers[headerName] = value.replace(/\n+/g, ' ');
46 }
47 });
48 }
49
50 // Wait for the request interceptor, if it was provided
51 // WARNING: don't put anything between this and the request firing unless
52 // you have a good reason!
53 if (request.requestInterceptor) {
54 request = (await request.requestInterceptor(request)) || request;
55 }
56
57 /**
58 * For content-type=multipart/form-data remove content-type from request before fetch,
59 * so that correct one with `boundary` is set when request body is different from boundary encoded string.
60 */
61 const contentType = request.headers['content-type'] || request.headers['Content-Type'];
62 if (/multipart\/form-data/i.test(contentType)) {
63 delete request.headers['content-type'];
64 delete request.headers['Content-Type'];
65 }
66
67 // eslint-disable-next-line no-undef
68 let res;
69 try {
70 res = await (request.userFetch || fetch)(request.url, request);
71 res = await self.serializeRes(res, url, request);
72 if (request.responseInterceptor) {
73 res = (await request.responseInterceptor(res)) || res;
74 }
75 } catch (resError) {
76 if (!res) {
77 // res is completely absent, so we can't construct our own error
78 // so we'll just throw the error we got
79 throw resError;
80 }
81 const error = new Error(res.statusText || `response status is ${res.status}`);
82 error.status = res.status;
83 error.statusCode = res.status;
84 error.responseError = resError;
85 throw error;
86 }
87 if (!res.ok) {
88 const error = new Error(res.statusText || `response status is ${res.status}`);
89 error.status = res.status;
90 error.statusCode = res.status;
91 error.response = res;
92 throw error;
93 }
94 return res;
95}
96
97// exported for testing
98const shouldDownloadAsText = (contentType = '') => /(json|xml|yaml|text)\b/.test(contentType);
99exports.shouldDownloadAsText = shouldDownloadAsText;
100function parseBody(body, contentType) {
101 if (contentType && (contentType.indexOf('application/json') === 0 || contentType.indexOf('+json') > 0)) {
102 return JSON.parse(body);
103 }
104 return _jsYaml.default.load(body);
105}
106
107// Serialize the response, returns a promise with headers and the body part of the hash
108function serializeRes(oriRes, url, {
109 loadSpec = false
110} = {}) {
111 const res = {
112 ok: oriRes.ok,
113 url: oriRes.url || url,
114 status: oriRes.status,
115 statusText: oriRes.statusText,
116 headers: serializeHeaders(oriRes.headers)
117 };
118 const contentType = res.headers['content-type'];
119 const useText = loadSpec || shouldDownloadAsText(contentType);
120 const getBody = useText ? oriRes.text : oriRes.blob || oriRes.buffer;
121 return getBody.call(oriRes).then(body => {
122 res.text = body;
123 res.data = body;
124 if (useText) {
125 try {
126 const obj = parseBody(body, contentType);
127 res.body = obj;
128 res.obj = obj;
129 } catch (e) {
130 res.parseError = e;
131 }
132 }
133 return res;
134 });
135}
136function serializeHeaderValue(value) {
137 const isMulti = value.includes(', ');
138 return isMulti ? value.split(', ') : value;
139}
140
141// Serialize headers into a hash, where mutliple-headers result in an array.
142//
143// eg: Cookie: one
144// Cookie: two
145// = { Cookie: [ "one", "two" ]
146function serializeHeaders(headers = {}) {
147 if (typeof headers.entries !== 'function') return {};
148 return Array.from(headers.entries()).reduce((acc, [header, value]) => {
149 acc[header] = serializeHeaderValue(value);
150 return acc;
151 }, {});
152}
153function isFile(obj, navigatorObj) {
154 if (!navigatorObj && typeof navigator !== 'undefined') {
155 // eslint-disable-next-line no-undef
156 navigatorObj = navigator;
157 }
158 if (navigatorObj && navigatorObj.product === 'ReactNative') {
159 if (obj && typeof obj === 'object' && typeof obj.uri === 'string') {
160 return true;
161 }
162 return false;
163 }
164 if (typeof File !== 'undefined' && obj instanceof File) {
165 return true;
166 }
167 if (typeof Blob !== 'undefined' && obj instanceof Blob) {
168 return true;
169 }
170 if (ArrayBuffer.isView(obj)) {
171 return true;
172 }
173 return obj !== null && typeof obj === 'object' && typeof obj.pipe === 'function';
174}
175function isArrayOfFile(obj, navigatorObj) {
176 return Array.isArray(obj) && obj.some(v => isFile(v, navigatorObj));
177}
178const STYLE_SEPARATORS = {
179 form: ',',
180 spaceDelimited: '%20',
181 pipeDelimited: '|'
182};
183const SEPARATORS = {
184 csv: ',',
185 ssv: '%20',
186 tsv: '%09',
187 pipes: '|'
188};
189
190/**
191 * Specialized sub-class of File class, that only
192 * accepts string data and retain this data in `data`
193 * public property throughout the lifecycle of its instances.
194 *
195 * This sub-class is exclusively used only when Encoding Object
196 * is defined within the Media Type Object (OpenAPI 3.x.y).
197 */
198class FileWithData extends File {
199 constructor(data, name = '', options = {}) {
200 super([data], name, options);
201 this.data = data;
202 }
203 valueOf() {
204 return this.data;
205 }
206 toString() {
207 return this.valueOf();
208 }
209}
210
211// Formats a key-value and returns an array of key-value pairs.
212//
213// Return value example 1: [['color', 'blue']]
214// Return value example 2: [['color', 'blue,black,brown']]
215// Return value example 3: [['color', ['blue', 'black', 'brown']]]
216// Return value example 4: [['color', 'R,100,G,200,B,150']]
217// Return value example 5: [['R', '100'], ['G', '200'], ['B', '150']]
218// Return value example 6: [['color[R]', '100'], ['color[G]', '200'], ['color[B]', '150']]
219function formatKeyValue(key, input, skipEncoding = false) {
220 const {
221 collectionFormat,
222 allowEmptyValue,
223 serializationOption,
224 encoding
225 } = input;
226 // `input` can be string
227 const value = typeof input === 'object' && !Array.isArray(input) ? input.value : input;
228 const encodeFn = skipEncoding ? k => k.toString() : k => encodeURIComponent(k);
229 const encodedKey = encodeFn(key);
230 if (typeof value === 'undefined' && allowEmptyValue) {
231 return [[encodedKey, '']];
232 }
233
234 // file
235 if (isFile(value) || isArrayOfFile(value)) {
236 return [[encodedKey, value]];
237 }
238
239 // for OAS 3 Parameter Object for serialization
240 if (serializationOption) {
241 return formatKeyValueBySerializationOption(key, value, skipEncoding, serializationOption);
242 }
243
244 // for OAS 3 Encoding Object
245 if (encoding) {
246 if ([typeof encoding.style, typeof encoding.explode, typeof encoding.allowReserved].some(type => type !== 'undefined')) {
247 const {
248 style,
249 explode,
250 allowReserved
251 } = encoding;
252 return formatKeyValueBySerializationOption(key, value, skipEncoding, {
253 style,
254 explode,
255 allowReserved
256 });
257 }
258 if (typeof encoding.contentType === 'string') {
259 if (encoding.contentType.startsWith('application/json')) {
260 // if value is a string, assume value is already a JSON string
261 const json = typeof value === 'string' ? value : JSON.stringify(value);
262 const encodedJson = encodeFn(json);
263 const file = new FileWithData(encodedJson, 'blob', {
264 type: encoding.contentType
265 });
266 return [[encodedKey, file]];
267 }
268 const encodedData = encodeFn(String(value));
269 const blob = new FileWithData(encodedData, 'blob', {
270 type: encoding.contentType
271 });
272 return [[encodedKey, blob]];
273 }
274
275 // Primitive
276 if (typeof value !== 'object') {
277 return [[encodedKey, encodeFn(value)]];
278 }
279
280 // Array of primitives
281 if (Array.isArray(value) && value.every(v => typeof v !== 'object')) {
282 return [[encodedKey, value.map(encodeFn).join(',')]];
283 }
284
285 // Array or object
286 return [[encodedKey, encodeFn(JSON.stringify(value))]];
287 }
288
289 // for OAS 2 Parameter Object
290 // Primitive
291 if (typeof value !== 'object') {
292 return [[encodedKey, encodeFn(value)]];
293 }
294
295 // Array
296 if (Array.isArray(value)) {
297 if (collectionFormat === 'multi') {
298 // In case of multipart/formdata, it is used as array.
299 // Otherwise, the caller will convert it to a query by qs.stringify.
300 return [[encodedKey, value.map(encodeFn)]];
301 }
302 return [[encodedKey, value.map(encodeFn).join(SEPARATORS[collectionFormat || 'csv'])]];
303 }
304
305 // Object
306 return [[encodedKey, '']];
307}
308function formatKeyValueBySerializationOption(key, value, skipEncoding, serializationOption) {
309 const style = serializationOption.style || 'form';
310 const explode = typeof serializationOption.explode === 'undefined' ? style === 'form' : serializationOption.explode;
311 // eslint-disable-next-line no-nested-ternary
312 const escape = skipEncoding ? false : serializationOption && serializationOption.allowReserved ? 'unsafe' : 'reserved';
313 const encodeFn = v => (0, _styleSerializer.encodeDisallowedCharacters)(v, {
314 escape
315 });
316 const encodeKeyFn = skipEncoding ? k => k : k => (0, _styleSerializer.encodeDisallowedCharacters)(k, {
317 escape
318 });
319
320 // Primitive
321 if (typeof value !== 'object') {
322 return [[encodeKeyFn(key), encodeFn(value)]];
323 }
324
325 // Array
326 if (Array.isArray(value)) {
327 if (explode) {
328 // In case of multipart/formdata, it is used as array.
329 // Otherwise, the caller will convert it to a query by qs.stringify.
330 return [[encodeKeyFn(key), value.map(encodeFn)]];
331 }
332 return [[encodeKeyFn(key), value.map(encodeFn).join(STYLE_SEPARATORS[style])]];
333 }
334
335 // Object
336 if (style === 'deepObject') {
337 return Object.keys(value).map(valueKey => [encodeKeyFn(`${key}[${valueKey}]`), encodeFn(value[valueKey])]);
338 }
339 if (explode) {
340 return Object.keys(value).map(valueKey => [encodeKeyFn(valueKey), encodeFn(value[valueKey])]);
341 }
342 return [[encodeKeyFn(key), Object.keys(value).map(valueKey => [`${encodeKeyFn(valueKey)},${encodeFn(value[valueKey])}`]).join(',')]];
343}
344function buildFormData(reqForm) {
345 /**
346 * Build a new FormData instance, support array as field value
347 * OAS2.0 - when collectionFormat is multi
348 * OAS3.0 - when explode of Encoding Object is true
349 *
350 * This function explicitly handles Buffers (for backward compatibility)
351 * if provided as a values to FormData. FormData can only handle USVString
352 * or Blob.
353 *
354 * @param {Object} reqForm - ori req.form
355 * @return {FormData} - new FormData instance
356 */
357 return Object.entries(reqForm).reduce((formData, [name, input]) => {
358 // eslint-disable-next-line no-restricted-syntax
359 for (const [key, value] of formatKeyValue(name, input, true)) {
360 if (Array.isArray(value)) {
361 // eslint-disable-next-line no-restricted-syntax
362 for (const v of value) {
363 if (ArrayBuffer.isView(v)) {
364 const blob = new Blob([v]);
365 formData.append(key, blob);
366 } else {
367 formData.append(key, v);
368 }
369 }
370 } else if (ArrayBuffer.isView(value)) {
371 const blob = new Blob([value]);
372 formData.append(key, blob);
373 } else {
374 formData.append(key, value);
375 }
376 }
377 return formData;
378 }, new FormData());
379}
380
381// Encodes an object using appropriate serializer.
382function encodeFormOrQuery(data) {
383 /**
384 * Encode parameter names and values
385 * @param {Object} result - parameter names and values
386 * @param {string} parameterName - Parameter name
387 * @return {object} encoded parameter names and values
388 */
389 const encodedQuery = Object.keys(data).reduce((result, parameterName) => {
390 // eslint-disable-next-line no-restricted-syntax
391 for (const [key, value] of formatKeyValue(parameterName, data[parameterName])) {
392 if (value instanceof FileWithData) {
393 result[key] = value.valueOf();
394 } else {
395 result[key] = value;
396 }
397 }
398 return result;
399 }, {});
400 return _qs.default.stringify(encodedQuery, {
401 encode: false,
402 indices: false
403 }) || '';
404}
405
406// If the request has a `query` object, merge it into the request.url, and delete the object
407// If file and/or multipart, also create FormData instance
408function mergeInQueryOrForm(req = {}) {
409 const {
410 url = '',
411 query,
412 form
413 } = req;
414 const joinSearch = (...strs) => {
415 const search = strs.filter(a => a).join('&'); // Only truthy value
416 return search ? `?${search}` : ''; // Only add '?' if there is a str
417 };
418 if (form) {
419 const hasFile = Object.keys(form).some(key => {
420 const {
421 value
422 } = form[key];
423 return isFile(value) || isArrayOfFile(value);
424 });
425 const contentType = req.headers['content-type'] || req.headers['Content-Type'];
426 if (hasFile || /multipart\/form-data/i.test(contentType)) {
427 const formdata = buildFormData(req.form);
428 req.formdata = formdata;
429 req.body = formdata;
430 } else {
431 req.body = encodeFormOrQuery(form);
432 }
433 delete req.form;
434 }
435 if (query) {
436 const [baseUrl, oriSearch] = url.split('?');
437 let newStr = '';
438 if (oriSearch) {
439 const oriQuery = _qs.default.parse(oriSearch);
440 const keysToRemove = Object.keys(query);
441 keysToRemove.forEach(key => delete oriQuery[key]);
442 newStr = _qs.default.stringify(oriQuery, {
443 encode: true
444 });
445 }
446 const finalStr = joinSearch(newStr, encodeFormOrQuery(query));
447 req.url = baseUrl + finalStr;
448 delete req.query;
449 }
450 return req;
451}
452
453// Wrap a http function ( there are otherways to do this, consider this deprecated )
454function makeHttp(httpFn, preFetch, postFetch) {
455 postFetch = postFetch || (a => a);
456 preFetch = preFetch || (a => a);
457 return req => {
458 if (typeof req === 'string') {
459 req = {
460 url: req
461 };
462 }
463 self.mergeInQueryOrForm(req);
464 req = preFetch(req);
465 return postFetch(httpFn(req));
466 };
467}
Note: See TracBrowser for help on using the repository browser.