eppo-server-sdk 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: ce6db52c824656217a079b75231b2749b10bbbd656bda249e2f103666b173acb
4
+ data.tar.gz: 4629ce299c56cbcbfe342e7572803c935266172b376ef1c8aa1eac8f0bb8074b
5
+ SHA512:
6
+ metadata.gz: 29de3064e5d780257e0e883295a6101b86e321ba6203dcbcc21e46192cc853156faab52031782b323be2cf37fe6e045345a5099acdb3ead7f6383fbcf5396f2c
7
+ data.tar.gz: c3a669f35584ecac1b4f12e4796b61de972bb2f117cfd95e5cd3c693fb9f53af3bf232d9800a3a519245408576978037371394ff4b9931655850f38b80e8f0bb
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'custom_errors'
4
+
5
+ module EppoClient
6
+ # The base assignment logger class to override
7
+ class AssignmentLogger
8
+ def log_assignment(_assignment)
9
+ raise(EppoClient::AssignmentLoggerError, 'log_assignment has not been set up')
10
+ end
11
+ end
12
+ end
data/lib/client.rb ADDED
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'singleton'
4
+ require 'time'
5
+
6
+ require 'validation'
7
+ require 'rules'
8
+ require 'shard'
9
+ require 'sdk_logger'
10
+ require 'custom_errors'
11
+
12
+ module EppoClient
13
+ # The main client singleton
14
+ class Client
15
+ include Singleton
16
+ attr_accessor :config_requestor, :assignment_logger, :poller
17
+
18
+ def instance
19
+ Client.instance
20
+ end
21
+
22
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
23
+ def get_assignment(subject_key, flag_or_experiment_key, subject_attributes = {})
24
+ EppoClient.validate_not_blank('subject_key', subject_key)
25
+ EppoClient.validate_not_blank('flag_or_experiment_key', flag_or_experiment_key)
26
+ experiment_config = @config_requestor.get_configuration(flag_or_experiment_key)
27
+ override = get_subject_variation_override(experiment_config, subject_key)
28
+ return override unless override.nil?
29
+
30
+ if experiment_config.nil? || experiment_config.enabled == false
31
+ EppoClient.logger('out').info(
32
+ "[Eppo SDK] No assigned variation. No active experiment or flag for key: #{flag_or_experiment_key}"
33
+ )
34
+ return nil
35
+ end
36
+
37
+ matched_rule = EppoClient.find_matching_rule(subject_attributes, experiment_config.rules)
38
+ if matched_rule.nil?
39
+ EppoClient.logger('out').info(
40
+ "[Eppo SDK] No assigned variation. Subject attributes do not match targeting rules: #{subject_attributes}"
41
+ )
42
+ return nil
43
+ end
44
+
45
+ allocation = experiment_config.allocations[matched_rule.allocation_key]
46
+ unless in_experiment_sample?(
47
+ subject_key,
48
+ flag_or_experiment_key,
49
+ experiment_config.subject_shards,
50
+ allocation.percent_exposure
51
+ )
52
+ EppoClient.logger('out').info(
53
+ '[Eppo SDK] No assigned variation. Subject is not part of experiment sample population'
54
+ )
55
+ return nil
56
+ end
57
+
58
+ shard = EppoClient.get_shard("assignment-#{subject_key}-#{flag_or_experiment_key}", experiment_config.subject_shards)
59
+ assigned_variation = allocation.variations.find { |var| var.shard_range.shard_in_range?(shard) }.value
60
+
61
+ assignment_event = {
62
+ "experiment": flag_or_experiment_key,
63
+ "variation": assigned_variation,
64
+ "subject": subject_key,
65
+ "timestamp": Time.now.utc.iso8601,
66
+ "subjectAttributes": subject_attributes
67
+ }
68
+
69
+ begin
70
+ @assignment_logger.log_assignment(assignment_event)
71
+ rescue EppoClient::AssignmentLoggerError => e
72
+ # This error means that log_assignment was not set up. This is okay to ignore.
73
+ rescue StandardError => e
74
+ EppoClient.logger('err').info("[Eppo SDK] Error logging assignment event: #{e}")
75
+ end
76
+
77
+ assigned_variation
78
+ end
79
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
80
+
81
+ def shutdown
82
+ @poller.stop
83
+ end
84
+
85
+ def get_subject_variation_override(experiment_config, subject)
86
+ subject_hash = Digest::MD5.hexdigest(subject.to_s)
87
+ experiment_config&.overrides && experiment_config.overrides[subject_hash]
88
+ end
89
+
90
+ def in_experiment_sample?(subject, experiment_key, subject_shards, percent_exposure)
91
+ shard = EppoClient.get_shard("exposure-#{subject}-#{experiment_key}", subject_shards)
92
+ shard <= percent_exposure * subject_shards
93
+ end
94
+ end
95
+ end
data/lib/config.rb ADDED
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'validation'
4
+ require '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
10
+
11
+ def initialize(api_key, assignment_logger: AssignmentLogger.new, base_url: 'https://eppo.cloud/api')
12
+ @api_key = api_key
13
+ @assignment_logger = assignment_logger
14
+ @base_url = base_url
15
+ end
16
+
17
+ def validate
18
+ EppoClient.validate_not_blank('api_key', @api_key)
19
+ end
20
+
21
+ # Hide instance variables (specifically api_key) from logs
22
+ def inspect
23
+ "#<EppoClient::Config:#{object_id}>"
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'sdk_logger'
4
+ require 'custom_errors'
5
+ require 'constants'
6
+
7
+ module EppoClient
8
+ # A class for the variation object
9
+ class VariationDto
10
+ attr_reader :name, :value, :shard_range
11
+
12
+ def initialize(name, value, shard_range)
13
+ @name = name
14
+ @value = 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, :rules, :allocations
32
+
33
+ def initialize(exp_config)
34
+ @subject_shards = exp_config['subjectShards']
35
+ @enabled = exp_config['enabled']
36
+ @name = exp_config['name'] || nil
37
+ @overrides = exp_config['overrides'] || {}
38
+ @rules = exp_config['rules'] || []
39
+ @allocations = exp_config['allocations']
40
+ end
41
+ end
42
+
43
+ # A class for getting exp configs from the local cache or API
44
+ class ExperimentConfigurationRequestor
45
+
46
+ attr_reader :config_store
47
+
48
+ def initialize(http_client, config_store)
49
+ @http_client = http_client
50
+ @config_store = config_store
51
+ end
52
+
53
+ def get_configuration(experiment_key)
54
+ @http_client.is_unauthorized && raise(EppoClient::UnauthorizedError, 'please check your API key')
55
+ @config_store.retrieve_configuration(experiment_key)
56
+ end
57
+
58
+ # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
59
+ def fetch_and_store_configurations
60
+ configs = {}
61
+ begin
62
+ exp_configs = @http_client.get(EppoClient::RAC_ENDPOINT).fetch('flags', {})
63
+ exp_configs.each do |exp_key, exp_config|
64
+ exp_config['allocations'].each do |k, v|
65
+ exp_config['allocations'][k] = EppoClient::AllocationDto.new(
66
+ v['percentExposure'],
67
+ v['variations'].map do |var|
68
+ EppoClient::VariationDto.new(
69
+ var['name'],
70
+ var['value'],
71
+ EppoClient::ShardRange.new(var['shardRange']['start'], var['shardRange']['end'])
72
+ )
73
+ end
74
+ )
75
+ end
76
+ exp_config['rules'] = exp_config['rules'].map do |rule|
77
+ EppoClient::Rule.new(
78
+ conditions: rule['conditions'].map do |condition|
79
+ EppoClient::Condition.new(
80
+ value: condition['value'],
81
+ operator: condition['operator'],
82
+ attribute: condition['attribute']
83
+ )
84
+ end,
85
+ allocation_key: rule['allocationKey']
86
+ )
87
+ end
88
+ configs[exp_key] = EppoClient::ExperimentConfigurationDto.new(exp_config)
89
+ end
90
+ @config_store.assign_configurations(configs)
91
+ rescue EppoClient::HttpRequestError => e
92
+ EppoClient.logger('err').error("Error retrieving assignment configurations: #{e}")
93
+ end
94
+ configs
95
+ end
96
+ # rubocop:enable Metrics/MethodLength, Metrics/AbcSize
97
+ end
98
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'concurrent/atomic/read_write_lock'
4
+
5
+ require '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
+ configs.each do |key, config|
24
+ @cache[key] = config
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
data/lib/constants.rb ADDED
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EppoClient
4
+ # configuration cache constants
5
+ MAX_CACHE_ENTRIES = 1000 # arbitrary; the caching library requires a max limit
6
+
7
+ # poller constants
8
+ SECOND_MILLIS = 1000
9
+ MINUTE_MILLIS = 60 * SECOND_MILLIS
10
+ POLL_JITTER_MILLIS = 30 * SECOND_MILLIS
11
+ POLL_INTERVAL_MILLIS = 5 * MINUTE_MILLIS
12
+
13
+ # the configs endpoint
14
+ RAC_ENDPOINT = 'randomized_assignment/v2/config'
15
+ end
@@ -0,0 +1,34 @@
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 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
+ # A custom error class for invalid values
29
+ class InvalidValueError < StandardError
30
+ def initialize(message)
31
+ super("InvalidValueError: #{message}")
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'assignment_logger'
4
+ require 'http_client'
5
+ require 'poller'
6
+ require 'config'
7
+ require 'client'
8
+ require 'constants'
9
+ require 'configuration_requestor'
10
+ require 'configuration_store'
11
+
12
+ # This module scopes all the client SDK's classes and functions
13
+ module EppoClient
14
+ @sdk_version = '1.1.1'
15
+
16
+ # rubocop:disable Metrics/MethodLength
17
+ def initialize_client(config_requestor, assignment_logger)
18
+ client = EppoClient::Client.instance
19
+ !client.poller.nil? && client.shutdown
20
+ client.config_requestor = config_requestor
21
+ client.assignment_logger = assignment_logger
22
+ client.poller = EppoClient::Poller.new(
23
+ EppoClient::POLL_INTERVAL_MILLIS,
24
+ EppoClient::POLL_JITTER_MILLIS,
25
+ proc { client.config_requestor.fetch_and_store_configurations }
26
+ )
27
+ client.poller.start
28
+ client
29
+ end
30
+ # rubocop:enable Metrics/MethodLength
31
+
32
+ def init(config)
33
+ config.validate
34
+ sdk_params = EppoClient::SdkParams.new(config.api_key, 'ruby', @sdk_version)
35
+ http_client = EppoClient::HttpClient.new(config.base_url, sdk_params.formatted)
36
+ config_store = EppoClient::ConfigurationStore.new(EppoClient::MAX_CACHE_ENTRIES)
37
+ config_store.lock.with_write_lock do
38
+ EppoClient.initialize_client(
39
+ EppoClient::ExperimentConfigurationRequestor.new(http_client, config_store),
40
+ config.assignment_logger
41
+ )
42
+ end
43
+ end
44
+
45
+ module_function :init, :initialize_client
46
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'faraday'
4
+ require 'faraday/retry'
5
+
6
+ require 'custom_errors'
7
+
8
+ REQUEST_TIMEOUT_SECONDS = 2
9
+ # This applies only to failed DNS lookups and connection timeouts,
10
+ # never to requests where data has made it to the server.
11
+ MAX_RETRIES = 3
12
+
13
+ module EppoClient
14
+ # The SDK params object
15
+ class SdkParams
16
+ attr_reader :api_key, :sdk_name, :sdk_version
17
+
18
+ def initialize(api_key, sdk_name, sdk_version)
19
+ @api_key = api_key
20
+ @sdk_name = sdk_name
21
+ @sdk_version = sdk_version
22
+ end
23
+
24
+ # attributes are camelCase because that's what the backend endpoint expects
25
+ def formatted
26
+ {
27
+ 'apiKey' => api_key,
28
+ 'sdkName' => sdk_name,
29
+ 'sdkVersion' => sdk_version
30
+ }
31
+ end
32
+
33
+ # Hide instance variables (specifically api_key) from logs
34
+ def inspect
35
+ "#<EppoClient::SdkParams:#{object_id}>"
36
+ end
37
+ end
38
+
39
+ # The http request client with retry/timeout behavior
40
+ class HttpClient
41
+ attr_reader :is_unauthorized
42
+
43
+ @retry_options = {
44
+ max: MAX_RETRIES,
45
+ interval: 0.05,
46
+ interval_randomness: 0.5,
47
+ backoff_factor: 2,
48
+ exceptions: ['Timeout::Error']
49
+ }
50
+
51
+ def initialize(base_url, sdk_params)
52
+ @base_url = base_url
53
+ @sdk_params = sdk_params
54
+ @is_unauthorized = false
55
+ end
56
+
57
+ def get(resource)
58
+ conn = Faraday::Connection.new(@base_url, params: @sdk_params) do |f|
59
+ f.request :retry, @retry_options
60
+ end
61
+ conn.options.timeout = REQUEST_TIMEOUT_SECONDS
62
+ response = conn.get(resource)
63
+ @is_unauthorized = response.status == 401
64
+ raise get_http_error(response.status, resource) if response.status != 200
65
+
66
+ JSON.parse(response.body)
67
+ end
68
+
69
+ private
70
+
71
+ def get_http_error(status_code, resource)
72
+ EppoClient::HttpRequestError.new("HTTP #{status_code} error while requesting resource #{resource}", status_code)
73
+ end
74
+ end
75
+ end
data/lib/lru_cache.rb ADDED
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EppoClient
4
+ # The LRU cache relies on the fact that Ruby's Hash class maintains insertion order. So deleting
5
+ # and re-inserting a key-value pair on access moves the key to the last position. When an
6
+ # entry is added and the cache is full, the first entry is removed.
7
+ class LRUCache
8
+ attr_reader :cache
9
+
10
+ # Creates a new LRUCache that can hold +size+ entries.
11
+ def initialize(size)
12
+ @size = size
13
+ @cache = {}
14
+ end
15
+
16
+ # Returns the stored value for +key+ or +nil+ if no value was stored under the key.
17
+ def [](key)
18
+ (val = @cache.delete(key)).nil? ? nil : @cache[key] = val
19
+ end
20
+
21
+ # Stores the +value+ under the +key+.
22
+ def []=(key, value)
23
+ @cache.delete(key)
24
+ @cache[key] = value
25
+ @cache.shift if @cache.length > @size
26
+ end
27
+ end
28
+ end
data/lib/poller.rb ADDED
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'concurrent/atom'
4
+ require 'sdk_logger'
5
+
6
+ # The poller
7
+ module EppoClient
8
+ # The poller class invokes a callback and waits on repeat on a separate thread
9
+ class Poller
10
+ def initialize(interval_millis, jitter_millis, callback)
11
+ @jitter_millis = jitter_millis
12
+ @interval = interval_millis
13
+ @stopped = Concurrent::Atom.new(false)
14
+ @callback = callback
15
+ @thread = nil
16
+ end
17
+
18
+ def start
19
+ @stopped.reset(false)
20
+ @thread = Thread.new { poll }
21
+ end
22
+
23
+ def stop
24
+ @stopped.reset(true)
25
+ Thread.kill(@thread)
26
+ end
27
+
28
+ def stopped?
29
+ @stopped.value
30
+ end
31
+
32
+ def poll
33
+ until stopped?
34
+ begin
35
+ @callback.call
36
+ rescue StandardError => e
37
+ EppoClient.logger('err').error("Unexpected error running poll task: #{e}")
38
+ break
39
+ end
40
+ _wait_for_interval
41
+ end
42
+ end
43
+
44
+ def _wait_for_interval
45
+ interval_with_jitter = @interval - rand(@jitter_millis)
46
+ sleep interval_with_jitter / 1000
47
+ end
48
+ end
49
+ end
data/lib/rules.rb ADDED
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ # The helper module for rules
4
+ module EppoClient
5
+ module OperatorType
6
+ MATCHES = 'MATCHES'
7
+ GTE = 'GTE'
8
+ GT = 'GT'
9
+ LTE = 'LTE'
10
+ LT = 'LT'
11
+ ONE_OF = 'ONE_OF'
12
+ NOT_ONE_OF = 'NOT_ONE_OF'
13
+ end
14
+
15
+ # A class for the Condition object
16
+ class Condition
17
+ attr_accessor :operator, :attribute, :value
18
+
19
+ def initialize(operator:, attribute:, value:)
20
+ @operator = operator
21
+ @attribute = attribute
22
+ @value = value
23
+ end
24
+ end
25
+
26
+ # A class for the Rule object
27
+ class Rule
28
+ attr_accessor :allocation_key, :conditions
29
+
30
+ def initialize(allocation_key:, conditions:)
31
+ @allocation_key = allocation_key
32
+ @conditions = conditions
33
+ end
34
+ end
35
+
36
+ def find_matching_rule(subject_attributes, rules)
37
+ rules.each do |rule|
38
+ return rule if matches_rule(subject_attributes, rule)
39
+ end
40
+ nil
41
+ end
42
+
43
+ def matches_rule(subject_attributes, rule)
44
+ rule.conditions.each do |condition|
45
+ return false unless evaluate_condition(subject_attributes, condition)
46
+ end
47
+ true
48
+ end
49
+
50
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
51
+ def evaluate_condition(subject_attributes, condition)
52
+ subject_value = subject_attributes[condition.attribute]
53
+ return false if subject_value.nil?
54
+
55
+ case condition.operator
56
+ when OperatorType::MATCHES
57
+ !!(Regexp.new(condition.value) =~ subject_value.to_s)
58
+ when OperatorType::ONE_OF
59
+ condition.value.map(&:downcase).include?(subject_value.to_s.downcase)
60
+ when OperatorType::NOT_ONE_OF
61
+ !condition.value.map(&:downcase).include?(subject_value.to_s.downcase)
62
+ else
63
+ subject_value.is_a?(Numeric) && evaluate_numeric_condition(subject_value, condition)
64
+ end
65
+ end
66
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
67
+
68
+ # rubocop:disable Metrics/MethodLength
69
+ def evaluate_numeric_condition(subject_value, condition)
70
+ case condition.operator
71
+ when OperatorType::GT
72
+ subject_value > condition.value
73
+ when OperatorType::GTE
74
+ subject_value >= condition.value
75
+ when OperatorType::LT
76
+ subject_value < condition.value
77
+ when OperatorType::LTE
78
+ subject_value <= condition.value
79
+ else
80
+ false
81
+ end
82
+ end
83
+ # rubocop:enable Metrics/MethodLength
84
+
85
+ module_function :find_matching_rule, :matches_rule, :evaluate_condition, :evaluate_numeric_condition
86
+ end
data/lib/sdk_logger.rb ADDED
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'logger'
4
+
5
+ # The helper module for logging
6
+ module EppoClient
7
+ @stdout_logger = Logger.new($stdout)
8
+ @stderr_logger = Logger.new($stderr)
9
+
10
+ def self.logger(type)
11
+ case type
12
+ when 'out'
13
+ @stdout_logger
14
+ when 'err'
15
+ @stderr_logger
16
+ else
17
+ @stderr_logger.error("[Eppo SDK] Invalid logger type: #{type}")
18
+ end
19
+ end
20
+ end
data/lib/shard.rb ADDED
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'digest'
4
+
5
+ # The helper module for shard logic
6
+ module EppoClient
7
+ # A class for checking if a shard is in a range
8
+ class ShardRange
9
+ attr_reader :start, :end
10
+
11
+ def initialize(range_start, range_end)
12
+ @start = range_start
13
+ @end = range_end
14
+ end
15
+
16
+ def shard_in_range?(shard)
17
+ shard >= @start && shard < @end
18
+ end
19
+ end
20
+
21
+ def get_shard(input, subject_shards)
22
+ hash_output = Digest::MD5.hexdigest(input)
23
+ # get the first 4 bytes of the md5 hex string and parse it using base 16
24
+ # (8 hex characters represent 4 bytes, e.g. 0xffffffff represents the max 4-byte integer)
25
+ int_from_hash = hash_output[0...8].to_i(16)
26
+ int_from_hash % subject_shards
27
+ end
28
+
29
+ module_function :get_shard
30
+ end
data/lib/validation.rb ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'custom_errors'
4
+
5
+ # The helper module to validate keys
6
+ module EppoClient
7
+ def validate_not_blank(field_name, field_value)
8
+ (field_value.nil? || field_value == '') && raise(EppoClient::InvalidValueError, "#{field_name} cannot be blank")
9
+ end
10
+
11
+ module_function :validate_not_blank
12
+ end
metadata ADDED
@@ -0,0 +1,191 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: eppo-server-sdk
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Eppo
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2022-12-23 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: rake
75
+ requirement: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - "~>"
78
+ - !ruby/object:Gem::Version
79
+ version: '13.0'
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: 13.0.6
83
+ type: :development
84
+ prerelease: false
85
+ version_requirements: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '13.0'
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ version: 13.0.6
93
+ - !ruby/object:Gem::Dependency
94
+ name: rspec
95
+ requirement: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - "~>"
98
+ - !ruby/object:Gem::Version
99
+ version: '3.12'
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: 3.12.0
103
+ type: :development
104
+ prerelease: false
105
+ version_requirements: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - "~>"
108
+ - !ruby/object:Gem::Version
109
+ version: '3.12'
110
+ - - ">="
111
+ - !ruby/object:Gem::Version
112
+ version: 3.12.0
113
+ - !ruby/object:Gem::Dependency
114
+ name: rubocop
115
+ requirement: !ruby/object:Gem::Requirement
116
+ requirements:
117
+ - - "~>"
118
+ - !ruby/object:Gem::Version
119
+ version: '1.41'
120
+ type: :development
121
+ prerelease: false
122
+ version_requirements: !ruby/object:Gem::Requirement
123
+ requirements:
124
+ - - "~>"
125
+ - !ruby/object:Gem::Version
126
+ version: '1.41'
127
+ - !ruby/object:Gem::Dependency
128
+ name: webmock
129
+ requirement: !ruby/object:Gem::Requirement
130
+ requirements:
131
+ - - "~>"
132
+ - !ruby/object:Gem::Version
133
+ version: '3.18'
134
+ - - ">="
135
+ - !ruby/object:Gem::Version
136
+ version: 3.18.1
137
+ type: :development
138
+ prerelease: false
139
+ version_requirements: !ruby/object:Gem::Requirement
140
+ requirements:
141
+ - - "~>"
142
+ - !ruby/object:Gem::Version
143
+ version: '3.18'
144
+ - - ">="
145
+ - !ruby/object:Gem::Version
146
+ version: 3.18.1
147
+ description:
148
+ email: eppo-team@geteppo.com
149
+ executables: []
150
+ extensions: []
151
+ extra_rdoc_files: []
152
+ files:
153
+ - lib/assignment_logger.rb
154
+ - lib/client.rb
155
+ - lib/config.rb
156
+ - lib/configuration_requestor.rb
157
+ - lib/configuration_store.rb
158
+ - lib/constants.rb
159
+ - lib/custom_errors.rb
160
+ - lib/eppo_client.rb
161
+ - lib/http_client.rb
162
+ - lib/lru_cache.rb
163
+ - lib/poller.rb
164
+ - lib/rules.rb
165
+ - lib/sdk_logger.rb
166
+ - lib/shard.rb
167
+ - lib/validation.rb
168
+ homepage: https://github.com/Eppo-exp/ruby-sdk
169
+ licenses:
170
+ - MIT
171
+ metadata: {}
172
+ post_install_message:
173
+ rdoc_options: []
174
+ require_paths:
175
+ - lib
176
+ required_ruby_version: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - ">="
179
+ - !ruby/object:Gem::Version
180
+ version: 3.1.2
181
+ required_rubygems_version: !ruby/object:Gem::Requirement
182
+ requirements:
183
+ - - ">="
184
+ - !ruby/object:Gem::Version
185
+ version: '0'
186
+ requirements: []
187
+ rubygems_version: 3.3.7
188
+ signing_key:
189
+ specification_version: 4
190
+ summary: Eppo SDK for Ruby
191
+ test_files: []