momento 0.2.0 → 0.5.0
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 +105 -0
- data/CONTRIBUTING.md +5 -6
- data/Gemfile +1 -6
- data/Gemfile.lock +34 -28
- data/README.md +64 -44
- data/README.template.md +30 -25
- data/examples/.gitignore +1 -0
- data/examples/Gemfile +1 -1
- data/examples/README.md +5 -6
- data/examples/compact.rb +13 -9
- data/examples/example.rb +24 -8
- data/examples/file.rb +7 -6
- data/lib/README-generating-pb.txt +1 -1
- data/lib/momento/auth/credential_provider.rb +78 -0
- data/lib/momento/cache_client.rb +478 -0
- data/lib/momento/collection_ttl.rb +79 -0
- data/lib/momento/config/configuration.rb +42 -0
- data/lib/momento/config/configurations.rb +24 -0
- data/lib/momento/config/transport/grpc_configuration.rb +35 -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 +22 -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 +11 -0
- data/sig/momento/collection_ttl.rbs +22 -0
- data/sig/momento/config/configuration.rbs +13 -0
- data/sig/momento/config/configurations.rbs +9 -0
- data/sig/momento/config/transport/grpc_configuration.rbs +9 -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/compact.rb
CHANGED
@@ -2,18 +2,19 @@
|
|
2
2
|
|
3
3
|
require 'momento'
|
4
4
|
|
5
|
-
#
|
6
|
-
|
7
|
-
|
8
|
-
# Cached items will be deleted after 12.5 seconds.
|
9
|
-
TTL_SECONDS = 12.5
|
5
|
+
# Cached items will be deleted after 10 seconds.
|
6
|
+
TTL_SECONDS = 10
|
10
7
|
|
11
8
|
# The name of the cache to create *and delete*
|
12
|
-
CACHE_NAME = ENV.fetch('MOMENTO_CACHE_NAME')
|
9
|
+
CACHE_NAME = ENV.fetch('MOMENTO_CACHE_NAME', 'ruby-examples')
|
10
|
+
|
11
|
+
# Create a credential provider that loads a Momento API Key from an environment variable.
|
12
|
+
credential_provider = Momento::CredentialProvider.from_env_var('MOMENTO_API_KEY')
|
13
13
|
|
14
14
|
# Instantiate a Momento client.
|
15
|
-
client = Momento::
|
16
|
-
|
15
|
+
client = Momento::CacheClient.new(
|
16
|
+
configuration: Momento::Cache::Configurations::Laptop.latest,
|
17
|
+
credential_provider: credential_provider,
|
17
18
|
default_ttl: TTL_SECONDS
|
18
19
|
)
|
19
20
|
|
@@ -23,7 +24,10 @@ response = client.create_cache(CACHE_NAME)
|
|
23
24
|
raise response.error if response.error?
|
24
25
|
|
25
26
|
# List our caches.
|
26
|
-
|
27
|
+
response = client.list_caches
|
28
|
+
raise response.error if response.error?
|
29
|
+
|
30
|
+
puts "Caches: #{response.cache_names&.join(", ")}"
|
27
31
|
|
28
32
|
# Put an item in the cache.
|
29
33
|
response = client.set(CACHE_NAME, "key", "You cached something!")
|
data/examples/example.rb
CHANGED
@@ -1,20 +1,31 @@
|
|
1
1
|
# An example of the basic functionality of
|
2
|
-
# Momento::
|
2
|
+
# Momento::CacheClient.
|
3
3
|
|
4
4
|
require 'momento'
|
5
5
|
|
6
|
-
# Get your Momento token from an environment variable.
|
7
|
-
TOKEN = ENV.fetch('MOMENTO_AUTH_TOKEN')
|
8
|
-
|
9
6
|
# Cached items will be deleted after 12.5 seconds.
|
10
7
|
TTL_SECONDS = 12.5
|
11
8
|
|
12
9
|
# The name of the cache to create *and delete*
|
13
|
-
CACHE_NAME = ENV.fetch('MOMENTO_CACHE_NAME')
|
10
|
+
CACHE_NAME = ENV.fetch('MOMENTO_CACHE_NAME', 'ruby-examples')
|
11
|
+
|
12
|
+
# Create a credential provider that loads a Momento API Key from an environment variable.
|
13
|
+
credential_provider = Momento::CredentialProvider.from_env_var('MOMENTO_API_KEY')
|
14
|
+
|
15
|
+
# This is a reasonable configuration for dev work on a laptop.
|
16
|
+
configuration = Momento::Cache::Configurations::Laptop.latest
|
17
|
+
# This configuration might be better for a production where you want more aggressive timeouts
|
18
|
+
# configuration = Momento::Cache::Configuration::InRegion.latest
|
19
|
+
# To set a custom timeout, you can use the with_timeout method.
|
20
|
+
# configuration = configuration.with_timeout(10_000)
|
21
|
+
# To increase the number of TCP connections for a client where you expect a high volume of traffic,
|
22
|
+
# you can use the with_num_connections method.
|
23
|
+
# configuration = configuration.with_num_connections(4)
|
14
24
|
|
15
25
|
# Instantiate a Momento client.
|
16
|
-
client = Momento::
|
17
|
-
|
26
|
+
client = Momento::CacheClient.new(
|
27
|
+
configuration: configuration,
|
28
|
+
credential_provider: credential_provider,
|
18
29
|
default_ttl: TTL_SECONDS
|
19
30
|
)
|
20
31
|
|
@@ -29,7 +40,12 @@ elsif response.error?
|
|
29
40
|
end
|
30
41
|
|
31
42
|
# List our caches.
|
32
|
-
|
43
|
+
response = client.list_caches
|
44
|
+
if response.success?
|
45
|
+
puts "Caches: #{response.cache_names&.join(", ")}"
|
46
|
+
elsif response.error?
|
47
|
+
raise "Couldn't list the caches: #{response.error}"
|
48
|
+
end
|
33
49
|
|
34
50
|
# Put an item in the cache.
|
35
51
|
response = client.set(CACHE_NAME, "key", "You cached something!")
|
data/examples/file.rb
CHANGED
@@ -5,14 +5,11 @@
|
|
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
|
|
14
11
|
# The name of the cache to create *and delete*
|
15
|
-
CACHE_NAME = ENV.fetch('MOMENTO_CACHE_NAME')
|
12
|
+
CACHE_NAME = ENV.fetch('MOMENTO_CACHE_NAME', 'ruby-examples')
|
16
13
|
|
17
14
|
# So it can be run from the top of the repo
|
18
15
|
# or from the examples directory.
|
@@ -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,478 @@
|
|
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
|
+
@next_cache_stub_index = 0
|
71
|
+
@num_cache_stubs = @configuration.transport_strategy.grpc_configuration.num_grpc_channels
|
72
|
+
@is_first_request = true
|
73
|
+
end
|
74
|
+
|
75
|
+
# Get a value in a cache.
|
76
|
+
#
|
77
|
+
# The value can be retrieved as either bytes or a string.
|
78
|
+
# @example
|
79
|
+
# response = client.get("my_cache", "key")
|
80
|
+
# if response.hit?
|
81
|
+
# puts "We got #{response.value_string}"
|
82
|
+
# elsif response.miss?
|
83
|
+
# puts "It's not in the cache"
|
84
|
+
# elsif response.error?
|
85
|
+
# raise response.error
|
86
|
+
# end
|
87
|
+
#
|
88
|
+
# @see Momento::GetResponse
|
89
|
+
# @param cache_name [String]
|
90
|
+
# @param key [String] must only contain ASCII characters
|
91
|
+
# @return [Momento::GetResponse]
|
92
|
+
# @raise [TypeError] when the cache_name or key is not a String
|
93
|
+
def get(cache_name, key)
|
94
|
+
builder = GetResponseBuilder.new(
|
95
|
+
context: { cache_name: cache_name, key: key }
|
96
|
+
)
|
97
|
+
|
98
|
+
builder.from_block do
|
99
|
+
cache_stub.get(
|
100
|
+
MomentoProtos::CacheClient::PB__GetRequest.new(cache_key: to_bytes(key)),
|
101
|
+
metadata: grpc_metadata(cache_name)
|
102
|
+
)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# Set a value in a cache.
|
107
|
+
#
|
108
|
+
# If ttl is not set, it will use the default_ttl.
|
109
|
+
# @example
|
110
|
+
# response = client.set("my_cache", "key", "value")
|
111
|
+
# raise response.error if response.error?
|
112
|
+
#
|
113
|
+
# @see Momento::SetResponse
|
114
|
+
# @param cache_name [String]
|
115
|
+
# @param key [String] must only contain ASCII characters
|
116
|
+
# @param value [String] the value to cache
|
117
|
+
# @param ttl [Numeric] time-to-live, in seconds.
|
118
|
+
# @raise [ArgumentError] if the ttl is invalid
|
119
|
+
# @return [Momento::SetResponse]
|
120
|
+
# @raise [TypeError] when the cache_name, key, or value is not a String
|
121
|
+
def set(cache_name, key, value, ttl: default_ttl)
|
122
|
+
ttl = Momento::Ttl.to_ttl(ttl)
|
123
|
+
|
124
|
+
builder = SetResponseBuilder.new(
|
125
|
+
context: { cache_name: cache_name, key: key, value: value, ttl: ttl }
|
126
|
+
)
|
127
|
+
|
128
|
+
builder.from_block do
|
129
|
+
req = MomentoProtos::CacheClient::PB__SetRequest.new(
|
130
|
+
cache_key: to_bytes(key),
|
131
|
+
cache_body: to_bytes(value),
|
132
|
+
ttl_milliseconds: ttl.milliseconds
|
133
|
+
)
|
134
|
+
|
135
|
+
cache_stub.set(req, metadata: grpc_metadata(cache_name))
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
# Delete a key in a cache.
|
140
|
+
#
|
141
|
+
# If the key does not exist, delete will still succeed.
|
142
|
+
# @example
|
143
|
+
# response = client.delete("my_cache", "key")
|
144
|
+
# raise response.error if response.error?
|
145
|
+
#
|
146
|
+
# @see Momento::DeleteResponse
|
147
|
+
# @param cache_name [String]
|
148
|
+
# @param key [String] must only contain ASCII characters
|
149
|
+
# @return [Momento::DeleteResponse]
|
150
|
+
# @raise [TypeError] when the cache_name or key is not a String
|
151
|
+
def delete(cache_name, key)
|
152
|
+
builder = DeleteResponseBuilder.new(
|
153
|
+
context: { cache_name: cache_name, key: key }
|
154
|
+
)
|
155
|
+
|
156
|
+
builder.from_block do
|
157
|
+
cache_stub.delete(
|
158
|
+
MomentoProtos::CacheClient::PB__DeleteRequest.new(cache_key: to_bytes(key)),
|
159
|
+
metadata: grpc_metadata(cache_name)
|
160
|
+
)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
# Create a new Momento cache.
|
165
|
+
# @example
|
166
|
+
# response = client.create_cache("my_cache")
|
167
|
+
# if response.success?
|
168
|
+
# puts "my_cache was created"
|
169
|
+
# elsif response.already_exists?
|
170
|
+
# puts "my_cache already exists"
|
171
|
+
# elsif response.error?
|
172
|
+
# raise response.error
|
173
|
+
# end
|
174
|
+
#
|
175
|
+
# @see Momento::CreateCacheResponse
|
176
|
+
# @param cache_name [String] the name of the cache to create.
|
177
|
+
# @return [Momento::CreateCacheResponse] the response from Momento.
|
178
|
+
# @raise [TypeError] when the cache_name is not a String
|
179
|
+
def create_cache(cache_name)
|
180
|
+
builder = CreateCacheResponseBuilder.new(
|
181
|
+
context: { cache_name: cache_name }
|
182
|
+
)
|
183
|
+
|
184
|
+
builder.from_block do
|
185
|
+
control_stub.create_cache(
|
186
|
+
MomentoProtos::ControlClient::PB__CreateCacheRequest.new(cache_name: validate_cache_name(cache_name))
|
187
|
+
)
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
# Delete an existing Momento cache.
|
192
|
+
#
|
193
|
+
# @example
|
194
|
+
# response = client.delete_cache("my_cache")
|
195
|
+
# raise response.error if response.error?
|
196
|
+
#
|
197
|
+
# @see Momento::DeleteCacheResponse
|
198
|
+
# @param cache_name [String] the name of the cache to delete.
|
199
|
+
# @return [Momento::DeleteCacheResponse] the response from Momento.
|
200
|
+
# @raise [TypeError] when the cache_name is not a String
|
201
|
+
def delete_cache(cache_name)
|
202
|
+
builder = DeleteCacheResponseBuilder.new(
|
203
|
+
context: { cache_name: cache_name }
|
204
|
+
)
|
205
|
+
|
206
|
+
builder.from_block do
|
207
|
+
control_stub.delete_cache(
|
208
|
+
MomentoProtos::ControlClient::PB__DeleteCacheRequest.new(cache_name: validate_cache_name(cache_name))
|
209
|
+
)
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
# Lists your caches.
|
214
|
+
#
|
215
|
+
# @see Momento::ListCachesResponse
|
216
|
+
# @return [Momento::ListCachesResponse]
|
217
|
+
def list_caches
|
218
|
+
builder = ListCachesResponseBuilder.new(
|
219
|
+
context: {}
|
220
|
+
)
|
221
|
+
builder.from_block do
|
222
|
+
control_stub.list_caches(
|
223
|
+
MomentoProtos::ControlClient::PB__ListCachesRequest.new
|
224
|
+
)
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
# Put an element in a sorted set
|
229
|
+
#
|
230
|
+
# If collection_ttl is not set, it will use the default_ttl.
|
231
|
+
# @example
|
232
|
+
# response = client.sorted_set_put_element('my_cache', 'my_set', 'value', 1.0)
|
233
|
+
# raise response.error if response.error?
|
234
|
+
#
|
235
|
+
# @see Momento::SortedSetPutElementResponse
|
236
|
+
# @param cache_name [String]
|
237
|
+
# @param sorted_set_name [String]
|
238
|
+
# @param value [String] the value to add to the sorted set.
|
239
|
+
# @param score [Float] the score of the value. Determines its place in the set.
|
240
|
+
# @param collection_ttl [Momento::CollectionTtl] time-to-live, in seconds.
|
241
|
+
# @raise [ArgumentError] if the ttl is invalid
|
242
|
+
# @return [Momento::SortedSetPutElementResponse]
|
243
|
+
# @raise [TypeError] when the cache_name, sorted_set_name, or value is not a String
|
244
|
+
def sorted_set_put_element(cache_name, sorted_set_name, value, score, collection_ttl: CollectionTtl.from_cache_ttl)
|
245
|
+
collection_ttl = collection_ttl.with_ttl_if_absent(default_ttl.seconds)
|
246
|
+
builder = SortedSetPutElementResponseBuilder.new(
|
247
|
+
context: { cache_name: cache_name, set_name: sorted_set_name, value: value, score: score,
|
248
|
+
collection_ttl: collection_ttl }
|
249
|
+
)
|
250
|
+
|
251
|
+
builder.from_block do
|
252
|
+
req = MomentoProtos::CacheClient::PB__SortedSetPutRequest.new(
|
253
|
+
set_name: to_bytes(sorted_set_name),
|
254
|
+
elements: [{ value: to_bytes(value), score: score }],
|
255
|
+
ttl_milliseconds: collection_ttl.ttl_milliseconds,
|
256
|
+
refresh_ttl: collection_ttl.refresh_ttl
|
257
|
+
)
|
258
|
+
|
259
|
+
# noinspection RubyResolve
|
260
|
+
cache_stub.sorted_set_put(req, metadata: grpc_metadata(cache_name))
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
# Put multiple elements in a sorted set
|
265
|
+
#
|
266
|
+
# If collection_ttl is not set, it will use the default_ttl.
|
267
|
+
# @example
|
268
|
+
# response = client.sorted_set_put_element('my_cache', 'my_set', [['value', 1.0]])
|
269
|
+
# raise response.error if response.error?
|
270
|
+
#
|
271
|
+
# @see Momento::SortedSetPutElementsResponse
|
272
|
+
# @param cache_name [String]
|
273
|
+
# @param sorted_set_name [String]
|
274
|
+
# @param elements [Hash, Array] the elements to add. Must be a hash of String values to Float scores,
|
275
|
+
# an array of arrays [["value", 1.0]], or an array of hashes of value and score [{value: "value", score: 1.0}].
|
276
|
+
# @param collection_ttl [Integer] time-to-live, in seconds.
|
277
|
+
# @raise [ArgumentError] if the ttl is invalid
|
278
|
+
# @return [Momento::SortedSetPutElementsResponse]
|
279
|
+
# @raise [TypeError] when the cache_name, or sorted_set_name is not a String, or if elements is not
|
280
|
+
# an Array or Hash
|
281
|
+
def sorted_set_put_elements(cache_name, sorted_set_name, elements, collection_ttl = CollectionTtl.from_cache_ttl)
|
282
|
+
collection_ttl = collection_ttl.with_ttl_if_absent(default_ttl.seconds)
|
283
|
+
builder = SortedSetPutElementsResponseBuilder.new(
|
284
|
+
context: { cache_name: cache_name, set_name: sorted_set_name, elements: elements,
|
285
|
+
collection_ttl: collection_ttl }
|
286
|
+
)
|
287
|
+
|
288
|
+
builder.from_block do
|
289
|
+
req = MomentoProtos::CacheClient::PB__SortedSetPutRequest.new(
|
290
|
+
set_name: to_bytes(sorted_set_name),
|
291
|
+
elements: to_sorted_set_elements(elements),
|
292
|
+
ttl_milliseconds: collection_ttl.ttl_milliseconds,
|
293
|
+
refresh_ttl: collection_ttl.refresh_ttl
|
294
|
+
)
|
295
|
+
|
296
|
+
# noinspection RubyResolve
|
297
|
+
cache_stub.sorted_set_put(req, metadata: grpc_metadata(cache_name))
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
# rubocop:disable Metrics/ParameterLists
|
302
|
+
|
303
|
+
# Fetch the elements a sorted set by score.
|
304
|
+
#
|
305
|
+
# @example
|
306
|
+
# response = client.sorted_set_fetch_by_score("my_cache", "sorted_set", min_score: 0.0, max_score: 1.0)
|
307
|
+
# raise response.error if response.error?
|
308
|
+
#
|
309
|
+
# @see Momento::SortedSetFetchResponse
|
310
|
+
# @param cache_name [String]
|
311
|
+
# @param sorted_set_name [String]
|
312
|
+
# @param min_score [Float] The minimum score (inclusive) of the elements to fetch. Defaults to negative infinity.
|
313
|
+
# @param max_score [Float] The maximum score (inclusive) of the elements to fetch. Defaults to positive infinity.
|
314
|
+
# @param sort_order [SortOrder] The order to fetch the elements in. Defaults to ascending.
|
315
|
+
# @param offset [Integer] The number of elements to skip before returning the first element. Defaults to 0.
|
316
|
+
# @param count [Integer] The maximum number of elements to return. Defaults to all elements.
|
317
|
+
# @return [Momento::SortedSetFetchResponse]
|
318
|
+
# @raise [TypeError] when the cache_name, or sorted_set_name is not a String.
|
319
|
+
def sorted_set_fetch_by_score(cache_name, sorted_set_name, min_score: nil, max_score: nil,
|
320
|
+
sort_order: SortOrder::ASCENDING, offset: 0, count: -1)
|
321
|
+
builder = SortedSetFetchResponseBuilder.new(
|
322
|
+
context: { cache_name: cache_name, set_name: sorted_set_name, min_score: min_score, max_score: max_score,
|
323
|
+
sort_order: sort_order, offset: offset, count: count }
|
324
|
+
)
|
325
|
+
|
326
|
+
builder.from_block do
|
327
|
+
by_score = build_sorted_set_by_score(min_score, max_score, offset, count)
|
328
|
+
|
329
|
+
req = MomentoProtos::CacheClient::PB__SortedSetFetchRequest.new(
|
330
|
+
set_name: to_bytes(sorted_set_name),
|
331
|
+
order: to_grpc_order(sort_order),
|
332
|
+
with_scores: true,
|
333
|
+
by_score: by_score
|
334
|
+
)
|
335
|
+
|
336
|
+
# noinspection RubyResolve
|
337
|
+
cache_stub.sorted_set_fetch(req, metadata: grpc_metadata(cache_name))
|
338
|
+
end
|
339
|
+
end
|
340
|
+
# rubocop:enable Metrics/ParameterLists
|
341
|
+
|
342
|
+
private
|
343
|
+
|
344
|
+
def cache_stub
|
345
|
+
@cache_stubs ||= (1..@num_cache_stubs).map {
|
346
|
+
CACHE_CLIENT_STUB_CLASS.new(@cache_endpoint, combined_credentials,
|
347
|
+
timeout: @configuration.transport_strategy.grpc_configuration.deadline,
|
348
|
+
channel_args: { 'grpc.use_local_subchannel_pool' => 1 }
|
349
|
+
)
|
350
|
+
}
|
351
|
+
@next_cache_stub_index = (@next_cache_stub_index + 1) % @num_cache_stubs
|
352
|
+
@cache_stubs[@next_cache_stub_index]
|
353
|
+
end
|
354
|
+
|
355
|
+
def control_stub
|
356
|
+
@control_stub ||= CONTROL_CLIENT_STUB_CLASS.new(@control_endpoint, combined_credentials)
|
357
|
+
end
|
358
|
+
|
359
|
+
def combined_credentials
|
360
|
+
@combined_credentials ||= make_combined_credentials
|
361
|
+
end
|
362
|
+
|
363
|
+
def make_combined_credentials
|
364
|
+
# :nocov:
|
365
|
+
auth_proc = proc do
|
366
|
+
{ authorization: @api_key }
|
367
|
+
end
|
368
|
+
# :nocov:
|
369
|
+
|
370
|
+
call_creds = GRPC::Core::CallCredentials.new(auth_proc)
|
371
|
+
|
372
|
+
GRPC::Core::ChannelCredentials.new.compose(call_creds)
|
373
|
+
end
|
374
|
+
|
375
|
+
def to_grpc_order(sort_order)
|
376
|
+
case sort_order
|
377
|
+
when SortOrder::ASCENDING
|
378
|
+
MomentoProtos::CacheClient::PB__SortedSetFetchRequest::Order::ASCENDING
|
379
|
+
when SortOrder::DESCENDING
|
380
|
+
MomentoProtos::CacheClient::PB__SortedSetFetchRequest::Order::DESCENDING
|
381
|
+
else
|
382
|
+
raise TypeError, "Invalid sort order: #{sort_order}"
|
383
|
+
end
|
384
|
+
end
|
385
|
+
|
386
|
+
# Momento accepts sorted sets as an array of hashes. This will transform an array of arrays [["value", 1.0]],
|
387
|
+
# an array of hashes [{value: "value", score: 1.0}], or a hash of values to scores to the correct format.
|
388
|
+
# @param elements [Hash, Array] A hash of string values to scores or an array of tuples (value, score)
|
389
|
+
# # @return [Array<Hash>] An array of sorted set elements, where each element is a hash of :value and :score
|
390
|
+
def to_sorted_set_elements(elements)
|
391
|
+
case elements
|
392
|
+
when Hash
|
393
|
+
elements.map { |value, score| { value: to_bytes(value), score: score.to_f } }
|
394
|
+
when Array
|
395
|
+
if elements.first.is_a?(Hash)
|
396
|
+
elements.map { |element| { value: to_bytes(element[:value]), score: element[:score].to_f } }
|
397
|
+
else
|
398
|
+
elements.map { |value, score| { value: to_bytes(value), score: score.to_f } }
|
399
|
+
end
|
400
|
+
else
|
401
|
+
raise ArgumentError, "Sorted set elements must be a Hash or an Array of tuples"
|
402
|
+
end
|
403
|
+
end
|
404
|
+
|
405
|
+
def build_sorted_set_by_score(min_score, max_score, offset, count)
|
406
|
+
MomentoProtos::CacheClient::PB__SortedSetFetchRequest::PB__ByScore.new(
|
407
|
+
min_score: min_score ? build_score(min_score) : nil,
|
408
|
+
unbounded_min: min_score ? nil : MomentoProtos::Common::PB__Unbounded.new,
|
409
|
+
max_score: max_score ? build_score(max_score) : nil,
|
410
|
+
unbounded_max: max_score ? nil : MomentoProtos::Common::PB__Unbounded.new,
|
411
|
+
offset: offset,
|
412
|
+
count: count
|
413
|
+
)
|
414
|
+
end
|
415
|
+
|
416
|
+
def build_score(score)
|
417
|
+
MomentoProtos::CacheClient::PB__SortedSetFetchRequest::PB__ByScore::PB__Score.new(
|
418
|
+
score: score,
|
419
|
+
exclusive: false
|
420
|
+
)
|
421
|
+
end
|
422
|
+
|
423
|
+
# Ruby uses String for bytes. GRPC wants a String encoded as ASCII.
|
424
|
+
# GRPC will re-encode a String, but treats it as characters; GRPC will
|
425
|
+
# raise if you pass a String with non-ASCII characters.
|
426
|
+
# So we do the re-encoding ourselves in a way that treats the String as
|
427
|
+
# bytes and will not raise. The data is not changed.
|
428
|
+
#
|
429
|
+
# If the input String is ASCII, we treat it as binary data. Otherwise,
|
430
|
+
# we ensure it is encoded as UTF-8 to stop the SDK from being able to
|
431
|
+
# write non-UTF-8 strings to the server.
|
432
|
+
#
|
433
|
+
# A duplicate String is returned, but since Ruby is copy-on-write it
|
434
|
+
# does not copy the data.
|
435
|
+
#
|
436
|
+
# @param string [String] the string to make safe for GRPC bytes
|
437
|
+
# @return [String] a duplicate safe to use as GRPC bytes
|
438
|
+
# @raise [TypeError] when the string is not a String
|
439
|
+
def to_bytes(string)
|
440
|
+
raise TypeError, "expected a String, got a #{string.class}" unless string.is_a?(String)
|
441
|
+
|
442
|
+
if string.encoding == Encoding::ASCII_8BIT
|
443
|
+
string.dup
|
444
|
+
else
|
445
|
+
utf8_encoded = string.encode('UTF-8')
|
446
|
+
utf8_encoded.force_encoding(Encoding::ASCII_8BIT)
|
447
|
+
end
|
448
|
+
end
|
449
|
+
|
450
|
+
def grpc_metadata(cache_name)
|
451
|
+
if @is_first_request
|
452
|
+
@is_first_request = false
|
453
|
+
{
|
454
|
+
cache: validate_cache_name(cache_name),
|
455
|
+
Agent: "ruby:cache:#{VERSION}",
|
456
|
+
'Runtime-Version': "ruby:#{RUBY_VERSION}"
|
457
|
+
}
|
458
|
+
else
|
459
|
+
{ cache: validate_cache_name(cache_name) }
|
460
|
+
end
|
461
|
+
end
|
462
|
+
|
463
|
+
# Return a UTF-8 version of the cache name.
|
464
|
+
#
|
465
|
+
# @param name [String] the cache name to validate
|
466
|
+
# @raise [TypeError] when the name is not a String
|
467
|
+
# @raise [Momento::CacheNameError] when the name is not UTF-8 compatible
|
468
|
+
def validate_cache_name(name)
|
469
|
+
raise TypeError, "Cache name must be a String, got a #{name.class}" unless name.is_a?(String)
|
470
|
+
|
471
|
+
encoded_name = name.encode('UTF-8')
|
472
|
+
raise Momento::CacheNameError, "Cache name must be UTF-8 compatible" unless name.valid_encoding?
|
473
|
+
|
474
|
+
encoded_name
|
475
|
+
end
|
476
|
+
end
|
477
|
+
# rubocop:enable Metrics/ClassLength
|
478
|
+
end
|