Class: Spikard::App
- Inherits:
-
Object
- Object
- Spikard::App
- Includes:
- LifecycleHooks, ProvideSupport
- Defined in:
- lib/spikard/app.rb
Overview
Collects route metadata so the Rust engine can execute handlers. rubocop:disable Metrics/ClassLength
Constant Summary collapse
- HTTP_METHODS =
%w[GET POST PUT PATCH DELETE OPTIONS HEAD TRACE].freeze
- SUPPORTED_OPTIONS =
%i[request_schema response_schema parameter_schema file_params is_async cors body_param_name jsonrpc_method].freeze
Instance Attribute Summary collapse
-
#routes ⇒ Object
readonly
Returns the value of attribute routes.
Instance Method Summary collapse
- #default_handler_name(method, path) ⇒ Object
- #handler(name, &block) ⇒ Object
- #handler_map ⇒ Object
-
#initialize ⇒ App
constructor
A new instance of App.
- #normalized_routes_json ⇒ Object
- #register_route(method, path, handler_name: nil, **options, &block) ⇒ Object
- #route_metadata ⇒ Object
-
#run(config: nil, host: nil, port: nil) ⇒ Object
Run the Spikard server with the given configuration.
-
#sse(path, _handler_name: nil, **_options) { ... } ⇒ Proc
Register a Server-Sent Events endpoint.
-
#sse_producers ⇒ Hash
Get all registered SSE producers.
-
#websocket(path, _handler_name: nil, **_options) { ... } ⇒ Proc
Register a WebSocket endpoint.
-
#websocket_handlers ⇒ Hash
Get all registered WebSocket handlers.
Methods included from ProvideSupport
Methods included from LifecycleHooks
#on_error, #on_request, #on_response, #pre_handler, #pre_validation
Constructor Details
#initialize ⇒ App
Returns a new instance of App.
126 127 128 129 130 131 132 133 |
# File 'lib/spikard/app.rb', line 126 def initialize @routes = [] @websocket_handlers = {} @sse_producers = {} @native_hooks = Spikard::Native::LifecycleRegistry.new @native_dependencies = Spikard::Native::DependencyRegistry.new @named_handlers = {} end |
Instance Attribute Details
#routes ⇒ Object (readonly)
Returns the value of attribute routes.
124 125 126 |
# File 'lib/spikard/app.rb', line 124 def routes @routes end |
Instance Method Details
#default_handler_name(method, path) ⇒ Object
196 197 198 199 200 201 202 203 |
# File 'lib/spikard/app.rb', line 196 def default_handler_name(method, path) normalized_path = path.gsub(/[^a-zA-Z0-9]+/, '_').gsub(/__+/, '_') # ReDoS mitigation: use bounded quantifier {1,100} instead of + to prevent # polynomial time complexity with excessive trailing underscores normalized_path = normalized_path.sub(/^_{1,100}/, '').sub(/_{1,100}$/, '') normalized_path = 'root' if normalized_path.empty? "#{method.to_s.downcase}_#{normalized_path}" end |
#handler(name, &block) ⇒ Object
168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 |
# File 'lib/spikard/app.rb', line 168 def handler(name, &block) raise ArgumentError, 'block required for handler' unless block handler_name = name.to_s @named_handlers[handler_name] = block @routes.each do |entry| next unless entry.[:handler_name] == handler_name entry.handler = block next unless entry..is_a?(Hash) deps = extract_handler_dependencies(block) entry.[:handler_dependencies] = deps unless deps.empty? end block end |
#handler_map ⇒ Object
157 158 159 160 161 162 163 164 165 166 |
# File 'lib/spikard/app.rb', line 157 def handler_map map = {} @routes.each do |entry| name = entry.[:handler_name] # Pass raw handler - DI resolution happens in Rust layer map[name] = entry.handler end map.merge!(@named_handlers) map end |
#normalized_routes_json ⇒ Object
187 188 189 190 191 192 193 194 |
# File 'lib/spikard/app.rb', line 187 def normalized_routes_json json = JSON.generate() if defined?(Spikard::Native) && Spikard::Native.respond_to?(:normalize_route_metadata) Spikard::Native.(json) else json end end |
#register_route(method, path, handler_name: nil, **options, &block) ⇒ Object
135 136 137 138 139 140 141 142 143 144 145 |
# File 'lib/spikard/app.rb', line 135 def register_route(method, path, handler_name: nil, **, &block) method = method.to_s path = path.to_s handler_name = handler_name&.to_s handler = block || (handler_name && @named_handlers[handler_name]) validate_route_arguments!(handler, handler_name, ) = (method, path, handler_name, , handler) @routes << RouteEntry.new(, handler) handler end |
#route_metadata ⇒ Object
153 154 155 |
# File 'lib/spikard/app.rb', line 153 def @routes.map(&:metadata) end |
#run(config: nil, host: nil, port: nil) ⇒ Object
Run the Spikard server with the given configuration
rubocop:disable Metrics/MethodLength
274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 |
# File 'lib/spikard/app.rb', line 274 def run(config: nil, host: nil, port: nil) require 'json' # Backward compatibility: if host/port are provided directly, create a config if config.nil? && (host || port) config = ServerConfig.new( host: host || '127.0.0.1', port: port || 8000 ) elsif config.nil? config = ServerConfig.new elsif config.is_a?(Hash) config = ServerConfig.new(**config) end routes_json = normalized_routes_json # Get handler map handlers = handler_map # Get lifecycle hooks hooks = @native_hooks # Get WebSocket handlers and SSE producers ws_handlers = websocket_handlers sse_prods = sse_producers # Get dependencies for DI deps = @native_dependencies # Call the Rust extension's run_server function Spikard::Native.run_server(routes_json, handlers, config, hooks, ws_handlers, sse_prods, deps) # Keep Ruby process alive while server runs sleep rescue LoadError => e raise 'Failed to load Spikard extension. ' \ "Build it with: task build:ruby\n#{e.}" end |
#sse(path, _handler_name: nil, **_options) { ... } ⇒ Proc
Register a Server-Sent Events endpoint
232 233 234 235 236 237 |
# File 'lib/spikard/app.rb', line 232 def sse(path, _handler_name: nil, **, &factory) raise ArgumentError, 'block required for SSE producer factory' unless factory @sse_producers[path] = factory factory end |
#sse_producers ⇒ Hash
Get all registered SSE producers
249 250 251 |
# File 'lib/spikard/app.rb', line 249 def sse_producers @sse_producers.dup end |
#websocket(path, _handler_name: nil, **_options) { ... } ⇒ Proc
Register a WebSocket endpoint
215 216 217 218 219 220 |
# File 'lib/spikard/app.rb', line 215 def websocket(path, _handler_name: nil, **, &factory) raise ArgumentError, 'block required for WebSocket handler factory' unless factory @websocket_handlers[path] = factory factory end |
#websocket_handlers ⇒ Hash
Get all registered WebSocket handlers
242 243 244 |
# File 'lib/spikard/app.rb', line 242 def websocket_handlers @websocket_handlers.dup end |