eppo-server-sdk 3.2.2-arm-linux → 3.2.4-arm-linux

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: 2f0ca5bbf9642333c3dec3aa884bc5fce1606f8b96112420cabee9f4bee9b23c
4
- data.tar.gz: 55c2d5dec9a3b4d146f56a0b0c0b3aea1c95750efd0ab77ebbcb7f6ebb2084ca
3
+ metadata.gz: 21c9f998369dd0eaff97be827e7093399f3376aceec698422c6c411cfde08224
4
+ data.tar.gz: fd5341173146830de48c03718b2e37233667e887b74f85afc3ee9a2537dd7980
5
5
  SHA512:
6
- metadata.gz: 948255134043b0791ce88bdbc036819e2ce57f8da975371974f43618ecfaf5f95cbbf611e732c6c6532b3e6607a067621034e25061ea5b0900bd4f510180ea16
7
- data.tar.gz: e2ca17817170bad7400eb52abfede161b744cf530bab080a960334fc7aa701c9c89f32fe97546c6182fa0f2d91c56b0ca174526da7c684aff1aac8833a97ee74
6
+ metadata.gz: 6907bd7a8158343b0e8b0fe896a42040b8543e93b646dc301a1d613165a155cabfdc14c8775032247ba54e9a33fcf792b1be3865f8df4a38b07d38aa37efd18c
7
+ data.tar.gz: add1bdc2edb2ec7db0406dcde5ce22256cf13dc7f44f9e2367a5530ea84af39e941223d4347bb64a3b424494fd48f876c8fa4ec7c001b5b2e5d16f57c247c4c4
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: arm-linux
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