flipper-api 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/flipper-api.gemspec +23 -0
- data/lib/flipper/api/action.rb +116 -0
- data/lib/flipper/api/action_collection.rb +19 -0
- data/lib/flipper/api/error.rb +10 -0
- data/lib/flipper/api/middleware.rb +62 -0
- data/lib/flipper/api/v1/actions/boolean_gate.rb +21 -0
- data/lib/flipper/api/v1/actions/feature.rb +44 -0
- data/lib/flipper/api/v1/decorators/feature.rb +28 -0
- data/lib/flipper/api/v1/decorators/gate.rb +35 -0
- data/lib/flipper/api.rb +42 -0
- data/lib/flipper/version.rb +3 -0
- data/lib/flipper-api.rb +1 -0
- data/spec/flipper/api/v1/actions/boolean_gate_spec.rb +39 -0
- data/spec/flipper/api/v1/actions/feature_spec.rb +118 -0
- metadata +94 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 197876b4a2596f886d53e8201fece30624ba53c0
|
4
|
+
data.tar.gz: ff6181dddff5fe04923d61d0e591650b95bc3654
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 9b4827eee0a212add12f181b1d8d83fb4da8b00d243e4d70bbaa3199cc0de4a4e4a71ea904839f5221c3a8b582f1ae1213e22745718c94d0a2de1784f14a3079
|
7
|
+
data.tar.gz: 920d154ee0c38d1394c6e3c0274b0d7cd711e32456d6824488959c2cf95caf4fe929b27490f8228219e889931ec9af622a0ad5e0ad2a5882b65be863667283a6
|
data/flipper-api.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/flipper/version', __FILE__)
|
3
|
+
|
4
|
+
flipper_api_files = lambda { |file|
|
5
|
+
file =~ /(flipper)[\/-]api/
|
6
|
+
}
|
7
|
+
|
8
|
+
Gem::Specification.new do |gem|
|
9
|
+
gem.authors = ["John Nunemaker"]
|
10
|
+
gem.email = ["nunemaker@gmail.com"]
|
11
|
+
gem.summary = "API for the Flipper gem"
|
12
|
+
gem.description = "Rack middleware that provides an API for the flipper gem."
|
13
|
+
gem.license = "MIT"
|
14
|
+
gem.homepage = "https://github.com/jnunemaker/flipper"
|
15
|
+
gem.files = `git ls-files`.split("\n").select(&flipper_api_files) + ["lib/flipper/version.rb"]
|
16
|
+
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n").select(&flipper_api_files)
|
17
|
+
gem.name = "flipper-api"
|
18
|
+
gem.require_paths = ["lib"]
|
19
|
+
gem.version = Flipper::VERSION
|
20
|
+
|
21
|
+
gem.add_dependency 'rack', '>= 1.4', '< 3'
|
22
|
+
gem.add_dependency 'flipper', "~> #{Flipper::VERSION}"
|
23
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
module Flipper
|
2
|
+
module Api
|
3
|
+
class Action
|
4
|
+
extend Forwardable
|
5
|
+
|
6
|
+
# Public: Call this in subclasses so the action knows its route.
|
7
|
+
#
|
8
|
+
# regex - The Regexp that this action should run for.
|
9
|
+
#
|
10
|
+
# Returns nothing.
|
11
|
+
def self.route(regex)
|
12
|
+
@regex = regex
|
13
|
+
end
|
14
|
+
|
15
|
+
# Internal: Initializes and runs an action for a given request.
|
16
|
+
#
|
17
|
+
# flipper - The Flipper::DSL instance.
|
18
|
+
# request - The Rack::Request that was sent.
|
19
|
+
#
|
20
|
+
# Returns result of Action#run.
|
21
|
+
def self.run(flipper, request)
|
22
|
+
new(flipper, request).run
|
23
|
+
end
|
24
|
+
|
25
|
+
# Internal: The regex that matches which routes this action will work for.
|
26
|
+
def self.regex
|
27
|
+
@regex || raise("#{name}.route is not set")
|
28
|
+
end
|
29
|
+
|
30
|
+
# Public: The instance of the Flipper::DSL the middleware was
|
31
|
+
# initialized with.
|
32
|
+
attr_reader :flipper
|
33
|
+
|
34
|
+
# Public: The Rack::Request to provide a response for.
|
35
|
+
attr_reader :request
|
36
|
+
|
37
|
+
# Public: The params for the request.
|
38
|
+
def_delegator :@request, :params
|
39
|
+
|
40
|
+
def initialize(flipper, request)
|
41
|
+
@flipper, @request = flipper, request
|
42
|
+
@code = 200
|
43
|
+
@headers = {"Content-Type" => Api::CONTENT_TYPE }
|
44
|
+
end
|
45
|
+
|
46
|
+
# Public: Runs the request method for the provided request.
|
47
|
+
#
|
48
|
+
# Returns whatever the request method returns in the action.
|
49
|
+
def run
|
50
|
+
if respond_to?(request_method_name)
|
51
|
+
catch(:halt) { send(request_method_name) }
|
52
|
+
else
|
53
|
+
raise Api::RequestMethodNotSupported, "#{self.class} does not support request method #{request_method_name.inspect}"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# Public: Runs another action from within the request method of a
|
58
|
+
# different action.
|
59
|
+
#
|
60
|
+
# action_class - The class of the other action to run.
|
61
|
+
#
|
62
|
+
# Examples
|
63
|
+
#
|
64
|
+
# run_other_action Home
|
65
|
+
# # => result of running Home action
|
66
|
+
#
|
67
|
+
# Returns result of other action.
|
68
|
+
def run_other_action(action_class)
|
69
|
+
action_class.new(flipper, request).run
|
70
|
+
end
|
71
|
+
|
72
|
+
# Public: Call this with a response to immediately stop the current action
|
73
|
+
# and respond however you want.
|
74
|
+
#
|
75
|
+
# response - The response you would like to return.
|
76
|
+
def halt(response)
|
77
|
+
throw :halt, response
|
78
|
+
end
|
79
|
+
|
80
|
+
def json_response(object, status = 200)
|
81
|
+
header 'Content-Type', Api::CONTENT_TYPE
|
82
|
+
status(status)
|
83
|
+
body = JSON.dump(object)
|
84
|
+
halt [@code, @headers, [body]]
|
85
|
+
end
|
86
|
+
|
87
|
+
# Public: Set the status code for the response.
|
88
|
+
#
|
89
|
+
# code - The Integer code you would like the response to return.
|
90
|
+
def status(code)
|
91
|
+
@code = code.to_i
|
92
|
+
end
|
93
|
+
|
94
|
+
# Public: Set a header.
|
95
|
+
#
|
96
|
+
# name - The String name of the header.
|
97
|
+
# value - The value of the header.
|
98
|
+
def header(name, value)
|
99
|
+
@headers[name] = value
|
100
|
+
end
|
101
|
+
|
102
|
+
private
|
103
|
+
|
104
|
+
# Private: Returns the request method converted to an action method.
|
105
|
+
def request_method_name
|
106
|
+
@request_method_name ||= @request.request_method.downcase
|
107
|
+
end
|
108
|
+
|
109
|
+
# Private: split request path by "/"
|
110
|
+
# Example: "api/v1/features/feature_name" => ['api', 'v1', 'features', 'feature_name']
|
111
|
+
def path_parts
|
112
|
+
@request.path.split("/")
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Flipper
|
2
|
+
module Api
|
3
|
+
class ActionCollection
|
4
|
+
def initialize
|
5
|
+
@action_classes = []
|
6
|
+
end
|
7
|
+
|
8
|
+
def add(action_class)
|
9
|
+
@action_classes << action_class
|
10
|
+
end
|
11
|
+
|
12
|
+
def action_for_request(request)
|
13
|
+
@action_classes.detect { |action_class|
|
14
|
+
request.path_info =~ action_class.regex
|
15
|
+
}
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
module Flipper
|
2
|
+
module Api
|
3
|
+
# All flipper api errors inherit from this.
|
4
|
+
Error = Class.new(StandardError)
|
5
|
+
|
6
|
+
# Raised when a request method (get, post, etc.) is called for an action
|
7
|
+
# that does not know how to handle it.
|
8
|
+
RequestMethodNotSupported = Class.new(Error)
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'rack'
|
2
|
+
require 'flipper/api/action_collection'
|
3
|
+
|
4
|
+
# Require all V1 actions automatically.
|
5
|
+
Pathname(__FILE__).dirname.join('v1/actions').each_child(false) do |name|
|
6
|
+
require "flipper/api/v1/actions/#{name}"
|
7
|
+
end
|
8
|
+
|
9
|
+
module Flipper
|
10
|
+
module Api
|
11
|
+
class Middleware
|
12
|
+
# Public: Initializes an instance of the API middleware.
|
13
|
+
#
|
14
|
+
# app - The app this middleware is included in.
|
15
|
+
# flipper_or_block - The Flipper::DSL instance or a block that yields a
|
16
|
+
# Flipper::DSL instance to use for all operations.
|
17
|
+
#
|
18
|
+
# Examples
|
19
|
+
#
|
20
|
+
# flipper = Flipper.new(...)
|
21
|
+
#
|
22
|
+
# # using with a normal flipper instance
|
23
|
+
# use Flipper::Api::Middleware, flipper
|
24
|
+
#
|
25
|
+
# # using with a block that yields a flipper instance
|
26
|
+
# use Flipper::Api::Middleware, lambda { Flipper.new(...) }
|
27
|
+
#
|
28
|
+
def initialize(app, flipper_or_block)
|
29
|
+
@app = app
|
30
|
+
|
31
|
+
if flipper_or_block.respond_to?(:call)
|
32
|
+
@flipper_block = flipper_or_block
|
33
|
+
else
|
34
|
+
@flipper = flipper_or_block
|
35
|
+
end
|
36
|
+
|
37
|
+
@action_collection = ActionCollection.new
|
38
|
+
@action_collection.add Api::V1::Actions::BooleanGate
|
39
|
+
@action_collection.add Api::V1::Actions::Feature
|
40
|
+
end
|
41
|
+
|
42
|
+
def flipper
|
43
|
+
@flipper ||= @flipper_block.call
|
44
|
+
end
|
45
|
+
|
46
|
+
def call(env)
|
47
|
+
dup.call!(env)
|
48
|
+
end
|
49
|
+
|
50
|
+
def call!(env)
|
51
|
+
request = Rack::Request.new(env)
|
52
|
+
action_class = @action_collection.action_for_request(request)
|
53
|
+
if action_class.nil?
|
54
|
+
@app.status = 404
|
55
|
+
@app.call(env)
|
56
|
+
else
|
57
|
+
action_class.run(flipper, request)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'flipper/api/action'
|
2
|
+
|
3
|
+
module Flipper
|
4
|
+
module Api
|
5
|
+
module V1
|
6
|
+
module Actions
|
7
|
+
class BooleanGate < Api::Action
|
8
|
+
route %r{api/v1/features/[^/]*/(enable|disable)/?\Z}
|
9
|
+
|
10
|
+
def put
|
11
|
+
feature_name = Rack::Utils.unescape(path_parts[-2])
|
12
|
+
feature = flipper[feature_name.to_sym]
|
13
|
+
action = Rack::Utils.unescape(path_parts.last)
|
14
|
+
feature.send(action)
|
15
|
+
json_response({}, 204)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'flipper/api/action'
|
2
|
+
require 'flipper/api/v1/decorators/feature'
|
3
|
+
|
4
|
+
module Flipper
|
5
|
+
module Api
|
6
|
+
module V1
|
7
|
+
module Actions
|
8
|
+
class Feature < Api::Action
|
9
|
+
|
10
|
+
route %r{api/v1/features/[^/]*/?\Z}
|
11
|
+
|
12
|
+
def get
|
13
|
+
if feature_names.include?(feature_name)
|
14
|
+
feature = Decorators::Feature.new(flipper[feature_name])
|
15
|
+
json_response(feature.as_json)
|
16
|
+
else
|
17
|
+
json_response({}, 404)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def delete
|
22
|
+
if feature_names.include?(feature_name)
|
23
|
+
flipper.remove(feature_name)
|
24
|
+
|
25
|
+
json_response({}, 204)
|
26
|
+
else
|
27
|
+
json_response({}, 404)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def feature_name
|
34
|
+
@feature_name ||= Rack::Utils.unescape(path_parts.last)
|
35
|
+
end
|
36
|
+
|
37
|
+
def feature_names
|
38
|
+
@feature_names ||= flipper.adapter.features
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'delegate'
|
2
|
+
require 'flipper/api/v1/decorators/gate'
|
3
|
+
|
4
|
+
module Flipper
|
5
|
+
module Api
|
6
|
+
module V1
|
7
|
+
module Decorators
|
8
|
+
class Feature < SimpleDelegator
|
9
|
+
|
10
|
+
# Public: The feature being decorated.
|
11
|
+
alias_method :feature, :__getobj__
|
12
|
+
|
13
|
+
# Public: Returns instance as hash that is ready to be json dumped.
|
14
|
+
def as_json
|
15
|
+
gate_values = feature.gate_values
|
16
|
+
{
|
17
|
+
'key' => key,
|
18
|
+
'state' => state.to_s,
|
19
|
+
'gates' => gates.map { |gate|
|
20
|
+
Decorators::Gate.new(gate, gate_values[gate.key]).as_json
|
21
|
+
},
|
22
|
+
}
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Flipper
|
2
|
+
module Api
|
3
|
+
module V1
|
4
|
+
module Decorators
|
5
|
+
class Gate < SimpleDelegator
|
6
|
+
# Public the gate being decorated
|
7
|
+
alias_method :gate, :__getobj__
|
8
|
+
|
9
|
+
# Public: the value for the gate from the adapter.
|
10
|
+
attr_reader :value
|
11
|
+
|
12
|
+
def initialize(gate, value = nil)
|
13
|
+
super gate
|
14
|
+
@value = value
|
15
|
+
end
|
16
|
+
|
17
|
+
def as_json
|
18
|
+
{
|
19
|
+
'key' => gate.key.to_s,
|
20
|
+
'name' => gate.name.to_s,
|
21
|
+
'value' => value_as_json,
|
22
|
+
}
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
# json doesn't like sets
|
28
|
+
def value_as_json
|
29
|
+
data_type == :set ? value.to_a : value
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/flipper/api.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'rack'
|
2
|
+
require 'flipper'
|
3
|
+
require 'flipper/api/middleware'
|
4
|
+
|
5
|
+
module Flipper
|
6
|
+
module Api
|
7
|
+
CONTENT_TYPE = 'application/json'.freeze
|
8
|
+
|
9
|
+
def self.app(flipper)
|
10
|
+
app = App.new(200,{ 'Content-Type' => CONTENT_TYPE }, [''])
|
11
|
+
builder = Rack::Builder.new
|
12
|
+
yield builder if block_given?
|
13
|
+
builder.use Flipper::Api::Middleware, flipper
|
14
|
+
builder.run app
|
15
|
+
builder
|
16
|
+
end
|
17
|
+
|
18
|
+
class App
|
19
|
+
# Public: HTTP response code
|
20
|
+
# Use this method to update status code before responding
|
21
|
+
attr_writer :status
|
22
|
+
|
23
|
+
def initialize(status, headers, body)
|
24
|
+
@status = status
|
25
|
+
@headers = headers
|
26
|
+
@body = body
|
27
|
+
end
|
28
|
+
|
29
|
+
# Public : Rack expects object that responds to call
|
30
|
+
# env - environment hash
|
31
|
+
def call(env)
|
32
|
+
response
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def response
|
38
|
+
[@status, @headers, @body]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/lib/flipper-api.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'flipper/api'
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
RSpec.describe Flipper::Api::V1::Actions::BooleanGate do
|
4
|
+
let(:app) { build_api(flipper) }
|
5
|
+
|
6
|
+
describe 'enable' do
|
7
|
+
before do
|
8
|
+
flipper[:my_feature].disable
|
9
|
+
put '/api/v1/features/my_feature/enable'
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'enables feature' do
|
13
|
+
expect(last_response.status).to eq(204)
|
14
|
+
expect(flipper[:my_feature].on?).to be_truthy
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
describe 'disable' do
|
19
|
+
before do
|
20
|
+
flipper[:my_feature].enable
|
21
|
+
put '/api/v1/features/my_feature/disable'
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'disables feature' do
|
25
|
+
expect(last_response.status).to eq(204)
|
26
|
+
expect(flipper[:my_feature].off?).to be_truthy
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe 'invalid paremeter' do
|
31
|
+
before do
|
32
|
+
put '/api/v1/features/my_feature/invalid_param'
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'responds with 404 when not sent enable or disable parameter' do
|
36
|
+
expect(last_response.status).to eq(404)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
RSpec.describe Flipper::Api::V1::Actions::Feature do
|
4
|
+
let(:app) { build_api(flipper) }
|
5
|
+
let(:feature) { build_feature }
|
6
|
+
let(:gate) { feature.gate(:boolean) }
|
7
|
+
|
8
|
+
describe 'get' do
|
9
|
+
context 'enabled feature' do
|
10
|
+
|
11
|
+
before do
|
12
|
+
flipper[:my_feature].enable
|
13
|
+
get 'api/v1/features/my_feature'
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'responds with correct attributes' do
|
17
|
+
response_body = {
|
18
|
+
'key' => 'my_feature',
|
19
|
+
'state' => 'on',
|
20
|
+
'gates' => [
|
21
|
+
{
|
22
|
+
'key' => 'boolean',
|
23
|
+
'name' => 'boolean',
|
24
|
+
'value' => true,
|
25
|
+
},
|
26
|
+
{
|
27
|
+
'key' => 'groups',
|
28
|
+
'name' => 'group',
|
29
|
+
'value' => [],
|
30
|
+
},
|
31
|
+
{
|
32
|
+
'key' => 'actors',
|
33
|
+
'name' => 'actor',
|
34
|
+
'value' => [],
|
35
|
+
},
|
36
|
+
{
|
37
|
+
'key' => 'percentage_of_actors',
|
38
|
+
'name' => 'percentage_of_actors',
|
39
|
+
'value' => 0,
|
40
|
+
},
|
41
|
+
{
|
42
|
+
'key' => 'percentage_of_time',
|
43
|
+
'name' => 'percentage_of_time',
|
44
|
+
'value' => 0,
|
45
|
+
}
|
46
|
+
]
|
47
|
+
}
|
48
|
+
|
49
|
+
expect(last_response.status).to eq(200)
|
50
|
+
expect(json_response).to eq(response_body)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
context 'disabled feature' do
|
55
|
+
before do
|
56
|
+
flipper[:my_feature].disable
|
57
|
+
get 'api/v1/features/my_feature'
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'responds with correct attributes' do
|
61
|
+
response_body = {
|
62
|
+
'key' => 'my_feature',
|
63
|
+
'state' => 'off',
|
64
|
+
'gates' => [
|
65
|
+
{
|
66
|
+
'key' => 'boolean',
|
67
|
+
'name' => 'boolean',
|
68
|
+
'value' => false,
|
69
|
+
},
|
70
|
+
{
|
71
|
+
'key'=> 'groups',
|
72
|
+
'name'=> 'group',
|
73
|
+
'value'=> [],
|
74
|
+
},
|
75
|
+
{
|
76
|
+
'key' => 'actors',
|
77
|
+
'name' => 'actor',
|
78
|
+
'value' => [],
|
79
|
+
},
|
80
|
+
{
|
81
|
+
'key' => 'percentage_of_actors',
|
82
|
+
'name' => 'percentage_of_actors',
|
83
|
+
'value'=> 0,
|
84
|
+
},
|
85
|
+
{
|
86
|
+
'key' => 'percentage_of_time',
|
87
|
+
'name' => 'percentage_of_time',
|
88
|
+
'value' => 0,
|
89
|
+
}
|
90
|
+
]
|
91
|
+
}
|
92
|
+
|
93
|
+
expect(last_response.status).to eq(200)
|
94
|
+
expect(json_response).to eq(response_body)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
context 'feature does not exist' do
|
99
|
+
before do
|
100
|
+
get 'api/v1/features/not_a_feature'
|
101
|
+
end
|
102
|
+
|
103
|
+
it 'returns 404' do
|
104
|
+
expect(last_response.status).to eq(404)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
describe 'delete' do
|
110
|
+
it 'deletes feature' do
|
111
|
+
flipper[:my_feature].enable
|
112
|
+
expect(flipper.features.map(&:key)).to include('my_feature')
|
113
|
+
delete 'api/v1/features/my_feature'
|
114
|
+
expect(last_response.status).to eq(204)
|
115
|
+
expect(flipper.features.map(&:key)).not_to include('my_feature')
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
metadata
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: flipper-api
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.8.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- John Nunemaker
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-06-21 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rack
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.4'
|
20
|
+
- - "<"
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '3'
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '1.4'
|
30
|
+
- - "<"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '3'
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: flipper
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: 0.8.0
|
40
|
+
type: :runtime
|
41
|
+
prerelease: false
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - "~>"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: 0.8.0
|
47
|
+
description: Rack middleware that provides an API for the flipper gem.
|
48
|
+
email:
|
49
|
+
- nunemaker@gmail.com
|
50
|
+
executables: []
|
51
|
+
extensions: []
|
52
|
+
extra_rdoc_files: []
|
53
|
+
files:
|
54
|
+
- flipper-api.gemspec
|
55
|
+
- lib/flipper-api.rb
|
56
|
+
- lib/flipper/api.rb
|
57
|
+
- lib/flipper/api/action.rb
|
58
|
+
- lib/flipper/api/action_collection.rb
|
59
|
+
- lib/flipper/api/error.rb
|
60
|
+
- lib/flipper/api/middleware.rb
|
61
|
+
- lib/flipper/api/v1/actions/boolean_gate.rb
|
62
|
+
- lib/flipper/api/v1/actions/feature.rb
|
63
|
+
- lib/flipper/api/v1/decorators/feature.rb
|
64
|
+
- lib/flipper/api/v1/decorators/gate.rb
|
65
|
+
- lib/flipper/version.rb
|
66
|
+
- spec/flipper/api/v1/actions/boolean_gate_spec.rb
|
67
|
+
- spec/flipper/api/v1/actions/feature_spec.rb
|
68
|
+
homepage: https://github.com/jnunemaker/flipper
|
69
|
+
licenses:
|
70
|
+
- MIT
|
71
|
+
metadata: {}
|
72
|
+
post_install_message:
|
73
|
+
rdoc_options: []
|
74
|
+
require_paths:
|
75
|
+
- lib
|
76
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
77
|
+
requirements:
|
78
|
+
- - ">="
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
version: '0'
|
81
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
82
|
+
requirements:
|
83
|
+
- - ">="
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
requirements: []
|
87
|
+
rubyforge_project:
|
88
|
+
rubygems_version: 2.4.5.1
|
89
|
+
signing_key:
|
90
|
+
specification_version: 4
|
91
|
+
summary: API for the Flipper gem
|
92
|
+
test_files:
|
93
|
+
- spec/flipper/api/v1/actions/boolean_gate_spec.rb
|
94
|
+
- spec/flipper/api/v1/actions/feature_spec.rb
|