Class: Otto::RouteDefinition
- Inherits:
-
Object
- Object
- Otto::RouteDefinition
- Defined in:
- lib/otto/route_definition.rb
Overview
Immutable data class representing a complete route definition This encapsulates all aspects of a route: path, target, and options
Constant Summary collapse
- SECURITY_GATING_OPTIONS =
Options that gate access to a route. A malformed token for one of these (e.g.
csrfinstead ofcsrf=exempt, or a bareauth) must not fall back to the default behavior silently — the route would serve without its intended protection (issue #191). %w[auth role csrf].freeze
Instance Attribute Summary collapse
-
#definition ⇒ String
readonly
The original definition string.
-
#keys ⇒ Array<String>
readonly
The parameter keys extracted from the path.
-
#kind ⇒ Symbol
readonly
The invocation kind (:class, :instance, or :logic).
-
#klass_name ⇒ String
readonly
The class name portion.
-
#method_name ⇒ String
readonly
The method name portion.
-
#options ⇒ Hash
readonly
The route options (auth, response, csrf, etc.).
-
#path ⇒ String
readonly
The URL path pattern.
-
#pattern ⇒ Regexp
readonly
The compiled path pattern for matching.
-
#target ⇒ String
readonly
The target class and method (e.g., “TestApp.index”).
-
#verb ⇒ String
readonly
The HTTP verb (GET, POST, etc.).
Class Method Summary collapse
-
.parse_option_token(part, context) ⇒ Array(Symbol, String)?
Parse a single whitespace-delimited
key=valueoption token, applying the security-gating fail-fast rule shared by normal routes and the MCP RouteParser (issue #191 and its MCP/TOOL follow-up).
Instance Method Summary collapse
-
#auth_requirement ⇒ String?
Get authentication requirement (backward compatibility - returns first requirement).
-
#auth_requirements ⇒ Array<String>
Get all authentication requirements as an array Supports multiple strategies: auth=session,apikey,oauth.
-
#csrf_exempt? ⇒ Boolean
Check if CSRF is exempt for this route.
-
#has_option?(key) ⇒ Boolean
Check if route has specific option.
-
#initialize(verb, path, definition, pattern: nil, keys: nil) ⇒ RouteDefinition
constructor
A new instance of RouteDefinition.
-
#inspect ⇒ String
Detailed inspection.
-
#logic_route? ⇒ Boolean
Check if this is a Logic class route (no . or # in target).
-
#option(key, default = nil) ⇒ Object
Get option value with optional default.
-
#response_type ⇒ String
Get response type.
-
#role_requirement ⇒ String?
Get role requirement for route-level authorization Supports single role or comma-separated roles (OR logic): role=admin,editor.
-
#role_requirements ⇒ Array<String>
Get all role requirements as an array Supports multiple roles with OR logic: role=admin,editor.
-
#to_h ⇒ Hash
Convert to hash representation.
-
#to_s ⇒ String
String representation for debugging.
-
#with_options(new_options) ⇒ RouteDefinition
Create a new RouteDefinition with modified options.
Constructor Details
#initialize(verb, path, definition, pattern: nil, keys: nil) ⇒ RouteDefinition
Returns a new instance of RouteDefinition.
47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
# File 'lib/otto/route_definition.rb', line 47 def initialize(verb, path, definition, pattern: nil, keys: nil) @verb = verb.to_s.upcase.to_sym @path = path @definition = definition @pattern = pattern @keys = keys || [] # Parse the definition into target and options parsed = parse_definition(definition) @target = parsed[:target] @options = parsed[:options].freeze # Parse the target into class, method, and kind target_parsed = parse_target(@target) @klass_name = target_parsed[:klass_name] @method_name = target_parsed[:method_name] @kind = target_parsed[:kind] # Freeze for immutability freeze end |
Instance Attribute Details
#definition ⇒ String (readonly)
Returns The original definition string.
24 25 26 |
# File 'lib/otto/route_definition.rb', line 24 def definition @definition end |
#keys ⇒ Array<String> (readonly)
Returns The parameter keys extracted from the path.
45 46 47 |
# File 'lib/otto/route_definition.rb', line 45 def keys @keys end |
#kind ⇒ Symbol (readonly)
Returns The invocation kind (:class, :instance, or :logic).
36 37 38 |
# File 'lib/otto/route_definition.rb', line 36 def kind @kind end |
#klass_name ⇒ String (readonly)
Returns The class name portion.
30 31 32 |
# File 'lib/otto/route_definition.rb', line 30 def klass_name @klass_name end |
#method_name ⇒ String (readonly)
Returns The method name portion.
33 34 35 |
# File 'lib/otto/route_definition.rb', line 33 def method_name @method_name end |
#options ⇒ Hash (readonly)
Returns The route options (auth, response, csrf, etc.).
39 40 41 |
# File 'lib/otto/route_definition.rb', line 39 def @options end |
#path ⇒ String (readonly)
Returns The URL path pattern.
21 22 23 |
# File 'lib/otto/route_definition.rb', line 21 def path @path end |
#pattern ⇒ Regexp (readonly)
Returns The compiled path pattern for matching.
42 43 44 |
# File 'lib/otto/route_definition.rb', line 42 def pattern @pattern end |
#target ⇒ String (readonly)
Returns The target class and method (e.g., “TestApp.index”).
27 28 29 |
# File 'lib/otto/route_definition.rb', line 27 def target @target end |
#verb ⇒ String (readonly)
Returns The HTTP verb (GET, POST, etc.).
18 19 20 |
# File 'lib/otto/route_definition.rb', line 18 def verb @verb end |
Class Method Details
.parse_option_token(part, context) ⇒ Array(Symbol, String)?
Parse a single whitespace-delimited key=value option token, applying
the security-gating fail-fast rule shared by normal routes and the MCP
RouteParser (issue #191 and its MCP/TOOL follow-up).
133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 |
# File 'lib/otto/route_definition.rb', line 133 def self.parse_option_token(part, context) key, value = part.split('=', 2) normalized_key = key&.downcase if SECURITY_GATING_OPTIONS.include?(normalized_key) if key != normalized_key || value.nil? || value.empty? raise Otto::RouteDefinitionError, "Malformed security option #{part.inspect} in #{context}: " \ "expected #{normalized_key}=value" end [key.to_sym, value] elsif key && !key.empty? && value [key.to_sym, value] end end |
Instance Method Details
#auth_requirement ⇒ String?
Get authentication requirement (backward compatibility - returns first requirement)
86 87 88 |
# File 'lib/otto/route_definition.rb', line 86 def auth_requirement auth_requirements.first end |
#auth_requirements ⇒ Array<String>
Get all authentication requirements as an array Supports multiple strategies: auth=session,apikey,oauth
93 94 95 96 97 98 |
# File 'lib/otto/route_definition.rb', line 93 def auth_requirements auth = option(:auth) return [] unless auth auth.split(',').map(&:strip).reject(&:empty?) end |
#csrf_exempt? ⇒ Boolean
Check if CSRF is exempt for this route
151 152 153 |
# File 'lib/otto/route_definition.rb', line 151 def csrf_exempt? option(:csrf) == 'exempt' end |
#has_option?(key) ⇒ Boolean
Check if route has specific option
72 73 74 |
# File 'lib/otto/route_definition.rb', line 72 def has_option?(key) @options.key?(key.to_sym) end |
#inspect ⇒ String
Detailed inspection
196 197 198 |
# File 'lib/otto/route_definition.rb', line 196 def inspect "#<Otto::RouteDefinition #{self} options=#{@options.inspect}>" end |
#logic_route? ⇒ Boolean
Check if this is a Logic class route (no . or # in target)
157 158 159 |
# File 'lib/otto/route_definition.rb', line 157 def logic_route? kind == :logic end |
#option(key, default = nil) ⇒ Object
Get option value with optional default
80 81 82 |
# File 'lib/otto/route_definition.rb', line 80 def option(key, default = nil) @options.fetch(key.to_sym, default) end |
#response_type ⇒ String
Get response type
119 120 121 |
# File 'lib/otto/route_definition.rb', line 119 def response_type option(:response, 'default') end |
#role_requirement ⇒ String?
Get role requirement for route-level authorization Supports single role or comma-separated roles (OR logic): role=admin,editor
103 104 105 |
# File 'lib/otto/route_definition.rb', line 103 def role_requirement option(:role) end |
#role_requirements ⇒ Array<String>
Get all role requirements as an array Supports multiple roles with OR logic: role=admin,editor
110 111 112 113 114 115 |
# File 'lib/otto/route_definition.rb', line 110 def role_requirements role = option(:role) return [] unless role role.split(',').map(&:strip).reject(&:empty?) end |
#to_h ⇒ Hash
Convert to hash representation
173 174 175 176 177 178 179 180 181 182 183 184 185 186 |
# File 'lib/otto/route_definition.rb', line 173 def to_h { verb: @verb, path: @path, definition: @definition, target: @target, klass_name: @klass_name, method_name: @method_name, kind: @kind, options: @options, pattern: @pattern, keys: @keys, } end |
#to_s ⇒ String
String representation for debugging
190 191 192 |
# File 'lib/otto/route_definition.rb', line 190 def to_s "#{@verb} #{@path} #{@definition}" end |
#with_options(new_options) ⇒ RouteDefinition
Create a new RouteDefinition with modified options
164 165 166 167 168 169 |
# File 'lib/otto/route_definition.rb', line 164 def () = @options.merge() new_definition = [@target, *.map { |k, v| "#{k}=#{v}" }].join(' ') self.class.new(@verb, @path, new_definition, pattern: @pattern, keys: @keys) end |