bolt_rb 0.1.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 +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +218 -0
- data/lib/bolt_rb/app.rb +216 -0
- data/lib/bolt_rb/configuration.rb +44 -0
- data/lib/bolt_rb/context.rb +173 -0
- data/lib/bolt_rb/handlers/action_handler.rb +162 -0
- data/lib/bolt_rb/handlers/base.rb +194 -0
- data/lib/bolt_rb/handlers/command_handler.rb +119 -0
- data/lib/bolt_rb/handlers/event_handler.rb +113 -0
- data/lib/bolt_rb/handlers/shortcut_handler.rb +132 -0
- data/lib/bolt_rb/handlers/view_submission_handler.rb +159 -0
- data/lib/bolt_rb/middleware/base.rb +35 -0
- data/lib/bolt_rb/middleware/chain.rb +58 -0
- data/lib/bolt_rb/middleware/logging.rb +60 -0
- data/lib/bolt_rb/router.rb +75 -0
- data/lib/bolt_rb/socket_mode/client.rb +296 -0
- data/lib/bolt_rb/testing/payload_factory.rb +143 -0
- data/lib/bolt_rb/testing/rspec_helpers.rb +62 -0
- data/lib/bolt_rb/testing.rb +20 -0
- data/lib/bolt_rb/version.rb +4 -0
- data/lib/bolt_rb.rb +76 -0
- metadata +149 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BoltRb
|
|
4
|
+
module Handlers
|
|
5
|
+
# Handler for Slack shortcuts (global shortcuts and message shortcuts)
|
|
6
|
+
#
|
|
7
|
+
# This handler provides the `shortcut` DSL for matching shortcut payloads.
|
|
8
|
+
# Global shortcuts are triggered from the lightning bolt menu in Slack,
|
|
9
|
+
# while message shortcuts appear in the context menu of messages.
|
|
10
|
+
#
|
|
11
|
+
# @example Global shortcut handler
|
|
12
|
+
# class CreateTicketHandler < BoltRb::ShortcutHandler
|
|
13
|
+
# shortcut 'create_ticket'
|
|
14
|
+
#
|
|
15
|
+
# def handle
|
|
16
|
+
# ack
|
|
17
|
+
# # Open a modal with views.open using trigger_id
|
|
18
|
+
# end
|
|
19
|
+
# end
|
|
20
|
+
#
|
|
21
|
+
# @example Message shortcut handler
|
|
22
|
+
# class QuoteMessageHandler < BoltRb::ShortcutHandler
|
|
23
|
+
# shortcut 'quote_message'
|
|
24
|
+
#
|
|
25
|
+
# def handle
|
|
26
|
+
# ack
|
|
27
|
+
# text = message_text
|
|
28
|
+
# # Do something with the quoted message
|
|
29
|
+
# end
|
|
30
|
+
# end
|
|
31
|
+
#
|
|
32
|
+
# @example Regex-based shortcut matching
|
|
33
|
+
# class CreateHandler < BoltRb::ShortcutHandler
|
|
34
|
+
# shortcut /^create_/
|
|
35
|
+
#
|
|
36
|
+
# def handle
|
|
37
|
+
# ack
|
|
38
|
+
# # Handle any shortcut starting with 'create_'
|
|
39
|
+
# end
|
|
40
|
+
# end
|
|
41
|
+
class ShortcutHandler < Base
|
|
42
|
+
# Valid shortcut payload types
|
|
43
|
+
# 'shortcut' is a global shortcut (from lightning bolt menu)
|
|
44
|
+
# 'message_action' is a message shortcut (from message context menu)
|
|
45
|
+
SHORTCUT_TYPES = %w[shortcut message_action].freeze
|
|
46
|
+
|
|
47
|
+
class << self
|
|
48
|
+
# Configures which callback_id this handler responds to
|
|
49
|
+
#
|
|
50
|
+
# @param callback_id [String, Regexp] The callback_id to match (exact string or regex)
|
|
51
|
+
# @return [void]
|
|
52
|
+
#
|
|
53
|
+
# @example Match exact callback_id
|
|
54
|
+
# shortcut 'create_ticket'
|
|
55
|
+
#
|
|
56
|
+
# @example Match callback_id pattern
|
|
57
|
+
# shortcut /^create_/
|
|
58
|
+
def shortcut(callback_id)
|
|
59
|
+
@matcher_config = {
|
|
60
|
+
type: :shortcut,
|
|
61
|
+
callback_id: callback_id
|
|
62
|
+
}
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Determines if this handler matches the given payload
|
|
66
|
+
#
|
|
67
|
+
# Checks if the payload is a shortcut or message_action type and if the
|
|
68
|
+
# callback_id matches the configured pattern.
|
|
69
|
+
#
|
|
70
|
+
# @param payload [Hash] The incoming Slack shortcut payload
|
|
71
|
+
# @return [Boolean] true if this handler should process the shortcut
|
|
72
|
+
def matches?(payload)
|
|
73
|
+
return false unless matcher_config
|
|
74
|
+
return false unless SHORTCUT_TYPES.include?(payload['type'])
|
|
75
|
+
|
|
76
|
+
if matcher_config[:callback_id].is_a?(Regexp)
|
|
77
|
+
matcher_config[:callback_id].match?(payload['callback_id'])
|
|
78
|
+
else
|
|
79
|
+
payload['callback_id'] == matcher_config[:callback_id]
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Returns the callback_id from the shortcut payload
|
|
85
|
+
#
|
|
86
|
+
# @return [String, nil] The callback_id
|
|
87
|
+
def callback_id
|
|
88
|
+
payload['callback_id']
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Returns the trigger_id for opening modals
|
|
92
|
+
#
|
|
93
|
+
# Slack provides a trigger_id with shortcuts that can be used
|
|
94
|
+
# to open modals within 3 seconds of receiving the shortcut.
|
|
95
|
+
#
|
|
96
|
+
# @return [String, nil] The trigger_id for views.open
|
|
97
|
+
def trigger_id
|
|
98
|
+
payload['trigger_id']
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Returns the type of shortcut (:global or :message)
|
|
102
|
+
#
|
|
103
|
+
# @return [Symbol] :message for message shortcuts (message_action),
|
|
104
|
+
# :global for global shortcuts
|
|
105
|
+
def shortcut_type
|
|
106
|
+
payload['type'] == 'message_action' ? :message : :global
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Returns the message object for message shortcuts
|
|
110
|
+
#
|
|
111
|
+
# Only available for message shortcuts (message_action type).
|
|
112
|
+
# Contains the original message that the shortcut was triggered on.
|
|
113
|
+
#
|
|
114
|
+
# @return [Hash, nil] The message object or nil for global shortcuts
|
|
115
|
+
def message
|
|
116
|
+
payload['message']
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Returns the text of the message for message shortcuts
|
|
120
|
+
#
|
|
121
|
+
# Convenience method to get the message text directly.
|
|
122
|
+
#
|
|
123
|
+
# @return [String, nil] The message text or nil if not available
|
|
124
|
+
def message_text
|
|
125
|
+
message&.dig('text')
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Top-level alias for convenience
|
|
131
|
+
ShortcutHandler = Handlers::ShortcutHandler
|
|
132
|
+
end
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BoltRb
|
|
4
|
+
module Handlers
|
|
5
|
+
# Handler for Slack view submissions (modal form submissions)
|
|
6
|
+
#
|
|
7
|
+
# This handler provides the `view` DSL for matching view_submission payloads.
|
|
8
|
+
# View submissions are triggered when users click the submit button on modals
|
|
9
|
+
# opened via views.open or views.push.
|
|
10
|
+
#
|
|
11
|
+
# @example Basic modal submission handler
|
|
12
|
+
# class CreateTicketSubmitHandler < BoltRb::ViewSubmissionHandler
|
|
13
|
+
# view 'create_ticket_modal'
|
|
14
|
+
#
|
|
15
|
+
# def handle
|
|
16
|
+
# ack
|
|
17
|
+
# # Process the form submission
|
|
18
|
+
# ticket_title = values.dig('title_block', 'title_input', 'value')
|
|
19
|
+
# end
|
|
20
|
+
# end
|
|
21
|
+
#
|
|
22
|
+
# @example Handler with validation errors
|
|
23
|
+
# class ValidatedSubmitHandler < BoltRb::ViewSubmissionHandler
|
|
24
|
+
# view 'validated_form'
|
|
25
|
+
#
|
|
26
|
+
# def handle
|
|
27
|
+
# if invalid_input?
|
|
28
|
+
# ack(response_action: 'errors', errors: { 'input_block' => 'Invalid input' })
|
|
29
|
+
# else
|
|
30
|
+
# ack
|
|
31
|
+
# process_submission
|
|
32
|
+
# end
|
|
33
|
+
# end
|
|
34
|
+
# end
|
|
35
|
+
#
|
|
36
|
+
# @example Regex-based view matching
|
|
37
|
+
# class DynamicFormHandler < BoltRb::ViewSubmissionHandler
|
|
38
|
+
# view /^form_step_/
|
|
39
|
+
#
|
|
40
|
+
# def handle
|
|
41
|
+
# ack
|
|
42
|
+
# step = callback_id.gsub('form_step_', '')
|
|
43
|
+
# # Handle based on step
|
|
44
|
+
# end
|
|
45
|
+
# end
|
|
46
|
+
class ViewSubmissionHandler < Base
|
|
47
|
+
class << self
|
|
48
|
+
# Configures which callback_id this handler responds to
|
|
49
|
+
#
|
|
50
|
+
# @param callback_id [String, Regexp] The callback_id to match (exact string or regex)
|
|
51
|
+
# @return [void]
|
|
52
|
+
#
|
|
53
|
+
# @example Match exact callback_id
|
|
54
|
+
# view 'create_ticket_modal'
|
|
55
|
+
#
|
|
56
|
+
# @example Match callback_id pattern
|
|
57
|
+
# view /^create_/
|
|
58
|
+
def view(callback_id)
|
|
59
|
+
@matcher_config = {
|
|
60
|
+
type: :view_submission,
|
|
61
|
+
callback_id: callback_id
|
|
62
|
+
}
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Determines if this handler matches the given payload
|
|
66
|
+
#
|
|
67
|
+
# Checks if the payload is a view_submission type and if the
|
|
68
|
+
# callback_id matches the configured pattern.
|
|
69
|
+
#
|
|
70
|
+
# @param payload [Hash] The incoming Slack view_submission payload
|
|
71
|
+
# @return [Boolean] true if this handler should process the submission
|
|
72
|
+
def matches?(payload)
|
|
73
|
+
return false unless matcher_config
|
|
74
|
+
return false unless payload['type'] == 'view_submission'
|
|
75
|
+
|
|
76
|
+
view_callback_id = payload.dig('view', 'callback_id')
|
|
77
|
+
return false unless view_callback_id
|
|
78
|
+
|
|
79
|
+
if matcher_config[:callback_id].is_a?(Regexp)
|
|
80
|
+
matcher_config[:callback_id].match?(view_callback_id)
|
|
81
|
+
else
|
|
82
|
+
view_callback_id == matcher_config[:callback_id]
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Returns the view object from the payload
|
|
88
|
+
#
|
|
89
|
+
# @return [Hash, nil] The view object containing callback_id, private_metadata, state, etc.
|
|
90
|
+
def view
|
|
91
|
+
payload['view']
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Returns the callback_id from the view
|
|
95
|
+
#
|
|
96
|
+
# @return [String, nil] The callback_id
|
|
97
|
+
def callback_id
|
|
98
|
+
view&.dig('callback_id')
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Returns the private_metadata from the view
|
|
102
|
+
#
|
|
103
|
+
# Private metadata is a string field you can use to pass data between
|
|
104
|
+
# the view open and submission events. Often used to store IDs.
|
|
105
|
+
#
|
|
106
|
+
# @return [String, nil] The private_metadata value
|
|
107
|
+
def private_metadata
|
|
108
|
+
view&.dig('private_metadata')
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Returns the state values from the submitted form
|
|
112
|
+
#
|
|
113
|
+
# The values hash is keyed by block_id, then action_id, then contains
|
|
114
|
+
# the input value (format depends on input type).
|
|
115
|
+
#
|
|
116
|
+
# @return [Hash] The form values hash
|
|
117
|
+
# @example Structure
|
|
118
|
+
# {
|
|
119
|
+
# 'title_block' => {
|
|
120
|
+
# 'title_input' => { 'type' => 'plain_text_input', 'value' => 'My Title' }
|
|
121
|
+
# },
|
|
122
|
+
# 'select_block' => {
|
|
123
|
+
# 'select_input' => { 'type' => 'static_select', 'selected_option' => { 'value' => 'opt1' } }
|
|
124
|
+
# }
|
|
125
|
+
# }
|
|
126
|
+
def values
|
|
127
|
+
view&.dig('state', 'values') || {}
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Returns the user ID from the payload
|
|
131
|
+
#
|
|
132
|
+
# @return [String, nil] The user ID who submitted the form
|
|
133
|
+
def user_id
|
|
134
|
+
payload.dig('user', 'id')
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Returns the response URLs for block-based responses
|
|
138
|
+
#
|
|
139
|
+
# Only present if the modal was opened from a message interaction.
|
|
140
|
+
#
|
|
141
|
+
# @return [Array<Hash>] Array of response_url objects
|
|
142
|
+
def response_urls
|
|
143
|
+
payload['response_urls'] || []
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Returns the hash value of the submitted view
|
|
147
|
+
#
|
|
148
|
+
# Used for optimistic locking when updating views.
|
|
149
|
+
#
|
|
150
|
+
# @return [String, nil] The view hash
|
|
151
|
+
def view_hash
|
|
152
|
+
view&.dig('hash')
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# Top-level alias for convenience
|
|
158
|
+
ViewSubmissionHandler = Handlers::ViewSubmissionHandler
|
|
159
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BoltRb
|
|
4
|
+
module Middleware
|
|
5
|
+
# Base class for all middleware
|
|
6
|
+
#
|
|
7
|
+
# Middleware classes should inherit from this and override #call
|
|
8
|
+
# to implement custom behavior. The default implementation simply
|
|
9
|
+
# yields to the next middleware in the chain.
|
|
10
|
+
#
|
|
11
|
+
# @example Custom middleware
|
|
12
|
+
# class AuthMiddleware < BoltRb::Middleware::Base
|
|
13
|
+
# def call(context)
|
|
14
|
+
# if authorized?(context)
|
|
15
|
+
# yield if block_given?
|
|
16
|
+
# else
|
|
17
|
+
# context.respond("Unauthorized")
|
|
18
|
+
# end
|
|
19
|
+
# end
|
|
20
|
+
# end
|
|
21
|
+
class Base
|
|
22
|
+
# Processes the request through this middleware
|
|
23
|
+
#
|
|
24
|
+
# Override this method in subclasses to add custom behavior.
|
|
25
|
+
# Always call yield to continue the middleware chain.
|
|
26
|
+
#
|
|
27
|
+
# @param context [BoltRb::Context] The request context
|
|
28
|
+
# @yield Continues to the next middleware in the chain
|
|
29
|
+
# @return [void]
|
|
30
|
+
def call(context)
|
|
31
|
+
yield if block_given?
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BoltRb
|
|
4
|
+
module Middleware
|
|
5
|
+
# Executes a chain of middleware in order
|
|
6
|
+
#
|
|
7
|
+
# The middleware chain follows the onion model where each middleware
|
|
8
|
+
# can execute code before and after the next middleware in the chain.
|
|
9
|
+
# This is similar to Rack middleware or Rails around_action filters.
|
|
10
|
+
#
|
|
11
|
+
# @example Basic usage
|
|
12
|
+
# chain = Chain.new([LoggingMiddleware, AuthMiddleware])
|
|
13
|
+
# chain.call(context) do
|
|
14
|
+
# # Handler code runs after all middleware have yielded
|
|
15
|
+
# end
|
|
16
|
+
class Chain
|
|
17
|
+
# Creates a new middleware chain
|
|
18
|
+
#
|
|
19
|
+
# @param middleware_classes [Array<Class>] Array of middleware classes to instantiate and run
|
|
20
|
+
def initialize(middleware_classes)
|
|
21
|
+
@middleware_classes = middleware_classes
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Executes the middleware chain
|
|
25
|
+
#
|
|
26
|
+
# Each middleware is instantiated and called in order. The provided
|
|
27
|
+
# block is called after all middleware have yielded. Middleware can
|
|
28
|
+
# stop the chain by not yielding.
|
|
29
|
+
#
|
|
30
|
+
# @param context [BoltRb::Context] The request context
|
|
31
|
+
# @yield The handler to run after middleware processing
|
|
32
|
+
# @return [void]
|
|
33
|
+
def call(context, &block)
|
|
34
|
+
chain = @middleware_classes.map(&:new)
|
|
35
|
+
run_chain(chain, context, &block)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
# Recursively runs through the middleware chain
|
|
41
|
+
#
|
|
42
|
+
# @param chain [Array<Base>] Remaining middleware instances
|
|
43
|
+
# @param context [BoltRb::Context] The request context
|
|
44
|
+
# @yield The handler to run when chain is empty
|
|
45
|
+
# @return [void]
|
|
46
|
+
def run_chain(chain, context, &block)
|
|
47
|
+
if chain.empty?
|
|
48
|
+
block.call
|
|
49
|
+
else
|
|
50
|
+
middleware = chain.shift
|
|
51
|
+
middleware.call(context) do
|
|
52
|
+
run_chain(chain, context, &block)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BoltRb
|
|
4
|
+
module Middleware
|
|
5
|
+
# Logging middleware for request/response logging
|
|
6
|
+
#
|
|
7
|
+
# Logs the event type being processed and the time taken to process it.
|
|
8
|
+
# This is useful for debugging and monitoring your Bolt application.
|
|
9
|
+
#
|
|
10
|
+
# @example Output
|
|
11
|
+
# [BoltRb] Processing event:message
|
|
12
|
+
# [BoltRb] Completed event:message in 12.34ms
|
|
13
|
+
class Logging < Base
|
|
14
|
+
# Processes the request with logging
|
|
15
|
+
#
|
|
16
|
+
# Logs the event type before processing and the elapsed time after.
|
|
17
|
+
#
|
|
18
|
+
# @param context [BoltRb::Context] The request context
|
|
19
|
+
# @yield Continues to the next middleware in the chain
|
|
20
|
+
# @return [void]
|
|
21
|
+
def call(context)
|
|
22
|
+
event_type = determine_event_type(context.payload)
|
|
23
|
+
BoltRb.logger.info "[BoltRb] Processing #{event_type}"
|
|
24
|
+
started = Time.now
|
|
25
|
+
|
|
26
|
+
yield if block_given?
|
|
27
|
+
|
|
28
|
+
elapsed = ((Time.now - started) * 1000).round(2)
|
|
29
|
+
BoltRb.logger.info "[BoltRb] Completed #{event_type} in #{elapsed}ms"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
# Determines the event type from the payload
|
|
35
|
+
#
|
|
36
|
+
# Handles various payload formats from Slack:
|
|
37
|
+
# - Events API: event.type
|
|
38
|
+
# - Slash commands: command
|
|
39
|
+
# - Block actions: type with action_ids
|
|
40
|
+
# - Shortcuts: type with callback_id
|
|
41
|
+
#
|
|
42
|
+
# @param payload [Hash] The raw Slack payload
|
|
43
|
+
# @return [String] A descriptive event type string
|
|
44
|
+
def determine_event_type(payload)
|
|
45
|
+
if payload['event']
|
|
46
|
+
"event:#{payload['event']['type']}"
|
|
47
|
+
elsif payload['command']
|
|
48
|
+
"command:#{payload['command']}"
|
|
49
|
+
elsif payload['type'] == 'block_actions'
|
|
50
|
+
action_ids = payload['actions']&.map { |a| a['action_id'] }&.join(',')
|
|
51
|
+
"action:#{action_ids}"
|
|
52
|
+
elsif payload['type'] == 'shortcut' || payload['type'] == 'message_action'
|
|
53
|
+
"shortcut:#{payload['callback_id']}"
|
|
54
|
+
else
|
|
55
|
+
'unknown'
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BoltRb
|
|
4
|
+
# Router maintains a registry of handlers and routes incoming payloads
|
|
5
|
+
# to the appropriate handlers based on their matching criteria.
|
|
6
|
+
#
|
|
7
|
+
# The router acts as the central dispatch mechanism for Bolt applications,
|
|
8
|
+
# collecting registered handlers and determining which ones should process
|
|
9
|
+
# a given Slack payload.
|
|
10
|
+
#
|
|
11
|
+
# @example Basic usage
|
|
12
|
+
# router = BoltRb::Router.new
|
|
13
|
+
#
|
|
14
|
+
# router.register(MyMessageHandler)
|
|
15
|
+
# router.register(MyCommandHandler)
|
|
16
|
+
#
|
|
17
|
+
# handlers = router.route(payload)
|
|
18
|
+
# handlers.each { |h| h.new(context).call }
|
|
19
|
+
#
|
|
20
|
+
# @example Using the global router
|
|
21
|
+
# BoltRb.router.register(MyHandler)
|
|
22
|
+
# BoltRb.router.route(payload)
|
|
23
|
+
class Router
|
|
24
|
+
# Creates a new Router instance with an empty handler registry
|
|
25
|
+
def initialize
|
|
26
|
+
@handlers = []
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Registers a handler class with the router
|
|
30
|
+
#
|
|
31
|
+
# The handler class should respond to .matches?(payload) to determine
|
|
32
|
+
# if it should process a given payload. Duplicate registrations are ignored.
|
|
33
|
+
#
|
|
34
|
+
# @param handler_class [Class] A handler class (EventHandler, CommandHandler, etc.)
|
|
35
|
+
# @return [void]
|
|
36
|
+
#
|
|
37
|
+
# @example
|
|
38
|
+
# router.register(MyMessageHandler)
|
|
39
|
+
def register(handler_class)
|
|
40
|
+
@handlers << handler_class unless @handlers.include?(handler_class)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Routes a payload to all matching handlers
|
|
44
|
+
#
|
|
45
|
+
# Iterates through all registered handlers and returns those whose
|
|
46
|
+
# .matches?(payload) method returns true.
|
|
47
|
+
#
|
|
48
|
+
# @param payload [Hash] The incoming Slack payload
|
|
49
|
+
# @return [Array<Class>] Array of handler classes that match the payload
|
|
50
|
+
#
|
|
51
|
+
# @example
|
|
52
|
+
# payload = { 'event' => { 'type' => 'message', 'text' => 'hello' } }
|
|
53
|
+
# handlers = router.route(payload)
|
|
54
|
+
# # => [MessageHandler, HelloHandler]
|
|
55
|
+
def route(payload)
|
|
56
|
+
@handlers.select { |handler| handler.matches?(payload) }
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Returns the number of registered handlers
|
|
60
|
+
#
|
|
61
|
+
# @return [Integer] The count of registered handlers
|
|
62
|
+
def handler_count
|
|
63
|
+
@handlers.length
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Removes all registered handlers
|
|
67
|
+
#
|
|
68
|
+
# Useful for testing or reconfiguration scenarios.
|
|
69
|
+
#
|
|
70
|
+
# @return [void]
|
|
71
|
+
def clear
|
|
72
|
+
@handlers.clear
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|