eight_ball 1.0.5 → 2.0.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 +4 -4
- data/.rspec +1 -0
- data/CHANGELOG.md +7 -1
- data/Gemfile +1 -1
- data/Gemfile.lock +32 -27
- 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 +3 -2
- data/lib/eight_ball/configuration_error.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 +27 -39
- 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: d034f89a36b50a0d0c9b8b9a4831a064e5ba94040b420f87a5afd440ff219674
|
4
|
+
data.tar.gz: 9b545a863f9eb98522ae8d039fae1c4f53d21c16d9a86427f919753327ff5d7d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: eb363e7384b897cf5a17f6be921881de912828a496ddf1d403be301c145bcfc85cebc76acca1b3938e7c823f1176f76ab4297ed8b45407ec83a90b18ed2adfbf
|
7
|
+
data.tar.gz: ffa4369cfba089198cac8495cc1b4ccdc2c158a82feddbf6df88e33c5da77e0810fd372eb2fcd9c5d4f5cd343d1e99647a36c0ef9aa095e2d60f37c89f29456d
|
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--require spec_helper
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,12 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## [2.0.0]
|
4
|
+
- [BREAKING] `Parsers` have been replaced with `Marshallers`, allowing bi-directional conversions
|
5
|
+
- Added `EightBall.marshall` as a way to output the Feature list to an external format (e.g. to create a JSON file)
|
6
|
+
- Added `EightBall.features` as a shortcut to `EightBall.provider.features`
|
7
|
+
- Testing framework has been moved from Minitest to rspec
|
8
|
+
- Updated dev dependencies
|
9
|
+
|
3
10
|
## [1.0.5]
|
4
11
|
Security: Update rake to >= 12.3.3
|
5
12
|
|
@@ -14,4 +21,3 @@ Security: Update yard 0.9.16 -> 0.9.20
|
|
14
21
|
|
15
22
|
## [1.0.0]
|
16
23
|
Initial release!
|
17
|
-
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,43 +1,50 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
eight_ball (
|
4
|
+
eight_ball (2.0.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 (11.1.
|
13
|
-
coderay (1.1.
|
12
|
+
awrence (1.1.1)
|
13
|
+
byebug (11.1.3)
|
14
|
+
coderay (1.1.3)
|
15
|
+
diff-lcs (1.3)
|
14
16
|
docile (1.3.2)
|
15
17
|
inch (0.8.0)
|
16
18
|
pry
|
17
19
|
sparkr (>= 0.2.0)
|
18
20
|
term-ansicolor
|
19
21
|
yard (~> 0.9.12)
|
20
|
-
method_source (0.
|
21
|
-
minitest (5.14.0)
|
22
|
-
minitest-reporters (1.4.2)
|
23
|
-
ansi
|
24
|
-
builder
|
25
|
-
minitest (>= 5.0)
|
26
|
-
ruby-progressbar
|
27
|
-
mocha (1.11.2)
|
22
|
+
method_source (1.0.0)
|
28
23
|
plissken (1.3.1)
|
29
|
-
pry (0.
|
30
|
-
coderay (~> 1.1
|
31
|
-
method_source (~>
|
32
|
-
pry-byebug (3.
|
24
|
+
pry (0.13.1)
|
25
|
+
coderay (~> 1.1)
|
26
|
+
method_source (~> 1.0)
|
27
|
+
pry-byebug (3.9.0)
|
33
28
|
byebug (~> 11.0)
|
34
|
-
pry (~> 0.
|
35
|
-
rake (
|
36
|
-
|
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)
|
37
44
|
simplecov (0.18.5)
|
38
45
|
docile (~> 1.1)
|
39
46
|
simplecov-html (~> 0.11)
|
40
|
-
simplecov-console (0.
|
47
|
+
simplecov-console (0.7.2)
|
41
48
|
ansi
|
42
49
|
simplecov
|
43
50
|
terminal-table
|
@@ -48,10 +55,10 @@ GEM
|
|
48
55
|
tins (~> 1.0)
|
49
56
|
terminal-table (1.8.0)
|
50
57
|
unicode-display_width (~> 1.1, >= 1.1.1)
|
51
|
-
tins (1.
|
58
|
+
tins (1.25.0)
|
52
59
|
sync
|
53
|
-
unicode-display_width (1.
|
54
|
-
yard (0.9.
|
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)
|
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
|
|
@@ -16,7 +16,8 @@ module EightBall::Conditions
|
|
16
16
|
|
17
17
|
def parameter=(parameter)
|
18
18
|
return if parameter.nil?
|
19
|
-
|
19
|
+
|
20
|
+
@parameter = parameter.gsub(/(.)([A-Z])/, '\1_\2').downcase
|
20
21
|
end
|
21
22
|
end
|
22
23
|
end
|
@@ -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.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Rewind.io
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-06-17 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,6 +144,7 @@ extensions: []
|
|
158
144
|
extra_rdoc_files: []
|
159
145
|
files:
|
160
146
|
- ".gitignore"
|
147
|
+
- ".rspec"
|
161
148
|
- ".travis.yml"
|
162
149
|
- CHANGELOG.md
|
163
150
|
- Gemfile
|
@@ -175,8 +162,9 @@ files:
|
|
175
162
|
- lib/eight_ball/conditions/list.rb
|
176
163
|
- lib/eight_ball/conditions/never.rb
|
177
164
|
- lib/eight_ball/conditions/range.rb
|
165
|
+
- lib/eight_ball/configuration_error.rb
|
178
166
|
- lib/eight_ball/feature.rb
|
179
|
-
- lib/eight_ball/
|
167
|
+
- lib/eight_ball/marshallers/json.rb
|
180
168
|
- lib/eight_ball/providers/http.rb
|
181
169
|
- lib/eight_ball/providers/refresh_policies/interval.rb
|
182
170
|
- lib/eight_ball/providers/static.rb
|
@@ -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
|