otto 1.6.0 → 2.0.0.pre1

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.
Files changed (136) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +1 -1
  3. data/.github/workflows/claude-code-review.yml +53 -0
  4. data/.github/workflows/claude.yml +49 -0
  5. data/.gitignore +3 -0
  6. data/.rubocop.yml +24 -345
  7. data/CHANGELOG.rst +83 -0
  8. data/CLAUDE.md +56 -0
  9. data/Gemfile +10 -3
  10. data/Gemfile.lock +23 -28
  11. data/README.md +2 -0
  12. data/bin/rspec +4 -4
  13. data/changelog.d/20250911_235619_delano_next.rst +28 -0
  14. data/changelog.d/20250912_123055_delano_remove_ostruct.rst +21 -0
  15. data/changelog.d/20250912_175625_claude_delano_remove_ostruct.rst +21 -0
  16. data/changelog.d/README.md +120 -0
  17. data/changelog.d/scriv.ini +5 -0
  18. data/docs/.gitignore +1 -0
  19. data/docs/migrating/v2.0.0-pre1.md +276 -0
  20. data/examples/.gitignore +1 -0
  21. data/examples/advanced_routes/README.md +33 -0
  22. data/examples/advanced_routes/app/controllers/handlers/async.rb +9 -0
  23. data/examples/advanced_routes/app/controllers/handlers/dynamic.rb +9 -0
  24. data/examples/advanced_routes/app/controllers/handlers/static.rb +9 -0
  25. data/examples/advanced_routes/app/controllers/modules/auth.rb +9 -0
  26. data/examples/advanced_routes/app/controllers/modules/transformer.rb +9 -0
  27. data/examples/advanced_routes/app/controllers/modules/validator.rb +9 -0
  28. data/examples/advanced_routes/app/controllers/routes_app.rb +232 -0
  29. data/examples/advanced_routes/app/controllers/v2/admin.rb +9 -0
  30. data/examples/advanced_routes/app/controllers/v2/config.rb +9 -0
  31. data/examples/advanced_routes/app/controllers/v2/settings.rb +9 -0
  32. data/examples/advanced_routes/app/logic/admin/logic/manager.rb +27 -0
  33. data/examples/advanced_routes/app/logic/admin/panel.rb +27 -0
  34. data/examples/advanced_routes/app/logic/analytics_processor.rb +25 -0
  35. data/examples/advanced_routes/app/logic/complex/business/handler.rb +27 -0
  36. data/examples/advanced_routes/app/logic/data_logic.rb +23 -0
  37. data/examples/advanced_routes/app/logic/data_processor.rb +25 -0
  38. data/examples/advanced_routes/app/logic/input_validator.rb +24 -0
  39. data/examples/advanced_routes/app/logic/nested/feature/logic.rb +27 -0
  40. data/examples/advanced_routes/app/logic/reports_generator.rb +27 -0
  41. data/examples/advanced_routes/app/logic/simple_logic.rb +25 -0
  42. data/examples/advanced_routes/app/logic/system/config/manager.rb +27 -0
  43. data/examples/advanced_routes/app/logic/test_logic.rb +23 -0
  44. data/examples/advanced_routes/app/logic/transform_logic.rb +23 -0
  45. data/examples/advanced_routes/app/logic/upload_logic.rb +23 -0
  46. data/examples/advanced_routes/app/logic/v2/logic/dashboard.rb +27 -0
  47. data/examples/advanced_routes/app/logic/v2/logic/processor.rb +27 -0
  48. data/examples/advanced_routes/app.rb +33 -0
  49. data/examples/advanced_routes/config.rb +23 -0
  50. data/examples/advanced_routes/config.ru +7 -0
  51. data/examples/advanced_routes/puma.rb +20 -0
  52. data/examples/advanced_routes/routes +167 -0
  53. data/examples/advanced_routes/run.rb +39 -0
  54. data/examples/advanced_routes/test.rb +58 -0
  55. data/examples/authentication_strategies/README.md +32 -0
  56. data/examples/authentication_strategies/app/auth.rb +68 -0
  57. data/examples/authentication_strategies/app/controllers/auth_controller.rb +29 -0
  58. data/examples/authentication_strategies/app/controllers/main_controller.rb +28 -0
  59. data/examples/authentication_strategies/config.ru +24 -0
  60. data/examples/authentication_strategies/routes +37 -0
  61. data/examples/basic/README.md +29 -0
  62. data/examples/basic/app.rb +7 -35
  63. data/examples/basic/routes +0 -9
  64. data/examples/mcp_demo/README.md +87 -0
  65. data/examples/mcp_demo/app.rb +29 -34
  66. data/examples/mcp_demo/config.ru +9 -60
  67. data/examples/security_features/README.md +46 -0
  68. data/examples/security_features/app.rb +23 -24
  69. data/examples/security_features/config.ru +8 -10
  70. data/lib/otto/core/configuration.rb +167 -0
  71. data/lib/otto/core/error_handler.rb +86 -0
  72. data/lib/otto/core/file_safety.rb +61 -0
  73. data/lib/otto/core/middleware_stack.rb +157 -0
  74. data/lib/otto/core/router.rb +183 -0
  75. data/lib/otto/core/uri_generator.rb +44 -0
  76. data/lib/otto/design_system.rb +7 -5
  77. data/lib/otto/helpers/base.rb +3 -0
  78. data/lib/otto/helpers/request.rb +10 -8
  79. data/lib/otto/helpers/response.rb +5 -4
  80. data/lib/otto/helpers/validation.rb +9 -7
  81. data/lib/otto/mcp/auth/token.rb +10 -9
  82. data/lib/otto/mcp/protocol.rb +24 -27
  83. data/lib/otto/mcp/rate_limiting.rb +8 -3
  84. data/lib/otto/mcp/registry.rb +7 -2
  85. data/lib/otto/mcp/route_parser.rb +10 -15
  86. data/lib/otto/mcp/server.rb +21 -11
  87. data/lib/otto/mcp/validation.rb +14 -10
  88. data/lib/otto/response_handlers/auto.rb +39 -0
  89. data/lib/otto/response_handlers/base.rb +16 -0
  90. data/lib/otto/response_handlers/default.rb +16 -0
  91. data/lib/otto/response_handlers/factory.rb +39 -0
  92. data/lib/otto/response_handlers/json.rb +28 -0
  93. data/lib/otto/response_handlers/redirect.rb +25 -0
  94. data/lib/otto/response_handlers/view.rb +24 -0
  95. data/lib/otto/response_handlers.rb +9 -135
  96. data/lib/otto/route.rb +9 -9
  97. data/lib/otto/route_definition.rb +15 -18
  98. data/lib/otto/route_handlers/base.rb +121 -0
  99. data/lib/otto/route_handlers/class_method.rb +89 -0
  100. data/lib/otto/route_handlers/factory.rb +29 -0
  101. data/lib/otto/route_handlers/instance_method.rb +69 -0
  102. data/lib/otto/route_handlers/lambda.rb +59 -0
  103. data/lib/otto/route_handlers/logic_class.rb +93 -0
  104. data/lib/otto/route_handlers.rb +10 -405
  105. data/lib/otto/security/authentication/auth_strategy.rb +44 -0
  106. data/lib/otto/security/authentication/authentication_middleware.rb +123 -0
  107. data/lib/otto/security/authentication/failure_result.rb +36 -0
  108. data/lib/otto/security/authentication/strategies/api_key_strategy.rb +40 -0
  109. data/lib/otto/security/authentication/strategies/permission_strategy.rb +47 -0
  110. data/lib/otto/security/authentication/strategies/public_strategy.rb +19 -0
  111. data/lib/otto/security/authentication/strategies/role_strategy.rb +57 -0
  112. data/lib/otto/security/authentication/strategies/session_strategy.rb +41 -0
  113. data/lib/otto/security/authentication/strategy_result.rb +223 -0
  114. data/lib/otto/security/authentication.rb +28 -282
  115. data/lib/otto/security/config.rb +14 -12
  116. data/lib/otto/security/configurator.rb +219 -0
  117. data/lib/otto/security/csrf.rb +8 -143
  118. data/lib/otto/security/middleware/csrf_middleware.rb +151 -0
  119. data/lib/otto/security/middleware/rate_limit_middleware.rb +38 -0
  120. data/lib/otto/security/middleware/validation_middleware.rb +252 -0
  121. data/lib/otto/security/rate_limiter.rb +86 -0
  122. data/lib/otto/security/rate_limiting.rb +10 -105
  123. data/lib/otto/security/validator.rb +8 -253
  124. data/lib/otto/static.rb +3 -0
  125. data/lib/otto/utils.rb +14 -0
  126. data/lib/otto/version.rb +3 -1
  127. data/lib/otto.rb +142 -498
  128. data/otto.gemspec +2 -2
  129. metadata +89 -28
  130. data/examples/dynamic_pages/app.rb +0 -115
  131. data/examples/dynamic_pages/config.ru +0 -30
  132. data/examples/dynamic_pages/routes +0 -21
  133. data/examples/helpers_demo/app.rb +0 -244
  134. data/examples/helpers_demo/config.ru +0 -26
  135. data/examples/helpers_demo/routes +0 -7
  136. data/lib/concurrent_cache_store.rb +0 -68
@@ -0,0 +1,121 @@
1
+ # frozen_string_literal: true
2
+
3
+ # lib/otto/route_handlers/base.rb
4
+ require 'json'
5
+
6
+ class Otto
7
+ module RouteHandlers
8
+ # Base class for all route handlers
9
+ # Provides common functionality and interface
10
+ class BaseHandler
11
+ attr_reader :route_definition, :otto_instance
12
+
13
+ def initialize(route_definition, otto_instance = nil)
14
+ @route_definition = route_definition
15
+ @otto_instance = otto_instance
16
+ end
17
+
18
+ # Execute the route handler
19
+ # @param env [Hash] Rack environment
20
+ # @param extra_params [Hash] Additional parameters
21
+ # @return [Array] Rack response array
22
+ def call(env, extra_params = {})
23
+ raise NotImplementedError, 'Subclasses must implement #call'
24
+ end
25
+
26
+ protected
27
+
28
+ # Get the target class, loading it safely
29
+ # @return [Class] The target class
30
+ def target_class
31
+ @target_class ||= safe_const_get(route_definition.klass_name)
32
+ end
33
+
34
+ # Setup request and response with the same extensions and processing as Route#call
35
+ # @param req [Rack::Request] Request object
36
+ # @param res [Rack::Response] Response object
37
+ # @param env [Hash] Rack environment
38
+ # @param extra_params [Hash] Additional parameters
39
+ def setup_request_response(req, res, env, extra_params)
40
+ # Apply the same extensions as original Route#call
41
+ req.extend Otto::RequestHelpers
42
+ res.extend Otto::ResponseHelpers
43
+ res.request = req
44
+
45
+ # Make security config available to response helpers
46
+ if otto_instance.respond_to?(:security_config) && otto_instance.security_config
47
+ env['otto.security_config'] = otto_instance.security_config
48
+ end
49
+
50
+ # Make route definition and options available to middleware and handlers
51
+ env['otto.route_definition'] = route_definition
52
+ env['otto.route_options'] = route_definition.options
53
+
54
+ # Process parameters through security layer
55
+ req.params.merge! extra_params
56
+ req.params.replace Otto::Static.indifferent_params(req.params)
57
+
58
+ # Add security headers
59
+ if otto_instance.respond_to?(:security_config) && otto_instance.security_config
60
+ otto_instance.security_config.security_headers.each do |header, value|
61
+ res.headers[header] = value
62
+ end
63
+ end
64
+
65
+ # Setup class extensions if target_class is available
66
+ if target_class
67
+ target_class.extend Otto::Route::ClassMethods
68
+ target_class.otto = otto_instance if otto_instance
69
+ end
70
+
71
+ # Add security helpers if CSRF is enabled
72
+ if otto_instance.respond_to?(:security_config) && otto_instance.security_config&.csrf_enabled?
73
+ res.extend Otto::Security::CSRFHelpers
74
+ end
75
+
76
+ # Add validation helpers
77
+ res.extend Otto::Security::ValidationHelpers
78
+ end
79
+
80
+ # Finalize response with the same processing as Route#call
81
+ # @param res [Rack::Response] Response object
82
+ # @return [Array] Rack response array
83
+ def finalize_response(res)
84
+ res.body = [res.body] unless res.body.respond_to?(:each)
85
+ res.finish
86
+ end
87
+
88
+ # Handle response using appropriate response handler
89
+ # @param result [Object] Result from route execution
90
+ # @param response [Rack::Response] Response object
91
+ # @param context [Hash] Additional context for response handling
92
+ def handle_response(result, response, context = {})
93
+ response_type = route_definition.response_type
94
+
95
+ # Get the appropriate response handler
96
+ handler_class = case response_type
97
+ in 'json' then Otto::ResponseHandlers::JSONHandler
98
+ in 'redirect' then Otto::ResponseHandlers::RedirectHandler
99
+ in 'view' then Otto::ResponseHandlers::ViewHandler
100
+ in 'auto' then Otto::ResponseHandlers::AutoHandler
101
+ else Otto::ResponseHandlers::DefaultHandler
102
+ end
103
+
104
+ handler_class.handle(result, response, context)
105
+ end
106
+
107
+ private
108
+
109
+ # Safely get a constant from a string name
110
+ # @param name [String] Class name
111
+ # @return [Class] The class
112
+ def safe_const_get(name)
113
+ name.split('::').inject(Object) do |scope, const_name|
114
+ scope.const_get(const_name)
115
+ end
116
+ rescue NameError => e
117
+ raise NameError, "Unknown class: #{name} (#{e})"
118
+ end
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ # lib/otto/route_handlers/class_method.rb
4
+
5
+ require 'json'
6
+ require 'securerandom'
7
+
8
+ require_relative 'base'
9
+
10
+ class Otto
11
+ module RouteHandlers
12
+ # Handler for class methods (existing Otto pattern)
13
+ # Maintains backward compatibility for Controller.action patterns
14
+ class ClassMethodHandler < BaseHandler
15
+ def call(env, extra_params = {})
16
+ req = Rack::Request.new(env)
17
+ res = Rack::Response.new
18
+
19
+ begin
20
+ # Apply the same extensions and processing as original Route#call
21
+ setup_request_response(req, res, env, extra_params)
22
+
23
+ # Call class method directly (existing Otto behavior)
24
+ result = target_class.send(route_definition.method_name, req, res)
25
+
26
+ # Only handle response if response_type is not default
27
+ if route_definition.response_type != 'default'
28
+ handle_response(result, res, {
29
+ class: target_class,
30
+ request: req,
31
+ })
32
+ end
33
+ rescue StandardError => e
34
+ # Check if we're being called through Otto's integrated context (vs direct handler testing)
35
+ # In integrated context, let Otto's centralized error handler manage the response
36
+ # In direct testing context, handle errors locally for unit testing
37
+ if otto_instance
38
+ # Log error for handler-specific context but let Otto's centralized error handler manage the response
39
+ Otto.logger.error "[ClassMethodHandler] #{e.class}: #{e.message}"
40
+ Otto.logger.debug "[ClassMethodHandler] Backtrace: #{e.backtrace.join("\n")}" if Otto.debug
41
+ raise e # Re-raise to let Otto's centralized error handler manage the response
42
+ else
43
+ # Direct handler testing context - handle errors locally with security improvements
44
+ error_id = SecureRandom.hex(8)
45
+ Otto.logger.error "[#{error_id}] #{e.class}: #{e.message}"
46
+ Otto.logger.debug "[#{error_id}] Backtrace: #{e.backtrace.join("\n")}" if Otto.debug
47
+
48
+ res.status = 500
49
+
50
+ # Content negotiation for error response
51
+ accept_header = env['HTTP_ACCEPT'].to_s
52
+ if accept_header.include?('application/json')
53
+ res.headers['content-type'] = 'application/json'
54
+ error_data = if Otto.env?(:dev, :development)
55
+ {
56
+ error: 'Internal Server Error',
57
+ message: 'Server error occurred. Check logs for details.',
58
+ error_id: error_id,
59
+ }
60
+ else
61
+ {
62
+ error: 'Internal Server Error',
63
+ message: 'An error occurred. Please try again later.',
64
+ }
65
+ end
66
+ res.write JSON.generate(error_data)
67
+ else
68
+ res.headers['content-type'] = 'text/plain'
69
+ if Otto.env?(:dev, :development)
70
+ res.write "Server error (ID: #{error_id}). Check logs for details."
71
+ else
72
+ res.write 'An error occurred. Please try again later.'
73
+ end
74
+ end
75
+
76
+ # Add security headers if available
77
+ if otto_instance.respond_to?(:security_config) && otto_instance.security_config
78
+ otto_instance.security_config.security_headers.each do |header, value|
79
+ res.headers[header] = value
80
+ end
81
+ end
82
+ end
83
+ end
84
+
85
+ finalize_response(res)
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ # lib/otto/route_handlers/factory.rb
4
+
5
+ require_relative 'base'
6
+
7
+ class Otto
8
+ module RouteHandlers
9
+ # Factory for creating appropriate handlers based on route definitions
10
+ class HandlerFactory
11
+ # Create a handler for the given route definition
12
+ # @param route_definition [Otto::RouteDefinition] The route definition
13
+ # @param otto_instance [Otto] The Otto instance for configuration access
14
+ # @return [BaseHandler] Appropriate handler for the route
15
+ def self.create_handler(route_definition, otto_instance = nil)
16
+ case route_definition.kind
17
+ when :logic
18
+ LogicClassHandler.new(route_definition, otto_instance)
19
+ when :instance
20
+ InstanceMethodHandler.new(route_definition, otto_instance)
21
+ when :class
22
+ ClassMethodHandler.new(route_definition, otto_instance)
23
+ else
24
+ raise ArgumentError, "Unknown handler kind: #{route_definition.kind}"
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ # lib/otto/route_handlers/instance_method.rb
4
+ require 'securerandom'
5
+
6
+ require_relative 'base'
7
+
8
+ class Otto
9
+ module RouteHandlers
10
+ # Handler for instance methods (existing Otto pattern)
11
+ # Maintains backward compatibility for Controller#action patterns
12
+ class InstanceMethodHandler < BaseHandler
13
+ def call(env, extra_params = {})
14
+ req = Rack::Request.new(env)
15
+ res = Rack::Response.new
16
+
17
+ begin
18
+ # Apply the same extensions and processing as original Route#call
19
+ setup_request_response(req, res, env, extra_params)
20
+
21
+ # Create instance and call method (existing Otto behavior)
22
+ instance = target_class.new(req, res)
23
+ result = instance.send(route_definition.method_name)
24
+
25
+ # Only handle response if response_type is not default
26
+ if route_definition.response_type != 'default'
27
+ handle_response(result, res, {
28
+ instance: instance,
29
+ request: req,
30
+ })
31
+ end
32
+ rescue StandardError => e
33
+ # Check if we're being called through Otto's integrated context (vs direct handler testing)
34
+ # In integrated context, let Otto's centralized error handler manage the response
35
+ # In direct testing context, handle errors locally for unit testing
36
+ if otto_instance
37
+ # Log error for handler-specific context but let the centralized error handler manage the response
38
+ Otto.logger.error "[InstanceMethodHandler] #{e.class}: #{e.message}"
39
+ Otto.logger.debug "[InstanceMethodHandler] Backtrace: #{e.backtrace.join("\n")}" if Otto.debug
40
+ raise e # Re-raise to let Otto's centralized error handler manage the response
41
+ else
42
+ # Direct handler testing context - handle errors locally with security improvements
43
+ error_id = SecureRandom.hex(8)
44
+ Otto.logger.error "[#{error_id}] #{e.class}: #{e.message}"
45
+ Otto.logger.debug "[#{error_id}] Backtrace: #{e.backtrace.join("\n")}" if Otto.debug
46
+
47
+ res.status = 500
48
+ res.headers['content-type'] = 'text/plain'
49
+
50
+ if Otto.env?(:dev, :development)
51
+ res.write "Server error (ID: #{error_id}). Check logs for details."
52
+ else
53
+ res.write 'An error occurred. Please try again later.'
54
+ end
55
+
56
+ # Add security headers if available
57
+ if otto_instance.respond_to?(:security_config) && otto_instance.security_config
58
+ otto_instance.security_config.security_headers.each do |header, value|
59
+ res.headers[header] = value
60
+ end
61
+ end
62
+ end
63
+ end
64
+
65
+ finalize_response(res)
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ # lib/otto/route_handlers/lambda.rb
4
+ require 'securerandom'
5
+
6
+ require_relative 'base'
7
+
8
+ class Otto
9
+ module RouteHandlers
10
+ # Custom handler for lambda/proc definitions (future extension)
11
+ class LambdaHandler < BaseHandler
12
+ def call(env, extra_params = {})
13
+ req = Rack::Request.new(env)
14
+ res = Rack::Response.new
15
+
16
+ begin
17
+ # Security: Lambda handlers require pre-configured procs from Otto instance
18
+ # This prevents code injection via eval and maintains security
19
+ handler_name = route_definition.klass_name
20
+ lambda_registry = otto_instance&.config&.dig(:lambda_handlers) || {}
21
+
22
+ lambda_proc = lambda_registry[handler_name]
23
+ unless lambda_proc.respond_to?(:call)
24
+ raise ArgumentError, "Lambda handler '#{handler_name}' not found in registry or not callable"
25
+ end
26
+
27
+ result = lambda_proc.call(req, res, extra_params)
28
+
29
+ handle_response(result, res, {
30
+ lambda: lambda_proc,
31
+ request: req,
32
+ })
33
+ rescue StandardError => e
34
+ error_id = SecureRandom.hex(8)
35
+ Otto.logger.error "[#{error_id}] #{e.class}: #{e.message}"
36
+ Otto.logger.debug "[#{error_id}] Backtrace: #{e.backtrace.join("\n")}" if Otto.debug
37
+
38
+ res.status = 500
39
+ res.headers['content-type'] = 'text/plain'
40
+
41
+ if Otto.env?(:dev, :development)
42
+ res.write "Lambda handler error (ID: #{error_id}). Check logs for details."
43
+ else
44
+ res.write 'An error occurred. Please try again later.'
45
+ end
46
+
47
+ # Add security headers if available
48
+ if otto_instance.respond_to?(:security_config) && otto_instance.security_config
49
+ otto_instance.security_config.security_headers.each do |header, value|
50
+ res.headers[header] = value
51
+ end
52
+ end
53
+ end
54
+
55
+ res.finish
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ # lib/otto/route_handlers/logic_class.rb
4
+ require 'json'
5
+ require 'securerandom'
6
+
7
+ require_relative 'base'
8
+
9
+ class Otto
10
+ module RouteHandlers
11
+ # Handler for Logic classes (new in Otto Framework Enhancement)
12
+ # Handles Logic class routes with the modern RequestContext pattern
13
+ # Logic classes use signature: initialize(context, params, locale)
14
+ class LogicClassHandler < BaseHandler
15
+ def call(env, extra_params = {})
16
+ req = Rack::Request.new(env)
17
+ res = Rack::Response.new
18
+
19
+ begin
20
+ # Get strategy result (guaranteed to exist from auth middleware)
21
+ strategy_result = env['otto.strategy_result'] || Otto::Security::Authentication::StrategyResult.anonymous
22
+
23
+ # Initialize Logic class with new signature: context, params, locale
24
+ logic_params = req.params.merge(extra_params)
25
+
26
+ # Handle JSON request bodies
27
+ if req.content_type&.include?('application/json') && req.body.size.positive?
28
+ begin
29
+ req.body.rewind
30
+ json_data = JSON.parse(req.body.read)
31
+ logic_params = logic_params.merge(json_data) if json_data.is_a?(Hash)
32
+ rescue JSON::ParserError => e
33
+ Otto.logger.error "[LogicClassHandler] JSON parsing error: #{e.message}"
34
+ end
35
+ end
36
+
37
+ locale = env['otto.locale'] || 'en'
38
+
39
+ logic = target_class.new(strategy_result, logic_params, locale)
40
+
41
+ # Execute standard Logic class lifecycle
42
+ logic.raise_concerns if logic.respond_to?(:raise_concerns)
43
+
44
+ result = if logic.respond_to?(:process)
45
+ logic.process
46
+ else
47
+ logic.call || logic
48
+ end
49
+
50
+ # Handle response with Logic instance context
51
+ handle_response(result, res, {
52
+ logic_instance: logic,
53
+ request: req,
54
+ status_code: logic.respond_to?(:status_code) ? logic.status_code : nil,
55
+ })
56
+ rescue StandardError => e
57
+ # Check if we're being called through Otto's integrated context (vs direct handler testing)
58
+ # In integrated context, let Otto's centralized error handler manage the response
59
+ # In direct testing context, handle errors locally for unit testing
60
+ if otto_instance
61
+ # Log error for handler-specific context but let Otto's centralized error handler manage the response
62
+ Otto.logger.error "[LogicClassHandler] #{e.class}: #{e.message}"
63
+ Otto.logger.debug "[LogicClassHandler] Backtrace: #{e.backtrace.join("\n")}" if Otto.debug
64
+ raise e # Re-raise to let Otto's centralized error handler manage the response
65
+ else
66
+ # Direct handler testing context - handle errors locally with security improvements
67
+ error_id = SecureRandom.hex(8)
68
+ Otto.logger.error "[#{error_id}] #{e.class}: #{e.message}"
69
+ Otto.logger.debug "[#{error_id}] Backtrace: #{e.backtrace.join("\n")}" if Otto.debug
70
+
71
+ res.status = 500
72
+ res.headers['content-type'] = 'text/plain'
73
+
74
+ if Otto.env?(:dev, :development)
75
+ res.write "Server error (ID: #{error_id}). Check logs for details."
76
+ else
77
+ res.write 'An error occurred. Please try again later.'
78
+ end
79
+
80
+ # Add security headers if available
81
+ if otto_instance.respond_to?(:security_config) && otto_instance.security_config
82
+ otto_instance.security_config.security_headers.each do |header, value|
83
+ res.headers[header] = value
84
+ end
85
+ end
86
+ end
87
+ end
88
+
89
+ res.finish
90
+ end
91
+ end
92
+ end
93
+ end