momento 0.1.0 → 0.4.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (94) hide show
  1. checksums.yaml +4 -4
  2. data/.release-please-manifest.json +3 -0
  3. data/.rubocop.yml +26 -1
  4. data/.ruby-version +1 -1
  5. data/.yardopts +2 -0
  6. data/CHANGELOG.md +119 -0
  7. data/CONTRIBUTING.md +6 -7
  8. data/Gemfile +1 -6
  9. data/Gemfile.lock +34 -24
  10. data/README.md +143 -38
  11. data/README.template.md +93 -0
  12. data/examples/.gitignore +1 -0
  13. data/examples/Gemfile +5 -0
  14. data/examples/README.md +34 -0
  15. data/examples/compact.rb +47 -0
  16. data/examples/example.rb +72 -0
  17. data/examples/file.rb +58 -0
  18. data/lib/README-generating-pb.txt +1 -1
  19. data/lib/momento/auth/credential_provider.rb +78 -0
  20. data/lib/momento/cache_client.rb +457 -0
  21. data/lib/momento/collection_ttl.rb +79 -0
  22. data/lib/momento/config/configuration.rb +16 -0
  23. data/lib/momento/config/configurations.rb +17 -0
  24. data/lib/momento/config/transport/grpc_configuration.rb +25 -0
  25. data/lib/momento/config/transport/static_transport_strategy.rb +12 -0
  26. data/lib/momento/config/transport/transport_strategy.rb +16 -0
  27. data/lib/momento/error/grpc_details.rb +38 -0
  28. data/lib/momento/error/transport_details.rb +20 -0
  29. data/lib/momento/error/types.rb +247 -0
  30. data/lib/momento/error.rb +54 -0
  31. data/lib/momento/error_builder.rb +50 -0
  32. data/lib/momento/exceptions.rb +7 -0
  33. data/lib/momento/generated/README.md +16 -0
  34. data/lib/momento/generated/auth_pb.rb +52 -0
  35. data/lib/momento/generated/auth_services_pb.rb +27 -0
  36. data/lib/momento/generated/cacheclient_pb.rb +203 -0
  37. data/lib/momento/generated/cacheclient_services_pb.rb +90 -0
  38. data/lib/momento/generated/cacheping_pb.rb +38 -0
  39. data/lib/momento/generated/cacheping_services_pb.rb +23 -0
  40. data/lib/momento/generated/cachepubsub_pb.rb +48 -0
  41. data/lib/momento/generated/cachepubsub_services_pb.rb +56 -0
  42. data/lib/momento/generated/common_pb.rb +44 -0
  43. data/lib/momento/generated/controlclient_pb.rb +72 -0
  44. data/lib/momento/generated/controlclient_services_pb.rb +35 -0
  45. data/lib/momento/generated/extensions_pb.rb +35 -0
  46. data/lib/momento/generated/generate_protos.sh +47 -0
  47. data/lib/momento/generated/leaderboard_pb.rb +56 -0
  48. data/lib/momento/generated/leaderboard_services_pb.rb +57 -0
  49. data/lib/momento/generated/permissionmessages_pb.rb +48 -0
  50. data/lib/momento/generated/token_pb.rb +43 -0
  51. data/lib/momento/generated/token_services_pb.rb +23 -0
  52. data/lib/momento/generated/webhook_pb.rb +49 -0
  53. data/lib/momento/generated/webhook_services_pb.rb +32 -0
  54. data/lib/momento/{create_cache_response.rb → response/control/create_cache_response.rb} +33 -24
  55. data/lib/momento/{delete_cache_response.rb → response/control/delete_cache_response.rb} +28 -21
  56. data/lib/momento/response/control/list_caches_response.rb +80 -0
  57. data/lib/momento/response/delete_response.rb +45 -0
  58. data/lib/momento/response/error.rb +10 -3
  59. data/lib/momento/response/get_response.rb +107 -0
  60. data/lib/momento/response/response.rb +67 -0
  61. data/lib/momento/response/response_builder.rb +18 -0
  62. data/lib/momento/response/set_response.rb +59 -0
  63. data/lib/momento/response/sort_order.rb +11 -0
  64. data/lib/momento/response/sorted_set/sorted_set_fetch_response.rb +107 -0
  65. data/lib/momento/response/sorted_set/sorted_set_put_element_response.rb +44 -0
  66. data/lib/momento/response/sorted_set/sorted_set_put_elements_response.rb +44 -0
  67. data/lib/momento/ttl.rb +48 -0
  68. data/lib/momento/version.rb +2 -1
  69. data/lib/momento.rb +6 -1
  70. data/momento.gemspec +6 -3
  71. data/release-please-config.json +15 -0
  72. data/sig/momento/auth/credential_provider.rbs +11 -0
  73. data/sig/momento/cache_client.rbs +12 -0
  74. data/sig/momento/collection_ttl.rbs +22 -0
  75. data/sig/momento/config/configuration.rbs +9 -0
  76. data/sig/momento/config/configurations.rbs +9 -0
  77. data/sig/momento/config/transport/grpc_configuration.rbs +7 -0
  78. data/sig/momento/config/transport/transport_strategy.rbs +7 -0
  79. data/sig/momento/list_caches_response.rbs +7 -0
  80. data/sig/momento/sorted_set_fetch_response.rbs +13 -0
  81. data/sig/momento/sorted_set_put_element_response.rbs +5 -0
  82. data/sig/momento/sorted_set_put_elements_response.rbs +5 -0
  83. metadata +127 -28
  84. data/examples/basic.rb +0 -45
  85. data/lib/momento/cacheclient_pb.rb +0 -332
  86. data/lib/momento/cacheclient_services_pb.rb +0 -42
  87. data/lib/momento/controlclient_pb.rb +0 -71
  88. data/lib/momento/controlclient_services_pb.rb +0 -29
  89. data/lib/momento/delete_response.rb +0 -33
  90. data/lib/momento/get_response.rb +0 -80
  91. data/lib/momento/list_caches_response.rb +0 -53
  92. data/lib/momento/response.rb +0 -17
  93. data/lib/momento/set_response.rb +0 -39
  94. data/lib/momento/simple_cache_client.rb +0 -204
@@ -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
@@ -0,0 +1,25 @@
1
+ module Momento
2
+ # Encapsulates gRPC configuration tunables
3
+ class GrpcConfiguration
4
+ # Number of milliseconds the client is willing to wait for an RPC to
5
+ # complete before it is terminated with a DeadlineExceeded error
6
+ attr_reader :deadline
7
+
8
+ def self.with_deadline(deadline)
9
+ return GrpcConfiguration.new(deadline)
10
+ end
11
+
12
+ def initialize(deadline)
13
+ unless deadline.is_a? Integer
14
+ raise Momento::Error::InvalidArgumentError,
15
+ 'Client timeout must be an integer'
16
+ end
17
+ if (deadline.is_a? Integer) && (deadline < 1)
18
+ raise Momento::Error::InvalidArgumentError,
19
+ 'Client timeout must be positive'
20
+ end
21
+
22
+ @deadline = deadline
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,12 @@
1
+ require_relative 'transport_strategy'
2
+
3
+ module Momento
4
+ # Predefined transport strategy for communicating with the Momento server
5
+ class StaticTransportStrategy < TransportStrategy
6
+ attr_reader :grpc_configuration
7
+
8
+ def self.with_grpc_configuration(grpc_configuration)
9
+ return StaticTransportStrategy.new(grpc_configuration)
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,16 @@
1
+ module Momento
2
+ # Low-level gRPC settings for communication with the Momento server
3
+ class TransportStrategy
4
+ attr_reader :grpc_configuration
5
+
6
+ def self.with_grpc_configuration(grpc_configuration)
7
+ return TransportStrategy.new(grpc_configuration)
8
+ end
9
+
10
+ def initialize(grpc_configuration)
11
+ @grpc_configuration = grpc_configuration
12
+ rescue StandardError
13
+ raise Momento::Error::InvalidArgumentError, 'invalid gRPC configuration'
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,38 @@
1
+ module Momento
2
+ module Error
3
+ # Details about a GRPC error.
4
+ # Returned by `response.error.transport_details.grpc`
5
+ #
6
+ # @example
7
+ # # Information about the underlying GRPC error which
8
+ # # caused a Momento error response.
9
+ # puts response.error.transport_details.grpc.details
10
+ class GrpcDetails
11
+ # @return [GRPC::BadStatus] the GRPC exception
12
+ attr_reader :grpc
13
+
14
+ # @param grpc [GRPC::BadStatus] the GRPC exception to wrap
15
+ def initialize(grpc)
16
+ @grpc = grpc
17
+ end
18
+
19
+ # The GRPC numeric error code
20
+ # @return [Integer]
21
+ def code
22
+ grpc.code
23
+ end
24
+
25
+ # Any details about the error provided by GRPC
26
+ # @return [String]
27
+ def details
28
+ grpc.details
29
+ end
30
+
31
+ # Any metadata associated with the GRPC error
32
+ # @return [Hash]
33
+ def metadata
34
+ grpc.metadata
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,20 @@
1
+ module Momento
2
+ module Error
3
+ # A class to capture information specific to particular transport layers.
4
+ #
5
+ # @example
6
+ # # Information about the underlying GRPC error which
7
+ # # caused a Momento error response.
8
+ # puts response.error.transport_details.grpc.details
9
+ class TransportDetails
10
+ # Details specific to GRPC.
11
+ # @return [Momento::Error::GrpcDetails]
12
+ attr_reader :grpc
13
+
14
+ # param grpc [GRPC::BadStatus]
15
+ def initialize(grpc:)
16
+ @grpc = GrpcDetails.new(grpc)
17
+ end
18
+ end
19
+ end
20
+ end