eppo-server-sdk 3.2.2-x86_64-linux-musl → 3.2.7-x86_64-linux-musl

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c22565d875c7b40a28dcbffe1032a9d49f06b44f3043ec6dfc31aa1df66eac1c
4
- data.tar.gz: f3f4e6cd79b4c2474bf931a7bebcaaac5cbd6909f74a484be5f59b1eac3c868f
3
+ metadata.gz: 3f22fae84d379957d4c998ab883d780fd6a7bc3fbab81ab1fc3318d5ec87226a
4
+ data.tar.gz: 38dbfe398495c75c34c084510a6b683bee664bd7ad0f1ef91a0560bf14e2f69f
5
5
  SHA512:
6
- metadata.gz: 317e0cf947b0c7893292814529211f4ad2bb622db64d603c8b50b0b1fb00e3af01da4467caf9a54f8c5e967b7d1686e45a68db68307d61abeab09abea7fcae53
7
- data.tar.gz: 7e6c1d2f0f91c44e4a7a164fe593769a57718fcceb2f21494c4e4347154502fe107bf9aec55bb1a2ed53c64175ef52e624421ca9bba050b136ffa41a665c860c
6
+ metadata.gz: 2068819797f3e979a4d5e4fec13c77c4c323f6d361884a007a20c392730100364337183573ca402e28c6a4bd47c85b7f836d50d22497a6efc6c6c0f467928c85
7
+ data.tar.gz: 127cf70d19bbf83da41f950074593613c2ae09fe58e1f2ef182abd759227d520f746e5789c81ba2a40002cba8bb492418212d64de429671913abe877f6fa4e7b
data/README.md ADDED
@@ -0,0 +1,28 @@
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`
28
+ * Run `bundle` to update `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,200 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "singleton"
4
+ require "logger"
5
+
6
+ require_relative "config"
7
+
8
+ # Tries to require the extension for the current Ruby version first
9
+ begin
10
+ RUBY_VERSION =~ /(\d+\.\d+)/
11
+ require_relative "#{Regexp.last_match(1)}/eppo_client"
12
+ rescue LoadError
13
+ require_relative "eppo_client"
14
+ end
15
+
16
+ module EppoClient
17
+ # The main client singleton
18
+ class Client
19
+ include Singleton
20
+ attr_accessor :assignment_logger
21
+
22
+ def init(config)
23
+ config.validate
24
+
25
+ if !@core.nil?
26
+ STDERR.puts "Eppo Warning: multiple initialization of the client"
27
+ @core.shutdown
28
+ end
29
+
30
+ @assignment_logger = config.assignment_logger
31
+ @core = EppoClient::Core::Client.new(config)
32
+ end
33
+
34
+ def configuration
35
+ @core.configuration
36
+ end
37
+
38
+ def configuration=(configuration)
39
+ @core.configuration = configuration
40
+ end
41
+
42
+ def shutdown
43
+ @core.shutdown
44
+ end
45
+
46
+ def get_string_assignment(flag_key, subject_key, subject_attributes, default_value)
47
+ get_assignment_inner(flag_key, subject_key, subject_attributes, "STRING", default_value)
48
+ end
49
+
50
+ def get_numeric_assignment(flag_key, subject_key, subject_attributes, default_value)
51
+ get_assignment_inner(flag_key, subject_key, subject_attributes, "NUMERIC", default_value)
52
+ end
53
+
54
+ def get_integer_assignment(flag_key, subject_key, subject_attributes, default_value)
55
+ get_assignment_inner(flag_key, subject_key, subject_attributes, "INTEGER", default_value)
56
+ end
57
+
58
+ def get_boolean_assignment(flag_key, subject_key, subject_attributes, default_value)
59
+ get_assignment_inner(flag_key, subject_key, subject_attributes, "BOOLEAN", default_value)
60
+ end
61
+
62
+ def get_json_assignment(flag_key, subject_key, subject_attributes, default_value)
63
+ get_assignment_inner(flag_key, subject_key, subject_attributes, "JSON", default_value)
64
+ end
65
+
66
+ def get_string_assignment_details(flag_key, subject_key, subject_attributes, default_value)
67
+ get_assignment_details_inner(flag_key, subject_key, subject_attributes, "STRING", default_value)
68
+ end
69
+
70
+ def get_numeric_assignment_details(flag_key, subject_key, subject_attributes, default_value)
71
+ get_assignment_details_inner(flag_key, subject_key, subject_attributes, "NUMERIC", default_value)
72
+ end
73
+
74
+ def get_integer_assignment_details(flag_key, subject_key, subject_attributes, default_value)
75
+ get_assignment_details_inner(flag_key, subject_key, subject_attributes, "INTEGER", default_value)
76
+ end
77
+
78
+ def get_boolean_assignment_details(flag_key, subject_key, subject_attributes, default_value)
79
+ get_assignment_details_inner(flag_key, subject_key, subject_attributes, "BOOLEAN", default_value)
80
+ end
81
+
82
+ def get_json_assignment_details(flag_key, subject_key, subject_attributes, default_value)
83
+ get_assignment_details_inner(flag_key, subject_key, subject_attributes, "JSON", default_value)
84
+ end
85
+
86
+ def get_bandit_action(flag_key, subject_key, subject_attributes, actions, default_variation)
87
+ attributes = coerce_context_attributes(subject_attributes)
88
+ actions = actions.to_h { |action, attributes| [action, coerce_context_attributes(attributes)] }
89
+ result = @core.get_bandit_action(flag_key, subject_key, attributes, actions, default_variation)
90
+
91
+ log_assignment(result[:assignment_event])
92
+ log_bandit_action(result[:bandit_event])
93
+
94
+ return {:variation => result[:variation], :action => result[:action]}
95
+ end
96
+
97
+ def get_bandit_action_details(flag_key, subject_key, subject_attributes, actions, default_variation)
98
+ attributes = coerce_context_attributes(subject_attributes)
99
+ actions = actions.to_h { |action, attributes| [action, coerce_context_attributes(attributes)] }
100
+ result, details = @core.get_bandit_action_details(flag_key, subject_key, attributes, actions, default_variation)
101
+
102
+ log_assignment(result[:assignment_event])
103
+ log_bandit_action(result[:bandit_event])
104
+
105
+ return {
106
+ :variation => result[:variation],
107
+ :action => result[:action],
108
+ :evaluationDetails => details
109
+ }
110
+ end
111
+
112
+ private
113
+
114
+ # rubocop:disable Metrics/MethodLength
115
+ def get_assignment_inner(flag_key, subject_key, subject_attributes, expected_type, default_value)
116
+ logger = Logger.new($stdout)
117
+ begin
118
+ assignment = @core.get_assignment(flag_key, subject_key, subject_attributes, expected_type)
119
+ if not assignment then
120
+ return default_value
121
+ end
122
+
123
+ log_assignment(assignment[:event])
124
+
125
+ return assignment[:value][:value]
126
+ rescue StandardError => error
127
+ logger.debug("[Eppo SDK] Failed to get assignment: #{error}")
128
+
129
+ # TODO: non-graceful mode?
130
+ default_value
131
+ end
132
+ end
133
+ # rubocop:enable Metrics/MethodLength
134
+
135
+ # rubocop:disable Metrics/MethodLength
136
+ def get_assignment_details_inner(flag_key, subject_key, subject_attributes, expected_type, default_value)
137
+ result, event = @core.get_assignment_details(flag_key, subject_key, subject_attributes, expected_type)
138
+ log_assignment(event)
139
+
140
+ if not result[:variation] then
141
+ result[:variation] = default_value
142
+ else
143
+ # unwrap from AssignmentValue to untyped value
144
+ result[:variation] = result[:variation][:value]
145
+ end
146
+
147
+ return result
148
+ end
149
+ # rubocop:enable Metrics/MethodLength
150
+
151
+ def log_assignment(event)
152
+ if not event then return end
153
+
154
+ # Because rust's AssignmentEvent has a #[flatten] extra_logging
155
+ # field, serde_magnus serializes it as a normal HashMap with
156
+ # string keys.
157
+ #
158
+ # Convert keys to symbols here, so that logger sees symbol-keyed
159
+ # events for both flag assignment and bandit actions.
160
+ event = event.to_h { |key, value| [key.to_sym, value]}
161
+
162
+ begin
163
+ @assignment_logger.log_assignment(event)
164
+ rescue EppoClient::AssignmentLoggerError
165
+ # Error means log_assignment was not set up. This is okay to ignore.
166
+ rescue StandardError => error
167
+ logger = Logger.new($stdout)
168
+ logger.error("[Eppo SDK] Error logging assignment event: #{error}")
169
+ end
170
+ end
171
+
172
+ def log_bandit_action(event)
173
+ if not event then return end
174
+
175
+ begin
176
+ @assignment_logger.log_bandit_action(event)
177
+ rescue EppoClient::AssignmentLoggerError
178
+ # Error means log_assignment was not set up. This is okay to ignore.
179
+ rescue StandardError => error
180
+ logger = Logger.new($stdout)
181
+ logger.error("[Eppo SDK] Error logging bandit action event: #{error}")
182
+ end
183
+ end
184
+
185
+ def coerce_context_attributes(attributes)
186
+ numeric_attributes = attributes[:numeric_attributes] || attributes["numericAttributes"]
187
+ categorical_attributes = attributes[:categorical_attributes] || attributes["categoricalAttributes"]
188
+ if numeric_attributes || categorical_attributes then
189
+ {
190
+ numericAttributes: numeric_attributes.to_h do |key, value|
191
+ value.is_a?(Numeric) ? [key, value] : [nil, nil]
192
+ end.compact,
193
+ categoricalAttributes: categorical_attributes.to_h do |key, value|
194
+ value.nil? ? [nil, nil] : [key, value.to_s]
195
+ end.compact,
196
+ }
197
+ end
198
+ end
199
+ end
200
+ 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.7"
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.7
5
5
  platform: x86_64-linux-musl
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.so
21
23
  - lib/eppo_client/3.1/eppo_client.so
22
24
  - lib/eppo_client/3.2/eppo_client.so
23
25
  - lib/eppo_client/3.3/eppo_client.so
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