Skip to content

Commit

Permalink
Add NamingStyle adapter to enforce format for added features.
Browse files Browse the repository at this point in the history
  • Loading branch information
bkeepers committed Aug 22, 2024
1 parent 90372a0 commit 5ed3a4c
Show file tree
Hide file tree
Showing 2 changed files with 112 additions and 0 deletions.
42 changes: 42 additions & 0 deletions lib/flipper/adapters/naming_style.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
module Flipper
module Adapters
# An adapter that enforces a naming style for added features.
#
# Flipper.configure do |config|
# config.use Flipper::Adapters::NamingStyle, :snake # or :camel, :kebab, :screaming_snake, or a Regexp
# end
#
class NamingStyle < Wrapper
InvalidFormat = Class.new(Flipper::Error)

PRESETS = {
camel: /^([A-Z][a-z0-9]*)+$/, # CamelCase
snake: /^[a-z0-9]+(_[a-z0-9]+)*$/, # snake_case
kebab: /^[a-z0-9]+(-[a-z0-9]+)*$/, # kebab-case
screaming_snake: /^[A-Z0-9]+(_[A-Z0-9]+)*$/, # SCREAMING_SNAKE_CASE
}

attr_reader :format

def initialize(adapter, format = :snake)
@format = format.is_a?(Regexp) ? format : PRESETS.fetch(format) {
raise ArgumentError, "Unknown format: #{format.inspect}. Must be a Regexp or one of #{PRESETS.keys.join(', ')}"
}

super(adapter)
end

def add(feature)
unless valid?(feature.key)
raise InvalidFormat, "Feature key #{feature.key.inspect} does not match format #{format.inspect}"
end

super feature
end

def valid?(name)
format.match?(name)
end
end
end
end
70 changes: 70 additions & 0 deletions spec/flipper/adapters/naming_style_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
require "flipper/adapters/naming_style"

RSpec.describe Flipper::Adapters::NamingStyle do
it_should_behave_like "a flipper adapter" do
let(:format) { /.*/ }
let(:memory) { Flipper::Adapters::Memory.new }
let(:adapter) { described_class.new(memory, format) }

subject { adapter }

describe "#initialize" do
it "accepts a regex" do
expect { described_class.new(memory, format) }.not_to raise_error
end

it "accepts a symbol" do
[:camel, :snake, :kebab, :screaming_snake].each do |format|
expect { described_class.new(memory, format) }.not_to raise_error
end
end

it "raises an error if the format is an unknown symbol" do
expect { described_class.new(memory, :Pascal) }.to raise_error(ArgumentError)
end
end

describe "#add" do
{
/\A(breaker|feature)\// => {
valid: %w[breaker/search feature/search],
invalid: %w[search breaker_search breaker],
},
camel: {
valid: %w[Camel CamelCase SCREAMINGCamelCase CamelCase1 Camel1Case],
invalid: %w[snake_case Camel-Kebab lowercase],
},
snake: {
valid: %w[lower snake_case snake_case_1],
invalid: %w[CamelCase cobraCase double__underscore],
},
kebab: {
valid: %w[kebab kebab-case kebab-case-1 htt-party],
invalid: %w[CamelCase CamelCase1 double__dash],
},
screaming_snake: {
valid: %w[SCREAMING SCREAMING_SNAKE SCREAMING_SNAKE_1 HTTP_THING],
invalid: %w[CamelCase CamelCase1 double__underscore],
}
}.each do |format, examples|
context "with format=#{format.inspect}" do
let(:format) { format }

examples[:valid].each do |feature|
it "adds feature named #{feature}" do
expect(subject.add(flipper[feature])).to eq(true)
expect(subject.features).to eq(Set[feature])
end
end

examples[:invalid].each do |feature|
it "raises an error for feature named #{feature}" do
expect { adapter.add(flipper[feature]) }.to raise_error(Flipper::Adapters::NamingStyle::InvalidFormat)
expect(subject.features).to eq(Set[])
end
end
end
end
end
end
end

0 comments on commit 5ed3a4c

Please sign in to comment.