solid-redis 1.0.7 → 1.0.8
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/CHANGELOG.md +9 -0
- data/lib/solid_redis/resp.rb +28 -229
- data/lib/solid_redis/version.rb +1 -1
- data/lib/solid_redis.rb +1 -0
- metadata +16 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: '068ad360bb74b73918a5c3ea54efadb9c54d2b60299c9727e0b419f65e9f7222'
|
|
4
|
+
data.tar.gz: 81a8967caed43336537bd71405c4ca08fc31f1d7d281e8c1e8cc3346221ec834
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5b655ca4855ecbbd882beb95dcbc8f9f77323ae9b40efa0cf78bf639a71605d46e8868586960b98c8fd9fdd109f3011c7da0028e74cafda24f54dfb65e4fb40e
|
|
7
|
+
data.tar.gz: 1bac6bf305f29200e8441ce27d36f4938512bf4e74211932d58c9480132480169635cfd18bb5b8ea7db671385f2ac1aef45a87121843c0c3d5136a307c7cac90
|
data/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,14 @@ All notable changes to this project are documented in this file. The format
|
|
|
4
4
|
follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the
|
|
5
5
|
project adheres to [Semantic Versioning](https://semver.org/).
|
|
6
6
|
|
|
7
|
+
## [1.0.8] - 2026-09-29
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- Use `solid-resp-ractor` for RESP2/RESP3 encoding and parsing while
|
|
12
|
+
preserving SolidRedis's existing error classes and `SolidRedis::RESP`
|
|
13
|
+
compatibility API.
|
|
14
|
+
|
|
7
15
|
## [1.0.7] - 2026-09-29
|
|
8
16
|
|
|
9
17
|
### Changed
|
|
@@ -140,6 +148,7 @@ project adheres to [Semantic Versioning](https://semver.org/).
|
|
|
140
148
|
TLS, reconnection), `Pool` owned by a single Ractor, pipelines, and
|
|
141
149
|
lifecycle callbacks via `callback-collection`.
|
|
142
150
|
|
|
151
|
+
[1.0.8]: https://github.com/nicolasva/solid-redis/compare/v1.0.7...v1.0.8
|
|
143
152
|
[1.0.7]: https://github.com/nicolasva/solid-redis/compare/v1.0.6...v1.0.7
|
|
144
153
|
[1.0.6]: https://github.com/nicolasva/solid-redis/compare/v1.0.5...v1.0.6
|
|
145
154
|
[1.0.5]: https://github.com/nicolasva/solid-redis/compare/v1.0.4...v1.0.5
|
data/lib/solid_redis/resp.rb
CHANGED
|
@@ -2,253 +2,52 @@
|
|
|
2
2
|
|
|
3
3
|
module SolidRedis
|
|
4
4
|
module RESP
|
|
5
|
-
CRLF =
|
|
5
|
+
CRLF = SolidRespRactor::Encoder::CRLF
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
def encode(command)
|
|
10
|
-
length = command.sum { |argument| argument.is_a?(Array) ? argument.length : 1 }
|
|
11
|
-
raise ArgumentError, "Redis command cannot be empty" if length.zero?
|
|
7
|
+
module ErrorMapper
|
|
8
|
+
module_function
|
|
12
9
|
|
|
13
|
-
|
|
14
|
-
if
|
|
15
|
-
|
|
10
|
+
def call(message, blob:)
|
|
11
|
+
error_class = if message.start_with?("NOAUTH", "WRONGPASS")
|
|
12
|
+
AuthenticationError
|
|
16
13
|
else
|
|
17
|
-
|
|
14
|
+
CommandError
|
|
18
15
|
end
|
|
16
|
+
error_class.new(message)
|
|
19
17
|
end
|
|
20
18
|
end
|
|
21
19
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
buffer << "$#{value.bytesize}#{CRLF}#{value}#{CRLF}"
|
|
25
|
-
end
|
|
20
|
+
CODEC = SolidRespRactor::Codec.new(error_mapper: ErrorMapper)
|
|
21
|
+
Ractor.make_shareable(CODEC)
|
|
26
22
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
when true then "1"
|
|
32
|
-
when false then "0"
|
|
33
|
-
when nil then ""
|
|
34
|
-
else value.to_s
|
|
35
|
-
end
|
|
23
|
+
module_function
|
|
24
|
+
|
|
25
|
+
def encode(command)
|
|
26
|
+
CODEC.encode(command)
|
|
36
27
|
end
|
|
37
28
|
|
|
38
|
-
class Reader
|
|
29
|
+
class Reader < SolidRespRactor::Reader
|
|
39
30
|
def initialize(io, read_timeout:)
|
|
40
|
-
|
|
41
|
-
@read_timeout = read_timeout
|
|
42
|
-
@buffer = +""
|
|
43
|
-
@offset = 0
|
|
31
|
+
super(io, read_timeout: read_timeout, error_mapper: ErrorMapper)
|
|
44
32
|
end
|
|
45
33
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
@read_timeout = previous
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
# Waits until at least one byte is available without consuming it.
|
|
57
|
-
# Returns +false+ on timeout. Unlike a timed-out +read+, this never
|
|
58
|
-
# leaves a partially consumed frame behind, so it is the safe way to
|
|
59
|
-
# poll for the next Pub/Sub message.
|
|
60
|
-
def wait_readable(timeout)
|
|
61
|
-
return true if available_bytes.positive?
|
|
62
|
-
return true unless @io.respond_to?(:to_io)
|
|
63
|
-
|
|
64
|
-
!IO.select([@io], nil, nil, timeout).nil?
|
|
65
|
-
rescue IOError, SystemCallError => error
|
|
34
|
+
def read(...)
|
|
35
|
+
super
|
|
36
|
+
rescue SolidRespRactor::ProtocolError => error
|
|
37
|
+
raise ProtocolError, error.message, cause: error
|
|
38
|
+
rescue SolidRespRactor::TimeoutError => error
|
|
39
|
+
raise TimeoutError, error.message, cause: error
|
|
40
|
+
rescue SolidRespRactor::ConnectionError => error
|
|
66
41
|
raise ConnectionError, error.message, cause: error
|
|
67
42
|
end
|
|
68
43
|
|
|
69
|
-
def
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
when "$" then read_bulk
|
|
75
|
-
when "*" then read_array(exception)
|
|
76
|
-
when "_" then read_line && nil
|
|
77
|
-
when "#" then read_line == "t"
|
|
78
|
-
when "," then Float(read_line)
|
|
79
|
-
when "(" then Integer(read_line)
|
|
80
|
-
when "%" then read_map(exception)
|
|
81
|
-
when "~", ">" then read_collection(exception)
|
|
82
|
-
when "=" then read_verbatim
|
|
83
|
-
when "!" then error_response(read_sized_string, exception)
|
|
84
|
-
when "|" then read_attribute(exception)
|
|
85
|
-
else raise ProtocolError, "Unknown RESP type byte: #{type.inspect}"
|
|
86
|
-
end
|
|
87
|
-
rescue EOFError
|
|
88
|
-
raise ConnectionError, "Redis closed the connection"
|
|
89
|
-
rescue ArgumentError => error
|
|
90
|
-
raise ProtocolError, "Invalid Redis response: #{error.message}", cause: error
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
private
|
|
94
|
-
|
|
95
|
-
def read_bulk
|
|
96
|
-
length = Integer(read_line)
|
|
97
|
-
return if length == -1
|
|
98
|
-
|
|
99
|
-
read_sized_value(length)
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
def read_array(exception)
|
|
103
|
-
length = Integer(read_line)
|
|
104
|
-
return if length == -1
|
|
105
|
-
|
|
106
|
-
finish_collection(Array.new(length) { read(exception: false) }, exception)
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
def read_collection(exception)
|
|
110
|
-
length = Integer(read_line)
|
|
111
|
-
finish_collection(Array.new(length) { read(exception: false) }, exception)
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
def read_map(exception)
|
|
115
|
-
length = Integer(read_line)
|
|
116
|
-
map = {}.tap do |result|
|
|
117
|
-
length.times { result[read(exception: false)] = read(exception: false) }
|
|
118
|
-
end
|
|
119
|
-
finish_collection(map, exception)
|
|
120
|
-
end
|
|
121
|
-
|
|
122
|
-
def read_attribute(exception)
|
|
123
|
-
attributes = read_map(false)
|
|
124
|
-
value = read(exception: false)
|
|
125
|
-
raise_nested_error(attributes) if exception
|
|
126
|
-
raise_nested_error(value) if exception
|
|
127
|
-
value
|
|
128
|
-
end
|
|
129
|
-
|
|
130
|
-
def finish_collection(value, exception)
|
|
131
|
-
raise_nested_error(value) if exception
|
|
132
|
-
value
|
|
133
|
-
end
|
|
134
|
-
|
|
135
|
-
def raise_nested_error(value)
|
|
136
|
-
case value
|
|
137
|
-
when CommandError
|
|
138
|
-
raise value
|
|
139
|
-
when Array
|
|
140
|
-
value.each { |element| raise_nested_error(element) }
|
|
141
|
-
when Hash
|
|
142
|
-
value.each do |key, element|
|
|
143
|
-
raise_nested_error(key)
|
|
144
|
-
raise_nested_error(element)
|
|
145
|
-
end
|
|
146
|
-
end
|
|
147
|
-
end
|
|
148
|
-
|
|
149
|
-
def read_verbatim
|
|
150
|
-
read_sized_string.byteslice(4..)
|
|
151
|
-
end
|
|
152
|
-
|
|
153
|
-
def read_sized_string
|
|
154
|
-
read_sized_value(Integer(read_line))
|
|
155
|
-
end
|
|
156
|
-
|
|
157
|
-
def read_sized_value(length)
|
|
158
|
-
value = read_bytes(length)
|
|
159
|
-
actual = read_bytes(2)
|
|
160
|
-
raise ProtocolError, "Expected CRLF, got #{actual.inspect}" unless actual == CRLF
|
|
161
|
-
|
|
162
|
-
value
|
|
163
|
-
end
|
|
164
|
-
|
|
165
|
-
def read_line
|
|
166
|
-
loop do
|
|
167
|
-
if (index = @buffer.index(CRLF, @offset))
|
|
168
|
-
value = @buffer.byteslice(@offset, index - @offset)
|
|
169
|
-
@offset = index + 2
|
|
170
|
-
clear_consumed_buffer
|
|
171
|
-
return value
|
|
172
|
-
end
|
|
173
|
-
fill_buffer
|
|
174
|
-
end
|
|
175
|
-
end
|
|
176
|
-
|
|
177
|
-
def read_bytes(length)
|
|
178
|
-
fill_buffer while available_bytes < length
|
|
179
|
-
value = @buffer.byteslice(@offset, length)
|
|
180
|
-
@offset += length
|
|
181
|
-
clear_consumed_buffer
|
|
182
|
-
value
|
|
183
|
-
end
|
|
184
|
-
|
|
185
|
-
def fill_buffer
|
|
186
|
-
compact_buffer
|
|
187
|
-
wait_for = :readable
|
|
188
|
-
deadline = nil
|
|
189
|
-
loop do
|
|
190
|
-
if @io.respond_to?(:to_io)
|
|
191
|
-
chunk = @io.read_nonblock(16_384, exception: false)
|
|
192
|
-
else
|
|
193
|
-
chunk = @io.read(16_384)
|
|
194
|
-
end
|
|
195
|
-
|
|
196
|
-
if chunk == :wait_readable || chunk == :wait_writable
|
|
197
|
-
wait_for = chunk == :wait_readable ? :readable : :writable
|
|
198
|
-
deadline ||= monotonic_time + @read_timeout if @read_timeout
|
|
199
|
-
remaining = deadline && deadline - monotonic_time
|
|
200
|
-
if remaining && remaining <= 0
|
|
201
|
-
raise TimeoutError, "Redis read timed out after #{@read_timeout}s"
|
|
202
|
-
end
|
|
203
|
-
|
|
204
|
-
readers = wait_for == :readable ? [@io] : nil
|
|
205
|
-
writers = wait_for == :writable ? [@io] : nil
|
|
206
|
-
unless IO.select(readers, writers, nil, remaining)
|
|
207
|
-
raise TimeoutError, "Redis read timed out after #{@read_timeout}s"
|
|
208
|
-
end
|
|
209
|
-
next
|
|
210
|
-
end
|
|
211
|
-
raise EOFError if chunk.nil?
|
|
212
|
-
|
|
213
|
-
@buffer << chunk
|
|
214
|
-
return
|
|
215
|
-
end
|
|
216
|
-
rescue IOError, SystemCallError => error
|
|
44
|
+
def wait_readable(...)
|
|
45
|
+
super
|
|
46
|
+
rescue SolidRespRactor::TimeoutError => error
|
|
47
|
+
raise TimeoutError, error.message, cause: error
|
|
48
|
+
rescue SolidRespRactor::ConnectionError => error
|
|
217
49
|
raise ConnectionError, error.message, cause: error
|
|
218
50
|
end
|
|
219
|
-
|
|
220
|
-
def available_bytes
|
|
221
|
-
@buffer.bytesize - @offset
|
|
222
|
-
end
|
|
223
|
-
|
|
224
|
-
def clear_consumed_buffer
|
|
225
|
-
return unless @offset == @buffer.bytesize
|
|
226
|
-
|
|
227
|
-
@buffer.clear
|
|
228
|
-
@offset = 0
|
|
229
|
-
end
|
|
230
|
-
|
|
231
|
-
def compact_buffer
|
|
232
|
-
return if @offset.zero?
|
|
233
|
-
|
|
234
|
-
@buffer = @buffer.byteslice(@offset..) || +""
|
|
235
|
-
@offset = 0
|
|
236
|
-
end
|
|
237
|
-
|
|
238
|
-
def monotonic_time
|
|
239
|
-
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
240
|
-
end
|
|
241
|
-
|
|
242
|
-
def error_response(message, exception)
|
|
243
|
-
error = if message.start_with?("NOAUTH", "WRONGPASS")
|
|
244
|
-
AuthenticationError.new(message)
|
|
245
|
-
else
|
|
246
|
-
CommandError.new(message)
|
|
247
|
-
end
|
|
248
|
-
raise error if exception
|
|
249
|
-
|
|
250
|
-
error
|
|
251
|
-
end
|
|
252
51
|
end
|
|
253
52
|
end
|
|
254
53
|
end
|
data/lib/solid_redis/version.rb
CHANGED
data/lib/solid_redis.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: solid-redis
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.0.
|
|
4
|
+
version: 1.0.8
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Nicolas Vandenbogaerde
|
|
@@ -37,6 +37,20 @@ dependencies:
|
|
|
37
37
|
- - "~>"
|
|
38
38
|
- !ruby/object:Gem::Version
|
|
39
39
|
version: '0.2'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: solid-resp-ractor
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '0.1'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '0.1'
|
|
40
54
|
- !ruby/object:Gem::Dependency
|
|
41
55
|
name: minitest
|
|
42
56
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -104,7 +118,7 @@ licenses:
|
|
|
104
118
|
- MIT
|
|
105
119
|
metadata:
|
|
106
120
|
rubygems_mfa_required: 'true'
|
|
107
|
-
documentation_uri: https://www.rubydoc.info/gems/solid-redis/1.0.
|
|
121
|
+
documentation_uri: https://www.rubydoc.info/gems/solid-redis/1.0.8
|
|
108
122
|
source_code_uri: https://github.com/nicolasva/solid-redis
|
|
109
123
|
changelog_uri: https://github.com/nicolasva/solid-redis/blob/main/CHANGELOG.md
|
|
110
124
|
rdoc_options: []
|