Class: Otto::Core::MiddlewareStack
- Inherits:
-
Object
- Object
- Otto::Core::MiddlewareStack
- Includes:
- Enumerable, Freezable
- Defined in:
- lib/otto/core/middleware_stack.rb
Overview
Enhanced middleware stack management for Otto framework. Provides better middleware registration, introspection capabilities, and improved execution chain management.
Instance Method Summary collapse
-
#add(middleware_class, *args, **options) ⇒ Object
(also: #use, #<<)
Enhanced middleware registration with argument uniqueness and immutability check.
-
#add_with_position(middleware_class, *args, position: nil, **options) ⇒ Object
Add middleware with position hint for optimal ordering.
-
#clear! ⇒ Object
Clear all middleware.
-
#count(middleware_class) ⇒ Object
Count occurrences of a specific middleware class.
-
#each ⇒ Object
Enumerable support.
- #empty? ⇒ Boolean
-
#includes?(middleware_class) ⇒ Boolean
Check if middleware is registered - now O(1) using Set.
-
#initialize ⇒ MiddlewareStack
constructor
A new instance of MiddlewareStack.
-
#middleware_details ⇒ Object
Detailed introspection.
-
#middleware_list ⇒ Object
Returns list of middleware classes in order.
-
#on_change(&callback) ⇒ Object
Set a callback to be invoked when the middleware stack changes.
-
#remove(middleware_class) ⇒ Object
Remove middleware.
-
#reverse_each ⇒ Object
Legacy compatibility methods for existing Otto interface.
-
#size ⇒ Object
Statistics.
-
#validate_mcp_middleware_order ⇒ Array<String>
Validate MCP middleware ordering.
-
#wrap(base_app, security_config = nil) ⇒ Object
Build Rack application with middleware chain.
Methods included from Freezable
Constructor Details
#initialize ⇒ MiddlewareStack
Returns a new instance of MiddlewareStack.
16 17 18 19 20 |
# File 'lib/otto/core/middleware_stack.rb', line 16 def initialize @stack = [] @middleware_set = Set.new @on_change_callback = nil end |
Instance Method Details
#add(middleware_class, *args, **options) ⇒ Object Also known as: use, <<
Enhanced middleware registration with argument uniqueness and immutability check
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
# File 'lib/otto/core/middleware_stack.rb', line 29 def add(middleware_class, *args, **) # Prevent modifications to frozen configurations raise FrozenError, 'Cannot modify frozen middleware stack' if frozen? # Check if an identical middleware configuration already exists existing_entry = @stack.find do |entry| entry[:middleware] == middleware_class && entry[:args] == args && entry[:options] == end # Only add if no identical middleware configuration exists return if existing_entry entry = { middleware: middleware_class, args: args, options: } @stack << entry @middleware_set.add(middleware_class) # Notify of change @on_change_callback&.call end |
#add_with_position(middleware_class, *args, position: nil, **options) ⇒ Object
Add middleware with position hint for optimal ordering
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 |
# File 'lib/otto/core/middleware_stack.rb', line 56 def add_with_position(middleware_class, *args, position: nil, **) raise FrozenError, 'Cannot modify frozen middleware stack' if frozen? # Check for identical configuration existing_entry = @stack.find do |entry| entry[:middleware] == middleware_class && entry[:args] == args && entry[:options] == end return if existing_entry entry = { middleware: middleware_class, args: args, options: } case position when :first @stack.unshift(entry) when :last @stack << entry else @stack << entry # Default append end @middleware_set.add(middleware_class) # Notify of change @on_change_callback&.call end |
#clear! ⇒ Object
Clear all middleware
161 162 163 164 165 166 167 168 169 |
# File 'lib/otto/core/middleware_stack.rb', line 161 def clear! # Prevent modifications to frozen configurations raise FrozenError, 'Cannot modify frozen middleware stack' if frozen? @stack.clear @middleware_set.clear # Notify of change @on_change_callback&.call end |
#count(middleware_class) ⇒ Object
Count occurrences of a specific middleware class
223 224 225 |
# File 'lib/otto/core/middleware_stack.rb', line 223 def count(middleware_class) @stack.count { |entry| entry[:middleware] == middleware_class } end |
#each ⇒ Object
Enumerable support
172 173 174 |
# File 'lib/otto/core/middleware_stack.rb', line 172 def each(&) @stack.each(&) end |
#empty? ⇒ Boolean
218 219 220 |
# File 'lib/otto/core/middleware_stack.rb', line 218 def empty? @stack.empty? end |
#includes?(middleware_class) ⇒ Boolean
Check if middleware is registered - now O(1) using Set
156 157 158 |
# File 'lib/otto/core/middleware_stack.rb', line 156 def includes?(middleware_class) @middleware_set.include?(middleware_class) end |
#middleware_details ⇒ Object
Detailed introspection
203 204 205 206 207 208 209 210 211 |
# File 'lib/otto/core/middleware_stack.rb', line 203 def middleware_details @stack.map do |entry| { middleware: entry[:middleware], args: entry[:args], options: entry[:options], } end end |
#middleware_list ⇒ Object
Returns list of middleware classes in order
198 199 200 |
# File 'lib/otto/core/middleware_stack.rb', line 198 def middleware_list @stack.map { |entry| entry[:middleware] } end |
#on_change(&callback) ⇒ Object
Set a callback to be invoked when the middleware stack changes
24 25 26 |
# File 'lib/otto/core/middleware_stack.rb', line 24 def on_change(&callback) @on_change_callback = callback end |
#remove(middleware_class) ⇒ Object
Remove middleware
140 141 142 143 144 145 146 147 148 149 150 151 152 153 |
# File 'lib/otto/core/middleware_stack.rb', line 140 def remove(middleware_class) # Prevent modifications to frozen configurations raise FrozenError, 'Cannot modify frozen middleware stack' if frozen? matches = @stack.reject! { |entry| entry[:middleware] == middleware_class } # Update middleware set if any matching entries were found return unless matches # Rebuild the set of unique middleware classes @middleware_set = Set.new(@stack.map { |entry| entry[:middleware] }) # Notify of change @on_change_callback&.call end |
#reverse_each ⇒ Object
Legacy compatibility methods for existing Otto interface
230 231 232 |
# File 'lib/otto/core/middleware_stack.rb', line 230 def reverse_each(&) @stack.reverse_each(&) end |
#size ⇒ Object
Statistics
214 215 216 |
# File 'lib/otto/core/middleware_stack.rb', line 214 def size @stack.size end |
#validate_mcp_middleware_order ⇒ Array<String>
Validate MCP middleware ordering
MCP middleware must be in security-optimal order:
-
RateLimitMiddleware (reject excessive requests early)
-
Auth middleware (validate credentials before parsing)
-
SchemaValidationMiddleware (expensive JSON schema validation last)
92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 |
# File 'lib/otto/core/middleware_stack.rb', line 92 def validate_mcp_middleware_order warnings = [] # PERFORMANCE NOTE: This implementation intentionally uses select + find_index # rather than a single-pass approach. The filtered mcp_middlewares array is # typically 0-3 items, making the performance difference unmeasurable. # The current approach prioritizes readability over micro-optimization. # Single-pass alternatives were considered but rejected as premature optimization. mcp_middlewares = @stack.select do |entry| [ Otto::MCP::RateLimitMiddleware, Otto::MCP::Auth::TokenMiddleware, Otto::MCP::SchemaValidationMiddleware, ].include?(entry[:middleware]) end return warnings if mcp_middlewares.size < 2 # Find positions rate_limit_pos = mcp_middlewares.find_index { |e| e[:middleware] == Otto::MCP::RateLimitMiddleware } auth_pos = mcp_middlewares.find_index { |e| e[:middleware] == Otto::MCP::Auth::TokenMiddleware } validation_pos = mcp_middlewares.find_index { |e| e[:middleware] == Otto::MCP::SchemaValidationMiddleware } # Check optimal order: rate_limit < auth < validation if rate_limit_pos && auth_pos && rate_limit_pos > auth_pos warnings << <<~MSG.chomp [MCP Middleware] RateLimitMiddleware should come before TokenMiddleware MSG end if auth_pos && validation_pos && auth_pos > validation_pos warnings << <<~MSG.chomp [MCP Middleware] TokenMiddleware should come before SchemaValidationMiddleware MSG end if rate_limit_pos && validation_pos && rate_limit_pos > validation_pos warnings << <<~MSG.chomp [MCP Middleware] RateLimitMiddleware should come before SchemaValidationMiddleware MSG end warnings end |
#wrap(base_app, security_config = nil) ⇒ Object
Build Rack application with middleware chain
177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 |
# File 'lib/otto/core/middleware_stack.rb', line 177 def wrap(base_app, security_config = nil) @stack.reduce(base_app) do |app, entry| middleware = entry[:middleware] args = entry[:args] = entry[:options] if middleware.respond_to?(:new) # Inject security_config for security middleware, placing it before custom args if security_config && middleware_needs_config?(middleware) middleware.new(app, security_config, *args, **) else middleware.new(app, *args, **) end else # Proc-based middleware middleware.call(app) end end end |