lightrate-client 1.0.1
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 +7 -0
- data/.rspec +1 -0
- data/.rubocop.yml +32 -0
- data/Gemfile +11 -0
- data/Gemfile.lock +133 -0
- data/README.md +289 -0
- data/Rakefile +11 -0
- data/examples/basic_usage.rb +146 -0
- data/lib/lightrate_client/client.rb +244 -0
- data/lib/lightrate_client/configuration.rb +29 -0
- data/lib/lightrate_client/errors.rb +31 -0
- data/lib/lightrate_client/types.rb +199 -0
- data/lib/lightrate_client/version.rb +5 -0
- data/lib/lightrate_client.rb +33 -0
- metadata +214 -0
@@ -0,0 +1,244 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "faraday"
|
4
|
+
require "faraday/retry"
|
5
|
+
require "json"
|
6
|
+
require "time"
|
7
|
+
|
8
|
+
module LightrateClient
|
9
|
+
class Client
|
10
|
+
attr_reader :configuration, :token_buckets
|
11
|
+
|
12
|
+
def initialize(api_key = nil, application_id = nil, options = {})
|
13
|
+
if api_key
|
14
|
+
# Create a new configuration with the provided API key and application ID
|
15
|
+
@configuration = LightrateClient::Configuration.new.tap do |c|
|
16
|
+
c.api_key = api_key
|
17
|
+
c.application_id = application_id
|
18
|
+
c.timeout = options[:timeout] || LightrateClient.configuration.timeout
|
19
|
+
c.retry_attempts = options[:retry_attempts] || LightrateClient.configuration.retry_attempts
|
20
|
+
c.logger = options[:logger] || LightrateClient.configuration.logger
|
21
|
+
c.default_local_bucket_size = options[:default_local_bucket_size] || LightrateClient.configuration.default_local_bucket_size
|
22
|
+
end
|
23
|
+
else
|
24
|
+
@configuration = options.is_a?(LightrateClient::Configuration) ? options : LightrateClient.configuration
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
validate_configuration!
|
29
|
+
setup_connection
|
30
|
+
setup_token_buckets
|
31
|
+
end
|
32
|
+
|
33
|
+
# Consume tokens by operation or path using local bucket
|
34
|
+
# @param operation [String, nil] The operation name (mutually exclusive with path)
|
35
|
+
# @param path [String, nil] The API path (mutually exclusive with operation)
|
36
|
+
# @param http_method [String, nil] The HTTP method (required when path is provided)
|
37
|
+
# @param user_identifier [String] The user identifier
|
38
|
+
# @param tokens_requested [Integer] Number of tokens to consume
|
39
|
+
def consume_local_bucket_token(operation: nil, path: nil, http_method: nil, user_identifier:)
|
40
|
+
# Get or create bucket for this user/operation/path combination
|
41
|
+
bucket = get_or_create_bucket(user_identifier, operation, path, http_method)
|
42
|
+
|
43
|
+
# Use the bucket's mutex to synchronize the entire operation
|
44
|
+
# This prevents race conditions between multiple threads trying to consume from the same bucket
|
45
|
+
bucket.synchronize do
|
46
|
+
# Try to consume a token atomically first
|
47
|
+
has_tokens, consumed_successfully = bucket.check_and_consume_token
|
48
|
+
|
49
|
+
# If we successfully consumed a local token, return success
|
50
|
+
if consumed_successfully
|
51
|
+
return LightrateClient::ConsumeLocalBucketTokenResponse.new(
|
52
|
+
success: true,
|
53
|
+
used_local_token: true,
|
54
|
+
bucket_status: bucket.status
|
55
|
+
)
|
56
|
+
end
|
57
|
+
|
58
|
+
# No local tokens available, need to fetch from API
|
59
|
+
tokens_to_fetch = get_bucket_size_for_operation(operation, path)
|
60
|
+
|
61
|
+
# Make API call
|
62
|
+
request = LightrateClient::ConsumeTokensRequest.new(
|
63
|
+
application_id: @configuration.application_id,
|
64
|
+
operation: operation,
|
65
|
+
path: path,
|
66
|
+
http_method: http_method,
|
67
|
+
user_identifier: user_identifier,
|
68
|
+
tokens_requested: tokens_to_fetch
|
69
|
+
)
|
70
|
+
|
71
|
+
# Make the API call
|
72
|
+
response = post("/api/v1/tokens/consume", request.to_h)
|
73
|
+
tokens_consumed = response['tokensConsumed']&.to_i || 0
|
74
|
+
|
75
|
+
# If we got tokens from API, refill the bucket and try to consume
|
76
|
+
if tokens_consumed > 0
|
77
|
+
tokens_added, has_tokens_after_refill = bucket.refill_and_check(tokens_consumed)
|
78
|
+
|
79
|
+
# Try to consume a token after refilling
|
80
|
+
_, final_consumed = bucket.check_and_consume_token
|
81
|
+
|
82
|
+
return LightrateClient::ConsumeLocalBucketTokenResponse.new(
|
83
|
+
success: final_consumed,
|
84
|
+
used_local_token: false,
|
85
|
+
bucket_status: bucket.status
|
86
|
+
)
|
87
|
+
else
|
88
|
+
# No tokens available from API
|
89
|
+
return LightrateClient::ConsumeLocalBucketTokenResponse.new(
|
90
|
+
success: false,
|
91
|
+
used_local_token: false,
|
92
|
+
bucket_status: bucket.status
|
93
|
+
)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def consume_tokens(operation: nil, path: nil, http_method: nil, user_identifier:, tokens_requested:)
|
99
|
+
request = LightrateClient::ConsumeTokensRequest.new(
|
100
|
+
application_id: @configuration.application_id,
|
101
|
+
operation: operation,
|
102
|
+
path: path,
|
103
|
+
http_method: http_method,
|
104
|
+
user_identifier: user_identifier,
|
105
|
+
tokens_requested: tokens_requested
|
106
|
+
)
|
107
|
+
consume_tokens_with_request(request)
|
108
|
+
end
|
109
|
+
|
110
|
+
private
|
111
|
+
|
112
|
+
# Consume tokens from the token bucket using a request object
|
113
|
+
# @param request [ConsumeTokensRequest] The token consumption request
|
114
|
+
# @return [ConsumeTokensResponse] The response indicating success/failure and remaining tokens
|
115
|
+
def consume_tokens_with_request(request)
|
116
|
+
raise ArgumentError, "Invalid request" unless request.is_a?(LightrateClient::ConsumeTokensRequest)
|
117
|
+
raise ArgumentError, "Request validation failed" unless request.valid?
|
118
|
+
|
119
|
+
response = post("/api/v1/tokens/consume", request.to_h)
|
120
|
+
LightrateClient::ConsumeTokensResponse.from_hash(response)
|
121
|
+
end
|
122
|
+
|
123
|
+
def setup_token_buckets
|
124
|
+
@token_buckets = {}
|
125
|
+
@buckets_mutex = Mutex.new
|
126
|
+
end
|
127
|
+
|
128
|
+
def get_or_create_bucket(user_identifier, operation, path, http_method = nil)
|
129
|
+
# Create a unique key for this user/operation/path combination
|
130
|
+
bucket_key = create_bucket_key(user_identifier, operation, path, http_method)
|
131
|
+
|
132
|
+
# Double-checked locking pattern for thread-safe bucket creation
|
133
|
+
return @token_buckets[bucket_key] if @token_buckets[bucket_key]
|
134
|
+
|
135
|
+
@buckets_mutex.synchronize do
|
136
|
+
# Check again inside the mutex to prevent duplicate creation
|
137
|
+
@token_buckets[bucket_key] ||= begin
|
138
|
+
bucket_size = get_bucket_size_for_operation(operation, path)
|
139
|
+
TokenBucket.new(bucket_size)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
@token_buckets[bucket_key]
|
144
|
+
end
|
145
|
+
|
146
|
+
def get_bucket_size_for_operation(operation, path)
|
147
|
+
# Always use the default bucket size for all operations and paths
|
148
|
+
@configuration.default_local_bucket_size
|
149
|
+
end
|
150
|
+
|
151
|
+
def create_bucket_key(user_identifier, operation, path, http_method = nil)
|
152
|
+
# Create a unique key that combines user, operation, and path
|
153
|
+
if operation
|
154
|
+
"#{user_identifier}:operation:#{operation}"
|
155
|
+
elsif path
|
156
|
+
"#{user_identifier}:path:#{path}:#{http_method}"
|
157
|
+
else
|
158
|
+
raise ArgumentError, "Either operation or path must be specified"
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
def validate_configuration!
|
163
|
+
raise ConfigurationError, "API key is required" unless configuration.api_key
|
164
|
+
raise ConfigurationError, "Application ID is required" unless configuration.application_id
|
165
|
+
end
|
166
|
+
|
167
|
+
def setup_connection
|
168
|
+
@connection = Faraday.new(url: "https://api.lightrate.lightbournetechnologies.ca") do |conn|
|
169
|
+
conn.request :json
|
170
|
+
conn.response :json, content_type: /\bjson$/
|
171
|
+
conn.response :logger, configuration.logger if configuration.logger
|
172
|
+
conn.use Faraday::Retry::Middleware, retry_options
|
173
|
+
conn.adapter Faraday.default_adapter
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
def retry_options
|
178
|
+
{
|
179
|
+
max: configuration.retry_attempts,
|
180
|
+
interval: 0.5,
|
181
|
+
backoff_factor: 2,
|
182
|
+
retry_if: ->(env, _exception) { should_retry?(env) }
|
183
|
+
}
|
184
|
+
end
|
185
|
+
|
186
|
+
def should_retry?(env)
|
187
|
+
status = env.status
|
188
|
+
[429, 500, 502, 503, 504].include?(status)
|
189
|
+
end
|
190
|
+
|
191
|
+
def get(path, params = {})
|
192
|
+
request(:get, path, params: params)
|
193
|
+
end
|
194
|
+
|
195
|
+
def post(path, body = {})
|
196
|
+
request(:post, path, body: body)
|
197
|
+
end
|
198
|
+
|
199
|
+
def request(method, path, **options)
|
200
|
+
response = @connection.public_send(method, path) do |req|
|
201
|
+
req.headers["Authorization"] = "Bearer #{configuration.api_key}"
|
202
|
+
req.headers["User-Agent"] = "lightrate-client-ruby/#{VERSION}"
|
203
|
+
req.headers["Accept"] = "application/json"
|
204
|
+
req.headers["Content-Type"] = "application/json"
|
205
|
+
|
206
|
+
req.params.merge!(options[:params]) if options[:params]
|
207
|
+
req.body = options[:body].to_json if options[:body]
|
208
|
+
|
209
|
+
req.options.timeout = configuration.timeout
|
210
|
+
end
|
211
|
+
|
212
|
+
handle_response(response)
|
213
|
+
rescue Faraday::TimeoutError
|
214
|
+
raise TimeoutError, "Request timed out after #{configuration.timeout} seconds"
|
215
|
+
rescue Faraday::ConnectionFailed, Faraday::SSLError => e
|
216
|
+
raise NetworkError, "Network error: #{e.message}"
|
217
|
+
end
|
218
|
+
|
219
|
+
def handle_response(response)
|
220
|
+
case response.status
|
221
|
+
when 200..299
|
222
|
+
response.body
|
223
|
+
when 400
|
224
|
+
raise BadRequestError.new("Bad Request", response.status, response.body)
|
225
|
+
when 401
|
226
|
+
raise UnauthorizedError.new("Unauthorized", response.status, response.body)
|
227
|
+
when 403
|
228
|
+
raise ForbiddenError.new("Forbidden", response.status, response.body)
|
229
|
+
when 404
|
230
|
+
raise NotFoundError.new("Not Found", response.status, response.body)
|
231
|
+
when 422
|
232
|
+
raise UnprocessableEntityError.new("Unprocessable Entity", response.status, response.body)
|
233
|
+
when 429
|
234
|
+
raise TooManyRequestsError.new("Too Many Requests", response.status, response.body)
|
235
|
+
when 500
|
236
|
+
raise InternalServerError.new("Internal Server Error", response.status, response.body)
|
237
|
+
when 503
|
238
|
+
raise ServiceUnavailableError.new("Service Unavailable", response.status, response.body)
|
239
|
+
else
|
240
|
+
raise APIError.new("API Error: #{response.status}", response.status, response.body)
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LightrateClient
|
4
|
+
class Configuration
|
5
|
+
attr_accessor :api_key, :application_id, :timeout, :retry_attempts, :logger, :default_local_bucket_size
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@timeout = 30
|
9
|
+
@retry_attempts = 3
|
10
|
+
@logger = nil
|
11
|
+
@default_local_bucket_size = 5
|
12
|
+
end
|
13
|
+
|
14
|
+
def valid?
|
15
|
+
api_key && application_id
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_h
|
19
|
+
{
|
20
|
+
api_key: "******",
|
21
|
+
application_id: application_id,
|
22
|
+
timeout: timeout,
|
23
|
+
retry_attempts: retry_attempts,
|
24
|
+
logger: logger,
|
25
|
+
default_local_bucket_size: default_local_bucket_size
|
26
|
+
}
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LightrateClient
|
4
|
+
class Error < StandardError; end
|
5
|
+
|
6
|
+
class ConfigurationError < Error; end
|
7
|
+
|
8
|
+
class AuthenticationError < Error; end
|
9
|
+
|
10
|
+
class APIError < Error
|
11
|
+
attr_reader :status_code, :response_body
|
12
|
+
|
13
|
+
def initialize(message, status_code = nil, response_body = nil)
|
14
|
+
super(message)
|
15
|
+
@status_code = status_code
|
16
|
+
@response_body = response_body
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class BadRequestError < APIError; end
|
21
|
+
class UnauthorizedError < APIError; end
|
22
|
+
class ForbiddenError < APIError; end
|
23
|
+
class NotFoundError < APIError; end
|
24
|
+
class UnprocessableEntityError < APIError; end
|
25
|
+
class TooManyRequestsError < APIError; end
|
26
|
+
class InternalServerError < APIError; end
|
27
|
+
class ServiceUnavailableError < APIError; end
|
28
|
+
|
29
|
+
class NetworkError < Error; end
|
30
|
+
class TimeoutError < Error; end
|
31
|
+
end
|
@@ -0,0 +1,199 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LightrateClient
|
4
|
+
# Request types
|
5
|
+
class ConsumeTokensRequest
|
6
|
+
attr_accessor :application_id, :operation, :path, :http_method, :user_identifier, :tokens_requested, :timestamp
|
7
|
+
|
8
|
+
def initialize(application_id:, operation: nil, path: nil, http_method: nil, user_identifier:, tokens_requested:, timestamp: nil)
|
9
|
+
@application_id = application_id
|
10
|
+
@operation = operation
|
11
|
+
@path = path
|
12
|
+
@http_method = http_method
|
13
|
+
@user_identifier = user_identifier
|
14
|
+
@tokens_requested = tokens_requested
|
15
|
+
@timestamp = timestamp || Time.now
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_h
|
19
|
+
{
|
20
|
+
applicationId: @application_id,
|
21
|
+
operation: @operation,
|
22
|
+
path: @path,
|
23
|
+
httpMethod: @http_method,
|
24
|
+
userIdentifier: @user_identifier,
|
25
|
+
tokensRequested: @tokens_requested,
|
26
|
+
timestamp: @timestamp
|
27
|
+
}.compact
|
28
|
+
end
|
29
|
+
|
30
|
+
def valid?
|
31
|
+
return false if @application_id.nil? || @application_id.empty?
|
32
|
+
return false if @user_identifier.nil? || @user_identifier.empty?
|
33
|
+
return false if @tokens_requested.nil? || @tokens_requested <= 0
|
34
|
+
return false if @operation.nil? && @path.nil?
|
35
|
+
return false if @operation && @path
|
36
|
+
return false if @path && @http_method.nil?
|
37
|
+
|
38
|
+
true
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Response types
|
43
|
+
class ConsumeTokensResponse
|
44
|
+
attr_reader :tokens_remaining, :tokens_consumed, :throttles, :rule
|
45
|
+
|
46
|
+
def initialize(tokens_remaining:, tokens_consumed:, throttles: 0, rule: nil)
|
47
|
+
@tokens_remaining = tokens_remaining
|
48
|
+
@tokens_consumed = tokens_consumed
|
49
|
+
@throttles = throttles
|
50
|
+
@rule = rule
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.from_hash(hash)
|
54
|
+
rule = nil
|
55
|
+
if hash['rule'] || hash[:rule]
|
56
|
+
rule_hash = hash['rule'] || hash[:rule]
|
57
|
+
rule = Rule.from_hash(rule_hash)
|
58
|
+
end
|
59
|
+
|
60
|
+
new(
|
61
|
+
tokens_remaining: hash['tokensRemaining'] || hash[:tokens_remaining],
|
62
|
+
tokens_consumed: hash['tokensConsumed'] || hash[:tokens_consumed],
|
63
|
+
throttles: hash['throttles'] || hash[:throttles] || 0,
|
64
|
+
rule: rule
|
65
|
+
)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
class ConsumeLocalBucketTokenResponse
|
70
|
+
attr_reader :success, :used_local_token, :bucket_status
|
71
|
+
|
72
|
+
def initialize(success:, used_local_token: false, bucket_status: nil)
|
73
|
+
@success = success
|
74
|
+
@used_local_token = used_local_token
|
75
|
+
@bucket_status = bucket_status
|
76
|
+
end
|
77
|
+
|
78
|
+
# Indicates if this request required fetching tokens from the server
|
79
|
+
def required_fetch?
|
80
|
+
!@used_local_token
|
81
|
+
end
|
82
|
+
|
83
|
+
# Indicates if there were no more tokens available locally before this request
|
84
|
+
def was_bucket_empty?
|
85
|
+
!@used_local_token
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
class Rule
|
90
|
+
attr_reader :id, :name, :refill_rate, :burst_rate, :is_default
|
91
|
+
|
92
|
+
def initialize(id:, name:, refill_rate:, burst_rate:, is_default: false)
|
93
|
+
@id = id
|
94
|
+
@name = name
|
95
|
+
@refill_rate = refill_rate
|
96
|
+
@burst_rate = burst_rate
|
97
|
+
@is_default = is_default
|
98
|
+
end
|
99
|
+
|
100
|
+
def self.from_hash(hash)
|
101
|
+
new(
|
102
|
+
id: hash['id'] || hash[:id],
|
103
|
+
name: hash['name'] || hash[:name],
|
104
|
+
refill_rate: hash['refillRate'] || hash[:refill_rate],
|
105
|
+
burst_rate: hash['burstRate'] || hash[:burst_rate],
|
106
|
+
is_default: hash['isDefault'] || hash[:is_default] || false
|
107
|
+
)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
# Token bucket for local token management
|
112
|
+
class TokenBucket
|
113
|
+
attr_reader :available_tokens, :max_tokens
|
114
|
+
|
115
|
+
def initialize(max_tokens)
|
116
|
+
@max_tokens = max_tokens
|
117
|
+
@available_tokens = 0
|
118
|
+
@mutex = Mutex.new
|
119
|
+
end
|
120
|
+
|
121
|
+
# Check if tokens are available locally (caller must hold lock)
|
122
|
+
# @return [Boolean] true if tokens are available
|
123
|
+
def has_tokens?
|
124
|
+
@available_tokens > 0
|
125
|
+
end
|
126
|
+
|
127
|
+
# Consume one token from the bucket (caller must hold lock)
|
128
|
+
# @return [Boolean] true if token was consumed, false if no tokens available
|
129
|
+
def consume_token
|
130
|
+
return false if @available_tokens <= 0
|
131
|
+
|
132
|
+
@available_tokens -= 1
|
133
|
+
true
|
134
|
+
end
|
135
|
+
|
136
|
+
# Consume multiple tokens from the bucket (caller must hold lock)
|
137
|
+
# @param count [Integer] Number of tokens to consume
|
138
|
+
# @return [Integer] Number of tokens actually consumed
|
139
|
+
def consume_tokens(count)
|
140
|
+
return 0 if count <= 0 || @available_tokens <= 0
|
141
|
+
|
142
|
+
tokens_to_consume = [count, @available_tokens].min
|
143
|
+
@available_tokens -= tokens_to_consume
|
144
|
+
tokens_to_consume
|
145
|
+
end
|
146
|
+
|
147
|
+
# Refill the bucket with tokens from the server (caller must hold lock)
|
148
|
+
# @param tokens_to_fetch [Integer] Number of tokens to fetch
|
149
|
+
# @return [Integer] Number of tokens actually added to the bucket
|
150
|
+
def refill(tokens_to_fetch)
|
151
|
+
tokens_to_add = [tokens_to_fetch, @max_tokens - @available_tokens].min
|
152
|
+
@available_tokens += tokens_to_add
|
153
|
+
tokens_to_add
|
154
|
+
end
|
155
|
+
|
156
|
+
# Get current bucket status (caller must hold lock)
|
157
|
+
# @return [Hash] Current bucket status with tokens_remaining and max_tokens
|
158
|
+
def status
|
159
|
+
{
|
160
|
+
tokens_remaining: @available_tokens,
|
161
|
+
max_tokens: @max_tokens
|
162
|
+
}
|
163
|
+
end
|
164
|
+
|
165
|
+
# Reset bucket to empty state (caller must hold lock)
|
166
|
+
def reset
|
167
|
+
@available_tokens = 0
|
168
|
+
end
|
169
|
+
|
170
|
+
# Check tokens and consume atomically (caller must hold lock)
|
171
|
+
# This prevents race conditions between checking and consuming
|
172
|
+
# @return [Array] [has_tokens, consumed_successfully]
|
173
|
+
def check_and_consume_token
|
174
|
+
has_tokens = @available_tokens > 0
|
175
|
+
if has_tokens
|
176
|
+
@available_tokens -= 1
|
177
|
+
[true, true]
|
178
|
+
else
|
179
|
+
[false, false]
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
# Refill and check tokens atomically (caller must hold lock)
|
184
|
+
# @param tokens_to_fetch [Integer] Number of tokens to fetch
|
185
|
+
# @return [Array] [tokens_added, has_tokens_after_refill]
|
186
|
+
def refill_and_check(tokens_to_fetch)
|
187
|
+
tokens_to_add = [tokens_to_fetch, @max_tokens - @available_tokens].min
|
188
|
+
@available_tokens += tokens_to_add
|
189
|
+
has_tokens_after = @available_tokens > 0
|
190
|
+
[tokens_to_add, has_tokens_after]
|
191
|
+
end
|
192
|
+
|
193
|
+
# Synchronize access to this bucket for thread-safe operations
|
194
|
+
# @yield Block to execute under bucket lock
|
195
|
+
def synchronize(&block)
|
196
|
+
@mutex.synchronize(&block)
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "lightrate_client/version"
|
4
|
+
require_relative "lightrate_client/client"
|
5
|
+
require_relative "lightrate_client/errors"
|
6
|
+
require_relative "lightrate_client/configuration"
|
7
|
+
require_relative "lightrate_client/types"
|
8
|
+
|
9
|
+
module LightrateClient
|
10
|
+
class << self
|
11
|
+
def configure
|
12
|
+
yield(configuration)
|
13
|
+
end
|
14
|
+
|
15
|
+
def configuration
|
16
|
+
@configuration ||= Configuration.new
|
17
|
+
end
|
18
|
+
|
19
|
+
def client
|
20
|
+
@client ||= Client.new
|
21
|
+
end
|
22
|
+
|
23
|
+
# Create a new client with API key and application ID
|
24
|
+
def new_client(api_key, application_id, **options)
|
25
|
+
Client.new(api_key, application_id, options)
|
26
|
+
end
|
27
|
+
|
28
|
+
def reset!
|
29
|
+
@configuration = nil
|
30
|
+
@client = nil
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|