Nopik / serverless-serve

Local lambda HTTP serve plugin for Serverless framework, a.k.a. API Gateway simulator.
119 stars 19 forks source link

Build event object based on requestParameters/requestTemplates #4

Open jagthedrummer opened 8 years ago

jagthedrummer commented 8 years ago

In the live AWS environment request parameters are not passed through to the serverless module in the event object unless the requestParameters and requestTemplates options in s-function.json are set up properly. It would be great if serverless-serve mirrored this to make it easier to catch mapping/template problems.

Nopik commented 8 years ago

Yeah, that was on my radar for long time. Sooner or later, it will get implemented.

kayoub5 commented 8 years ago

requestTemplates are actually written using Apache Velocity, a template engine do exist in JavaScript velocity.js. So all needed to do is recreate the variables $context, $input, $stageVariables and $util. I would have done it myself, but I am short on time at the moment.

kayoub5 commented 8 years ago

@Nopik this is a PoC I have been working on so far, it's not complete, but it should be a good starting point. the below snippet generates an event object from an express request object, and a serverless endpoint request template

'use strict';

const jsonpath = require('jsonpath');
class Input {
  constructor(req) {
    var self = this;
    self._body = req.body;
    self._path = {};
    self._querystring = {};
    self._header = {};
    self._params = {
      path: self._path,
      querystring: self._querystring,
      header: self._header
    };
    Object.keys(req.headers).forEach(function(key) {
      let newkey = key.replace(/((?:^|-)[a-z])/g, function(val) {
        return val.toUpperCase();
      });
      // custom hack for X-Parse-Os-Version ==> X-Parse-OS-Version
      newkey = newkey.replace(/(-Os-)/g, function(val) {
        return val.toUpperCase();
      });
      self._header[newkey] = req.headers[key];
      self._params[newkey] = req.headers[key];
    });
    Object.keys(req.query).forEach(function(key) {
      self._querystring[key] = req.query[key];
      self._params[key] = req.query[key];
    });
    Object.keys(req.params).forEach(function(key) {
      self._path[key] = req.params[key];
      self._params[key] = req.params[key];
    });
  }
  path(p) {
    let value = jsonpath.value(this._body, p);
    if (value === null || value === undefined) {
      value = '';
    }
    return value;
  }
  json(p) {
    return JSON.stringify(this.path(p));
  }
  params(key) {
    if (!arguments.length) {
      return this._params;
    }
    return this._params[key] || '';
  }
}

let util = {
  escapeJavaScript: function(obj) {
    return JSON.stringify(obj).slice(1, -1);
  },
  urlDecode: decodeURI,
  urlEncode: encodeURI,
  base64Encode: function(data) {
    return new Buffer(data).toString('base64');
  },
  base64Decode: function(data) {
    return new Buffer(data, 'base64').toString('utf8');
  }
};

function createContext(req) {
  return {
    input: new Input(req),
    util: util
  };
}

module.exports.createEvent = function(template, req) {
  if (!template) {
    return req.body;
  }
  if (typeof template !== 'string') {
    template = JSON.stringify(template);
  }
  let Velocity = require( 'velocityjs' );
  return Velocity.render(template, createContext(req));
};