Module: RedisClient::RESP3

Defined in:
lib/redis_client/ruby_connection/resp3.rb

Constant Summary collapse

Error =
Class.new(RedisClient::Error)
UnknownType =
Class.new(Error)
SyntaxError =
Class.new(Error)
EOL =
"\r\n".b.freeze
EOL_SIZE =
EOL.bytesize
DUMP_TYPES =

rubocop:disable Style/MutableConstant

{ # rubocop:disable Style/MutableConstant
  String => :dump_string,
  Symbol => :dump_symbol,
  Integer => :dump_numeric,
  Float => :dump_numeric,
}
PARSER_TYPES =
{
  '#' => :parse_boolean,
  '$' => :parse_blob,
  '+' => :parse_string,
  '=' => :parse_verbatim_string,
  '-' => :parse_error,
  ':' => :parse_integer,
  '(' => :parse_integer,
  ',' => :parse_double,
  '_' => :parse_null,
  '*' => :parse_array,
  '%' => :parse_map,
  '~' => :parse_set,
  '>' => :parse_array,
}.transform_keys(&:ord).freeze
INTEGER_RANGE =
((((2**64) / 2) * -1)..(((2**64) / 2) - 1)).freeze

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.dump(command, buffer = nil) ⇒ Object



36
37
38
39
40
41
42
43
44
45
46
47
# File 'lib/redis_client/ruby_connection/resp3.rb', line 36

def dump(command, buffer = nil)
  buffer ||= new_buffer
  command = command.flat_map do |element|
    case element
    when Hash
      element.flatten
    else
      element
    end
  end
  dump_array(command, buffer)
end

.dump_any(object, buffer) ⇒ Object



57
58
59
60
61
62
63
64
65
66
# File 'lib/redis_client/ruby_connection/resp3.rb', line 57

def dump_any(object, buffer)
  method = DUMP_TYPES.fetch(object.class) do |unexpected_class|
    if superclass = DUMP_TYPES.keys.find { |t| t > unexpected_class }
      DUMP_TYPES[unexpected_class] = DUMP_TYPES[superclass]
    else
      raise TypeError, "Unsupported command argument type: #{unexpected_class}"
    end
  end
  send(method, object, buffer)
end

.dump_array(array, buffer) ⇒ Object



68
69
70
71
72
73
74
# File 'lib/redis_client/ruby_connection/resp3.rb', line 68

def dump_array(array, buffer)
  buffer << '*' << array.size.to_s << EOL
  array.each do |item|
    dump_any(item, buffer)
  end
  buffer
end

.dump_hash(hash, buffer) ⇒ Object



84
85
86
87
88
89
90
91
# File 'lib/redis_client/ruby_connection/resp3.rb', line 84

def dump_hash(hash, buffer)
  buffer << '%' << hash.size.to_s << EOL
  hash.each_pair do |key, value|
    dump_any(key, buffer)
    dump_any(value, buffer)
  end
  buffer
end

.dump_numeric(numeric, buffer) ⇒ Object



93
94
95
# File 'lib/redis_client/ruby_connection/resp3.rb', line 93

def dump_numeric(numeric, buffer)
  dump_string(numeric.to_s, buffer)
end

.dump_set(set, buffer) ⇒ Object



76
77
78
79
80
81
82
# File 'lib/redis_client/ruby_connection/resp3.rb', line 76

def dump_set(set, buffer)
  buffer << '~' << set.size.to_s << EOL
  set.each do |item|
    dump_any(item, buffer)
  end
  buffer
end

.dump_string(string, buffer) ⇒ Object



97
98
99
100
# File 'lib/redis_client/ruby_connection/resp3.rb', line 97

def dump_string(string, buffer)
  string = string.b unless string.ascii_only?
  buffer << '$' << string.bytesize.to_s << EOL << string << EOL
end

.load(io) ⇒ Object



49
50
51
# File 'lib/redis_client/ruby_connection/resp3.rb', line 49

def load(io)
  parse(io)
end

.new_bufferObject



53
54
55
# File 'lib/redis_client/ruby_connection/resp3.rb', line 53

def new_buffer
  String.new(encoding: Encoding::BINARY, capacity: 127)
end

.parse(io) ⇒ Object



112
113
114
115
116
117
118
# File 'lib/redis_client/ruby_connection/resp3.rb', line 112

def parse(io)
  type = io.getbyte
  method = PARSER_TYPES.fetch(type) do
    raise UnknownType, "Unknown sigil type: #{type.chr.inspect}"
  end
  send(method, io)
end

.parse_array(io) ⇒ Object



142
143
144
# File 'lib/redis_client/ruby_connection/resp3.rb', line 142

def parse_array(io)
  parse_sequence(io, parse_integer(io))
end

.parse_blob(io) ⇒ Object



194
195
196
197
198
199
200
201
202
# File 'lib/redis_client/ruby_connection/resp3.rb', line 194

def parse_blob(io)
  bytesize = parse_integer(io)
  return if bytesize < 0 # RESP2 nil type

  str = io.read_chomp(bytesize)
  str.force_encoding(Encoding.default_external)
  str.force_encoding(Encoding::BINARY) unless str.valid_encoding?
  str
end

.parse_boolean(io) ⇒ Object



131
132
133
134
135
136
137
138
139
140
# File 'lib/redis_client/ruby_connection/resp3.rb', line 131

def parse_boolean(io)
  case value = io.gets_chomp
  when "t"
    true
  when "f"
    false
  else
    raise SyntaxError, "Expected `t` or `f` after `#`, got: #{value}"
  end
end

.parse_double(io) ⇒ Object



176
177
178
179
180
181
182
183
184
185
186
187
# File 'lib/redis_client/ruby_connection/resp3.rb', line 176

def parse_double(io)
  case value = io.gets_chomp
  when "nan"
    Float::NAN
  when "inf"
    Float::INFINITY
  when "-inf"
    -Float::INFINITY
  else
    Float(value)
  end
end

.parse_error(io) ⇒ Object



127
128
129
# File 'lib/redis_client/ruby_connection/resp3.rb', line 127

def parse_error(io)
  CommandError.parse(parse_string(io))
end

.parse_integer(io) ⇒ Object



172
173
174
# File 'lib/redis_client/ruby_connection/resp3.rb', line 172

def parse_integer(io)
  Integer(io.gets_chomp)
end

.parse_map(io) ⇒ Object



150
151
152
153
154
155
156
# File 'lib/redis_client/ruby_connection/resp3.rb', line 150

def parse_map(io)
  hash = {}
  parse_integer(io).times do
    hash[parse(io)] = parse(io)
  end
  hash
end

.parse_null(io) ⇒ Object



189
190
191
192
# File 'lib/redis_client/ruby_connection/resp3.rb', line 189

def parse_null(io)
  io.skip(EOL_SIZE)
  nil
end

.parse_push(io) ⇒ Object



158
159
160
# File 'lib/redis_client/ruby_connection/resp3.rb', line 158

def parse_push(io)
  parse_array(io)
end

.parse_sequence(io, size) ⇒ Object



162
163
164
165
166
167
168
169
170
# File 'lib/redis_client/ruby_connection/resp3.rb', line 162

def parse_sequence(io, size)
  return if size < 0 # RESP2 nil

  array = Array.new(size)
  size.times do |index|
    array[index] = parse(io)
  end
  array
end

.parse_set(io) ⇒ Object



146
147
148
# File 'lib/redis_client/ruby_connection/resp3.rb', line 146

def parse_set(io)
  parse_sequence(io, parse_integer(io))
end

.parse_string(io) ⇒ Object



120
121
122
123
124
125
# File 'lib/redis_client/ruby_connection/resp3.rb', line 120

def parse_string(io)
  str = io.gets_chomp
  str.force_encoding(Encoding.default_external)
  str.force_encoding(Encoding::BINARY) unless str.valid_encoding?
  str.freeze
end

.parse_verbatim_string(io) ⇒ Object



204
205
206
207
# File 'lib/redis_client/ruby_connection/resp3.rb', line 204

def parse_verbatim_string(io)
  blob = parse_blob(io)
  blob.byteslice(4..-1)
end

Instance Method Details

#dump_symbol(symbol, buffer) ⇒ Object



103
104
105
# File 'lib/redis_client/ruby_connection/resp3.rb', line 103

def dump_symbol(symbol, buffer)
  dump_string(symbol.name, buffer)
end