prefab-cloud-ruby 0.12.0 → 0.13.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +39 -0
  3. data/Gemfile +4 -1
  4. data/Gemfile.lock +5 -20
  5. data/Rakefile +1 -0
  6. data/VERSION +1 -1
  7. data/lib/prefab/auth_interceptor.rb +1 -0
  8. data/lib/prefab/cancellable_interceptor.rb +1 -0
  9. data/lib/prefab/client.rb +17 -3
  10. data/lib/prefab/config_client.rb +16 -4
  11. data/lib/prefab/config_helper.rb +1 -0
  12. data/lib/prefab/config_loader.rb +73 -11
  13. data/lib/prefab/config_resolver.rb +3 -2
  14. data/lib/prefab/error.rb +6 -0
  15. data/lib/prefab/errors/initialization_timeout_error.rb +13 -0
  16. data/lib/prefab/errors/invalid_api_key_error.rb +19 -0
  17. data/lib/prefab/errors/missing_default_error.rb +13 -0
  18. data/lib/prefab/feature_flag_client.rb +32 -12
  19. data/lib/prefab/internal_logger.rb +1 -0
  20. data/lib/prefab/logger_client.rb +10 -8
  21. data/lib/prefab/murmer3.rb +1 -0
  22. data/lib/prefab/noop_cache.rb +1 -0
  23. data/lib/prefab/noop_stats.rb +1 -0
  24. data/lib/prefab/options.rb +2 -1
  25. data/lib/prefab/ratelimit_client.rb +1 -0
  26. data/lib/prefab-cloud-ruby.rb +5 -0
  27. data/lib/prefab_pb.rb +1 -0
  28. data/lib/prefab_services_pb.rb +1 -0
  29. data/prefab-cloud-ruby.gemspec +9 -6
  30. data/run_test_harness_server.sh +8 -2
  31. data/test/.prefab.test.config.yaml +22 -0
  32. data/test/harness_server.rb +10 -1
  33. data/test/test_client.rb +91 -0
  34. data/test/test_config_client.rb +38 -1
  35. data/test/test_config_loader.rb +1 -1
  36. data/test/test_config_resolver.rb +1 -1
  37. data/test/test_feature_flag_client.rb +25 -19
  38. data/test/test_helper.rb +22 -2
  39. data/test/test_logger.rb +23 -19
  40. metadata +8 -17
  41. data/lib/prefab/prefab.rb +0 -32
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2525930a7f8c2e2924db43504b7dfb6d32e60647bbc359b8a85af8ae3c9f2db0
4
- data.tar.gz: 583f5dc901866eaf206f7f693a95bec47b7ea04c54f3d56275a04bbf0a7bb75f
3
+ metadata.gz: 70fdeb9c1e8651a918fb7700a921e23843374ee9d5f3904a09d076e4be3b3669
4
+ data.tar.gz: bd1cd6750f66a4e1f1940010fdaef01596de605f84ec80034747c906203aa8fc
5
5
  SHA512:
6
- metadata.gz: 6c4ff539c75a2d2cfc5e5d6c3ac7bbab494489386d9a1c66ec97af47ab11d46e723a86df862ab8f86a06b09e6ce93d93425f991656cc473395ccf7e2e5fa4da5
7
- data.tar.gz: 1fe296a9c44484d2d8cc4a185929aaef80358fdee8f3bd83d47152933aa6f684fcf995c3f18bae6feb0389370620e9557d2d37f00d657cc72ef43fdd3b55e37e
6
+ metadata.gz: 47cd4f946a8ed79956d5b7f9822eb6b4d666db234bab4767990aa9fd79402acfbe569f4708d2ce08c57b65dedeafa374d6c9f97460ded8c6307d71293ee812fa
7
+ data.tar.gz: 5078a253a71a7166a00f45a325d3f5b1f3435d99f2011ad197ebeb0af8be38699caecdba18fb36d019b13b92992f6c7cef82ae14ef672cda4ae8a3ca199c7236
@@ -0,0 +1,39 @@
1
+ # This workflow uses actions that are not certified by GitHub.
2
+ # They are provided by a third-party and are governed by
3
+ # separate terms of service, privacy policy, and support
4
+ # documentation.
5
+ # This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake
6
+ # For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby
7
+
8
+ name: Ruby
9
+
10
+ on:
11
+ push:
12
+ branches: [ "main" ]
13
+ pull_request:
14
+ branches: [ "main" ]
15
+
16
+ permissions:
17
+ contents: read
18
+
19
+ jobs:
20
+ test:
21
+
22
+ runs-on: ubuntu-latest
23
+ strategy:
24
+ matrix:
25
+ ruby-version: ['2.6', '2.7', '3.0']
26
+
27
+ steps:
28
+ - uses: actions/checkout@v3
29
+ - name: Set up Ruby
30
+ # To automatically get bug fixes and new Ruby versions for ruby/setup-ruby,
31
+ # change this to (see https://github.com/ruby/setup-ruby#versioning):
32
+ # uses: ruby/setup-ruby@v1
33
+ # uses: ruby/setup-ruby@2b019609e2b0f1ea1a2bc8ca11cb82ab46ada124
34
+ uses: ruby/setup-ruby@v1
35
+ with:
36
+ ruby-version: ${{ matrix.ruby-version }}
37
+ bundler-cache: true # runs 'bundle install' and caches installed gems automatically
38
+ - name: Run tests
39
+ run: bundle exec rake
data/Gemfile CHANGED
@@ -9,10 +9,13 @@ gem 'googleapis-common-protos-types', :platforms => :ruby
9
9
 
10
10
  group :development do
11
11
  gem 'grpc-tools', :platforms => :ruby
12
- gem "shoulda", ">= 0"
13
12
  gem "rdoc", "~> 3.12"
14
13
  gem "bundler"
15
14
  gem "juwelier", "~> 2.4.9"
16
15
  gem "simplecov", ">= 0"
17
16
  gem 'thin'
18
17
  end
18
+
19
+ group :test do
20
+ gem "minitest"
21
+ end
data/Gemfile.lock CHANGED
@@ -1,15 +1,10 @@
1
1
  GEM
2
2
  remote: https://rubygems.org/
3
3
  specs:
4
- activesupport (5.2.4.5)
5
- concurrent-ruby (~> 1.0, >= 1.0.2)
6
- i18n (>= 0.7, < 2)
7
- minitest (~> 5.1)
8
- tzinfo (~> 1.1)
9
4
  addressable (2.8.0)
10
5
  public_suffix (>= 2.0.2, < 5.0)
11
6
  builder (3.2.4)
12
- concurrent-ruby (1.1.8)
7
+ concurrent-ruby (1.1.10)
13
8
  daemons (1.4.1)
14
9
  descendants_tracker (0.0.4)
15
10
  thread_safe (~> 0.3, >= 0.3.1)
@@ -34,7 +29,7 @@ GEM
34
29
  faraday (>= 0.8, < 2)
35
30
  hashie (~> 3.5, >= 3.5.2)
36
31
  oauth2 (~> 1.0)
37
- google-protobuf (3.19.3)
32
+ google-protobuf (3.21.4)
38
33
  googleapis-common-protos-types (1.3.0)
39
34
  google-protobuf (~> 3.14)
40
35
  grpc (1.43.1)
@@ -51,8 +46,6 @@ GEM
51
46
  http-cookie (1.0.4)
52
47
  domain_name (~> 0.5)
53
48
  http-form_data (2.3.0)
54
- i18n (1.8.9)
55
- concurrent-ruby (~> 1.0)
56
49
  json (1.8.6)
57
50
  juwelier (2.4.9)
58
51
  builder
@@ -76,7 +69,7 @@ GEM
76
69
  ffi-compiler (~> 1.0)
77
70
  rake (~> 13.0)
78
71
  mini_portile2 (2.8.0)
79
- minitest (5.14.4)
72
+ minitest (5.16.2)
80
73
  multi_json (1.15.0)
81
74
  multi_xml (0.6.0)
82
75
  multipart-post (2.1.1)
@@ -92,19 +85,13 @@ GEM
92
85
  psych (3.3.1)
93
86
  public_suffix (4.0.6)
94
87
  racc (1.6.0)
95
- rack (2.2.3)
88
+ rack (2.2.4)
96
89
  rake (13.0.3)
97
90
  rchardet (1.8.0)
98
91
  rdoc (3.12.2)
99
92
  json (~> 1.4)
100
93
  ruby2_keywords (0.0.4)
101
94
  semver2 (3.4.2)
102
- shoulda (4.0.0)
103
- shoulda-context (~> 2.0)
104
- shoulda-matchers (~> 4.0)
105
- shoulda-context (2.0.0)
106
- shoulda-matchers (4.5.1)
107
- activesupport (>= 4.2.0)
108
95
  simplecov (0.18.5)
109
96
  docile (~> 1.1)
110
97
  simplecov-html (~> 0.11)
@@ -114,8 +101,6 @@ GEM
114
101
  eventmachine (~> 1.0, >= 1.0.4)
115
102
  rack (>= 1, < 3)
116
103
  thread_safe (0.3.6)
117
- tzinfo (1.2.9)
118
- thread_safe (~> 0.1)
119
104
  unf (0.1.4)
120
105
  unf_ext
121
106
  unf_ext (0.0.8)
@@ -133,8 +118,8 @@ DEPENDENCIES
133
118
  grpc-tools
134
119
  juwelier (~> 2.4.9)
135
120
  ld-eventsource
121
+ minitest
136
122
  rdoc (~> 3.12)
137
- shoulda
138
123
  simplecov
139
124
  thin
140
125
 
data/Rakefile CHANGED
@@ -1,4 +1,5 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
2
3
 
3
4
  require 'rubygems'
4
5
  require 'bundler'
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.12.0
1
+ 0.13.0
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Prefab
2
3
  class AuthInterceptor < GRPC::ClientInterceptor
3
4
  def initialize(api_key)
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Prefab
2
3
  class CancellableInterceptor < GRPC::ClientInterceptor
3
4
  WAIT_SEC = 3
data/lib/prefab/client.rb CHANGED
@@ -1,8 +1,9 @@
1
+ # frozen_string_literal: true
1
2
  module Prefab
2
-
3
3
  class Client
4
4
  MAX_SLEEP_SEC = 10
5
5
  BASE_SLEEP_SEC = 0.5
6
+ NO_DEFAULT_PROVIDED = :no_default_provided
6
7
 
7
8
  attr_reader :project_id, :shared_cache, :stats, :namespace, :interceptor, :api_key, :prefab_api_url, :options
8
9
 
@@ -18,8 +19,7 @@ module Prefab
18
19
  log_internal Logger::INFO, "Prefab Running in Local Mode"
19
20
  else
20
21
  @api_key = @options.api_key
21
- raise "No API key. Set PREFAB_API_KEY env var or use PREFAB_DATASOURCES=LOCAL_ONLY" if @api_key.nil? || @api_key.empty?
22
- raise "PREFAB_API_KEY format invalid. Expecting 123-development-yourapikey-SDK" unless @api_key.count("-") == 3
22
+ raise Prefab::Errors::InvalidApiKeyError.new(@api_key) if @api_key.nil? || @api_key.empty? || api_key.count("-") != 3
23
23
  @project_id = @api_key.split("-")[0].to_i # unvalidated, but that's ok. APIs only listen to the actual passwd
24
24
  @interceptor = Prefab::AuthInterceptor.new(@api_key)
25
25
  @prefab_api_url = @options.prefab_api_url
@@ -91,6 +91,20 @@ module Prefab
91
91
  @_channel = nil
92
92
  end
93
93
 
94
+ def enabled?(feature_name, lookup_key=nil, attributes={})
95
+ feature_flag_client.feature_is_on_for?(feature_name, lookup_key, attributes: attributes)
96
+ end
97
+
98
+ def get(key, default_or_lookup_key=NO_DEFAULT_PROVIDED, attributes={}, ff_default=NO_DEFAULT_PROVIDED)
99
+ result = config_client.get(key, default_or_lookup_key)
100
+
101
+ if result.is_a?(Prefab::FeatureFlag)
102
+ feature_flag_client.get(key, default_or_lookup_key, attributes, default: ff_default)
103
+ else
104
+ result
105
+ end
106
+ end
107
+
94
108
  private
95
109
 
96
110
  def http_secure?
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Prefab
2
3
  class ConfigClient
3
4
  include Prefab::ConfigHelper
@@ -73,9 +74,9 @@ module Prefab
73
74
  rows: [Prefab::ConfigRow.new(value: config_value)])
74
75
  end
75
76
 
76
- def get(key)
77
+ def get(key, default=Prefab::Client::NO_DEFAULT_PROVIDED)
77
78
  config = _get(key)
78
- config ? value_of(config[:value]) : nil
79
+ config ? value_of(config[:value]) : handle_default(key, default)
79
80
  end
80
81
 
81
82
  def get_config_obj(key)
@@ -85,6 +86,18 @@ module Prefab
85
86
 
86
87
  private
87
88
 
89
+ def handle_default(key, default)
90
+ if default != Prefab::Client::NO_DEFAULT_PROVIDED
91
+ return default
92
+ end
93
+
94
+ if @options.on_no_default == Prefab::Options::ON_NO_DEFAULT::RAISE
95
+ raise Prefab::Errors::MissingDefaultError.new(key)
96
+ end
97
+
98
+ nil
99
+ end
100
+
88
101
  def _get(key)
89
102
  # wait timeout sec for the initalization to be complete
90
103
  @initialized_future.value(@options.initialization_timeout_sec)
@@ -93,7 +106,7 @@ module Prefab
93
106
  @base_client.log_internal Logger::WARN, "Couldn't Initialize In #{@options.initialization_timeout_sec}. Key #{key}. Returning what we have"
94
107
  @initialization_lock.release_write_lock
95
108
  else
96
- raise "Prefab Couldn't Initialize In #{@options.initialization_timeout_sec} 2 timeout. Key #{key}. "
109
+ raise Prefab::Errors::InitializationTimeoutError.new(@options.initialization_timeout_sec, key)
97
110
  end
98
111
  end
99
112
  @config_resolver._get(key)
@@ -140,7 +153,6 @@ module Prefab
140
153
  rescue GRPC::Unauthenticated
141
154
  @base_client.log_internal Logger::WARN, "Unauthenticated"
142
155
  rescue => e
143
- puts e.class
144
156
  @base_client.log_internal Logger::WARN, "Unexpected grpc_api problem loading checkpoint #{e}"
145
157
  false
146
158
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Prefab
2
3
  module ConfigHelper
3
4
  def value_of(config_value)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'yaml'
2
4
  module Prefab
3
5
  class ConfigLoader
@@ -70,22 +72,35 @@ module Prefab
70
72
  if v.class == Hash
71
73
  v.each do |env_k, env_v|
72
74
  if k == @prefab_options.defaults_env
73
- rtn[env_k] = { source: file,
74
- match: k,
75
- config: Prefab::Config.new(key: env_k, rows: [
76
- Prefab::ConfigRow.new(value: Prefab::ConfigValue.new(value_from(env_v)))
77
- ]) }
75
+ if env_v.class == Hash && env_v['feature_flag']
76
+ rtn[env_k] = feature_flag_config(file, k, env_k, env_v)
77
+ else
78
+ rtn[env_k] = {
79
+ source: file,
80
+ match: k,
81
+ config: Prefab::Config.new(
82
+ key: env_k,
83
+ rows: [
84
+ Prefab::ConfigRow.new(value: Prefab::ConfigValue.new(value_from(env_v)))
85
+ ]
86
+ )
87
+ }
88
+ end
78
89
  else
79
90
  next
80
91
  end
81
92
  end
82
93
  else
83
- rtn[k] = { source: file,
84
- match: "default",
85
- config: Prefab::Config.new(key: k, rows: [
86
- Prefab::ConfigRow.new(value: Prefab::ConfigValue.new(value_from(v)))
87
- ]) }
88
-
94
+ rtn[k] = {
95
+ source: file,
96
+ match: "default",
97
+ config: Prefab::Config.new(
98
+ key: k,
99
+ rows: [
100
+ Prefab::ConfigRow.new(value: Prefab::ConfigValue.new(value_from(v)))
101
+ ]
102
+ )
103
+ }
89
104
  end
90
105
  end
91
106
  end
@@ -114,5 +129,52 @@ module Prefab
114
129
  { double: raw }
115
130
  end
116
131
  end
132
+
133
+ def feature_flag_config(file, k, env_k, env_v)
134
+ criteria = Prefab::Criteria.new(operator: 'ALWAYS_TRUE')
135
+
136
+ if env_v['criteria']
137
+ criteria = Prefab::Criteria.new(criteria_values(env_v['criteria']))
138
+ end
139
+
140
+ row = Prefab::ConfigRow.new(
141
+ value: Prefab::ConfigValue.new(
142
+ feature_flag: Prefab::FeatureFlag.new(
143
+ active: true,
144
+ inactive_variant_idx: -1, # not supported
145
+ rules: [
146
+ Prefab::Rule.new(
147
+ variant_weights: [
148
+ Prefab::VariantWeight.new(variant_idx: 0, weight: 1000)
149
+ ],
150
+ criteria: criteria
151
+ )
152
+ ]
153
+ )
154
+ )
155
+ )
156
+
157
+ unless env_v.has_key?('value')
158
+ raise Prefab::Error, "Feature flag config `#{env_k}` #{file} must have a `value`"
159
+ end
160
+
161
+ {
162
+ source: file,
163
+ match: k,
164
+ config: Prefab::Config.new(
165
+ key: env_k,
166
+ variants: [Prefab::FeatureFlagVariant.new(value_from(env_v['value']))],
167
+ rows: [row]
168
+ )
169
+ }
170
+ end
171
+
172
+ def criteria_values(criteria_hash)
173
+ if RUBY_VERSION < '2.7'
174
+ criteria_hash.transform_keys(&:to_sym)
175
+ else
176
+ criteria_hash
177
+ end
178
+ end
117
179
  end
118
180
  end
@@ -1,7 +1,8 @@
1
+ # frozen_string_literal: true
1
2
  module Prefab
2
3
  class ConfigResolver
3
4
  include Prefab::ConfigHelper
4
- NAMESPACE_DELIMITER = ".".freeze
5
+ NAMESPACE_DELIMITER = "."
5
6
 
6
7
  attr_accessor :project_env_id # this will be set by the config_client when it gets an API response
7
8
 
@@ -28,7 +29,7 @@ module Prefab
28
29
  elements << "Match: #{v[:match]}".slice(0..29).ljust(30)
29
30
  elements << "Source: #{v[:source]}"
30
31
  end
31
- str << elements.join(" | ") << "\n"
32
+ str += elements.join(" | ") << "\n"
32
33
  end
33
34
  end
34
35
  str
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Prefab
4
+ class Error < StandardError
5
+ end
6
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Prefab
4
+ module Errors
5
+ class InitializationTimeoutError < Prefab::Error
6
+ def initialize(timeout_sec, key)
7
+ message = "Prefab couldn't initialize in #{timeout_sec} second timeout. Trying to fetch key `#{key}`."
8
+
9
+ super(message)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Prefab
4
+ module Errors
5
+ class InvalidApiKeyError < Prefab::Error
6
+ def initialize(key)
7
+ if key.nil? || key.empty?
8
+ message = "No API key. Set PREFAB_API_KEY env var or use PREFAB_DATASOURCES=LOCAL_ONLY"
9
+
10
+ super(message)
11
+ else
12
+ message = "Your API key format is invalid. Expecting something like 123-development-yourapikey-SDK. You provided `#{key}`"
13
+
14
+ super(message)
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Prefab
4
+ module Errors
5
+ class MissingDefaultError < Prefab::Error
6
+ def initialize(key)
7
+ message = "No value found for key '#{key}' and no default was provided.\n\nIf you'd prefer returning `nil` rather than raising when this occurs, modify the `on_no_default` value you provide in your Prefab::Options."
8
+
9
+ super(message)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Prefab
2
3
  class FeatureFlagClient
3
4
  include Prefab::ConfigHelper
@@ -18,27 +19,48 @@ module Prefab
18
19
  def feature_is_on_for?(feature_name, lookup_key, attributes: {})
19
20
  @base_client.stats.increment("prefab.featureflag.on", tags: ["feature:#{feature_name}"])
20
21
 
21
- return is_on?(get(feature_name, lookup_key, attributes))
22
+ return is_on?(_get(feature_name, lookup_key, attributes, default: false))
22
23
  end
23
24
 
24
- def get(feature_name, lookup_key=nil, attributes={})
25
- feature_obj = @base_client.config_client.get(feature_name)
26
- config_obj = @base_client.config_client.get_config_obj(feature_name)
27
- return nil if feature_obj.nil? || config_obj.nil?
28
- variants = config_obj.variants
29
- evaluate(feature_name, lookup_key, attributes, feature_obj, variants)
30
- end
25
+ def get(feature_name, lookup_key=nil, attributes={}, default: false)
26
+ variant = _get(feature_name, lookup_key, attributes, default: default)
31
27
 
32
- def evaluate(feature_name, lookup_key, attributes, feature_obj, variants)
33
- value_of_variant(get_variant(feature_name, lookup_key, attributes, feature_obj, variants))
28
+ value_of_variant_or_nil(variant, default)
34
29
  end
35
30
 
36
31
  private
37
32
 
33
+ def value_of_variant_or_nil(variant_maybe, default)
34
+ if variant_maybe.nil?
35
+ default != Prefab::Client::NO_DEFAULT_PROVIDED ? default : nil
36
+ else
37
+ value_of_variant(variant_maybe)
38
+ end
39
+ end
40
+
41
+ def _get(feature_name, lookup_key=nil, attributes={}, default:)
42
+ feature_obj = @base_client.config_client.get(feature_name, default)
43
+ config_obj = @base_client.config_client.get_config_obj(feature_name)
44
+
45
+ return nil if feature_obj.nil? || config_obj.nil?
46
+
47
+ if feature_obj == !!feature_obj
48
+ return feature_obj
49
+ end
50
+
51
+ variants = config_obj.variants
52
+ get_variant(feature_name, lookup_key, attributes, feature_obj, variants)
53
+ end
54
+
38
55
  def is_on?(variant)
39
56
  if variant.nil?
40
57
  return false
41
58
  end
59
+
60
+ if variant == !!variant
61
+ return variant
62
+ end
63
+
42
64
  variant.bool
43
65
  rescue
44
66
  @base_client.log.info("is_on? methods only work for boolean feature flags variants. This feature flags variant is '#{variant}'. Returning false")
@@ -61,7 +83,6 @@ module Prefab
61
83
  end
62
84
  end
63
85
 
64
-
65
86
  percent_through_distribution = rand()
66
87
  if lookup_key
67
88
  percent_through_distribution = get_user_pct(feature_name, lookup_key)
@@ -104,7 +125,6 @@ module Prefab
104
125
  #
105
126
  # end
106
127
  def criteria_match?(criteria, lookup_key, attributes)
107
-
108
128
  if criteria.operator == :ALWAYS_TRUE
109
129
  return true
110
130
  elsif criteria.operator == :LOOKUP_KEY_IN
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Prefab
2
3
  class InternalLogger < Logger
3
4
  def initialize(path, logger)
@@ -1,8 +1,10 @@
1
+ # frozen_string_literal: true
1
2
  module Prefab
2
3
  class LoggerClient < Logger
3
4
 
4
- SEP = ".".freeze
5
- BASE = "log_level".freeze
5
+ SEP = "."
6
+ BASE = "log_level"
7
+ UNKNOWN = "unknown"
6
8
 
7
9
  def initialize(logdev, formatter: nil)
8
10
  super(logdev)
@@ -107,10 +109,10 @@ module Prefab
107
109
 
108
110
  # Find the closest match to 'log_level.path' in config
109
111
  def level_of(path)
110
- closest_log_level_match = @config_client.get(BASE) || :warn
112
+ closest_log_level_match = @config_client.get(BASE, :warn)
111
113
  path.split(SEP).inject([BASE]) do |memo, n|
112
114
  memo << n
113
- val = @config_client.get(memo.join(SEP))
115
+ val = @config_client.get(memo.join(SEP), nil)
114
116
  unless val.nil?
115
117
  closest_log_level_match = val
116
118
  end
@@ -122,12 +124,12 @@ module Prefab
122
124
  # sanitize & clean the path of the caller so the key
123
125
  # looks like log_level.app.models.user
124
126
  def get_path(absolute_path, base_label)
125
- path = (absolute_path || "unknown") + ""
127
+ path = (absolute_path || UNKNOWN).dup
126
128
  path.slice! Dir.pwd
127
129
 
128
130
  path.gsub!(/.*?(?=\/lib\/)/im, "")
129
131
 
130
- path = "#{path.gsub("/", SEP).gsub(".rb", "")}#{SEP}#{base_label}"
132
+ path = path.gsub("/", SEP).gsub(".rb", "") + SEP + base_label
131
133
  path.slice! ".lib"
132
134
  path.slice! SEP
133
135
  path
@@ -141,8 +143,8 @@ module Prefab
141
143
  # StubConfigClient to be used while config client initializes
142
144
  # since it may log
143
145
  class BootstrappingConfigClient
144
- def get(key)
145
- ENV["PREFAB_LOG_CLIENT_BOOTSTRAP_LOG_LEVEL"] || :info
146
+ def get(key, default=nil)
147
+ ENV["PREFAB_LOG_CLIENT_BOOTSTRAP_LOG_LEVEL"] || default
146
148
  end
147
149
  end
148
150
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  class Murmur3
2
3
  ## MurmurHash3 was written by Austin Appleby, and is placed in the public
3
4
  ## domain. The author hereby disclaims copyright to this source code.
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Prefab
2
3
  class NoopCache
3
4
  def fetch(name, opts, &method)
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Prefab
2
3
 
3
4
  class NoopStats
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Prefab
2
3
  class Options
3
4
  attr_reader :api_key
@@ -25,7 +26,7 @@ module Prefab
25
26
  end
26
27
  module ON_NO_DEFAULT
27
28
  RAISE = 1
28
- RETURN = 2
29
+ RETURN_NIL = 2
29
30
  end
30
31
  module DATASOURCES
31
32
  ALL = 1
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Prefab
2
3
  class RateLimitClient
3
4
 
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require "concurrent/atomics"
2
3
  require 'concurrent'
3
4
  require 'faraday'
@@ -5,6 +6,10 @@ require 'openssl'
5
6
  require 'openssl'
6
7
  require 'ld-eventsource'
7
8
  require 'prefab_pb'
9
+ require 'prefab/error'
10
+ require 'prefab/errors/initialization_timeout_error'
11
+ require 'prefab/errors/invalid_api_key_error'
12
+ require 'prefab/errors/missing_default_error'
8
13
  require 'prefab_services_pb'
9
14
  require 'prefab/options'
10
15
  require 'prefab/internal_logger'
data/lib/prefab_pb.rb CHANGED
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  # Generated by the protocol buffer compiler. DO NOT EDIT!
2
3
  # source: prefab.proto
3
4
 
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  # Generated by the protocol buffer compiler. DO NOT EDIT!
2
3
  # Source: prefab.proto for package 'prefab'
3
4
 
@@ -2,16 +2,16 @@
2
2
  # DO NOT EDIT THIS FILE DIRECTLY
3
3
  # Instead, edit Juwelier::Tasks in Rakefile, and run 'rake gemspec'
4
4
  # -*- encoding: utf-8 -*-
5
- # stub: prefab-cloud-ruby 0.12.0 ruby lib
5
+ # stub: prefab-cloud-ruby 0.13.0 ruby lib
6
6
 
7
7
  Gem::Specification.new do |s|
8
8
  s.name = "prefab-cloud-ruby".freeze
9
- s.version = "0.12.0"
9
+ s.version = "0.13.0"
10
10
 
11
11
  s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
12
12
  s.require_paths = ["lib".freeze]
13
13
  s.authors = ["Jeff Dwyer".freeze]
14
- s.date = "2022-07-21"
14
+ s.date = "2022-08-10"
15
15
  s.description = "RateLimits & Config as a service".freeze
16
16
  s.email = "jdwyer@prefab.cloud".freeze
17
17
  s.extra_rdoc_files = [
@@ -20,6 +20,7 @@ Gem::Specification.new do |s|
20
20
  ]
21
21
  s.files = [
22
22
  ".envrc",
23
+ ".github/workflows/ruby.yml",
23
24
  ".tool-versions",
24
25
  "CODEOWNERS",
25
26
  "Gemfile",
@@ -37,6 +38,10 @@ Gem::Specification.new do |s|
37
38
  "lib/prefab/config_helper.rb",
38
39
  "lib/prefab/config_loader.rb",
39
40
  "lib/prefab/config_resolver.rb",
41
+ "lib/prefab/error.rb",
42
+ "lib/prefab/errors/initialization_timeout_error.rb",
43
+ "lib/prefab/errors/invalid_api_key_error.rb",
44
+ "lib/prefab/errors/missing_default_error.rb",
40
45
  "lib/prefab/feature_flag_client.rb",
41
46
  "lib/prefab/internal_logger.rb",
42
47
  "lib/prefab/logger_client.rb",
@@ -44,7 +49,6 @@ Gem::Specification.new do |s|
44
49
  "lib/prefab/noop_cache.rb",
45
50
  "lib/prefab/noop_stats.rb",
46
51
  "lib/prefab/options.rb",
47
- "lib/prefab/prefab.rb",
48
52
  "lib/prefab/ratelimit_client.rb",
49
53
  "lib/prefab_pb.rb",
50
54
  "lib/prefab_services_pb.rb",
@@ -52,6 +56,7 @@ Gem::Specification.new do |s|
52
56
  "run_test_harness_server.sh",
53
57
  "test/.prefab.test.config.yaml",
54
58
  "test/harness_server.rb",
59
+ "test/test_client.rb",
55
60
  "test/test_config_client.rb",
56
61
  "test/test_config_loader.rb",
57
62
  "test/test_config_resolver.rb",
@@ -76,7 +81,6 @@ Gem::Specification.new do |s|
76
81
  s.add_runtime_dependency(%q<google-protobuf>.freeze, [">= 0"])
77
82
  s.add_runtime_dependency(%q<googleapis-common-protos-types>.freeze, [">= 0"])
78
83
  s.add_development_dependency(%q<grpc-tools>.freeze, [">= 0"])
79
- s.add_development_dependency(%q<shoulda>.freeze, [">= 0"])
80
84
  s.add_development_dependency(%q<rdoc>.freeze, ["~> 3.12"])
81
85
  s.add_development_dependency(%q<bundler>.freeze, [">= 0"])
82
86
  s.add_development_dependency(%q<juwelier>.freeze, ["~> 2.4.9"])
@@ -90,7 +94,6 @@ Gem::Specification.new do |s|
90
94
  s.add_dependency(%q<google-protobuf>.freeze, [">= 0"])
91
95
  s.add_dependency(%q<googleapis-common-protos-types>.freeze, [">= 0"])
92
96
  s.add_dependency(%q<grpc-tools>.freeze, [">= 0"])
93
- s.add_dependency(%q<shoulda>.freeze, [">= 0"])
94
97
  s.add_dependency(%q<rdoc>.freeze, ["~> 3.12"])
95
98
  s.add_dependency(%q<bundler>.freeze, [">= 0"])
96
99
  s.add_dependency(%q<juwelier>.freeze, ["~> 2.4.9"])
@@ -1,2 +1,8 @@
1
- PREFAB_LOG_CLIENT_BOOTSTRAP_LOG_LEVEL=debug PREFAB_CLOUD_HTTP=true PREFAB_GRPC_URL="localhost:50051" ruby -Ilib test/harness_server.rb
2
- #GRPC_TRACE=all GRPC_VERBOSITY=DEBUG PREFAB_LOG_CLIENT_BOOTSTRAP_LOG_LEVEL=debug PREFAB_CLOUD_HTTP=true PREFAB_API_KEY="1|local_development_api_key" PREFAB_API_URL="localhost:50051" ruby -Ilib test/harness_server.rb
1
+ #! /usr/bin/env bash
2
+
3
+ PREFAB_CDN_URL="https://api-prefab-cloud.global.ssl.fastly.net" \
4
+ PREFAB_LOG_CLIENT_BOOTSTRAP_LOG_LEVEL=debug \
5
+ PREFAB_CLOUD_HTTP=true \
6
+ PREFAB_API_KEY="1|local_development_api_key" \
7
+ PREFAB_GRPC_URL="localhost:50051" \
8
+ ruby -Ilib test/harness_server.rb
@@ -2,9 +2,31 @@ sample: default sample value
2
2
  sample_int: 123
3
3
  sample_double: 12.12
4
4
  sample_bool: true
5
+ false_value: false
6
+ zero_value: 0
5
7
  sample_to_override: Foo
6
8
  prefab.log_level: debug
7
9
  unit_tests:
8
10
  sample: test sample value
11
+ enabled_flag: true
12
+ disabled_flag: false
13
+ flag_with_a_value:
14
+ feature_flag: true
15
+ value: "all-features"
16
+ in_lookup_key:
17
+ feature_flag: true
18
+ value: true
19
+ criteria:
20
+ operator: LOOKUP_KEY_IN
21
+ values:
22
+ - abc123
23
+ - xyz987
24
+ just_my_domain:
25
+ feature_flag: true
26
+ value: new-version
27
+ criteria:
28
+ operator: PROP_IS_ONE_OF
29
+ property: domain
30
+ values: ["prefab.cloud", "example.com"]
9
31
  ignored_env:
10
32
  sample: ignored value
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'prefab-cloud-ruby'
2
3
  require 'rack'
3
4
  require 'base64'
@@ -20,11 +21,19 @@ class RackApp
20
21
  is_feature_flag = !props["feature_flag"].nil?
21
22
  attributes = props["attributes"]
22
23
  puts props
23
- client = Prefab::Client.new(
24
+
25
+ options = Prefab::Options.new(
24
26
  api_key: api_key,
25
27
  namespace: namespace,
28
+ initialization_timeout_sec: 1,
29
+ # We want to `return` rather than raise so we'll use the initial payload if we can't connect to the SSE server
30
+ on_init_failure: Prefab::Options::ON_INITIALIZATION_FAILURE::RETURN,
31
+ # Want to return `nil` rather than raise so we can verify empty values
32
+ on_no_default: Prefab::Options::ON_NO_DEFAULT::RETURN_NIL
26
33
  )
27
34
 
35
+ client = Prefab::Client.new(options)
36
+
28
37
  puts "Key #{key}"
29
38
  puts "User #{user_key}"
30
39
  puts "api_key #{api_key}"
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+ require 'test_helper'
3
+
4
+ class TestClient < Minitest::Test
5
+ def setup
6
+ @client = new_client
7
+ end
8
+
9
+ def test_get
10
+ assert_equal "test sample value", @client.get("sample")
11
+ assert_equal 123, @client.get("sample_int")
12
+ end
13
+
14
+ def test_get_with_default
15
+ # A `false` value is not replaced with the default
16
+ assert_equal false, @client.get("false_value", "red")
17
+
18
+ # A falsy value is not replaced with the default
19
+ assert_equal 0, @client.get("zero_value", "red")
20
+
21
+ # A missing value returns the default
22
+ assert_equal "buckets", @client.get("missing_value", "buckets")
23
+ end
24
+
25
+ def test_get_with_missing_default
26
+ # it raises by default
27
+ err = assert_raises(Prefab::Errors::MissingDefaultError) do
28
+ assert_nil @client.get("missing_value")
29
+ end
30
+
31
+ assert_match(/No value found for key/, err.message)
32
+ assert_match(/on_no_default/, err.message)
33
+
34
+ # you can opt-in to return `nil` instead
35
+ client = new_client(on_no_default: Prefab::Options::ON_NO_DEFAULT::RETURN_NIL)
36
+ assert_nil client.get("missing_value")
37
+ end
38
+
39
+ def test_enabled
40
+ assert_equal false, @client.enabled?("does_not_exist")
41
+ assert_equal true, @client.enabled?("enabled_flag")
42
+ assert_equal false, @client.enabled?("disabled_flag")
43
+ assert_equal false, @client.enabled?("flag_with_a_value")
44
+ end
45
+
46
+ def test_ff_enabled_with_lookup_key
47
+ assert_equal false, @client.enabled?("in_lookup_key", "jimmy")
48
+ assert_equal true, @client.enabled?("in_lookup_key", "abc123")
49
+ assert_equal true, @client.enabled?("in_lookup_key", "xyz987")
50
+ end
51
+
52
+ def test_ff_get_with_lookup_key
53
+ assert_nil @client.get("in_lookup_key", "jimmy")
54
+ assert_equal "DEFAULT", @client.get("in_lookup_key", "jimmy", {}, "DEFAULT")
55
+
56
+ assert_equal true, @client.get("in_lookup_key", "abc123")
57
+ assert_equal true, @client.get("in_lookup_key", "xyz987")
58
+ end
59
+
60
+ def test_ff_enabled_with_attributes
61
+ assert_equal false, @client.enabled?("just_my_domain", "abc123", { domain: "gmail.com" })
62
+ assert_equal false, @client.enabled?("just_my_domain", "abc123", { domain: "prefab.cloud" })
63
+ assert_equal false, @client.enabled?("just_my_domain", "abc123", { domain: "example.com" })
64
+ end
65
+
66
+ def test_ff_get_with_attributes
67
+ assert_nil @client.get("just_my_domain", "abc123", { domain: "gmail.com" })
68
+ assert_equal "DEFAULT", @client.get("just_my_domain", "abc123", { domain: "gmail.com" }, "DEFAULT")
69
+
70
+ assert_equal "new-version", @client.get("just_my_domain", "abc123", { domain: "prefab.cloud" })
71
+ assert_equal "new-version", @client.get("just_my_domain", "abc123", { domain: "example.com" })
72
+ end
73
+
74
+ def test_getting_feature_flag_value
75
+ assert_equal false, @client.enabled?("flag_with_a_value")
76
+ assert_equal "all-features", @client.get("flag_with_a_value")
77
+ end
78
+
79
+ private
80
+
81
+ def new_client(overrides = {})
82
+ options = Prefab::Options.new(**{
83
+ prefab_config_override_dir: "none",
84
+ prefab_config_classpath_dir: "test",
85
+ defaults_env: "unit_tests",
86
+ prefab_datasources: Prefab::Options::DATASOURCES::LOCAL_ONLY
87
+ }.merge(overrides))
88
+
89
+ Prefab::Client.new(options)
90
+ end
91
+ end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'test_helper'
2
3
 
3
4
  class TestConfigClient < Minitest::Test
@@ -6,8 +7,9 @@ class TestConfigClient < Minitest::Test
6
7
  prefab_config_override_dir: "none",
7
8
  prefab_config_classpath_dir: "test",
8
9
  defaults_env: "unit_tests",
9
- local_only: true
10
+ prefab_datasources: Prefab::Options::DATASOURCES::LOCAL_ONLY
10
11
  )
12
+
11
13
  @config_client = Prefab::ConfigClient.new(MockBaseClient.new(options), 10)
12
14
  end
13
15
 
@@ -16,4 +18,39 @@ class TestConfigClient < Minitest::Test
16
18
  assert_equal 123, @config_client.get("sample_int")
17
19
  end
18
20
 
21
+ def test_initialization_timeout_error
22
+ options = Prefab::Options.new(
23
+ api_key: "123-ENV-KEY-SDK",
24
+ initialization_timeout_sec: 0.01,
25
+ logdev: StringIO.new
26
+ )
27
+
28
+ err = assert_raises(Prefab::Errors::InitializationTimeoutError) do
29
+ Prefab::Client.new(options).config_client.get("anything")
30
+ end
31
+
32
+ assert_match(/couldn't initialize in 0.01 second timeout/, err.message)
33
+ end
34
+
35
+ def test_invalid_api_key_error
36
+ options = Prefab::Options.new(
37
+ api_key: "",
38
+ )
39
+
40
+ err = assert_raises(Prefab::Errors::InvalidApiKeyError) do
41
+ Prefab::Client.new(options).config_client.get("anything")
42
+ end
43
+
44
+ assert_match(/No API key/, err.message)
45
+
46
+ options = Prefab::Options.new(
47
+ api_key: "invalid",
48
+ )
49
+
50
+ err = assert_raises(Prefab::Errors::InvalidApiKeyError) do
51
+ Prefab::Client.new(options).config_client.get("anything")
52
+ end
53
+
54
+ assert_match(/format is invalid/, err.message)
55
+ end
19
56
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'test_helper'
2
3
 
3
4
  class TestConfigLoader < Minitest::Test
@@ -89,5 +90,4 @@ class TestConfigLoader < Minitest::Test
89
90
  assert_equal type, @loader.calc_config[key][:config].rows[0].value.type
90
91
  assert_equal value, @loader.calc_config[key][:config].rows[0].value.send(type)
91
92
  end
92
-
93
93
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'test_helper'
2
3
 
3
4
  class TestConfigResolver < Minitest::Test
@@ -174,7 +175,6 @@ class TestConfigResolver < Minitest::Test
174
175
  assert_equal "value", @resolverKeyWith.get("Key:With:Colons")
175
176
 
176
177
  @resolverKeyWithExtra = resolver_for_namespace("Key:With:Extra", @loader)
177
- puts @resolverKeyWithExtra.to_s
178
178
  assert_nil @resolverKeyWithExtra.get("Colons")
179
179
  assert_nil @resolverKeyWithExtra.get("With:Colons")
180
180
  assert_equal "value", @resolverKeyWithExtra.get("Key:With:Colons")
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'test_helper'
2
3
 
3
4
  class TestFeatureFlagClient < Minitest::Test
@@ -8,6 +9,7 @@ class TestFeatureFlagClient < Minitest::Test
8
9
  @client = Prefab::FeatureFlagClient.new(@mock_base_client)
9
10
  Prefab::FeatureFlagClient.send(:public, :is_on?) #publicize for testing
10
11
  Prefab::FeatureFlagClient.send(:public, :segment_match?) #publicize for testing
12
+ Prefab::FeatureFlagClient.send(:public, :get_variant) #publicize for testing
11
13
  end
12
14
 
13
15
  def test_pct
@@ -37,10 +39,10 @@ class TestFeatureFlagClient < Minitest::Test
37
39
 
38
40
  # "1FlagNamehashes high" hashes to 86.322% through dist
39
41
  assert_equal false,
40
- @client.evaluate(feature, "hashes high", [], flag, variants)
42
+ evaluate(feature, "hashes high", [], flag, variants)
41
43
  # "1FlagNamehashes low" hashes to 44.547% through dist
42
44
  assert_equal true,
43
- @client.evaluate(feature, "hashes low", [], flag, variants)
45
+ evaluate(feature, "hashes low", [], flag, variants)
44
46
 
45
47
  end
46
48
 
@@ -56,9 +58,9 @@ class TestFeatureFlagClient < Minitest::Test
56
58
  rules: default_ff_rule(2)
57
59
  )
58
60
  assert_equal true,
59
- @client.evaluate(feature, "hashes high", [], flag, variants)
61
+ evaluate(feature, "hashes high", [], flag, variants)
60
62
  assert_equal true,
61
- @client.evaluate(feature, "hashes low", [], flag, variants)
63
+ evaluate(feature, "hashes low", [], flag, variants)
62
64
 
63
65
  variants = [
64
66
  Prefab::FeatureFlagVariant.new(bool: false),
@@ -70,9 +72,9 @@ class TestFeatureFlagClient < Minitest::Test
70
72
  rules: default_ff_rule(2)
71
73
  )
72
74
  assert_equal false,
73
- @client.evaluate(feature, "hashes high", [], flag, variants)
75
+ evaluate(feature, "hashes high", [], flag, variants)
74
76
  assert_equal false,
75
- @client.evaluate(feature, "hashes low", [], flag, variants)
77
+ evaluate(feature, "hashes low", [], flag, variants)
76
78
  end
77
79
 
78
80
  def test_inclusion_rule
@@ -108,9 +110,9 @@ class TestFeatureFlagClient < Minitest::Test
108
110
  )
109
111
 
110
112
  assert_equal "rule target",
111
- @client.evaluate(feature, "user:1", [], flag, variants)
113
+ evaluate(feature, "user:1", [], flag, variants)
112
114
  assert_equal "default",
113
- @client.evaluate(feature, "user:2", [], flag, variants)
115
+ evaluate(feature, "user:2", [], flag, variants)
114
116
 
115
117
  end
116
118
 
@@ -148,13 +150,13 @@ class TestFeatureFlagClient < Minitest::Test
148
150
  )
149
151
 
150
152
  assert_equal "default",
151
- @client.evaluate(feature, "user:1", { email: "not@example.com" }, flag, variants)
153
+ evaluate(feature, "user:1", { email: "not@example.com" }, flag, variants)
152
154
  assert_equal "default",
153
- @client.evaluate(feature, "user:2", {}, flag, variants)
155
+ evaluate(feature, "user:2", {}, flag, variants)
154
156
  assert_equal "rule target",
155
- @client.evaluate(feature, "user:2", { email: "b@example.com" }, flag, variants)
157
+ evaluate(feature, "user:2", { email: "b@example.com" }, flag, variants)
156
158
  assert_equal "rule target",
157
- @client.evaluate(feature, "user:2", { "email" => "b@example.com" }, flag, variants)
159
+ evaluate(feature, "user:2", { "email" => "b@example.com" }, flag, variants)
158
160
 
159
161
  end
160
162
 
@@ -223,9 +225,9 @@ class TestFeatureFlagClient < Minitest::Test
223
225
  )
224
226
 
225
227
  assert_equal "rule target",
226
- @client.evaluate(feature, "user:1", [], flag, variants)
228
+ evaluate(feature, "user:1", [], flag, variants)
227
229
  assert_equal "default",
228
- @client.evaluate(feature, "user:2", [], flag, variants)
230
+ evaluate(feature, "user:2", [], flag, variants)
229
231
 
230
232
  end
231
233
 
@@ -285,16 +287,20 @@ class TestFeatureFlagClient < Minitest::Test
285
287
  )
286
288
 
287
289
  assert_equal "rule target",
288
- @client.evaluate(feature, "user:1", [], flag, variants)
290
+ evaluate(feature, "user:1", [], flag, variants)
289
291
  assert_equal "rule target",
290
- @client.evaluate(feature, "user:2", [], flag, variants), "matches segment 1"
292
+ evaluate(feature, "user:2", [], flag, variants), "matches segment 1"
291
293
  assert_equal "rule target",
292
- @client.evaluate(feature, "user:3", [], flag, variants)
294
+ evaluate(feature, "user:3", [], flag, variants)
293
295
  assert_equal "rule target",
294
- @client.evaluate(feature, "user:4", [], flag, variants)
296
+ evaluate(feature, "user:4", [], flag, variants)
295
297
  assert_equal "default",
296
- @client.evaluate(feature, "user:5", [], flag, variants)
298
+ evaluate(feature, "user:5", [], flag, variants)
297
299
 
298
300
  end
299
301
 
302
+ def evaluate(feature_name, lookup_key, attributes, flag, variants)
303
+ variant = @client.get_variant(feature_name, lookup_key, attributes, flag, variants)
304
+ @client.value_of_variant(variant)
305
+ end
300
306
  end
data/test/test_helper.rb CHANGED
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'minitest/autorun'
2
3
  require 'prefab-cloud-ruby'
3
4
 
@@ -35,8 +36,8 @@ class MockConfigClient
35
36
  def initialize(config_values = {})
36
37
  @config_values = config_values
37
38
  end
38
- def get(key)
39
- @config_values[key]
39
+ def get(key, default=nil)
40
+ @config_values.fetch(key, default)
40
41
  end
41
42
 
42
43
  def get_config(key)
@@ -67,3 +68,22 @@ def default_ff_rule(variant_idx)
67
68
  )
68
69
  ]
69
70
  end
71
+
72
+ def with_env(key, value)
73
+ old_value = ENV[key]
74
+
75
+ ENV[key] = value
76
+ ensure
77
+ ENV[key] = old_value
78
+ end
79
+
80
+ def new_client(overrides = {})
81
+ options = Prefab::Options.new(**{
82
+ prefab_config_override_dir: "none",
83
+ prefab_config_classpath_dir: "test",
84
+ defaults_env: "unit_tests",
85
+ prefab_datasources: Prefab::Options::DATASOURCES::LOCAL_ONLY
86
+ }.merge(overrides))
87
+
88
+ Prefab::Client.new(options)
89
+ end
data/test/test_logger.rb CHANGED
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'test_helper'
2
3
 
3
4
  class TestCLogger < Minitest::Test
@@ -18,24 +19,27 @@ class TestCLogger < Minitest::Test
18
19
  end
19
20
 
20
21
  def test_level_of
21
- assert_equal Logger::INFO,
22
- @logger.level_of("app.models.user"), "PREFAB_LOG_CLIENT_BOOTSTRAP_LOG_LEVEL is info"
23
-
24
- @logger.set_config_client(MockConfigClient.new({}))
25
- assert_equal Logger::WARN,
26
- @logger.level_of("app.models.user"), "default is warn"
27
-
28
- @logger.set_config_client(MockConfigClient.new("log_level.app" => "info"))
29
- assert_equal Logger::INFO,
30
- @logger.level_of("app.models.user")
31
-
32
- @logger.set_config_client(MockConfigClient.new("log_level.app" => "debug"))
33
- assert_equal Logger::DEBUG,
34
- @logger.level_of("app.models.user")
35
-
36
- @logger.set_config_client(MockConfigClient.new("log_level.app" => "debug",
37
- "log_level.app.models" => "warn"))
38
- assert_equal Logger::WARN,
39
- @logger.level_of("app.models.user")
22
+ with_env("PREFAB_LOG_CLIENT_BOOTSTRAP_LOG_LEVEL", "info") do
23
+ # env var overrides the default level
24
+ assert_equal Logger::INFO,
25
+ @logger.level_of("app.models.user"), "PREFAB_LOG_CLIENT_BOOTSTRAP_LOG_LEVEL is info"
26
+
27
+ @logger.set_config_client(MockConfigClient.new({}))
28
+ assert_equal Logger::WARN,
29
+ @logger.level_of("app.models.user"), "default is warn"
30
+
31
+ @logger.set_config_client(MockConfigClient.new("log_level.app" => "info"))
32
+ assert_equal Logger::INFO,
33
+ @logger.level_of("app.models.user")
34
+
35
+ @logger.set_config_client(MockConfigClient.new("log_level.app" => "debug"))
36
+ assert_equal Logger::DEBUG,
37
+ @logger.level_of("app.models.user")
38
+
39
+ @logger.set_config_client(MockConfigClient.new("log_level.app" => "debug",
40
+ "log_level.app.models" => "warn"))
41
+ assert_equal Logger::WARN,
42
+ @logger.level_of("app.models.user")
43
+ end
40
44
  end
41
45
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: prefab-cloud-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.12.0
4
+ version: 0.13.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeff Dwyer
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-07-21 00:00:00.000000000 Z
11
+ date: 2022-08-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby
@@ -114,20 +114,6 @@ dependencies:
114
114
  - - ">="
115
115
  - !ruby/object:Gem::Version
116
116
  version: '0'
117
- - !ruby/object:Gem::Dependency
118
- name: shoulda
119
- requirement: !ruby/object:Gem::Requirement
120
- requirements:
121
- - - ">="
122
- - !ruby/object:Gem::Version
123
- version: '0'
124
- type: :development
125
- prerelease: false
126
- version_requirements: !ruby/object:Gem::Requirement
127
- requirements:
128
- - - ">="
129
- - !ruby/object:Gem::Version
130
- version: '0'
131
117
  - !ruby/object:Gem::Dependency
132
118
  name: rdoc
133
119
  requirement: !ruby/object:Gem::Requirement
@@ -207,6 +193,7 @@ extra_rdoc_files:
207
193
  - README.md
208
194
  files:
209
195
  - ".envrc"
196
+ - ".github/workflows/ruby.yml"
210
197
  - ".tool-versions"
211
198
  - CODEOWNERS
212
199
  - Gemfile
@@ -224,6 +211,10 @@ files:
224
211
  - lib/prefab/config_helper.rb
225
212
  - lib/prefab/config_loader.rb
226
213
  - lib/prefab/config_resolver.rb
214
+ - lib/prefab/error.rb
215
+ - lib/prefab/errors/initialization_timeout_error.rb
216
+ - lib/prefab/errors/invalid_api_key_error.rb
217
+ - lib/prefab/errors/missing_default_error.rb
227
218
  - lib/prefab/feature_flag_client.rb
228
219
  - lib/prefab/internal_logger.rb
229
220
  - lib/prefab/logger_client.rb
@@ -231,7 +222,6 @@ files:
231
222
  - lib/prefab/noop_cache.rb
232
223
  - lib/prefab/noop_stats.rb
233
224
  - lib/prefab/options.rb
234
- - lib/prefab/prefab.rb
235
225
  - lib/prefab/ratelimit_client.rb
236
226
  - lib/prefab_pb.rb
237
227
  - lib/prefab_services_pb.rb
@@ -239,6 +229,7 @@ files:
239
229
  - run_test_harness_server.sh
240
230
  - test/.prefab.test.config.yaml
241
231
  - test/harness_server.rb
232
+ - test/test_client.rb
242
233
  - test/test_config_client.rb
243
234
  - test/test_config_loader.rb
244
235
  - test/test_config_resolver.rb
data/lib/prefab/prefab.rb DELETED
@@ -1,32 +0,0 @@
1
- module Prefab
2
-
3
- @@singleton__mutex__ = Mutex.new
4
-
5
- public
6
-
7
- def self.initialize(options = nil)
8
- @@singleton__mutex__.synchronize do
9
- unless @singleton.nil?
10
- puts 'Prefab Double Initialization'
11
- return @singleton
12
- end
13
- @singleton = Prefab::Client.new(options)
14
- end
15
- end
16
-
17
- def self.get(key)
18
- require_initialization
19
- @singleton.config_client.get(key)
20
- end
21
-
22
- private
23
-
24
- def self.require_initialization
25
- if @singleton.nil?
26
- puts "Prefab initializing for you. You probably want to call Prefab.initialize(Prefab::Options.new())"
27
- self.initialize
28
- end
29
- end
30
- module_function initialize
31
- module_function get
32
- end