openapi-to-graphql-nullable
Version:
Fork of openapi-to-graphql that fixes nullable handling. Published to NPM since there's no other easy way to consume a Lerna package. Unmaintained.
941 lines • 42.5 kB
JavaScript
;
// Copyright IBM Corp. 2018. All Rights Reserved.
// Node module: openapi-to-graphql
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
Object.defineProperty(exports, "__esModule", { value: true });
exports.extractRequestDataFromArgs = exports.getResolver = exports.getPublishResolver = exports.getSubscribe = exports.OPENAPI_TO_GRAPHQL = void 0;
const NodeRequest = require("request");
// Imports:
const Oas3Tools = require("./oas_3_tools");
const querystring = require("querystring");
const JSONPath = require("jsonpath-plus");
const debug_1 = require("debug");
const graphql_1 = require("graphql");
const form_urlencoded_1 = require("form-urlencoded");
const graphql_subscriptions_1 = require("graphql-subscriptions");
const pubsub = new graphql_subscriptions_1.PubSub();
const translationLog = debug_1.debug('translation');
const httpLog = debug_1.debug('http');
const pubsubLog = debug_1.debug('pubsub');
// OAS runtime expression reference locations
const RUNTIME_REFERENCES = ['header.', 'query.', 'path.', 'body'];
exports.OPENAPI_TO_GRAPHQL = '_openAPIToGraphQL';
/*
* If the operation type is Subscription, create and return a resolver object
* that contains subscribe to perform subscription and resolve to execute
* payload transformation
*/
function getSubscribe({ operation, payloadName, data, baseUrl, connectOptions }) {
// Determine the appropriate URL:
if (typeof baseUrl === 'undefined') {
baseUrl = Oas3Tools.getBaseUrl(operation);
}
// Return custom resolver if it is defined
const customResolvers = data.options.customSubscriptionResolvers;
const title = operation.oas.info.title;
const path = operation.path;
const method = operation.method;
if (typeof customResolvers === 'object' &&
typeof customResolvers[title] === 'object' &&
typeof customResolvers[title][path] === 'object' &&
typeof customResolvers[title][path][method] === 'object' &&
typeof customResolvers[title][path][method].subscribe === 'function') {
translationLog(`Use custom publish resolver for ${operation.operationString}`);
return customResolvers[title][path][method].subscribe;
}
return (root, args, context, info) => {
/**
* Determine possible topic(s) by resolving callback path
*
* GraphQL produces sanitized payload names, so we have to sanitize before
* lookup here
*/
const paramName = Oas3Tools.sanitize(payloadName, Oas3Tools.CaseStyle.camelCase);
let resolveData = {};
if (payloadName && typeof payloadName === 'string') {
// The option genericPayloadArgName will change the payload name to "requestBody"
const sanePayloadName = data.options.genericPayloadArgName
? 'requestBody'
: Oas3Tools.sanitize(payloadName, Oas3Tools.CaseStyle.camelCase);
if (sanePayloadName in args) {
if (typeof args[sanePayloadName] === 'object') {
const rawPayload = Oas3Tools.desanitizeObjectKeys(args[sanePayloadName], data.saneMap);
resolveData.usedPayload = rawPayload;
}
else {
const rawPayload = JSON.parse(args[sanePayloadName]);
resolveData.usedPayload = rawPayload;
}
}
}
if (connectOptions) {
resolveData.usedRequestOptions = connectOptions;
}
else {
resolveData.usedRequestOptions = {
method: resolveData.usedPayload.method
? resolveData.usedPayload.method
: method.toUpperCase()
};
}
pubsubLog(`Subscription schema: ${JSON.stringify(resolveData.usedPayload)}`);
let value = path;
let paramNameWithoutLocation = paramName;
if (paramName.indexOf('.') !== -1) {
paramNameWithoutLocation = paramName.split('.')[1];
}
// See if the callback path contains constants expression
if (value.search(/{|}/) === -1) {
args[paramNameWithoutLocation] = isRuntimeExpression(value)
? resolveRuntimeExpression(paramName, value, resolveData, root, args)
: value;
}
else {
// Replace callback expression with appropriate values
const cbParams = value.match(/{([^}]*)}/g);
pubsubLog(`Analyzing subscription path: ${cbParams.toString()}`);
cbParams.forEach((cbParam) => {
value = value.replace(cbParam, resolveRuntimeExpression(paramName, cbParam.substring(1, cbParam.length - 1), resolveData, root, args));
});
args[paramNameWithoutLocation] = value;
}
const topic = args[paramNameWithoutLocation] || 'test';
pubsubLog(`Subscribing to: ${topic}`);
return context.pubsub
? context.pubsub.asyncIterator(topic)
: pubsub.asyncIterator(topic);
};
}
exports.getSubscribe = getSubscribe;
/*
* If the operation type is Subscription, create and return a resolver function
* triggered after a message has been published to the corresponding subscribe
* topic(s) to execute payload transformation
*/
function getPublishResolver({ operation, responseName, data }) {
// Return custom resolver if it is defined
const customResolvers = data.options.customSubscriptionResolvers;
const title = operation.oas.info.title;
const path = operation.path;
const method = operation.method;
if (typeof customResolvers === 'object' &&
typeof customResolvers[title] === 'object' &&
typeof customResolvers[title][path] === 'object' &&
typeof customResolvers[title][path][method] === 'object' &&
typeof customResolvers[title][path][method].resolve === 'function') {
translationLog(`Use custom publish resolver for ${operation.operationString}`);
return customResolvers[title][path][method].resolve;
}
return (payload, args, context, info) => {
// Validate and format based on operation.responseDefinition
const typeOfResponse = operation.responseDefinition.targetGraphQLType;
pubsubLog(`Message received: ${responseName}, ${typeOfResponse}, ${JSON.stringify(payload)}`);
let responseBody;
let saneData;
if (typeof payload === 'object') {
if (typeOfResponse === 'object') {
if (Buffer.isBuffer(payload)) {
try {
responseBody = JSON.parse(payload.toString());
}
catch (e) {
const errorString = `Cannot JSON parse payload` +
`operation ${operation.operationString} ` +
`even though it has content-type 'application/json'`;
pubsubLog(errorString);
return null;
}
}
else {
responseBody = payload;
}
saneData = Oas3Tools.sanitizeObjectKeys(payload);
}
else if ((Buffer.isBuffer(payload) || Array.isArray(payload)) &&
typeOfResponse === 'string') {
saneData = payload.toString();
}
}
else if (typeof payload === 'string') {
if (typeOfResponse === 'object') {
try {
responseBody = JSON.parse(payload);
saneData = Oas3Tools.sanitizeObjectKeys(responseBody);
}
catch (e) {
const errorString = `Cannot JSON parse payload` +
`operation ${operation.operationString} ` +
`even though it has content-type 'application/json'`;
pubsubLog(errorString);
return null;
}
}
else if (typeOfResponse === 'string') {
saneData = payload;
}
}
pubsubLog(`Message forwarded: ${JSON.stringify(saneData ? saneData : payload)}`);
return saneData ? saneData : payload;
};
}
exports.getPublishResolver = getPublishResolver;
/**
* If the operation type is Query or Mutation, create and return a resolver
* function that performs API requests for the given GraphQL query
*/
function getResolver({ operation, argsFromLink = {}, payloadName, data, baseUrl, requestOptions }) {
// Determine the appropriate URL:
if (typeof baseUrl === 'undefined') {
baseUrl = Oas3Tools.getBaseUrl(operation);
}
// Return custom resolver if it is defined
const customResolvers = data.options.customResolvers;
const title = operation.oas.info.title;
const path = operation.path;
const method = operation.method;
if (typeof customResolvers === 'object' &&
typeof customResolvers[title] === 'object' &&
typeof customResolvers[title][path] === 'object' &&
typeof customResolvers[title][path][method] === 'function') {
translationLog(`Use custom resolver for ${operation.operationString}`);
return customResolvers[title][path][method];
}
// Return resolve function :
return (source, args, context, info) => {
/**
* Fetch resolveData from possibly existing _openAPIToGraphQL
*
* NOTE: _openAPIToGraphQL is an object used to pass security info and data
* from previous resolvers
*/
let resolveData = {};
if (source &&
typeof source === 'object' &&
typeof source[exports.OPENAPI_TO_GRAPHQL] === 'object' &&
typeof source[exports.OPENAPI_TO_GRAPHQL].data === 'object') {
const parentIdentifier = getParentIdentifier(info);
if (!(parentIdentifier.length === 0) &&
parentIdentifier in source[exports.OPENAPI_TO_GRAPHQL].data) {
/**
* Resolving link params may change the usedParams, but these changes
* should not be present in the parent _openAPIToGraphQL, therefore copy
* the object
*/
resolveData = JSON.parse(JSON.stringify(source[exports.OPENAPI_TO_GRAPHQL].data[parentIdentifier]));
}
}
if (typeof resolveData.usedParams === 'undefined') {
resolveData.usedParams = {};
}
/**
* Handle default values of parameters, if they have not yet been defined by
* the user.
*/
operation.parameters.forEach((param) => {
const paramName = Oas3Tools.sanitize(param.name, !data.options.simpleNames
? Oas3Tools.CaseStyle.camelCase
: Oas3Tools.CaseStyle.simple);
if (typeof args[paramName] === 'undefined' &&
param.schema &&
typeof param.schema === 'object') {
let schema = param.schema;
if (schema && schema.$ref && typeof schema.$ref === 'string') {
schema = Oas3Tools.resolveRef(schema.$ref, operation.oas);
}
if (schema &&
schema.default &&
typeof schema.default !== 'undefined') {
args[paramName] = schema.default;
}
}
});
// Handle arguments provided by links
for (const paramName in argsFromLink) {
const saneParamName = Oas3Tools.sanitize(paramName, !data.options.simpleNames
? Oas3Tools.CaseStyle.camelCase
: Oas3Tools.CaseStyle.simple);
let value = argsFromLink[paramName];
/**
* see if the link parameter contains constants that are appended to the link parameter
*
* e.g. instead of:
* $response.body#/employerId
*
* it could be:
* abc_{$response.body#/employerId}
*/
if (value.search(/{|}/) === -1) {
args[saneParamName] = isRuntimeExpression(value)
? resolveRuntimeExpression(paramName, value, resolveData, source, args)
: value;
}
else {
// Replace link parameters with appropriate values
const linkParams = value.match(/{([^}]*)}/g);
linkParams.forEach((linkParam) => {
value = value.replace(linkParam, resolveRuntimeExpression(paramName, linkParam.substring(1, linkParam.length - 1), resolveData, source, args));
});
args[saneParamName] = value;
}
}
// Stored used parameters to future requests:
resolveData.usedParams = Object.assign(resolveData.usedParams, args);
// Build URL (i.e., fill in path parameters):
const { path, qs, headers } = extractRequestDataFromArgs(operation.path, operation.parameters, args, data);
const url = baseUrl + path;
/**
* The Content-Type and Accept property should not be changed because the
* object type has already been created and unlike these properties, it
* cannot be easily changed
*
* NOTE: This may cause the user to encounter unexpected changes
*/
if (operation.method !== Oas3Tools.HTTP_METHODS.get) {
headers['content-type'] =
typeof operation.payloadContentType !== 'undefined'
? operation.payloadContentType
: 'application/json';
}
headers['accept'] =
typeof operation.responseContentType !== 'undefined'
? operation.responseContentType
: 'application/json';
let options;
if (requestOptions) {
options = Object.assign(Object.assign({}, requestOptions), { method: operation.method, url // Must be after the requestOptions spread as url is a mandatory field so undefined may be used
});
options.headers = {}; // Handle requestOptions.header later if applicable
options.qs = {}; // Handle requestOptions.qs later if applicable
if (requestOptions.headers) {
// requestOptions.headers may be either an object or a function
if (typeof requestOptions.headers === 'object') {
Object.assign(options.headers, headers, requestOptions.headers);
}
else if (typeof requestOptions.headers === 'function') {
const headers = requestOptions.headers(method, path, title, {
source,
args,
context,
info
});
if (typeof headers === 'object') {
Object.assign(options.headers, headers, headers);
}
}
}
else {
options.headers = headers;
}
if (requestOptions.qs) {
Object.assign(options.qs, qs, requestOptions.qs);
}
else {
options.qs = qs;
}
}
else {
options = {
method: operation.method,
url,
headers,
qs
};
}
/**
* Determine possible payload
*
* GraphQL produces sanitized payload names, so we have to sanitize before
* lookup here
*/
resolveData.usedPayload = undefined;
if (typeof payloadName === 'string') {
// The option genericPayloadArgName will change the payload name to "requestBody"
const sanePayloadName = data.options.genericPayloadArgName
? 'requestBody'
: Oas3Tools.sanitize(payloadName, Oas3Tools.CaseStyle.camelCase);
let rawPayload;
if (operation.payloadContentType === 'application/json') {
rawPayload = JSON.stringify(Oas3Tools.desanitizeObjectKeys(args[sanePayloadName], data.saneMap));
}
else if (operation.payloadContentType === 'application/x-www-form-urlencoded') {
rawPayload = form_urlencoded_1.default(Oas3Tools.desanitizeObjectKeys(args[sanePayloadName], data.saneMap));
}
else {
// Payload is not an object
rawPayload = args[sanePayloadName];
}
options.body = rawPayload;
resolveData.usedPayload = rawPayload;
}
/**
* Pass on OpenAPI-to-GraphQL options
*/
if (typeof data.options === 'object') {
// Headers:
if (typeof data.options.headers === 'object') {
Object.assign(options.headers, data.options.headers);
}
else if (typeof data.options.headers === 'function') {
const headers = data.options.headers(method, path, title, {
source,
args,
context,
info
});
if (typeof headers === 'object') {
Object.assign(options.headers, headers);
}
}
// Query string:
if (typeof data.options.qs === 'object') {
Object.assign(options.qs, data.options.qs);
}
}
// Get authentication headers and query parameters
if (source &&
typeof source === 'object' &&
typeof source[exports.OPENAPI_TO_GRAPHQL] === 'object') {
const { authHeaders, authQs, authCookie } = getAuthOptions(operation, source[exports.OPENAPI_TO_GRAPHQL], data);
// ...and pass them to the options
Object.assign(options.headers, authHeaders);
Object.assign(options.qs, authQs);
// Add authentication cookie if created
if (authCookie !== null) {
const j = NodeRequest.jar();
j.setCookie(authCookie, options.url);
options.jar = j;
}
}
// Extract OAuth token from context (if available)
if (data.options.sendOAuthTokenInQuery) {
const oauthQueryObj = createOAuthQS(data, context);
Object.assign(options.qs, oauthQueryObj);
}
else {
const oauthHeader = createOAuthHeader(data, context);
Object.assign(options.headers, oauthHeader);
}
resolveData.usedRequestOptions = options;
resolveData.usedStatusCode = operation.statusCode;
// Make the call
httpLog(`Call ${options.method.toUpperCase()} ${options.url}?${querystring.stringify(options.qs)}\n` +
`headers: ${JSON.stringify(options.headers)}\n` +
`request body: ${options.body}`);
return new Promise((resolve, reject) => {
NodeRequest(options, (err, response, body) => {
if (err) {
httpLog(err);
reject(err);
}
else if (response.statusCode < 200 || response.statusCode > 299) {
httpLog(`${response.statusCode} - ${Oas3Tools.trim(body, 100)}`);
const errorString = `Could not invoke operation ${operation.operationString}`;
if (data.options.provideErrorExtensions) {
let responseBody;
try {
responseBody = JSON.parse(body);
}
catch (e) {
responseBody = body;
}
const extensions = {
method: operation.method,
path: operation.path,
statusCode: response.statusCode,
responseHeaders: response.headers,
responseBody
};
reject(graphQLErrorWithExtensions(errorString, extensions));
}
else {
reject(new Error(errorString));
}
// Successful response code 200-299
}
else {
httpLog(`${response.statusCode} - ${Oas3Tools.trim(body, 100)}`);
if (response.headers['content-type']) {
/**
* Throw warning if the non-application/json content does not
* match the OAS.
*
* Use an inclusion test in case of charset
*
* i.e. text/plain; charset=utf-8
*/
if (!(response.headers['content-type'].includes(operation.responseContentType) ||
operation.responseContentType.includes(response.headers['content-type']))) {
const errorString = `Operation ` +
`${operation.operationString} ` +
`should have a content-type '${operation.responseContentType}' ` +
`but has '${response.headers['content-type']}' instead`;
httpLog(errorString);
reject(errorString);
}
else {
/**
* If the response body is type JSON, then parse it
*
* content-type may not be necessarily 'application/json' it can be
* 'application/json; charset=utf-8' for example
*/
if (response.headers['content-type'].includes('application/json')) {
let responseBody;
try {
responseBody = JSON.parse(body);
}
catch (e) {
const errorString = `Cannot JSON parse response body of ` +
`operation ${operation.operationString} ` +
`even though it has content-type 'application/json'`;
httpLog(errorString);
reject(errorString);
}
resolveData.responseHeaders = response.headers;
// Deal with the fact that the server might send unsanitized data
let saneData = Oas3Tools.sanitizeObjectKeys(responseBody, !data.options.simpleNames
? Oas3Tools.CaseStyle.camelCase
: Oas3Tools.CaseStyle.simple);
// Pass on _openAPIToGraphQL to subsequent resolvers
if (saneData && typeof saneData === 'object') {
if (Array.isArray(saneData)) {
saneData.forEach((element) => {
if (typeof element[exports.OPENAPI_TO_GRAPHQL] === 'undefined') {
element[exports.OPENAPI_TO_GRAPHQL] = {
data: {}
};
}
if (source &&
typeof source === 'object' &&
typeof source[exports.OPENAPI_TO_GRAPHQL] === 'object') {
Object.assign(element[exports.OPENAPI_TO_GRAPHQL], source[exports.OPENAPI_TO_GRAPHQL]);
}
element[exports.OPENAPI_TO_GRAPHQL].data[getIdentifier(info)] = resolveData;
});
}
else {
if (typeof saneData[exports.OPENAPI_TO_GRAPHQL] === 'undefined') {
saneData[exports.OPENAPI_TO_GRAPHQL] = {
data: {}
};
}
if (source &&
typeof source === 'object' &&
typeof source[exports.OPENAPI_TO_GRAPHQL] === 'object') {
Object.assign(saneData[exports.OPENAPI_TO_GRAPHQL], source[exports.OPENAPI_TO_GRAPHQL]);
}
saneData[exports.OPENAPI_TO_GRAPHQL].data[getIdentifier(info)] = resolveData;
}
}
// Apply limit argument
if (data.options.addLimitArgument &&
/**
* NOTE: Does not differentiate between autogenerated args and
* preexisting args
*
* Ensure that there is not preexisting 'limit' argument
*/
!operation.parameters.find((parameter) => {
return parameter.name === 'limit';
}) &&
// Only array data
Array.isArray(saneData) &&
// Only array of objects/arrays
saneData.some((data) => {
return typeof data === 'object';
})) {
let arraySaneData = saneData;
if ('limit' in args) {
const limit = args['limit'];
if (limit >= 0) {
arraySaneData = arraySaneData.slice(0, limit);
}
else {
reject(new Error(`Auto-generated 'limit' argument must be greater than or equal to 0`));
}
}
else {
reject(new Error(`Cannot get value for auto-generated 'limit' argument`));
}
saneData = arraySaneData;
}
resolve(saneData);
}
else {
// TODO: Handle YAML
resolve(body);
}
}
}
else {
/**
* Check to see if there is not supposed to be a response body,
* if that is the case, that would explain why there is not
* a content-type
*/
const { responseContentType } = Oas3Tools.getResponseObject(operation, operation.statusCode, operation.oas);
if (responseContentType === null) {
resolve(null);
}
else {
const errorString = 'Response does not have a Content-Type property';
httpLog(errorString);
reject(errorString);
}
}
}
});
});
};
}
exports.getResolver = getResolver;
/**
* Attempts to create an object to become an OAuth query string by extracting an
* OAuth token from the context based on the JSON path provided in the options.
*/
function createOAuthQS(data, context) {
return typeof data.options.tokenJSONpath !== 'string'
? {}
: extractToken(data, context);
}
function extractToken(data, context) {
const tokenJSONpath = data.options.tokenJSONpath;
const tokens = JSONPath.JSONPath({
path: tokenJSONpath,
json: context
});
if (Array.isArray(tokens) && tokens.length > 0) {
const token = tokens[0];
return {
access_token: token
};
}
else {
httpLog(`Warning: could not extract OAuth token from context at '${tokenJSONpath}'`);
return {};
}
}
/**
* Attempts to create an OAuth authorization header by extracting an OAuth token
* from the context based on the JSON path provided in the options.
*/
function createOAuthHeader(data, context) {
if (typeof data.options.tokenJSONpath !== 'string') {
return {};
}
// Extract token
const tokenJSONpath = data.options.tokenJSONpath;
const tokens = JSONPath.JSONPath({
path: tokenJSONpath,
json: context
});
if (Array.isArray(tokens) && tokens.length > 0) {
const token = tokens[0];
return {
Authorization: `Bearer ${token}`,
'User-Agent': 'openapi-to-graphql'
};
}
else {
httpLog(`Warning: could not extract OAuth token from context at ` +
`'${tokenJSONpath}'`);
return {};
}
}
/**
* Return the headers and query strings to authenticate a request (if any).
* Return authHeader and authQs, which hold headers and query parameters
* respectively to authentication a request.
*/
function getAuthOptions(operation, _openAPIToGraphQL, data) {
const authHeaders = {};
const authQs = {};
let authCookie = null;
/**
* Determine if authentication is required, and which protocol (if any) we can
* use
*/
const { authRequired, securityRequirement, sanitizedSecurityRequirement } = getAuthReqAndProtcolName(operation, _openAPIToGraphQL);
// Possibly, we don't need to do anything:
if (!authRequired) {
return { authHeaders, authQs, authCookie };
}
// If authentication is required, but we can't fulfill the protocol, throw:
if (authRequired && typeof securityRequirement !== 'string') {
throw new Error(`Missing information to authenticate API request.`);
}
if (typeof securityRequirement === 'string') {
const security = data.security[securityRequirement];
switch (security.def.type) {
case 'apiKey':
const apiKey = _openAPIToGraphQL.security[sanitizedSecurityRequirement].apiKey;
if ('in' in security.def) {
if (typeof security.def.name === 'string') {
if (security.def.in === 'header') {
authHeaders[security.def.name] = apiKey;
}
else if (security.def.in === 'query') {
authQs[security.def.name] = apiKey;
}
else if (security.def.in === 'cookie') {
authCookie = NodeRequest.cookie(`${security.def.name}=${apiKey}`);
}
}
else {
throw new Error(`Cannot send API key in '${JSON.stringify(security.def.in)}'`);
}
}
break;
case 'http':
switch (security.def.scheme) {
case 'basic':
const username = _openAPIToGraphQL.security[sanitizedSecurityRequirement].username;
const password = _openAPIToGraphQL.security[sanitizedSecurityRequirement].password;
const credentials = `${username}:${password}`;
authHeaders['Authorization'] = `Basic ${Buffer.from(credentials).toString('base64')}`;
break;
default:
throw new Error(`Cannot recognize http security scheme ` +
`'${JSON.stringify(security.def.scheme)}'`);
}
break;
case 'oauth2':
break;
case 'openIdConnect':
break;
default:
throw new Error(`Cannot recognize security type '${security.def.type}'`);
}
}
return { authHeaders, authQs, authCookie };
}
/**
* Determines whether a given operation requires authentication, and which of
* the (possibly multiple) authentication protocols can be used based on the
* data present in the given context.
*/
function getAuthReqAndProtcolName(operation, _openAPIToGraphQL) {
let authRequired = false;
if (Array.isArray(operation.securityRequirements) &&
operation.securityRequirements.length > 0) {
authRequired = true;
for (let securityRequirement of operation.securityRequirements) {
const sanitizedSecurityRequirement = Oas3Tools.sanitize(securityRequirement, Oas3Tools.CaseStyle.camelCase);
if (typeof _openAPIToGraphQL.security[sanitizedSecurityRequirement] ===
'object') {
return {
authRequired,
securityRequirement,
sanitizedSecurityRequirement
};
}
}
}
return {
authRequired
};
}
/**
* Given a link parameter or callback path, determine the value from the runtime
* expression
*
* The link parameter or callback path is a reference to data contained in the
* url/method/statuscode or response/request body/query/path/header
*/
function resolveRuntimeExpression(paramName, value, resolveData, root, args) {
if (value === '$url') {
return resolveData.usedRequestOptions.url;
}
else if (value === '$method') {
return resolveData.usedRequestOptions.method;
}
else if (value === '$statusCode') {
return resolveData.usedStatusCode;
}
else if (value.startsWith('$request.')) {
// CASE: parameter is previous body
if (value === '$request.body') {
return resolveData.usedPayload;
// CASE: parameter in previous body
}
else if (value.startsWith('$request.body#')) {
const tokens = JSONPath.JSONPath({
path: value.split('body#/')[1],
json: resolveData.usedPayload
});
if (Array.isArray(tokens) && tokens.length > 0) {
return tokens[0];
}
else {
httpLog(`Warning: could not extract parameter '${paramName}' from link`);
}
// CASE: parameter in previous query parameter
}
else if (value.startsWith('$request.query')) {
return resolveData.usedParams[Oas3Tools.sanitize(value.split('query.')[1], Oas3Tools.CaseStyle.camelCase)];
// CASE: parameter in previous path parameter
}
else if (value.startsWith('$request.path')) {
return resolveData.usedParams[Oas3Tools.sanitize(value.split('path.')[1], Oas3Tools.CaseStyle.camelCase)];
// CASE: parameter in previous header parameter
}
else if (value.startsWith('$request.header')) {
return resolveData.usedRequestOptions.headers[value.split('header.')[1]];
}
}
else if (value.startsWith('$response.')) {
/**
* CASE: parameter is body
*
* NOTE: may not be used because it implies that the operation does not
* return a JSON object and OpenAPI-to-GraphQL does not create GraphQL
* objects for non-JSON data and links can only exists between objects.
*/
if (value === '$response.body') {
const result = JSON.parse(JSON.stringify(root));
/**
* _openAPIToGraphQL contains data used by OpenAPI-to-GraphQL to create the GraphQL interface
* and should not be exposed
*/
result._openAPIToGraphQL = undefined;
return result;
// CASE: parameter in body
}
else if (value.startsWith('$response.body#')) {
const tokens = JSONPath.JSONPath({
path: value.split('body#/')[1],
json: root
});
if (Array.isArray(tokens) && tokens.length > 0) {
return tokens[0];
}
else {
httpLog(`Warning: could not extract parameter '${paramName}' from link`);
}
// CASE: parameter in query parameter
}
else if (value.startsWith('$response.query')) {
// NOTE: handled the same way $request.query is handled
return resolveData.usedParams[Oas3Tools.sanitize(value.split('query.')[1], Oas3Tools.CaseStyle.camelCase)];
// CASE: parameter in path parameter
}
else if (value.startsWith('$response.path')) {
// NOTE: handled the same way $request.path is handled
return resolveData.usedParams[Oas3Tools.sanitize(value.split('path.')[1], Oas3Tools.CaseStyle.camelCase)];
// CASE: parameter in header parameter
}
else if (value.startsWith('$response.header')) {
return resolveData.responseHeaders[value.split('header.')[1]];
}
}
throw new Error(`Cannot create link because '${value}' is an invalid runtime expression`);
}
/**
* Check if a string is a runtime expression in the context of link parameters
*/
function isRuntimeExpression(str) {
if (str === '$url' || str === '$method' || str === '$statusCode') {
return true;
}
else if (str.startsWith('$request.')) {
for (let i = 0; i < RUNTIME_REFERENCES.length; i++) {
if (str.startsWith(`$request.${RUNTIME_REFERENCES[i]}`)) {
return true;
}
}
}
else if (str.startsWith('$response.')) {
for (let i = 0; i < RUNTIME_REFERENCES.length; i++) {
if (str.startsWith(`$response.${RUNTIME_REFERENCES[i]}`)) {
return true;
}
}
}
return false;
}
/**
* From the info object provided by the resolver, get a unique identifier, which
* is the path formed from the nested field names (or aliases if provided)
*
* Used to store and retrieve the _openAPIToGraphQL of parent field
*/
function getIdentifier(info) {
return getIdentifierRecursive(info.path);
}
/**
* From the info object provided by the resolver, get the unique identifier of
* the parent object
*/
function getParentIdentifier(info) {
return getIdentifierRecursive(info.path.prev);
}
/**
* Get the path of nested field names (or aliases if provided)
*/
function getIdentifierRecursive(path) {
return typeof path.prev === 'undefined'
? path.key
: /**
* Check if the identifier contains array indexing, if so remove.
*
* i.e. instead of 0/friends/1/friends/2/friends/user, create
* friends/friends/friends/user
*/
isNaN(parseInt(path.key))
? `${path.key}/${getIdentifierRecursive(path.prev)}`
: getIdentifierRecursive(path.prev);
}
/**
* Create a new GraphQLError with an extensions field
*/
function graphQLErrorWithExtensions(message, extensions) {
return new graphql_1.GraphQLError(message, null, null, null, null, null, extensions);
}
/**
* Extracts data from the GraphQL arguments of a particular field
*
* Replaces the path parameter in the given path with values in the given args.
* Furthermore adds the query parameters for a request.
*/
function extractRequestDataFromArgs(path, parameters, args, // NOTE: argument keys are sanitized!
data) {
const qs = {};
const headers = {};
// Iterate parameters:
for (const param of parameters) {
const sanitizedParamName = Oas3Tools.sanitize(param.name, !data.options.simpleNames
? Oas3Tools.CaseStyle.camelCase
: Oas3Tools.CaseStyle.simple);
if (sanitizedParamName && sanitizedParamName in args) {
switch (param.in) {
// Path parameters
case 'path':
path = path.replace(`{${param.name}}`, args[sanitizedParamName]);
break;
// Query parameters
case 'query':
qs[param.name] = args[sanitizedParamName];
break;
// Header parameters
case 'header':
headers[param.name] = args[sanitizedParamName];
break;
// Cookie parameters
case 'cookie':
if (!('cookie' in headers)) {
headers['cookie'] = '';
}
headers['cookie'] += `${param.name}=${args[sanitizedParamName]}; `;
break;
default:
httpLog(`Warning: The parameter location '${param.in}' in the ` +
`parameter '${param.name}' of operation '${path}' is not ` +
`supported`);
}
}
}
return { path, qs, headers };
}
exports.extractRequestDataFromArgs = extractRequestDataFromArgs;
//# sourceMappingURL=resolver_builder.js.map