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.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +1 -1
- data/.github/workflows/claude-code-review.yml +53 -0
- data/.github/workflows/claude.yml +49 -0
- data/.gitignore +3 -0
- data/.rubocop.yml +24 -345
- data/CHANGELOG.rst +83 -0
- data/CLAUDE.md +56 -0
- data/Gemfile +10 -3
- data/Gemfile.lock +23 -28
- data/README.md +2 -0
- data/bin/rspec +4 -4
- data/changelog.d/20250911_235619_delano_next.rst +28 -0
- data/changelog.d/20250912_123055_delano_remove_ostruct.rst +21 -0
- data/changelog.d/20250912_175625_claude_delano_remove_ostruct.rst +21 -0
- data/changelog.d/README.md +120 -0
- data/changelog.d/scriv.ini +5 -0
- data/docs/.gitignore +1 -0
- data/docs/migrating/v2.0.0-pre1.md +276 -0
- data/examples/.gitignore +1 -0
- data/examples/advanced_routes/README.md +33 -0
- data/examples/advanced_routes/app/controllers/handlers/async.rb +9 -0
- data/examples/advanced_routes/app/controllers/handlers/dynamic.rb +9 -0
- data/examples/advanced_routes/app/controllers/handlers/static.rb +9 -0
- data/examples/advanced_routes/app/controllers/modules/auth.rb +9 -0
- data/examples/advanced_routes/app/controllers/modules/transformer.rb +9 -0
- data/examples/advanced_routes/app/controllers/modules/validator.rb +9 -0
- data/examples/advanced_routes/app/controllers/routes_app.rb +232 -0
- data/examples/advanced_routes/app/controllers/v2/admin.rb +9 -0
- data/examples/advanced_routes/app/controllers/v2/config.rb +9 -0
- data/examples/advanced_routes/app/controllers/v2/settings.rb +9 -0
- data/examples/advanced_routes/app/logic/admin/logic/manager.rb +27 -0
- data/examples/advanced_routes/app/logic/admin/panel.rb +27 -0
- data/examples/advanced_routes/app/logic/analytics_processor.rb +25 -0
- data/examples/advanced_routes/app/logic/complex/business/handler.rb +27 -0
- data/examples/advanced_routes/app/logic/data_logic.rb +23 -0
- data/examples/advanced_routes/app/logic/data_processor.rb +25 -0
- data/examples/advanced_routes/app/logic/input_validator.rb +24 -0
- data/examples/advanced_routes/app/logic/nested/feature/logic.rb +27 -0
- data/examples/advanced_routes/app/logic/reports_generator.rb +27 -0
- data/examples/advanced_routes/app/logic/simple_logic.rb +25 -0
- data/examples/advanced_routes/app/logic/system/config/manager.rb +27 -0
- data/examples/advanced_routes/app/logic/test_logic.rb +23 -0
- data/examples/advanced_routes/app/logic/transform_logic.rb +23 -0
- data/examples/advanced_routes/app/logic/upload_logic.rb +23 -0
- data/examples/advanced_routes/app/logic/v2/logic/dashboard.rb +27 -0
- data/examples/advanced_routes/app/logic/v2/logic/processor.rb +27 -0
- data/examples/advanced_routes/app.rb +33 -0
- data/examples/advanced_routes/config.rb +23 -0
- data/examples/advanced_routes/config.ru +7 -0
- data/examples/advanced_routes/puma.rb +20 -0
- data/examples/advanced_routes/routes +167 -0
- data/examples/advanced_routes/run.rb +39 -0
- data/examples/advanced_routes/test.rb +58 -0
- data/examples/authentication_strategies/README.md +32 -0
- data/examples/authentication_strategies/app/auth.rb +68 -0
- data/examples/authentication_strategies/app/controllers/auth_controller.rb +29 -0
- data/examples/authentication_strategies/app/controllers/main_controller.rb +28 -0
- data/examples/authentication_strategies/config.ru +24 -0
- data/examples/authentication_strategies/routes +37 -0
- data/examples/basic/README.md +29 -0
- data/examples/basic/app.rb +7 -35
- data/examples/basic/routes +0 -9
- data/examples/mcp_demo/README.md +87 -0
- data/examples/mcp_demo/app.rb +29 -34
- data/examples/mcp_demo/config.ru +9 -60
- data/examples/security_features/README.md +46 -0
- data/examples/security_features/app.rb +23 -24
- data/examples/security_features/config.ru +8 -10
- data/lib/otto/core/configuration.rb +167 -0
- data/lib/otto/core/error_handler.rb +86 -0
- data/lib/otto/core/file_safety.rb +61 -0
- data/lib/otto/core/middleware_stack.rb +157 -0
- data/lib/otto/core/router.rb +183 -0
- data/lib/otto/core/uri_generator.rb +44 -0
- data/lib/otto/design_system.rb +7 -5
- data/lib/otto/helpers/base.rb +3 -0
- data/lib/otto/helpers/request.rb +10 -8
- data/lib/otto/helpers/response.rb +5 -4
- data/lib/otto/helpers/validation.rb +9 -7
- data/lib/otto/mcp/auth/token.rb +10 -9
- data/lib/otto/mcp/protocol.rb +24 -27
- data/lib/otto/mcp/rate_limiting.rb +8 -3
- data/lib/otto/mcp/registry.rb +7 -2
- data/lib/otto/mcp/route_parser.rb +10 -15
- data/lib/otto/mcp/server.rb +21 -11
- data/lib/otto/mcp/validation.rb +14 -10
- data/lib/otto/response_handlers/auto.rb +39 -0
- data/lib/otto/response_handlers/base.rb +16 -0
- data/lib/otto/response_handlers/default.rb +16 -0
- data/lib/otto/response_handlers/factory.rb +39 -0
- data/lib/otto/response_handlers/json.rb +28 -0
- data/lib/otto/response_handlers/redirect.rb +25 -0
- data/lib/otto/response_handlers/view.rb +24 -0
- data/lib/otto/response_handlers.rb +9 -135
- data/lib/otto/route.rb +9 -9
- data/lib/otto/route_definition.rb +15 -18
- data/lib/otto/route_handlers/base.rb +121 -0
- data/lib/otto/route_handlers/class_method.rb +89 -0
- data/lib/otto/route_handlers/factory.rb +29 -0
- data/lib/otto/route_handlers/instance_method.rb +69 -0
- data/lib/otto/route_handlers/lambda.rb +59 -0
- data/lib/otto/route_handlers/logic_class.rb +93 -0
- data/lib/otto/route_handlers.rb +10 -405
- data/lib/otto/security/authentication/auth_strategy.rb +44 -0
- data/lib/otto/security/authentication/authentication_middleware.rb +123 -0
- data/lib/otto/security/authentication/failure_result.rb +36 -0
- data/lib/otto/security/authentication/strategies/api_key_strategy.rb +40 -0
- data/lib/otto/security/authentication/strategies/permission_strategy.rb +47 -0
- data/lib/otto/security/authentication/strategies/public_strategy.rb +19 -0
- data/lib/otto/security/authentication/strategies/role_strategy.rb +57 -0
- data/lib/otto/security/authentication/strategies/session_strategy.rb +41 -0
- data/lib/otto/security/authentication/strategy_result.rb +223 -0
- data/lib/otto/security/authentication.rb +28 -282
- data/lib/otto/security/config.rb +14 -12
- data/lib/otto/security/configurator.rb +219 -0
- data/lib/otto/security/csrf.rb +8 -143
- data/lib/otto/security/middleware/csrf_middleware.rb +151 -0
- data/lib/otto/security/middleware/rate_limit_middleware.rb +38 -0
- data/lib/otto/security/middleware/validation_middleware.rb +252 -0
- data/lib/otto/security/rate_limiter.rb +86 -0
- data/lib/otto/security/rate_limiting.rb +10 -105
- data/lib/otto/security/validator.rb +8 -253
- data/lib/otto/static.rb +3 -0
- data/lib/otto/utils.rb +14 -0
- data/lib/otto/version.rb +3 -1
- data/lib/otto.rb +142 -498
- data/otto.gemspec +2 -2
- metadata +89 -28
- data/examples/dynamic_pages/app.rb +0 -115
- data/examples/dynamic_pages/config.ru +0 -30
- data/examples/dynamic_pages/routes +0 -21
- data/examples/helpers_demo/app.rb +0 -244
- data/examples/helpers_demo/config.ru +0 -26
- data/examples/helpers_demo/routes +0 -7
- 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
|