momento 0.2.0 → 0.4.9
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/.release-please-manifest.json +1 -1
- data/.rubocop.yml +14 -1
- data/.ruby-version +1 -1
- data/CHANGELOG.md +91 -0
- data/CONTRIBUTING.md +5 -6
- data/Gemfile +1 -6
- data/Gemfile.lock +30 -24
- data/README.md +53 -43
- data/README.template.md +30 -25
- data/examples/Gemfile +1 -1
- data/examples/README.md +3 -3
- data/examples/compact.rb +12 -8
- data/examples/example.rb +13 -7
- data/examples/file.rb +6 -5
- data/lib/README-generating-pb.txt +1 -1
- data/lib/momento/auth/credential_provider.rb +78 -0
- data/lib/momento/cache_client.rb +457 -0
- data/lib/momento/collection_ttl.rb +79 -0
- data/lib/momento/config/configuration.rb +16 -0
- data/lib/momento/config/configurations.rb +17 -0
- data/lib/momento/config/transport/grpc_configuration.rb +25 -0
- data/lib/momento/config/transport/static_transport_strategy.rb +12 -0
- data/lib/momento/config/transport/transport_strategy.rb +16 -0
- data/lib/momento/error/types.rb +15 -0
- data/lib/momento/generated/README.md +16 -0
- data/lib/momento/generated/auth_pb.rb +52 -0
- data/lib/momento/generated/auth_services_pb.rb +27 -0
- data/lib/momento/generated/cacheclient_pb.rb +203 -0
- data/lib/momento/generated/cacheclient_services_pb.rb +90 -0
- data/lib/momento/generated/cacheping_pb.rb +38 -0
- data/lib/momento/generated/cacheping_services_pb.rb +23 -0
- data/lib/momento/generated/cachepubsub_pb.rb +48 -0
- data/lib/momento/generated/cachepubsub_services_pb.rb +56 -0
- data/lib/momento/generated/common_pb.rb +44 -0
- data/lib/momento/generated/controlclient_pb.rb +72 -0
- data/lib/momento/generated/controlclient_services_pb.rb +35 -0
- data/lib/momento/generated/extensions_pb.rb +35 -0
- data/lib/momento/generated/generate_protos.sh +47 -0
- data/lib/momento/generated/leaderboard_pb.rb +56 -0
- data/lib/momento/generated/leaderboard_services_pb.rb +57 -0
- data/lib/momento/generated/permissionmessages_pb.rb +48 -0
- data/lib/momento/generated/token_pb.rb +43 -0
- data/lib/momento/generated/token_services_pb.rb +23 -0
- data/lib/momento/generated/webhook_pb.rb +49 -0
- data/lib/momento/generated/webhook_services_pb.rb +32 -0
- data/lib/momento/response/control/create_cache_response.rb +61 -0
- data/lib/momento/{delete_cache_response_builder.rb → response/control/delete_cache_response.rb} +24 -3
- data/lib/momento/response/control/list_caches_response.rb +80 -0
- data/lib/momento/{delete_response_builder.rb → response/delete_response.rb} +24 -2
- data/lib/momento/{get_response.rb → response/get_response.rb} +39 -9
- data/lib/momento/{response.rb → response/response.rb} +11 -14
- data/lib/momento/response/set_response.rb +59 -0
- data/lib/momento/response/sort_order.rb +11 -0
- data/lib/momento/response/sorted_set/sorted_set_fetch_response.rb +107 -0
- data/lib/momento/response/sorted_set/sorted_set_put_element_response.rb +44 -0
- data/lib/momento/response/sorted_set/sorted_set_put_elements_response.rb +44 -0
- data/lib/momento/version.rb +1 -1
- data/lib/momento.rb +6 -1
- data/momento.gemspec +5 -3
- data/release-please-config.json +1 -1
- data/sig/momento/auth/credential_provider.rbs +11 -0
- data/sig/momento/cache_client.rbs +12 -0
- data/sig/momento/collection_ttl.rbs +22 -0
- data/sig/momento/config/configuration.rbs +9 -0
- data/sig/momento/config/configurations.rbs +9 -0
- data/sig/momento/config/transport/grpc_configuration.rbs +7 -0
- data/sig/momento/config/transport/transport_strategy.rbs +7 -0
- data/sig/momento/list_caches_response.rbs +7 -0
- data/sig/momento/sorted_set_fetch_response.rbs +13 -0
- data/sig/momento/sorted_set_put_element_response.rbs +5 -0
- data/sig/momento/sorted_set_put_elements_response.rbs +5 -0
- metadata +101 -40
- data/lib/momento/cacheclient_pb.rb +0 -334
- data/lib/momento/cacheclient_services_pb.rb +0 -44
- data/lib/momento/controlclient_pb.rb +0 -73
- data/lib/momento/controlclient_services_pb.rb +0 -31
- data/lib/momento/create_cache_response.rb +0 -37
- data/lib/momento/create_cache_response_builder.rb +0 -27
- data/lib/momento/delete_cache_response.rb +0 -24
- data/lib/momento/delete_response.rb +0 -24
- data/lib/momento/get_response_builder.rb +0 -37
- data/lib/momento/list_caches_response.rb +0 -77
- data/lib/momento/list_caches_response_builder.rb +0 -25
- data/lib/momento/set_response.rb +0 -39
- data/lib/momento/set_response_builder.rb +0 -25
- data/lib/momento/simple_cache_client.rb +0 -336
- /data/lib/momento/{response_builder.rb → response/response_builder.rb} +0 -0
data/examples/file.rb
CHANGED
@@ -5,9 +5,6 @@
|
|
5
5
|
|
6
6
|
require 'momento'
|
7
7
|
|
8
|
-
# Get your Momento token from an environment variable.
|
9
|
-
TOKEN = ENV.fetch('MOMENTO_AUTH_TOKEN')
|
10
|
-
|
11
8
|
# Cached items will be deleted after 12.5 seconds.
|
12
9
|
TTL_SECONDS = 12.5
|
13
10
|
|
@@ -21,9 +18,13 @@ FILE_LOCATIONS = [
|
|
21
18
|
"../spec/support/assets/test.jpg"
|
22
19
|
].freeze
|
23
20
|
|
21
|
+
# Create a credential provider that loads a Momento API Key from an environment variable.
|
22
|
+
credential_provider = Momento::CredentialProvider.from_env_var('MOMENTO_API_KEY')
|
23
|
+
|
24
24
|
# Instantiate a Momento client.
|
25
|
-
client = Momento::
|
26
|
-
|
25
|
+
client = Momento::CacheClient.new(
|
26
|
+
configuration: Momento::Cache::Configurations::Laptop.latest,
|
27
|
+
credential_provider: credential_provider,
|
27
28
|
default_ttl: TTL_SECONDS
|
28
29
|
)
|
29
30
|
|
@@ -25,4 +25,4 @@ Put them in their own namespace.
|
|
25
25
|
1. Wrap the modules in `module Momento`.
|
26
26
|
2. In the *_services_pb.rb files, change the module names.
|
27
27
|
* rename ::ControlClient to ::Momento::ControlClient
|
28
|
-
* rename ::CacheClient to ::
|
28
|
+
* rename ::CacheClient to ::MomentoProtos::CacheClient
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require "base64"
|
2
|
+
require "json"
|
3
|
+
|
4
|
+
module Momento
|
5
|
+
# Contains the information required for a Momento client to connect to and authenticate with Momento services.
|
6
|
+
class CredentialProvider
|
7
|
+
attr_reader :api_key, :control_endpoint, :cache_endpoint
|
8
|
+
|
9
|
+
# Creates a CredentialProvider from a Momento API key loaded from an environment variable.
|
10
|
+
# @param env_var_name [String] the environment variable containing the API key
|
11
|
+
# @return [Momento::CredentialProvider]
|
12
|
+
# @raise [Momento::Error::InvalidArgumentError] if the API key is invalid
|
13
|
+
def self.from_env_var(env_var_name)
|
14
|
+
api_key = ENV.fetch(env_var_name) {
|
15
|
+
raise Momento::Error::InvalidArgumentError, "Env var #{env_var_name} must be set"
|
16
|
+
}
|
17
|
+
new(api_key)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Creates a CredentialProvider from a Momento API key
|
21
|
+
# @param api_key [String] the Momento API key
|
22
|
+
# @return [Momento::CredentialProvider]
|
23
|
+
# @raise [Momento::Error::InvalidArgumentError] if the API key is invalid
|
24
|
+
def self.from_string(api_key)
|
25
|
+
raise Momento::Error::InvalidArgumentError, 'Auth token string cannot be empty' if api_key.empty?
|
26
|
+
|
27
|
+
new(api_key)
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def initialize(api_key)
|
33
|
+
decoded_token = decode_api_key(api_key)
|
34
|
+
@api_key = decoded_token.api_key
|
35
|
+
@control_endpoint = decoded_token.control_endpoint
|
36
|
+
@cache_endpoint = decoded_token.cache_endpoint
|
37
|
+
rescue StandardError => e
|
38
|
+
raise Momento::Error::InvalidArgumentError, e.message
|
39
|
+
end
|
40
|
+
|
41
|
+
AuthTokenData = Struct.new(:api_key, :cache_endpoint, :control_endpoint)
|
42
|
+
|
43
|
+
def decode_api_key(api_key)
|
44
|
+
decode_v1_key(api_key)
|
45
|
+
rescue StandardError
|
46
|
+
decode_legacy_key(api_key)
|
47
|
+
end
|
48
|
+
|
49
|
+
def decode_legacy_key(api_key)
|
50
|
+
key_parts = api_key.split('.')
|
51
|
+
raise Momento::Error::InvalidArgumentError, 'Malformed legacy API key' if key_parts.size != 3
|
52
|
+
|
53
|
+
decoded_key = Base64.decode64(key_parts[1])
|
54
|
+
key_json = JSON.parse(decoded_key, symbolize_names: true)
|
55
|
+
validate_key_json(key_json, %i[c cp])
|
56
|
+
|
57
|
+
AuthTokenData.new(api_key, key_json[:c], key_json[:cp])
|
58
|
+
end
|
59
|
+
|
60
|
+
def decode_v1_key(api_key)
|
61
|
+
decoded_key = Base64.decode64(api_key)
|
62
|
+
key_json = JSON.parse(decoded_key, symbolize_names: true)
|
63
|
+
validate_key_json(key_json, %i[api_key endpoint])
|
64
|
+
|
65
|
+
AuthTokenData.new(key_json[:api_key], "cache.#{key_json[:endpoint]}", "control.#{key_json[:endpoint]}")
|
66
|
+
rescue StandardError
|
67
|
+
raise Momento::Error::InvalidArgumentError, 'Malformed Momento API Key'
|
68
|
+
end
|
69
|
+
|
70
|
+
def validate_key_json(key_json, required_fields)
|
71
|
+
missing_fields = required_fields.reject { |field| key_json.key?(field) }
|
72
|
+
return if missing_fields.empty?
|
73
|
+
|
74
|
+
raise Momento::Error::InvalidArgumentError,
|
75
|
+
"Required fields are missing: #{missing_fields.join(', ')}"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,457 @@
|
|
1
|
+
require_relative 'generated/cacheclient_services_pb'
|
2
|
+
require_relative 'generated/controlclient_services_pb'
|
3
|
+
require_relative 'response/response'
|
4
|
+
require_relative 'ttl'
|
5
|
+
require_relative 'exceptions'
|
6
|
+
|
7
|
+
module Momento
|
8
|
+
# rubocop:disable Metrics/ClassLength
|
9
|
+
|
10
|
+
# A simple client for Momento.
|
11
|
+
#
|
12
|
+
# CacheClient does not use exceptions to report errors.
|
13
|
+
# Instead it returns an error response. Please see {file:README.md#label-Error+Handling}.
|
14
|
+
#
|
15
|
+
# @example
|
16
|
+
# credential_provider = Momento::CredentialProvider.from_env_var('MOMENTO_API_KEY')
|
17
|
+
# config = Momento::Cache::Configurations::Laptop.latest
|
18
|
+
# client = Momento::CacheClient.new(
|
19
|
+
# configuration: config,
|
20
|
+
# credential_provider: credential_provider,
|
21
|
+
# # cached items will be deleted after 100 seconds
|
22
|
+
# default_ttl: 100
|
23
|
+
# )
|
24
|
+
#
|
25
|
+
# response = client.create_cache("my_cache")
|
26
|
+
# if response.success?
|
27
|
+
# puts "my_cache was created"
|
28
|
+
# elsif response.already_exists?
|
29
|
+
# puts "my_cache already exists"
|
30
|
+
# elsif response.error?
|
31
|
+
# raise response.error
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
# # set will only return success or error,
|
35
|
+
# # we only need to check for error
|
36
|
+
# response = client.set("my_cache", "key", "value")
|
37
|
+
# raise response.error if response.error?
|
38
|
+
#
|
39
|
+
# response = client.get("my_cache", "key")
|
40
|
+
# if response.hit?
|
41
|
+
# puts "We got #{response.value_string}"
|
42
|
+
# elsif response.miss?
|
43
|
+
# puts "It's not in the cache"
|
44
|
+
# elsif response.error?
|
45
|
+
# raise response.error
|
46
|
+
# end
|
47
|
+
#
|
48
|
+
# @see Momento::Response
|
49
|
+
class CacheClient
|
50
|
+
# This gem's version.
|
51
|
+
VERSION = Momento::VERSION
|
52
|
+
CACHE_CLIENT_STUB_CLASS = MomentoProtos::CacheClient::Scs::Stub
|
53
|
+
CONTROL_CLIENT_STUB_CLASS = MomentoProtos::ControlClient::ScsControl::Stub
|
54
|
+
private_constant :CACHE_CLIENT_STUB_CLASS, :CONTROL_CLIENT_STUB_CLASS
|
55
|
+
|
56
|
+
# @return [Numeric] how long items should remain in the cache, in seconds.
|
57
|
+
attr_accessor :default_ttl
|
58
|
+
|
59
|
+
# @param configuration [Momento::Cache::Configuration] the configuration for the client
|
60
|
+
# @param credential_provider [Momento::CredentialProvider] the provider for the
|
61
|
+
# credentials required to connect to Momento
|
62
|
+
# @param default_ttl [Numeric] time-to-live, in seconds
|
63
|
+
# @raise [ArgumentError] if the default_ttl or credential_provider is invalid
|
64
|
+
def initialize(configuration:, credential_provider:, default_ttl:)
|
65
|
+
@default_ttl = Momento::Ttl.to_ttl(default_ttl)
|
66
|
+
@api_key = credential_provider.api_key
|
67
|
+
@control_endpoint = credential_provider.control_endpoint
|
68
|
+
@cache_endpoint = credential_provider.cache_endpoint
|
69
|
+
@configuration = configuration
|
70
|
+
end
|
71
|
+
|
72
|
+
# Get a value in a cache.
|
73
|
+
#
|
74
|
+
# The value can be retrieved as either bytes or a string.
|
75
|
+
# @example
|
76
|
+
# response = client.get("my_cache", "key")
|
77
|
+
# if response.hit?
|
78
|
+
# puts "We got #{response.value_string}"
|
79
|
+
# elsif response.miss?
|
80
|
+
# puts "It's not in the cache"
|
81
|
+
# elsif response.error?
|
82
|
+
# raise response.error
|
83
|
+
# end
|
84
|
+
#
|
85
|
+
# @see Momento::GetResponse
|
86
|
+
# @param cache_name [String]
|
87
|
+
# @param key [String] must only contain ASCII characters
|
88
|
+
# @return [Momento::GetResponse]
|
89
|
+
# @raise [TypeError] when the cache_name or key is not a String
|
90
|
+
def get(cache_name, key)
|
91
|
+
builder = GetResponseBuilder.new(
|
92
|
+
context: { cache_name: cache_name, key: key }
|
93
|
+
)
|
94
|
+
|
95
|
+
builder.from_block do
|
96
|
+
cache_stub.get(
|
97
|
+
MomentoProtos::CacheClient::PB__GetRequest.new(cache_key: to_bytes(key)),
|
98
|
+
metadata: { cache: validate_cache_name(cache_name) }
|
99
|
+
)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
# Set a value in a cache.
|
104
|
+
#
|
105
|
+
# If ttl is not set, it will use the default_ttl.
|
106
|
+
# @example
|
107
|
+
# response = client.set("my_cache", "key", "value")
|
108
|
+
# raise response.error if response.error?
|
109
|
+
#
|
110
|
+
# @see Momento::SetResponse
|
111
|
+
# @param cache_name [String]
|
112
|
+
# @param key [String] must only contain ASCII characters
|
113
|
+
# @param value [String] the value to cache
|
114
|
+
# @param ttl [Numeric] time-to-live, in seconds.
|
115
|
+
# @raise [ArgumentError] if the ttl is invalid
|
116
|
+
# @return [Momento::SetResponse]
|
117
|
+
# @raise [TypeError] when the cache_name, key, or value is not a String
|
118
|
+
def set(cache_name, key, value, ttl: default_ttl)
|
119
|
+
ttl = Momento::Ttl.to_ttl(ttl)
|
120
|
+
|
121
|
+
builder = SetResponseBuilder.new(
|
122
|
+
context: { cache_name: cache_name, key: key, value: value, ttl: ttl }
|
123
|
+
)
|
124
|
+
|
125
|
+
builder.from_block do
|
126
|
+
req = MomentoProtos::CacheClient::PB__SetRequest.new(
|
127
|
+
cache_key: to_bytes(key),
|
128
|
+
cache_body: to_bytes(value),
|
129
|
+
ttl_milliseconds: ttl.milliseconds
|
130
|
+
)
|
131
|
+
|
132
|
+
cache_stub.set(req, metadata: { cache: validate_cache_name(cache_name) })
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
# Delete a key in a cache.
|
137
|
+
#
|
138
|
+
# If the key does not exist, delete will still succeed.
|
139
|
+
# @example
|
140
|
+
# response = client.delete("my_cache", "key")
|
141
|
+
# raise response.error if response.error?
|
142
|
+
#
|
143
|
+
# @see Momento::DeleteResponse
|
144
|
+
# @param cache_name [String]
|
145
|
+
# @param key [String] must only contain ASCII characters
|
146
|
+
# @return [Momento::DeleteResponse]
|
147
|
+
# @raise [TypeError] when the cache_name or key is not a String
|
148
|
+
def delete(cache_name, key)
|
149
|
+
builder = DeleteResponseBuilder.new(
|
150
|
+
context: { cache_name: cache_name, key: key }
|
151
|
+
)
|
152
|
+
|
153
|
+
builder.from_block do
|
154
|
+
cache_stub.delete(
|
155
|
+
MomentoProtos::CacheClient::PB__DeleteRequest.new(cache_key: to_bytes(key)),
|
156
|
+
metadata: { cache: validate_cache_name(cache_name) }
|
157
|
+
)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
# Create a new Momento cache.
|
162
|
+
# @example
|
163
|
+
# response = client.create_cache("my_cache")
|
164
|
+
# if response.success?
|
165
|
+
# puts "my_cache was created"
|
166
|
+
# elsif response.already_exists?
|
167
|
+
# puts "my_cache already exists"
|
168
|
+
# elsif response.error?
|
169
|
+
# raise response.error
|
170
|
+
# end
|
171
|
+
#
|
172
|
+
# @see Momento::CreateCacheResponse
|
173
|
+
# @param cache_name [String] the name of the cache to create.
|
174
|
+
# @return [Momento::CreateCacheResponse] the response from Momento.
|
175
|
+
# @raise [TypeError] when the cache_name is not a String
|
176
|
+
def create_cache(cache_name)
|
177
|
+
builder = CreateCacheResponseBuilder.new(
|
178
|
+
context: { cache_name: cache_name }
|
179
|
+
)
|
180
|
+
|
181
|
+
builder.from_block do
|
182
|
+
control_stub.create_cache(
|
183
|
+
MomentoProtos::ControlClient::PB__CreateCacheRequest.new(cache_name: validate_cache_name(cache_name))
|
184
|
+
)
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
# Delete an existing Momento cache.
|
189
|
+
#
|
190
|
+
# @example
|
191
|
+
# response = client.delete_cache("my_cache")
|
192
|
+
# raise response.error if response.error?
|
193
|
+
#
|
194
|
+
# @see Momento::DeleteCacheResponse
|
195
|
+
# @param cache_name [String] the name of the cache to delete.
|
196
|
+
# @return [Momento::DeleteCacheResponse] the response from Momento.
|
197
|
+
# @raise [TypeError] when the cache_name is not a String
|
198
|
+
def delete_cache(cache_name)
|
199
|
+
builder = DeleteCacheResponseBuilder.new(
|
200
|
+
context: { cache_name: cache_name }
|
201
|
+
)
|
202
|
+
|
203
|
+
builder.from_block do
|
204
|
+
control_stub.delete_cache(
|
205
|
+
MomentoProtos::ControlClient::PB__DeleteCacheRequest.new(cache_name: validate_cache_name(cache_name))
|
206
|
+
)
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
# Lists your caches.
|
211
|
+
#
|
212
|
+
# @see Momento::ListCachesResponse
|
213
|
+
# @return [Momento::ListCachesResponse]
|
214
|
+
def list_caches
|
215
|
+
builder = ListCachesResponseBuilder.new(
|
216
|
+
context: {}
|
217
|
+
)
|
218
|
+
builder.from_block do
|
219
|
+
control_stub.list_caches(
|
220
|
+
MomentoProtos::ControlClient::PB__ListCachesRequest.new
|
221
|
+
)
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
# Put an element in a sorted set
|
226
|
+
#
|
227
|
+
# If collection_ttl is not set, it will use the default_ttl.
|
228
|
+
# @example
|
229
|
+
# response = client.sorted_set_put_element('my_cache', 'my_set', 'value', 1.0)
|
230
|
+
# raise response.error if response.error?
|
231
|
+
#
|
232
|
+
# @see Momento::SortedSetPutElementResponse
|
233
|
+
# @param cache_name [String]
|
234
|
+
# @param sorted_set_name [String]
|
235
|
+
# @param value [String] the value to add to the sorted set.
|
236
|
+
# @param score [Float] the score of the value. Determines its place in the set.
|
237
|
+
# @param collection_ttl [Momento::CollectionTtl] time-to-live, in seconds.
|
238
|
+
# @raise [ArgumentError] if the ttl is invalid
|
239
|
+
# @return [Momento::SortedSetPutElementResponse]
|
240
|
+
# @raise [TypeError] when the cache_name, sorted_set_name, or value is not a String
|
241
|
+
def sorted_set_put_element(cache_name, sorted_set_name, value, score, collection_ttl: CollectionTtl.from_cache_ttl)
|
242
|
+
collection_ttl = collection_ttl.with_ttl_if_absent(default_ttl.seconds)
|
243
|
+
builder = SortedSetPutElementResponseBuilder.new(
|
244
|
+
context: { cache_name: cache_name, set_name: sorted_set_name, value: value, score: score,
|
245
|
+
collection_ttl: collection_ttl }
|
246
|
+
)
|
247
|
+
|
248
|
+
builder.from_block do
|
249
|
+
req = MomentoProtos::CacheClient::PB__SortedSetPutRequest.new(
|
250
|
+
set_name: to_bytes(sorted_set_name),
|
251
|
+
elements: [{ value: to_bytes(value), score: score }],
|
252
|
+
ttl_milliseconds: collection_ttl.ttl_milliseconds,
|
253
|
+
refresh_ttl: collection_ttl.refresh_ttl
|
254
|
+
)
|
255
|
+
|
256
|
+
# noinspection RubyResolve
|
257
|
+
cache_stub.sorted_set_put(req, metadata: { cache: validate_cache_name(cache_name) })
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
# Put multiple elements in a sorted set
|
262
|
+
#
|
263
|
+
# If collection_ttl is not set, it will use the default_ttl.
|
264
|
+
# @example
|
265
|
+
# response = client.sorted_set_put_element('my_cache', 'my_set', [['value', 1.0]])
|
266
|
+
# raise response.error if response.error?
|
267
|
+
#
|
268
|
+
# @see Momento::SortedSetPutElementsResponse
|
269
|
+
# @param cache_name [String]
|
270
|
+
# @param sorted_set_name [String]
|
271
|
+
# @param elements [Hash, Array] the elements to add. Must be a hash of String values to Float scores,
|
272
|
+
# an array of arrays [["value", 1.0]], or an array of hashes of value and score [{value: "value", score: 1.0}].
|
273
|
+
# @param collection_ttl [Integer] time-to-live, in seconds.
|
274
|
+
# @raise [ArgumentError] if the ttl is invalid
|
275
|
+
# @return [Momento::SortedSetPutElementsResponse]
|
276
|
+
# @raise [TypeError] when the cache_name, or sorted_set_name is not a String, or if elements is not
|
277
|
+
# an Array or Hash
|
278
|
+
def sorted_set_put_elements(cache_name, sorted_set_name, elements, collection_ttl = CollectionTtl.from_cache_ttl)
|
279
|
+
collection_ttl = collection_ttl.with_ttl_if_absent(default_ttl.seconds)
|
280
|
+
builder = SortedSetPutElementsResponseBuilder.new(
|
281
|
+
context: { cache_name: cache_name, set_name: sorted_set_name, elements: elements,
|
282
|
+
collection_ttl: collection_ttl }
|
283
|
+
)
|
284
|
+
|
285
|
+
builder.from_block do
|
286
|
+
req = MomentoProtos::CacheClient::PB__SortedSetPutRequest.new(
|
287
|
+
set_name: to_bytes(sorted_set_name),
|
288
|
+
elements: to_sorted_set_elements(elements),
|
289
|
+
ttl_milliseconds: collection_ttl.ttl_milliseconds,
|
290
|
+
refresh_ttl: collection_ttl.refresh_ttl
|
291
|
+
)
|
292
|
+
|
293
|
+
# noinspection RubyResolve
|
294
|
+
cache_stub.sorted_set_put(req, metadata: { cache: validate_cache_name(cache_name) })
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
298
|
+
# rubocop:disable Metrics/ParameterLists
|
299
|
+
|
300
|
+
# Fetch the elements a sorted set by score.
|
301
|
+
#
|
302
|
+
# @example
|
303
|
+
# response = client.sorted_set_fetch_by_score("my_cache", "sorted_set", min_score: 0.0, max_score: 1.0)
|
304
|
+
# raise response.error if response.error?
|
305
|
+
#
|
306
|
+
# @see Momento::SortedSetFetchResponse
|
307
|
+
# @param cache_name [String]
|
308
|
+
# @param sorted_set_name [String]
|
309
|
+
# @param min_score [Float] The minimum score (inclusive) of the elements to fetch. Defaults to negative infinity.
|
310
|
+
# @param max_score [Float] The maximum score (inclusive) of the elements to fetch. Defaults to positive infinity.
|
311
|
+
# @param sort_order [SortOrder] The order to fetch the elements in. Defaults to ascending.
|
312
|
+
# @param offset [Integer] The number of elements to skip before returning the first element. Defaults to 0.
|
313
|
+
# @param count [Integer] The maximum number of elements to return. Defaults to all elements.
|
314
|
+
# @return [Momento::SortedSetFetchResponse]
|
315
|
+
# @raise [TypeError] when the cache_name, or sorted_set_name is not a String.
|
316
|
+
def sorted_set_fetch_by_score(cache_name, sorted_set_name, min_score: nil, max_score: nil,
|
317
|
+
sort_order: SortOrder::ASCENDING, offset: 0, count: -1)
|
318
|
+
builder = SortedSetFetchResponseBuilder.new(
|
319
|
+
context: { cache_name: cache_name, set_name: sorted_set_name, min_score: min_score, max_score: max_score,
|
320
|
+
sort_order: sort_order, offset: offset, count: count }
|
321
|
+
)
|
322
|
+
|
323
|
+
builder.from_block do
|
324
|
+
by_score = build_sorted_set_by_score(min_score, max_score, offset, count)
|
325
|
+
|
326
|
+
req = MomentoProtos::CacheClient::PB__SortedSetFetchRequest.new(
|
327
|
+
set_name: to_bytes(sorted_set_name),
|
328
|
+
order: to_grpc_order(sort_order),
|
329
|
+
with_scores: true,
|
330
|
+
by_score: by_score
|
331
|
+
)
|
332
|
+
|
333
|
+
# noinspection RubyResolve
|
334
|
+
cache_stub.sorted_set_fetch(req, metadata: { cache: validate_cache_name(cache_name) })
|
335
|
+
end
|
336
|
+
end
|
337
|
+
# rubocop:enable Metrics/ParameterLists
|
338
|
+
|
339
|
+
private
|
340
|
+
|
341
|
+
def cache_stub
|
342
|
+
@cache_stub ||= CACHE_CLIENT_STUB_CLASS.new(@cache_endpoint, combined_credentials,
|
343
|
+
timeout: @configuration.transport_strategy.grpc_configuration.deadline
|
344
|
+
)
|
345
|
+
end
|
346
|
+
|
347
|
+
def control_stub
|
348
|
+
@control_stub ||= CONTROL_CLIENT_STUB_CLASS.new(@control_endpoint, combined_credentials)
|
349
|
+
end
|
350
|
+
|
351
|
+
def combined_credentials
|
352
|
+
@combined_credentials ||= make_combined_credentials
|
353
|
+
end
|
354
|
+
|
355
|
+
def make_combined_credentials
|
356
|
+
# :nocov:
|
357
|
+
auth_proc = proc do
|
358
|
+
{ authorization: @api_key, agent: "ruby:#{VERSION}" }
|
359
|
+
end
|
360
|
+
# :nocov:
|
361
|
+
|
362
|
+
call_creds = GRPC::Core::CallCredentials.new(auth_proc)
|
363
|
+
|
364
|
+
GRPC::Core::ChannelCredentials.new.compose(call_creds)
|
365
|
+
end
|
366
|
+
|
367
|
+
def to_grpc_order(sort_order)
|
368
|
+
case sort_order
|
369
|
+
when SortOrder::ASCENDING
|
370
|
+
MomentoProtos::CacheClient::PB__SortedSetFetchRequest::Order::ASCENDING
|
371
|
+
when SortOrder::DESCENDING
|
372
|
+
MomentoProtos::CacheClient::PB__SortedSetFetchRequest::Order::DESCENDING
|
373
|
+
else
|
374
|
+
raise TypeError, "Invalid sort order: #{sort_order}"
|
375
|
+
end
|
376
|
+
end
|
377
|
+
|
378
|
+
# Momento accepts sorted sets as an array of hashes. This will transform an array of arrays [["value", 1.0]],
|
379
|
+
# an array of hashes [{value: "value", score: 1.0}], or a hash of values to scores to the correct format.
|
380
|
+
# @param elements [Hash, Array] A hash of string values to scores or an array of tuples (value, score)
|
381
|
+
# # @return [Array<Hash>] An array of sorted set elements, where each element is a hash of :value and :score
|
382
|
+
def to_sorted_set_elements(elements)
|
383
|
+
case elements
|
384
|
+
when Hash
|
385
|
+
elements.map { |value, score| { value: to_bytes(value), score: score.to_f } }
|
386
|
+
when Array
|
387
|
+
if elements.first.is_a?(Hash)
|
388
|
+
elements.map { |element| { value: to_bytes(element[:value]), score: element[:score].to_f } }
|
389
|
+
else
|
390
|
+
elements.map { |value, score| { value: to_bytes(value), score: score.to_f } }
|
391
|
+
end
|
392
|
+
else
|
393
|
+
raise ArgumentError, "Sorted set elements must be a Hash or an Array of tuples"
|
394
|
+
end
|
395
|
+
end
|
396
|
+
|
397
|
+
def build_sorted_set_by_score(min_score, max_score, offset, count)
|
398
|
+
MomentoProtos::CacheClient::PB__SortedSetFetchRequest::PB__ByScore.new(
|
399
|
+
min_score: min_score ? build_score(min_score) : nil,
|
400
|
+
unbounded_min: min_score ? nil : MomentoProtos::Common::PB__Unbounded.new,
|
401
|
+
max_score: max_score ? build_score(max_score) : nil,
|
402
|
+
unbounded_max: max_score ? nil : MomentoProtos::Common::PB__Unbounded.new,
|
403
|
+
offset: offset,
|
404
|
+
count: count
|
405
|
+
)
|
406
|
+
end
|
407
|
+
|
408
|
+
def build_score(score)
|
409
|
+
MomentoProtos::CacheClient::PB__SortedSetFetchRequest::PB__ByScore::PB__Score.new(
|
410
|
+
score: score,
|
411
|
+
exclusive: false
|
412
|
+
)
|
413
|
+
end
|
414
|
+
|
415
|
+
# Ruby uses String for bytes. GRPC wants a String encoded as ASCII.
|
416
|
+
# GRPC will re-encode a String, but treats it as characters; GRPC will
|
417
|
+
# raise if you pass a String with non-ASCII characters.
|
418
|
+
# So we do the re-encoding ourselves in a way that treats the String as
|
419
|
+
# bytes and will not raise. The data is not changed.
|
420
|
+
#
|
421
|
+
# If the input String is ASCII, we treat it as binary data. Otherwise,
|
422
|
+
# we ensure it is encoded as UTF-8 to stop the SDK from being able to
|
423
|
+
# write non-UTF-8 strings to the server.
|
424
|
+
#
|
425
|
+
# A duplicate String is returned, but since Ruby is copy-on-write it
|
426
|
+
# does not copy the data.
|
427
|
+
#
|
428
|
+
# @param string [String] the string to make safe for GRPC bytes
|
429
|
+
# @return [String] a duplicate safe to use as GRPC bytes
|
430
|
+
# @raise [TypeError] when the string is not a String
|
431
|
+
def to_bytes(string)
|
432
|
+
raise TypeError, "expected a String, got a #{string.class}" unless string.is_a?(String)
|
433
|
+
|
434
|
+
if string.encoding == Encoding::ASCII_8BIT
|
435
|
+
string.dup
|
436
|
+
else
|
437
|
+
utf8_encoded = string.encode('UTF-8')
|
438
|
+
utf8_encoded.force_encoding(Encoding::ASCII_8BIT)
|
439
|
+
end
|
440
|
+
end
|
441
|
+
|
442
|
+
# Return a UTF-8 version of the cache name.
|
443
|
+
#
|
444
|
+
# @param name [String] the cache name to validate
|
445
|
+
# @raise [TypeError] when the name is not a String
|
446
|
+
# @raise [Momento::CacheNameError] when the name is not UTF-8 compatible
|
447
|
+
def validate_cache_name(name)
|
448
|
+
raise TypeError, "Cache name must be a String, got a #{name.class}" unless name.is_a?(String)
|
449
|
+
|
450
|
+
encoded_name = name.encode('UTF-8')
|
451
|
+
raise Momento::CacheNameError, "Cache name must be UTF-8 compatible" unless name.valid_encoding?
|
452
|
+
|
453
|
+
encoded_name
|
454
|
+
end
|
455
|
+
end
|
456
|
+
# rubocop:enable Metrics/ClassLength
|
457
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module Momento
|
2
|
+
# Represents the desired behavior for managing the TTL on collection objects.
|
3
|
+
#
|
4
|
+
# For cache operations that modify a collection (dictionaries, lists, or sets), there
|
5
|
+
# are a few things to consider. The first time the collection is created, we need to
|
6
|
+
# set a TTL on it. For subsequent operations that modify the collection you may choose
|
7
|
+
# to update the TTL in order to prolong the life of the cached collection object, or
|
8
|
+
# you may choose to leave the TTL unmodified in order to ensure that the collection
|
9
|
+
# expires at the original TTL.
|
10
|
+
#
|
11
|
+
# The default behaviour is to refresh the TTL (to prolong the life of the collection)
|
12
|
+
# each time it is written using the client's default item TTL.
|
13
|
+
class CollectionTtl
|
14
|
+
attr_reader :ttl_seconds, :refresh_ttl
|
15
|
+
|
16
|
+
# Create a CollectionTtl with optional ttl seconds and refresh.
|
17
|
+
# @param ttl_seconds [Integer | nil] the time to live of the collection. Uses the client default TTL if nil.
|
18
|
+
# @param refresh_ttl [Boolean] whether to refresh the collection's ttl when performing a cache operation.
|
19
|
+
# @return [Momento::CollectionTtl]
|
20
|
+
def initialize(ttl_seconds = nil, refresh_ttl: true)
|
21
|
+
validate_ttl_seconds(ttl_seconds) unless ttl_seconds.nil?
|
22
|
+
@ttl_seconds = ttl_seconds
|
23
|
+
@refresh_ttl = refresh_ttl
|
24
|
+
end
|
25
|
+
|
26
|
+
def ttl_milliseconds
|
27
|
+
@ttl_seconds.nil? ? nil : @ttl_seconds * 1000
|
28
|
+
end
|
29
|
+
|
30
|
+
# Creates a CollectionTtl that refreshes and uses the default client TTL.
|
31
|
+
# @return [Momento::CollectionTtl]
|
32
|
+
def self.from_cache_ttl
|
33
|
+
new
|
34
|
+
end
|
35
|
+
|
36
|
+
# Creates a CollectionTtl with the given TTL.
|
37
|
+
# @param ttl_seconds [Integer | nil] the time to live of the collection. Uses the client default TTL if nil.
|
38
|
+
# @return [Momento::CollectionTtl]
|
39
|
+
def self.of(ttl_seconds)
|
40
|
+
new(ttl_seconds)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Creates a CollectionTtl that sets refresh to true if ttl_seconds is provided and false otherwise
|
44
|
+
# @param ttl_seconds [Integer | nil] the time to live of the collection. If not nil, refresh is set to true.
|
45
|
+
# @return [Momento::CollectionTtl]
|
46
|
+
def self.refresh_ttl_if_provided(ttl_seconds = nil)
|
47
|
+
new(ttl_seconds, refresh_ttl: !ttl_seconds.nil?)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Copy constructor that uses the given TTL only if the parent CollectionTtl doesn't have one.
|
51
|
+
# @param ttl_seconds [Integer | nil] the time to live of the collection. Will be ignored if the parent has a TTL.
|
52
|
+
# @return [Momento::CollectionTtl]
|
53
|
+
def with_ttl_if_absent(ttl_seconds)
|
54
|
+
self.class.new(@ttl_seconds || ttl_seconds, refresh_ttl: @refresh_ttl)
|
55
|
+
end
|
56
|
+
|
57
|
+
# Copy constructor that uses the parent TTL and refreshes.
|
58
|
+
# @return [Momento::CollectionTtl]
|
59
|
+
def with_refresh_ttl_on_updates
|
60
|
+
self.class.new(@ttl_seconds)
|
61
|
+
end
|
62
|
+
|
63
|
+
# Copy constructor that uses the parent TTL and does not refresh.
|
64
|
+
# @return [Momento::CollectionTtl]
|
65
|
+
def with_no_refresh_ttl_on_updates
|
66
|
+
self.class.new(@ttl_seconds, refresh_ttl: false)
|
67
|
+
end
|
68
|
+
|
69
|
+
def to_s
|
70
|
+
"ttl: #{@ttl_seconds || 'null'}, refreshTtl: #{@refresh_ttl ? 'true' : 'false'}"
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def validate_ttl_seconds(ttl_seconds)
|
76
|
+
raise ArgumentError, "TTL must be a positive integer" unless ttl_seconds.is_a?(Integer) && ttl_seconds.positive?
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Momento
|
2
|
+
module Cache
|
3
|
+
# Configuration options for Momento CacheClient
|
4
|
+
class Configuration
|
5
|
+
attr_reader :transport_strategy
|
6
|
+
|
7
|
+
def self.with_transport_strategy(transport_strategy)
|
8
|
+
return Configuration.new(transport_strategy)
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(transport_strategy)
|
12
|
+
@transport_strategy = transport_strategy
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require_relative 'configuration'
|
2
|
+
require_relative 'transport/transport_strategy'
|
3
|
+
require_relative 'transport/static_transport_strategy'
|
4
|
+
require_relative 'transport/grpc_configuration'
|
5
|
+
|
6
|
+
module Momento
|
7
|
+
module Cache
|
8
|
+
module Configurations
|
9
|
+
# Default Laptop configuration with 5000ms client timeout
|
10
|
+
class Laptop < Cache::Configuration
|
11
|
+
def self.latest
|
12
|
+
return Configuration.new(StaticTransportStrategy.new(GrpcConfiguration.new(5000)))
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|