From 8fb8bcf726fb99ca63a4e3eefe07652d4ad01f0e Mon Sep 17 00:00:00 2001 From: Aotokitsuruya Date: Mon, 1 Jan 2024 16:09:29 +0800 Subject: [PATCH] Add Rack handler --- .github/workflows/main.yml | 38 ++++++++++++++++++++----- lib/hanami/lambda.rb | 35 +++++++++++++++++++++++ lib/hanami/lambda/application.rb | 37 ++++++++++++++++++++++++ lib/hanami/lambda/rack.rb | 49 ++++++++++++++++++++++++++++++++ 4 files changed, 152 insertions(+), 7 deletions(-) create mode 100644 lib/hanami/lambda/application.rb create mode 100644 lib/hanami/lambda/rack.rb diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 37a9318..d85aab6 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,21 +1,45 @@ -name: Ruby +name: CI on: push: + paths: + - '.github/workflows/ci.yml' + - 'lib/**' + - '*.gemspec' + - 'spec/**' + - 'Rakefile' + - 'Gemfile' + - '.rubocop.yml' + pull_request: branches: - main - - pull_request: + schedule: + - cron: '30 4 * * *' + create: jobs: - build: + rubocop: + runs-on: ubuntu-latest + name: Rubocop + steps: + - uses: actions/checkout@v4 + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: '3.2' + bundler-cache: true + - name: Run linter + run: bundle exec rake rubocop + + tests: runs-on: ubuntu-latest name: Ruby ${{ matrix.ruby }} strategy: matrix: ruby: - - '3.3.0' - + - '3.2' + - '3.1' + - '3.0' steps: - uses: actions/checkout@v4 - name: Set up Ruby @@ -23,5 +47,5 @@ jobs: with: ruby-version: ${{ matrix.ruby }} bundler-cache: true - - name: Run the default task + - name: Run all tests run: bundle exec rake diff --git a/lib/hanami/lambda.rb b/lib/hanami/lambda.rb index c75ea76..1eb358f 100644 --- a/lib/hanami/lambda.rb +++ b/lib/hanami/lambda.rb @@ -11,6 +11,41 @@ module Hanami # @since 0.1.0 # @api private module Lambda + @_mutex = Mutex.new + + # Returns the Hanami::Lambda application. + # + # @return [Hanami::Lambda::Application] the application + # @raise [Hanami::AppLoadError] if the application isn't configured + # + # @api public + # @since 0.1.0 + def app + @_mutex.synchronize do + unless defined?(@_app) + raise Hanami::AppLoadError, + "Hanami::Lambda.app is not yet configured. " + end + + @_app + end + end + + # @api private + # @since 0.1.0 + def app=(klass) + @_mutex.synchronize do + raise AppLoadError, "Hanami::Lambda.app is already configured." if instance_variable_defined?(:@_app) + + @_app = klass unless klass.name.nil? + end + end + + def call(event:, context:) + Hanami.boot + app.call(event: event, context: context) + end + # @since 0.1.0 # @api private def self.gem_loader diff --git a/lib/hanami/lambda/application.rb b/lib/hanami/lambda/application.rb new file mode 100644 index 0000000..593f6ef --- /dev/null +++ b/lib/hanami/lambda/application.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +module Hanami + module Lambda + # The application to configure for AWS Lambda. + # + # @since 0.1.0 + class Application + # @api private + def self.inherited(subclass) + super + + Hanami::Lambda.app = subclass + subclass.extend(ClassMethods) + end + + module ClassMethods + # Dispatch event to the handler + # + # @api private + # @since 0.1.0 + def call(event:, context:) + handler = lookup(event: event, context: context) + handler.call + end + + # Lookup the handler for the given event + # + # @api private + # @since 0.1.0 + def lookup(event:, context:) + Rack.new(Hanami.app, event: event, context: context) + end + end + end + end +end diff --git a/lib/hanami/lambda/rack.rb b/lib/hanami/lambda/rack.rb new file mode 100644 index 0000000..19a7a87 --- /dev/null +++ b/lib/hanami/lambda/rack.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +module Hanami + module Lambda + # Rack interface for AWS Lambda. + # + # @api private + # @since 0.1.0 + class Rack + attr_reader :app, :event, :context + + # @api private + def initialize(app, event:, context:) + @app = app + @event = event + @context = context + end + + # Handle the request + # + # @return [Hash] the response + # + # @since 0.1.0 + def call + status_code, headers, body = app.call(env) + + { + statusCode: status_code, + headers: headers, + body: body.join + } + end + + # Build the Rack environment + # + # @return [Hash] the Rack environment + # + # @since 0.1.0 + def env + { + ::Rack::REQUEST_METHOD => event["httpMethod"], + ::Rack::PATH_INFO => event["path"] || "", + ::Rack::VERSION => ::Rack::VERSION, + ::Rack::RACK_INPUT => StringIO.new(event["body"] || "") + } + end + end + end +end