eppo-server-sdk 0.3.0 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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