eppo-server-sdk 0.3.0 → 3.0.0
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 +4 -4
- data/.gitignore +22 -0
- data/.rspec +3 -0
- data/.rubocop.yml +8 -0
- data/Cargo.lock +1963 -0
- data/Cargo.toml +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +22 -0
- data/Rakefile +53 -0
- data/Steepfile +27 -0
- data/ext/eppo_client/Cargo.toml +19 -0
- data/ext/eppo_client/build.rs +5 -0
- data/ext/eppo_client/extconf.rb +6 -0
- data/ext/eppo_client/src/client.rs +119 -0
- data/ext/eppo_client/src/lib.rs +26 -0
- data/lib/eppo_client/assignment_logger.rb +7 -3
- data/lib/eppo_client/client.rb +99 -196
- data/lib/eppo_client/config.rb +4 -4
- data/lib/eppo_client/custom_errors.rb +0 -17
- data/lib/eppo_client/validation.rb +2 -2
- data/lib/eppo_client/version.rb +1 -1
- data/lib/eppo_client.rb +7 -45
- data/sig/eppo_server_sdk.rbs +96 -0
- metadata +30 -176
- data/lib/eppo_client/configuration_requestor.rb +0 -108
- data/lib/eppo_client/configuration_store.rb +0 -35
- data/lib/eppo_client/constants.rb +0 -20
- data/lib/eppo_client/http_client.rb +0 -75
- data/lib/eppo_client/lru_cache.rb +0 -28
- data/lib/eppo_client/poller.rb +0 -48
- data/lib/eppo_client/rules.rb +0 -119
- data/lib/eppo_client/shard.rb +0 -30
- data/lib/eppo_client/variation_type.rb +0 -39
data/lib/eppo_client/config.rb
CHANGED
@@ -1,21 +1,21 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative
|
4
|
-
require_relative
|
3
|
+
require_relative "validation"
|
4
|
+
require_relative "assignment_logger"
|
5
5
|
|
6
6
|
module EppoClient
|
7
7
|
# The class for configuring the Eppo client singleton
|
8
8
|
class Config
|
9
9
|
attr_reader :api_key, :assignment_logger, :base_url
|
10
10
|
|
11
|
-
def initialize(api_key, assignment_logger: AssignmentLogger.new, base_url:
|
11
|
+
def initialize(api_key, assignment_logger: AssignmentLogger.new, base_url: EppoClient::Core::DEFAULT_BASE_URL)
|
12
12
|
@api_key = api_key
|
13
13
|
@assignment_logger = assignment_logger
|
14
14
|
@base_url = base_url
|
15
15
|
end
|
16
16
|
|
17
17
|
def validate
|
18
|
-
EppoClient.validate_not_blank(
|
18
|
+
EppoClient.validate_not_blank("api_key", @api_key)
|
19
19
|
end
|
20
20
|
|
21
21
|
# Hide instance variables (specifically api_key) from logs
|
@@ -8,23 +8,6 @@ module EppoClient
|
|
8
8
|
end
|
9
9
|
end
|
10
10
|
|
11
|
-
# A custom error class for unauthorized requests
|
12
|
-
class UnauthorizedError < StandardError
|
13
|
-
def initialize(message)
|
14
|
-
super("Unauthorized: #{message}")
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
# A custom error class for HTTP requests
|
19
|
-
class HttpRequestError < StandardError
|
20
|
-
attr_reader :status_code
|
21
|
-
|
22
|
-
def initialize(message, status_code)
|
23
|
-
@status_code = status_code
|
24
|
-
super("HttpRequestError: #{message}")
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
11
|
# A custom error class for invalid values
|
29
12
|
class InvalidValueError < StandardError
|
30
13
|
def initialize(message)
|
@@ -1,13 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative
|
3
|
+
require_relative "custom_errors"
|
4
4
|
|
5
5
|
# The helper module to validate keys
|
6
6
|
module EppoClient
|
7
7
|
module_function
|
8
8
|
|
9
9
|
def validate_not_blank(field_name, field_value)
|
10
|
-
(field_value.nil? || field_value ==
|
10
|
+
(field_value.nil? || field_value == "") && raise(
|
11
11
|
EppoClient::InvalidValueError, "#{field_name} cannot be blank"
|
12
12
|
)
|
13
13
|
end
|
data/lib/eppo_client/version.rb
CHANGED
data/lib/eppo_client.rb
CHANGED
@@ -1,53 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative
|
4
|
-
require_relative
|
5
|
-
require_relative 'eppo_client/poller'
|
6
|
-
require_relative 'eppo_client/config'
|
7
|
-
require_relative 'eppo_client/client'
|
8
|
-
require_relative 'eppo_client/constants'
|
9
|
-
require_relative 'eppo_client/configuration_requestor'
|
10
|
-
require_relative 'eppo_client/configuration_store'
|
11
|
-
require_relative 'eppo_client/version'
|
3
|
+
require_relative "eppo_client/client"
|
4
|
+
require_relative "eppo_client/version"
|
12
5
|
|
13
|
-
#
|
6
|
+
# EppoClient is the main module for initializing the Eppo client.
|
7
|
+
# It provides a method to initialize the client with a given configuration.
|
14
8
|
module EppoClient
|
15
|
-
# rubocop:disable Metrics/MethodLength
|
16
|
-
def initialize_client(config_requestor, assignment_logger)
|
17
|
-
client = EppoClient::Client.instance
|
18
|
-
!client.poller.nil? && client.shutdown
|
19
|
-
client.config_requestor = config_requestor
|
20
|
-
client.assignment_logger = assignment_logger
|
21
|
-
client.poller = EppoClient::Poller.new(
|
22
|
-
EppoClient::POLL_INTERVAL_MILLIS,
|
23
|
-
EppoClient::POLL_JITTER_MILLIS,
|
24
|
-
proc { client.config_requestor.fetch_and_store_configurations }
|
25
|
-
)
|
26
|
-
client.poller.start
|
27
|
-
client
|
28
|
-
end
|
29
|
-
# rubocop:enable Metrics/MethodLength
|
30
|
-
|
31
|
-
# rubocop:disable Metrics/MethodLength
|
32
9
|
def init(config)
|
33
|
-
|
34
|
-
|
35
|
-
EppoClient::VERSION)
|
36
|
-
http_client = EppoClient::HttpClient.new(config.base_url,
|
37
|
-
sdk_params.formatted)
|
38
|
-
config_store = EppoClient::ConfigurationStore.new(
|
39
|
-
EppoClient::MAX_CACHE_ENTRIES
|
40
|
-
)
|
41
|
-
config_store.lock.with_write_lock do
|
42
|
-
EppoClient.initialize_client(
|
43
|
-
EppoClient::ExperimentConfigurationRequestor.new(
|
44
|
-
http_client, config_store
|
45
|
-
),
|
46
|
-
config.assignment_logger
|
47
|
-
)
|
48
|
-
end
|
10
|
+
client = EppoClient::Client.instance
|
11
|
+
client.init(config)
|
49
12
|
end
|
50
|
-
# rubocop:enable Metrics/MethodLength
|
51
13
|
|
52
|
-
module_function :init
|
14
|
+
module_function :init
|
53
15
|
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
# EppoClient is the main module for initializing the Eppo client.
|
2
|
+
# It provides a method to initialize the client with a given configuration.
|
3
|
+
module EppoClient
|
4
|
+
def self.init: (Config config) -> void
|
5
|
+
|
6
|
+
# The base assignment logger class to override
|
7
|
+
class AssignmentLogger
|
8
|
+
def log_assignment: (untyped assignment_event) -> void
|
9
|
+
|
10
|
+
def log_bandit_action: (untyped assignment_event) -> void
|
11
|
+
end
|
12
|
+
|
13
|
+
# The main client singleton
|
14
|
+
class Client
|
15
|
+
@assignment_logger: AssignmentLogger
|
16
|
+
@core: Core::Client
|
17
|
+
|
18
|
+
include Singleton
|
19
|
+
|
20
|
+
attr_accessor assignment_logger: AssignmentLogger
|
21
|
+
|
22
|
+
def self.instance: () -> Client
|
23
|
+
|
24
|
+
def init: (Config config) -> void
|
25
|
+
|
26
|
+
def shutdown: () -> void
|
27
|
+
|
28
|
+
def get_string_assignment: (String flag_key, String subject_key, Hash[String, untyped] subject_attributes, String default_value) -> String
|
29
|
+
|
30
|
+
def get_numeric_assignment: (String flag_key, String subject_key, Hash[String, untyped] subject_attributes, Numeric default_value) -> Numeric
|
31
|
+
|
32
|
+
def get_integer_assignment: (String flag_key, String subject_key, Hash[String, untyped] subject_attributes, Integer default_value) -> Integer
|
33
|
+
|
34
|
+
def get_boolean_assignment: (String flag_key, String subject_key, Hash[String, untyped] subject_attributes, bool default_value) -> bool
|
35
|
+
|
36
|
+
def get_json_assignment: (String flag_key, String subject_key, Hash[String, untyped] subject_attributes, Object default_value) -> Object
|
37
|
+
|
38
|
+
def get_bandit_action: (String flag_key, String subject_key, Hash[String, untyped] subject_attributes, Hash[String, untyped] actions, String default_variation) -> { variation: untyped, action: untyped }
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
# rubocop:disable Metrics/MethodLength
|
43
|
+
def get_assignment_inner: (untyped flag_key, String subject_key, untyped subject_attributes, untyped expected_type, untyped default_value) -> untyped
|
44
|
+
|
45
|
+
def log_assignment: (untyped event) -> void
|
46
|
+
|
47
|
+
def log_bandit_action: (untyped event) -> void
|
48
|
+
|
49
|
+
def enrich_event_metadata: (untyped event) -> void
|
50
|
+
|
51
|
+
def coerce_context_attributes: (untyped attributes) -> untyped
|
52
|
+
end
|
53
|
+
|
54
|
+
# The class for configuring the Eppo client singleton
|
55
|
+
class Config
|
56
|
+
@api_key: String
|
57
|
+
@assignment_logger: AssignmentLogger
|
58
|
+
@base_url: String
|
59
|
+
|
60
|
+
attr_reader api_key: String
|
61
|
+
attr_reader assignment_logger: AssignmentLogger
|
62
|
+
attr_reader base_url: String
|
63
|
+
|
64
|
+
def initialize: (String api_key, ?assignment_logger: AssignmentLogger, ?base_url: String) -> void
|
65
|
+
|
66
|
+
def validate: () -> void
|
67
|
+
|
68
|
+
# Hide instance variables (specifically api_key) from logs
|
69
|
+
def inspect: () -> ::String
|
70
|
+
end
|
71
|
+
|
72
|
+
# A custom error class for AssignmentLogger
|
73
|
+
class AssignmentLoggerError < StandardError
|
74
|
+
def initialize: (String message) -> void
|
75
|
+
end
|
76
|
+
|
77
|
+
# A custom error class for invalid values
|
78
|
+
class InvalidValueError < StandardError
|
79
|
+
def initialize: (String message) -> void
|
80
|
+
end
|
81
|
+
|
82
|
+
def self?.validate_not_blank: (String field_name, String field_value) -> void
|
83
|
+
|
84
|
+
VERSION: String
|
85
|
+
end
|
86
|
+
|
87
|
+
# Exposed from Rust
|
88
|
+
module EppoClient::Core
|
89
|
+
DEFAULT_BASE_URL: String
|
90
|
+
class Client
|
91
|
+
def self.new: (untyped config) -> Client
|
92
|
+
def shutdown: () -> void
|
93
|
+
def get_assignment: (String flag_key, String subject_key, untyped subject_attributes, String expected_type) -> untyped
|
94
|
+
def get_bandit_action: (String flag_key, String subject_key, untyped attributes, untyped actions, String default_variation) -> untyped
|
95
|
+
end
|
96
|
+
end
|
metadata
CHANGED
@@ -1,191 +1,45 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: eppo-server-sdk
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 3.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Eppo
|
8
|
-
autorequire:
|
9
|
-
bindir:
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-07-
|
12
|
-
dependencies:
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
requirements:
|
17
|
-
- - "~>"
|
18
|
-
- !ruby/object:Gem::Version
|
19
|
-
version: '1.1'
|
20
|
-
- - ">="
|
21
|
-
- !ruby/object:Gem::Version
|
22
|
-
version: 1.1.9
|
23
|
-
type: :runtime
|
24
|
-
prerelease: false
|
25
|
-
version_requirements: !ruby/object:Gem::Requirement
|
26
|
-
requirements:
|
27
|
-
- - "~>"
|
28
|
-
- !ruby/object:Gem::Version
|
29
|
-
version: '1.1'
|
30
|
-
- - ">="
|
31
|
-
- !ruby/object:Gem::Version
|
32
|
-
version: 1.1.9
|
33
|
-
- !ruby/object:Gem::Dependency
|
34
|
-
name: faraday
|
35
|
-
requirement: !ruby/object:Gem::Requirement
|
36
|
-
requirements:
|
37
|
-
- - "~>"
|
38
|
-
- !ruby/object:Gem::Version
|
39
|
-
version: '2.7'
|
40
|
-
- - ">="
|
41
|
-
- !ruby/object:Gem::Version
|
42
|
-
version: 2.7.1
|
43
|
-
type: :runtime
|
44
|
-
prerelease: false
|
45
|
-
version_requirements: !ruby/object:Gem::Requirement
|
46
|
-
requirements:
|
47
|
-
- - "~>"
|
48
|
-
- !ruby/object:Gem::Version
|
49
|
-
version: '2.7'
|
50
|
-
- - ">="
|
51
|
-
- !ruby/object:Gem::Version
|
52
|
-
version: 2.7.1
|
53
|
-
- !ruby/object:Gem::Dependency
|
54
|
-
name: faraday-retry
|
55
|
-
requirement: !ruby/object:Gem::Requirement
|
56
|
-
requirements:
|
57
|
-
- - "~>"
|
58
|
-
- !ruby/object:Gem::Version
|
59
|
-
version: '2.0'
|
60
|
-
- - ">="
|
61
|
-
- !ruby/object:Gem::Version
|
62
|
-
version: 2.0.0
|
63
|
-
type: :runtime
|
64
|
-
prerelease: false
|
65
|
-
version_requirements: !ruby/object:Gem::Requirement
|
66
|
-
requirements:
|
67
|
-
- - "~>"
|
68
|
-
- !ruby/object:Gem::Version
|
69
|
-
version: '2.0'
|
70
|
-
- - ">="
|
71
|
-
- !ruby/object:Gem::Version
|
72
|
-
version: 2.0.0
|
73
|
-
- !ruby/object:Gem::Dependency
|
74
|
-
name: semver2
|
75
|
-
requirement: !ruby/object:Gem::Requirement
|
76
|
-
requirements:
|
77
|
-
- - "~>"
|
78
|
-
- !ruby/object:Gem::Version
|
79
|
-
version: '3.4'
|
80
|
-
- - ">="
|
81
|
-
- !ruby/object:Gem::Version
|
82
|
-
version: 3.4.2
|
83
|
-
type: :runtime
|
84
|
-
prerelease: false
|
85
|
-
version_requirements: !ruby/object:Gem::Requirement
|
86
|
-
requirements:
|
87
|
-
- - "~>"
|
88
|
-
- !ruby/object:Gem::Version
|
89
|
-
version: '3.4'
|
90
|
-
- - ">="
|
91
|
-
- !ruby/object:Gem::Version
|
92
|
-
version: 3.4.2
|
93
|
-
- !ruby/object:Gem::Dependency
|
94
|
-
name: rake
|
95
|
-
requirement: !ruby/object:Gem::Requirement
|
96
|
-
requirements:
|
97
|
-
- - "~>"
|
98
|
-
- !ruby/object:Gem::Version
|
99
|
-
version: '13.0'
|
100
|
-
- - ">="
|
101
|
-
- !ruby/object:Gem::Version
|
102
|
-
version: 13.0.6
|
103
|
-
type: :development
|
104
|
-
prerelease: false
|
105
|
-
version_requirements: !ruby/object:Gem::Requirement
|
106
|
-
requirements:
|
107
|
-
- - "~>"
|
108
|
-
- !ruby/object:Gem::Version
|
109
|
-
version: '13.0'
|
110
|
-
- - ">="
|
111
|
-
- !ruby/object:Gem::Version
|
112
|
-
version: 13.0.6
|
113
|
-
- !ruby/object:Gem::Dependency
|
114
|
-
name: rspec
|
115
|
-
requirement: !ruby/object:Gem::Requirement
|
116
|
-
requirements:
|
117
|
-
- - "~>"
|
118
|
-
- !ruby/object:Gem::Version
|
119
|
-
version: '3.12'
|
120
|
-
- - ">="
|
121
|
-
- !ruby/object:Gem::Version
|
122
|
-
version: 3.12.0
|
123
|
-
type: :development
|
124
|
-
prerelease: false
|
125
|
-
version_requirements: !ruby/object:Gem::Requirement
|
126
|
-
requirements:
|
127
|
-
- - "~>"
|
128
|
-
- !ruby/object:Gem::Version
|
129
|
-
version: '3.12'
|
130
|
-
- - ">="
|
131
|
-
- !ruby/object:Gem::Version
|
132
|
-
version: 3.12.0
|
133
|
-
- !ruby/object:Gem::Dependency
|
134
|
-
name: rubocop
|
135
|
-
requirement: !ruby/object:Gem::Requirement
|
136
|
-
requirements:
|
137
|
-
- - "~>"
|
138
|
-
- !ruby/object:Gem::Version
|
139
|
-
version: 0.82.0
|
140
|
-
type: :development
|
141
|
-
prerelease: false
|
142
|
-
version_requirements: !ruby/object:Gem::Requirement
|
143
|
-
requirements:
|
144
|
-
- - "~>"
|
145
|
-
- !ruby/object:Gem::Version
|
146
|
-
version: 0.82.0
|
147
|
-
- !ruby/object:Gem::Dependency
|
148
|
-
name: webmock
|
149
|
-
requirement: !ruby/object:Gem::Requirement
|
150
|
-
requirements:
|
151
|
-
- - "~>"
|
152
|
-
- !ruby/object:Gem::Version
|
153
|
-
version: '3.18'
|
154
|
-
- - ">="
|
155
|
-
- !ruby/object:Gem::Version
|
156
|
-
version: 3.18.1
|
157
|
-
type: :development
|
158
|
-
prerelease: false
|
159
|
-
version_requirements: !ruby/object:Gem::Requirement
|
160
|
-
requirements:
|
161
|
-
- - "~>"
|
162
|
-
- !ruby/object:Gem::Version
|
163
|
-
version: '3.18'
|
164
|
-
- - ">="
|
165
|
-
- !ruby/object:Gem::Version
|
166
|
-
version: 3.18.1
|
167
|
-
description:
|
168
|
-
email: eppo-team@geteppo.com
|
11
|
+
date: 2024-07-24 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description:
|
14
|
+
email:
|
15
|
+
- eppo-team@geteppo.com
|
169
16
|
executables: []
|
170
|
-
extensions:
|
17
|
+
extensions:
|
18
|
+
- ext/eppo_client/Cargo.toml
|
171
19
|
extra_rdoc_files: []
|
172
20
|
files:
|
21
|
+
- ".gitignore"
|
22
|
+
- ".rspec"
|
23
|
+
- ".rubocop.yml"
|
24
|
+
- Cargo.lock
|
25
|
+
- Cargo.toml
|
26
|
+
- LICENSE.txt
|
27
|
+
- README.md
|
28
|
+
- Rakefile
|
29
|
+
- Steepfile
|
30
|
+
- ext/eppo_client/Cargo.toml
|
31
|
+
- ext/eppo_client/build.rs
|
32
|
+
- ext/eppo_client/extconf.rb
|
33
|
+
- ext/eppo_client/src/client.rs
|
34
|
+
- ext/eppo_client/src/lib.rs
|
173
35
|
- lib/eppo_client.rb
|
174
36
|
- lib/eppo_client/assignment_logger.rb
|
175
37
|
- lib/eppo_client/client.rb
|
176
38
|
- lib/eppo_client/config.rb
|
177
|
-
- lib/eppo_client/configuration_requestor.rb
|
178
|
-
- lib/eppo_client/configuration_store.rb
|
179
|
-
- lib/eppo_client/constants.rb
|
180
39
|
- lib/eppo_client/custom_errors.rb
|
181
|
-
- lib/eppo_client/http_client.rb
|
182
|
-
- lib/eppo_client/lru_cache.rb
|
183
|
-
- lib/eppo_client/poller.rb
|
184
|
-
- lib/eppo_client/rules.rb
|
185
|
-
- lib/eppo_client/shard.rb
|
186
40
|
- lib/eppo_client/validation.rb
|
187
|
-
- lib/eppo_client/variation_type.rb
|
188
41
|
- lib/eppo_client/version.rb
|
42
|
+
- sig/eppo_server_sdk.rbs
|
189
43
|
homepage: https://github.com/Eppo-exp/ruby-sdk
|
190
44
|
licenses:
|
191
45
|
- MIT
|
@@ -195,7 +49,7 @@ metadata:
|
|
195
49
|
homepage_uri: https://geteppo.com/
|
196
50
|
source_code_uri: https://github.com/Eppo-exp/ruby-sdk
|
197
51
|
wiki_uri: https://github.com/Eppo-exp/ruby-sdk/wiki
|
198
|
-
post_install_message:
|
52
|
+
post_install_message:
|
199
53
|
rdoc_options: []
|
200
54
|
require_paths:
|
201
55
|
- lib
|
@@ -203,15 +57,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
203
57
|
requirements:
|
204
58
|
- - ">="
|
205
59
|
- !ruby/object:Gem::Version
|
206
|
-
version: 3.0.
|
60
|
+
version: 3.0.0
|
207
61
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
208
62
|
requirements:
|
209
63
|
- - ">="
|
210
64
|
- !ruby/object:Gem::Version
|
211
|
-
version:
|
65
|
+
version: 3.3.11
|
212
66
|
requirements: []
|
213
|
-
rubygems_version: 3.
|
214
|
-
signing_key:
|
67
|
+
rubygems_version: 3.5.9
|
68
|
+
signing_key:
|
215
69
|
specification_version: 4
|
216
70
|
summary: Eppo SDK for Ruby
|
217
71
|
test_files: []
|
@@ -1,108 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative 'custom_errors'
|
4
|
-
require_relative 'constants'
|
5
|
-
|
6
|
-
module EppoClient
|
7
|
-
# A class for the variation object
|
8
|
-
class VariationDto
|
9
|
-
attr_reader :name, :value, :typed_value, :shard_range
|
10
|
-
|
11
|
-
def initialize(name, value, typed_value, shard_range)
|
12
|
-
@name = name
|
13
|
-
@value = value
|
14
|
-
@typed_value = typed_value
|
15
|
-
@shard_range = shard_range
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
# A class for the allocation object
|
20
|
-
class AllocationDto
|
21
|
-
attr_reader :percent_exposure, :variations
|
22
|
-
|
23
|
-
def initialize(percent_exposure, variations)
|
24
|
-
@percent_exposure = percent_exposure
|
25
|
-
@variations = variations
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
# A class for the experiment configuration object
|
30
|
-
class ExperimentConfigurationDto
|
31
|
-
attr_reader :subject_shards, :enabled, :name, :overrides,
|
32
|
-
:typed_overrides, :rules, :allocations
|
33
|
-
|
34
|
-
def initialize(exp_config)
|
35
|
-
@subject_shards = exp_config['subjectShards']
|
36
|
-
@enabled = exp_config['enabled']
|
37
|
-
@name = exp_config['name'] || nil
|
38
|
-
@overrides = exp_config['overrides'] || {}
|
39
|
-
@typed_overrides = exp_config['typedOverrides'] || {}
|
40
|
-
@rules = exp_config['rules'] || []
|
41
|
-
@allocations = exp_config['allocations']
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
# A class for getting exp configs from the local cache or API
|
46
|
-
class ExperimentConfigurationRequestor
|
47
|
-
attr_reader :config_store
|
48
|
-
|
49
|
-
def initialize(http_client, config_store)
|
50
|
-
@http_client = http_client
|
51
|
-
@config_store = config_store
|
52
|
-
end
|
53
|
-
|
54
|
-
def get_configuration(experiment_key)
|
55
|
-
@http_client.is_unauthorized && raise(EppoClient::UnauthorizedError,
|
56
|
-
'please check your API key')
|
57
|
-
@config_store.retrieve_configuration(experiment_key)
|
58
|
-
end
|
59
|
-
|
60
|
-
# rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
61
|
-
def fetch_and_store_configurations
|
62
|
-
configs = {}
|
63
|
-
begin
|
64
|
-
exp_configs = @http_client.get(EppoClient::RAC_ENDPOINT).fetch(
|
65
|
-
'flags', {}
|
66
|
-
)
|
67
|
-
# rubocop: disable Metrics/BlockLength
|
68
|
-
exp_configs.each do |exp_key, exp_config|
|
69
|
-
exp_config['allocations'].each do |k, v|
|
70
|
-
exp_config['allocations'][k] = EppoClient::AllocationDto.new(
|
71
|
-
v['percentExposure'],
|
72
|
-
v['variations'].map do |var|
|
73
|
-
EppoClient::VariationDto.new(
|
74
|
-
var['name'], var['value'], var['typedValue'],
|
75
|
-
EppoClient::ShardRange.new(var['shardRange']['start'],
|
76
|
-
var['shardRange']['end'])
|
77
|
-
)
|
78
|
-
end
|
79
|
-
)
|
80
|
-
end
|
81
|
-
exp_config['rules'] = exp_config['rules'].map do |rule|
|
82
|
-
EppoClient::Rule.new(
|
83
|
-
conditions: rule['conditions'].map do |condition|
|
84
|
-
EppoClient::Condition.new(
|
85
|
-
value: condition['value'],
|
86
|
-
operator: condition['operator'],
|
87
|
-
attribute: condition['attribute']
|
88
|
-
)
|
89
|
-
end,
|
90
|
-
allocation_key: rule['allocationKey']
|
91
|
-
)
|
92
|
-
end
|
93
|
-
configs[exp_key] = EppoClient::ExperimentConfigurationDto.new(
|
94
|
-
exp_config
|
95
|
-
)
|
96
|
-
end
|
97
|
-
# rubocop: enable Metrics/BlockLength
|
98
|
-
@config_store.assign_configurations(configs)
|
99
|
-
rescue EppoClient::HttpRequestError => e
|
100
|
-
Logger.new($stdout).error(
|
101
|
-
"Error retrieving assignment configurations: #{e}"
|
102
|
-
)
|
103
|
-
end
|
104
|
-
configs
|
105
|
-
end
|
106
|
-
# rubocop:enable Metrics/MethodLength, Metrics/AbcSize
|
107
|
-
end
|
108
|
-
end
|
@@ -1,35 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'concurrent/atomic/read_write_lock'
|
4
|
-
|
5
|
-
require_relative 'lru_cache'
|
6
|
-
|
7
|
-
module EppoClient
|
8
|
-
# A thread safe store for the configurations to ensure that retrievals pull from a single source of truth
|
9
|
-
class ConfigurationStore
|
10
|
-
attr_reader :lock, :cache
|
11
|
-
|
12
|
-
def initialize(max_size)
|
13
|
-
@cache = EppoClient::LRUCache.new(max_size)
|
14
|
-
@lock = Concurrent::ReadWriteLock.new
|
15
|
-
end
|
16
|
-
|
17
|
-
def retrieve_configuration(key)
|
18
|
-
@lock.with_read_lock { @cache[key] }
|
19
|
-
end
|
20
|
-
|
21
|
-
def assign_configurations(configs)
|
22
|
-
@lock.with_write_lock do
|
23
|
-
# Create a temporary new cache and populate it.
|
24
|
-
new_cache = EppoClient::LRUCache.new(@cache.size)
|
25
|
-
configs.each do |key, config|
|
26
|
-
new_cache[key] = config
|
27
|
-
end
|
28
|
-
|
29
|
-
# Replace the old cache with the new one.
|
30
|
-
# Performs an atomic swap.
|
31
|
-
@cache = new_cache
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
@@ -1,20 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'logger'
|
4
|
-
|
5
|
-
module EppoClient
|
6
|
-
# default level for logging
|
7
|
-
DEFAULT_LOGGER_LEVEL = Logger::INFO
|
8
|
-
|
9
|
-
# configuration cache constants
|
10
|
-
MAX_CACHE_ENTRIES = 1000 # arbitrary; the caching library requires a max limit
|
11
|
-
|
12
|
-
# poller constants
|
13
|
-
SECOND_MILLIS = 1000
|
14
|
-
MINUTE_MILLIS = 60 * SECOND_MILLIS
|
15
|
-
POLL_JITTER_MILLIS = 30 * SECOND_MILLIS
|
16
|
-
POLL_INTERVAL_MILLIS = 5 * MINUTE_MILLIS
|
17
|
-
|
18
|
-
# the configs endpoint
|
19
|
-
RAC_ENDPOINT = 'randomized_assignment/v3/config'
|
20
|
-
end
|