eight_ball 1.0.4 → 2.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rspec +1 -0
- data/CHANGELOG.md +28 -3
- data/Gemfile +1 -1
- data/Gemfile.lock +44 -39
- data/README.md +8 -8
- data/Rakefile +6 -7
- data/eight_ball.gemspec +6 -6
- data/lib/eight_ball.rb +43 -4
- data/lib/eight_ball/conditions/base.rb +16 -2
- data/lib/eight_ball/conditions/list.rb +6 -0
- data/lib/eight_ball/conditions/range.rb +6 -0
- data/lib/eight_ball/configuration_error.rb +9 -0
- data/lib/eight_ball/feature.rb +9 -0
- data/lib/eight_ball/marshallers/json.rb +115 -0
- data/lib/eight_ball/providers/http.rb +11 -7
- data/lib/eight_ball/providers/refresh_policies/interval.rb +1 -1
- data/lib/eight_ball/version.rb +1 -1
- metadata +28 -42
- data/.travis.yml +0 -14
- data/lib/eight_ball/parsers/json.rb +0 -72
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c05f1bec9958e4b2602bc9559ff20c54bddbfecbc791b25ade0996ca854a61f7
|
4
|
+
data.tar.gz: 7f8ee4370d9a1d1d935442623f847cad7b75b96ca8c497525ef704e715376dcf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b507c5aec0f43afad5d2f25541ebb9939be8478e9d0e5feac685a06c19b5433dd3c62d01270aa5843fd5836054f28cb860bc4df2bec808dc083f6e82696508e6
|
7
|
+
data.tar.gz: 1205c3924565b64c8d86eb4bed1ca0b7e67e1f16bd3c0c0c61c7b48501fb419575dbff352692573c03ab42c2266ac26fce8e6e470bc67d0745eac6cb63c07a84
|
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--require spec_helper
|
data/CHANGELOG.md
CHANGED
@@ -1,17 +1,42 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
-
## [
|
4
|
-
|
3
|
+
## [2.2.1]
|
4
|
+
|
5
|
+
- Switch release to github actions
|
6
|
+
|
7
|
+
## [2.2.0]
|
8
|
+
|
9
|
+
- Add `==` to `Feature` and `Conditions`
|
10
|
+
|
11
|
+
## [2.1.0]
|
12
|
+
|
13
|
+
- Add `features` parameter to `EightBall.marshall` to allow marshalling any Features, not just the ones
|
14
|
+
from the configured Provider.
|
15
|
+
|
16
|
+
## [2.0.0]
|
17
|
+
|
18
|
+
- [BREAKING] `Parsers` have been replaced with `Marshallers`, allowing bi-directional conversions
|
19
|
+
- Added `EightBall.marshall` as a way to output the Feature list to an external format (e.g. to create a JSON file)
|
20
|
+
- Added `EightBall.features` as a shortcut to `EightBall.provider.features`
|
21
|
+
- Testing framework has been moved from Minitest to rspec
|
22
|
+
- Updated dev dependencies
|
23
|
+
|
24
|
+
## [1.0.5]
|
25
|
+
|
26
|
+
Security: Update rake to >= 12.3.3
|
5
27
|
|
6
28
|
## [1.0.3]
|
29
|
+
|
7
30
|
Update .travis.yml
|
8
31
|
|
9
32
|
## [1.0.2]
|
33
|
+
|
10
34
|
Update .travis.yml
|
11
35
|
|
12
36
|
## [1.0.1]
|
37
|
+
|
13
38
|
Security: Update yard 0.9.16 -> 0.9.20
|
14
39
|
|
15
40
|
## [1.0.0]
|
16
|
-
Initial release!
|
17
41
|
|
42
|
+
Initial release!
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,57 +1,64 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
eight_ball (
|
4
|
+
eight_ball (2.2.0)
|
5
|
+
awrence (~> 1.1)
|
5
6
|
plissken (~> 1.2)
|
6
7
|
|
7
8
|
GEM
|
8
9
|
remote: https://rubygems.org/
|
9
10
|
specs:
|
10
11
|
ansi (1.5.0)
|
11
|
-
|
12
|
-
byebug (
|
13
|
-
coderay (1.1.
|
14
|
-
|
15
|
-
|
12
|
+
awrence (1.1.1)
|
13
|
+
byebug (11.1.3)
|
14
|
+
coderay (1.1.3)
|
15
|
+
diff-lcs (1.3)
|
16
|
+
docile (1.3.2)
|
16
17
|
inch (0.8.0)
|
17
18
|
pry
|
18
19
|
sparkr (>= 0.2.0)
|
19
20
|
term-ansicolor
|
20
21
|
yard (~> 0.9.12)
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
22
|
+
method_source (1.0.0)
|
23
|
+
plissken (1.3.1)
|
24
|
+
pry (0.13.1)
|
25
|
+
coderay (~> 1.1)
|
26
|
+
method_source (~> 1.0)
|
27
|
+
pry-byebug (3.9.0)
|
28
|
+
byebug (~> 11.0)
|
29
|
+
pry (~> 0.13.0)
|
30
|
+
rake (13.0.1)
|
31
|
+
rspec (3.9.0)
|
32
|
+
rspec-core (~> 3.9.0)
|
33
|
+
rspec-expectations (~> 3.9.0)
|
34
|
+
rspec-mocks (~> 3.9.0)
|
35
|
+
rspec-core (3.9.2)
|
36
|
+
rspec-support (~> 3.9.3)
|
37
|
+
rspec-expectations (3.9.2)
|
38
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
39
|
+
rspec-support (~> 3.9.0)
|
40
|
+
rspec-mocks (3.9.1)
|
41
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
42
|
+
rspec-support (~> 3.9.0)
|
43
|
+
rspec-support (3.9.3)
|
44
|
+
simplecov (0.18.5)
|
42
45
|
docile (~> 1.1)
|
43
|
-
|
44
|
-
|
45
|
-
simplecov-console (0.4.2)
|
46
|
+
simplecov-html (~> 0.11)
|
47
|
+
simplecov-console (0.7.2)
|
46
48
|
ansi
|
47
|
-
hirb
|
48
49
|
simplecov
|
49
|
-
|
50
|
+
terminal-table
|
51
|
+
simplecov-html (0.12.2)
|
50
52
|
sparkr (0.4.1)
|
51
|
-
|
53
|
+
sync (0.5.0)
|
54
|
+
term-ansicolor (1.7.1)
|
52
55
|
tins (~> 1.0)
|
53
|
-
|
54
|
-
|
56
|
+
terminal-table (1.8.0)
|
57
|
+
unicode-display_width (~> 1.1, >= 1.1.1)
|
58
|
+
tins (1.25.0)
|
59
|
+
sync
|
60
|
+
unicode-display_width (1.7.0)
|
61
|
+
yard (0.9.25)
|
55
62
|
|
56
63
|
PLATFORMS
|
57
64
|
ruby
|
@@ -60,11 +67,9 @@ DEPENDENCIES
|
|
60
67
|
bundler (~> 1.17)
|
61
68
|
eight_ball!
|
62
69
|
inch (~> 0.8)
|
63
|
-
minitest (~> 5.0)
|
64
|
-
minitest-reporters (~> 1.3)
|
65
|
-
mocha (~> 1.7)
|
66
70
|
pry-byebug (~> 3.6)
|
67
|
-
rake (~>
|
71
|
+
rake (~> 13.0)
|
72
|
+
rspec (~> 3.9.0)
|
68
73
|
simplecov (~> 0.16)
|
69
74
|
simplecov-console (~> 0.4)
|
70
75
|
|
data/README.md
CHANGED
@@ -42,8 +42,8 @@ json_input = %(
|
|
42
42
|
)
|
43
43
|
|
44
44
|
# Transform the JSON into a list of Features
|
45
|
-
|
46
|
-
features =
|
45
|
+
marshaller = EightBall::Marshallers::Json.new
|
46
|
+
features = marshaller.unmarshall json_input
|
47
47
|
|
48
48
|
# Tell EightBall about these Features
|
49
49
|
EightBall.provider = EightBall::Providers::Static.new features
|
@@ -76,7 +76,7 @@ A Condition must either be `true` or `false`. It describes when a Feature is ena
|
|
76
76
|
A Provider is able to give EightBall the list of Features it needs to answer queries.
|
77
77
|
|
78
78
|
**Supported Providers**
|
79
|
-
- [HTTP](lib/eight_ball/providers/http.rb): Connect to a URL and use the given
|
79
|
+
- [HTTP](lib/eight_ball/providers/http.rb): Connect to a URL and use the given Marshaller to convert the response into a list of Features.
|
80
80
|
- [Static](lib/eight_ball/providers/static.rb): Once initialized with a list of Features, always provides that same list of Features.
|
81
81
|
|
82
82
|
#### RefreshPolicies
|
@@ -85,15 +85,15 @@ Some Providers are able to automatically "refresh" their list of Features using
|
|
85
85
|
**Supported RefreshPolicies**
|
86
86
|
- [Interval](lib/eight_ball/providers/refresh_policies/interval.rb): The data is considered fresh for a given number of seconds, after which it is considered stale and should be refreshed.
|
87
87
|
|
88
|
-
###
|
89
|
-
A
|
88
|
+
### Marshallers
|
89
|
+
A Marshaller converts Features to and from another format.
|
90
90
|
|
91
|
-
**Supported
|
92
|
-
- [JSON](lib/eight_ball/
|
91
|
+
**Supported Marshaller**
|
92
|
+
- [JSON](lib/eight_ball/marshallers/json.rb)
|
93
93
|
|
94
94
|
## Development
|
95
95
|
|
96
|
-
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake
|
96
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
97
97
|
|
98
98
|
To install this gem onto your local machine, run `bundle exec rake install`.
|
99
99
|
|
data/Rakefile
CHANGED
@@ -1,10 +1,9 @@
|
|
1
|
-
require
|
2
|
-
require "rake/testtask"
|
1
|
+
require 'bundler/gem_tasks'
|
3
2
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
3
|
+
begin
|
4
|
+
require 'rspec/core/rake_task'
|
5
|
+
RSpec::Core::RakeTask.new(:spec)
|
6
|
+
rescue LoadError
|
8
7
|
end
|
9
8
|
|
10
|
-
task :
|
9
|
+
task default: :spec
|
data/eight_ball.gemspec
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
lib = File.expand_path('
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
4
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
5
|
require 'eight_ball/version'
|
6
6
|
|
@@ -17,22 +17,22 @@ Gem::Specification.new do |spec|
|
|
17
17
|
|
18
18
|
spec.required_ruby_version = '>= 2.5.0'
|
19
19
|
|
20
|
-
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(
|
20
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(spec|.github|examples)/}) }
|
21
21
|
spec.bindir = 'exe'
|
22
22
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
23
23
|
spec.require_paths = ['lib']
|
24
24
|
|
25
25
|
### DEPENDENCIES
|
26
|
+
|
27
|
+
spec.add_dependency 'awrence', '~> 1.1'
|
26
28
|
spec.add_dependency 'plissken', '~> 1.2'
|
27
29
|
|
28
30
|
# Development
|
29
31
|
spec.add_development_dependency 'bundler', '~> 1.17'
|
30
32
|
spec.add_development_dependency 'inch', '~> 0.8'
|
31
|
-
spec.add_development_dependency 'minitest', '~> 5.0'
|
32
|
-
spec.add_development_dependency 'minitest-reporters', '~> 1.3'
|
33
|
-
spec.add_development_dependency 'mocha', '~> 1.7'
|
34
33
|
spec.add_development_dependency 'pry-byebug', '~> 3.6'
|
35
|
-
spec.add_development_dependency 'rake', '~>
|
34
|
+
spec.add_development_dependency 'rake', '~> 13.0'
|
35
|
+
spec.add_development_dependency 'rspec', '~> 3.9.0'
|
36
36
|
spec.add_development_dependency 'simplecov', '~> 0.16'
|
37
37
|
spec.add_development_dependency 'simplecov-console', '~> 0.4'
|
38
38
|
end
|
data/lib/eight_ball.rb
CHANGED
@@ -3,6 +3,8 @@
|
|
3
3
|
require 'eight_ball/version'
|
4
4
|
require 'eight_ball/feature'
|
5
5
|
|
6
|
+
require 'eight_ball/configuration_error'
|
7
|
+
|
6
8
|
require 'eight_ball/conditions/conditions'
|
7
9
|
require 'eight_ball/conditions/base'
|
8
10
|
|
@@ -11,7 +13,7 @@ require 'eight_ball/conditions/list'
|
|
11
13
|
require 'eight_ball/conditions/never'
|
12
14
|
require 'eight_ball/conditions/range'
|
13
15
|
|
14
|
-
require 'eight_ball/
|
16
|
+
require 'eight_ball/marshallers/json'
|
15
17
|
|
16
18
|
require 'eight_ball/providers/http'
|
17
19
|
require 'eight_ball/providers/static'
|
@@ -33,6 +35,24 @@ module EightBall
|
|
33
35
|
@provider = provider
|
34
36
|
end
|
35
37
|
|
38
|
+
# Gets the {EightBall::Providers Provider} instance
|
39
|
+
# EightBall is configured to use
|
40
|
+
#
|
41
|
+
# @return {EightBall::Providers Provider}
|
42
|
+
def self.provider
|
43
|
+
@provider
|
44
|
+
end
|
45
|
+
|
46
|
+
# Serves as a shortcut to access the {EightBall::Feature Features} available
|
47
|
+
# on the configured {EightBall::Providers Provider}
|
48
|
+
#
|
49
|
+
# @return [Array<EightBall::Feature>]
|
50
|
+
def self.features
|
51
|
+
raise EightBall::ConfigurationError, 'No Provider has been configured; there can be no features. Please see "EightBall.provider="' unless provider
|
52
|
+
|
53
|
+
provider.features
|
54
|
+
end
|
55
|
+
|
36
56
|
# "EightBall, is the feature named 'NewFeature' enabled?"
|
37
57
|
#
|
38
58
|
# @return whether or not the {EightBall::Feature} is enabled.
|
@@ -45,7 +65,7 @@ module EightBall
|
|
45
65
|
# @example
|
46
66
|
# EightBall.enabled? 'feature1', account_id: 1
|
47
67
|
def self.enabled?(name, parameters = {})
|
48
|
-
feature =
|
68
|
+
feature = provider.features.find { |f| f.name == name }
|
49
69
|
return false unless feature
|
50
70
|
|
51
71
|
feature.enabled? parameters
|
@@ -69,7 +89,7 @@ module EightBall
|
|
69
89
|
# Yields to the given block of code if the {EightBall::Feature} is enabled.
|
70
90
|
#
|
71
91
|
# @return [nil] if block is yielded to
|
72
|
-
# @return [false] if
|
92
|
+
# @return [false] if no block is given
|
73
93
|
#
|
74
94
|
# @param name [String] The name of the {EightBall::Feature}.
|
75
95
|
# @param parameters [Hash] The parameters the {EightBall::Conditions} of this
|
@@ -85,9 +105,28 @@ module EightBall
|
|
85
105
|
yield if enabled? name, parameters
|
86
106
|
end
|
87
107
|
|
108
|
+
# Marshalls the {EightBall::Feature Features}. This can be useful for
|
109
|
+
# converting the data to, e.g., a JSON file.
|
110
|
+
#
|
111
|
+
# If a {EightBall::Marshallers Marshaller} is provided, use it.
|
112
|
+
#
|
113
|
+
# If no {EightBall::Marshallers Marshaller} is provided, uses the same
|
114
|
+
# Marshaller that the Provider is configured with.
|
115
|
+
#
|
116
|
+
# If the {EightBall::Providers Provider} does not expose a
|
117
|
+
# {EightBall::Marshallers Marshaller}, this will default to the
|
118
|
+
# {EightBall::Marshallers::Json JSON Marshaller}.
|
119
|
+
def self.marshall(marshaller = nil, features = EightBall.features)
|
120
|
+
marshaller ||=
|
121
|
+
(provider.respond_to?(:marshaller) && provider.marshaller) ||
|
122
|
+
EightBall::Marshallers::Json.new
|
123
|
+
|
124
|
+
marshaller.marshall features
|
125
|
+
end
|
126
|
+
|
88
127
|
def self.logger
|
89
128
|
@logger ||= Logger.new(STDOUT).tap do |log|
|
90
|
-
log.progname =
|
129
|
+
log.progname = name
|
91
130
|
end
|
92
131
|
end
|
93
132
|
|
@@ -4,7 +4,7 @@ module EightBall::Conditions
|
|
4
4
|
class Base
|
5
5
|
attr_reader :parameter
|
6
6
|
|
7
|
-
def initialize(
|
7
|
+
def initialize(_options = [])
|
8
8
|
@parameter = nil
|
9
9
|
end
|
10
10
|
|
@@ -12,11 +12,25 @@ module EightBall::Conditions
|
|
12
12
|
raise 'You can never satisfy the Base condition'
|
13
13
|
end
|
14
14
|
|
15
|
+
def ==(other)
|
16
|
+
other.class == self.class && other.state == state
|
17
|
+
end
|
18
|
+
alias eql? ==
|
19
|
+
|
20
|
+
def hash
|
21
|
+
state.hash
|
22
|
+
end
|
23
|
+
|
15
24
|
protected
|
16
25
|
|
26
|
+
def state
|
27
|
+
[@parameter]
|
28
|
+
end
|
29
|
+
|
17
30
|
def parameter=(parameter)
|
18
31
|
return if parameter.nil?
|
19
|
-
|
32
|
+
|
33
|
+
@parameter = parameter.gsub(/(.)([A-Z])/, '\1_\2').downcase
|
20
34
|
end
|
21
35
|
end
|
22
36
|
end
|
data/lib/eight_ball/feature.rb
CHANGED
@@ -48,6 +48,15 @@ module EightBall
|
|
48
48
|
any_satisfied?(@enabled_for, parameters) && !any_satisfied?(@disabled_for, parameters)
|
49
49
|
end
|
50
50
|
|
51
|
+
def ==(other)
|
52
|
+
name == other.name &&
|
53
|
+
enabled_for.size == other.enabled_for.size &&
|
54
|
+
enabled_for.all? { |condition| other.enabled_for.any? { |other_condition| condition == other_condition } } &&
|
55
|
+
disabled_for.size == other.disabled_for.size &&
|
56
|
+
disabled_for.all? { |condition| other.disabled_for.any? { |other_condition| condition == other_condition } }
|
57
|
+
end
|
58
|
+
alias eql? ==
|
59
|
+
|
51
60
|
private
|
52
61
|
|
53
62
|
def any_satisfied?(conditions, parameters)
|
@@ -0,0 +1,115 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'awrence'
|
4
|
+
require 'json'
|
5
|
+
require 'plissken'
|
6
|
+
|
7
|
+
# A JSON marshaller can convert back and forth between JSON and a list of {EightBall::Feature Features}
|
8
|
+
# The JSON produced will be pretty-printed, as it is assumed the output will be written to a file.
|
9
|
+
#
|
10
|
+
# When converting from JSON, the top-level JSON element must be an array and
|
11
|
+
# its keys must use camel-case; this will be converted to snake-case by EightBall
|
12
|
+
# in order to adhere to both JSON and Ruby standards.
|
13
|
+
#
|
14
|
+
# Below are some examples of valid JSON:
|
15
|
+
#
|
16
|
+
# @example A single {EightBall::Feature} is enabled for accounts 1-5 as well as region Europe
|
17
|
+
# [{
|
18
|
+
# "name": "Feature1",
|
19
|
+
# "enabledFor": [{
|
20
|
+
# "type": "range",
|
21
|
+
# "parameter": "accountId",
|
22
|
+
# "min": 1,
|
23
|
+
# "max": 5
|
24
|
+
# }, {
|
25
|
+
# "type": "list",
|
26
|
+
# "parameter": "regionName",
|
27
|
+
# "values": ["Europe"]
|
28
|
+
# }]
|
29
|
+
# }]
|
30
|
+
#
|
31
|
+
# @example A single {EightBall::Feature} is disabled completely using the {EightBall::Conditions::Always Always} condition
|
32
|
+
# [{
|
33
|
+
# "name": "Feature1",
|
34
|
+
# "disabledFor": [{
|
35
|
+
# "type": "always"
|
36
|
+
# }]
|
37
|
+
# }]
|
38
|
+
module EightBall::Marshallers
|
39
|
+
class Json
|
40
|
+
# Convert the given {EightBall::Feature Features} into a JSON array.
|
41
|
+
#
|
42
|
+
# @param [Array<EightBall::Feature>] features The {EightBall::Feature Features} to convert.
|
43
|
+
# @return [String] The resulting JSON string.
|
44
|
+
#
|
45
|
+
# @example
|
46
|
+
# json_string = <Read from somewhere>
|
47
|
+
#
|
48
|
+
# marshaller = EightBall::Marshallers::Json.new
|
49
|
+
# marshaller.marshall [Array<EightBall::Feature>] => json
|
50
|
+
def marshall(features)
|
51
|
+
JSON.generate(features.map { |feature| feature_to_hash(feature).to_camelback_keys })
|
52
|
+
end
|
53
|
+
|
54
|
+
# Convert the given JSON into a list of {EightBall::Feature Features}.
|
55
|
+
#
|
56
|
+
# @param [String] json The JSON string to convert.
|
57
|
+
# @return [Array<EightBall::Feature>] The parsed {EightBall::Feature Features}
|
58
|
+
#
|
59
|
+
# @example
|
60
|
+
# json_string = <Read from somewhere>
|
61
|
+
#
|
62
|
+
# marshaller = EightBall::Marshallers::Json.new
|
63
|
+
# marshaller.unmarshall json_string => [Features]
|
64
|
+
def unmarshall(json)
|
65
|
+
parsed = JSON.parse(json, symbolize_names: true).to_snake_keys
|
66
|
+
|
67
|
+
raise ArgumentError, 'JSON input was not an array' unless parsed.is_a? Array
|
68
|
+
|
69
|
+
parsed.map do |feature|
|
70
|
+
enabled_for = create_conditions_from_json feature[:enabled_for]
|
71
|
+
disabled_for = create_conditions_from_json feature[:disabled_for]
|
72
|
+
|
73
|
+
EightBall::Feature.new feature[:name], enabled_for, disabled_for
|
74
|
+
end
|
75
|
+
rescue JSON::ParserError => e
|
76
|
+
EightBall.logger.error { "Failed to parse JSON: #{e.message}" }
|
77
|
+
[]
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
def feature_to_hash(feature)
|
83
|
+
hash = {
|
84
|
+
name: feature.name
|
85
|
+
}
|
86
|
+
|
87
|
+
hash[:enabled_for] = feature.enabled_for.map { |condition| condition_to_hash(condition) } unless feature.enabled_for.empty?
|
88
|
+
hash[:disabled_for] = feature.disabled_for.map { |condition| condition_to_hash(condition) } unless feature.disabled_for.empty?
|
89
|
+
|
90
|
+
hash
|
91
|
+
end
|
92
|
+
|
93
|
+
def condition_to_hash(condition)
|
94
|
+
hash = {
|
95
|
+
type: condition.class.name.split('::').last.downcase
|
96
|
+
}
|
97
|
+
condition.instance_variables.each do |var|
|
98
|
+
next unless condition.instance_variable_get(var)
|
99
|
+
|
100
|
+
hash[var.to_s.delete('@')] = condition.instance_variable_get(var)
|
101
|
+
end
|
102
|
+
|
103
|
+
hash
|
104
|
+
end
|
105
|
+
|
106
|
+
def create_conditions_from_json(json_conditions)
|
107
|
+
return [] unless json_conditions&.is_a?(Array)
|
108
|
+
|
109
|
+
json_conditions.map do |condition|
|
110
|
+
condition_class = EightBall::Conditions.by_name condition[:type]
|
111
|
+
condition_class.new condition
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
@@ -1,22 +1,26 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'net/http'
|
4
|
+
|
3
5
|
module EightBall::Providers
|
4
6
|
# An HTTP Provider will make a GET request to a given URI, and convert
|
5
7
|
# the response into an array of {EightBall::Feature Features} using the
|
6
|
-
# given {EightBall::
|
8
|
+
# given {EightBall::Marshallers Marshaller}.
|
7
9
|
#
|
8
10
|
# The {EightBall::Feature Features} will be automatically kept up to date
|
9
11
|
# according to the given {EightBall::Providers::RefreshPolicies RefreshPolicy}.
|
10
12
|
class Http
|
11
13
|
SUPPORTED_SCHEMES = %w[http https].freeze
|
12
14
|
|
15
|
+
attr_reader :marshaller
|
16
|
+
|
13
17
|
# @param uri [String] The URI to GET the {EightBall::Feature Features} from.
|
14
18
|
# @param options [Hash] The options to create the Provider with.
|
15
19
|
#
|
16
|
-
# @option options [EightBall::
|
17
|
-
# The {EightBall::
|
20
|
+
# @option options [EightBall::Marshallers] :marshaller
|
21
|
+
# The {EightBall::Marshallers Marshaller} used to convert the response to an array
|
18
22
|
# of {EightBall::Feature Features}. Defaults to an instance of
|
19
|
-
# {EightBall::
|
23
|
+
# {EightBall::Marshallers::Json}
|
20
24
|
#
|
21
25
|
# @option options [EightBall::Providers::RefreshPolicies] :refresh_policy
|
22
26
|
# The {EightBall::Providers::RefreshPolicies Policy} used to determine
|
@@ -34,7 +38,7 @@ module EightBall::Providers
|
|
34
38
|
|
35
39
|
@uri = URI.parse uri
|
36
40
|
|
37
|
-
@
|
41
|
+
@marshaller = options[:marshaller] || EightBall::Marshallers::Json.new
|
38
42
|
@policy = options[:refresh_policy] || EightBall::Providers::RefreshPolicies::Interval.new
|
39
43
|
end
|
40
44
|
|
@@ -48,8 +52,8 @@ module EightBall::Providers
|
|
48
52
|
private
|
49
53
|
|
50
54
|
def fetch
|
51
|
-
@features = @
|
52
|
-
rescue => e
|
55
|
+
@features = @marshaller.unmarshall Net::HTTP.get(@uri)
|
56
|
+
rescue StandardError => e
|
53
57
|
EightBall.logger.error { "Failed to fetch data from #{@uri}: #{e.message}" }
|
54
58
|
@features = []
|
55
59
|
end
|
@@ -7,7 +7,7 @@ module EightBall::Providers::RefreshPolicies
|
|
7
7
|
# amount of time, after which it is considered stale and should be refreshed.
|
8
8
|
class Interval
|
9
9
|
SECONDS_IN_A_DAY = 86_400
|
10
|
-
|
10
|
+
|
11
11
|
# Creates a new instance of an Interval RefreshPolicy.
|
12
12
|
#
|
13
13
|
# @param seconds [Integer] The number of seconds the data is considered fresh.
|
data/lib/eight_ball/version.rb
CHANGED
metadata
CHANGED
@@ -1,127 +1,113 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: eight_ball
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Rewind.io
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-08-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: awrence
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '1.
|
19
|
+
version: '1.1'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '1.
|
27
|
-
- !ruby/object:Gem::Dependency
|
28
|
-
name: bundler
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
30
|
-
requirements:
|
31
|
-
- - "~>"
|
32
|
-
- !ruby/object:Gem::Version
|
33
|
-
version: '1.17'
|
34
|
-
type: :development
|
35
|
-
prerelease: false
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
37
|
-
requirements:
|
38
|
-
- - "~>"
|
39
|
-
- !ruby/object:Gem::Version
|
40
|
-
version: '1.17'
|
26
|
+
version: '1.1'
|
41
27
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
28
|
+
name: plissken
|
43
29
|
requirement: !ruby/object:Gem::Requirement
|
44
30
|
requirements:
|
45
31
|
- - "~>"
|
46
32
|
- !ruby/object:Gem::Version
|
47
|
-
version: '
|
48
|
-
type: :
|
33
|
+
version: '1.2'
|
34
|
+
type: :runtime
|
49
35
|
prerelease: false
|
50
36
|
version_requirements: !ruby/object:Gem::Requirement
|
51
37
|
requirements:
|
52
38
|
- - "~>"
|
53
39
|
- !ruby/object:Gem::Version
|
54
|
-
version: '
|
40
|
+
version: '1.2'
|
55
41
|
- !ruby/object:Gem::Dependency
|
56
|
-
name:
|
42
|
+
name: bundler
|
57
43
|
requirement: !ruby/object:Gem::Requirement
|
58
44
|
requirements:
|
59
45
|
- - "~>"
|
60
46
|
- !ruby/object:Gem::Version
|
61
|
-
version: '
|
47
|
+
version: '1.17'
|
62
48
|
type: :development
|
63
49
|
prerelease: false
|
64
50
|
version_requirements: !ruby/object:Gem::Requirement
|
65
51
|
requirements:
|
66
52
|
- - "~>"
|
67
53
|
- !ruby/object:Gem::Version
|
68
|
-
version: '
|
54
|
+
version: '1.17'
|
69
55
|
- !ruby/object:Gem::Dependency
|
70
|
-
name:
|
56
|
+
name: inch
|
71
57
|
requirement: !ruby/object:Gem::Requirement
|
72
58
|
requirements:
|
73
59
|
- - "~>"
|
74
60
|
- !ruby/object:Gem::Version
|
75
|
-
version: '
|
61
|
+
version: '0.8'
|
76
62
|
type: :development
|
77
63
|
prerelease: false
|
78
64
|
version_requirements: !ruby/object:Gem::Requirement
|
79
65
|
requirements:
|
80
66
|
- - "~>"
|
81
67
|
- !ruby/object:Gem::Version
|
82
|
-
version: '
|
68
|
+
version: '0.8'
|
83
69
|
- !ruby/object:Gem::Dependency
|
84
|
-
name:
|
70
|
+
name: pry-byebug
|
85
71
|
requirement: !ruby/object:Gem::Requirement
|
86
72
|
requirements:
|
87
73
|
- - "~>"
|
88
74
|
- !ruby/object:Gem::Version
|
89
|
-
version: '
|
75
|
+
version: '3.6'
|
90
76
|
type: :development
|
91
77
|
prerelease: false
|
92
78
|
version_requirements: !ruby/object:Gem::Requirement
|
93
79
|
requirements:
|
94
80
|
- - "~>"
|
95
81
|
- !ruby/object:Gem::Version
|
96
|
-
version: '
|
82
|
+
version: '3.6'
|
97
83
|
- !ruby/object:Gem::Dependency
|
98
|
-
name:
|
84
|
+
name: rake
|
99
85
|
requirement: !ruby/object:Gem::Requirement
|
100
86
|
requirements:
|
101
87
|
- - "~>"
|
102
88
|
- !ruby/object:Gem::Version
|
103
|
-
version: '
|
89
|
+
version: '13.0'
|
104
90
|
type: :development
|
105
91
|
prerelease: false
|
106
92
|
version_requirements: !ruby/object:Gem::Requirement
|
107
93
|
requirements:
|
108
94
|
- - "~>"
|
109
95
|
- !ruby/object:Gem::Version
|
110
|
-
version: '
|
96
|
+
version: '13.0'
|
111
97
|
- !ruby/object:Gem::Dependency
|
112
|
-
name:
|
98
|
+
name: rspec
|
113
99
|
requirement: !ruby/object:Gem::Requirement
|
114
100
|
requirements:
|
115
101
|
- - "~>"
|
116
102
|
- !ruby/object:Gem::Version
|
117
|
-
version:
|
103
|
+
version: 3.9.0
|
118
104
|
type: :development
|
119
105
|
prerelease: false
|
120
106
|
version_requirements: !ruby/object:Gem::Requirement
|
121
107
|
requirements:
|
122
108
|
- - "~>"
|
123
109
|
- !ruby/object:Gem::Version
|
124
|
-
version:
|
110
|
+
version: 3.9.0
|
125
111
|
- !ruby/object:Gem::Dependency
|
126
112
|
name: simplecov
|
127
113
|
requirement: !ruby/object:Gem::Requirement
|
@@ -158,7 +144,7 @@ extensions: []
|
|
158
144
|
extra_rdoc_files: []
|
159
145
|
files:
|
160
146
|
- ".gitignore"
|
161
|
-
- ".
|
147
|
+
- ".rspec"
|
162
148
|
- CHANGELOG.md
|
163
149
|
- Gemfile
|
164
150
|
- Gemfile.lock
|
@@ -175,8 +161,9 @@ files:
|
|
175
161
|
- lib/eight_ball/conditions/list.rb
|
176
162
|
- lib/eight_ball/conditions/never.rb
|
177
163
|
- lib/eight_ball/conditions/range.rb
|
164
|
+
- lib/eight_ball/configuration_error.rb
|
178
165
|
- lib/eight_ball/feature.rb
|
179
|
-
- lib/eight_ball/
|
166
|
+
- lib/eight_ball/marshallers/json.rb
|
180
167
|
- lib/eight_ball/providers/http.rb
|
181
168
|
- lib/eight_ball/providers/refresh_policies/interval.rb
|
182
169
|
- lib/eight_ball/providers/static.rb
|
@@ -200,8 +187,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
200
187
|
- !ruby/object:Gem::Version
|
201
188
|
version: '0'
|
202
189
|
requirements: []
|
203
|
-
|
204
|
-
rubygems_version: 2.7.7
|
190
|
+
rubygems_version: 3.0.3
|
205
191
|
signing_key:
|
206
192
|
specification_version: 4
|
207
193
|
summary: The most cost efficient way to flag features
|
data/.travis.yml
DELETED
@@ -1,14 +0,0 @@
|
|
1
|
-
sudo: false
|
2
|
-
language: ruby
|
3
|
-
cache: bundler
|
4
|
-
rvm:
|
5
|
-
- 2.5.0
|
6
|
-
before_install: gem install bundler -v 1.17.2
|
7
|
-
deploy:
|
8
|
-
provider: rubygems
|
9
|
-
api_key:
|
10
|
-
secure: DdePL5PDqSp2n3/pr45Vh75f1ImntBhR16T75Rke1O0Z2WiBMJZcHFFPQMs6cuf3H/8mdWF/lzexxM0U2U9J7eXs5WZILxougA3NAnJeC3GU6Nbpv7FvR73bJeb+W6Dcq5e6yjmQMU5cmt0LAOitvBoYjnIIRNnxnTyOaHkr2QcNNTuAOOGnmLx/SYsIrFqgjesC5Xi5QfEDFSFAPzEbb9Uq9m2vUTDx1OeXHb/6sXDZUrrTKyOdag2iqNMwrGztjZh5bSMjZmWuQjymzYU4xJRqjdUexYTfreFNde6e7Nm+5gjM0smo89rKUOUxd7FK9WLD0/uuMmkLFnyEQHa8ZMn8y17VNnaC9h3KEdJ1SVpvvzcrJ5Y7rRZxpc6Z+Q1+kwyjhz5enTZRDqoxL1j/8QO5uf3Wg6i0DNR5VqK0a+8h3CznQMdnb2e+beSZgvzj6vJLn6L7SIJ/EhU9EH4QouCDMsyrVeZWOrbzKwvO7PMd55azRlux/wYydSnxBKTZgPm3HXQ18rPoHb+OxCFDOtnK/BPJvxjosu/kbmktO/xMsAtMVzQ7qKSUSZYXc7Aw+YUALnHYO3TStfDToHH46lo0eRN/O/SzFe54WAd4oblYoglwf9htdc+59bhAfG4B4gM3J/k9TCXEoi8iKREjy4d8kVRdSImpKoGM4kx0Xyk=
|
11
|
-
gem: eight_ball
|
12
|
-
on:
|
13
|
-
tags: true
|
14
|
-
repo: rewindio/eight_ball
|
@@ -1,72 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'plissken'
|
4
|
-
|
5
|
-
module EightBall::Parsers
|
6
|
-
# A JSON parser will parse JSON into a list of {EightBall::Feature Features}.
|
7
|
-
# The top-level JSON element must be an array and must use camel-case;
|
8
|
-
# this will be converted to snake-case by EightBall.
|
9
|
-
#
|
10
|
-
# Below are some examples of valid JSON:
|
11
|
-
#
|
12
|
-
# @example A single {EightBall::Feature} is enabled for accounts 1-5 as well as region Europe
|
13
|
-
# [{
|
14
|
-
# "name": "Feature1",
|
15
|
-
# "enabledFor": [{
|
16
|
-
# "type": "range",
|
17
|
-
# "parameter": "accountId",
|
18
|
-
# "min": 1,
|
19
|
-
# "max": 5
|
20
|
-
# }, {
|
21
|
-
# "type": "list",
|
22
|
-
# "parameter": "regionName",
|
23
|
-
# "values": ["Europe"]
|
24
|
-
# }]
|
25
|
-
# }]
|
26
|
-
#
|
27
|
-
# @example A single {EightBall::Feature} is disabled completely using the {EightBall::Conditions::Always Always} condition
|
28
|
-
# [{
|
29
|
-
# "name": "Feature1",
|
30
|
-
# "disabledFor": [{
|
31
|
-
# "type": "always"
|
32
|
-
# }]
|
33
|
-
# }]
|
34
|
-
class Json
|
35
|
-
# Convert the JSON into a list of {EightBall::Feature Features}.
|
36
|
-
#
|
37
|
-
# @param [String] json The JSON string to parse.
|
38
|
-
# @return [Array<EightBall::Feature>] The parsed {EightBall::Feature Features}
|
39
|
-
#
|
40
|
-
# @example
|
41
|
-
# json_string = <Read from somewhere>
|
42
|
-
#
|
43
|
-
# parser = EightBall::Parsers::Json.new
|
44
|
-
# parser.parse json_string => [Features]
|
45
|
-
def parse(json)
|
46
|
-
parsed = JSON.parse(json, :symbolize_names => true).to_snake_keys
|
47
|
-
|
48
|
-
raise ArgumentError, 'JSON input was not an array' unless parsed.is_a? Array
|
49
|
-
|
50
|
-
parsed.map do |feature|
|
51
|
-
enabled_for = create_conditions feature[:enabled_for]
|
52
|
-
disabled_for = create_conditions feature[:disabled_for]
|
53
|
-
|
54
|
-
EightBall::Feature.new feature[:name], enabled_for, disabled_for
|
55
|
-
end
|
56
|
-
rescue JSON::ParserError => e
|
57
|
-
EightBall.logger.error { "Failed to parse JSON: #{e.message}" }
|
58
|
-
[]
|
59
|
-
end
|
60
|
-
|
61
|
-
private
|
62
|
-
|
63
|
-
def create_conditions(json_conditions)
|
64
|
-
return [] unless json_conditions && json_conditions.is_a?(Array)
|
65
|
-
|
66
|
-
json_conditions.map do |condition|
|
67
|
-
condition_class = EightBall::Conditions.by_name condition[:type]
|
68
|
-
condition_class.new condition
|
69
|
-
end
|
70
|
-
end
|
71
|
-
end
|
72
|
-
end
|