module Roda::RodaPlugins::Base::RequestMethods

def _match_array(matcher)

if no elements in the array match.
first match without evaluating future matches. Returns false
Match any of the elements in the given array. Return at the
def _match_array(matcher)
  matcher.any? do |m|
    if matched = match(m)
      if m.is_a?(String)
        @captures.push(m)
      end
    end
    matched
  end
end

def _match_class(klass)

String :: Match any non-empty segment, yielding result to block as a string
Integer :: Match an integer segment, yielding result to block as an integer
are supported by default:
Match the given class. Currently, the following classes
def _match_class(klass)
  meth = :"_match_class_#{klass}"
  if respond_to?(meth, true)
    # Allow calling private methods, as match methods are generally private
    send(meth)
  else
    unsupported_matcher(klass)
  end
end

def _match_class_Integer

integer.
Match integer segment of up to 100 decimal characters, and yield resulting value as an
def _match_class_Integer
  consume(/\A\/(\d{1,100})(?=\/|\z)/, :_convert_class_Integer)
end

def _match_hash(hash)

Match the given hash if all hash matchers match.
def _match_hash(hash)
  # Allow calling private methods, as match methods are generally private
  hash.all?{|k,v| send("match_#{k}", v)}
end

def _match_regexp(re)

Match the given regexp exactly if it matches a full segment.
Match only if all of the arguments in the given array match.
def _match_regexp(re)
  consume(self.class.cached_matcher(re){re})
end

def _match_string(str)

request path is a slash (indicating a new segment).
request path ends with the string or if the next character in the
Match the given string to the request path. Matches only if the
def _match_string(str)
  rp = @remaining_path
  length = str.length
  match = case rp.rindex(str, length)
  when nil
    # segment does not match, most common case
    return
  when 1
    # segment matches, check first character is /
    rp.getbyte(0) == 47
  else # must be 0
    # segment matches at first character, only a match if
    # empty string given and first character is /
    length == 0 && rp.getbyte(0) == 47
  end
  if match 
    length += 1
    case rp.getbyte(length)
    when 47
      # next character is /, update remaining path to rest of string
      @remaining_path = rp[length, 100000000]
    when nil
      # end of string, so remaining path is empty
      @remaining_path = ""
    # else
      # Any other value means this was partial segment match,
      # so we return nil in that case without updating the
      # remaining_path.  No need for explicit else clause.
    end
  end
end

def _match_symbol(sym=nil)

Match the given symbol if any segment matches.
def _match_symbol(sym=nil)
  rp = @remaining_path
  if rp.getbyte(0) == 47
    if last = rp.index('/', 1)
      @captures << rp[1, last-1]
      @remaining_path = rp[last, rp.length]
    elsif (len = rp.length) > 1
      @captures << rp[1, len]
      @remaining_path = ""
    end
  end
end

def _remaining_path(env)

The base remaining path to use.
def _remaining_path(env)
  env["PATH_INFO"]
end

def _verb(args, &block)

args is not empty, or a regular match if it is empty.
Backbone of the verb method support, using a terminal match if
def _verb(args, &block)
  if args.empty?
    always(&block)
  else
    args << TERM
    if_match(args, &block)
  end
end

def always

Yield to the match block and return rack response after the block returns.
def always
  block_result(yield)
  throw :halt, response.finish
end

def block_result(result)

and the response is empty, use the string as the response body.
Handle match block return values. By default, if a string is given
def block_result(result)
  res = response
  if res.empty? && (body = block_result_body(result))
    res.write(body)
  end
end

def block_result_body(result)

returned otherwise.
a body. By default, a String is returned directly, and nil is
The body to use for the response if the response does not already have
def block_result_body(result)
  case result
  when String
    result
  when nil, false
    # nothing
  else
    unsupported_block_result(result)
  end
end

def consume(pattern, meth=nil)

path from PATH_INFO, and updates captures with any regex captures.
SCRIPT_NAME to include the matched path, removes the matched
match, returns false without changes. Otherwise, modifies
Attempts to match the pattern to the current path. If there is no
def consume(pattern, meth=nil)
  if matchdata = pattern.match(@remaining_path)
    captures = matchdata.captures
    if meth
      return unless captures = scope.send(meth, *captures)
    # :nocov:
    elsif defined?(yield)
      # RODA4: Remove
      return unless captures = yield(*captures)
    # :nocov:
    end
    @remaining_path = matchdata.post_match
    if captures.is_a?(Array)
      @captures.concat(captures)
    else
      @captures << captures
    end
  end
end

def default_redirect_path

it is easy to create an infinite redirect.
If the current request is a GET request, raise an error, as otherwise

will work fine.
a POST request will redirect to a GET request at the same location
trigger a GET request. This is to make the common case where
For non-GET requests, redirects to the current path, which will
The default path to use for redirects when a path is not given.
def default_redirect_path
  raise RodaError, "must provide path argument to redirect for get requests" if is_get?
  path
end

def default_redirect_status

302 by default.
The default status to use for redirects if a status is not provided,
def default_redirect_status
  302
end

def empty_path?

Whether the current path is considered empty.
def empty_path?
  @remaining_path.empty?
end

def get(*args, &block)

given fully consume the path.
requests, otherwise, matches only GET requests where the arguments
Match GET requests. If no arguments are provided, matches all GET
def get(*args, &block)
  _verb(args, &block) if is_get?
end

def halt(res=response.finish)

r.halt
response.write 'Hello World!'
response['Content-Type'] = 'text/html'
response.status = 200

r.halt [200, {'Content-Type'=>'text/html'}, ['Hello World!']]

is given, uses the current response.
rack response array of status, headers, and body. If no argument
Immediately stop execution of the route block and return the given
def halt(res=response.finish)
  throw :halt, res
end

def http_version

def http_version
  # Prefer SERVER_PROTOCOL as it is required in Rack 3.
  # Still fall back to HTTP_VERSION if SERVER_PROTOCOL
  # is not set, in case the server in use is not Rack 3
  # compliant.
  @env['SERVER_PROTOCOL'] || @env['HTTP_VERSION']
end

def http_version

What HTTP version the request was submitted with.
def http_version
  # Prefer HTTP_VERSION as it is backwards compatible
  # with previous Roda versions. Fallback to
  # SERVER_PROTOCOL for servers that do not set
  # HTTP_VERSION.
  @env['HTTP_VERSION'] || @env['SERVER_PROTOCOL']
end

def if_match(args)

the match arguments doesn't match, does nothing.
returns the rack response when the block returns. If any of
If all of the arguments match, yields to the match block and
def if_match(args)
  path = @remaining_path
  # For every block, we make sure to reset captures so that
  # nesting matchers won't mess with each other's captures.
  captures = @captures.clear
  if match_all(args)
    block_result(yield(*captures))
    throw :halt, response.finish
  else
    @remaining_path = path
    false
  end
end

def initialize(scope, env)

Store the roda instance and environment.
def initialize(scope, env)
  @scope = scope
  @captures = []
  @remaining_path = _remaining_path(env)
  @env = env
end

def inspect

# => '#'
r.inspect

request method and full path.
Show information about current request, including request class,
def inspect
  "#<#{self.class.inspect} #{@env["REQUEST_METHOD"]} #{path}>"
end

def is(*args, &block)

end
end
# matches as path is empty after matching
r.is "" do
r.on 'foo/bar' do

end
# matches as path is empty after matching
r.is 'foo/bar/' do

end
# does not match, as path isn't fully matched (/ remaining)
r.is 'foo/bar' do

# => "/foo/bar/"
r.remaining_path

is empty, not if it still contains a trailing slash:
Note that this matches only if the path after matching the arguments

end
end
# matches as path is already empty
r.is do
r.on 'foo/bar' do

If no arguments are given, matches if the path is already fully matched.

end
# matches as path is empty after matching
r.is 'foo/bar' do

end
# does not match, as path isn't fully matched (/bar remaining)
r.is 'foo' do

# => "/foo/bar"
r.remaining_path

returned.
executed, and when the match block returns, the rack response is
have fully matched the path. If it matches, the match block is
Does a terminal match on the current path, matching only if the arguments
def is(*args, &block)
  if args.empty?
    if empty_path?
      always(&block)
    end
  else
    args << TERM
    if_match(args, &block)
  end
end

def is_get?

overridden without changing rack's behavior.
Similar to the default Rack::Request get? method, but can be
Optimized method for whether this request is a +GET+ request.
def is_get?
  @env["REQUEST_METHOD"] == 'GET'
end

def match(matcher)

common ruby types.
Attempt to match the argument to the given request, handling
def match(matcher)
  case matcher
  when String
    _match_string(matcher)
  when Class
    _match_class(matcher)
  when TERM
    empty_path?
  when Regexp
    _match_regexp(matcher)
  when true
    matcher
  when Array
    _match_array(matcher)
  when Hash
    _match_hash(matcher)
  when Symbol
    _match_symbol(matcher)
  when false, nil
    matcher
  when Proc
    matcher.call
  else
    unsupported_matcher(matcher)
  end
end

def match_all(args)

Match only if all of the arguments in the given array match.
def match_all(args)
  args.all?{|arg| match(arg)}
end

def match_method(type)

to match on multiple methods.
Match by request method. This can be an array if you want
def match_method(type)
  if type.is_a?(Array)
    type.any?{|t| match_method(t)}
  else
    type.to_s.upcase == @env["REQUEST_METHOD"]
  end
end

def matched_path

The already matched part of the path, including the original SCRIPT_NAME.
def matched_path
  e = @env
  e["SCRIPT_NAME"] + e["PATH_INFO"].chomp(@remaining_path)
end

def on(*args, &block)

end
end
# handle /foo/bar request
r.is 'bar' do
r.on 'foo' do

final handling for the request:
inside the match block that fully matches the path and does the
returned. However, in general you will call another routing method
executed, and when the match block returns, the rack response is
Like other routing methods, If it matches, the match block is

end
# does not match
r.on 'bar' do

end
# matches, path is /bar after matching
r.on 'foo' do

# => "/foo/bar"
r.remaining_path

not for final handling of the request.
path, this is usually used to setup branches of the routing tree,
have matched the path. Because this doesn't fully match the
Does a match on the path, matching only if the arguments
def on(*args, &block)
  if args.empty?
    always(&block)
  else
    if_match(args, &block)
  end
end

def path

# => '/foo/bar'
r.path
r.env['PATH_INFO'] = '/bar'
r.env['SCRIPT_NAME'] = '/foo'

This an an optimized version of Rack::Request#path.
def path
  e = @env
  "#{e["SCRIPT_NAME"]}#{e["PATH_INFO"]}"
end

def post(*args, &block)

given fully consume the path.
requests, otherwise, matches only POST requests where the arguments
Match POST requests. If no arguments are provided, matches all POST
def post(*args, &block)
  _verb(args, &block) if post?
end

def redirect(path=default_redirect_path, status=default_redirect_status)

end
end
r.redirect
# change state
r.post do

end
# show state
r.get do
r.is "foo" do

after changing:
returns the current state, and you want to show the current state
it easy to use where a +POST+ request to a URL changes state, +GET+
path if the request is not a +GET+ request. This is designed to make
If you do not provide a path, by default it will redirect to the same

response.status = 404 # not reached
r.redirect '/page2' # uses 302 status code
r.redirect '/page1', 301 if r['param'] == 'value1'

the processing of the request:
Immediately redirect to the path using the status code. This ends
def redirect(path=default_redirect_path, status=default_redirect_status)
  response.redirect(path, status)
  throw :halt, response.finish
end

def response

response['Header-Name'] = 'Header value'
response.status = 200

is to override the response status and headers:
instance methods for the response, but in general the most common usage
The response related to the current request. See ResponseMethods for
def response
  @scope.response
end

def roda_class

Return the Roda class related to this request.
def roda_class
  self.class.roda_class
end

def root(&block)

path is empty.
Use r.get true to handle +GET+ requests where the current

end
end
# does not match
r.root do
r.on 'foo' do

# => ['GET', '/foo']
[r.request_method, r.remaining_path]

Nor does it match empty paths:

is +/+.
Use r.post "" for +POST+ requests where the current path

end
# does not match
r.root do

# => ['POST', '/']
[r.request_method, r.remaining_path]

Note that this does not match non-+GET+ requests:

end
end
# matches
r.root do
r.on 'foo' do

# => ['GET', '/foo/']
[r.request_method, r.remaining_path]

This is usuable inside other match blocks:

end
# matches
r.root do

# => ['GET', '/']
[r.request_method, r.remaining_path]

the match block returns, the rack response is returned.
path is +/+. If it matches, the match block is executed, and when
Match method that only matches +GET+ requests where the current
def root(&block)
  if @remaining_path == "/" && is_get?
    always(&block)
  end
end

def run(app)

a URL mapper.
before dispatching to another rack app, so the app still works as
This updates SCRIPT_NAME/PATH_INFO based on the current remaining_path

response.status = 404 # not reached
r.run(proc{[404, {}, []]})
r.run(proc{[403, {}, []]}) unless r['letmein'] == '1'

the processing of the request:
from the rack app as the response for this request. This ends
Call the given rack app with the environment and return the response
def run(app)
  e = @env
  path = real_remaining_path
  sn = "SCRIPT_NAME"
  pi = "PATH_INFO"
  script_name = e[sn]
  path_info = e[pi]
  begin
    e[sn] += path_info.chomp(path)
    e[pi] = path
    throw :halt, app.call(e)
  ensure
    e[sn] = script_name
    e[pi] = path_info
  end
end

def session

a session handler has not been loaded.
The session for the current request. Raises a RodaError if
def session
  @env['rack.session'] || raise(RodaError, "You're missing a session handler, try using the sessions plugin.")
end

def unsupported_block_result(result)

By default raises an exception.
How to handle block results that are not nil, false, or a String.
def unsupported_block_result(result)
  raise RodaError, "unsupported block result: #{result.inspect}"
end

def unsupported_matcher(matcher)

Handle an unsupported matcher.
def unsupported_matcher(matcher)
  raise RodaError, "unsupported matcher: #{matcher.inspect}"
end