prefab-cloud-ruby 0.12.0 → 0.13.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.
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