launchdarkly-server-sdk 5.7.3 → 6.0.0

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