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.
@@ -1,21 +1,21 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'validation'
4
- require_relative 'assignment_logger'
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: 'https://fscdn.eppo.cloud/api')
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('api_key', @api_key)
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 'custom_errors'
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 == '') && raise(
10
+ (field_value.nil? || field_value == "") && raise(
11
11
  EppoClient::InvalidValueError, "#{field_name} cannot be blank"
12
12
  )
13
13
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module EppoClient
4
- VERSION = '0.3.0'
4
+ VERSION = "3.0.0"
5
5
  end
data/lib/eppo_client.rb CHANGED
@@ -1,53 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'eppo_client/assignment_logger'
4
- require_relative 'eppo_client/http_client'
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
- # This module scopes all the client SDK's classes and functions
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
- config.validate
34
- sdk_params = EppoClient::SdkParams.new(config.api_key, 'ruby',
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, :initialize_client
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: 0.3.0
4
+ version: 3.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Eppo
8
- autorequire:
9
- bindir: bin
8
+ autorequire:
9
+ bindir: exe
10
10
  cert_chain: []
11
- date: 2024-07-01 00:00:00.000000000 Z
12
- dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: concurrent-ruby
15
- requirement: !ruby/object:Gem::Requirement
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.6
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: '0'
65
+ version: 3.3.11
212
66
  requirements: []
213
- rubygems_version: 3.4.6
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