prefab-cloud-ruby 0.23.7 → 0.24.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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