1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165
|
require 'forwardable'
require 'flipper/api/error'
require 'flipper/api/error_response'
require 'json'
module Flipper
module Api
class Action
module FeatureNameFromRoute
def feature_name
@feature_name ||= begin
match = request.path_info.match(self.class.route_regex)
match ? Rack::Utils.unescape(match[:feature_name]) : nil
end
end
private :feature_name
end
extend Forwardable
VALID_REQUEST_METHOD_NAMES = Set.new([
'get'.freeze,
'post'.freeze,
'put'.freeze,
'delete'.freeze,
]).freeze
# Public: Call this in subclasses so the action knows its route.
#
# regex - The Regexp that this action should run for.
#
# Returns nothing.
def self.route(regex)
@route_regex = regex
end
# Internal: Does this action's route match the path.
def self.route_match?(path)
path.match(route_regex)
end
# Internal: The regex that matches which routes this action will work for.
def self.route_regex
@route_regex || raise("#{name}.route is not set")
end
# Internal: Initializes and runs an action for a given request.
#
# flipper - The Flipper::DSL instance.
# request - The Rack::Request that was sent.
#
# Returns result of Action#run.
def self.run(flipper, request)
new(flipper, request).run
end
# Public: The instance of the Flipper::DSL the middleware was
# initialized with.
attr_reader :flipper
# Public: The Rack::Request to provide a response for.
attr_reader :request
# Public: The params for the request.
def_delegator :@request, :params
def initialize(flipper, request)
@flipper = flipper
@request = request
@code = 200
@headers = { 'Content-Type' => Api::CONTENT_TYPE }
end
# Public: Runs the request method for the provided request.
#
# Returns whatever the request method returns in the action.
def run
if valid_request_method? && respond_to?(request_method_name)
catch(:halt) { send(request_method_name) }
else
raise Api::RequestMethodNotSupported,
"#{self.class} does not support request method #{request_method_name.inspect}"
end
end
# Public: Runs another action from within the request method of a
# different action.
#
# action_class - The class of the other action to run.
#
# Examples
#
# run_other_action Home
# # => result of running Home action
#
# Returns result of other action.
def run_other_action(action_class)
action_class.new(flipper, request).run
end
# Public: Call this with a response to immediately stop the current action
# and respond however you want.
#
# response - The response you would like to return.
def halt(response)
throw :halt, response
end
# Public: Call this with a json serializable object (i.e. Hash)
# to serialize object and respond to request
#
# object - json serializable object
# status - http status code
def json_response(object, status = 200)
header 'Content-Type', Api::CONTENT_TYPE
status(status)
body = JSON.dump(object)
halt [@code, @headers, [body]]
end
# Public: Call this with an ErrorResponse::ERRORS key to respond
# with the serialized error object as response body
#
# error_key - key to lookup error object
def json_error_response(error_key)
error = ErrorResponse::ERRORS.fetch(error_key.to_sym)
json_response(error.as_json, error.http_status)
end
# Public: Set the status code for the response.
#
# code - The Integer code you would like the response to return.
def status(code)
@code = code.to_i
end
# Public: Set a header.
#
# name - The String name of the header.
# value - The value of the header.
def header(name, value)
@headers[name] = value
end
private
# Private: Returns the request method converted to an action method.
def request_method_name
@request_method_name ||= @request.request_method.downcase
end
# Private: split request path by "/"
# Example: "features/feature_name" => ['features', 'feature_name']
def path_parts
@request.path.split('/')
end
def valid_request_method?
VALID_REQUEST_METHOD_NAMES.include?(request_method_name)
end
end
end
end
|