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
@@ -1,111 +1,16 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
3
|
+
# lib/otto/security/rate_limiting.rb
|
4
|
+
#
|
5
|
+
# Index file for rate limiting components
|
6
|
+
# Provides backward compatibility for existing rate limiting usage
|
7
|
+
|
8
|
+
require_relative 'rate_limiter'
|
9
|
+
require_relative 'middleware/rate_limit_middleware'
|
8
10
|
|
9
11
|
class Otto
|
10
12
|
module Security
|
11
|
-
|
12
|
-
|
13
|
-
return unless defined?(Rack::Attack)
|
14
|
-
|
15
|
-
# Use provided cache store or default
|
16
|
-
if config[:cache_store]
|
17
|
-
Rack::Attack.cache.store = config[:cache_store]
|
18
|
-
end
|
19
|
-
|
20
|
-
# Default rules
|
21
|
-
default_requests_per_minute = config.fetch(:requests_per_minute, 100)
|
22
|
-
|
23
|
-
# General request throttling
|
24
|
-
Rack::Attack.throttle('requests', limit: default_requests_per_minute, period: 60) do |request|
|
25
|
-
request.ip unless request.path.start_with?('/_') # Skip internal paths by default
|
26
|
-
end
|
27
|
-
|
28
|
-
# Apply custom rules if provided
|
29
|
-
if config[:custom_rules]
|
30
|
-
config[:custom_rules].each do |name, rule_config|
|
31
|
-
limit = rule_config[:limit]
|
32
|
-
period = rule_config[:period] || 60
|
33
|
-
condition = rule_config[:condition]
|
34
|
-
|
35
|
-
Rack::Attack.throttle(name.to_s, limit: limit, period: period) do |request|
|
36
|
-
if condition
|
37
|
-
request.ip if condition.call(request)
|
38
|
-
else
|
39
|
-
request.ip
|
40
|
-
end
|
41
|
-
end
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
# Custom response for rate limited requests
|
46
|
-
Rack::Attack.throttled_responder = lambda do |request|
|
47
|
-
match_data = request.env['rack.attack.match_data']
|
48
|
-
now = match_data[:epoch_time]
|
49
|
-
|
50
|
-
headers = {
|
51
|
-
'content-type' => 'application/json',
|
52
|
-
'retry-after' => (match_data[:period] - (now % match_data[:period])).to_s,
|
53
|
-
}
|
54
|
-
|
55
|
-
# Check if request expects JSON
|
56
|
-
accept_header = request.env['HTTP_ACCEPT'].to_s
|
57
|
-
if accept_header.include?('application/json')
|
58
|
-
error_response = {
|
59
|
-
error: 'Rate limit exceeded',
|
60
|
-
message: 'Too many requests',
|
61
|
-
retry_after: headers['retry-after'].to_i,
|
62
|
-
limit: match_data[:limit],
|
63
|
-
period: match_data[:period],
|
64
|
-
}
|
65
|
-
[429, headers, [JSON.generate(error_response)]]
|
66
|
-
else
|
67
|
-
body = "Rate limit exceeded. Retry after #{headers['retry-after']} seconds."
|
68
|
-
headers['content-type'] = 'text/plain'
|
69
|
-
[429, headers, [body]]
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
73
|
-
# Log blocked requests if ActiveSupport is available
|
74
|
-
return unless defined?(ActiveSupport::Notifications)
|
75
|
-
|
76
|
-
ActiveSupport::Notifications.subscribe('rack.attack') do |_name, _start, _finish, _request_id, payload|
|
77
|
-
req = payload[:request]
|
78
|
-
Otto.logger.warn "[Otto] Rate limit #{payload[:match_type]} for #{req.ip}: #{payload[:matched]}"
|
79
|
-
end
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
|
-
class RateLimitMiddleware
|
84
|
-
def initialize(app, security_config = nil)
|
85
|
-
@app = app
|
86
|
-
@security_config = security_config
|
87
|
-
@rate_limiter_available = defined?(Rack::Attack)
|
88
|
-
|
89
|
-
if @rate_limiter_available
|
90
|
-
configure_rate_limiting
|
91
|
-
else
|
92
|
-
Otto.logger.warn '[Otto] rack-attack not available - rate limiting disabled'
|
93
|
-
end
|
94
|
-
end
|
95
|
-
|
96
|
-
def call(env)
|
97
|
-
return @app.call(env) unless @rate_limiter_available
|
98
|
-
|
99
|
-
# Let rack-attack handle the rate limiting
|
100
|
-
@app.call(env)
|
101
|
-
end
|
102
|
-
|
103
|
-
private
|
104
|
-
|
105
|
-
def configure_rate_limiting
|
106
|
-
config = @security_config&.rate_limiting_config || {}
|
107
|
-
RateLimiting.configure_rack_attack!(config)
|
108
|
-
end
|
109
|
-
end
|
13
|
+
# Backward compatibility alias
|
14
|
+
RateLimitMiddleware = Middleware::RateLimitMiddleware
|
110
15
|
end
|
111
16
|
end
|
@@ -1,260 +1,15 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
3
|
+
# lib/otto/security/validator.rb
|
4
|
+
#
|
5
|
+
# Index file for validation middleware
|
6
|
+
# Provides backward compatibility for existing validation usage
|
7
7
|
|
8
|
-
require_relative '
|
8
|
+
require_relative 'middleware/validation_middleware'
|
9
9
|
|
10
10
|
class Otto
|
11
11
|
module Security
|
12
|
-
#
|
13
|
-
|
14
|
-
class ValidationMiddleware
|
15
|
-
# Character validation patterns
|
16
|
-
INVALID_CHARACTERS = /[\x00-\x1f\x7f-\xff]/n
|
17
|
-
NULL_BYTE = /\0/
|
18
|
-
|
19
|
-
# HTML/XSS sanitization is handled by Loofah library for better security coverage
|
20
|
-
|
21
|
-
SQL_INJECTION_PATTERNS = [
|
22
|
-
/('|(\\')|(;)|(\\)|(--)|(%27)|(%3B)|(%3D))/i,
|
23
|
-
/(union|select|insert|update|delete|drop|create|alter|exec|execute)/i,
|
24
|
-
/(or|and)\s+\w+\s*=\s*\w+/i,
|
25
|
-
/\d+\s*(=|>|<|>=|<=|<>|!=)\s*\d+/i,
|
26
|
-
].freeze
|
27
|
-
|
28
|
-
def initialize(app, config = nil)
|
29
|
-
@app = app
|
30
|
-
@config = config || Otto::Security::Config.new
|
31
|
-
end
|
32
|
-
|
33
|
-
def call(env)
|
34
|
-
return @app.call(env) unless @config.input_validation
|
35
|
-
|
36
|
-
request = Rack::Request.new(env)
|
37
|
-
|
38
|
-
begin
|
39
|
-
# Validate request size
|
40
|
-
validate_request_size(request)
|
41
|
-
|
42
|
-
# Validate content type
|
43
|
-
validate_content_type(request)
|
44
|
-
|
45
|
-
# Validate and sanitize parameters
|
46
|
-
begin
|
47
|
-
validate_parameters(request) if request.params
|
48
|
-
rescue Rack::QueryParser::QueryLimitError => ex
|
49
|
-
# Handle Rack's built-in query parsing limits
|
50
|
-
raise Otto::Security::ValidationError, "Parameter structure too complex: #{ex.message}"
|
51
|
-
end
|
52
|
-
|
53
|
-
# Validate headers
|
54
|
-
validate_headers(request)
|
55
|
-
|
56
|
-
@app.call(env)
|
57
|
-
rescue Otto::Security::ValidationError => ex
|
58
|
-
validation_error_response(ex.message)
|
59
|
-
rescue Otto::Security::RequestTooLargeError => ex
|
60
|
-
request_too_large_response(ex.message)
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
|
-
private
|
65
|
-
|
66
|
-
def validate_request_size(request)
|
67
|
-
content_length = request.env['CONTENT_LENGTH']
|
68
|
-
@config.validate_request_size(content_length)
|
69
|
-
end
|
70
|
-
|
71
|
-
def validate_content_type(request)
|
72
|
-
content_type = request.env['CONTENT_TYPE']
|
73
|
-
return unless content_type
|
74
|
-
|
75
|
-
# Block dangerous content types
|
76
|
-
dangerous_types = [
|
77
|
-
'application/x-shockwave-flash',
|
78
|
-
'application/x-silverlight-app',
|
79
|
-
'text/vbscript',
|
80
|
-
'application/vbscript',
|
81
|
-
]
|
82
|
-
|
83
|
-
if dangerous_types.any? { |type| content_type.downcase.include?(type) }
|
84
|
-
raise Otto::Security::ValidationError, "Dangerous content type: #{content_type}"
|
85
|
-
end
|
86
|
-
end
|
87
|
-
|
88
|
-
def validate_parameters(request)
|
89
|
-
validate_param_structure(request.params, 0)
|
90
|
-
sanitize_params(request.params)
|
91
|
-
end
|
92
|
-
|
93
|
-
def validate_param_structure(params, depth = 0)
|
94
|
-
if depth >= @config.max_param_depth
|
95
|
-
raise Otto::Security::ValidationError, "Parameter depth exceeds maximum (#{@config.max_param_depth})"
|
96
|
-
end
|
97
|
-
|
98
|
-
case params
|
99
|
-
when Hash
|
100
|
-
if params.keys.length > @config.max_param_keys
|
101
|
-
raise Otto::Security::ValidationError, "Too many parameters (#{params.keys.length} > #{@config.max_param_keys})"
|
102
|
-
end
|
103
|
-
|
104
|
-
params.each do |key, value|
|
105
|
-
validate_param_key(key)
|
106
|
-
validate_param_structure(value, depth + 1) if value.is_a?(Hash) || value.is_a?(Array)
|
107
|
-
end
|
108
|
-
when Array
|
109
|
-
if params.length > @config.max_param_keys
|
110
|
-
raise Otto::Security::ValidationError, "Too many array elements (#{params.length} > #{@config.max_param_keys})"
|
111
|
-
end
|
112
|
-
|
113
|
-
params.each do |value|
|
114
|
-
validate_param_structure(value, depth + 1) if value.is_a?(Hash) || value.is_a?(Array)
|
115
|
-
end
|
116
|
-
end
|
117
|
-
end
|
118
|
-
|
119
|
-
def validate_param_key(key)
|
120
|
-
key_str = key.to_s
|
121
|
-
|
122
|
-
# Check for dangerous characters in parameter names using shared patterns
|
123
|
-
if key_str.match?(NULL_BYTE) || key_str.match?(INVALID_CHARACTERS)
|
124
|
-
raise Otto::Security::ValidationError, "Invalid characters in parameter name: #{key_str}"
|
125
|
-
end
|
126
|
-
|
127
|
-
# Check for suspiciously long parameter names
|
128
|
-
if key_str.length > 256
|
129
|
-
raise Otto::Security::ValidationError, "Parameter name too long: #{key_str[0..50]}..."
|
130
|
-
end
|
131
|
-
end
|
132
|
-
|
133
|
-
def sanitize_params(params)
|
134
|
-
case params
|
135
|
-
when Hash
|
136
|
-
params.each do |key, value|
|
137
|
-
params[key] = sanitize_value(value)
|
138
|
-
end
|
139
|
-
when Array
|
140
|
-
params.map! { |value| sanitize_value(value) }
|
141
|
-
else
|
142
|
-
sanitize_value(params)
|
143
|
-
end
|
144
|
-
end
|
145
|
-
|
146
|
-
def sanitize_value(value)
|
147
|
-
return value unless value.is_a?(String)
|
148
|
-
|
149
|
-
# Check for extremely long values first
|
150
|
-
if value.length > 10_000
|
151
|
-
raise Otto::Security::ValidationError, "Parameter value too long (#{value.length} characters)"
|
152
|
-
end
|
153
|
-
|
154
|
-
# Start with the original value
|
155
|
-
original = value.dup
|
156
|
-
|
157
|
-
# Check for null bytes first (these should be rejected, not sanitized)
|
158
|
-
if original.match?(NULL_BYTE)
|
159
|
-
raise Otto::Security::ValidationError, 'Dangerous content detected in parameter'
|
160
|
-
end
|
161
|
-
|
162
|
-
# Check for script injection first (these should always be rejected)
|
163
|
-
if looks_like_script_injection?(original)
|
164
|
-
raise Otto::Security::ValidationError, 'Dangerous content detected in parameter'
|
165
|
-
end
|
166
|
-
|
167
|
-
# Use Loofah to sanitize HTML/XSS content for less dangerous HTML
|
168
|
-
# Loofah.fragment removes dangerous HTML but preserves safe content
|
169
|
-
sanitized = Loofah.fragment(original).scrub!(:whitewash).to_s
|
170
|
-
|
171
|
-
# Remove control characters (sanitize, don't block)
|
172
|
-
sanitized = sanitized.gsub(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/, '')
|
173
|
-
|
174
|
-
# Check for SQL injection patterns
|
175
|
-
SQL_INJECTION_PATTERNS.each do |pattern|
|
176
|
-
if sanitized.match?(pattern)
|
177
|
-
raise Otto::Security::ValidationError, 'Potential SQL injection detected'
|
178
|
-
end
|
179
|
-
end
|
180
|
-
|
181
|
-
sanitized
|
182
|
-
end
|
183
|
-
|
184
|
-
include ValidationHelpers
|
185
|
-
|
186
|
-
private
|
187
|
-
|
188
|
-
def validate_headers(request)
|
189
|
-
# Check for dangerous headers
|
190
|
-
dangerous_headers = %w[
|
191
|
-
HTTP_X_FORWARDED_HOST
|
192
|
-
HTTP_X_ORIGINAL_URL
|
193
|
-
HTTP_X_REWRITE_URL
|
194
|
-
HTTP_DESTINATION
|
195
|
-
HTTP_UPGRADE_INSECURE_REQUESTS
|
196
|
-
]
|
197
|
-
|
198
|
-
dangerous_headers.each do |header|
|
199
|
-
value = request.env[header]
|
200
|
-
next unless value
|
201
|
-
|
202
|
-
# Basic validation - no null bytes or control characters
|
203
|
-
if value.match?(NULL_BYTE) || value.match?(INVALID_CHARACTERS)
|
204
|
-
raise Otto::Security::ValidationError, "Invalid characters in header: #{header}"
|
205
|
-
end
|
206
|
-
end
|
207
|
-
|
208
|
-
# Validate User-Agent length
|
209
|
-
user_agent = request.env['HTTP_USER_AGENT']
|
210
|
-
if user_agent && user_agent.length > 1000
|
211
|
-
raise Otto::Security::ValidationError, 'User-Agent header too long'
|
212
|
-
end
|
213
|
-
|
214
|
-
# Validate Referer header
|
215
|
-
referer = request.env['HTTP_REFERER']
|
216
|
-
if referer && referer.length > 2000
|
217
|
-
raise Otto::Security::ValidationError, 'Referer header too long'
|
218
|
-
end
|
219
|
-
end
|
220
|
-
|
221
|
-
def validation_error_response(message)
|
222
|
-
[
|
223
|
-
400,
|
224
|
-
{
|
225
|
-
'content-type' => 'application/json',
|
226
|
-
'content-length' => validation_error_body(message).bytesize.to_s,
|
227
|
-
},
|
228
|
-
[validation_error_body(message)],
|
229
|
-
]
|
230
|
-
end
|
231
|
-
|
232
|
-
def request_too_large_response(message)
|
233
|
-
[
|
234
|
-
413,
|
235
|
-
{
|
236
|
-
'content-type' => 'application/json',
|
237
|
-
'content-length' => request_too_large_body(message).bytesize.to_s,
|
238
|
-
},
|
239
|
-
[request_too_large_body(message)],
|
240
|
-
]
|
241
|
-
end
|
242
|
-
|
243
|
-
def validation_error_body(message)
|
244
|
-
require 'json'
|
245
|
-
{
|
246
|
-
error: 'Validation failed',
|
247
|
-
message: message,
|
248
|
-
}.to_json
|
249
|
-
end
|
250
|
-
|
251
|
-
def request_too_large_body(message)
|
252
|
-
require 'json'
|
253
|
-
{
|
254
|
-
error: 'Request too large',
|
255
|
-
message: message,
|
256
|
-
}.to_json
|
257
|
-
end
|
258
|
-
end
|
12
|
+
# Backward compatibility alias
|
13
|
+
ValidationMiddleware = Middleware::ValidationMiddleware
|
259
14
|
end
|
260
15
|
end
|
data/lib/otto/static.rb
CHANGED
data/lib/otto/utils.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# lib/otto/utils.rb
|
4
|
+
|
5
|
+
class Otto
|
6
|
+
# Utility methods for common operations and helpers
|
7
|
+
module Utils
|
8
|
+
extend self
|
9
|
+
|
10
|
+
def yes?(value)
|
11
|
+
!value.to_s.empty? && %w[true yes 1].include?(value.to_s.downcase)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|