launchdarkly-server-sdk 5.7.3 → 6.0.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 (70) hide show
  1. checksums.yaml +5 -5
  2. data/.circleci/config.yml +28 -122
  3. data/.gitignore +1 -1
  4. data/.ldrelease/build-docs.sh +18 -0
  5. data/.ldrelease/circleci/linux/execute.sh +18 -0
  6. data/.ldrelease/circleci/mac/execute.sh +18 -0
  7. data/.ldrelease/circleci/template/build.sh +29 -0
  8. data/.ldrelease/circleci/template/publish.sh +23 -0
  9. data/.ldrelease/circleci/template/set-gem-home.sh +7 -0
  10. data/.ldrelease/circleci/template/test.sh +10 -0
  11. data/.ldrelease/circleci/template/update-version.sh +8 -0
  12. data/.ldrelease/circleci/windows/execute.ps1 +19 -0
  13. data/.ldrelease/config.yml +14 -2
  14. data/CHANGELOG.md +36 -0
  15. data/CONTRIBUTING.md +1 -1
  16. data/Gemfile.lock +92 -76
  17. data/README.md +5 -3
  18. data/azure-pipelines.yml +1 -1
  19. data/docs/Makefile +26 -0
  20. data/docs/index.md +9 -0
  21. data/launchdarkly-server-sdk.gemspec +20 -13
  22. data/lib/ldclient-rb.rb +0 -1
  23. data/lib/ldclient-rb/config.rb +15 -3
  24. data/lib/ldclient-rb/evaluation_detail.rb +293 -0
  25. data/lib/ldclient-rb/events.rb +1 -4
  26. data/lib/ldclient-rb/file_data_source.rb +1 -1
  27. data/lib/ldclient-rb/impl/evaluator.rb +225 -0
  28. data/lib/ldclient-rb/impl/evaluator_bucketing.rb +74 -0
  29. data/lib/ldclient-rb/impl/evaluator_operators.rb +160 -0
  30. data/lib/ldclient-rb/impl/event_sender.rb +56 -40
  31. data/lib/ldclient-rb/impl/integrations/consul_impl.rb +5 -5
  32. data/lib/ldclient-rb/impl/integrations/dynamodb_impl.rb +5 -5
  33. data/lib/ldclient-rb/impl/integrations/redis_impl.rb +8 -7
  34. data/lib/ldclient-rb/impl/model/serialization.rb +62 -0
  35. data/lib/ldclient-rb/impl/unbounded_pool.rb +34 -0
  36. data/lib/ldclient-rb/integrations/redis.rb +3 -0
  37. data/lib/ldclient-rb/ldclient.rb +16 -11
  38. data/lib/ldclient-rb/polling.rb +1 -4
  39. data/lib/ldclient-rb/redis_store.rb +1 -0
  40. data/lib/ldclient-rb/requestor.rb +25 -23
  41. data/lib/ldclient-rb/stream.rb +10 -30
  42. data/lib/ldclient-rb/user_filter.rb +3 -2
  43. data/lib/ldclient-rb/util.rb +12 -8
  44. data/lib/ldclient-rb/version.rb +1 -1
  45. data/spec/evaluation_detail_spec.rb +135 -0
  46. data/spec/event_sender_spec.rb +20 -2
  47. data/spec/events_spec.rb +11 -0
  48. data/spec/http_util.rb +11 -1
  49. data/spec/impl/evaluator_bucketing_spec.rb +111 -0
  50. data/spec/impl/evaluator_clause_spec.rb +55 -0
  51. data/spec/impl/evaluator_operators_spec.rb +141 -0
  52. data/spec/impl/evaluator_rule_spec.rb +96 -0
  53. data/spec/impl/evaluator_segment_spec.rb +125 -0
  54. data/spec/impl/evaluator_spec.rb +305 -0
  55. data/spec/impl/evaluator_spec_base.rb +75 -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 +10 -8
  60. data/spec/polling_spec.rb +2 -2
  61. data/spec/redis_feature_store_spec.rb +32 -3
  62. data/spec/requestor_spec.rb +11 -45
  63. data/spec/spec_helper.rb +0 -3
  64. data/spec/stream_spec.rb +1 -16
  65. metadata +110 -60
  66. data/.yardopts +0 -9
  67. data/lib/ldclient-rb/evaluation.rb +0 -462
  68. data/scripts/gendocs.sh +0 -11
  69. data/scripts/release.sh +0 -27
  70. 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,4 @@ 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
58
+ * [Feature Flagging Guide](https://github.com/launchdarkly/featureflags/ "Feature Flagging Guide") for best practices and strategies
@@ -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
@@ -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
@@ -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,20 +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.7"
25
- spec.add_development_dependency "rspec", "~> 3.2"
26
- spec.add_development_dependency "codeclimate-test-reporter", "~> 0"
27
- spec.add_development_dependency "diplomat", ">= 2.0.2"
28
- spec.add_development_dependency "redis", "~> 3.3.5"
29
- spec.add_development_dependency "connection_pool", ">= 2.1.2"
30
- spec.add_development_dependency "rspec_junit_formatter", "~> 0.3.0"
31
- spec.add_development_dependency "timecop", "~> 0.9.1"
32
- spec.add_development_dependency "listen", "~> 3.0" # see file_data_source.rb
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"
33
36
 
34
- spec.add_runtime_dependency "json", [">= 1.8", "< 3"]
35
37
  spec.add_runtime_dependency "semantic", "~> 1.6"
36
- spec.add_runtime_dependency "concurrent-ruby", "~> 1.0"
37
- 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"
40
+
41
+ # lock json to 2.3.x as ruby libraries often remove
42
+ # support for older ruby versions in minor releases
43
+ spec.add_runtime_dependency "json", "~> 2.3.1"
44
+ spec.add_runtime_dependency "http", "~> 4.4.1"
38
45
  end
@@ -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,293 @@
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
+ # The key of the prerequisite flag that did not return the desired variation. If {#kind} is not
124
+ # {#PREREQUISITE_FAILED}, this will be `nil`.
125
+ attr_reader :prerequisite_key
126
+
127
+ # A value indicating the general category of error. This should be one of the class constants such
128
+ # as {#ERROR_FLAG_NOT_FOUND}. If {#kind} is not {#ERROR}, it will be `nil`.
129
+ attr_reader :error_kind
130
+
131
+ # Returns an instance whose {#kind} is {#OFF}.
132
+ # @return [EvaluationReason]
133
+ def self.off
134
+ @@off
135
+ end
136
+
137
+ # Returns an instance whose {#kind} is {#FALLTHROUGH}.
138
+ # @return [EvaluationReason]
139
+ def self.fallthrough
140
+ @@fallthrough
141
+ end
142
+
143
+ # Returns an instance whose {#kind} is {#TARGET_MATCH}.
144
+ # @return [EvaluationReason]
145
+ def self.target_match
146
+ @@target_match
147
+ end
148
+
149
+ # Returns an instance whose {#kind} is {#RULE_MATCH}.
150
+ #
151
+ # @param rule_index [Number] the index of the rule that was matched (0 for the first rule in
152
+ # the feature flag)
153
+ # @param rule_id [String] unique string identifier for the matched rule
154
+ # @return [EvaluationReason]
155
+ # @raise [ArgumentError] if `rule_index` is not a number or `rule_id` is not a string
156
+ def self.rule_match(rule_index, rule_id)
157
+ raise ArgumentError.new("rule_index must be a number") if !(rule_index.is_a? Numeric)
158
+ 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
159
+ new(:RULE_MATCH, rule_index, rule_id, nil, nil)
160
+ end
161
+
162
+ # Returns an instance whose {#kind} is {#PREREQUISITE_FAILED}.
163
+ #
164
+ # @param prerequisite_key [String] key of the prerequisite flag that did not return the desired variation
165
+ # @return [EvaluationReason]
166
+ # @raise [ArgumentError] if `prerequisite_key` is nil or not a string
167
+ def self.prerequisite_failed(prerequisite_key)
168
+ raise ArgumentError.new("prerequisite_key must be a string") if !(prerequisite_key.is_a? String)
169
+ new(:PREREQUISITE_FAILED, nil, nil, prerequisite_key, nil)
170
+ end
171
+
172
+ # Returns an instance whose {#kind} is {#ERROR}.
173
+ #
174
+ # @param error_kind [Symbol] value indicating the general category of error
175
+ # @return [EvaluationReason]
176
+ # @raise [ArgumentError] if `error_kind` is not a symbol
177
+ def self.error(error_kind)
178
+ raise ArgumentError.new("error_kind must be a symbol") if !(error_kind.is_a? Symbol)
179
+ e = @@error_instances[error_kind]
180
+ e.nil? ? make_error(error_kind) : e
181
+ end
182
+
183
+ def ==(other)
184
+ if other.is_a? EvaluationReason
185
+ @kind == other.kind && @rule_index == other.rule_index && @rule_id == other.rule_id &&
186
+ @prerequisite_key == other.prerequisite_key && @error_kind == other.error_kind
187
+ elsif other.is_a? Hash
188
+ @kind.to_s == other[:kind] && @rule_index == other[:ruleIndex] && @rule_id == other[:ruleId] &&
189
+ @prerequisite_key == other[:prerequisiteKey] &&
190
+ (other[:errorKind] == @error_kind.nil? ? nil : @error_kind.to_s)
191
+ end
192
+ end
193
+
194
+ # Equivalent to {#inspect}.
195
+ # @return [String]
196
+ def to_s
197
+ inspect
198
+ end
199
+
200
+ # Returns a concise string representation of the reason. Examples: `"FALLTHROUGH"`,
201
+ # `"ERROR(FLAG_NOT_FOUND)"`. The exact syntax is not guaranteed to remain the same; this is meant
202
+ # for debugging.
203
+ # @return [String]
204
+ def inspect
205
+ case @kind
206
+ when :RULE_MATCH
207
+ "RULE_MATCH(#{@rule_index},#{@rule_id})"
208
+ when :PREREQUISITE_FAILED
209
+ "PREREQUISITE_FAILED(#{@prerequisite_key})"
210
+ when :ERROR
211
+ "ERROR(#{@error_kind})"
212
+ else
213
+ @kind.to_s
214
+ end
215
+ end
216
+
217
+ # Returns a hash that can be used as a JSON representation of the reason, in the format used
218
+ # in LaunchDarkly analytics events.
219
+ # @return [Hash]
220
+ def as_json(*) # parameter is unused, but may be passed if we're using the json gem
221
+ # Note that this implementation is somewhat inefficient; it allocates a new hash every time.
222
+ # However, in normal usage the SDK only serializes reasons if 1. full event tracking is
223
+ # enabled for a flag and the application called variation_detail, or 2. experimentation is
224
+ # enabled for an evaluation. We can't reuse these hashes because an application could call
225
+ # as_json and then modify the result.
226
+ case @kind
227
+ when :RULE_MATCH
228
+ { kind: @kind, ruleIndex: @rule_index, ruleId: @rule_id }
229
+ when :PREREQUISITE_FAILED
230
+ { kind: @kind, prerequisiteKey: @prerequisite_key }
231
+ when :ERROR
232
+ { kind: @kind, errorKind: @error_kind }
233
+ else
234
+ { kind: @kind }
235
+ end
236
+ end
237
+
238
+ # Same as {#as_json}, but converts the JSON structure into a string.
239
+ # @return [String]
240
+ def to_json(*a)
241
+ as_json.to_json(a)
242
+ end
243
+
244
+ # Allows this object to be treated as a hash corresponding to its JSON representation. For
245
+ # instance, if `reason.kind` is {#RULE_MATCH}, then `reason[:kind]` will be `"RULE_MATCH"` and
246
+ # `reason[:ruleIndex]` will be equal to `reason.rule_index`.
247
+ def [](key)
248
+ case key
249
+ when :kind
250
+ @kind.to_s
251
+ when :ruleIndex
252
+ @rule_index
253
+ when :ruleId
254
+ @rule_id
255
+ when :prerequisiteKey
256
+ @prerequisite_key
257
+ when :errorKind
258
+ @error_kind.nil? ? nil : @error_kind.to_s
259
+ else
260
+ nil
261
+ end
262
+ end
263
+
264
+ private
265
+
266
+ def initialize(kind, rule_index, rule_id, prerequisite_key, error_kind)
267
+ @kind = kind.to_sym
268
+ @rule_index = rule_index
269
+ @rule_id = rule_id
270
+ @rule_id.freeze if !rule_id.nil?
271
+ @prerequisite_key = prerequisite_key
272
+ @prerequisite_key.freeze if !prerequisite_key.nil?
273
+ @error_kind = error_kind
274
+ end
275
+
276
+ private_class_method :new
277
+
278
+ def self.make_error(error_kind)
279
+ new(:ERROR, nil, nil, nil, error_kind)
280
+ end
281
+
282
+ @@fallthrough = new(:FALLTHROUGH, nil, nil, nil, nil)
283
+ @@off = new(:OFF, nil, nil, nil, nil)
284
+ @@target_match = new(:TARGET_MATCH, nil, nil, nil, nil)
285
+ @@error_instances = {
286
+ ERROR_CLIENT_NOT_READY => make_error(ERROR_CLIENT_NOT_READY),
287
+ ERROR_FLAG_NOT_FOUND => make_error(ERROR_FLAG_NOT_FOUND),
288
+ ERROR_MALFORMED_FLAG => make_error(ERROR_MALFORMED_FLAG),
289
+ ERROR_USER_NOT_SPECIFIED => make_error(ERROR_USER_NOT_SPECIFIED),
290
+ ERROR_EXCEPTION => make_error(ERROR_EXCEPTION)
291
+ }
292
+ end
293
+ end