Skip to content

Commit 3741202

Browse files
committed
split out helpers to their own file
1 parent c88c43d commit 3741202

File tree

2 files changed

+111
-101
lines changed

2 files changed

+111
-101
lines changed

lib/hooks/app/api.rb

Lines changed: 4 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
require "grape"
44
require "json"
55
require "securerandom"
6+
require_relative "helpers"
67
require_relative "../handlers/base"
78
require_relative "../handlers/default"
89
require_relative "../core/logger_factory"
@@ -12,6 +13,8 @@ module Hooks
1213
module App
1314
# Factory for creating configured Grape API classes
1415
class API
16+
include Hooks::App::Helpers
17+
1518
# Create a new configured API class
1619
def self.create(config:, endpoints:, log:)
1720
# Store startup time for uptime calculation
@@ -40,107 +43,7 @@ def self.create(config:, endpoints:, log:)
4043
# Use class_eval to dynamically define routes
4144
api_class.class_eval do
4245
# Define helper methods first, before routes
43-
helpers do
44-
# Enforce request size and timeout limits
45-
def enforce_request_limits(config)
46-
# Check content length (handle different header formats and sources)
47-
content_length = headers["Content-Length"] || headers["CONTENT_LENGTH"] ||
48-
headers["content-length"] || headers["HTTP_CONTENT_LENGTH"] ||
49-
env["CONTENT_LENGTH"] || env["HTTP_CONTENT_LENGTH"]
50-
51-
# Also try to get from request object directly
52-
content_length ||= request.content_length if respond_to?(:request) && request.respond_to?(:content_length)
53-
54-
content_length = content_length&.to_i
55-
56-
if content_length && content_length > config[:request_limit]
57-
error!("request body too large", 413)
58-
end
59-
60-
# Note: Timeout enforcement would typically be handled at the server level (Puma, etc.)
61-
end
62-
63-
# Verify the incoming request using the configured authentication method
64-
def validate_auth!(payload, headers, endpoint_config)
65-
auth_config = endpoint_config[:auth]
66-
auth_plugin_type = auth_config[:type].downcase
67-
secret_env_key = auth_config[:secret_env_key]
68-
69-
return unless secret_env_key
70-
71-
secret = ENV[secret_env_key]
72-
unless secret
73-
error!("secret '#{secret_env_key}' not found in environment", 500)
74-
end
75-
76-
auth_class = nil
77-
78-
case auth_plugin_type
79-
when "hmac"
80-
auth_class = Plugins::Auth::HMAC
81-
when "shared_secret"
82-
auth_class = Plugins::Auth::SharedSecret
83-
else
84-
error!("Custom validators not implemented in POC", 500)
85-
end
86-
87-
unless auth_class.valid?(
88-
payload:,
89-
headers:,
90-
secret:,
91-
config: endpoint_config
92-
)
93-
error!("authentication failed", 401)
94-
end
95-
end
96-
97-
# Parse request payload
98-
def parse_payload(raw_body, headers, symbolize: true)
99-
content_type = headers["Content-Type"] || headers["CONTENT_TYPE"] || headers["content-type"] || headers["HTTP_CONTENT_TYPE"]
100-
101-
# Try to parse as JSON if content type suggests it or if it looks like JSON
102-
if content_type&.include?("application/json") || (raw_body.strip.start_with?("{", "[") rescue false)
103-
begin
104-
parsed_payload = JSON.parse(raw_body)
105-
parsed_payload = parsed_payload.transform_keys(&:to_sym) if symbolize && parsed_payload.is_a?(Hash)
106-
return parsed_payload
107-
rescue JSON::ParserError
108-
# If JSON parsing fails, return raw body
109-
end
110-
end
111-
112-
# Return raw body for all other cases
113-
raw_body
114-
end
115-
116-
# Load handler class
117-
def load_handler(handler_class_name, handler_dir)
118-
# Convert class name to file name (e.g., Team1Handler -> team1_handler.rb)
119-
# E.g.2: GithubHandler -> github_handler.rb
120-
# E.g.3: GitHubHandler -> git_hub_handler.rb
121-
file_name = handler_class_name.gsub(/([A-Z])/, '_\1').downcase.sub(/^_/, "") + ".rb"
122-
file_path = File.join(handler_dir, file_name)
123-
124-
if File.exist?(file_path)
125-
require file_path
126-
Object.const_get(handler_class_name).new
127-
else
128-
raise LoadError, "Handler #{handler_class_name} not found at #{file_path}"
129-
end
130-
rescue => e
131-
error!("failed to load handler #{handler_class_name}: #{e.message}", 500)
132-
end
133-
134-
# Determine HTTP error code from exception
135-
def determine_error_code(exception)
136-
case exception
137-
when ArgumentError then 400
138-
when SecurityError then 401
139-
when NotImplementedError then 501
140-
else 500
141-
end
142-
end
143-
end
46+
helpers Helpers
14447

14548
# Define operational endpoints
14649
get captured_config[:health_path] do

lib/hooks/app/helpers.rb

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
# frozen_string_literal: true
2+
3+
module Hooks
4+
module App
5+
module Helpers
6+
# Enforce request size and timeout limits
7+
def enforce_request_limits(config)
8+
# Check content length (handle different header formats and sources)
9+
content_length = headers["Content-Length"] || headers["CONTENT_LENGTH"] ||
10+
headers["content-length"] || headers["HTTP_CONTENT_LENGTH"] ||
11+
env["CONTENT_LENGTH"] || env["HTTP_CONTENT_LENGTH"]
12+
13+
# Also try to get from request object directly
14+
content_length ||= request.content_length if respond_to?(:request) && request.respond_to?(:content_length)
15+
16+
content_length = content_length&.to_i
17+
18+
if content_length && content_length > config[:request_limit]
19+
error!("request body too large", 413)
20+
end
21+
22+
# Note: Timeout enforcement would typically be handled at the server level (Puma, etc.)
23+
end
24+
25+
# Verify the incoming request using the configured authentication method
26+
def validate_auth!(payload, headers, endpoint_config)
27+
auth_config = endpoint_config[:auth]
28+
auth_plugin_type = auth_config[:type].downcase
29+
secret_env_key = auth_config[:secret_env_key]
30+
31+
return unless secret_env_key
32+
33+
secret = ENV[secret_env_key]
34+
unless secret
35+
error!("secret '#{secret_env_key}' not found in environment", 500)
36+
end
37+
38+
auth_class = nil
39+
40+
case auth_plugin_type
41+
when "hmac"
42+
auth_class = Plugins::Auth::HMAC
43+
when "shared_secret"
44+
auth_class = Plugins::Auth::SharedSecret
45+
else
46+
error!("Custom validators not implemented in POC", 500)
47+
end
48+
49+
unless auth_class.valid?(
50+
payload:,
51+
headers:,
52+
secret:,
53+
config: endpoint_config
54+
)
55+
error!("authentication failed", 401)
56+
end
57+
end
58+
59+
# Parse request payload
60+
def parse_payload(raw_body, headers, symbolize: true)
61+
content_type = headers["Content-Type"] || headers["CONTENT_TYPE"] || headers["content-type"] || headers["HTTP_CONTENT_TYPE"]
62+
63+
# Try to parse as JSON if content type suggests it or if it looks like JSON
64+
if content_type&.include?("application/json") || (raw_body.strip.start_with?("{", "[") rescue false)
65+
begin
66+
parsed_payload = JSON.parse(raw_body)
67+
parsed_payload = parsed_payload.transform_keys(&:to_sym) if symbolize && parsed_payload.is_a?(Hash)
68+
return parsed_payload
69+
rescue JSON::ParserError
70+
# If JSON parsing fails, return raw body
71+
end
72+
end
73+
74+
# Return raw body for all other cases
75+
raw_body
76+
end
77+
78+
# Load handler class
79+
def load_handler(handler_class_name, handler_dir)
80+
# Convert class name to file name (e.g., Team1Handler -> team1_handler.rb)
81+
# E.g.2: GithubHandler -> github_handler.rb
82+
# E.g.3: GitHubHandler -> git_hub_handler.rb
83+
file_name = handler_class_name.gsub(/([A-Z])/, '_\1').downcase.sub(/^_/, "") + ".rb"
84+
file_path = File.join(handler_dir, file_name)
85+
86+
if File.exist?(file_path)
87+
require file_path
88+
Object.const_get(handler_class_name).new
89+
else
90+
raise LoadError, "Handler #{handler_class_name} not found at #{file_path}"
91+
end
92+
rescue => e
93+
error!("failed to load handler #{handler_class_name}: #{e.message}", 500)
94+
end
95+
96+
# Determine HTTP error code from exception
97+
def determine_error_code(exception)
98+
case exception
99+
when ArgumentError then 400
100+
when SecurityError then 401
101+
when NotImplementedError then 501
102+
else 500
103+
end
104+
end
105+
end
106+
end
107+
end

0 commit comments

Comments
 (0)