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].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_map ⇒ Object
-
#initialize ⇒ App
constructor
A new instance of App.
- #normalized_routes_json ⇒ Object
-
#register_route(method, path, handler_name: nil, **options, &block) ⇒ Object
rubocop:disable Metrics/AbcSize, Metrics/MethodLength.
- #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 |
# 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 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
197 198 199 200 201 202 203 204 |
# File 'lib/spikard/app.rb', line 197 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_map ⇒ Object
178 179 180 181 182 183 184 185 186 |
# File 'lib/spikard/app.rb', line 178 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 end |
#normalized_routes_json ⇒ Object
188 189 190 191 192 193 194 195 |
# File 'lib/spikard/app.rb', line 188 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
rubocop:disable Metrics/AbcSize, Metrics/MethodLength
135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 |
# 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 validate_route_arguments!(block, ) = if defined?(Spikard::Native) && Spikard::Native.respond_to?(:build_route_metadata) Spikard::Native.( method, path, handler_name, [:request_schema], [:response_schema], [:parameter_schema], [:file_params], .fetch(:is_async, false), [:cors], [:body_param_name]&.to_s, block ) else handler_name ||= default_handler_name(method, path) # Extract handler dependencies from block parameters handler_dependencies = extract_handler_dependencies(block) (method, path, handler_name, , handler_dependencies) end @routes << RouteEntry.new(, block) block end |
#route_metadata ⇒ Object
174 175 176 |
# File 'lib/spikard/app.rb', line 174 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
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 313 |
# File 'lib/spikard/app.rb', line 275 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
233 234 235 236 237 238 |
# File 'lib/spikard/app.rb', line 233 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
250 251 252 |
# File 'lib/spikard/app.rb', line 250 def sse_producers @sse_producers.dup end |
#websocket(path, _handler_name: nil, **_options) { ... } ⇒ Proc
Register a WebSocket endpoint
216 217 218 219 220 221 |
# File 'lib/spikard/app.rb', line 216 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
243 244 245 |
# File 'lib/spikard/app.rb', line 243 def websocket_handlers @websocket_handlers.dup end |