devcycle-ruby-server-sdk 3.6.1 → 3.7.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.
- checksums.yaml +4 -4
- data/lib/devcycle-ruby-server-sdk/api/client.rb +102 -26
- data/lib/devcycle-ruby-server-sdk/api/dev_cycle_provider.rb +32 -5
- data/lib/devcycle-ruby-server-sdk/eval_hooks_runner.rb +135 -0
- data/lib/devcycle-ruby-server-sdk/eval_reasons.rb +13 -0
- data/lib/devcycle-ruby-server-sdk/localbucketing/bucketing-lib.release.wasm +0 -0
- data/lib/devcycle-ruby-server-sdk/localbucketing/proto/variableForUserParams.proto +8 -0
- data/lib/devcycle-ruby-server-sdk/localbucketing/proto/variableForUserParams_pb.rb +25 -59
- data/lib/devcycle-ruby-server-sdk/localbucketing/update_wasm.sh +2 -2
- data/lib/devcycle-ruby-server-sdk/models/eval_hook.rb +28 -0
- data/lib/devcycle-ruby-server-sdk/models/eval_hook_context.rb +22 -0
- data/lib/devcycle-ruby-server-sdk/models/variable.rb +9 -1
- data/lib/devcycle-ruby-server-sdk/version.rb +1 -1
- data/lib/devcycle-ruby-server-sdk.rb +6 -0
- data/spec/api/devcycle_api_spec.rb +11 -9
- data/spec/devcycle_provider_spec.rb +157 -7
- data/spec/eval_hooks_runner_spec.rb +410 -0
- data/spec/eval_hooks_spec.rb +245 -0
- metadata +10 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c3acf8324fc7606185240c3ea421048f87c8618a3944951ed71ed729ed4cde7a
|
4
|
+
data.tar.gz: 763e2632030ffbc90bd365614f158bc960c767e15bcf0d88afd2a2407ff56f24
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7830aef854188cded6ed20a3667d4fe6dbf2bcd4482e605b823c95023b7d9fb3f30a52d01543346adae4ed260290f25b71cd516a3172679e4209d54d342eac6b
|
7
|
+
data.tar.gz: be2f2cb6349a64b95216dd3868642284d917ea4c8db7d2991f550f9beb9e0ddb3f8dae91dc6aa5c817fbf0f2b5f562da1f95fcccee0bddceea67323b96ec783c
|
@@ -14,6 +14,7 @@ module DevCycle
|
|
14
14
|
@sdkKey = sdkKey
|
15
15
|
@dvc_options = dvc_options
|
16
16
|
@logger = dvc_options.logger
|
17
|
+
@eval_hooks_runner = EvalHooksRunner.new
|
17
18
|
|
18
19
|
if @dvc_options.enable_cloud_bucketing
|
19
20
|
@api_client = ApiClient.default
|
@@ -172,35 +173,72 @@ module DevCycle
|
|
172
173
|
|
173
174
|
validate_model(user)
|
174
175
|
|
175
|
-
|
176
|
-
|
177
|
-
|
176
|
+
# Create hook context
|
177
|
+
hook_context = HookContext.new(key: key, user: user, default_value: default)
|
178
|
+
|
179
|
+
before_hook_error = nil
|
180
|
+
# Run before hooks
|
181
|
+
begin
|
182
|
+
hook_context = @eval_hooks_runner.run_before_hooks(hook_context)
|
183
|
+
rescue BeforeHookError => e
|
184
|
+
before_hook_error = e
|
185
|
+
@logger.warn("Error in before hooks: #{e.message}")
|
178
186
|
end
|
179
187
|
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
value =
|
188
|
-
|
188
|
+
variable_result = nil
|
189
|
+
|
190
|
+
begin
|
191
|
+
if @dvc_options.enable_cloud_bucketing
|
192
|
+
data, _status_code, _headers = variable_with_http_info(key, user, default, opts)
|
193
|
+
variable_result = data
|
194
|
+
else
|
195
|
+
value = default
|
196
|
+
type = determine_variable_type(default)
|
197
|
+
defaulted = true
|
198
|
+
eval = { reason: DevCycle::EVAL_REASONS::DEFAULT, details: DevCycle::DEFAULT_REASON_DETAILS::USER_NOT_TARGETED }
|
199
|
+
if local_bucketing_initialized? && @local_bucketing.has_config
|
200
|
+
type_code = variable_type_code_from_type(type)
|
201
|
+
variable_pb = variable_for_user_pb(user, key, type_code)
|
202
|
+
unless variable_pb.nil?
|
203
|
+
value = get_variable_value(variable_pb)
|
204
|
+
defaulted = false
|
205
|
+
end
|
206
|
+
eval = get_eval_reason(variable_pb)
|
207
|
+
else
|
208
|
+
eval = { reason: DevCycle::EVAL_REASONS::DEFAULT, details: DevCycle::DEFAULT_REASON_DETAILS::MISSING_CONFIG }
|
209
|
+
@logger.warn("Local bucketing not initialized, returning default value for variable #{key}")
|
210
|
+
variable_event = Event.new({ type: DevCycle::EventTypes[:agg_variable_defaulted], target: key, metaData: { evalReason: DevCycle::EVAL_REASONS::DEFAULT }})
|
211
|
+
bucketed_config = BucketedUserConfig.new({}, {}, {}, {}, {}, {}, [])
|
212
|
+
@event_queue.queue_aggregate_event(variable_event, bucketed_config)
|
213
|
+
end
|
214
|
+
|
215
|
+
variable_result = Variable.new({
|
216
|
+
key: key,
|
217
|
+
value: value,
|
218
|
+
type: type,
|
219
|
+
defaultValue: default,
|
220
|
+
isDefaulted: defaulted,
|
221
|
+
eval: eval
|
222
|
+
})
|
189
223
|
end
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
224
|
+
|
225
|
+
|
226
|
+
# Run after hooks only if no before hook error occurred
|
227
|
+
if before_hook_error != nil
|
228
|
+
@logger.info("before_hook_error is not nil, skipping after hooks")
|
229
|
+
raise before_hook_error
|
230
|
+
else
|
231
|
+
@eval_hooks_runner.run_after_hooks(hook_context)
|
232
|
+
end
|
233
|
+
rescue => e
|
234
|
+
# Run error hooks
|
235
|
+
@eval_hooks_runner.run_error_hooks(hook_context, e)
|
236
|
+
ensure
|
237
|
+
# Run finally hooks in all cases
|
238
|
+
@eval_hooks_runner.run_finally_hooks(hook_context)
|
239
|
+
end
|
240
|
+
|
241
|
+
variable_result
|
204
242
|
end
|
205
243
|
|
206
244
|
def variable_for_user(user, key, variable_type_code)
|
@@ -526,6 +564,44 @@ module DevCycle
|
|
526
564
|
raise ArgumentError.new("Invalid type code for variable: #{type_code}")
|
527
565
|
end
|
528
566
|
end
|
567
|
+
|
568
|
+
def get_variable_value(variable_pb)
|
569
|
+
case variable_pb.type
|
570
|
+
when :Boolean
|
571
|
+
variable_pb.boolValue
|
572
|
+
when :Number
|
573
|
+
variable_pb.doubleValue
|
574
|
+
when :String
|
575
|
+
variable_pb.stringValue
|
576
|
+
when :JSON
|
577
|
+
JSON.parse variable_pb.stringValue
|
578
|
+
end
|
579
|
+
end
|
580
|
+
|
581
|
+
def get_eval_reason(variable_pb)
|
582
|
+
if variable_pb.nil?
|
583
|
+
{ reason: DevCycle::EVAL_REASONS::DEFAULT, details: DevCycle::DEFAULT_REASON_DETAILS::USER_NOT_TARGETED}
|
584
|
+
else
|
585
|
+
if variable_pb.eval.nil?
|
586
|
+
{ reason: DevCycle::EVAL_REASONS::DEFAULT, details: DevCycle::DEFAULT_REASON_DETAILS::USER_NOT_TARGETED }
|
587
|
+
else
|
588
|
+
{ reason: variable_pb.eval.reason, details: variable_pb.eval.details, target_id: variable_pb.eval.target_id }
|
589
|
+
end
|
590
|
+
end
|
591
|
+
end
|
592
|
+
|
593
|
+
# Adds an eval hook to the client
|
594
|
+
# @param [EvalHook] eval_hook The eval hook to add
|
595
|
+
# @return [void]
|
596
|
+
def add_eval_hook(eval_hook)
|
597
|
+
@eval_hooks_runner.add_hook(eval_hook)
|
598
|
+
end
|
599
|
+
|
600
|
+
# Clears all eval hooks from the client
|
601
|
+
# @return [void]
|
602
|
+
def clear_eval_hooks
|
603
|
+
@eval_hooks_runner.clear_hooks
|
604
|
+
end
|
529
605
|
end
|
530
606
|
|
531
607
|
# @deprecated Use `DevCycle::Client` instead.
|
@@ -48,15 +48,42 @@ module DevCycle
|
|
48
48
|
raise ArgumentError, "Invalid context type, expected OpenFeature::SDK::EvaluationContext but got #{context.class}"
|
49
49
|
end
|
50
50
|
args = {}
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
51
|
+
user_id = nil
|
52
|
+
user_id_field = nil
|
53
|
+
|
54
|
+
# Priority order: targeting_key -> user_id -> userId
|
55
|
+
if context.field('targeting_key')
|
56
|
+
user_id = context.field('targeting_key')
|
57
|
+
user_id_field = 'targeting_key'
|
58
|
+
elsif context.field('user_id')
|
59
|
+
user_id = context.field('user_id')
|
60
|
+
user_id_field = 'user_id'
|
61
|
+
elsif context.field('userId')
|
62
|
+
user_id = context.field('userId')
|
63
|
+
user_id_field = 'userId'
|
55
64
|
end
|
65
|
+
|
66
|
+
# Validate user_id is present and is a string
|
67
|
+
if user_id.nil?
|
68
|
+
raise ArgumentError, "User ID is required. Must provide one of: targeting_key, user_id, or userId"
|
69
|
+
end
|
70
|
+
|
71
|
+
unless user_id.is_a?(String)
|
72
|
+
raise ArgumentError, "User ID field '#{user_id_field}' must be a string, got #{user_id.class}"
|
73
|
+
end
|
74
|
+
|
75
|
+
# Check after type validation to avoid NoMethodError on non-strings
|
76
|
+
if user_id.empty?
|
77
|
+
raise ArgumentError, "User ID is required. Must provide one of: targeting_key, user_id, or userId"
|
78
|
+
end
|
79
|
+
|
80
|
+
args.merge!(user_id: user_id)
|
81
|
+
|
56
82
|
customData = {}
|
57
83
|
privateCustomData = {}
|
58
84
|
context.fields.each do |field, value|
|
59
|
-
|
85
|
+
# Skip all user ID fields from custom data
|
86
|
+
if field === 'targeting_key' || field === 'user_id' || field === 'userId'
|
60
87
|
next
|
61
88
|
end
|
62
89
|
if !(field === 'privateCustomData' || field === 'customData') && value.is_a?(Hash)
|
@@ -0,0 +1,135 @@
|
|
1
|
+
require 'devcycle-ruby-server-sdk/models/eval_hook'
|
2
|
+
require 'devcycle-ruby-server-sdk/models/eval_hook_context'
|
3
|
+
|
4
|
+
module DevCycle
|
5
|
+
# Custom error raised when a before hook fails
|
6
|
+
class BeforeHookError < StandardError
|
7
|
+
attr_reader :original_error, :hook_context
|
8
|
+
|
9
|
+
def initialize(message = nil, original_error = nil, hook_context = nil)
|
10
|
+
super(message || "Before hook execution failed")
|
11
|
+
@original_error = original_error
|
12
|
+
@hook_context = hook_context
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_s
|
16
|
+
msg = super
|
17
|
+
msg += "\nOriginal error: #{@original_error.message}" if @original_error
|
18
|
+
msg
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Custom error raised when an after hook fails
|
23
|
+
class AfterHookError < StandardError
|
24
|
+
attr_reader :original_error, :hook_context
|
25
|
+
|
26
|
+
def initialize(message = nil, original_error = nil, hook_context = nil)
|
27
|
+
super(message || "After hook execution failed")
|
28
|
+
@original_error = original_error
|
29
|
+
@hook_context = hook_context
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_s
|
33
|
+
msg = super
|
34
|
+
msg += "\nOriginal error: #{@original_error.message}" if @original_error
|
35
|
+
msg
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class EvalHooksRunner
|
40
|
+
# @return [Array<EvalHook>] Array of eval hooks to run
|
41
|
+
attr_reader :eval_hooks
|
42
|
+
|
43
|
+
# Initializes the EvalHooksRunner with an optional array of eval hooks
|
44
|
+
# @param [Array<EvalHook>, nil] eval_hooks Array of eval hooks to run
|
45
|
+
def initialize(eval_hooks = [])
|
46
|
+
@eval_hooks = eval_hooks || []
|
47
|
+
end
|
48
|
+
|
49
|
+
# Runs all before hooks with the given context
|
50
|
+
# @param [HookContext] context The context to pass to the hooks
|
51
|
+
# @return [HookContext] The potentially modified context
|
52
|
+
# @raise [BeforeHookError] when a before hook fails
|
53
|
+
def run_before_hooks(context)
|
54
|
+
current_context = context
|
55
|
+
|
56
|
+
@eval_hooks.each do |hook|
|
57
|
+
next unless hook.before
|
58
|
+
|
59
|
+
begin
|
60
|
+
result = hook.before.call(current_context)
|
61
|
+
# If the hook returns a new context, use it for subsequent hooks
|
62
|
+
current_context = result if result.is_a?(DevCycle::HookContext)
|
63
|
+
rescue => e
|
64
|
+
# Raise BeforeHookError to allow client to handle and skip after hooks
|
65
|
+
raise BeforeHookError.new(e.message, e, current_context)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
current_context
|
70
|
+
end
|
71
|
+
|
72
|
+
# Runs all after hooks with the given context
|
73
|
+
# @param [HookContext] context The context to pass to the hooks
|
74
|
+
# @return [void]
|
75
|
+
# @raise [AfterHookError] when an after hook fails
|
76
|
+
def run_after_hooks(context)
|
77
|
+
@eval_hooks.each do |hook|
|
78
|
+
next unless hook.after
|
79
|
+
|
80
|
+
begin
|
81
|
+
hook.after.call(context)
|
82
|
+
rescue => e
|
83
|
+
# Log error but continue with next hook
|
84
|
+
raise AfterHookError.new(e.message, e, context)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# Runs all finally hooks with the given context
|
90
|
+
# @param [HookContext] context The context to pass to the hooks
|
91
|
+
# @return [void]
|
92
|
+
def run_finally_hooks(context)
|
93
|
+
@eval_hooks.each do |hook|
|
94
|
+
next unless hook.on_finally
|
95
|
+
|
96
|
+
begin
|
97
|
+
hook.on_finally.call(context)
|
98
|
+
rescue => e
|
99
|
+
# Log error but don't re-raise to prevent blocking evaluation
|
100
|
+
warn "Error in finally hook: #{e.message}"
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# Runs all error hooks with the given context and error
|
106
|
+
# @param [HookContext] context The context to pass to the hooks
|
107
|
+
# @param [Exception] error The error that occurred
|
108
|
+
# @return [void]
|
109
|
+
def run_error_hooks(context, error)
|
110
|
+
@eval_hooks.each do |hook|
|
111
|
+
next unless hook.error
|
112
|
+
|
113
|
+
begin
|
114
|
+
hook.error.call(context, error)
|
115
|
+
rescue => e
|
116
|
+
# Log error but don't re-raise to prevent blocking evaluation
|
117
|
+
warn "Error in error hook: #{e.message}"
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
# Adds an eval hook to the runner
|
123
|
+
# @param [EvalHook] eval_hook The eval hook to add
|
124
|
+
# @return [void]
|
125
|
+
def add_hook(eval_hook)
|
126
|
+
@eval_hooks << eval_hook
|
127
|
+
end
|
128
|
+
|
129
|
+
# Clears all eval hooks from the runner
|
130
|
+
# @return [void]
|
131
|
+
def clear_hooks
|
132
|
+
@eval_hooks.clear
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module DevCycle
|
2
|
+
# Evaluation reasons for successful evaluations
|
3
|
+
module EVAL_REASONS
|
4
|
+
DEFAULT = 'DEFAULT'
|
5
|
+
ERROR = 'ERROR'
|
6
|
+
end
|
7
|
+
|
8
|
+
# Default reason details
|
9
|
+
module DEFAULT_REASON_DETAILS
|
10
|
+
MISSING_CONFIG = 'Missing Config'
|
11
|
+
USER_NOT_TARGETED = 'User Not Targeted'
|
12
|
+
end
|
13
|
+
end
|
Binary file
|
@@ -66,4 +66,12 @@ message SDKVariable_PB {
|
|
66
66
|
double doubleValue = 5;
|
67
67
|
string stringValue = 6;
|
68
68
|
NullableString evalReason = 7;
|
69
|
+
NullableString _feature = 8;
|
70
|
+
EvalReason_PB eval = 9;
|
69
71
|
}
|
72
|
+
|
73
|
+
message EvalReason_PB {
|
74
|
+
string reason = 1;
|
75
|
+
string details = 2;
|
76
|
+
string target_id = 3;
|
77
|
+
}
|
@@ -1,69 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
2
3
|
# source: variableForUserParams.proto
|
3
4
|
|
4
5
|
require 'google/protobuf'
|
5
6
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
optional :sdkKey, :string, 1
|
28
|
-
optional :variableKey, :string, 2
|
29
|
-
optional :variableType, :enum, 3, "proto.VariableType_PB"
|
30
|
-
optional :user, :message, 4, "proto.DVCUser_PB"
|
31
|
-
optional :shouldTrackEvent, :bool, 5
|
32
|
-
end
|
33
|
-
add_message "proto.DVCUser_PB" do
|
34
|
-
optional :user_id, :string, 1
|
35
|
-
optional :email, :message, 2, "proto.NullableString"
|
36
|
-
optional :name, :message, 3, "proto.NullableString"
|
37
|
-
optional :language, :message, 4, "proto.NullableString"
|
38
|
-
optional :country, :message, 5, "proto.NullableString"
|
39
|
-
optional :appBuild, :message, 6, "proto.NullableDouble"
|
40
|
-
optional :appVersion, :message, 7, "proto.NullableString"
|
41
|
-
optional :deviceModel, :message, 8, "proto.NullableString"
|
42
|
-
optional :customData, :message, 9, "proto.NullableCustomData"
|
43
|
-
optional :privateCustomData, :message, 10, "proto.NullableCustomData"
|
44
|
-
end
|
45
|
-
add_message "proto.SDKVariable_PB" do
|
46
|
-
optional :_id, :string, 1
|
47
|
-
optional :type, :enum, 2, "proto.VariableType_PB"
|
48
|
-
optional :key, :string, 3
|
49
|
-
optional :boolValue, :bool, 4
|
50
|
-
optional :doubleValue, :double, 5
|
51
|
-
optional :stringValue, :string, 6
|
52
|
-
optional :evalReason, :message, 7, "proto.NullableString"
|
53
|
-
end
|
54
|
-
add_enum "proto.VariableType_PB" do
|
55
|
-
value :Boolean, 0
|
56
|
-
value :Number, 1
|
57
|
-
value :String, 2
|
58
|
-
value :JSON, 3
|
59
|
-
end
|
60
|
-
add_enum "proto.CustomDataType" do
|
61
|
-
value :Bool, 0
|
62
|
-
value :Num, 1
|
63
|
-
value :Str, 2
|
64
|
-
value :Null, 3
|
7
|
+
|
8
|
+
descriptor_data = "\n\x1bvariableForUserParams.proto\x12\x05proto\"/\n\x0eNullableString\x12\r\n\x05value\x18\x01 \x01(\t\x12\x0e\n\x06isNull\x18\x02 \x01(\x08\"/\n\x0eNullableDouble\x12\r\n\x05value\x18\x01 \x01(\x01\x12\x0e\n\x06isNull\x18\x02 \x01(\x08\"s\n\x0f\x43ustomDataValue\x12#\n\x04type\x18\x01 \x01(\x0e\x32\x15.proto.CustomDataType\x12\x11\n\tboolValue\x18\x02 \x01(\x08\x12\x13\n\x0b\x64oubleValue\x18\x03 \x01(\x01\x12\x13\n\x0bstringValue\x18\x04 \x01(\t\"\x9f\x01\n\x12NullableCustomData\x12\x33\n\x05value\x18\x01 \x03(\x0b\x32$.proto.NullableCustomData.ValueEntry\x12\x0e\n\x06isNull\x18\x02 \x01(\x08\x1a\x44\n\nValueEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12%\n\x05value\x18\x02 \x01(\x0b\x32\x16.proto.CustomDataValue:\x02\x38\x01\"\xa8\x01\n\x18VariableForUserParams_PB\x12\x0e\n\x06sdkKey\x18\x01 \x01(\t\x12\x13\n\x0bvariableKey\x18\x02 \x01(\t\x12,\n\x0cvariableType\x18\x03 \x01(\x0e\x32\x16.proto.VariableType_PB\x12\x1f\n\x04user\x18\x04 \x01(\x0b\x32\x11.proto.DVCUser_PB\x12\x18\n\x10shouldTrackEvent\x18\x05 \x01(\x08\"\x9e\x03\n\nDVCUser_PB\x12\x0f\n\x07user_id\x18\x01 \x01(\t\x12$\n\x05\x65mail\x18\x02 \x01(\x0b\x32\x15.proto.NullableString\x12#\n\x04name\x18\x03 \x01(\x0b\x32\x15.proto.NullableString\x12\'\n\x08language\x18\x04 \x01(\x0b\x32\x15.proto.NullableString\x12&\n\x07\x63ountry\x18\x05 \x01(\x0b\x32\x15.proto.NullableString\x12\'\n\x08\x61ppBuild\x18\x06 \x01(\x0b\x32\x15.proto.NullableDouble\x12)\n\nappVersion\x18\x07 \x01(\x0b\x32\x15.proto.NullableString\x12*\n\x0b\x64\x65viceModel\x18\x08 \x01(\x0b\x32\x15.proto.NullableString\x12-\n\ncustomData\x18\t \x01(\x0b\x32\x19.proto.NullableCustomData\x12\x34\n\x11privateCustomData\x18\n \x01(\x0b\x32\x19.proto.NullableCustomData\"\x85\x02\n\x0eSDKVariable_PB\x12\x0b\n\x03_id\x18\x01 \x01(\t\x12$\n\x04type\x18\x02 \x01(\x0e\x32\x16.proto.VariableType_PB\x12\x0b\n\x03key\x18\x03 \x01(\t\x12\x11\n\tboolValue\x18\x04 \x01(\x08\x12\x13\n\x0b\x64oubleValue\x18\x05 \x01(\x01\x12\x13\n\x0bstringValue\x18\x06 \x01(\t\x12)\n\nevalReason\x18\x07 \x01(\x0b\x32\x15.proto.NullableString\x12\'\n\x08_feature\x18\x08 \x01(\x0b\x32\x15.proto.NullableString\x12\"\n\x04\x65val\x18\t \x01(\x0b\x32\x14.proto.EvalReason_PB\"C\n\rEvalReason_PB\x12\x0e\n\x06reason\x18\x01 \x01(\t\x12\x0f\n\x07\x64\x65tails\x18\x02 \x01(\t\x12\x11\n\ttarget_id\x18\x03 \x01(\t*@\n\x0fVariableType_PB\x12\x0b\n\x07\x42oolean\x10\x00\x12\n\n\x06Number\x10\x01\x12\n\n\x06String\x10\x02\x12\x08\n\x04JSON\x10\x03*6\n\x0e\x43ustomDataType\x12\x08\n\x04\x42ool\x10\x00\x12\x07\n\x03Num\x10\x01\x12\x07\n\x03Str\x10\x02\x12\x08\n\x04Null\x10\x03\x62\x06proto3"
|
9
|
+
|
10
|
+
pool = Google::Protobuf::DescriptorPool.generated_pool
|
11
|
+
|
12
|
+
begin
|
13
|
+
pool.add_serialized_file(descriptor_data)
|
14
|
+
rescue TypeError => e
|
15
|
+
# Compatibility code: will be removed in the next major version.
|
16
|
+
require 'google/protobuf/descriptor_pb'
|
17
|
+
parsed = Google::Protobuf::FileDescriptorProto.decode(descriptor_data)
|
18
|
+
parsed.clear_dependency
|
19
|
+
serialized = parsed.class.encode(parsed)
|
20
|
+
file = pool.add_serialized_file(serialized)
|
21
|
+
warn "Warning: Protobuf detected an import path issue while loading generated file #{__FILE__}"
|
22
|
+
imports = [
|
23
|
+
]
|
24
|
+
imports.each do |type_name, expected_filename|
|
25
|
+
import_file = pool.lookup(type_name).file_descriptor
|
26
|
+
if import_file.name != expected_filename
|
27
|
+
warn "- #{file.name} imports #{expected_filename}, but that import was loaded as #{import_file.name}"
|
65
28
|
end
|
66
29
|
end
|
30
|
+
warn "Each proto file must use a consistent fully-qualified name."
|
31
|
+
warn "This will become an error in the next major version."
|
67
32
|
end
|
68
33
|
|
69
34
|
module Proto
|
@@ -74,6 +39,7 @@ module Proto
|
|
74
39
|
VariableForUserParams_PB = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("proto.VariableForUserParams_PB").msgclass
|
75
40
|
DVCUser_PB = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("proto.DVCUser_PB").msgclass
|
76
41
|
SDKVariable_PB = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("proto.SDKVariable_PB").msgclass
|
42
|
+
EvalReason_PB = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("proto.EvalReason_PB").msgclass
|
77
43
|
VariableType_PB = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("proto.VariableType_PB").enummodule
|
78
44
|
CustomDataType = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("proto.CustomDataType").enummodule
|
79
45
|
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
#!/bin/bash
|
2
|
-
BUCKETING_LIB_VERSION="1.
|
2
|
+
BUCKETING_LIB_VERSION="1.41.0"
|
3
3
|
WAT_DOWNLOAD=0
|
4
4
|
rm bucketing-lib.release.wasm
|
5
|
-
wget "https://unpkg.com/@devcycle/bucketing-assembly-script@$BUCKETING_LIB_VERSION/build/bucketing-lib.release.wasm"
|
5
|
+
wget "https://unpkg.com/@devcycle/bucketing-assembly-script@$BUCKETING_LIB_VERSION/build/bucketing-lib.release.wasm"
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module DevCycle
|
2
|
+
class EvalHook
|
3
|
+
# Callback to be executed before evaluation
|
4
|
+
attr_accessor :before
|
5
|
+
|
6
|
+
# Callback to be executed after evaluation
|
7
|
+
attr_accessor :after
|
8
|
+
|
9
|
+
# Callback to be executed finally (always runs)
|
10
|
+
attr_accessor :on_finally
|
11
|
+
|
12
|
+
# Callback to be executed on error
|
13
|
+
attr_accessor :error
|
14
|
+
|
15
|
+
# Initializes the object with optional callback functions
|
16
|
+
# @param [Hash] callbacks Callback functions in the form of hash
|
17
|
+
# @option callbacks [Proc, nil] :before Callback to execute before evaluation
|
18
|
+
# @option callbacks [Proc, nil] :after Callback to execute after evaluation
|
19
|
+
# @option callbacks [Proc, nil] :on_finally Callback to execute finally (always runs)
|
20
|
+
# @option callbacks [Proc, nil] :error Callback to execute on error
|
21
|
+
def initialize(callbacks = {})
|
22
|
+
@before = callbacks[:before]
|
23
|
+
@after = callbacks[:after]
|
24
|
+
@on_finally = callbacks[:on_finally]
|
25
|
+
@error = callbacks[:error]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module DevCycle
|
2
|
+
class HookContext
|
3
|
+
# The key of the variable being evaluated
|
4
|
+
attr_accessor :key
|
5
|
+
|
6
|
+
# The user for whom the variable is being evaluated
|
7
|
+
attr_accessor :user
|
8
|
+
|
9
|
+
# The default value for the variable
|
10
|
+
attr_accessor :default_value
|
11
|
+
|
12
|
+
# Initializes the object
|
13
|
+
# @param [String] key The key of the variable being evaluated
|
14
|
+
# @param [DevCycle::User] user The user for whom the variable is being evaluated
|
15
|
+
# @param [Object] default_value The default value for the variable
|
16
|
+
def initialize(key:, user:, default_value:)
|
17
|
+
@key = key
|
18
|
+
@user = user
|
19
|
+
@default_value = default_value
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -29,6 +29,9 @@ module DevCycle
|
|
29
29
|
|
30
30
|
attr_accessor :defaultValue
|
31
31
|
|
32
|
+
# Variable evaluation details
|
33
|
+
attr_accessor :eval
|
34
|
+
|
32
35
|
class EnumAttributeValidator
|
33
36
|
attr_reader :datatype
|
34
37
|
attr_reader :allowable_values
|
@@ -58,7 +61,8 @@ module DevCycle
|
|
58
61
|
:'type' => :'type',
|
59
62
|
:'value' => :'value',
|
60
63
|
:'defaultValue' => :'defaultValue',
|
61
|
-
:'isDefaulted' => :'isDefaulted'
|
64
|
+
:'isDefaulted' => :'isDefaulted',
|
65
|
+
:'eval' => :'eval'
|
62
66
|
}
|
63
67
|
end
|
64
68
|
|
@@ -120,6 +124,10 @@ module DevCycle
|
|
120
124
|
if attributes.key?(:'defaultValue')
|
121
125
|
self.defaultValue = attributes[:'defaultValue']
|
122
126
|
end
|
127
|
+
|
128
|
+
if attributes.key?(:'eval')
|
129
|
+
self.eval = attributes[:'eval']
|
130
|
+
end
|
123
131
|
end
|
124
132
|
|
125
133
|
# Show invalid properties with the reasons. Usually used together with valid?
|
@@ -25,6 +25,12 @@ require 'devcycle-ruby-server-sdk/models/user'
|
|
25
25
|
require 'devcycle-ruby-server-sdk/models/user_data_and_events_body'
|
26
26
|
require 'devcycle-ruby-server-sdk/models/variable'
|
27
27
|
|
28
|
+
# Eval Hooks
|
29
|
+
require 'devcycle-ruby-server-sdk/eval_hooks_runner'
|
30
|
+
require 'devcycle-ruby-server-sdk/models/eval_hook'
|
31
|
+
require 'devcycle-ruby-server-sdk/models/eval_hook_context'
|
32
|
+
require 'devcycle-ruby-server-sdk/eval_reasons'
|
33
|
+
|
28
34
|
# APIs
|
29
35
|
require 'devcycle-ruby-server-sdk/api/client'
|
30
36
|
require 'devcycle-ruby-server-sdk/api/dev_cycle_provider'
|
@@ -21,16 +21,18 @@ describe 'DevCycle::Client' do
|
|
21
21
|
sdk_key = ENV["DEVCYCLE_SERVER_SDK_KEY"]
|
22
22
|
if sdk_key.nil?
|
23
23
|
puts("SDK KEY NOT SET - SKIPPING INIT")
|
24
|
-
|
24
|
+
@skip_tests = true
|
25
|
+
else
|
26
|
+
# run before each test
|
27
|
+
options = DevCycle::Options.new(enable_cloud_bucketing: true)
|
28
|
+
@api_instance = DevCycle::Client.new(sdk_key, options)
|
29
|
+
|
30
|
+
@user = DevCycle::User.new({
|
31
|
+
user_id: 'test-user',
|
32
|
+
appVersion: '1.2.3'
|
33
|
+
})
|
34
|
+
@skip_tests = false
|
25
35
|
end
|
26
|
-
# run before each test
|
27
|
-
options = DevCycle::Options.new(enable_cloud_bucketing: true)
|
28
|
-
@api_instance = DevCycle::Client.new(sdk_key, options)
|
29
|
-
|
30
|
-
@user = DevCycle::User.new({
|
31
|
-
user_id: 'test-user',
|
32
|
-
appVersion: '1.2.3'
|
33
|
-
})
|
34
36
|
end
|
35
37
|
|
36
38
|
after do
|