hooks-ruby 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: 2a1dfcc57a6bae5c2e5bd01f7e6165218220f04258ac8fab3ab256b685454bb6
4
- data.tar.gz: 724dbe5463d5bf223ef9652e566b448f38a4270d0131446e302236685ba43710
3
+ metadata.gz: ae1757617b84df588de7eb343799b93d1dfc6326c3d54ab1a2d578aa2d3e3b8e
4
+ data.tar.gz: a39cfa8162346d536ab5a543dfcf7517162282ba3a74098cd276bc76c636a41c
5
5
  SHA512:
6
- metadata.gz: d21c5cce4267f7d5209e495415c90b21375686e1f8d65d9d2080d3d3aa2677344a48cbc1167e4e83bff6a27223e6f12d891a6416ad122b54ee0d36832dd08311
7
- data.tar.gz: 8b0fd57ee957d8eb8d58d3556f7f4ab145a7eb5cac82286c2cefd439a77bc28f6e03abe38484154b308b59b3b30e69bd749fec968fe053ebb0b4f9560ae0eb17
6
+ metadata.gz: da5353fda88b8d348e7b743e155642376ad886b5e77a38cb08148a9a6fbb5bece88c099125bb4738d41a09e3d3e0b5c29bd317bafa3789caa595d02f1c7217a2
7
+ data.tar.gz: 0ba9c613e80a889da7b1fd7b6b4b84b53190aae6db27184d7caa0a90806c3d266f0fff06c4bff1cef702c0879f772056fde5c1b697d817852130167eb0724ebd
data/README.md CHANGED
@@ -58,7 +58,7 @@ Here is a very high-level overview of how Hooks works:
58
58
  ```ruby
59
59
  # file: plugins/handlers/my_custom_handler.rb
60
60
  class MyCustomHandler < Hooks::Plugins::Handlers::Base
61
- def call(payload:, headers:, config:)
61
+ def call(payload:, headers:, env:, config:)
62
62
  # Process the incoming webhook - optionally use the payload and headers
63
63
  # to perform some action or validation
64
64
  # For this example, we will just return a success message
@@ -233,7 +233,7 @@ Create custom handler plugins in the `plugins/handlers` directory to process inc
233
233
  ```ruby
234
234
  # file: plugins/handlers/hello_handler.rb
235
235
  class HelloHandler < Hooks::Plugins::Handlers::Base
236
- def call(payload:, headers:, config:)
236
+ def call(payload:, headers:, env:, config:)
237
237
  # Process the incoming webhook - optionally use the payload and headers
238
238
  # to perform some action or validation
239
239
  # For this example, we will just return a success message
@@ -251,7 +251,7 @@ And another handler plugin for the `/goodbye` endpoint:
251
251
  ```ruby
252
252
  # file: plugins/handlers/goodbye_handler.rb
253
253
  class GoodbyeHandler < Hooks::Plugins::Handlers::Base
254
- def call(payload:, headers:, config:)
254
+ def call(payload:, headers:, env:, config:)
255
255
  # Ditto for the goodbye endpoint
256
256
  {
257
257
  message: "goodbye webhook processed successfully",
data/lib/hooks/app/api.rb CHANGED
@@ -5,7 +5,9 @@ require "json"
5
5
  require "securerandom"
6
6
  require_relative "helpers"
7
7
  require_relative "auth/auth"
8
+ require_relative "rack_env_builder"
8
9
  require_relative "../plugins/handlers/base"
10
+ require_relative "../plugins/handlers/error"
9
11
  require_relative "../plugins/handlers/default"
10
12
  require_relative "../core/logger_factory"
11
13
  require_relative "../core/log"
@@ -65,41 +67,27 @@ module Hooks
65
67
  Core::LogContext.with(request_context) do
66
68
  begin
67
69
  # Build Rack environment for lifecycle hooks
68
- rack_env = {
69
- "REQUEST_METHOD" => request.request_method,
70
- "PATH_INFO" => request.path_info,
71
- "QUERY_STRING" => request.query_string,
72
- "HTTP_VERSION" => request.env["HTTP_VERSION"],
73
- "REQUEST_URI" => request.url,
74
- "SERVER_NAME" => request.env["SERVER_NAME"],
75
- "SERVER_PORT" => request.env["SERVER_PORT"],
76
- "CONTENT_TYPE" => request.content_type,
77
- "CONTENT_LENGTH" => request.content_length,
78
- "REMOTE_ADDR" => request.env["REMOTE_ADDR"],
79
- "hooks.request_id" => request_id,
80
- "hooks.handler" => handler_class_name,
81
- "hooks.endpoint_config" => endpoint_config,
82
- "hooks.start_time" => start_time.iso8601,
83
- "hooks.full_path" => full_path
84
- }
85
-
86
- # Add HTTP headers to environment
87
- headers.each do |key, value|
88
- env_key = "HTTP_#{key.upcase.tr('-', '_')}"
89
- rack_env[env_key] = value
90
- end
70
+ rack_env_builder = RackEnvBuilder.new(
71
+ request,
72
+ headers,
73
+ request_context,
74
+ endpoint_config,
75
+ start_time,
76
+ full_path
77
+ )
78
+ rack_env = rack_env_builder.build
91
79
 
92
80
  # Call lifecycle hooks: on_request
93
81
  Core::PluginLoader.lifecycle_plugins.each do |plugin|
94
82
  plugin.on_request(rack_env)
95
83
  end
96
84
 
97
- enforce_request_limits(config)
85
+ enforce_request_limits(config, request_context)
98
86
  request.body.rewind
99
87
  raw_body = request.body.read
100
88
 
101
89
  if endpoint_config[:auth]
102
- validate_auth!(raw_body, headers, endpoint_config, config)
90
+ validate_auth!(raw_body, headers, endpoint_config, config, request_context)
103
91
  end
104
92
 
105
93
  payload = parse_payload(raw_body, headers, symbolize: false)
@@ -109,6 +97,7 @@ module Hooks
109
97
  response = handler.call(
110
98
  payload:,
111
99
  headers: processed_headers,
100
+ env: rack_env,
112
101
  config: endpoint_config
113
102
  )
114
103
 
@@ -122,22 +111,50 @@ module Hooks
122
111
  status 200
123
112
  content_type "application/json"
124
113
  response.to_json
114
+ rescue Hooks::Plugins::Handlers::Error => e
115
+ # Handler called error! method - immediately return error response and exit the request
116
+ log.debug("handler #{handler_class_name} called `error!` method")
117
+
118
+ error_response = nil
119
+
120
+ status e.status
121
+ case e.body
122
+ when String
123
+ content_type "text/plain"
124
+ error_response = e.body
125
+ else
126
+ content_type "application/json"
127
+ error_response = e.body.to_json
128
+ end
129
+
130
+ return error_response
125
131
  rescue StandardError => e
126
- # Call lifecycle hooks: on_error
132
+ err_msg = "Error processing webhook event with handler: #{handler_class_name} - #{e.message} " \
133
+ "- request_id: #{request_id} - path: #{full_path} - method: #{http_method} - " \
134
+ "backtrace: #{e.backtrace.join("\n")}"
135
+ log.error(err_msg)
136
+
137
+ # call lifecycle hooks: on_error if the rack_env is available
138
+ # if the rack_env is not available, it means the error occurred before we could build it
127
139
  if defined?(rack_env)
128
140
  Core::PluginLoader.lifecycle_plugins.each do |plugin|
129
141
  plugin.on_error(e, rack_env)
130
142
  end
131
143
  end
132
144
 
133
- log.error("an error occuring during the processing of a webhook event - #{e.message}")
145
+ # construct a standardized error response
134
146
  error_response = {
135
- error: e.message,
136
- code: determine_error_code(e),
147
+ error: "server_error",
148
+ message: "an unexpected error occurred while processing the request",
137
149
  request_id:
138
150
  }
139
- error_response[:backtrace] = e.backtrace unless config[:production]
140
- status error_response[:code]
151
+
152
+ # enrich the error response with details if not in production
153
+ error_response[:backtrace] = e.backtrace.join("\n") unless config[:production]
154
+ error_response[:message] = e.message unless config[:production]
155
+ error_response[:handler] = handler_class_name unless config[:production]
156
+
157
+ status determine_error_code(e)
141
158
  content_type "application/json"
142
159
  error_response.to_json
143
160
  end
@@ -16,30 +16,45 @@ module Hooks
16
16
  # @param headers [Hash] The request headers.
17
17
  # @param endpoint_config [Hash] The endpoint configuration, must include :auth key.
18
18
  # @param global_config [Hash] The global configuration (optional, for compatibility).
19
+ # @param request_context [Hash] Context for the request, e.g. request ID, path, handler (optional).
19
20
  # @raise [StandardError] Raises error if authentication fails or is misconfigured.
20
21
  # @return [void]
21
22
  # @note This method will halt execution with an error if authentication fails.
22
- def validate_auth!(payload, headers, endpoint_config, global_config = {})
23
+ def validate_auth!(payload, headers, endpoint_config, global_config = {}, request_context = {})
23
24
  auth_config = endpoint_config[:auth]
25
+ request_id = request_context&.dig(:request_id)
24
26
 
25
27
  # Ensure auth type is present and valid
26
28
  auth_type = auth_config&.dig(:type)
27
29
  unless auth_type&.is_a?(String) && !auth_type.strip.empty?
28
- error!("authentication configuration missing or invalid", 500)
30
+ log.error("authentication configuration missing or invalid - request_id: #{request_id}")
31
+ error!({
32
+ error: "authentication_configuration_error",
33
+ message: "authentication configuration missing or invalid",
34
+ request_id:
35
+ }, 500)
29
36
  end
30
37
 
31
38
  # Get auth plugin from loaded plugins registry (boot-time loaded only)
32
39
  begin
33
40
  auth_class = Core::PluginLoader.get_auth_plugin(auth_type)
34
41
  rescue => e
35
- log.error("failed to load auth plugin '#{auth_type}': #{e.message}")
36
- error!("unsupported auth type '#{auth_type}'", 400)
42
+ log.error("failed to load auth plugin '#{auth_type}': #{e.message} - request_id: #{request_id}")
43
+ error!({
44
+ error: "authentication_plugin_error",
45
+ message: "unsupported auth type '#{auth_type}'",
46
+ request_id:
47
+ }, 400)
37
48
  end
38
49
 
39
50
  log.debug("validating auth for request with auth_class: #{auth_class.name}")
40
51
  unless auth_class.valid?(payload:, headers:, config: endpoint_config)
41
- log.warn("authentication failed for request with auth_class: #{auth_class.name}")
42
- error!("authentication failed", 401)
52
+ log.warn("authentication failed for request with auth_class: #{auth_class.name} - request_id: #{request_id}")
53
+ error!({
54
+ error: "authentication_failed",
55
+ message: "authentication failed",
56
+ request_id:
57
+ }, 401)
43
58
  end
44
59
  end
45
60
 
@@ -27,20 +27,36 @@ module Hooks
27
27
  # :nocov:
28
28
  proc do
29
29
  request_id = uuid
30
+ start_time = Time.now
30
31
 
31
32
  # Use captured values
32
33
  config = captured_config
33
34
  log = captured_logger
34
35
 
36
+ full_path = "#{config[:root_path]}/#{params[:path]}"
37
+
38
+ handler_class_name = "DefaultHandler"
39
+ http_method = "post"
40
+
35
41
  # Set request context for logging
36
42
  request_context = {
37
43
  request_id:,
38
- path: "/#{params[:path]}",
39
- handler: "DefaultHandler"
44
+ path: full_path,
45
+ handler: handler_class_name
40
46
  }
41
47
 
42
48
  Hooks::Core::LogContext.with(request_context) do
43
49
  begin
50
+ rack_env_builder = RackEnvBuilder.new(
51
+ request,
52
+ headers,
53
+ request_context,
54
+ config,
55
+ start_time,
56
+ full_path
57
+ )
58
+ rack_env = rack_env_builder.build
59
+
44
60
  # Enforce request limits
45
61
  enforce_request_limits(config)
46
62
 
@@ -58,32 +74,34 @@ module Hooks
58
74
  response = handler.call(
59
75
  payload:,
60
76
  headers:,
77
+ env: rack_env,
61
78
  config: {}
62
79
  )
63
80
 
64
- log.info "request processed successfully with default handler (id: #{request_id})"
65
-
66
- # Return response as JSON string when using txt format
81
+ log.info("successfully processed webhook event with handler: #{handler_class_name}")
82
+ log.debug("processing duration: #{Time.now - start_time}s")
67
83
  status 200
68
84
  content_type "application/json"
69
- (response || { status: "ok" }).to_json
70
-
85
+ response.to_json
71
86
  rescue StandardError => e
72
- log.error "request failed: #{e.message} (id: #{request_id})"
87
+ err_msg = "Error processing webhook event with handler: #{handler_class_name} - #{e.message} " \
88
+ "- request_id: #{request_id} - path: #{full_path} - method: #{http_method} - " \
89
+ "backtrace: #{e.backtrace.join("\n")}"
90
+ log.error(err_msg)
73
91
 
74
- # Return error response
92
+ # construct a standardized error response
75
93
  error_response = {
76
- error: e.message,
77
- code: determine_error_code(e),
78
- request_id: request_id
94
+ error: "server_error",
95
+ message: "an unexpected error occurred while processing the request",
96
+ request_id:
79
97
  }
80
98
 
81
- # Add backtrace in all environments except production
82
- unless config[:production] == true
83
- error_response[:backtrace] = e.backtrace
84
- end
99
+ # enrich the error response with details if not in production
100
+ error_response[:backtrace] = e.backtrace.join("\n") unless config[:production]
101
+ error_response[:message] = e.message unless config[:production]
102
+ error_response[:handler] = handler_class_name unless config[:production]
85
103
 
86
- status error_response[:code]
104
+ status determine_error_code(e)
87
105
  content_type "application/json"
88
106
  error_response.to_json
89
107
  end
@@ -17,10 +17,11 @@ module Hooks
17
17
  # Enforce request size and timeout limits
18
18
  #
19
19
  # @param config [Hash] The configuration hash, must include :request_limit
20
+ # @param request_context [Hash] Context for the request, e.g. request ID (optional)
20
21
  # @raise [StandardError] Halts with error if request body is too large
21
22
  # @return [void]
22
23
  # @note Timeout enforcement should be handled at the server level (e.g., Puma)
23
- def enforce_request_limits(config)
24
+ def enforce_request_limits(config, request_context = {})
24
25
  # Optimized content length check - check most common sources first
25
26
  content_length = request.content_length if respond_to?(:request) && request.respond_to?(:content_length)
26
27
 
@@ -34,7 +35,8 @@ module Hooks
34
35
  content_length = content_length&.to_i
35
36
 
36
37
  if content_length && content_length > config[:request_limit]
37
- error!("request body too large", 413)
38
+ request_id = request_context&.dig(:request_id)
39
+ error!({ error: "request_body_too_large", message: "request body too large", request_id: }, 413)
38
40
  end
39
41
 
40
42
  # Note: Timeout enforcement would typically be handled at the server level (Puma, etc.)
@@ -76,13 +78,14 @@ module Hooks
76
78
  # @return [Object] An instance of the loaded handler class
77
79
  # @raise [StandardError] If handler cannot be found
78
80
  def load_handler(handler_class_name)
79
- # Get handler class from loaded plugins registry (boot-time loaded only)
80
- begin
81
- handler_class = Core::PluginLoader.get_handler_plugin(handler_class_name)
82
- return handler_class.new
83
- rescue => e
84
- error!("failed to get handler '#{handler_class_name}': #{e.message}", 500)
85
- end
81
+ # Get handler class from loaded plugins registry (the registry is populated at boot time)
82
+ # NOTE: We create a new instance per request (not reuse boot-time instances) because:
83
+ # - Security: Prevents state pollution and information leakage between requests
84
+ # - Thread Safety: Avoids race conditions from shared instance state
85
+ # - Performance: Handler instantiation is fast; reusing instances provides minimal gain
86
+ # - Memory: Allows garbage collection of short-lived objects (Ruby GC optimization)
87
+ handler_class = Core::PluginLoader.get_handler_plugin(handler_class_name)
88
+ return handler_class.new
86
89
  end
87
90
 
88
91
  private
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hooks
4
+ module App
5
+ # Builds Rack environment hash for lifecycle hooks and handler processing
6
+ #
7
+ # This class centralizes the construction of the Rack environment that gets
8
+ # passed to lifecycle hooks and handlers, ensuring consistency and making
9
+ # it easy to reference the environment structure.
10
+ #
11
+ # @example Building a Rack environment
12
+ # builder = RackEnvBuilder.new(request, headers, request_context)
13
+ # rack_env = builder.build
14
+ #
15
+ class RackEnvBuilder
16
+ # Initialize the builder with required components
17
+ #
18
+ # @param request [Grape::Request] The Grape request object
19
+ # @param headers [Hash] Request headers hash
20
+ # @param request_context [Hash] Request context containing metadata
21
+ # @option request_context [String] :request_id Unique request identifier
22
+ # @option request_context [String] :handler Handler class name
23
+ # @param endpoint_config [Hash] Endpoint configuration
24
+ # @param start_time [Time] Request start time
25
+ # @param full_path [String] Full request path including root path
26
+ def initialize(request, headers, request_context, endpoint_config, start_time, full_path)
27
+ @request = request
28
+ @headers = headers
29
+ @request_context = request_context
30
+ @endpoint_config = endpoint_config
31
+ @start_time = start_time
32
+ @full_path = full_path
33
+ end
34
+
35
+ # Build the Rack environment hash
36
+ #
37
+ # Constructs a hash containing standard Rack environment variables
38
+ # plus Hooks-specific extensions for lifecycle hooks and handlers.
39
+ #
40
+ # @return [Hash] Complete Rack environment hash
41
+ def build
42
+ rack_env = build_base_environment
43
+ add_http_headers(rack_env)
44
+ rack_env
45
+ end
46
+
47
+ private
48
+
49
+ # Build the base Rack environment with standard and Hooks-specific variables
50
+ # This pretty much creates everything plus the kitchen sink. It will be very rich in information
51
+ # and will be used by lifecycle hooks and handlers to access request metadata.
52
+ #
53
+ # @return [Hash] Base environment hash
54
+ def build_base_environment
55
+ {
56
+ "REQUEST_METHOD" => @request.request_method,
57
+ "PATH_INFO" => @request.path_info,
58
+ "QUERY_STRING" => @request.query_string,
59
+ "HTTP_VERSION" => @request.env["HTTP_VERSION"],
60
+ "REQUEST_URI" => @request.url,
61
+ "SERVER_NAME" => @request.env["SERVER_NAME"],
62
+ "SERVER_PORT" => @request.env["SERVER_PORT"],
63
+ "CONTENT_TYPE" => @request.content_type,
64
+ "CONTENT_LENGTH" => @request.content_length,
65
+ "REMOTE_ADDR" => @request.env["REMOTE_ADDR"],
66
+ "hooks.request_id" => @request_context[:request_id],
67
+ "hooks.handler" => @request_context[:handler],
68
+ "hooks.endpoint_config" => @endpoint_config,
69
+ "hooks.start_time" => @start_time.iso8601,
70
+ "hooks.full_path" => @full_path
71
+ }
72
+ end
73
+
74
+ # Add HTTP headers to the environment with proper Rack naming convention
75
+ #
76
+ # @param rack_env [Hash] Environment hash to modify
77
+ def add_http_headers(rack_env)
78
+ @headers.each do |key, value|
79
+ env_key = "HTTP_#{key.upcase.tr('-', '_')}"
80
+ rack_env[env_key] = value
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative "../../core/global_components"
4
4
  require_relative "../../core/component_access"
5
+ require_relative "error"
5
6
 
6
7
  module Hooks
7
8
  module Plugins
@@ -16,12 +17,35 @@ module Hooks
16
17
  #
17
18
  # @param payload [Hash, String] Parsed request body (JSON Hash) or raw string
18
19
  # @param headers [Hash] HTTP headers (string keys, optionally normalized - default is normalized)
20
+ # @param env [Hash] Rack environment (contains the request context, headers, etc - very rich context)
19
21
  # @param config [Hash] Merged endpoint configuration including opts section (symbolized keys)
20
22
  # @return [Hash, String, nil] Response body (will be auto-converted to JSON)
21
23
  # @raise [NotImplementedError] if not implemented by subclass
22
- def call(payload:, headers:, config:)
24
+ def call(payload:, headers:, env:, config:)
23
25
  raise NotImplementedError, "Handler must implement #call method"
24
26
  end
27
+
28
+ # Terminate request processing with a custom error response
29
+ #
30
+ # This method provides the same interface as Grape's `error!` method,
31
+ # allowing handlers to immediately stop processing and return a specific
32
+ # error response to the client.
33
+ #
34
+ # @param body [Object] The error body/data to return to the client
35
+ # @param status [Integer] The HTTP status code to return (default: 500)
36
+ # @raise [Hooks::Plugins::Handlers::Error] Always raises to terminate processing
37
+ #
38
+ # @example Return a custom error with status 400
39
+ # error!({ error: "validation_failed", message: "Invalid payload" }, 400)
40
+ #
41
+ # @example Return a simple string error with status 401
42
+ # error!("Unauthorized", 401)
43
+ #
44
+ # @example Return an error with default 500 status
45
+ # error!({ error: "internal_error", message: "Something went wrong" })
46
+ def error!(body, status = 500)
47
+ raise Error.new(body, status)
48
+ end
25
49
  end
26
50
  end
27
51
  end
@@ -20,6 +20,7 @@ class DefaultHandler < Hooks::Plugins::Handlers::Base
20
20
  #
21
21
  # @param payload [Hash, String] The webhook payload (parsed JSON or raw string)
22
22
  # @param headers [Hash<String, String>] HTTP headers from the webhook request
23
+ # @param env [Hash] Rack environment (contains the request context, headers, config, etc - very rich context)
23
24
  # @param config [Hash] Endpoint configuration containing handler options
24
25
  # @return [Hash] Response indicating successful processing
25
26
  # @option config [Hash] :opts Additional handler-specific configuration options
@@ -29,10 +30,11 @@ class DefaultHandler < Hooks::Plugins::Handlers::Base
29
30
  # response = handler.call(
30
31
  # payload: { "event" => "push" },
31
32
  # headers: { "Content-Type" => "application/json" },
33
+ # env: { "REQUEST_METHOD" => "POST", "hooks.request_id" => "12345" },
32
34
  # config: { opts: {} }
33
35
  # )
34
36
  # # => { message: "webhook processed successfully", handler: "DefaultHandler", timestamp: "..." }
35
- def call(payload:, headers:, config:)
37
+ def call(payload:, headers:, env:, config:)
36
38
 
37
39
  log.info("🔔 Default handler invoked for webhook 🔔")
38
40
 
@@ -41,6 +43,10 @@ class DefaultHandler < Hooks::Plugins::Handlers::Base
41
43
  log.debug("received payload: #{payload.inspect}")
42
44
  end
43
45
 
46
+ if env
47
+ log.debug("default handler got a request with the following request_id: #{env['hooks.request_id']}")
48
+ end
49
+
44
50
  {
45
51
  message: "webhook processed successfully",
46
52
  handler: "DefaultHandler",
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hooks
4
+ module Plugins
5
+ module Handlers
6
+ # Custom exception class for handler errors
7
+ #
8
+ # This exception is used when handlers call the `error!` method to
9
+ # immediately terminate request processing and return a specific error response.
10
+ # It carries the error details back to the Grape API context where it can be
11
+ # properly formatted and returned to the client.
12
+ #
13
+ # @example Usage in handler
14
+ # error!({ error: "validation_failed", message: "Invalid payload" }, 400)
15
+ #
16
+ # @see Hooks::Plugins::Handlers::Base#error!
17
+ class Error < StandardError
18
+ # @return [Object] The error body/data to return to the client
19
+ attr_reader :body
20
+
21
+ # @return [Integer] The HTTP status code to return
22
+ attr_reader :status
23
+
24
+ # Initialize a new handler error
25
+ #
26
+ # @param body [Object] The error body/data to return to the client
27
+ # @param status [Integer] The HTTP status code to return (default: 500)
28
+ def initialize(body, status = 500)
29
+ @body = body
30
+ @status = status.to_i
31
+ super("Handler error: #{status} - #{body}")
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
data/lib/hooks/version.rb CHANGED
@@ -4,5 +4,5 @@
4
4
  module Hooks
5
5
  # Current version of the Hooks webhook framework
6
6
  # @return [String] The version string following semantic versioning
7
- VERSION = "0.1.0".freeze
7
+ VERSION = "0.2.0".freeze
8
8
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hooks-ruby
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
  - github
@@ -129,6 +129,7 @@ files:
129
129
  - lib/hooks/app/endpoints/health.rb
130
130
  - lib/hooks/app/endpoints/version.rb
131
131
  - lib/hooks/app/helpers.rb
132
+ - lib/hooks/app/rack_env_builder.rb
132
133
  - lib/hooks/core/builder.rb
133
134
  - lib/hooks/core/component_access.rb
134
135
  - lib/hooks/core/config_loader.rb
@@ -145,6 +146,7 @@ files:
145
146
  - lib/hooks/plugins/auth/timestamp_validator.rb
146
147
  - lib/hooks/plugins/handlers/base.rb
147
148
  - lib/hooks/plugins/handlers/default.rb
149
+ - lib/hooks/plugins/handlers/error.rb
148
150
  - lib/hooks/plugins/instruments/failbot.rb
149
151
  - lib/hooks/plugins/instruments/failbot_base.rb
150
152
  - lib/hooks/plugins/instruments/stats.rb