Skip to content

Commit d614c36

Browse files
committed
move utils to its own dir
1 parent 8dd8338 commit d614c36

File tree

6 files changed

+92
-89
lines changed

6 files changed

+92
-89
lines changed

lib/hooks.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@
99
require file
1010
end
1111

12+
# Load all utils
13+
Dir[File.join(__dir__, "hooks/utils/**/*.rb")].sort.each do |file|
14+
require file
15+
end
16+
1217
# Main module for the Hooks webhook server framework
1318
module Hooks
1419
# Build a Rack-compatible webhook server application

lib/hooks/app/api.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ def determine_error_code(exception)
206206
handler = load_handler(handler_class_name, config[:handler_dir])
207207

208208
# Normalize the headers based on the endpoint configuration (normalization is the default)
209-
headers = Hooks::Plugins::Utils::Normalize.headers(headers) if config[:normalize_headers]
209+
headers = Hooks::Utils::Normalize.headers(headers) if config[:normalize_headers]
210210

211211
# Call handler
212212
response = handler.call(

lib/hooks/plugins/request_validator/github_webhooks.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ def self.valid?(payload:, headers:, secret:, config:)
2626
signature_header = config.dig(:request_validator, :header) || "X-Hub-Signature-256"
2727
algorithm = config.dig(:request_validator, :algorithm) || "sha256"
2828

29-
signature_header = Utils::Normalize.header(signature_header)
30-
headers = Utils::Normalize.headers(headers)
29+
signature_header = Hooks::Utils::Normalize.header(signature_header)
30+
headers = Hooks::Utils::Normalize.headers(headers)
3131

3232
provided_signature = headers[signature_header]
3333
return false if provided_signature.nil? || provided_signature.empty?

lib/hooks/plugins/utils/normalize.rb

Lines changed: 0 additions & 85 deletions
This file was deleted.

lib/hooks/utils/normalize.rb

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
# frozen_string_literal: true
2+
3+
module Hooks
4+
module Utils
5+
# Utility class for normalizing HTTP headers
6+
#
7+
# Provides a robust method to consistently format HTTP headers
8+
# across the application, handling various edge cases and formats.
9+
class Normalize
10+
# Normalize a hash of HTTP headers
11+
#
12+
# @param headers [Hash, #each] Headers hash or hash-like object
13+
# @return [Hash] Normalized headers hash with downcased keys and trimmed values
14+
#
15+
# @example Hash of headers normalization
16+
# headers = { "Content-Type" => " application/json ", "X-GitHub-Event" => "push" }
17+
# normalized = Normalize.headers(headers)
18+
# # => { "content-type" => "application/json", "x-github-event" => "push" }
19+
#
20+
# @example Handle various input types
21+
# Normalize.headers(nil) # => nil
22+
# Normalize.headers({}) # => {}
23+
# Normalize.headers({ "KEY" => ["a", "b"] }) # => { "key" => "a" }
24+
# Normalize.headers({ "Key" => 123 }) # => { "key" => "123" }
25+
def self.headers(headers)
26+
# Handle nil input
27+
return nil if headers.nil?
28+
29+
# Fast path for non-enumerable inputs (numbers, etc.)
30+
return {} unless headers.respond_to?(:each)
31+
32+
normalized = {}
33+
34+
headers.each do |key, value|
35+
# Skip nil keys or values entirely
36+
next if key.nil? || value.nil?
37+
38+
# Convert key to string, downcase, and strip in one operation
39+
normalized_key = key.to_s.downcase.strip
40+
next if normalized_key.empty?
41+
42+
# Handle different value types efficiently
43+
normalized_value = case value
44+
when String
45+
value.strip
46+
when Array
47+
# Take first non-empty element for multi-value headers
48+
first_valid = value.find { |v| v && !v.to_s.strip.empty? }
49+
first_valid ? first_valid.to_s.strip : nil
50+
else
51+
value.to_s.strip
52+
end
53+
54+
# Only add if we have a non-empty value
55+
normalized[normalized_key] = normalized_value if normalized_value && !normalized_value.empty?
56+
end
57+
58+
normalized
59+
end
60+
61+
# Normalize a single HTTP header name
62+
#
63+
# @param header [String] Header name to normalize
64+
# @return [String, nil] Normalized header name (downcased and trimmed), or nil if input is nil
65+
#
66+
# @example Single header normalization
67+
# Normalize.header(" Content-Type ") # => "content-type"
68+
# Normalize.header("X-GitHub-Event") # => "x-github-event"
69+
# Normalize.header("") # => ""
70+
# Normalize.header(nil) # => nil
71+
#
72+
# @raise [ArgumentError] If input is not a String or nil
73+
def self.header(header)
74+
return nil if header.nil?
75+
if header.is_a?(String)
76+
header.downcase.strip
77+
else
78+
raise ArgumentError, "Expected a String for header normalization"
79+
end
80+
end
81+
end
82+
end
83+
end

spec/lib/hooks/plugins/utils/normalize_spec.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# frozen_string_literal: true
22

3-
describe Hooks::Plugins::Utils::Normalize do
3+
describe Hooks::Utils::Normalize do
44
describe ".header" do
55
context "when input is a single string" do
66
it "normalizes header name to lowercase and trims whitespace" do

0 commit comments

Comments
 (0)