Class: Tina4::Route

Inherits:
Object
  • Object
show all
Defined in:
lib/tina4/router.rb

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(method, path, handler, auth_handler: nil, swagger_meta: {}, middleware: [], template: nil) ⇒ Route

Returns a new instance of Route.



12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# File 'lib/tina4/router.rb', line 12

def initialize(method, path, handler, auth_handler: nil, swagger_meta: {}, middleware: [], template: nil)
  @method = method.to_s.upcase.freeze
  @path = normalize_path(path).freeze
  @handler = handler
  @auth_handler = auth_handler
  @swagger_meta = swagger_meta
  @middleware = middleware.freeze
  @template = template&.freeze
  # Write routes are secure by default — bearer-token auth is enforced
  # on POST/PUT/PATCH/DELETE regardless of attached middleware.
  # Middleware is additive, never an auth bypass. tina4-book#141
  # PY-10-02. Call .no_auth on the route to opt out explicitly.
  @auth_required = %w[POST PUT PATCH DELETE].include?(@method)
  @cached = false
  @param_names = []
  @path_regex = compile_pattern(@path)
  @param_names.freeze
end

Instance Attribute Details

#auth_handlerObject (readonly)

Returns the value of attribute auth_handler.



8
9
10
# File 'lib/tina4/router.rb', line 8

def auth_handler
  @auth_handler
end

#auth_requiredObject

Returns the value of attribute auth_required.



10
11
12
# File 'lib/tina4/router.rb', line 10

def auth_required
  @auth_required
end

#cachedObject

Returns the value of attribute cached.



10
11
12
# File 'lib/tina4/router.rb', line 10

def cached
  @cached
end

#handlerObject (readonly)

Returns the value of attribute handler.



8
9
10
# File 'lib/tina4/router.rb', line 8

def handler
  @handler
end

#methodObject (readonly)

Returns the value of attribute method.



8
9
10
# File 'lib/tina4/router.rb', line 8

def method
  @method
end

#param_namesObject (readonly)

Returns the value of attribute param_names.



8
9
10
# File 'lib/tina4/router.rb', line 8

def param_names
  @param_names
end

#pathObject (readonly)

Returns the value of attribute path.



8
9
10
# File 'lib/tina4/router.rb', line 8

def path
  @path
end

#path_regexObject (readonly)

Returns the value of attribute path_regex.



8
9
10
# File 'lib/tina4/router.rb', line 8

def path_regex
  @path_regex
end

#swagger_metaObject (readonly)

Returns the value of attribute swagger_meta.



8
9
10
# File 'lib/tina4/router.rb', line 8

def swagger_meta
  @swagger_meta
end

#templateObject (readonly)

Returns the value of attribute template.



8
9
10
# File 'lib/tina4/router.rb', line 8

def template
  @template
end

Class Method Details

.function_middleware?(mw) ⇒ Boolean

Detect Express/FastAPI-style function middleware.

A Proc/Lambda/Method whose arity indicates 3+ positional params (req, resp, next_handler). Ruby arity quirk: required-args-only arity is non-negative; if the callable accepts a splat or optionals, arity is negated (-required-1). We treat arity >= 3 OR arity <= -4 as function-style. Anything else (a class with before_/after_ methods, or a 2-arg callable) is treated as class-style and goes through #run_middleware.

Returns:

  • (Boolean)


128
129
130
131
132
133
134
135
# File 'lib/tina4/router.rb', line 128

def self.function_middleware?(mw)
  return false if mw.is_a?(Class) || mw.is_a?(Module)
  return false unless mw.respond_to?(:arity)
  ar = mw.arity
  ar >= 3 || ar <= -4
rescue StandardError
  false
end

Instance Method Details

#cacheObject

Mark this route as cacheable. Returns self for chaining: Router.get(“/path”) { … }.cache



47
48
49
50
# File 'lib/tina4/router.rb', line 47

def cache
  @cached = true
  self
end

#function_middlewareObject

Function-style middleware attached to this route, in declaration order. The route dispatcher folds them into a Russian-doll continuation chain — first declared is the OUTERMOST layer (runs first on the way in, last on the way out). tina4-book#141 PY-10-01 — chapter 10 documented 8+ examples of function middleware for years; before this fix the framework silently ignored them.



115
116
117
# File 'lib/tina4/router.rb', line 115

def function_middleware
  @middleware.select { |mw| Route.function_middleware?(mw) }
end

#match?(request_path, request_method = nil) ⇒ Boolean

Returns params hash if matched, false otherwise

Returns:

  • (Boolean)


71
72
73
74
# File 'lib/tina4/router.rb', line 71

def match?(request_path, request_method = nil)
  return false if request_method && @method != "ANY" && @method != request_method.to_s.upcase
  match_path(request_path)
end

#match_path(request_path) ⇒ Object

Returns params hash if matched, false otherwise



77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/tina4/router.rb', line 77

def match_path(request_path)
  match = @path_regex.match(request_path)
  return false unless match

  if @param_names.empty?
    {}
  else
    params = {}
    @param_names.each_with_index do |param_def, i|
      raw_value = match[i + 1]
      params[param_def[:name]] = cast_param(raw_value, param_def[:type])
    end
    params
  end
end

#middleware(*middleware_classes) ⇒ Object

Dual-mode: getter (no args) returns the middleware array; setter (with args) appends middleware and returns self for chaining. Router.post(“/api”) { … }.middleware(AuthMiddleware)

Middleware is purely additive — registering middleware NEVER flips (POST/PUT/PATCH/DELETE) stays in effect; if a route truly wants to opt out of the built-in bearer check, call .no_auth explicitly. tina4-book#141 PY-10-02 — previously, attaching ANY middleware silently turned off auth_required, which let attackers bypass auth by routing through a logging middleware. Cross-framework parity.



63
64
65
66
67
68
# File 'lib/tina4/router.rb', line 63

def middleware(*middleware_classes)
  return @middleware if middleware_classes.empty?

  @middleware = @middleware.dup + middleware_classes
  self
end

#no_authObject

Opt out of the secure-by-default auth on write routes. Returns self for chaining: Router.post(“/login”) { … }.no_auth



40
41
42
43
# File 'lib/tina4/router.rb', line 40

def no_auth
  @auth_required = false
  self
end

#run_middleware(request, response) ⇒ Object

Run per-route CLASS-based middleware. Function-style middleware (Proc/Lambda taking 3+ args: req, resp, next_handler) is skipped here — it’s dispatched separately via #function_middleware which wraps the route handler in a continuation chain (see rack_app.rb).

Returns true if all class-based middleware passed, false if any returned literal false (halt request).



100
101
102
103
104
105
106
107
# File 'lib/tina4/router.rb', line 100

def run_middleware(request, response)
  @middleware.each do |mw|
    next if Route.function_middleware?(mw)
    result = mw.call(request, response)
    return false if result == false
  end
  true
end

#secureObject

Mark this route as requiring bearer-token authentication. Returns self for chaining: Router.get(“/path”) { … }.secure



33
34
35
36
# File 'lib/tina4/router.rb', line 33

def secure
  @auth_required = true
  self
end