swagger-client
Version:
SwaggerJS - a collection of interfaces for OAI specs
453 lines (428 loc) • 15.5 kB
JavaScript
;
var _interopRequireWildcard = require("@babel/runtime-corejs3/helpers/interopRequireWildcard").default;
var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault").default;
exports.__esModule = true;
exports.baseUrl = baseUrl;
exports.buildRequest = buildRequest;
exports.execute = execute;
exports.self = void 0;
var _ramda = require("ramda");
var _ramdaAdjunct = require("ramda-adjunct");
var _openapiServerUrlTemplating = require("openapi-server-url-templating");
var _apidomError = require("@swagger-api/apidom-error");
var _empty = require("@swagger-api/apidom-reference/configuration/empty");
var _constants = require("../constants.js");
var _index = _interopRequireDefault(require("../http/index.js"));
var _index2 = require("../http/serializers/request/index.js");
var _parameterBuilders = _interopRequireDefault(require("./swagger2/parameter-builders.js"));
var OAS3_PARAMETER_BUILDERS = _interopRequireWildcard(require("./oas3/parameter-builders.js"));
var _buildRequest = _interopRequireDefault(require("./oas3/build-request.js"));
var _buildRequest2 = _interopRequireDefault(require("./swagger2/build-request.js"));
var _index3 = require("../helpers/index.js");
var _openapiPredicates = require("../helpers/openapi-predicates.js");
var _cookie = require("../helpers/cookie.js");
const arrayOrEmpty = ar => Array.isArray(ar) ? ar : [];
const findObjectOrArraySchema = (schema, {
recurse = true,
depth = 1
} = {}) => {
if (!(0, _ramdaAdjunct.isPlainObject)(schema)) return undefined;
// check if the schema is an object or array type
if (schema.type === 'object' || schema.type === 'array' || Array.isArray(schema.type) && (schema.type.includes('object') || schema.type.includes('array'))) {
return schema;
}
if (depth > _constants.TRAVERSE_LIMIT) return undefined;
if (recurse) {
// traverse oneOf keyword first
const oneOfResult = Array.isArray(schema.oneOf) ? schema.oneOf.find(subschema => findObjectOrArraySchema(subschema, {
recurse,
depth: depth + 1
})) : undefined;
if (oneOfResult) return oneOfResult;
// traverse anyOf keyword second
const anyOfResult = Array.isArray(schema.anyOf) ? schema.anyOf.find(subschema => findObjectOrArraySchema(subschema, {
recurse,
depth: depth + 1
})) : undefined;
if (anyOfResult) return anyOfResult;
}
return undefined;
};
const parseJsonObjectOrArray = ({
value,
silentFail = false
}) => {
try {
const parsedValue = JSON.parse(value);
if ((0, _ramdaAdjunct.isPlainObject)(parsedValue) || Array.isArray(parsedValue)) {
return parsedValue;
}
if (!silentFail) {
throw new Error('Expected JSON serialized object or array');
}
} catch {
if (!silentFail) {
throw new Error('Could not parse parameter value string as JSON Object or JSON Array');
}
}
return value;
};
/**
* `parseURIReference` function simulates the behavior of `node:url` parse function.
* New WHATWG URL API is not capable of parsing relative references natively,
* but can be adapter by utilizing the `base` parameter.
*/
const parseURIReference = uriReference => {
try {
return new URL(uriReference);
} catch {
const parsedURL = new URL(uriReference, _constants.DEFAULT_BASE_URL);
const pathname = String(uriReference).startsWith('/') ? parsedURL.pathname : parsedURL.pathname.substring(1);
return {
hash: parsedURL.hash,
host: '',
hostname: '',
href: '',
origin: '',
password: '',
pathname,
port: '',
protocol: '',
search: parsedURL.search,
searchParams: parsedURL.searchParams
};
}
};
class OperationNotFoundError extends _apidomError.ApiDOMStructuredError {}
const findParametersWithName = (name, parameters) => parameters.filter(p => p.name === name);
// removes parameters that have duplicate 'in' and 'name' properties
const deduplicateParameters = parameters => {
const paramsMap = {};
parameters.forEach(p => {
if (!paramsMap[p.in]) {
paramsMap[p.in] = {};
}
paramsMap[p.in][p.name] = p;
});
const dedupedParameters = [];
Object.keys(paramsMap).forEach(i => {
Object.keys(paramsMap[i]).forEach(p => {
dedupedParameters.push(paramsMap[i][p]);
});
});
return dedupedParameters;
};
// For stubbing in tests
const self = exports.self = {
buildRequest
};
// Execute request, with the given operationId and parameters
// pathName/method or operationId is optional
function execute({
http: userHttp,
fetch,
// This is legacy
spec,
operationId,
pathName,
method,
parameters,
securities,
...extras
}) {
// Provide default fetch implementation
const http = userHttp || fetch || _index.default; // Default to _our_ http
if (pathName && method && !operationId) {
operationId = (0, _index3.idFromPathMethodLegacy)(pathName, method);
}
const request = self.buildRequest({
spec,
operationId,
parameters,
securities,
http,
...extras
});
if (request.body && ((0, _ramdaAdjunct.isPlainObject)(request.body) || Array.isArray(request.body))) {
request.body = JSON.stringify(request.body);
}
// Build request and execute it
return http(request);
}
// Build a request, which can be handled by the `http.js` implementation.
function buildRequest(options) {
const {
spec,
operationId,
responseContentType,
scheme,
requestInterceptor,
responseInterceptor,
contextUrl,
userFetch,
server,
serverVariables,
http,
signal,
serverVariableEncoder
} = options;
let {
parameters,
parameterBuilders,
baseURL
} = options;
const specIsOAS3 = (0, _openapiPredicates.isOpenAPI3)(spec);
if (!parameterBuilders) {
// user did not provide custom parameter builders
if (specIsOAS3) {
parameterBuilders = OAS3_PARAMETER_BUILDERS;
} else {
parameterBuilders = _parameterBuilders.default;
}
}
// Set credentials with 'http.withCredentials' value
const credentials = http && http.withCredentials ? 'include' : 'same-origin';
// Base Template
let req = {
url: '',
credentials,
headers: {},
cookies: {}
};
if (signal) {
req.signal = signal;
}
if (requestInterceptor) {
req.requestInterceptor = requestInterceptor;
}
if (responseInterceptor) {
req.responseInterceptor = responseInterceptor;
}
if (userFetch) {
req.userFetch = userFetch;
}
const operationRaw = (0, _index3.getOperationRaw)(spec, operationId);
if (!operationRaw) {
throw new OperationNotFoundError(`Operation ${operationId} not found`);
}
const {
operation = {},
method,
pathName
} = operationRaw;
baseURL = baseURL != null ? baseURL : baseUrl({
spec,
scheme,
contextUrl,
server,
serverVariables,
pathName,
method,
serverVariableEncoder
});
req.url += baseURL;
// Mostly for testing
if (!operationId) {
// Not removing req.cookies causes testing issues and would
// change our interface, so we're always sure to remove it.
// See the same statement lower down in this function for
// more context.
delete req.cookies;
return req;
}
req.url += pathName; // Have not yet replaced the path parameters
req.method = `${method}`.toUpperCase();
parameters = parameters || {};
const path = spec.paths[pathName] || {};
if (responseContentType) {
req.headers.accept = responseContentType;
}
const combinedParameters = deduplicateParameters([].concat(arrayOrEmpty(operation.parameters)) // operation parameters
.concat(arrayOrEmpty(path.parameters))); // path parameters
// REVIEW: OAS3: have any key names or parameter shapes changed?
// Any new features that need to be plugged in here?
// Add values to request
combinedParameters.forEach(parameter => {
const builder = parameterBuilders[parameter.in];
let value;
if (parameter.in === 'body' && parameter.schema && parameter.schema.properties) {
value = parameters;
}
value = parameter && parameter.name && parameters[parameter.name];
if (typeof value === 'undefined') {
// check for `name-in` formatted key
value = parameter && parameter.name && parameters[`${parameter.in}.${parameter.name}`];
} else if (findParametersWithName(parameter.name, combinedParameters).length > 1) {
// value came from `parameters[parameter.name]`
// check to see if this is an ambiguous parameter
// eslint-disable-next-line no-console
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.`);
}
if (value === null) {
return;
}
if (typeof parameter.default !== 'undefined' && typeof value === 'undefined') {
value = parameter.default;
}
if (typeof value === 'undefined' && parameter.required && !parameter.allowEmptyValue) {
throw new Error(`Required parameter ${parameter.name} is not provided`);
}
if (specIsOAS3 && typeof value === 'string') {
if ((0, _ramda.has)('type', parameter.schema) && typeof parameter.schema.type === 'string' && findObjectOrArraySchema(parameter.schema, {
recurse: false
})) {
value = parseJsonObjectOrArray({
value,
silentFail: false
});
} else if ((0, _ramda.has)('type', parameter.schema) && Array.isArray(parameter.schema.type) && findObjectOrArraySchema(parameter.schema, {
recurse: false
})) {
value = parseJsonObjectOrArray({
value,
silentFail: true
});
} else if (!(0, _ramda.has)('type', parameter.schema) && findObjectOrArraySchema(parameter.schema, {
recurse: true
})) {
value = parseJsonObjectOrArray({
value,
silentFail: true
});
}
}
if (builder) {
builder({
req,
parameter,
value,
operation,
spec,
baseURL
});
}
});
// Do version-specific tasks, then return those results.
const versionSpecificOptions = {
...options,
operation
};
if (specIsOAS3) {
req = (0, _buildRequest.default)(versionSpecificOptions, req);
} else {
// If not OAS3, then treat as Swagger2.
req = (0, _buildRequest2.default)(versionSpecificOptions, req);
}
// If the cookie convenience object exists in our request,
// serialize its content and then delete the cookie object.
if (req.cookies && Object.keys(req.cookies).length > 0) {
const cookieString = (0, _cookie.serialize)(req.cookies);
if ((0, _ramdaAdjunct.isNonEmptyString)(req.headers.Cookie)) {
req.headers.Cookie += `; ${cookieString}`;
} else {
req.headers.Cookie = cookieString;
}
}
if (req.cookies) {
// even if no cookies were defined, we need to remove
// the cookies key from our request, or many legacy
// tests will break.
delete req.cookies;
}
// Will add the query object into the URL, if it exists
// ... will also create a FormData instance, if multipart/form-data (eg: a file)
return (0, _index2.serializeRequest)(req);
}
const stripNonAlpha = str => str ? str.replace(/\W/g, '') : null;
// be careful when modifying this! it is a publicly-exposed method.
function baseUrl(obj) {
const specIsOAS3 = (0, _openapiPredicates.isOpenAPI3)(obj.spec);
return specIsOAS3 ? oas3BaseUrl(obj) : swagger2BaseUrl(obj);
}
const isNonEmptyServerList = value => Array.isArray(value) && value.length > 0;
function oas3BaseUrl({
spec,
pathName,
method,
server,
contextUrl,
serverVariables = {},
serverVariableEncoder
}) {
var _spec$paths, _spec$paths2;
let servers = [];
let selectedServerUrl = '';
let selectedServerObj;
// compute the servers (this will be taken care of by ApiDOM refrator plugins in future
const operationLevelServers = spec == null || (_spec$paths = spec.paths) == null || (_spec$paths = _spec$paths[pathName]) == null || (_spec$paths = _spec$paths[(method || '').toLowerCase()]) == null ? void 0 : _spec$paths.servers;
const pathItemLevelServers = spec == null || (_spec$paths2 = spec.paths) == null || (_spec$paths2 = _spec$paths2[pathName]) == null ? void 0 : _spec$paths2.servers;
const rootLevelServers = spec == null ? void 0 : spec.servers;
servers = isNonEmptyServerList(operationLevelServers) // eslint-disable-line no-nested-ternary
? operationLevelServers : isNonEmptyServerList(pathItemLevelServers) // eslint-disable-line no-nested-ternary
? pathItemLevelServers : isNonEmptyServerList(rootLevelServers) ? rootLevelServers : [_constants.DEFAULT_OPENAPI_3_SERVER];
// pick the first server that matches the server url
if (server) {
selectedServerObj = servers.find(srv => srv.url === server);
if (selectedServerObj) selectedServerUrl = server;
}
// default to the first server if we don't have one by now
if (!selectedServerUrl) {
[selectedServerObj] = servers;
selectedServerUrl = selectedServerObj.url;
}
if ((0, _openapiServerUrlTemplating.test)(selectedServerUrl, {
strict: true
})) {
const selectedServerVariables = Object.entries({
...selectedServerObj.variables
}).reduce((acc, [serverVariableName, serverVariable]) => {
acc[serverVariableName] = serverVariable.default;
return acc;
}, {});
selectedServerUrl = (0, _openapiServerUrlTemplating.substitute)(selectedServerUrl, {
...selectedServerVariables,
...serverVariables
}, {
encoder: typeof serverVariableEncoder === 'function' ? serverVariableEncoder : _ramda.identity
});
}
return buildOas3UrlWithContext(selectedServerUrl, contextUrl);
}
function buildOas3UrlWithContext(ourUrl = '', contextUrl = '') {
// relative server url should be resolved against contextUrl
const parsedUrl = ourUrl && contextUrl ? parseURIReference(_empty.url.resolve(contextUrl, ourUrl)) : parseURIReference(ourUrl);
const parsedContextUrl = parseURIReference(contextUrl);
const computedScheme = stripNonAlpha(parsedUrl.protocol) || stripNonAlpha(parsedContextUrl.protocol);
const computedHost = parsedUrl.host || parsedContextUrl.host;
const computedPath = parsedUrl.pathname;
let res;
if (computedScheme && computedHost) {
res = `${computedScheme}://${computedHost + computedPath}`;
// if last character is '/', trim it off
} else {
res = computedPath;
}
return res[res.length - 1] === '/' ? res.slice(0, -1) : res;
}
// Compose the baseUrl ( scheme + host + basePath )
function swagger2BaseUrl({
spec,
scheme,
contextUrl = ''
}) {
const parsedContextUrl = parseURIReference(contextUrl);
const firstSchemeInSpec = Array.isArray(spec.schemes) ? spec.schemes[0] : null;
const computedScheme = scheme || firstSchemeInSpec || stripNonAlpha(parsedContextUrl.protocol) || 'http';
const computedHost = spec.host || parsedContextUrl.host || '';
const computedPath = spec.basePath || '';
let res;
if (computedScheme && computedHost) {
// we have what we need for an absolute URL
res = `${computedScheme}://${computedHost + computedPath}`;
} else {
// if not, a relative URL will have to do
res = computedPath;
}
// If last character is '/', trim it off
return res[res.length - 1] === '/' ? res.slice(0, -1) : res;
}