launchdarkly-server-sdk 5.8.1 → 6.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. checksums.yaml +5 -5
  2. data/.circleci/config.yml +28 -122
  3. data/.github/ISSUE_TEMPLATE/bug_report.md +1 -1
  4. data/.github/ISSUE_TEMPLATE/config.yml +5 -0
  5. data/.gitignore +2 -1
  6. data/.ldrelease/build-docs.sh +18 -0
  7. data/.ldrelease/circleci/linux/execute.sh +18 -0
  8. data/.ldrelease/circleci/mac/execute.sh +18 -0
  9. data/.ldrelease/circleci/template/build.sh +29 -0
  10. data/.ldrelease/circleci/template/publish.sh +23 -0
  11. data/.ldrelease/circleci/template/set-gem-home.sh +7 -0
  12. data/.ldrelease/circleci/template/test.sh +10 -0
  13. data/.ldrelease/circleci/template/update-version.sh +8 -0
  14. data/.ldrelease/circleci/windows/execute.ps1 +19 -0
  15. data/.ldrelease/config.yml +14 -2
  16. data/CHANGELOG.md +29 -0
  17. data/CONTRIBUTING.md +1 -1
  18. data/README.md +4 -3
  19. data/azure-pipelines.yml +1 -1
  20. data/docs/Makefile +26 -0
  21. data/docs/index.md +9 -0
  22. data/launchdarkly-server-sdk.gemspec +16 -16
  23. data/lib/ldclient-rb.rb +0 -1
  24. data/lib/ldclient-rb/config.rb +15 -3
  25. data/lib/ldclient-rb/evaluation_detail.rb +324 -0
  26. data/lib/ldclient-rb/events.rb +6 -7
  27. data/lib/ldclient-rb/file_data_source.rb +1 -1
  28. data/lib/ldclient-rb/impl/evaluator.rb +231 -0
  29. data/lib/ldclient-rb/impl/evaluator_bucketing.rb +87 -0
  30. data/lib/ldclient-rb/impl/evaluator_operators.rb +160 -0
  31. data/lib/ldclient-rb/impl/event_factory.rb +28 -0
  32. data/lib/ldclient-rb/impl/event_sender.rb +56 -40
  33. data/lib/ldclient-rb/impl/integrations/consul_impl.rb +5 -5
  34. data/lib/ldclient-rb/impl/integrations/dynamodb_impl.rb +5 -5
  35. data/lib/ldclient-rb/impl/integrations/redis_impl.rb +5 -7
  36. data/lib/ldclient-rb/impl/model/serialization.rb +62 -0
  37. data/lib/ldclient-rb/impl/unbounded_pool.rb +34 -0
  38. data/lib/ldclient-rb/ldclient.rb +36 -15
  39. data/lib/ldclient-rb/polling.rb +1 -4
  40. data/lib/ldclient-rb/requestor.rb +25 -15
  41. data/lib/ldclient-rb/stream.rb +9 -6
  42. data/lib/ldclient-rb/util.rb +12 -8
  43. data/lib/ldclient-rb/version.rb +1 -1
  44. data/spec/evaluation_detail_spec.rb +135 -0
  45. data/spec/event_sender_spec.rb +20 -2
  46. data/spec/events_spec.rb +10 -0
  47. data/spec/http_util.rb +11 -1
  48. data/spec/impl/evaluator_bucketing_spec.rb +216 -0
  49. data/spec/impl/evaluator_clause_spec.rb +55 -0
  50. data/spec/impl/evaluator_operators_spec.rb +141 -0
  51. data/spec/impl/evaluator_rule_spec.rb +128 -0
  52. data/spec/impl/evaluator_segment_spec.rb +125 -0
  53. data/spec/impl/evaluator_spec.rb +349 -0
  54. data/spec/impl/evaluator_spec_base.rb +75 -0
  55. data/spec/impl/event_factory_spec.rb +108 -0
  56. data/spec/impl/model/serialization_spec.rb +41 -0
  57. data/spec/launchdarkly-server-sdk_spec.rb +1 -1
  58. data/spec/ldclient_end_to_end_spec.rb +34 -0
  59. data/spec/ldclient_spec.rb +64 -12
  60. data/spec/polling_spec.rb +2 -2
  61. data/spec/redis_feature_store_spec.rb +2 -2
  62. data/spec/requestor_spec.rb +11 -11
  63. metadata +92 -48
  64. data/.yardopts +0 -9
  65. data/Gemfile.lock +0 -89
  66. data/lib/ldclient-rb/evaluation.rb +0 -462
  67. data/scripts/gendocs.sh +0 -11
  68. data/scripts/release.sh +0 -27
  69. data/spec/evaluation_spec.rb +0 -789
data/README.md CHANGED
@@ -5,6 +5,8 @@ LaunchDarkly Server-side SDK for Ruby
5
5
 
6
6
  [![Circle CI](https://circleci.com/gh/launchdarkly/ruby-server-sdk/tree/master.svg?style=svg)](https://circleci.com/gh/launchdarkly/ruby-server-sdk/tree/master)
7
7
  [![Security](https://hakiri.io/github/launchdarkly/ruby-server-sdk/master.svg)](https://hakiri.io/github/launchdarkly/ruby-server-sdk/master)
8
+ [![RubyDoc](https://img.shields.io/static/v1?label=docs+-+all+versions&message=reference&color=00add8)](https://www.rubydoc.info/gems/launchdarkly-server-sdk)
9
+ [![GitHub Pages](https://img.shields.io/static/v1?label=docs+-+latest&message=reference&color=00add8)](https://launchdarkly.github.io/ruby-server-sdk)
8
10
 
9
11
  LaunchDarkly overview
10
12
  -------------------------
@@ -15,7 +17,7 @@ LaunchDarkly overview
15
17
  Supported Ruby versions
16
18
  -----------------------
17
19
 
18
- This version of the LaunchDarkly SDK has a minimum Ruby version of 2.2.6, or 9.1.6 for JRuby.
20
+ This version of the LaunchDarkly SDK has a minimum Ruby version of 2.5.0, or 9.2.0 for JRuby.
19
21
 
20
22
  Getting started
21
23
  -----------
@@ -27,7 +29,7 @@ Learn more
27
29
 
28
30
  Check out our [documentation](http://docs.launchdarkly.com) for in-depth instructions on configuring and using LaunchDarkly. You can also head straight to the [reference guide for this SDK](http://docs.launchdarkly.com/docs/ruby-sdk-reference).
29
31
 
30
- Generated API documentation is on [RubyDoc.info](https://www.rubydoc.info/gems/launchdarkly-server-sdk).
32
+ Generated API documentation for all versions of the SDK is on [RubyDoc.info](https://www.rubydoc.info/gems/launchdarkly-server-sdk). The API documentation for the latest version is also on [GitHub Pages](https://launchdarkly.github.io/ruby-server-sdk).
31
33
 
32
34
  Testing
33
35
  -------
@@ -53,4 +55,3 @@ About LaunchDarkly
53
55
  * [docs.launchdarkly.com](https://docs.launchdarkly.com/ "LaunchDarkly Documentation") for our documentation and SDK reference guides
54
56
  * [apidocs.launchdarkly.com](https://apidocs.launchdarkly.com/ "LaunchDarkly API Documentation") for our API documentation
55
57
  * [blog.launchdarkly.com](https://blog.launchdarkly.com/ "LaunchDarkly Blog Documentation") for the latest product updates
56
- * [Feature Flagging Guide](https://github.com/launchdarkly/featureflags/ "Feature Flagging Guide") for best practices and strategies
data/azure-pipelines.yml CHANGED
@@ -45,7 +45,7 @@ jobs:
45
45
  workingDirectory: $(System.DefaultWorkingDirectory)
46
46
  script: |
47
47
  ruby -v
48
- gem install bundler -v 1.17.3
48
+ gem install bundler
49
49
  bundle install
50
50
  mkdir rspec
51
51
  bundle exec rspec --format progress --format RspecJunitFormatter -o ./rspec/rspec.xml spec
data/docs/Makefile ADDED
@@ -0,0 +1,26 @@
1
+
2
+ ifeq ($(LD_RELEASE_VERSION),)
3
+ TITLE=LaunchDarkly Ruby SDK
4
+ else
5
+ TITLE=LaunchDarkly Ruby SDK ($(LD_RELEASE_VERSION))
6
+ endif
7
+
8
+ .PHONY: dependencies html
9
+
10
+ html: dependencies
11
+ rm -rf ./build
12
+ cd .. && yard doc \
13
+ -o docs/build/html \
14
+ --title "$(TITLE)" \
15
+ --no-private \
16
+ --markup markdown \
17
+ --embed-mixins \
18
+ -r docs/index.md \
19
+ lib/*.rb \
20
+ lib/**/*.rb \
21
+ lib/**/**/*.rb \
22
+ lib/**/**/**/*.rb
23
+
24
+ dependencies:
25
+ gem install --conservative yard
26
+ gem install --conservative redcarpet # provides Markdown formatting
data/docs/index.md ADDED
@@ -0,0 +1,9 @@
1
+ # LaunchDarkly Server-side SDK for Ruby
2
+
3
+ This generated API documentation lists all types and methods in the SDK.
4
+
5
+ The API documentation for the most recent SDK release is hosted on [GitHub Pages](https://launchdarkly.github.io/ruby-server-sdk). API documentation for current and past releases is hosted on [RubyDoc.info](https://www.rubydoc.info/gems/launchdarkly-server-sdk).
6
+
7
+ Source code and readme: [GitHub](https://github.com/launchdarkly/ruby-server-sdk)
8
+
9
+ SDK reference guide: [docs.launchdarkly.com](https://docs.launchdarkly.com/sdk/server-side/ruby)
@@ -19,27 +19,27 @@ Gem::Specification.new do |spec|
19
19
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
20
20
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
21
21
  spec.require_paths = ["lib"]
22
+ spec.required_ruby_version = ">= 2.5.0"
22
23
 
23
- spec.add_development_dependency "aws-sdk-dynamodb", "~> 1.18"
24
- spec.add_development_dependency "bundler", "~> 1.17"
25
- spec.add_development_dependency "rspec", "~> 3.2"
26
- spec.add_development_dependency "diplomat", ">= 2.0.2"
27
- spec.add_development_dependency "redis", "~> 3.3.5"
28
- spec.add_development_dependency "connection_pool", ">= 2.1.2"
29
- spec.add_development_dependency "rspec_junit_formatter", "~> 0.3.0"
30
- spec.add_development_dependency "timecop", "~> 0.9.1"
31
- spec.add_development_dependency "listen", "~> 3.0" # see file_data_source.rb
32
- # these are transitive dependencies of listen and consul respectively
33
- # we constrain them here to make sure the ruby 2.2, 2.3, and 2.4 CI
34
- # cases all pass
35
- spec.add_development_dependency "ffi", "<= 1.12" # >1.12 doesnt support ruby 2.2
36
- spec.add_development_dependency "faraday", "~> 0.17" # >=0.18 doesnt support ruby 2.2
24
+ spec.add_development_dependency "aws-sdk-dynamodb", "~> 1.57"
25
+ spec.add_development_dependency "bundler", "~> 2.1"
26
+ spec.add_development_dependency "rspec", "~> 3.10"
27
+ spec.add_development_dependency "diplomat", "~> 2.4.2"
28
+ spec.add_development_dependency "redis", "~> 4.2"
29
+ spec.add_development_dependency "connection_pool", "~> 2.2.3"
30
+ spec.add_development_dependency "rspec_junit_formatter", "~> 0.4"
31
+ spec.add_development_dependency "timecop", "~> 0.9"
32
+ spec.add_development_dependency "listen", "~> 3.3" # see file_data_source.rb
33
+ spec.add_development_dependency "webrick", "~> 1.7"
34
+ # required by dynamodb
35
+ spec.add_development_dependency "oga", "~> 2.2"
37
36
 
38
37
  spec.add_runtime_dependency "semantic", "~> 1.6"
39
- spec.add_runtime_dependency "concurrent-ruby", "~> 1.0"
40
- spec.add_runtime_dependency "ld-eventsource", "1.0.3"
38
+ spec.add_runtime_dependency "concurrent-ruby", "~> 1.1"
39
+ spec.add_runtime_dependency "ld-eventsource", "~> 2.0"
41
40
 
42
41
  # lock json to 2.3.x as ruby libraries often remove
43
42
  # support for older ruby versions in minor releases
44
43
  spec.add_runtime_dependency "json", "~> 2.3.1"
44
+ spec.add_runtime_dependency "http", "~> 4.4.1"
45
45
  end
data/lib/ldclient-rb.rb CHANGED
@@ -8,7 +8,6 @@ end
8
8
  require "ldclient-rb/version"
9
9
  require "ldclient-rb/interfaces"
10
10
  require "ldclient-rb/util"
11
- require "ldclient-rb/evaluation"
12
11
  require "ldclient-rb/flags_state"
13
12
  require "ldclient-rb/ldclient"
14
13
  require "ldclient-rb/cache_store"
@@ -15,7 +15,7 @@ module LaunchDarkly
15
15
  #
16
16
  # @param opts [Hash] the configuration options
17
17
  # @option opts [Logger] :logger See {#logger}.
18
- # @option opts [String] :base_uri ("https://app.launchdarkly.com") See {#base_uri}.
18
+ # @option opts [String] :base_uri ("https://sdk.launchdarkly.com") See {#base_uri}.
19
19
  # @option opts [String] :stream_uri ("https://stream.launchdarkly.com") See {#stream_uri}.
20
20
  # @option opts [String] :events_uri ("https://events.launchdarkly.com") See {#events_uri}.
21
21
  # @option opts [Integer] :capacity (10000) See {#capacity}.
@@ -41,6 +41,7 @@ module LaunchDarkly
41
41
  # @option opts [Float] :diagnostic_recording_interval (900) See {#diagnostic_recording_interval}.
42
42
  # @option opts [String] :wrapper_name See {#wrapper_name}.
43
43
  # @option opts [String] :wrapper_version See {#wrapper_version}.
44
+ # @option opts [#open] :socket_factory See {#socket_factory}.
44
45
  #
45
46
  def initialize(opts = {})
46
47
  @base_uri = (opts[:base_uri] || Config.default_base_uri).chomp("/")
@@ -71,6 +72,7 @@ module LaunchDarkly
71
72
  opts[:diagnostic_recording_interval] : Config.default_diagnostic_recording_interval
72
73
  @wrapper_name = opts[:wrapper_name]
73
74
  @wrapper_version = opts[:wrapper_version]
75
+ @socket_factory = opts[:socket_factory]
74
76
  end
75
77
 
76
78
  #
@@ -305,6 +307,16 @@ module LaunchDarkly
305
307
  #
306
308
  attr_reader :wrapper_version
307
309
 
310
+ #
311
+ # The factory used to construct sockets for HTTP operations. The factory must
312
+ # provide the method `open(uri, timeout)`. The `open` method must return a
313
+ # connected stream that implements the `IO` class, such as a `TCPSocket`.
314
+ #
315
+ # Defaults to nil.
316
+ # @return [#open]
317
+ #
318
+ attr_reader :socket_factory
319
+
308
320
  #
309
321
  # The default LaunchDarkly client configuration. This configuration sets
310
322
  # reasonable defaults for most users.
@@ -324,10 +336,10 @@ module LaunchDarkly
324
336
 
325
337
  #
326
338
  # The default value for {#base_uri}.
327
- # @return [String] "https://app.launchdarkly.com"
339
+ # @return [String] "https://sdk.launchdarkly.com"
328
340
  #
329
341
  def self.default_base_uri
330
- "https://app.launchdarkly.com"
342
+ "https://sdk.launchdarkly.com"
331
343
  end
332
344
 
333
345
  #
@@ -0,0 +1,324 @@
1
+
2
+ module LaunchDarkly
3
+ # An object returned by {LDClient#variation_detail}, combining the result of a flag evaluation with
4
+ # an explanation of how it was calculated.
5
+ class EvaluationDetail
6
+ # Creates a new instance.
7
+ #
8
+ # @param value the result value of the flag evaluation; may be of any type
9
+ # @param variation_index [int|nil] the index of the value within the flag's list of variations, or
10
+ # `nil` if the application default value was returned
11
+ # @param reason [EvaluationReason] an object describing the main factor that influenced the result
12
+ # @raise [ArgumentError] if `variation_index` or `reason` is not of the correct type
13
+ def initialize(value, variation_index, reason)
14
+ raise ArgumentError.new("variation_index must be a number") if !variation_index.nil? && !(variation_index.is_a? Numeric)
15
+ raise ArgumentError.new("reason must be an EvaluationReason") if !(reason.is_a? EvaluationReason)
16
+ @value = value
17
+ @variation_index = variation_index
18
+ @reason = reason
19
+ end
20
+
21
+ #
22
+ # The result of the flag evaluation. This will be either one of the flag's variations, or the
23
+ # default value that was passed to {LDClient#variation_detail}. It is the same as the return
24
+ # value of {LDClient#variation}.
25
+ #
26
+ # @return [Object]
27
+ #
28
+ attr_reader :value
29
+
30
+ #
31
+ # The index of the returned value within the flag's list of variations. The first variation is
32
+ # 0, the second is 1, etc. This is `nil` if the default value was returned.
33
+ #
34
+ # @return [int|nil]
35
+ #
36
+ attr_reader :variation_index
37
+
38
+ #
39
+ # An object describing the main factor that influenced the flag evaluation value.
40
+ #
41
+ # @return [EvaluationReason]
42
+ #
43
+ attr_reader :reason
44
+
45
+ #
46
+ # Tests whether the flag evaluation returned a default value. This is the same as checking
47
+ # whether {#variation_index} is nil.
48
+ #
49
+ # @return [Boolean]
50
+ #
51
+ def default_value?
52
+ variation_index.nil?
53
+ end
54
+
55
+ def ==(other)
56
+ @value == other.value && @variation_index == other.variation_index && @reason == other.reason
57
+ end
58
+ end
59
+
60
+ # Describes the reason that a flag evaluation produced a particular value. This is returned by
61
+ # methods such as {LDClient#variation_detail} as the `reason` property of an {EvaluationDetail}.
62
+ #
63
+ # The `kind` property is always defined, but other properties will have non-nil values only for
64
+ # certain values of `kind`. All properties are immutable.
65
+ #
66
+ # There is a standard JSON representation of evaluation reasons when they appear in analytics events.
67
+ # Use `as_json` or `to_json` to convert to this representation.
68
+ #
69
+ # Use factory methods such as {EvaluationReason#off} to obtain instances of this class.
70
+ class EvaluationReason
71
+ # Value for {#kind} indicating that the flag was off and therefore returned its configured off value.
72
+ OFF = :OFF
73
+
74
+ # Value for {#kind} indicating that the flag was on but the user did not match any targets or rules.
75
+ FALLTHROUGH = :FALLTHROUGH
76
+
77
+ # Value for {#kind} indicating that the user key was specifically targeted for this flag.
78
+ TARGET_MATCH = :TARGET_MATCH
79
+
80
+ # Value for {#kind} indicating that the user matched one of the flag's rules.
81
+ RULE_MATCH = :RULE_MATCH
82
+
83
+ # Value for {#kind} indicating that the flag was considered off because it had at least one
84
+ # prerequisite flag that either was off or did not return the desired variation.
85
+ PREREQUISITE_FAILED = :PREREQUISITE_FAILED
86
+
87
+ # Value for {#kind} indicating that the flag could not be evaluated, e.g. because it does not exist
88
+ # or due to an unexpected error. In this case the result value will be the application default value
89
+ # that the caller passed to the client. Check {#error_kind} for more details on the problem.
90
+ ERROR = :ERROR
91
+
92
+ # Value for {#error_kind} indicating that the caller tried to evaluate a flag before the client had
93
+ # successfully initialized.
94
+ ERROR_CLIENT_NOT_READY = :CLIENT_NOT_READY
95
+
96
+ # Value for {#error_kind} indicating that the caller provided a flag key that did not match any known flag.
97
+ ERROR_FLAG_NOT_FOUND = :FLAG_NOT_FOUND
98
+
99
+ # Value for {#error_kind} indicating that there was an internal inconsistency in the flag data, e.g.
100
+ # a rule specified a nonexistent variation. An error message will always be logged in this case.
101
+ ERROR_MALFORMED_FLAG = :MALFORMED_FLAG
102
+
103
+ # Value for {#error_kind} indicating that the caller passed `nil` for the user parameter, or the
104
+ # user lacked a key.
105
+ ERROR_USER_NOT_SPECIFIED = :USER_NOT_SPECIFIED
106
+
107
+ # Value for {#error_kind} indicating that an unexpected exception stopped flag evaluation. An error
108
+ # message will always be logged in this case.
109
+ ERROR_EXCEPTION = :EXCEPTION
110
+
111
+ # Indicates the general category of the reason. Will always be one of the class constants such
112
+ # as {#OFF}.
113
+ attr_reader :kind
114
+
115
+ # The index of the rule that was matched (0 for the first rule in the feature flag). If
116
+ # {#kind} is not {#RULE_MATCH}, this will be `nil`.
117
+ attr_reader :rule_index
118
+
119
+ # A unique string identifier for the matched rule, which will not change if other rules are added
120
+ # or deleted. If {#kind} is not {#RULE_MATCH}, this will be `nil`.
121
+ attr_reader :rule_id
122
+
123
+ # A boolean or nil value representing if the rule or fallthrough has an experiment rollout.
124
+ attr_reader :in_experiment
125
+
126
+ # The key of the prerequisite flag that did not return the desired variation. If {#kind} is not
127
+ # {#PREREQUISITE_FAILED}, this will be `nil`.
128
+ attr_reader :prerequisite_key
129
+
130
+ # A value indicating the general category of error. This should be one of the class constants such
131
+ # as {#ERROR_FLAG_NOT_FOUND}. If {#kind} is not {#ERROR}, it will be `nil`.
132
+ attr_reader :error_kind
133
+
134
+ # Returns an instance whose {#kind} is {#OFF}.
135
+ # @return [EvaluationReason]
136
+ def self.off
137
+ @@off
138
+ end
139
+
140
+ # Returns an instance whose {#kind} is {#FALLTHROUGH}.
141
+ # @return [EvaluationReason]
142
+ def self.fallthrough(in_experiment=false)
143
+ if in_experiment
144
+ @@fallthrough_with_experiment
145
+ else
146
+ @@fallthrough
147
+ end
148
+ end
149
+
150
+ # Returns an instance whose {#kind} is {#TARGET_MATCH}.
151
+ # @return [EvaluationReason]
152
+ def self.target_match
153
+ @@target_match
154
+ end
155
+
156
+ # Returns an instance whose {#kind} is {#RULE_MATCH}.
157
+ #
158
+ # @param rule_index [Number] the index of the rule that was matched (0 for the first rule in
159
+ # the feature flag)
160
+ # @param rule_id [String] unique string identifier for the matched rule
161
+ # @return [EvaluationReason]
162
+ # @raise [ArgumentError] if `rule_index` is not a number or `rule_id` is not a string
163
+ def self.rule_match(rule_index, rule_id, in_experiment=false)
164
+ raise ArgumentError.new("rule_index must be a number") if !(rule_index.is_a? Numeric)
165
+ raise ArgumentError.new("rule_id must be a string") if !rule_id.nil? && !(rule_id.is_a? String) # in test data, ID could be nil
166
+
167
+ if in_experiment
168
+ er = new(:RULE_MATCH, rule_index, rule_id, nil, nil, true)
169
+ else
170
+ er = new(:RULE_MATCH, rule_index, rule_id, nil, nil)
171
+ end
172
+ er
173
+ end
174
+
175
+ # Returns an instance whose {#kind} is {#PREREQUISITE_FAILED}.
176
+ #
177
+ # @param prerequisite_key [String] key of the prerequisite flag that did not return the desired variation
178
+ # @return [EvaluationReason]
179
+ # @raise [ArgumentError] if `prerequisite_key` is nil or not a string
180
+ def self.prerequisite_failed(prerequisite_key)
181
+ raise ArgumentError.new("prerequisite_key must be a string") if !(prerequisite_key.is_a? String)
182
+ new(:PREREQUISITE_FAILED, nil, nil, prerequisite_key, nil)
183
+ end
184
+
185
+ # Returns an instance whose {#kind} is {#ERROR}.
186
+ #
187
+ # @param error_kind [Symbol] value indicating the general category of error
188
+ # @return [EvaluationReason]
189
+ # @raise [ArgumentError] if `error_kind` is not a symbol
190
+ def self.error(error_kind)
191
+ raise ArgumentError.new("error_kind must be a symbol") if !(error_kind.is_a? Symbol)
192
+ e = @@error_instances[error_kind]
193
+ e.nil? ? make_error(error_kind) : e
194
+ end
195
+
196
+ def ==(other)
197
+ if other.is_a? EvaluationReason
198
+ @kind == other.kind && @rule_index == other.rule_index && @rule_id == other.rule_id &&
199
+ @prerequisite_key == other.prerequisite_key && @error_kind == other.error_kind
200
+ elsif other.is_a? Hash
201
+ @kind.to_s == other[:kind] && @rule_index == other[:ruleIndex] && @rule_id == other[:ruleId] &&
202
+ @prerequisite_key == other[:prerequisiteKey] &&
203
+ (other[:errorKind] == @error_kind.nil? ? nil : @error_kind.to_s)
204
+ end
205
+ end
206
+
207
+ # Equivalent to {#inspect}.
208
+ # @return [String]
209
+ def to_s
210
+ inspect
211
+ end
212
+
213
+ # Returns a concise string representation of the reason. Examples: `"FALLTHROUGH"`,
214
+ # `"ERROR(FLAG_NOT_FOUND)"`. The exact syntax is not guaranteed to remain the same; this is meant
215
+ # for debugging.
216
+ # @return [String]
217
+ def inspect
218
+ case @kind
219
+ when :RULE_MATCH
220
+ if @in_experiment
221
+ "RULE_MATCH(#{@rule_index},#{@rule_id},#{@in_experiment})"
222
+ else
223
+ "RULE_MATCH(#{@rule_index},#{@rule_id})"
224
+ end
225
+ when :PREREQUISITE_FAILED
226
+ "PREREQUISITE_FAILED(#{@prerequisite_key})"
227
+ when :ERROR
228
+ "ERROR(#{@error_kind})"
229
+ when :FALLTHROUGH
230
+ @in_experiment ? "FALLTHROUGH(#{@in_experiment})" : @kind.to_s
231
+ else
232
+ @kind.to_s
233
+ end
234
+ end
235
+
236
+ # Returns a hash that can be used as a JSON representation of the reason, in the format used
237
+ # in LaunchDarkly analytics events.
238
+ # @return [Hash]
239
+ def as_json(*) # parameter is unused, but may be passed if we're using the json gem
240
+ # Note that this implementation is somewhat inefficient; it allocates a new hash every time.
241
+ # However, in normal usage the SDK only serializes reasons if 1. full event tracking is
242
+ # enabled for a flag and the application called variation_detail, or 2. experimentation is
243
+ # enabled for an evaluation. We can't reuse these hashes because an application could call
244
+ # as_json and then modify the result.
245
+ case @kind
246
+ when :RULE_MATCH
247
+ if @in_experiment
248
+ { kind: @kind, ruleIndex: @rule_index, ruleId: @rule_id, inExperiment: @in_experiment }
249
+ else
250
+ { kind: @kind, ruleIndex: @rule_index, ruleId: @rule_id }
251
+ end
252
+ when :PREREQUISITE_FAILED
253
+ { kind: @kind, prerequisiteKey: @prerequisite_key }
254
+ when :ERROR
255
+ { kind: @kind, errorKind: @error_kind }
256
+ when :FALLTHROUGH
257
+ if @in_experiment
258
+ { kind: @kind, inExperiment: @in_experiment }
259
+ else
260
+ { kind: @kind }
261
+ end
262
+ else
263
+ { kind: @kind }
264
+ end
265
+ end
266
+
267
+ # Same as {#as_json}, but converts the JSON structure into a string.
268
+ # @return [String]
269
+ def to_json(*a)
270
+ as_json.to_json(a)
271
+ end
272
+
273
+ # Allows this object to be treated as a hash corresponding to its JSON representation. For
274
+ # instance, if `reason.kind` is {#RULE_MATCH}, then `reason[:kind]` will be `"RULE_MATCH"` and
275
+ # `reason[:ruleIndex]` will be equal to `reason.rule_index`.
276
+ def [](key)
277
+ case key
278
+ when :kind
279
+ @kind.to_s
280
+ when :ruleIndex
281
+ @rule_index
282
+ when :ruleId
283
+ @rule_id
284
+ when :prerequisiteKey
285
+ @prerequisite_key
286
+ when :errorKind
287
+ @error_kind.nil? ? nil : @error_kind.to_s
288
+ else
289
+ nil
290
+ end
291
+ end
292
+
293
+ private
294
+
295
+ def initialize(kind, rule_index, rule_id, prerequisite_key, error_kind, in_experiment=nil)
296
+ @kind = kind.to_sym
297
+ @rule_index = rule_index
298
+ @rule_id = rule_id
299
+ @rule_id.freeze if !rule_id.nil?
300
+ @prerequisite_key = prerequisite_key
301
+ @prerequisite_key.freeze if !prerequisite_key.nil?
302
+ @error_kind = error_kind
303
+ @in_experiment = in_experiment
304
+ end
305
+
306
+ private_class_method :new
307
+
308
+ def self.make_error(error_kind)
309
+ new(:ERROR, nil, nil, nil, error_kind)
310
+ end
311
+
312
+ @@fallthrough_with_experiment = new(:FALLTHROUGH, nil, nil, nil, nil, true)
313
+ @@fallthrough = new(:FALLTHROUGH, nil, nil, nil, nil)
314
+ @@off = new(:OFF, nil, nil, nil, nil)
315
+ @@target_match = new(:TARGET_MATCH, nil, nil, nil, nil)
316
+ @@error_instances = {
317
+ ERROR_CLIENT_NOT_READY => make_error(ERROR_CLIENT_NOT_READY),
318
+ ERROR_FLAG_NOT_FOUND => make_error(ERROR_FLAG_NOT_FOUND),
319
+ ERROR_MALFORMED_FLAG => make_error(ERROR_MALFORMED_FLAG),
320
+ ERROR_USER_NOT_SPECIFIED => make_error(ERROR_USER_NOT_SPECIFIED),
321
+ ERROR_EXCEPTION => make_error(ERROR_EXCEPTION)
322
+ }
323
+ end
324
+ end