Class: Spikard::App

Inherits:
Object
  • Object
show all
Includes:
LifecycleHooks
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 LifecycleHooks

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

Constructor Details

#initializeApp

Returns a new instance of App.



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

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

Instance Attribute Details

#routesObject (readonly)

Returns the value of attribute routes.



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

def routes
  @routes
end

Instance Method Details

#default_handler_name(method, path) ⇒ Object



170
171
172
173
174
# File 'lib/spikard/app.rb', line 170

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



161
162
163
164
165
166
167
168
# File 'lib/spikard/app.rb', line 161

def handler_map
  map = {}
  @routes.each do |entry|
    name = entry.[:handler_name]
    map[name] = entry.handler
  end
  map
end

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



142
143
144
145
146
147
148
149
# File 'lib/spikard/app.rb', line 142

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

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

#route_metadataObject



157
158
159
# File 'lib/spikard/app.rb', line 157

def 
  @routes.map(&:metadata)
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.



245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
# File 'lib/spikard/app.rb', line 245

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

  # Call the Rust extension's run_server function
  Spikard::Native.run_server(routes_json, handlers, config, hooks, ws_handlers, sse_prods)

  # 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)


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

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



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

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)


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

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



213
214
215
# File 'lib/spikard/app.rb', line 213

def websocket_handlers
  @websocket_handlers.dup
end