class Blix::Rest::RequestMapper

register routes with this class and then we can match paths to these routes and return an associated block and parameters.

Constants

PATH_SEP
STAR_PLACEHOLDER
WILD_PLACEHOLDER

Attributes

path_root_length[R]

Public Class Methods

add_path(verb, path, opts = {}, &blk) click to toggle source

declare a route

# File lib/blix/rest/request_mapper.rb, line 146
def add_path(verb, path, opts = {}, &blk)
  path = path[1..-1] if path[0, 1] == PATH_SEP
  RequestMapper.locations[verb] << [verb, path, opts, blk]
  @table = nil # force recompile
end
compile() click to toggle source

compile routes into a tree structure for easy lookup

# File lib/blix/rest/request_mapper.rb, line 112
def compile
  @table = Hash.new { |h, k| h[k] = TableNode.new('') }
  locations.each do |verb, routes|
    routes.each do |info|
      verb, path, opts, blk = info
      parts = path.split(PATH_SEP)
      current = @table[verb]
      parts.each_with_index do |section, idx|
        node = TableNode.new(section)
        # check that a wildstar is the last element.
        if (section[0] == STAR_PLACEHOLDER) && (idx < (parts.length - 1))
          raise RequestMapperError, "do not add a path after the * in #{path}"
        end

        # check that wild card match in name
        if current[node.value]
          if (node.value == WILD_PLACEHOLDER) && (node.parameter != current[node.value].parameter)
            raise RequestMapperError, "parameter mismatch in route=#{path}, expected #{current[node.value].parameter} but got #{node.parameter}"
          end

        else
          current[node.value] = node
        end
        current = current[node.value]
      end
      current.blk = blk
      current.opts = opts || {}
      current.extract_format = opts[:extension] if opts.key?(:extension)
    end
  end
  @table
end
dump() click to toggle source
# File lib/blix/rest/request_mapper.rb, line 82
def dump
  table.each do |k, v|
    puts k
    dump_node(v, 1)
  end
end
dump_node(item, indent = 0) click to toggle source
# File lib/blix/rest/request_mapper.rb, line 89
def dump_node(item, indent = 0)
  puts "#{' ' * indent} value=#{item.value.inspect} opts=#{item.opts.inspect} params=#{item.parameter.inspect}"
  item.children.each_value { |c| dump_node(c, indent + 1) }
end
full_path(path) click to toggle source
# File lib/blix/rest/request_mapper.rb, line 69
def full_path(path)
  path = path[1..-1] if path[0, 1] == '/'
  path_root + path
end
locations() click to toggle source
# File lib/blix/rest/request_mapper.rb, line 74
def locations
  @locations ||= Hash.new { |h, k| h[k] = [] }
end
match(verb, path) click to toggle source

match a given path to declared route.

# File lib/blix/rest/request_mapper.rb, line 153
def match(verb, path)
  path = PATH_SEP + path if path[0, 1] != PATH_SEP # ensure a leading slash on path

  path = path[path_root_length..-1] if (path_root_length.to_i > 0) #&& (path[0,path_root_length] == path_root)
  if path
    path = path[1..-1] if path[0, 1] == PATH_SEP # remove the leading slash
  else
    return [nil, {}, nil]
  end

  parameters = StringHash.new

  parts = path.split(PATH_SEP)
  current = table[verb]
  limit   = parts.length - 1

  # handle the root node here
  if path == ''
    if current.blk
      return [current.blk, parameters, current.opts]
    elsif (havewild = current[STAR_PLACEHOLDER])
      parameters[havewild.parameter.to_s] = '/'
      return [havewild.blk, parameters, havewild.opts]
    else
      return [nil, {}, nil]
    end
  end

  parts.each_with_index do |section, idx|
    # first save the last node that we used
    # before updating the current node.

    last = current # table nodes

    # check to see if there is a path which includes a format part
    # only on the last section
    if idx == limit
      if last[section]
        current = last[section]
      else
        format  = File.extname(section)
        base    = File.basename(section, format)
        current = last[base]
        if current
          parameters['format'] = format[1..-1].to_sym # !format.empty?
          section = base
        end
      end
    else
      current = last[section]
    end

    # if current is set here that means that this section matches a fixed
    # part of the route.
    if current

      # if this is the last section then we have to decide here if we
      # have a valid result..
      # .. if we have a block then fine ..
      # .. if there is a wildpath foloowing then fine ..
      # .. otherwise an error !

      if idx == limit # the last section of request
        if current.blk
          return [current.blk, parameters, current.opts]
        elsif (havewild = current[STAR_PLACEHOLDER])
          parameters[havewild.parameter.to_s] = '/'
          return [havewild.blk, parameters, havewild.opts]
        else
          return [nil, {}, nil]
        end
      end
    else

      # this section is  not part of a static path so
      # check if we have a path variable first ..

      current = last[WILD_PLACEHOLDER]
      if current

        # yes this is a path variable section
        if idx == limit

          # the last section of request -
          if current.extract_format
            format  = File.extname(section)
            base    = File.basename(section, format)
            parameters[current.parameter.to_s] = base
            parameters['format'] = format[1..-1].to_sym unless format.empty?
          else
            parameters[current.parameter.to_s] = section
          end

          # check if we have a valid block otherwise see if
          # a wild path follows.

          if current.blk
            return [current.blk, parameters, current.opts]
          elsif (havewild = current[STAR_PLACEHOLDER])
            parameters[havewild.parameter.to_s] = '/'
            return [havewild.blk, parameters, havewild.opts]
          else
            return [nil, {}, nil]
          end

        else
          parameters[current.parameter.to_s] = section
        end
      else
        current = last[STAR_PLACEHOLDER]
        if current
          wildpath = '/' + parts[idx..-1].join('/')
          wildformat = File.extname(wildpath)
          unless wildformat.empty? || !current.extract_format
            wildpath = wildpath[0..-(wildformat.length + 1)]
            parameters['format'] = wildformat[1..-1].to_sym
          end
          parameters[current.parameter.to_s] = wildpath
          return [current.blk, parameters, current.opts]
        else
          return [nil, {}, nil]
        end
      end
    end
  end
  [nil, {}, nil]
end
path_root() click to toggle source
# File lib/blix/rest/request_mapper.rb, line 63
def path_root
  @path_root || '/'
end
process(verb, path) click to toggle source

match a path to a route and call any associated block with the extracted parameters.

# File lib/blix/rest/request_mapper.rb, line 282
def process(verb, path)
  blk, params = match(verb, path)
  blk&.call(params)
end
reset(vals = nil) click to toggle source

used for testing only !!

# File lib/blix/rest/request_mapper.rb, line 95
def reset(vals = nil)
  save = [@table&.dup, @locations&.dup, @path_root&.dup, @path_root_length]
  if vals
    @table     = vals[0]
    @locations = vals[1]
    @path_root = vals[2]
    @path_root_length = vals[3]
  else
    @table     = nil
    @locations = nil
    @path_root = nil
    @path_root_length = 0
  end
  save
end
routes() click to toggle source
# File lib/blix/rest/request_mapper.rb, line 287
def routes
  hash = {}
  locations.values.each do |group|
    group.each do |route|
      verb = route[0]
      options = route[2]
      options_string = String.new
      options_string = ' ' + options.inspect.to_s unless options.empty?
      path = '/' + route[1]
      hash[path] ||= {}
      hash[path][verb] = options_string
    end
  end
  list = hash.to_a
  list.sort! { |a, b| a[0] <=> b[0] }
  str = String.new
  list.each do |route|
    #pairs = route[1]
    (HTTP_VERBS + ['ALL']).each do |verb|
      if route[1].key? verb
        str << verb << "\t" << route[0] << route[1][verb] << "\n"
      end
    end
    str << "\n"
  end
  str
end
set_path_root(root) click to toggle source
# File lib/blix/rest/request_mapper.rb, line 55
def set_path_root(root)
  root = root.to_s
  root = '/' + root if root[0, 1] != '/'
  root += '/' if root[-1, 1] != '/'
  @path_root = root
  @path_root_length = @path_root.length - 1
end
table() click to toggle source
# File lib/blix/rest/request_mapper.rb, line 78
def table
  @table ||= compile
end