Class: Takagi::Router

Inherits:
Object
  • Object
show all
Defined in:
lib/takagi/router.rb,
lib/takagi/router/route_matcher.rb,
lib/takagi/router/metadata_extractor.rb,
sig/takagi/router.rbs,
sig/takagi/router/route_matcher.rbs,
sig/takagi/router/metadata_extractor.rbs

Direct Known Subclasses

Base

Defined Under Namespace

Classes: MetadataExtractor, RouteContext, RouteEntry, RouteMatcher

Constant Summary collapse

DEFAULT_CONTENT_FORMAT =

Returns:

  • (50)
Takagi::CoAP::Registries::ContentFormat::JSON

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeRouter

Returns a new instance of Router.



144
145
146
147
148
149
150
# File 'lib/takagi/router.rb', line 144

def initialize
  @routes = {}
  @routes_mutex = Mutex.new # Protects route modifications in multithreaded environments
  @logger = Takagi.logger
  @route_matcher = RouteMatcher.new(@logger)
  @metadata_extractor = MetadataExtractor.new(@logger)
end

Class Method Details

.instanceRouter

Global singleton instance for backward compatibility

Returns:



19
20
21
# File 'lib/takagi/router.rb', line 19

def instance
  @instance ||= new
end

.reset!void

This method returns an undefined value.

Reset the global instance (for testing)



26
27
28
29
30
# File 'lib/takagi/router.rb', line 26

def reset!
  @instance = nil
  # Ensure Takagi::Base fetches a fresh router after reset
  Takagi::Base.instance_variable_set(:@router, nil) if defined?(Takagi::Base)
end

Instance Method Details

#add_route(method, path, metadata: {}, &block) {|arg0| ... } ⇒ Object

Registers a new route for a given HTTP method and path

Parameters:

  • method (String)

    The HTTP method (GET, POST, etc.)

  • path (String)

    The URL path, can include dynamic segments like :id

  • block (Proc)

    The handler to be executed when the route is matched

  • metadata: (::Hash[untyped, untyped]) (defaults to: {})

Yields:

Yield Parameters:

  • arg0

Yield Returns:

  • (Object)

Returns:

  • (Object)


156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
# File 'lib/takagi/router.rb', line 156

def add_route(method, path, metadata: {}, &block)
  @routes_mutex.synchronize do
    entry = build_route_entry(method, path, , block)
    @routes["#{method} #{path}"] = entry
    @logger.debug "Add new route: #{method} #{path}"

    # Extract metadata from core blocks inside the handler
    (entry) if block

    Takagi::Hooks.emit(
      :router_route_added,
      method: method,
      path: path,
      entry: entry
    )
  end
end

#all_routesObject

Returns:

  • (Object)


193
194
195
# File 'lib/takagi/router.rb', line 193

def all_routes
  @routes.values.map { |entry| "#{entry.method} #{entry.path}" }
end

#build_route_entry(method, path, metadata, block) ⇒ Object

Parameters:

  • method (Object)
  • path (Object)
  • metadata (Object)
  • block (Object)

Returns:

  • (Object)


266
267
268
269
270
271
272
273
274
# File 'lib/takagi/router.rb', line 266

def build_route_entry(method, path, , block)
  RouteEntry.new(
    method: method,
    path: path,
    block: block,
    metadata: (method, path, ),
    receiver: block&.binding&.receiver
  )
end

#configure_core(method, path, &block) ⇒ void

This method returns an undefined value.

Applies CoRE metadata outside the request cycle. Useful for boot time configuration where the DSL block does not have a live request object.

Parameters:

  • method (Object)
  • path (Object)


231
232
233
234
235
236
237
238
239
240
241
242
243
# File 'lib/takagi/router.rb', line 231

def configure_core(method, path, &block)
  return unless block

  @routes_mutex.synchronize do
    entry = @routes["#{method} #{path}"]
    unless entry
      @logger.warn "configure_core skipped: #{method} #{path} not registered"
      return
    end

    entry.configure_attributes(&block)
  end
end

#default_interface(method) ⇒ "takagi.observe", ::String

Parameters:

  • method (Object)

Returns:

  • ("takagi.observe", ::String)


295
296
297
# File 'lib/takagi/router.rb', line 295

def default_interface(method)
  method == 'OBSERVE' ? 'takagi.observe' : "takagi.#{method.downcase}"
end

#default_resource_type(method) ⇒ "core#observable", "core#endpoint"

Parameters:

  • method (Object)

Returns:



291
292
293
# File 'lib/takagi/router.rb', line 291

def default_resource_type(method)
  method == 'OBSERVE' ? 'core#observable' : 'core#endpoint'
end

#extract_metadata_from_handler(entry) ⇒ Object

Executes route handler in metadata extraction mode to capture core block attributes Delegates to MetadataExtractor for the actual extraction logic

Parameters:

  • entry (RouteEntry)

    The route entry to extract metadata from

Returns:

  • (Object)


302
303
304
# File 'lib/takagi/router.rb', line 302

def (entry)
  @metadata_extractor.extract(entry)
end

#find_observable(path) ⇒ Object

Parameters:

  • path (Object)

Returns:

  • (Object)


197
198
199
# File 'lib/takagi/router.rb', line 197

def find_observable(path)
  @routes.values.find { |entry| entry.method == 'OBSERVE' && entry.path == path }
end

#find_route(method, path) ⇒ Proc, Hash

Finds a registered route for a given method and path

Parameters:

  • method (String)

    HTTP method

  • path (String)

    URL path

Returns:

  • (Proc, Hash)

    The matching handler and extracted parameters



205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
# File 'lib/takagi/router.rb', line 205

def find_route(method, path)
  @routes_mutex.synchronize do
    @logger.debug "Routes: #{@routes.inspect}"
    @logger.debug "Looking for route: #{method} #{path}"
    entry = @routes["#{method} #{path}"]
    params = {}

    return wrap_block(entry), params if entry

    @logger.debug '[Debug] Find dynamic route'
    entry, params = match_dynamic_route(method, path)

    return wrap_block(entry), params if entry

    [nil, {}]
  end
end

Returns:

  • (Object)


223
224
225
226
227
# File 'lib/takagi/router.rb', line 223

def link_format_entries
  @routes_mutex.synchronize do
    @routes.values.reject { |entry| entry.[:discovery] }.map(&:dup)
  end
end

#match_dynamic_route(method, path) ⇒ Array(RouteEntry, Hash)

Matches dynamic routes that contain parameters (e.g., /users/:id) Delegates to RouteMatcher for the actual matching logic

Parameters:

  • method (String)

    HTTP method

  • path (String)

    Request path

Returns:

  • (Array(RouteEntry, Hash))

    Matched route entry and extracted parameters



262
263
264
# File 'lib/takagi/router.rb', line 262

def match_dynamic_route(method, path)
  @route_matcher.match(@routes, method, path)
end

#normalize_metadata(method, path, metadata) ⇒ Hash

Normalizes route metadata with sensible defaults for CoRE Link Format

Parameters:

  • method (String)

    HTTP-like method (GET, POST, OBSERVE, etc.)

  • path (String)

    Route path

  • metadata (Hash, nil)

    User-provided metadata

Returns:

  • (Hash)

    Normalized metadata with defaults applied



282
283
284
285
286
287
288
289
# File 'lib/takagi/router.rb', line 282

def (method, path, )
  normalized = ( || {}).transform_keys(&:to_sym)
  normalized[:rt] ||= default_resource_type(method)
  normalized[:if] ||= default_interface(method)
  normalized[:ct] = DEFAULT_CONTENT_FORMAT unless normalized.key?(:ct)
  normalized[:title] ||= "#{method} #{path}"
  normalized
end

#observable(path, metadata: {}, &block) {|arg0| ... } ⇒ Object

Registers a OBSERVE route

Parameters:

  • path (String)

    The URL path

  • block (Proc)

    The handler function

  • metadata: (::Hash[untyped, untyped]) (defaults to: {})

Yields:

Yield Parameters:

  • arg0

Yield Returns:

  • (Object)

Returns:

  • (Object)


188
189
190
191
# File 'lib/takagi/router.rb', line 188

def observable(path, metadata: {}, &block)
   = { obs: true, rt: 'core#observable', if: 'takagi.observe' }
  add_route('OBSERVE', path, metadata: .merge(), &block)
end

#wrap_block(entry) ⇒ nil, untyped

Parameters:

  • entry (Object)

Returns:

  • (nil, untyped)


247
248
249
250
251
252
253
254
255
# File 'lib/takagi/router.rb', line 247

def wrap_block(entry)
  block = entry&.block
  return nil unless block

  lambda do |req, params = {}|
    context = RouteContext.new(entry, req, params, entry.receiver)
    context.run(block)
  end
end