serverless-offline
Version:
Emulate AWS λ and API Gateway locally when developing your Serverless project
183 lines (137 loc) • 6.17 kB
JavaScript
;
const log = require('./utils/log');
const logDebug = require('./utils/logDebug');
const logWarning = require('./utils/logWarning');
const logAndExit = require('./utils/logAndExit');
module.exports = function createLambdaCallback(fun, endpoint, requestId, response) {
return function callback(error, data) {
// Everything in this block happens once the lambda function has resolved
logDebug('_____ HANDLER RESOLVED _____');
// Timeout clearing if needed
if (_clearTimeout(requestId)) return;
// User should not call context.done twice
if (requests[requestId].done) {
console.log();
log(`Warning: context.done called twice within handler '${funName}'!`);
logDebug('requestId:', requestId);
return;
}
requests[requestId].done = true;
let result = data;
let responseName = 'default';
let responseContentType = 'application/json';
/* RESPONSE SELECTION (among endpoint's possible responses) */
// Failure handling
if (error) {
const errorMessage = (error.message || error).toString();
// Mocks Lambda errors
result = {
errorMessage,
errorType: error.constructor.name,
stackTrace: _getArrayStackTrace(error.stack)
};
log(`Failure: ${errorMessage}`);
if (result.stackTrace) console.log(result.stackTrace.join('\n '));
for (let key in endpoint.responses) {
if (key === 'default') continue;
if (errorMessage.match('^' + (endpoint.responses[key].selectionPattern || key) + '$')) {
responseName = key;
break;
}
}
}
logDebug(`Using response '${responseName}'`);
const chosenResponse = endpoint.responses[responseName];
/* RESPONSE PARAMETERS PROCCESSING */
const responseParameters = chosenResponse.responseParameters;
if (isPlainObject(responseParameters)) {
const responseParametersKeys = Object.keys(responseParameters);
logDebug('_____ RESPONSE PARAMETERS PROCCESSING _____');
logDebug(`Found ${responseParametersKeys.length} responseParameters for '${responseName}' response`);
responseParametersKeys.forEach(key => {
// responseParameters use the following shape: "key": "value"
const value = responseParameters[key];
const keyArray = key.split('.'); // eg: "method.response.header.location"
const valueArray = value.split('.'); // eg: "integration.response.body.redirect.url"
logDebug(`Processing responseParameter "${key}": "${value}"`);
// For now the plugin only supports modifying headers
if (key.startsWith('method.response.header') && keyArray[3]) {
const headerName = keyArray.slice(3).join('.');
let headerValue;
logDebug('Found header in left-hand:', headerName);
if (value.startsWith('integration.response')) {
if (valueArray[2] === 'body') {
logDebug('Found body in right-hand');
headerValue = JSON.stringify(valueArray[3] ? jsonPath(result, valueArray.slice(3).join('.')) : result);
} else {
console.log();
log(`Warning: while processing responseParameter "${key}": "${value}"`);
log(`Offline plugin only supports "integration.response.body[.JSON_path]" right-hand responseParameter. Found "${value}" instead. Skipping.`);
logPluginIssue();
console.log();
}
} else {
headerValue = value;
}
// Applies the header;
logDebug(`Will assign "${headerValue}" to header "${headerName}"`);
response.header(headerName, headerValue);
}
else {
console.log();
log(`Warning: while processing responseParameter "${key}": "${value}"`);
log(`Offline plugin only supports "method.response.header.PARAM_NAME" left-hand responseParameter. Found "${key}" instead. Skipping.`);
logPluginIssue();
console.log();
}
});
}
/* RESPONSE TEMPLATE PROCCESSING */
// If there is a responseTemplate, we apply it to the result
const responseTemplates = chosenResponse.responseTemplates;
if (isPlainObject(responseTemplates)) {
const responseTemplatesKeys = Object.keys(responseTemplates);
if (responseTemplatesKeys.length) {
// BAD IMPLEMENTATION: first key in responseTemplates
const templateName = responseTemplatesKeys[0];
const responseTemplate = responseTemplates[templateName];
responseContentType = templateName;
if (responseTemplate) {
logDebug('_____ RESPONSE TEMPLATE PROCCESSING _____');
logDebug(`Using responseTemplate '${templateName}'`);
try {
const reponseContext = createApigContext(request, velocityContextOptions, result);
result = renderVelocityTemplateObject({ root: responseTemplate }, reponseContext).root;
}
catch (err) {
log(`Error while parsing responseTemplate '${templateName}' for λ ${funName}:`);
console.log(err.stack);
}
}
}
}
/* HAPIJS RESPONSE CONFIGURATION */
const statusCode = chosenResponse.statusCode || 200;
if (!chosenResponse.statusCode) {
console.log();
log(`Warning: No statusCode found for response "${responseName}".`);
}
response.header('Content-Type', responseContentType);
response.statusCode = statusCode;
response.source = result;
// Log response
let whatToLog = result;
try {
whatToLog = JSON.stringify(result);
}
catch(err) {
// nothing
}
finally {
log(err ? `Replying ${statusCode}` : `[${statusCode}] ${whatToLog}`);
logDebug('requestId:', requestId);
}
// Bon voyage!
response.send();
};
};