eppo-server-sdk 3.2.2-aarch64-linux-musl → 3.2.4-aarch64-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: 372425ea5322ef1186bb3b04f171ef6e687fc2ded988dc45e5ade0f3ba679e9d
4
- data.tar.gz: cc17d59b1ad07b24bafb58a7bf687bd76e0701cd7de802544f51bc7e100cf1e3
3
+ metadata.gz: 946d9507dc3bf372e8290de6232e0726ae8f2993f1187dad64b4c0c1d9069165
4
+ data.tar.gz: 444b071ce036fdd9c073a66c83e369f9ab40e6a2ca61876ed2ee8289106095fb
5
5
  SHA512:
6
- metadata.gz: 17018ff1417ea214609a8466dfc465a45e679426e63ec85fb59051bc0affb2dab0cbcdc49342fa8fef7d5dca2a5407b35301393fed4596dd6478e1ff739dceaf
7
- data.tar.gz: 9517946d5006a51acddd7e25666217b272574ca92fe3aac3f0f3ce259857a83066ccca45a9405e7efdb477c895c6b2cf16273e85ca04ee2043e44c6a8274f55a
6
+ metadata.gz: bc3aaeaec337a15ddcd33d2a0243572f8cc4e7344ccb9729dda20fa98434ddcdc7139447efe20449359f05ed40c4065c2089c34350684f23d27c3ed8cc73bb70
7
+ data.tar.gz: fdc81d087e46f210b06e877026da78a89cc41ec7a6f6d2277be8b9bb655c3079577d148427d077dd1f681bedc8617348f43aa2f4567b53cad2f34b82be6d60f5
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: aarch64-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