Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* api gemspec * api middleware * api boolean_gate * api middleware * boolean gate spec
- Loading branch information
1 parent
e98f9cc
commit 4992aa9
Showing
13 changed files
with
333 additions
and
21 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
# -*- encoding: utf-8 -*- | ||
require File.expand_path('../lib/flipper/version', __FILE__) | ||
|
||
flipper_api_files = lambda { |file| | ||
file =~ /(flipper)[\/-]api/ | ||
} | ||
|
||
Gem::Specification.new do |gem| | ||
gem.authors = ["John Nunemaker"] | ||
gem.email = ["nunemaker@gmail.com"] | ||
gem.summary = "API for the Flipper gem" | ||
gem.description = "Rack middleware that provides an API for the flipper gem." | ||
gem.license = "MIT" | ||
gem.homepage = "https://github.com/jnunemaker/flipper" | ||
gem.files = `git ls-files`.split("\n").select(&flipper_api_files) + ["lib/flipper/version.rb"] | ||
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n").select(&flipper_api_files) | ||
gem.name = "flipper-api" | ||
gem.require_paths = ["lib"] | ||
gem.version = Flipper::VERSION | ||
|
||
gem.add_dependency 'rack', '>= 1.4', '< 3' | ||
gem.add_dependency 'flipper', "~> #{Flipper::VERSION}" | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
require 'flipper/api' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
module Flipper | ||
# Internal: Used to detect the action that should be used in the middleware. | ||
class ActionCollection | ||
def initialize | ||
@action_classes = [] | ||
end | ||
|
||
def add(action_class) | ||
@action_classes << action_class | ||
end | ||
|
||
def action_for_request(request) | ||
@action_classes.detect { |action_class| | ||
request.path_info =~ action_class.regex | ||
} | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
require 'rack' | ||
require 'flipper' | ||
require 'flipper/api/middleware' | ||
|
||
module Flipper | ||
module Api | ||
def self.app(flipper) | ||
app = App.new(200,{'Content-Type' => 'application/json'}, ['']) | ||
builder = Rack::Builder.new | ||
yield builder if block_given? | ||
builder.use Flipper::Api::Middleware, flipper | ||
builder.run app | ||
builder | ||
end | ||
|
||
class App | ||
# Public: HTTP response code | ||
# Use this method to update status code before responding | ||
attr_writer :status | ||
|
||
def initialize(status, headers, body) | ||
@status = status | ||
@headers = headers | ||
@body = body | ||
end | ||
|
||
# Public : Rack expects object that responds to call | ||
# env - environment hash | ||
def call(env) | ||
response | ||
end | ||
|
||
private | ||
|
||
def response | ||
[@status, @headers, @body] | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
module Flipper | ||
module Api | ||
class Action | ||
extend Forwardable | ||
|
||
# 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) | ||
@regex = regex | ||
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 | ||
|
||
# Internal: The regex that matches which routes this action will work for. | ||
def self.regex | ||
@regex || raise("#{name}.route is not set") | ||
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, @request = flipper, request | ||
@code = 200 | ||
@headers = {"Content-Type" => "application/json"} | ||
end | ||
|
||
# Public: Runs the request method for the provided request. | ||
# | ||
# Returns whatever the request method returns in the action. | ||
def run | ||
if 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 | ||
|
||
def json_response(object) | ||
header 'Content-Type', 'application/json' | ||
status(200) | ||
body = JSON.dump(object) | ||
halt [@code, @headers, [body]] | ||
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: Returns the request method converted to an action method. | ||
def request_method_name | ||
@request_method_name ||= @request.request_method.downcase | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
module Flipper | ||
module Api | ||
# All flipper api errors inherit from this. | ||
Error = Class.new(StandardError) | ||
|
||
# Raised when a request method (get, post, etc.) is called for an action | ||
# that does not know how to handle it. | ||
RequestMethodNotSupported = Class.new(Error) | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
require 'rack' | ||
require 'flipper/action_collection' | ||
|
||
# Require all V1 actions automatically. | ||
Pathname(__FILE__).dirname.join('v1/actions').each_child(false) do |name| | ||
require "flipper/api/v1/actions/#{name}" | ||
end | ||
|
||
module Flipper | ||
module Api | ||
class Middleware | ||
# Public: Initializes an instance of the API middleware. | ||
# | ||
# app - The app this middleware is included in. | ||
# flipper_or_block - The Flipper::DSL instance or a block that yields a | ||
# Flipper::DSL instance to use for all operations. | ||
# | ||
# Examples | ||
# | ||
# flipper = Flipper.new(...) | ||
# | ||
# # using with a normal flipper instance | ||
# use Flipper::Api::Middleware, flipper | ||
# | ||
# # using with a block that yields a flipper instance | ||
# use Flipper::Api::Middleware, lambda { Flipper.new(...) } | ||
# | ||
def initialize(app, flipper_or_block) | ||
@app = app | ||
|
||
if flipper_or_block.respond_to?(:call) | ||
@flipper_block = flipper_or_block | ||
else | ||
@flipper = flipper_or_block | ||
end | ||
|
||
@action_collection = ActionCollection.new | ||
@action_collection.add Api::V1::Actions::BooleanGate | ||
end | ||
|
||
def flipper | ||
@flipper ||= @flipper_block.call | ||
end | ||
|
||
def call(env) | ||
dup.call!(env) | ||
end | ||
|
||
def call!(env) | ||
request = Rack::Request.new(env) | ||
action_class = @action_collection.action_for_request(request) | ||
if action_class.nil? | ||
@app.status = 404 | ||
@app.call(env) | ||
else | ||
action_class.run(flipper, request) | ||
end | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
require 'flipper/api/action' | ||
|
||
module Flipper | ||
module Api | ||
module V1 | ||
module Actions | ||
class BooleanGate < Api::Action | ||
route %r{api/v1/features/[^/]*/(enable|disable)/?\Z} | ||
|
||
def put | ||
feature_name = Rack::Utils.unescape(route_parts[-2]) | ||
feature = flipper[feature_name.to_sym] | ||
action = Rack::Utils.unescape(route_parts.last) | ||
feature.send(action) | ||
json_response({feature: feature}) | ||
end | ||
|
||
private | ||
|
||
def route_parts | ||
request.path.split("/") | ||
end | ||
end | ||
end | ||
end | ||
end | ||
end |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
require 'helper' | ||
|
||
RSpec.describe Flipper::Api::V1::Actions::BooleanGate do | ||
let(:app) { build_api(flipper) } | ||
|
||
describe 'enable' do | ||
before do | ||
flipper[:my_feature].disable | ||
put '/api/v1/features/my_feature/enable' | ||
end | ||
|
||
it 'enables feature' do | ||
expect(last_response.status).to eq(200) | ||
expect(flipper[:my_feature].on?).to be_truthy | ||
end | ||
end | ||
|
||
describe 'disable' do | ||
before do | ||
flipper[:my_feature].enable | ||
put '/api/v1/features/my_feature/disable' | ||
end | ||
|
||
it 'disables feature' do | ||
expect(last_response.status).to eq(200) | ||
expect(flipper[:my_feature].off?).to be_truthy | ||
end | ||
end | ||
|
||
describe 'invalid paremeter' do | ||
before do | ||
put '/api/v1/features/my_feature/invalid_param' | ||
end | ||
|
||
it 'responds with 404 when not sent enable or disable parameter' do | ||
expect(last_response.status).to eq(404) | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters