eppo-server-sdk 3.2.2-arm64-darwin → 3.2.4-arm64-darwin

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b7b5674af76373caf9c4fc98616fbc975bed876825f7e11d25bad6b0cd8dd2d9
4
- data.tar.gz: b7a21824503acc2747af666919a9828e427b9bca4fad738ec6ae55025f916eb3
3
+ metadata.gz: 263ebee418eb1592ed7f0760e19ab8da22d5eb400ef46aac801295b9588070cd
4
+ data.tar.gz: 4152bb907db4fdb6fafec116c72728addea03d25060094e9264ddfd4ac0cd8de
5
5
  SHA512:
6
- metadata.gz: 41075419d1a099be78ed9ef21e731b77cfe78371b418f7049045de0d7553f66193adb53ea8eae2fc78d85730f5c82ef8a9a839cf16ceb3a213fabd320647c7e0
7
- data.tar.gz: fd766c37435b07ad9bab319d897726b7990e6bc207e010bf2a793c865a075e87d439e07426908dc80ac91751f7b313d39f02b7dfe809cd8cd57cf84d18ab1617
6
+ metadata.gz: 0c9e2b4218bf673e32c04927b88f7634f7a2a6045fff351374e9c93d8cbf4973a325b31393e27f82dd6c273d4e5d57e947cb76a47610901d013dbf2cf9c59dc4
7
+ data.tar.gz: ee11a040fe1ec3e37ac0bc4c4ddc431a11d7693ffa7f0b3e276e8798f73445388211b52158b88118c396b158fc84fe4531e93a67db3fef7974ec85ff51078084
data/README.md ADDED
@@ -0,0 +1,27 @@
1
+ # Eppo SDK for Ruby
2
+
3
+ ## Getting Started
4
+
5
+ Refer to our [SDK documentation](https://docs.geteppo.com/feature-flags/sdks/ruby) for how to install and use the SDK.
6
+
7
+ ## Supported Ruby Versions
8
+ This version of the SDK is compatible with Ruby 3.0.6 and above.
9
+
10
+ # Contributing
11
+
12
+ ## Testing with local version of `eppo_core`
13
+
14
+ To run build and tests against a local version of `eppo_core`, you should instruct Cargo to look for it at the local path.
15
+
16
+ Add the following to `.cargo/config.toml` file (relative to `ruby-sdk`):
17
+ ```toml
18
+ [patch.crates-io]
19
+ eppo_core = { path = '../eppo_core' }
20
+ ```
21
+
22
+ Make sure you remove the override before updating `Cargo.lock`. Otherwise, the lock file will be missing `eppo_core` checksum and will be unsuitable for release. (CI will warn you if you do this accidentally.)
23
+
24
+ ## Releasing
25
+
26
+ * Bump versions in `ruby-sdk/lib/eppo_client/version.rb` and `ruby-sdk/ext/eppo_client/Cargo.toml`.
27
+ * Run `cargo update --workspace --verbose` to update `Cargo.lock` and `Gemfile.lock`.
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "custom_errors"
4
+
5
+ module EppoClient
6
+ # The base assignment logger class to override
7
+ class AssignmentLogger
8
+ def log_assignment(_assignment_event)
9
+ raise(EppoClient::AssignmentLoggerError, "log_assignment has not been set up")
10
+ end
11
+
12
+ def log_bandit_action(_assignment_event)
13
+ raise(EppoClient::AssignmentLoggerError, "log_bandit_action has not been set up")
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,193 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "singleton"
4
+ require "logger"
5
+
6
+ require_relative "config"
7
+ require_relative "eppo_client"
8
+
9
+ module EppoClient
10
+ # The main client singleton
11
+ class Client
12
+ include Singleton
13
+ attr_accessor :assignment_logger
14
+
15
+ def init(config)
16
+ config.validate
17
+
18
+ if !@core.nil? then
19
+ STDERR.puts "Eppo Warning: multiple initialization of the client"
20
+ @core.shutdown
21
+ end
22
+
23
+ @assignment_logger = config.assignment_logger
24
+ @core = EppoClient::Core::Client.new(config)
25
+ end
26
+
27
+ def configuration
28
+ @core.configuration
29
+ end
30
+
31
+ def configuration=(configuration)
32
+ @core.configuration = configuration
33
+ end
34
+
35
+ def shutdown
36
+ @core.shutdown
37
+ end
38
+
39
+ def get_string_assignment(flag_key, subject_key, subject_attributes, default_value)
40
+ get_assignment_inner(flag_key, subject_key, subject_attributes, "STRING", default_value)
41
+ end
42
+
43
+ def get_numeric_assignment(flag_key, subject_key, subject_attributes, default_value)
44
+ get_assignment_inner(flag_key, subject_key, subject_attributes, "NUMERIC", default_value)
45
+ end
46
+
47
+ def get_integer_assignment(flag_key, subject_key, subject_attributes, default_value)
48
+ get_assignment_inner(flag_key, subject_key, subject_attributes, "INTEGER", default_value)
49
+ end
50
+
51
+ def get_boolean_assignment(flag_key, subject_key, subject_attributes, default_value)
52
+ get_assignment_inner(flag_key, subject_key, subject_attributes, "BOOLEAN", default_value)
53
+ end
54
+
55
+ def get_json_assignment(flag_key, subject_key, subject_attributes, default_value)
56
+ get_assignment_inner(flag_key, subject_key, subject_attributes, "JSON", default_value)
57
+ end
58
+
59
+ def get_string_assignment_details(flag_key, subject_key, subject_attributes, default_value)
60
+ get_assignment_details_inner(flag_key, subject_key, subject_attributes, "STRING", default_value)
61
+ end
62
+
63
+ def get_numeric_assignment_details(flag_key, subject_key, subject_attributes, default_value)
64
+ get_assignment_details_inner(flag_key, subject_key, subject_attributes, "NUMERIC", default_value)
65
+ end
66
+
67
+ def get_integer_assignment_details(flag_key, subject_key, subject_attributes, default_value)
68
+ get_assignment_details_inner(flag_key, subject_key, subject_attributes, "INTEGER", default_value)
69
+ end
70
+
71
+ def get_boolean_assignment_details(flag_key, subject_key, subject_attributes, default_value)
72
+ get_assignment_details_inner(flag_key, subject_key, subject_attributes, "BOOLEAN", default_value)
73
+ end
74
+
75
+ def get_json_assignment_details(flag_key, subject_key, subject_attributes, default_value)
76
+ get_assignment_details_inner(flag_key, subject_key, subject_attributes, "JSON", default_value)
77
+ end
78
+
79
+ def get_bandit_action(flag_key, subject_key, subject_attributes, actions, default_variation)
80
+ attributes = coerce_context_attributes(subject_attributes)
81
+ actions = actions.to_h { |action, attributes| [action, coerce_context_attributes(attributes)] }
82
+ result = @core.get_bandit_action(flag_key, subject_key, attributes, actions, default_variation)
83
+
84
+ log_assignment(result[:assignment_event])
85
+ log_bandit_action(result[:bandit_event])
86
+
87
+ return {:variation => result[:variation], :action => result[:action]}
88
+ end
89
+
90
+ def get_bandit_action_details(flag_key, subject_key, subject_attributes, actions, default_variation)
91
+ attributes = coerce_context_attributes(subject_attributes)
92
+ actions = actions.to_h { |action, attributes| [action, coerce_context_attributes(attributes)] }
93
+ result, details = @core.get_bandit_action_details(flag_key, subject_key, attributes, actions, default_variation)
94
+
95
+ log_assignment(result[:assignment_event])
96
+ log_bandit_action(result[:bandit_event])
97
+
98
+ return {
99
+ :variation => result[:variation],
100
+ :action => result[:action],
101
+ :evaluationDetails => details
102
+ }
103
+ end
104
+
105
+ private
106
+
107
+ # rubocop:disable Metrics/MethodLength
108
+ def get_assignment_inner(flag_key, subject_key, subject_attributes, expected_type, default_value)
109
+ logger = Logger.new($stdout)
110
+ begin
111
+ assignment = @core.get_assignment(flag_key, subject_key, subject_attributes, expected_type)
112
+ if not assignment then
113
+ return default_value
114
+ end
115
+
116
+ log_assignment(assignment[:event])
117
+
118
+ return assignment[:value][:value]
119
+ rescue StandardError => error
120
+ logger.debug("[Eppo SDK] Failed to get assignment: #{error}")
121
+
122
+ # TODO: non-graceful mode?
123
+ default_value
124
+ end
125
+ end
126
+ # rubocop:enable Metrics/MethodLength
127
+
128
+ # rubocop:disable Metrics/MethodLength
129
+ def get_assignment_details_inner(flag_key, subject_key, subject_attributes, expected_type, default_value)
130
+ result, event = @core.get_assignment_details(flag_key, subject_key, subject_attributes, expected_type)
131
+ log_assignment(event)
132
+
133
+ if not result[:variation] then
134
+ result[:variation] = default_value
135
+ else
136
+ # unwrap from AssignmentValue to untyped value
137
+ result[:variation] = result[:variation][:value]
138
+ end
139
+
140
+ return result
141
+ end
142
+ # rubocop:enable Metrics/MethodLength
143
+
144
+ def log_assignment(event)
145
+ if not event then return end
146
+
147
+ # Because rust's AssignmentEvent has a #[flatten] extra_logging
148
+ # field, serde_magnus serializes it as a normal HashMap with
149
+ # string keys.
150
+ #
151
+ # Convert keys to symbols here, so that logger sees symbol-keyed
152
+ # events for both flag assignment and bandit actions.
153
+ event = event.to_h { |key, value| [key.to_sym, value]}
154
+
155
+ begin
156
+ @assignment_logger.log_assignment(event)
157
+ rescue EppoClient::AssignmentLoggerError
158
+ # Error means log_assignment was not set up. This is okay to ignore.
159
+ rescue StandardError => error
160
+ logger = Logger.new($stdout)
161
+ logger.error("[Eppo SDK] Error logging assignment event: #{error}")
162
+ end
163
+ end
164
+
165
+ def log_bandit_action(event)
166
+ if not event then return end
167
+
168
+ begin
169
+ @assignment_logger.log_bandit_action(event)
170
+ rescue EppoClient::AssignmentLoggerError
171
+ # Error means log_assignment was not set up. This is okay to ignore.
172
+ rescue StandardError => error
173
+ logger = Logger.new($stdout)
174
+ logger.error("[Eppo SDK] Error logging bandit action event: #{error}")
175
+ end
176
+ end
177
+
178
+ def coerce_context_attributes(attributes)
179
+ numeric_attributes = attributes[:numeric_attributes] || attributes["numericAttributes"]
180
+ categorical_attributes = attributes[:categorical_attributes] || attributes["categoricalAttributes"]
181
+ if numeric_attributes || categorical_attributes then
182
+ {
183
+ numericAttributes: numeric_attributes.to_h do |key, value|
184
+ value.is_a?(Numeric) ? [key, value] : [nil, nil]
185
+ end.compact,
186
+ categoricalAttributes: categorical_attributes.to_h do |key, value|
187
+ value.nil? ? [nil, nil] : [key, value.to_s]
188
+ end.compact,
189
+ }
190
+ end
191
+ end
192
+ end
193
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "validation"
4
+ require_relative "assignment_logger"
5
+
6
+ module EppoClient
7
+ # The class for configuring the Eppo client singleton
8
+ class Config
9
+ attr_reader :api_key, :assignment_logger, :base_url, :poll_interval_seconds, :poll_jitter_seconds
10
+
11
+ def initialize(api_key, assignment_logger: AssignmentLogger.new, base_url: EppoClient::Core::DEFAULT_BASE_URL, poll_interval_seconds: EppoClient::Core::DEFAULT_POLL_INTERVAL_SECONDS, poll_jitter_seconds: EppoClient::Core::DEFAULT_POLL_JITTER_SECONDS, initial_configuration: nil)
12
+ @api_key = api_key
13
+ @assignment_logger = assignment_logger
14
+ @base_url = base_url
15
+ @poll_interval_seconds = poll_interval_seconds
16
+ @poll_jitter_seconds = poll_jitter_seconds
17
+ end
18
+
19
+ def validate
20
+ EppoClient.validate_not_blank("api_key", @api_key)
21
+ end
22
+
23
+ # Hide instance variables (specifically api_key) from logs
24
+ def inspect
25
+ "#<EppoClient::Config:#{object_id}>"
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EppoClient
4
+ # A custom error class for AssignmentLogger
5
+ class AssignmentLoggerError < StandardError
6
+ def initialize(message)
7
+ super("AssignmentLoggerError: #{message}")
8
+ end
9
+ end
10
+
11
+ # A custom error class for invalid values
12
+ class InvalidValueError < StandardError
13
+ def initialize(message)
14
+ super("InvalidValueError: #{message}")
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "custom_errors"
4
+
5
+ # The helper module to validate keys
6
+ module EppoClient
7
+ module_function
8
+
9
+ def validate_not_blank(field_name, field_value)
10
+ (field_value.nil? || field_value == "") && raise(
11
+ EppoClient::InvalidValueError, "#{field_name} cannot be blank"
12
+ )
13
+ end
14
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ # TODO: this version and ext/eppo_client/Cargo.toml should be in sync
4
+ module EppoClient
5
+ VERSION = "3.2.4"
6
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "eppo_client/client"
4
+ require_relative "eppo_client/version"
5
+
6
+ # EppoClient is the main module for initializing the Eppo client.
7
+ # It provides a method to initialize the client with a given configuration.
8
+ module EppoClient
9
+ def init(config)
10
+ client = EppoClient::Client.instance
11
+ client.init(config)
12
+ end
13
+
14
+ module_function :init
15
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: eppo-server-sdk
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.2.2
4
+ version: 3.2.4
5
5
  platform: arm64-darwin
6
6
  authors:
7
7
  - Eppo
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-10-17 00:00:00.000000000 Z
11
+ date: 2024-10-22 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description:
14
14
  email:
@@ -17,10 +17,18 @@ executables: []
17
17
  extensions: []
18
18
  extra_rdoc_files: []
19
19
  files:
20
+ - README.md
21
+ - lib/eppo_client.rb
20
22
  - lib/eppo_client/3.0/eppo_client.bundle
21
23
  - lib/eppo_client/3.1/eppo_client.bundle
22
24
  - lib/eppo_client/3.2/eppo_client.bundle
23
25
  - lib/eppo_client/3.3/eppo_client.bundle
26
+ - lib/eppo_client/assignment_logger.rb
27
+ - lib/eppo_client/client.rb
28
+ - lib/eppo_client/config.rb
29
+ - lib/eppo_client/custom_errors.rb
30
+ - lib/eppo_client/validation.rb
31
+ - lib/eppo_client/version.rb
24
32
  homepage: https://github.com/Eppo-exp/ruby-sdk
25
33
  licenses:
26
34
  - MIT