Module: Belt::LambdaHandler

Includes:
Helpers::Response
Defined in:
lib/belt/lambda_handler.rb

Overview

Lambda handler module — include in your Lambda entry point to get automatic observability setup, CORS preflight handling, JSON body parsing, and error wrapping.

Usage:

require "belt"

include Belt::LambdaHandler

def execute(path:, body:, event:)
  ROUTER.route(event: event, body: body)
end

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Helpers::Response

#cors_headers, #error_response, #handle_error_and_respond, #html_response, #success_response

Instance Attribute Details

#loggerObject

Returns the value of attribute logger.



24
25
26
# File 'lib/belt/lambda_handler.rb', line 24

def logger
  @logger
end

#metricsObject

Returns the value of attribute metrics.



24
25
26
# File 'lib/belt/lambda_handler.rb', line 24

def metrics
  @metrics
end

Class Method Details

.included(base) ⇒ Object



26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# File 'lib/belt/lambda_handler.rb', line 26

def self.included(base)
  base.instance_variable_set(:@belt_lambda_handler_included, true)

  # Skip auto-registration in test environments to avoid stale paths
  return if ENV['BELT_ENV'] == 'test' || ENV['RACK_ENV'] == 'test' || defined?(RSpec)

  # Auto-register controllers directory relative to the including file's location
  caller_file = caller_locations(1, 1)&.first&.path
  return unless caller_file

  controllers_dir = File.join(File.dirname(caller_file), 'controllers')
  return unless File.directory?(controllers_dir)

  Belt.controller_paths << controllers_dir
  Dir.children(controllers_dir).each do |child|
    subdir = File.join(controllers_dir, child)
    Belt.controller_paths << subdir if File.directory?(subdir)
  end
end

Instance Method Details

#lambda_handler(event:, context:) ⇒ Object

API Gateway Lambda handler. Handles HTTP requests with automatic CORS, body parsing, observability, and error wrapping. Override ‘execute` to provide your own routing logic.



49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/belt/lambda_handler.rb', line 49

def lambda_handler(event:, context:)
  init_observability(context: context)

  LambdaLoadout.with_logging_and_metrics(
    logger,
    metrics,
    context,
    event: event,
    error_notification_config: {
      sns_topic_arn: ENV.fetch('ERROR_NOTIFICATION_TOPIC_ARN', nil)
    }
  ) do
    logger.info('Lambda invoked',
                http_method: event['httpMethod'],
                path: event['path'],
                source_ip: event.dig('requestContext', 'identity', 'sourceIp'))

    return { statusCode: 200, headers: cors_headers(event), body: '{}' } if event['httpMethod'] == 'OPTIONS'

    begin
      body = JSON.parse(event['body'] || '{}')
    rescue JSON::ParserError
      return error_response('Invalid JSON in request body')
    end

    begin
      result = execute(path: event['path'], body: body, event: event)
      logger.info('Request completed', status_code: result[:statusCode], path: event['path'])
      result
    rescue StandardError => e
      handle_error_and_respond(e, 'Unhandled error during request processing',
                               { path: event['path'], method: event['httpMethod'] })
    end
  end
rescue StandardError => e
  Belt::Helpers::ErrorLogging.log_error(@logger, 'Unhandled Lambda error', e,
                                        { phase: 'lambda_handler', path: event&.dig('path') })

  body = { error: 'Internal server error' }
  if verbose_errors_enabled?
    body[:message] = e.message
    body[:type] = e.class.name
    body[:backtrace] = Belt::Helpers::ErrorLogging.filter_backtrace(e.backtrace || [])
  end

  { statusCode: 500, headers: cors_headers, body: JSON.generate(body) }
end