Permalink
Browse files

lam process controller working

  • Loading branch information...
tongueroo committed Oct 24, 2017
1 parent f0c7f22 commit 84d1f8607fe87ed4bf06ff9ca63c33279e96467f
@@ -1,12 +1,22 @@
guard "rspec" do
watch(%r{^spec/.+_spec\.rb$})
watch(%r{^lib/(.+)\.rb$}) { "spec/lam_spec.rb" }
watch(%r{^lib/lam/(.+)\.rb$}) { "spec/lam_spec.rb" }
watch("spec/spec_helper.rb") { "spec/lam_spec.rb" }
watch(%r{^lib/lam/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
end

guard "bundler" do
guard "bundler", cmd: "bundle" do
watch("Gemfile")
watch(/^.+\.gemspec/)
end

guard :rspec, cmd: "bundle exec rspec" do
require "guard/rspec/dsl"
dsl = Guard::RSpec::Dsl.new(self)

# RSpec files
rspec = dsl.rspec
watch(rspec.spec_helper) { rspec.spec_dir }
watch(rspec.spec_support) { rspec.spec_dir }
watch(rspec.spec_files)

# Ruby files
ruby = dsl.ruby
puts "ruby.lib_files #{ruby.lib_files.inspect}"
dsl.watch_spec_files_for(ruby.lib_files)

watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
end
@@ -5,4 +5,5 @@ module Lam
autoload :Command, "lam/command"
autoload :CLI, "lam/cli"
autoload :Process, 'lam/process'
autoload :BaseController, 'lam/base_controller'
end
@@ -0,0 +1,37 @@
module Lam
class BaseController
def initialize(event, context)
@event, @context = event, context
end

private
def render(options={})
# render json: {"mytestdata": "value1"}, status: 200, headers: {...}
if options.has_key?(:json)
# Transform the structure to Lambda Proxy structure
# {statusCode: ..., body: ..., headers: }
status = options.delete(:status)
body = options.delete(:json)
result = options.merge(
statusCode: status,
body: body
)
# render text: "text"
elsif options.has_key?(:text)
result = options.delete(:text)
else
raise "Unsupported render option. Only :text and :json supported. options #{options.inspect}"
end

result
end

# API Gateway LAMBDA_PROXY wraps the event in its own structure.
# We unwrap the "body" before sending it back
# For regular Lambda function calls, no need to unwrap but need to
# transform it to a string with JSON.dump.
def normalize_event_body(event)
body = event.has_key?("body") ? event["body"] : JSON.dump(event)
end
end
end
@@ -7,14 +7,6 @@ class CLI < Command
class_option :verbose, type: :boolean
class_option :noop, type: :boolean

desc "hello NAME", "say hello to NAME"
long_desc Help.hello
option :from, desc: "from person"
def hello(name)
puts "from: #{options[:from]}" if options[:from]
puts "Hello #{name}"
end

desc "process TYPE", "process subcommand tasks"
long_desc Help.process
subcommand "process", Lam::Process
@@ -2,6 +2,9 @@

class Lam::Process < Lam::Command
autoload :Help, 'lam/process/help'
autoload :Infer, 'lam/process/infer'
autoload :BaseProcessor, 'lam/process/base_processor'
autoload :ControllerProcessor, 'lam/process/controller_processor'

class_option :verbose, type: :boolean
class_option :noop, type: :boolean
@@ -12,7 +15,6 @@ class Lam::Process < Lam::Command
option :randomize_stack_name, type: :boolean, desc: "tack on random string at the end of the stack name", default: nil
long_desc Help.controller
def controller(event, context, handler)
puts "event"
# Create.new(name, options).run
ControllerProcessor.new(event, context, handler).run
end
end
@@ -0,0 +1,20 @@
require 'json'
require_relative 'infer'

# Global overrides for Lambda processing
$stdout.sync = true
# This might seem weird but we want puts to write to stderr which is set in
# the node shim to write to stderr. This directs the output to Lambda logs.
# Printing to stdout can managle up the payload returned from Lambda function.
# This is not desired if you want to return say a json payload to API Gateway
# eventually.
def puts(text)
$stderr.puts(text)
end

class Lam::Process::BaseProcessor
attr_reader :event, :context, :handler
def initialize(event, context, handler)
@event, @context, @handler = event, context, handler
end
end
@@ -0,0 +1,34 @@
require_relative "base_processor"

class Lam::Process
class ControllerProcessor < Lam::Process::BaseProcessor
def run
# Use the handler value (ie: posts.create) to infer the user's business
# code to require and run.
infer = Infer.new(handler)
path = infer.controller[:path]
code = infer.controller[:code]

begin
require path # require "app/controllers/posts_controller.rb"
# Puts the return value of user's code to stdout because this is
# what eventually gets used by API Gateway.
# Explicitly using $stdout since puts redirected to $stderr.
result = instance_eval(code, path) # result = PostsController.new(event, context).create

# JSON.dump is pretty robust. If it cannot dump the structure into a
# json string, it just dumps it to a plain text string.
$stdout.puts JSON.dump(result) # only place where we write to stdout.
rescue Exception => e
# Customize error message slightly so nodejs shim can process the
# returned error message.
# The "RubyError: " is a marker that the javascript shim scans for.
$stderr.puts("RubyError: #{e.class}: #{e.message}") # js needs this as the first line
backtrace = e.backtrace.map {|l| " #{l}" }
$stderr.puts(backtrace)
# $stderr.puts("END OF RUBY OUTPUT")
exit 1 # instead of re-raising to control the error backtrace output
end
end
end
end
@@ -0,0 +1,53 @@
class Lam::Process::Infer
def initialize(handler)
@handler = handler
@project_root = ENV['PROJECT_ROOT'] || '.'
end

# Infers the path and method from the handler. Example:
#
# InferCode.new("handlers/functions/posts.create").function
# => {path: "app/functions/posts.rb", code: "create(event, context)"}
#
# Summary:
#
# Input:
# handler: handlers/functions/posts.create
# Output:
# path: app/functions/posts.rb
# code: create(event, context) # code to instance_eval
#
# Returns: {path: path, code: code}
def function
path, meth = @handler.split('.')
path = "#{@project_root}/" + path.sub("handlers", "app") + ".rb"
code = "#{meth}(event, context)"
{path: path, code: code}
end

# Infers the path and method from the handler. Example:
#
# InferCode.new("handlers/controllers/posts.create").controller
# => {path: "controllers/posts_controller.rb", code: "create"}
#
# Summary:
#
# Input:
# handler: handlers/controllers/posts.create
# Output:
# path: app/controllers/posts_controller.rb
# code: create # code to instance_eval
#
# Returns: {path: path, code: code}
def controller
handler_path, meth = @handler.split('.')

path = "#{@project_root}/" + handler_path.sub("handlers", "app") + "_controller.rb"

controller_name = handler_path.sub(%r{.*handlers/controllers/}, "") + "_controller" # posts_controller
controller_class = controller_name.split('_').collect(&:capitalize).join # PostsController
code = "#{controller_class}.new(event, context).#{meth}" # PostsController.new(event, context).create

{path: path, code: code}
end
end
@@ -0,0 +1,2 @@
class ApplicationController < Lam::BaseController
end
@@ -0,0 +1,8 @@
class PostsController < Lam::BaseController
def create
# render text: "test2" # more consistent for web controllers

# render returns Lamba Proxy struture for web requests
render json: {"mytestdata": "value2"}, status: 200
end
end
@@ -11,9 +11,9 @@
end

describe "lam" do
it "should hello world" do
out = execute("bin/lam hello world #{@args}")
expect(out).to include("from: Tung\nHello world")
end
# it "should hello world" do
# out = execute("bin/lam hello world #{@args}")
# expect(out).to include("from: Tung\nHello world")
# end
end
end
@@ -0,0 +1,29 @@
require "spec_helper"


describe Lam::Process::Infer do
before(:all) do
# @args = "--from Tung"
end

describe "lam" do
let(:infer) { Lam::Process::Infer.new(handle) }

context("controller") do
let(:handle) { "handlers/controllers/posts.create" }
it "should infer path and code" do
expect(infer.controller[:path]).to include "app/controllers/posts_controller.rb"
expect(infer.controller[:code]).to eq "PostsController.new(event, context).create"
end
end

context("function") do
let(:handle) { "handlers/functions/posts.create" }
it "should infer path and code" do
expect(infer.function[:path]).to include "app/functions/posts.rb"
expect(infer.function[:code]).to eq "create(event, context)"
end
end

end
end
@@ -14,4 +14,3 @@
end
end
end

@@ -1,4 +1,5 @@
ENV["TEST"] = "1"
ENV["PROJECT_ROOT"] = "./spec/fixtures/project"

require "simplecov"
SimpleCov.start

0 comments on commit 84d1f86

Please sign in to comment.