launchdarkly-server-sdk 5.8.1 → 6.2.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 (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