hanami-lambda 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e6434425a9bc1477663be14851eb304da6fd9fc5ebe9a9ff3b800afb72986f1d
4
- data.tar.gz: 94b184c7584379f57d7214c24e4963e3ca2b66d9a5cac02781e6612254f868a3
3
+ metadata.gz: c8a8ab1a1e0affa3af3a684eb7f96cf8b4d5f19a18f73cbc9f86a0661726a8cd
4
+ data.tar.gz: 281445c481e12c80c41e9a217e719eb3306aaa9e8214feedeb9999e7a7347276
5
5
  SHA512:
6
- metadata.gz: 296484573f0c74479a2f3ddf78f4eef69c8d41e7ae34eae2bc18055aaa17c7d9ad49f843f016555a08c72743272d237e289d64dba5aadc8a7dcb696f9e07d5a4
7
- data.tar.gz: d8b969b733db865b342cb55a9e800020ed416922db7061115ff36cb4d391bc76ff78d83f4e49e731e40c512a41ebda80b416d3bece37c559429eba5fed29b507
6
+ metadata.gz: 59c7fe7721eaf15a3481d418ded8ecf61f6ff5774024c6a6702d413f7792624565b394f99db736013961750e48c801febfb531962a77e39cd38532e619b1f470
7
+ data.tar.gz: c4abc0406ebb796740201d79e1e00986c08f3b8ea23ec3a7e056943d4ab1b641a3aec90c291edddb86477175fb3cab91c56b7712051ae545b32a181be3b7984a
data/README.md CHANGED
@@ -10,7 +10,7 @@ Hanami Lambda is a gem that provides a way to run hanami application on AWS Lamb
10
10
 
11
11
  ## Rubies
12
12
 
13
- **Hanami::Cucumber** supports Ruby (MRI) 3.0+
13
+ **Hanami::Lambda** supports Ruby (MRI) 3.0+
14
14
 
15
15
  ## Installation
16
16
 
@@ -26,14 +26,25 @@ And then execute:
26
26
  $ bundle install
27
27
  ```
28
28
 
29
- Create `config/lambda.rb` with below content
29
+ Update `config/app.rb` with below content
30
30
 
31
31
  ```ruby
32
+ require 'hanami'
32
33
  require 'hanami/lambda'
33
34
 
34
35
  module MyApp # Rename to your app name
35
- class Lambda < Hanami::Lambda::Application
36
- end
36
+ class Lambda < Hanami::App
37
+ extend Hanami::Lambda::Application
38
+ end
39
+ end
40
+ ```
41
+
42
+ Create `app/function.rb` as handler base class
43
+
44
+ ```ruby
45
+ module MyApp
46
+ class Function < Hanami::Lambda::Function
47
+ end
37
48
  end
38
49
  ```
39
50
 
@@ -44,17 +55,45 @@ end
44
55
  Use `config/lambda.Hanami::Lambda.call` as the function handler
45
56
 
46
57
  ```yaml
58
+ # AWS SAM
47
59
  Resources:
48
- ExampleHandler:
60
+ MyFunction:
49
61
  Type: AWS::Serverless::Function
50
- Name: "example-api"
51
62
  Properties:
52
63
  CodeUri: .
53
64
  Handler: config/lambda.Hanami::Lambda.call
54
65
  Runtime: ruby3.2
55
66
  ```
56
67
 
57
- > Currently, the only `APIGateWay` event is supported
68
+ ### Delegate
69
+
70
+ If the lambda function isn't trigger by APIGateway, we can use `delegate` method to define the handler function.
71
+
72
+ Create `config/lambda.rb` with below content
73
+
74
+ ```ruby
75
+ module MyApp
76
+ class Lambda < Hanami::Lambda::Dispatcher
77
+ delegate "MyFunction", to: "daily_task"
78
+ end
79
+ end
80
+ ```
81
+
82
+ > The IaC generated function will be `my-app-MyFunction-r8faNAo3iUqx` therefore the dispatcher will use include `MyFunction` to find targeted function
83
+
84
+ Add `app/functions/daily_task.rb` to define the handle action
85
+
86
+ ```ruby
87
+ module MyApp
88
+ module Functions
89
+ class DailyTask < MyApp::Function
90
+ def handle(event, context)
91
+ # ...
92
+ end
93
+ end
94
+ end
95
+ end
96
+ ```
58
97
 
59
98
  ## Development
60
99
 
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hanami
4
+ module Lambda
5
+ # Base error for Hanami::Lambda
6
+ #
7
+ # @api public
8
+ # @since 0.2.0
9
+ Error = Class.new(StandardError)
10
+
11
+ # Raised when {Hanami::Lambda::Application} fails to load.
12
+ #
13
+ # @api public
14
+ # @since 0.2.0
15
+ AppLoadError = Class.new(Error)
16
+ end
17
+ end
@@ -2,34 +2,62 @@
2
2
 
3
3
  module Hanami
4
4
  module Lambda
5
+ HANDLER_KEY_NAMESPACE = "functions"
6
+
5
7
  # The application to configure for AWS Lambda.
6
8
  #
7
9
  # @since 0.1.0
8
- class Application
10
+ module Application
11
+ # @since 0.2.0
12
+ # @api private
13
+ def self.extended(base)
14
+ base.class_eval do
15
+ prepare_load_path if respond_to?(:prepare_load_path)
16
+ end
17
+ end
18
+
19
+ # Dispatch event to the handler
20
+ #
9
21
  # @api private
10
- def self.inherited(subclass)
11
- super
22
+ # @since 0.1.0
23
+ def handle_lambda(event:, context:)
24
+ lambda_dispatcher.call(event: event, context: context)
25
+ end
12
26
 
13
- Hanami::Lambda.app = subclass
14
- subclass.extend(ClassMethods)
27
+ # Get lambda dispatcher
28
+ #
29
+ # @return [Hanami::Lambda::Dispatcher] the dispatcher
30
+ #
31
+ # @since 0.1.0
32
+ def lambda_dispatcher
33
+ @lambda_dispatcher ||= load_lambda_dispatcher
15
34
  end
16
35
 
17
- module ClassMethods
18
- # Dispatch event to the handler
19
- #
20
- # @api private
21
- # @since 0.1.0
22
- def call(event:, context:)
23
- handler = lookup(event: event, context: context)
24
- handler.call
36
+ # Load lambda dispatcher
37
+ #
38
+ # @return [Hanami::Lambda::Dispatcher] the dispatcher
39
+ #
40
+ # @since 0.2.0
41
+ # @api private
42
+ def load_lambda_dispatcher
43
+ if root.directory?
44
+ dispatcher_path = File.join(root, LAMBDA_CONFIG_PATH)
45
+
46
+ begin
47
+ require dispatcher_path
48
+ rescue LoadError => exception
49
+ raise exception unless exception.path == dispatcher_path
50
+ end
25
51
  end
26
52
 
27
- # Lookup the handler for the given event
28
- #
29
- # @api private
30
- # @since 0.1.0
31
- def lookup(event:, context:)
32
- Rack.new(Hanami.app, event: event, context: context)
53
+ begin
54
+ dispatcher_class = namespace.const_get(LAMBDA_CLASS_NAME)
55
+ dispatcher_class.build(
56
+ rack_app: app.rack_app,
57
+ resolver: ->(to) { app.resolve("#{HANDLER_KEY_NAMESPACE}.#{to}") }
58
+ )
59
+ rescue NameError => exception
60
+ raise exception unless exception.name == LAMBDA_CLASS_NAME
33
61
  end
34
62
  end
35
63
  end
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hanami
4
+ module Lambda
5
+ # Dispatch Event to the Handler
6
+ #
7
+ # @api private
8
+ class Dispatcher
9
+ # @since 0.2.0
10
+ # @api private
11
+ DEFAULT_RESOLVER = ->(to) { to }
12
+
13
+ attr_reader :rack_app, :handlers, :default, :resolver
14
+
15
+ # @since 0.2.0
16
+ def initialize(rack_app:, resolver: DEFAULT_RESOLVER)
17
+ @handlers = {}
18
+ @resolver = resolver
19
+ @default = Rack.new(rack_app)
20
+ end
21
+
22
+ # Call the handler
23
+ #
24
+ # @param event [Hash] the event
25
+ # @param context [Hash] the context
26
+ #
27
+ # @since 0.2.0
28
+ def call(event:, context:)
29
+ handler = lookup(event: event, context: context)
30
+ return default.call(event: event, context: context) unless handler
31
+
32
+ handler.call(event: event, context: context)
33
+ end
34
+
35
+ # Lookup the handler
36
+ #
37
+ # @param event [Hash] the event
38
+ # @param context [Hash] the context
39
+ #
40
+ # @return [Handler] the handler
41
+ def lookup(event:, context:) # rubocop:disable Lint/UnusedMethodArgument
42
+ function_name = ENV.fetch("AWS_LAMBDA_FUNCTION_NAME", context.function_name)
43
+ handlers
44
+ .select { |name, _| function_name.include?(name) }
45
+ .max_by { |name, _| name.length }
46
+ &.last
47
+ end
48
+
49
+ # Register a handler
50
+ #
51
+ # @param name [String] the name of the handler
52
+ # @param args [Array] the arguments to pass to the handler
53
+ # @param kwargs [Hash] the keyword arguments to pass to the handler
54
+ # @param block [Proc] the block to pass to the handler
55
+ #
56
+ # @since 0.2.0
57
+ def register(name, to: nil)
58
+ handlers[name] =
59
+ if to.nil?
60
+ @default
61
+ else
62
+ resolver.call(to)
63
+ end
64
+ end
65
+
66
+ class << self
67
+ # Definitions of handlers
68
+ #
69
+ # @api private
70
+ def definitions
71
+ @definitions ||= []
72
+ end
73
+
74
+ # Define function delegate action
75
+ #
76
+ # @param name [String] the name of the handler
77
+ # @param args [Array] the arguments to pass to the handler
78
+ # @param kwargs [Hash] the keyword arguments to pass to the handler
79
+ # @param block [Proc] the block to pass to the handler
80
+ def delegate(name, *args, **kwargs, &block)
81
+ definitions << [name, args, kwargs, block]
82
+ end
83
+
84
+ # Build Dispatcher
85
+ #
86
+ # @api private
87
+ def build(rack_app:, resolver:)
88
+ new(rack_app: rack_app, resolver: resolver).tap do |dispatcher|
89
+ definitions.each do |(name, args, kwargs, block)|
90
+ if block
91
+ dispatcher.register(name, *args, **kwargs, &block)
92
+ else
93
+ dispatcher.register(name, *args, **kwargs)
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "hanami/utils/hash"
4
+
5
+ module Hanami
6
+ module Lambda
7
+ # Base event class
8
+ #
9
+ # @since 0.2.0
10
+ class Event
11
+ attr_reader :raw
12
+
13
+ # @param event [Hash] the raw event from AWS Lambda
14
+ # @return [Hanami::Lambda::Event]
15
+ #
16
+ # @since 0.2.0
17
+ def initialize(event)
18
+ @raw = event
19
+ @event = Hanami::Utils::Hash.deep_symbolize(@raw)
20
+ freeze
21
+ end
22
+
23
+ # Return the value of the given key
24
+ #
25
+ # @param key [Symbol] the key to fetch
26
+ #
27
+ # @return [Object,NilClass] the associated value if found
28
+ #
29
+ # @since 0.2.0
30
+ def [](key)
31
+ @event[key]
32
+ end
33
+
34
+ # Return an value associated with the given event key
35
+ #
36
+ # @param keys [Array<Symbol, Integer>] the keys to fetch
37
+ #
38
+ # @return [Object,NilClass] the associated value if found
39
+ #
40
+ # @since 0.2.0
41
+ def get(*keys)
42
+ @event.dig(*keys)
43
+ end
44
+ alias_method :dig, :get
45
+
46
+ # Return the hash of the event
47
+ #
48
+ # @return [Hash] the hash of the event
49
+ #
50
+ # @since 0.2.0
51
+ def to_h
52
+ @event
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hanami
4
+ module Lambda
5
+ module Events
6
+ class Base < Dry::Struct
7
+ transform_keys do |key|
8
+ Hanami::Lambda.inflector.underscore(key).to_sym
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hanami
4
+ module Lambda
5
+ module Events
6
+ class EventBridge < Base
7
+ attribute :version, Types::Integer
8
+ attribute :id, Types::String
9
+ attribute :detail_type, Types::String
10
+ attribute :source, Types::String
11
+ attribute :account, Types::String
12
+ attribute :time, Types::Time
13
+ attribute :region, Types::String
14
+ attribute :resources, Types::Array.of(Types::String)
15
+ attribute :detail, Types::String
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hanami
4
+ module Lambda
5
+ # The base class for handler
6
+ #
7
+ # @since 0.2.0
8
+ class Function
9
+ # Override the Ruby's hook for modules
10
+ #
11
+ # @param base [Class] the target class
12
+ #
13
+ # @since 0.2.0
14
+ # @api private
15
+ def self.inherited(subclass)
16
+ super
17
+
18
+ subclass.extend ClassMethods
19
+
20
+ if instance_variable_defined?(:@event_type)
21
+ subclass.instance_variable_set(:@event_type, @event_type)
22
+ end
23
+ end
24
+
25
+ module ClassMethods
26
+ # Return the class which define the event type
27
+ #
28
+ # @return [Class] the class which define the event type
29
+ #
30
+ # @since 0.2.0
31
+ # @api private
32
+ def event_type
33
+ @event_type || Event
34
+ end
35
+
36
+ # Define the event type
37
+ #
38
+ # @param klass [Class] the class which define the event type
39
+ #
40
+ # @since 0.2.0
41
+ # @api private
42
+ def type(klass)
43
+ @event_type = klass
44
+ end
45
+ end
46
+
47
+ # @since 0.2.0
48
+ def call(event:, context:)
49
+ event = self.class.event_type.new(event)
50
+ handle(event, context)
51
+ end
52
+
53
+ protected
54
+
55
+ def handle(_event, _context); end
56
+ end
57
+ end
58
+ end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "rack"
4
+
3
5
  module Hanami
4
6
  module Lambda
5
7
  # Rack interface for AWS Lambda.
@@ -7,13 +9,13 @@ module Hanami
7
9
  # @api private
8
10
  # @since 0.1.0
9
11
  class Rack
10
- attr_reader :app, :event, :context
12
+ attr_reader :app
11
13
 
12
- # @api private
13
- def initialize(app, event:, context:)
14
+ # Initialize the Rack interface
15
+ #
16
+ # @since 0.1.0
17
+ def initialize(app)
14
18
  @app = app
15
- @event = event
16
- @context = context
17
19
  end
18
20
 
19
21
  # Handle the request
@@ -21,7 +23,8 @@ module Hanami
21
23
  # @return [Hash] the response
22
24
  #
23
25
  # @since 0.1.0
24
- def call
26
+ def call(event:, context:)
27
+ env = build_env(event, context)
25
28
  status_code, headers, body = app.call(env)
26
29
 
27
30
  {
@@ -36,12 +39,14 @@ module Hanami
36
39
  # @return [Hash] the Rack environment
37
40
  #
38
41
  # @since 0.1.0
39
- def env
42
+ def build_env(event, context)
40
43
  {
41
44
  ::Rack::REQUEST_METHOD => event["httpMethod"],
42
45
  ::Rack::PATH_INFO => event["path"] || "",
43
46
  ::Rack::VERSION => ::Rack::VERSION,
44
- ::Rack::RACK_INPUT => StringIO.new(event["body"] || "")
47
+ ::Rack::RACK_INPUT => StringIO.new(event["body"] || ""),
48
+ ::Hanami::Lambda::LAMBDA_EVENT => event,
49
+ ::Hanami::Lambda::LAMBDA_CONTEXT => context
45
50
  }
46
51
  end
47
52
  end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry-struct"
4
+
5
+ module Hanami
6
+ module Lambda
7
+ module Types
8
+ include Dry.Types(default: :params)
9
+ end
10
+ end
11
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Hanami
4
4
  module Lambda
5
- VERSION = "0.1.0"
5
+ VERSION = "0.2.0"
6
6
  end
7
7
  end
data/lib/hanami/lambda.rb CHANGED
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "zeitwerk"
4
+ require "dry-struct"
5
+ require "hanami"
4
6
 
5
7
  # @see Hanami::Lambda
6
8
  # @since 0.1.0
@@ -10,41 +12,52 @@ module Hanami
10
12
  # @since 0.1.0
11
13
  # @api private
12
14
  module Lambda
15
+ # @since 0.1.0
16
+ LAMBDA_EVENT = "lambda.event"
17
+
18
+ # @since 0.1.0
19
+ LAMBDA_CONTEXT = "lambda.context"
20
+
21
+ # @since 0.2.0
22
+ LAMBDA_CONFIG_PATH = File.join("config", "lambda")
23
+
24
+ # @since 0.2.0
25
+ LAMBDA_CLASS_NAME = "Lambda"
26
+
13
27
  @_mutex = Mutex.new
14
28
 
15
- # Returns the Hanami::Lambda application.
16
- #
17
- # @return [Hanami::Lambda::Application] the application
18
- # @raise [Hanami::AppLoadError] if the application isn't configured
29
+ # Return the application
19
30
  #
20
31
  # @api public
21
32
  # @since 0.1.0
33
+ #
34
+ # @return [Hanami::Lambda::Application] the application
22
35
  def self.app
23
- @_mutex.synchronize do
24
- unless defined?(@_app)
25
- raise Hanami::AppLoadError,
26
- "Hanami::Lambda.app is not yet configured. "
27
- end
36
+ Hanami.app
37
+ end
28
38
 
29
- @_app
30
- end
39
+ # Run the application
40
+ #
41
+ # @api public
42
+ def self.call(event:, context:)
43
+ app.boot
44
+ app.handle_lambda(event: event, context: context)
31
45
  end
32
46
 
47
+ # Inflector to convert event key
48
+ #
49
+ # @return [Dry::Inflector]
50
+ #
51
+ # @since 0.2.0
33
52
  # @api private
34
- # @since 0.1.0
35
- def self.app=(klass)
53
+ def self.inflector
36
54
  @_mutex.synchronize do
37
- raise AppLoadError, "Hanami::Lambda.app is already configured." if instance_variable_defined?(:@_app)
55
+ return @inflector if defined?(@inflector)
38
56
 
39
- @_app = klass unless klass.name.nil?
57
+ @inflector ||= Dry::Inflector.new
40
58
  end
41
- end
42
-
43
- def self.call(event:, context:)
44
- require "hanami/setup"
45
59
 
46
- Hanami.boot
47
- app.call(event: event, context: context)
60
+ @inflector
48
61
  end
49
62
 
50
63
  # @since 0.1.0
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hanami-lambda
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aotokitsuruya
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-01-01 00:00:00.000000000 Z
11
+ date: 2024-01-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: zeitwerk
@@ -24,6 +24,62 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '2.6'
27
+ - !ruby/object:Gem::Dependency
28
+ name: hanami
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: hanami-utils
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '2.0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '2.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: dry-struct
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: dry-inflector
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '1.0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '1.0'
27
83
  - !ruby/object:Gem::Dependency
28
84
  name: rubocop
29
85
  requirement: !ruby/object:Gem::Requirement
@@ -52,9 +108,16 @@ files:
52
108
  - README.md
53
109
  - Rakefile
54
110
  - lib/hanami-lambda.rb
111
+ - lib/hanami/errors.rb
55
112
  - lib/hanami/lambda.rb
56
113
  - lib/hanami/lambda/application.rb
114
+ - lib/hanami/lambda/dispatcher.rb
115
+ - lib/hanami/lambda/event.rb
116
+ - lib/hanami/lambda/events/base.rb
117
+ - lib/hanami/lambda/events/event_bridge.rb
118
+ - lib/hanami/lambda/function.rb
57
119
  - lib/hanami/lambda/rack.rb
120
+ - lib/hanami/lambda/types.rb
58
121
  - lib/hanami/lambda/version.rb
59
122
  - sig/hanami/lambda.rbs
60
123
  homepage: https://github.com/elct9620/hanami-lambda