Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Strict adapter to ensure feature exists on get #760

Merged
merged 6 commits into from
Sep 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions examples/strict.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
require 'bundler/setup'
require 'flipper'

adapter = Flipper::Adapters::Strict.new(Flipper::Adapters::Memory.new)
flipper = Flipper.new(adapter)

begin
puts "Checking :unknown_feature, which should raise an error."
flipper.enabled?(:unknown_feature)
warn "An error was not raised, but should have been"
exit 1
rescue Flipper::Adapters::Strict::NotFound => exception
puts "Ok, the exepcted error was raised: #{exception.message}"
end

puts "Flipper.add(:new_feature)"
flipper.add(:new_feature)
puts "Flipper.enabled?(:new_feature) => #{flipper.enabled?(:new_feature)}"
1 change: 1 addition & 0 deletions lib/flipper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ def groups_registry=(registry)
require 'flipper/adapter'
require 'flipper/adapters/memoizable'
require 'flipper/adapters/memory'
require 'flipper/adapters/strict'
require 'flipper/adapters/instrumented'
require 'flipper/configuration'
require 'flipper/dsl'
Expand Down
47 changes: 47 additions & 0 deletions lib/flipper/adapters/strict.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
module Flipper
module Adapters
# An adapter that ensures a feature exists before checking it.
class Strict
extend Forwardable
include ::Flipper::Adapter
attr_reader :name, :adapter, :handler

class NotFound < ::Flipper::Error
def initialize(name)
super "Could not find feature #{name.inspect}. Call `Flipper.add(#{name.inspect})` to create it."
end
end

HANDLERS = {
raise: ->(feature) { raise NotFound.new(feature.key) },
warn: ->(feature) { warn NotFound.new(feature.key).message },
noop: ->(_) { },
}

def_delegators :@adapter, :features, :get_all, :add, :remove, :clear, :enable, :disable

def initialize(adapter, handler = nil, &block)
@name = :strict
@adapter = adapter
@handler = block || HANDLERS.fetch(handler)
end

def get(feature)
assert_feature_exists(feature)
@adapter.get(feature)
end

def get_multi(features)
features.each { |feature| assert_feature_exists(feature) }
@adapter.get_multi(features)
end

private

def assert_feature_exists(feature)
@handler.call(feature) unless @adapter.features.include?(feature.key)
end

end
end
end
62 changes: 62 additions & 0 deletions spec/flipper/adapters/strict_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
RSpec.describe Flipper::Adapters::Strict do
let(:flipper) { Flipper.new(subject) }
let(:feature) { flipper[:unknown] }

it_should_behave_like 'a flipper adapter' do
subject { described_class.new(Flipper::Adapters::Memory.new, :noop) }
end

context "handler = :raise" do
subject { described_class.new(Flipper::Adapters::Memory.new, :raise) }

context "#get" do
it "raises an error for unknown feature" do
expect { subject.get(feature) }.to raise_error(Flipper::Adapters::Strict::NotFound)
end
end

context "#get_multi" do
it "raises an error for unknown feature" do
expect { subject.get_multi([feature]) }.to raise_error(Flipper::Adapters::Strict::NotFound)
end
end
end

context "handler = :warn" do
subject { described_class.new(Flipper::Adapters::Memory.new, :warn) }

context "#get" do
it "raises an error for unknown feature" do
expect(silence { subject.get(feature) }).to match(/Could not find feature "unknown"/)
end
end

context "#get_multi" do
it "raises an error for unknown feature" do
expect(silence { subject.get_multi([feature]) }).to match(/Could not find feature "unknown"/)
end
end
end

context "handler = Block" do
let(:unknown_features) { [] }
subject do
described_class.new(Flipper::Adapters::Memory.new) { |feature| unknown_features << feature.key}
end


context "#get" do
it "raises an error for unknown feature" do
subject.get(feature)
expect(unknown_features).to eq(["unknown"])
end
end

context "#get_multi" do
it "raises an error for unknown feature" do
subject.get_multi([flipper[:foo], flipper[:bar]])
expect(unknown_features).to eq(["foo", "bar"])
end
end
end
end