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

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: 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