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 +4 -4
- data/README.md +27 -0
- data/lib/eppo_client/3.0/eppo_client.so +0 -0
- data/lib/eppo_client/3.1/eppo_client.so +0 -0
- data/lib/eppo_client/3.2/eppo_client.so +0 -0
- data/lib/eppo_client/3.3/eppo_client.so +0 -0
- data/lib/eppo_client/assignment_logger.rb +16 -0
- data/lib/eppo_client/client.rb +193 -0
- data/lib/eppo_client/config.rb +28 -0
- data/lib/eppo_client/custom_errors.rb +17 -0
- data/lib/eppo_client/validation.rb +14 -0
- data/lib/eppo_client/version.rb +6 -0
- data/lib/eppo_client.rb +15 -0
- metadata +10 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 946d9507dc3bf372e8290de6232e0726ae8f2993f1187dad64b4c0c1d9069165
|
4
|
+
data.tar.gz: 444b071ce036fdd9c073a66c83e369f9ab40e6a2ca61876ed2ee8289106095fb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
data/lib/eppo_client.rb
ADDED
@@ -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.
|
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-
|
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
|