Class: Spikard::App

Inherits:
Object
  • Object
show all
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].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from ProvideSupport

#dependencies, #provide

Methods included from LifecycleHooks

#lifecycle_hooks, #on_error, #on_request, #on_response, #pre_handler, #pre_validation

Constructor Details

#initializeApp

Returns a new instance of App.



130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/spikard/app.rb', line 130

def initialize
  @routes = []
  @websocket_handlers = {}
  @sse_producers = {}
  @dependencies = {}
  @lifecycle_hooks = {
    on_request: [],
    pre_validation: [],
    pre_handler: [],
    on_response: [],
    on_error: []
  }
end

Instance Attribute Details

#routesObject (readonly)

Returns the value of attribute routes.



128
129
130
# File 'lib/spikard/app.rb', line 128

def routes
  @routes
end

Instance Method Details

#default_handler_name(method, path) ⇒ Object



187
188
189
190
191
# File 'lib/spikard/app.rb', line 187

def default_handler_name(method, path)
  normalized_path = path.gsub(/[^a-zA-Z0-9]+/, '_').gsub(/__+/, '_').sub(/^_+|_+$/, '')
  normalized_path = 'root' if normalized_path.empty?
  "#{method.to_s.downcase}_#{normalized_path}"
end

#handler_mapObject



177
178
179
180
181
182
183
184
185
# File 'lib/spikard/app.rb', line 177

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

#register_route(method, path, handler_name: nil, **options, &block) ⇒ Object



144
145
146
147
148
149
150
151
152
153
154
155
# File 'lib/spikard/app.rb', line 144

def register_route(method, path, handler_name: nil, **options, &block)
  validate_route_arguments!(block, options)
  handler_name ||= default_handler_name(method, path)

  # Extract handler dependencies from block parameters
  handler_dependencies = extract_handler_dependencies(block)

   = (method, path, handler_name, options, handler_dependencies)

  @routes << RouteEntry.new(, block)
  block
end

#route_metadataObject



163
164
165
166
167
168
169
170
171
172
173
174
175
# File 'lib/spikard/app.rb', line 163

def 
  # Extract handler dependencies when metadata is requested
  # This allows dependencies to be registered after routes
  @routes.map do |entry|
     = entry..dup

    # Re-extract dependencies in case they were registered after the route
    handler_dependencies = extract_handler_dependencies(entry.handler)
    [:handler_dependencies] = handler_dependencies unless handler_dependencies.empty?

    
  end
end

#run(config: nil, host: nil, port: nil) ⇒ Object

Run the Spikard server with the given configuration

rubocop:disable Metrics/AbcSize, Metrics/MethodLength

Examples:

With ServerConfig

config = Spikard::ServerConfig.new(
  host: '0.0.0.0',
  port: 8080,
  compression: Spikard::CompressionConfig.new(quality: 9)
)
app.run(config: config)

With Hash

app.run(config: { host: '0.0.0.0', port: 8080 })

Backward compatible (deprecated)

app.run(host: '0.0.0.0', port: 8000)

Parameters:

  • config (ServerConfig, Hash, nil) (defaults to: nil)

    Server configuration Can be a ServerConfig object, a Hash with configuration keys, or nil to use defaults. If a Hash is provided, it will be converted to a ServerConfig. For backward compatibility, also accepts host: and port: keyword arguments.



262
263
264
265
266
267
268
269
270
271
272
273
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
# File 'lib/spikard/app.rb', line 262

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

  # Convert route metadata to JSON
  routes_json = JSON.generate()

  # Get handler map
  handlers = handler_map

  # Get lifecycle hooks
  hooks = lifecycle_hooks

  # Get WebSocket handlers and SSE producers
  ws_handlers = websocket_handlers
  sse_prods = sse_producers

  # Get dependencies for DI
  deps = 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.message}"
end

#sse(path, _handler_name: nil, **_options) { ... } ⇒ Proc

Register a Server-Sent Events endpoint

Examples:

app.sse('/notifications') do
  NotificationProducer.new
end

Parameters:

  • path (String)

    URL path for the SSE endpoint

Yields:

  • Factory block that returns a SseEventProducer instance

Returns:

  • (Proc)

    The factory block (for chaining)

Raises:

  • (ArgumentError)


220
221
222
223
224
225
# File 'lib/spikard/app.rb', line 220

def sse(path, _handler_name: nil, **_options, &factory)
  raise ArgumentError, 'block required for SSE producer factory' unless factory

  @sse_producers[path] = factory
  factory
end

#sse_producersHash

Get all registered SSE producers

Returns:

  • (Hash)

    Dictionary mapping paths to producer factory blocks



237
238
239
# File 'lib/spikard/app.rb', line 237

def sse_producers
  @sse_producers.dup
end

#websocket(path, _handler_name: nil, **_options) { ... } ⇒ Proc

Register a WebSocket endpoint

Examples:

app.websocket('/chat') do
  ChatHandler.new
end

Parameters:

  • path (String)

    URL path for the WebSocket endpoint

Yields:

  • Factory block that returns a WebSocketHandler instance

Returns:

  • (Proc)

    The factory block (for chaining)

Raises:

  • (ArgumentError)


203
204
205
206
207
208
# File 'lib/spikard/app.rb', line 203

def websocket(path, _handler_name: nil, **_options, &factory)
  raise ArgumentError, 'block required for WebSocket handler factory' unless factory

  @websocket_handlers[path] = factory
  factory
end

#websocket_handlersHash

Get all registered WebSocket handlers

Returns:

  • (Hash)

    Dictionary mapping paths to handler factory blocks



230
231
232
# File 'lib/spikard/app.rb', line 230

def websocket_handlers
  @websocket_handlers.dup
end