prefab-cloud-ruby 0.23.7 → 0.24.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.
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Prefab
4
+ class ResolvedConfigPresenter
5
+ class ConfigRow
6
+ include Comparable
7
+
8
+ attr_reader :key, :value, :match, :source
9
+
10
+ def initialize(key, value, match, source)
11
+ @key = key
12
+ @value = value
13
+ @match = match
14
+ @source = source
15
+ end
16
+
17
+ def <=>(other)
18
+ inspect <=> other.inspect
19
+ end
20
+
21
+ def inspect
22
+ [@key, @value, @match, @source].inspect
23
+ end
24
+ end
25
+
26
+ def initialize(resolver, lock, local_store)
27
+ @resolver = resolver
28
+ @lock = lock
29
+ @local_store = local_store
30
+ end
31
+
32
+ def each(&block)
33
+ to_h.each(&block)
34
+ end
35
+
36
+ def to_h
37
+ hash = {}
38
+
39
+ Prefab::Context.with_context({}) do
40
+ @lock.with_read_lock do
41
+ @local_store.keys.sort.each do |k|
42
+ v = @local_store[k]
43
+
44
+ if v.nil?
45
+ hash[k] = ConfigRow.new(k, nil, nil, nil)
46
+ else
47
+ config = @resolver.evaluate(v[:config])
48
+ value = Prefab::ConfigValueUnwrapper.unwrap(config, k, {})
49
+ hash[k] = ConfigRow.new(k, value, v[:match], v[:source])
50
+ end
51
+ end
52
+ end
53
+ end
54
+
55
+ hash
56
+ end
57
+
58
+ def to_s
59
+ str = "\n"
60
+
61
+ Prefab::Context.with_context({}) do
62
+ @lock.with_read_lock do
63
+ @local_store.keys.sort.each do |k|
64
+ v = @local_store[k]
65
+ elements = [k.slice(0..49).ljust(50)]
66
+ if v.nil?
67
+ elements << 'tombstone'
68
+ else
69
+ config = @resolver.evaluate(v[:config], {})
70
+ value = Prefab::ConfigValueUnwrapper.unwrap(config, k, {})
71
+ elements << value.to_s.slice(0..34).ljust(35)
72
+ elements << value.class.to_s.slice(0..6).ljust(7)
73
+ elements << "Match: #{v[:match]}".slice(0..29).ljust(30)
74
+ elements << "Source: #{v[:source]}"
75
+ end
76
+ str += elements.join(' | ') << "\n"
77
+ end
78
+ end
79
+ end
80
+
81
+ str
82
+ end
83
+ end
84
+ end
@@ -4,14 +4,14 @@ module Prefab
4
4
  class WeightedValueResolver
5
5
  MAX_32_FLOAT = 4_294_967_294.0
6
6
 
7
- def initialize(weights, config_key, lookup_key)
7
+ def initialize(weights, config_key, context_hash_value)
8
8
  @weights = weights
9
9
  @config_key = config_key
10
- @lookup_key = lookup_key
10
+ @context_hash_value = context_hash_value
11
11
  end
12
12
 
13
13
  def resolve
14
- percent = @lookup_key ? user_percent : rand
14
+ percent = @context_hash_value ? user_percent : rand
15
15
 
16
16
  index = variant_index(percent)
17
17
 
@@ -19,7 +19,7 @@ module Prefab
19
19
  end
20
20
 
21
21
  def user_percent
22
- to_hash = "#{@config_key}#{@lookup_key}"
22
+ to_hash = "#{@config_key}#{@context_hash_value}"
23
23
  int_value = Murmur3.murmur3_32(to_hash)
24
24
  int_value / MAX_32_FLOAT
25
25
  end
@@ -1,5 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ module Prefab
4
+ NO_DEFAULT_PROVIDED = :no_default_provided
5
+ end
6
+
3
7
  require 'concurrent/atomics'
4
8
  require 'concurrent'
5
9
  require 'faraday'
@@ -21,8 +25,10 @@ require 'prefab/criteria_evaluator'
21
25
  require 'prefab/config_loader'
22
26
  require 'prefab/local_config_parser'
23
27
  require 'prefab/yaml_config_parser'
28
+ require 'prefab/resolved_config_presenter'
24
29
  require 'prefab/config_resolver'
25
30
  require 'prefab/http_connection'
31
+ require 'prefab/context'
26
32
  require 'prefab/client'
27
33
  require 'prefab/config_client'
28
34
  require 'prefab/feature_flag_client'
data/lib/prefab_pb.rb CHANGED
@@ -32,6 +32,7 @@ Google::Protobuf::DescriptorPool.generated_pool.build do
32
32
  end
33
33
  add_message "prefab.WeightedValues" do
34
34
  repeated :weighted_values, :message, 1, "prefab.WeightedValue"
35
+ proto3_optional :hash_by_property_name, :string, 2
35
36
  end
36
37
  add_message "prefab.Configs" do
37
38
  repeated :configs, :message, 1, "prefab.Config"
@@ -60,6 +61,42 @@ Google::Protobuf::DescriptorPool.generated_pool.build do
60
61
  repeated :criteria, :message, 1, "prefab.Criterion"
61
62
  optional :value, :message, 2, "prefab.ConfigValue"
62
63
  end
64
+ add_message "prefab.Criterion" do
65
+ optional :property_name, :string, 1
66
+ optional :operator, :enum, 2, "prefab.Criterion.CriterionOperator"
67
+ optional :value_to_match, :message, 3, "prefab.ConfigValue"
68
+ end
69
+ add_enum "prefab.Criterion.CriterionOperator" do
70
+ value :NOT_SET, 0
71
+ value :LOOKUP_KEY_IN, 1
72
+ value :LOOKUP_KEY_NOT_IN, 2
73
+ value :IN_SEG, 3
74
+ value :NOT_IN_SEG, 4
75
+ value :ALWAYS_TRUE, 5
76
+ value :PROP_IS_ONE_OF, 6
77
+ value :PROP_IS_NOT_ONE_OF, 7
78
+ value :PROP_ENDS_WITH_ONE_OF, 8
79
+ value :PROP_DOES_NOT_END_WITH_ONE_OF, 9
80
+ value :HIERARCHICAL_MATCH, 10
81
+ end
82
+ add_message "prefab.Loggers" do
83
+ repeated :loggers, :message, 1, "prefab.Logger"
84
+ optional :start_at, :int64, 2
85
+ optional :end_at, :int64, 3
86
+ optional :instance_hash, :string, 4
87
+ proto3_optional :namespace, :string, 5
88
+ end
89
+ add_message "prefab.Logger" do
90
+ optional :logger_name, :string, 1
91
+ proto3_optional :traces, :int64, 2
92
+ proto3_optional :debugs, :int64, 3
93
+ proto3_optional :infos, :int64, 4
94
+ proto3_optional :warns, :int64, 5
95
+ proto3_optional :errors, :int64, 6
96
+ proto3_optional :fatals, :int64, 7
97
+ end
98
+ add_message "prefab.LoggerReportResponse" do
99
+ end
63
100
  add_message "prefab.LimitResponse" do
64
101
  optional :passed, :bool, 1
65
102
  optional :expires_at, :int64, 2
@@ -95,24 +132,6 @@ Google::Protobuf::DescriptorPool.generated_pool.build do
95
132
  value :MINIMUM, 1
96
133
  value :MAXIMUM, 2
97
134
  end
98
- add_message "prefab.Criterion" do
99
- optional :property_name, :string, 1
100
- optional :operator, :enum, 2, "prefab.Criterion.CriterionOperator"
101
- optional :value_to_match, :message, 3, "prefab.ConfigValue"
102
- end
103
- add_enum "prefab.Criterion.CriterionOperator" do
104
- value :NOT_SET, 0
105
- value :LOOKUP_KEY_IN, 1
106
- value :LOOKUP_KEY_NOT_IN, 2
107
- value :IN_SEG, 3
108
- value :NOT_IN_SEG, 4
109
- value :ALWAYS_TRUE, 5
110
- value :PROP_IS_ONE_OF, 6
111
- value :PROP_IS_NOT_ONE_OF, 7
112
- value :PROP_ENDS_WITH_ONE_OF, 8
113
- value :PROP_DOES_NOT_END_WITH_ONE_OF, 9
114
- value :HIERARCHICAL_MATCH, 10
115
- end
116
135
  add_message "prefab.Identity" do
117
136
  proto3_optional :lookup, :string, 1
118
137
  map :attributes, :string, :string, 2
@@ -181,24 +200,6 @@ Google::Protobuf::DescriptorPool.generated_pool.build do
181
200
  optional :sequence_name, :string, 3
182
201
  optional :size, :int64, 4
183
202
  end
184
- add_message "prefab.Loggers" do
185
- repeated :loggers, :message, 1, "prefab.Logger"
186
- optional :start_at, :int64, 2
187
- optional :end_at, :int64, 3
188
- optional :instance_hash, :string, 4
189
- proto3_optional :namespace, :string, 5
190
- end
191
- add_message "prefab.Logger" do
192
- optional :logger_name, :string, 1
193
- proto3_optional :traces, :int64, 2
194
- proto3_optional :debugs, :int64, 3
195
- proto3_optional :infos, :int64, 4
196
- proto3_optional :warns, :int64, 5
197
- proto3_optional :errors, :int64, 6
198
- proto3_optional :fatals, :int64, 7
199
- end
200
- add_message "prefab.LoggerReportResponse" do
201
- end
202
203
  add_enum "prefab.ConfigType" do
203
204
  value :NOT_SET_CONFIG_TYPE, 0
204
205
  value :CONFIG, 1
@@ -236,12 +237,15 @@ module Prefab
236
237
  ChangedBy = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("prefab.ChangedBy").msgclass
237
238
  ConfigRow = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("prefab.ConfigRow").msgclass
238
239
  ConditionalValue = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("prefab.ConditionalValue").msgclass
240
+ Criterion = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("prefab.Criterion").msgclass
241
+ Criterion::CriterionOperator = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("prefab.Criterion.CriterionOperator").enummodule
242
+ Loggers = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("prefab.Loggers").msgclass
243
+ Logger = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("prefab.Logger").msgclass
244
+ LoggerReportResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("prefab.LoggerReportResponse").msgclass
239
245
  LimitResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("prefab.LimitResponse").msgclass
240
246
  LimitResponse::LimitPolicyNames = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("prefab.LimitResponse.LimitPolicyNames").enummodule
241
247
  LimitRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("prefab.LimitRequest").msgclass
242
248
  LimitRequest::LimitCombiner = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("prefab.LimitRequest.LimitCombiner").enummodule
243
- Criterion = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("prefab.Criterion").msgclass
244
- Criterion::CriterionOperator = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("prefab.Criterion.CriterionOperator").enummodule
245
249
  Identity = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("prefab.Identity").msgclass
246
250
  ClientConfigValue = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("prefab.ClientConfigValue").msgclass
247
251
  ConfigEvaluations = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("prefab.ConfigEvaluations").msgclass
@@ -254,9 +258,6 @@ module Prefab
254
258
  CreationResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("prefab.CreationResponse").msgclass
255
259
  IdBlock = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("prefab.IdBlock").msgclass
256
260
  IdBlockRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("prefab.IdBlockRequest").msgclass
257
- Loggers = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("prefab.Loggers").msgclass
258
- Logger = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("prefab.Logger").msgclass
259
- LoggerReportResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("prefab.LoggerReportResponse").msgclass
260
261
  ConfigType = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("prefab.ConfigType").enummodule
261
262
  LogLevel = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("prefab.LogLevel").enummodule
262
263
  OnFailure = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("prefab.OnFailure").enummodule
@@ -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.23.7 ruby lib
5
+ # stub: prefab-cloud-ruby 0.24.0 ruby lib
6
6
 
7
7
  Gem::Specification.new do |s|
8
8
  s.name = "prefab-cloud-ruby".freeze
9
- s.version = "0.23.7"
9
+ s.version = "0.24.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 = "2023-04-21"
14
+ s.date = "2023-04-26"
15
15
  s.description = "Feature Flags, Live Config, and Dynamic Log Levels as a service".freeze
16
16
  s.email = "jdwyer@prefab.cloud".freeze
17
17
  s.extra_rdoc_files = [
@@ -38,6 +38,7 @@ Gem::Specification.new do |s|
38
38
  "lib/prefab/config_loader.rb",
39
39
  "lib/prefab/config_resolver.rb",
40
40
  "lib/prefab/config_value_unwrapper.rb",
41
+ "lib/prefab/context.rb",
41
42
  "lib/prefab/criteria_evaluator.rb",
42
43
  "lib/prefab/error.rb",
43
44
  "lib/prefab/errors/initialization_timeout_error.rb",
@@ -54,11 +55,11 @@ Gem::Specification.new do |s|
54
55
  "lib/prefab/noop_cache.rb",
55
56
  "lib/prefab/noop_stats.rb",
56
57
  "lib/prefab/options.rb",
58
+ "lib/prefab/resolved_config_presenter.rb",
57
59
  "lib/prefab/sse_logger.rb",
58
60
  "lib/prefab/weighted_value_resolver.rb",
59
61
  "lib/prefab/yaml_config_parser.rb",
60
62
  "lib/prefab_pb.rb",
61
- "lib/prefab_services_pb.rb",
62
63
  "prefab-cloud-ruby.gemspec",
63
64
  "test/.prefab.default.config.yaml",
64
65
  "test/.prefab.unit_tests.config.yaml",
@@ -69,6 +70,7 @@ Gem::Specification.new do |s|
69
70
  "test/test_config_loader.rb",
70
71
  "test/test_config_resolver.rb",
71
72
  "test/test_config_value_unwrapper.rb",
73
+ "test/test_context.rb",
72
74
  "test/test_criteria_evaluator.rb",
73
75
  "test/test_exponential_backoff.rb",
74
76
  "test/test_feature_flag_client.rb",
@@ -9,8 +9,9 @@ sample: test sample value
9
9
  enabled_flag: true
10
10
  disabled_flag: false
11
11
  flag_with_a_value: { "feature_flag": "true", value: "all-features" }
12
- in_lookup_key: { "feature_flag": "true", value: true, criterion: { operator: LOOKUP_KEY_IN, values: [ "abc123", "xyz987" ] } }
13
- just_my_domain: { "feature_flag": "true", value: "new-version", criterion: { operator: PROP_IS_ONE_OF, property: "domain", values: [ "prefab.cloud", "example.com" ] } }
12
+ user_key_match: { "feature_flag": "true", value: true, criterion: { operator: PROP_IS_ONE_OF, values: [ "abc123", "xyz987" ], property: "user.key" } }
13
+ just_my_domain: { "feature_flag": "true", value: "new-version", criterion: { operator: PROP_IS_ONE_OF, property: "user.domain", values: [ "prefab.cloud", "example.com" ] } }
14
+ deprecated_no_dot_notation: { "feature_flag": "true", value: true, criterion: { operator: PROP_IS_ONE_OF, property: "domain", values: [ "prefab.cloud", "example.com" ] } }
14
15
 
15
16
  nested:
16
17
  values:
@@ -1,18 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class IntegrationTest
4
- attr_reader :func
5
- attr_reader :input
6
- attr_reader :expected
7
- attr_reader :test_client
4
+ attr_reader :func, :input, :expected, :test_client
8
5
 
9
6
  def initialize(test_data)
10
7
  @client_overrides = parse_client_overrides(test_data['client_overrides'])
11
8
  @func = parse_function(test_data['function'])
12
9
  @input = parse_input(test_data['input'])
13
10
  @expected = parse_expected(test_data['expected'])
14
- test_client = :"#{test_data['client']}"
15
- @test_client = base_client.send(test_client)
11
+ @test_client = base_client
16
12
  end
17
13
 
18
14
  def test_type
@@ -40,7 +36,7 @@ class IntegrationTest
40
36
  def parse_function(function)
41
37
  case function
42
38
  when 'get_or_raise' then :get
43
- when 'enabled' then :feature_is_on_for?
39
+ when 'enabled' then :enabled?
44
40
  else :"#{function}"
45
41
  end
46
42
  end
@@ -62,7 +58,7 @@ class IntegrationTest
62
58
  end
63
59
 
64
60
  def parse_ff_input(input)
65
- [input['flag'], input['lookup_key'], input['properties'] || {}]
61
+ [input['flag'], input['context']]
66
62
  end
67
63
 
68
64
  def parse_expected(expected)
data/test/test_client.rb CHANGED
@@ -44,32 +44,36 @@ class TestClient < Minitest::Test
44
44
  assert_equal false, @client.enabled?('flag_with_a_value')
45
45
  end
46
46
 
47
- def test_ff_enabled_with_lookup_key
48
- assert_equal false, @client.enabled?('in_lookup_key', 'jimmy')
49
- assert_equal true, @client.enabled?('in_lookup_key', 'abc123')
50
- assert_equal true, @client.enabled?('in_lookup_key', 'xyz987')
47
+ def test_ff_enabled_with_user_key_match
48
+ assert_equal_context_and_jit(false, :enabled?, 'user_key_match', { user: { key: 'jimmy' } })
49
+ assert_equal_context_and_jit(true, :enabled?, 'user_key_match', { user: { key: 'abc123' } })
50
+ assert_equal_context_and_jit(true, :enabled?, 'user_key_match', { user: { key: 'xyz987' } })
51
51
  end
52
52
 
53
- def test_ff_get_with_lookup_key
54
- assert_nil @client.get('in_lookup_key', 'jimmy')
55
- assert_equal 'DEFAULT', @client.get('in_lookup_key', 'jimmy', {}, 'DEFAULT')
56
-
57
- assert_equal true, @client.get('in_lookup_key', 'abc123')
58
- assert_equal true, @client.get('in_lookup_key', 'xyz987')
53
+ def test_ff_enabled_with_context
54
+ assert_equal_context_and_jit(false, :enabled?, 'just_my_domain', user: { domain: 'gmail.com' })
55
+ assert_equal_context_and_jit(false, :enabled?, 'just_my_domain', user: { domain: 'prefab.cloud' })
56
+ assert_equal_context_and_jit(false, :enabled?, 'just_my_domain', user: { domain: 'example.com' })
59
57
  end
60
58
 
61
- def test_ff_enabled_with_attributes
62
- assert_equal false, @client.enabled?('just_my_domain', 'abc123', { domain: 'gmail.com' })
63
- assert_equal false, @client.enabled?('just_my_domain', 'abc123', { domain: 'prefab.cloud' })
64
- assert_equal false, @client.enabled?('just_my_domain', 'abc123', { domain: 'example.com' })
59
+ def test_ff_get_with_context
60
+ assert_nil @client.get('just_my_domain', 'abc123', user: { domain: 'gmail.com' })
61
+ assert_equal 'DEFAULT', @client.get('just_my_domain', 'abc123', { user: { domain: 'gmail.com' } }, 'DEFAULT')
62
+
63
+ assert_equal_context_and_jit('new-version', :get, 'just_my_domain', { user: { domain: 'prefab.cloud' } })
64
+ assert_equal_context_and_jit('new-version', :get, 'just_my_domain', { user: { domain: 'example.com' } })
65
65
  end
66
66
 
67
- def test_ff_get_with_attributes
68
- assert_nil @client.get('just_my_domain', 'abc123', { domain: 'gmail.com' })
69
- assert_equal 'DEFAULT', @client.get('just_my_domain', 'abc123', { domain: 'gmail.com' }, 'DEFAULT')
67
+ def test_deprecated_no_dot_notation_ff_enabled_with_jit_context
68
+ # with no lookup key
69
+ assert_equal false, @client.enabled?('deprecated_no_dot_notation', { domain: 'gmail.com' })
70
+ assert_equal true, @client.enabled?('deprecated_no_dot_notation', { domain: 'prefab.cloud' })
71
+ assert_equal true, @client.enabled?('deprecated_no_dot_notation', { domain: 'example.com' })
70
72
 
71
- assert_equal 'new-version', @client.get('just_my_domain', 'abc123', { domain: 'prefab.cloud' })
72
- assert_equal 'new-version', @client.get('just_my_domain', 'abc123', { domain: 'example.com' })
73
+ # with a lookup key
74
+ assert_equal false, @client.enabled?('deprecated_no_dot_notation', 'some-lookup-key', { domain: 'gmail.com' })
75
+ assert_equal true, @client.enabled?('deprecated_no_dot_notation', 'some-lookup-key', { domain: 'prefab.cloud' })
76
+ assert_equal true, @client.enabled?('deprecated_no_dot_notation', 'some-lookup-key', { domain: 'example.com' })
73
77
  end
74
78
 
75
79
  def test_getting_feature_flag_value
@@ -103,14 +107,11 @@ class TestClient < Minitest::Test
103
107
 
104
108
  private
105
109
 
106
- def new_client(overrides = {})
107
- options = Prefab::Options.new(**{
108
- prefab_config_override_dir: 'none',
109
- prefab_config_classpath_dir: 'test',
110
- prefab_envs: ['unit_tests'],
111
- prefab_datasources: Prefab::Options::DATASOURCES::LOCAL_ONLY
112
- }.merge(overrides))
110
+ def assert_equal_context_and_jit(expected, method, key, context)
111
+ assert_equal expected, @client.send(method, key, context)
113
112
 
114
- Prefab::Client.new(options)
113
+ Prefab::Context.with_context(context) do
114
+ assert_equal expected, @client.send(method, key)
115
+ end
115
116
  end
116
117
  end