source: node_modules/swagger-client/es/execute/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: 12.8 KB
Line 
1import cookie from 'cookie';
2import { isPlainObject } from 'is-plain-object';
3import { url } from '@swagger-api/apidom-reference/configuration/empty';
4import { DEFAULT_BASE_URL, DEFAULT_OPENAPI_3_SERVER } from '../constants.js';
5import stockHttp, { mergeInQueryOrForm } from '../http/index.js';
6import createError from '../specmap/lib/create-error.js';
7import SWAGGER2_PARAMETER_BUILDERS from './swagger2/parameter-builders.js';
8import * as OAS3_PARAMETER_BUILDERS from './oas3/parameter-builders.js';
9import oas3BuildRequest from './oas3/build-request.js';
10import swagger2BuildRequest from './swagger2/build-request.js';
11import { getOperationRaw, idFromPathMethodLegacy } from '../helpers/index.js';
12import { isOpenAPI3 } from '../helpers/openapi-predicates.js';
13const arrayOrEmpty = ar => Array.isArray(ar) ? ar : [];
14
15/**
16 * `parseURIReference` function simulates the behavior of `node:url` parse function.
17 * New WHATWG URL API is not capable of parsing relative references natively,
18 * but can be adapter by utilizing the `base` parameter.
19 */
20const parseURIReference = uriReference => {
21 try {
22 return new URL(uriReference);
23 } catch {
24 const parsedURL = new URL(uriReference, DEFAULT_BASE_URL);
25 const pathname = String(uriReference).startsWith('/') ? parsedURL.pathname : parsedURL.pathname.substring(1);
26 return {
27 hash: parsedURL.hash,
28 host: '',
29 hostname: '',
30 href: '',
31 origin: '',
32 password: '',
33 pathname,
34 port: '',
35 protocol: '',
36 search: parsedURL.search,
37 searchParams: parsedURL.searchParams
38 };
39 }
40};
41const OperationNotFoundError = createError('OperationNotFoundError', function cb(message, extra, oriError) {
42 this.originalError = oriError;
43 Object.assign(this, extra || {});
44});
45const findParametersWithName = (name, parameters) => parameters.filter(p => p.name === name);
46
47// removes parameters that have duplicate 'in' and 'name' properties
48const deduplicateParameters = parameters => {
49 const paramsMap = {};
50 parameters.forEach(p => {
51 if (!paramsMap[p.in]) {
52 paramsMap[p.in] = {};
53 }
54 paramsMap[p.in][p.name] = p;
55 });
56 const dedupedParameters = [];
57 Object.keys(paramsMap).forEach(i => {
58 Object.keys(paramsMap[i]).forEach(p => {
59 dedupedParameters.push(paramsMap[i][p]);
60 });
61 });
62 return dedupedParameters;
63};
64
65// For stubbing in tests
66export const self = {
67 buildRequest
68};
69
70// Execute request, with the given operationId and parameters
71// pathName/method or operationId is optional
72export function execute({
73 http: userHttp,
74 fetch,
75 // This is legacy
76 spec,
77 operationId,
78 pathName,
79 method,
80 parameters,
81 securities,
82 ...extras
83}) {
84 // Provide default fetch implementation
85 const http = userHttp || fetch || stockHttp; // Default to _our_ http
86
87 if (pathName && method && !operationId) {
88 operationId = idFromPathMethodLegacy(pathName, method);
89 }
90 const request = self.buildRequest({
91 spec,
92 operationId,
93 parameters,
94 securities,
95 http,
96 ...extras
97 });
98 if (request.body && (isPlainObject(request.body) || Array.isArray(request.body))) {
99 request.body = JSON.stringify(request.body);
100 }
101
102 // Build request and execute it
103 return http(request);
104}
105
106// Build a request, which can be handled by the `http.js` implementation.
107export function buildRequest(options) {
108 const {
109 spec,
110 operationId,
111 responseContentType,
112 scheme,
113 requestInterceptor,
114 responseInterceptor,
115 contextUrl,
116 userFetch,
117 server,
118 serverVariables,
119 http,
120 signal
121 } = options;
122 let {
123 parameters,
124 parameterBuilders
125 } = options;
126 const specIsOAS3 = isOpenAPI3(spec);
127 if (!parameterBuilders) {
128 // user did not provide custom parameter builders
129 if (specIsOAS3) {
130 parameterBuilders = OAS3_PARAMETER_BUILDERS;
131 } else {
132 parameterBuilders = SWAGGER2_PARAMETER_BUILDERS;
133 }
134 }
135
136 // Set credentials with 'http.withCredentials' value
137 const credentials = http && http.withCredentials ? 'include' : 'same-origin';
138
139 // Base Template
140 let req = {
141 url: '',
142 credentials,
143 headers: {},
144 cookies: {}
145 };
146 if (signal) {
147 req.signal = signal;
148 }
149 if (requestInterceptor) {
150 req.requestInterceptor = requestInterceptor;
151 }
152 if (responseInterceptor) {
153 req.responseInterceptor = responseInterceptor;
154 }
155 if (userFetch) {
156 req.userFetch = userFetch;
157 }
158 const operationRaw = getOperationRaw(spec, operationId);
159 if (!operationRaw) {
160 throw new OperationNotFoundError(`Operation ${operationId} not found`);
161 }
162 const {
163 operation = {},
164 method,
165 pathName
166 } = operationRaw;
167 req.url += baseUrl({
168 spec,
169 scheme,
170 contextUrl,
171 server,
172 serverVariables,
173 pathName,
174 method
175 });
176
177 // Mostly for testing
178 if (!operationId) {
179 // Not removing req.cookies causes testing issues and would
180 // change our interface, so we're always sure to remove it.
181 // See the same statement lower down in this function for
182 // more context.
183 delete req.cookies;
184 return req;
185 }
186 req.url += pathName; // Have not yet replaced the path parameters
187 req.method = `${method}`.toUpperCase();
188 parameters = parameters || {};
189 const path = spec.paths[pathName] || {};
190 if (responseContentType) {
191 req.headers.accept = responseContentType;
192 }
193 const combinedParameters = deduplicateParameters([].concat(arrayOrEmpty(operation.parameters)) // operation parameters
194 .concat(arrayOrEmpty(path.parameters))); // path parameters
195
196 // REVIEW: OAS3: have any key names or parameter shapes changed?
197 // Any new features that need to be plugged in here?
198
199 // Add values to request
200 combinedParameters.forEach(parameter => {
201 const builder = parameterBuilders[parameter.in];
202 let value;
203 if (parameter.in === 'body' && parameter.schema && parameter.schema.properties) {
204 value = parameters;
205 }
206 value = parameter && parameter.name && parameters[parameter.name];
207 if (typeof value === 'undefined') {
208 // check for `name-in` formatted key
209 value = parameter && parameter.name && parameters[`${parameter.in}.${parameter.name}`];
210 } else if (findParametersWithName(parameter.name, combinedParameters).length > 1) {
211 // value came from `parameters[parameter.name]`
212 // check to see if this is an ambiguous parameter
213 // eslint-disable-next-line no-console
214 console.warn(`Parameter '${parameter.name}' is ambiguous because the defined spec has more than one parameter with the name: '${parameter.name}' and the passed-in parameter values did not define an 'in' value.`);
215 }
216 if (value === null) {
217 return;
218 }
219 if (typeof parameter.default !== 'undefined' && typeof value === 'undefined') {
220 value = parameter.default;
221 }
222 if (typeof value === 'undefined' && parameter.required && !parameter.allowEmptyValue) {
223 throw new Error(`Required parameter ${parameter.name} is not provided`);
224 }
225 if (specIsOAS3 && parameter.schema && parameter.schema.type === 'object' && typeof value === 'string') {
226 try {
227 value = JSON.parse(value);
228 } catch (e) {
229 throw new Error('Could not parse object parameter value string as JSON');
230 }
231 }
232 if (builder) {
233 builder({
234 req,
235 parameter,
236 value,
237 operation,
238 spec
239 });
240 }
241 });
242
243 // Do version-specific tasks, then return those results.
244 const versionSpecificOptions = {
245 ...options,
246 operation
247 };
248 if (specIsOAS3) {
249 req = oas3BuildRequest(versionSpecificOptions, req);
250 } else {
251 // If not OAS3, then treat as Swagger2.
252 req = swagger2BuildRequest(versionSpecificOptions, req);
253 }
254
255 // If the cookie convenience object exists in our request,
256 // serialize its content and then delete the cookie object.
257 if (req.cookies && Object.keys(req.cookies).length) {
258 const cookieString = Object.keys(req.cookies).reduce((prev, cookieName) => {
259 const cookieValue = req.cookies[cookieName];
260 const prefix = prev ? '&' : '';
261 const stringified = cookie.serialize(cookieName, cookieValue);
262 return prev + prefix + stringified;
263 }, '');
264 req.headers.Cookie = cookieString;
265 }
266 if (req.cookies) {
267 // even if no cookies were defined, we need to remove
268 // the cookies key from our request, or many legacy
269 // tests will break.
270 delete req.cookies;
271 }
272
273 // Will add the query object into the URL, if it exists
274 // ... will also create a FormData instance, if multipart/form-data (eg: a file)
275 mergeInQueryOrForm(req);
276 return req;
277}
278const stripNonAlpha = str => str ? str.replace(/\W/g, '') : null;
279
280// be careful when modifying this! it is a publicly-exposed method.
281export function baseUrl(obj) {
282 const specIsOAS3 = isOpenAPI3(obj.spec);
283 return specIsOAS3 ? oas3BaseUrl(obj) : swagger2BaseUrl(obj);
284}
285const isNonEmptyServerList = value => Array.isArray(value) && value.length > 0;
286function oas3BaseUrl({
287 spec,
288 pathName,
289 method,
290 server,
291 contextUrl,
292 serverVariables = {}
293}) {
294 var _spec$paths, _spec$paths2;
295 let servers = [];
296 let selectedServerUrl = '';
297 let selectedServerObj;
298
299 // compute the servers (this will be taken care of by ApiDOM refrator plugins in future
300 const operationLevelServers = spec === null || spec === void 0 || (_spec$paths = spec.paths) === null || _spec$paths === void 0 || (_spec$paths = _spec$paths[pathName]) === null || _spec$paths === void 0 || (_spec$paths = _spec$paths[(method || '').toLowerCase()]) === null || _spec$paths === void 0 ? void 0 : _spec$paths.servers;
301 const pathItemLevelServers = spec === null || spec === void 0 || (_spec$paths2 = spec.paths) === null || _spec$paths2 === void 0 || (_spec$paths2 = _spec$paths2[pathName]) === null || _spec$paths2 === void 0 ? void 0 : _spec$paths2.servers;
302 const rootLevelServers = spec === null || spec === void 0 ? void 0 : spec.servers;
303 servers = isNonEmptyServerList(operationLevelServers) // eslint-disable-line no-nested-ternary
304 ? operationLevelServers : isNonEmptyServerList(pathItemLevelServers) // eslint-disable-line no-nested-ternary
305 ? pathItemLevelServers : isNonEmptyServerList(rootLevelServers) ? rootLevelServers : [DEFAULT_OPENAPI_3_SERVER];
306
307 // pick the first server that matches the server url
308 if (server) {
309 selectedServerObj = servers.find(srv => srv.url === server);
310 if (selectedServerObj) selectedServerUrl = server;
311 }
312
313 // default to the first server if we don't have one by now
314 if (!selectedServerUrl) {
315 [selectedServerObj] = servers;
316 selectedServerUrl = selectedServerObj.url;
317 }
318 if (selectedServerUrl.includes('{')) {
319 // do variable substitution
320 const varNames = getVariableTemplateNames(selectedServerUrl);
321 varNames.forEach(variable => {
322 if (selectedServerObj.variables && selectedServerObj.variables[variable]) {
323 // variable is defined in server
324 const variableDefinition = selectedServerObj.variables[variable];
325 const variableValue = serverVariables[variable] || variableDefinition.default;
326 const re = new RegExp(`{${variable}}`, 'g');
327 selectedServerUrl = selectedServerUrl.replace(re, variableValue);
328 }
329 });
330 }
331 return buildOas3UrlWithContext(selectedServerUrl, contextUrl);
332}
333function buildOas3UrlWithContext(ourUrl = '', contextUrl = '') {
334 // relative server url should be resolved against contextUrl
335 const parsedUrl = ourUrl && contextUrl ? parseURIReference(url.resolve(contextUrl, ourUrl)) : parseURIReference(ourUrl);
336 const parsedContextUrl = parseURIReference(contextUrl);
337 const computedScheme = stripNonAlpha(parsedUrl.protocol) || stripNonAlpha(parsedContextUrl.protocol);
338 const computedHost = parsedUrl.host || parsedContextUrl.host;
339 const computedPath = parsedUrl.pathname;
340 let res;
341 if (computedScheme && computedHost) {
342 res = `${computedScheme}://${computedHost + computedPath}`;
343
344 // if last character is '/', trim it off
345 } else {
346 res = computedPath;
347 }
348 return res[res.length - 1] === '/' ? res.slice(0, -1) : res;
349}
350function getVariableTemplateNames(str) {
351 const results = [];
352 const re = /{([^}]+)}/g;
353 let text;
354
355 // eslint-disable-next-line no-cond-assign
356 while (text = re.exec(str)) {
357 results.push(text[1]);
358 }
359 return results;
360}
361
362// Compose the baseUrl ( scheme + host + basePath )
363function swagger2BaseUrl({
364 spec,
365 scheme,
366 contextUrl = ''
367}) {
368 const parsedContextUrl = parseURIReference(contextUrl);
369 const firstSchemeInSpec = Array.isArray(spec.schemes) ? spec.schemes[0] : null;
370 const computedScheme = scheme || firstSchemeInSpec || stripNonAlpha(parsedContextUrl.protocol) || 'http';
371 const computedHost = spec.host || parsedContextUrl.host || '';
372 const computedPath = spec.basePath || '';
373 let res;
374 if (computedScheme && computedHost) {
375 // we have what we need for an absolute URL
376 res = `${computedScheme}://${computedHost + computedPath}`;
377 } else {
378 // if not, a relative URL will have to do
379 res = computedPath;
380 }
381
382 // If last character is '/', trim it off
383 return res[res.length - 1] === '/' ? res.slice(0, -1) : res;
384}
Note: See TracBrowser for help on using the repository browser.