Module: Valkey::Utils
- Included in:
- Valkey
- Defined in:
- lib/valkey/utils.rb
Overview
Valkey Utils module
This module provides utility functions for transforming and processing data structures commonly used in Valkey commands.
It includes methods for converting values to boolean, hash, or float, as well as methods for handling specific Valkey command responses.
Constant Summary collapse
- Boolify =
lambda { |value| return value if value.is_a?(TrueClass) || value.is_a?(FalseClass) value != 0 unless value.nil? }
- BoolifySet =
lambda { |value| case value when "OK" true when nil false else value end }
- Hashify =
lambda { |value| if value.is_a?(Hash) value elsif value.respond_to?(:each_slice) if value.first.is_a?(Array) value.to_h else value.each_slice(2).to_h end else value end }
- Pairify =
lambda { |value| if value.respond_to?(:each_slice) if value.first.is_a?(Array) value else value.each_slice(2).to_a end else value end }
- Floatify =
lambda { |value| case value when "inf" Float::INFINITY when "-inf" -Float::INFINITY when String Float(value) else value end }
- FloatifyPair =
lambda { |(first, score)| [first, Floatify.call(score)] }
- FloatifyPairs =
lambda { |value| return value unless value.respond_to?(:each_slice) if value.first.is_a?(Array) value.map(&FloatifyPair) else value.each_slice(2).map(&FloatifyPair) end }
- HashifyInfo =
lambda { |reply| lines = reply.split("\r\n").grep_v(/^(#|$)/) lines.map! { |line| line.split(':', 2) } lines.compact! lines.to_h }
- HashifyStreams =
lambda { |reply| case reply when nil {} else reply.transform_values { |entries| HashifyStreamEntries.call(entries) } end }
- HashifyStreamEntries =
lambda { |reply| return [] if reply.nil? # In cluster mode, MAP responses come as Hash: {id => [fields], ...} if reply.is_a?(Hash) return reply.map { |entry_id, values| [entry_id, values.is_a?(Array) ? values.flatten : []] } end return [] if !reply.is_a?(Array) || reply.empty? # Reply format: [[entry_id, [field1, value1, field2, value2, ...]], ...] # Match redis-rb: return flat arrays [["id", ["field", "value", ...]], ...] # Check if first element is a pair [entry_id, values_array] first_elem = reply.first if first_elem.is_a?(Array) && first_elem.length == 2 # Already in pair format: [[entry_id, [fields...]], ...] reply.compact.map do |entry_id, values| # Return flat array format like redis-rb, not hash values_array = if values.nil? [] elsif values.is_a?(Array) values else [] end [entry_id, values_array] end else # Flat array format: [entry_id1, [field1, value1, ...], entry_id2, [field2, value2, ...], ...] reply.compact.each_slice(2).map do |entry_id, values| # Return flat array format like redis-rb, not hash values_array = if values.nil? [] elsif values.is_a?(Array) values else [] end [entry_id, values_array] end end }
- HashifyStreamAutoclaim =
lambda { |reply| { 'next' => reply[0], 'entries' => if reply[1].nil? [] elsif reply[1].is_a?(Array) # Reply[1] is already an array of entries: [[id, [field, value, ...]], ...] # Use HashifyStreamEntries to convert them properly HashifyStreamEntries.call(reply[1]) else [] end } }
- HashifyStreamAutoclaimJustId =
lambda { |reply| { 'next' => reply[0], 'entries' => reply[1] } }
- HashifyStreamPendings =
lambda { |reply| { 'size' => reply[0], 'min_entry_id' => reply[1], 'max_entry_id' => reply[2], 'consumers' => reply[3].nil? ? {} : reply[3].to_h } }
- HashifyStreamPendingDetails =
lambda { |reply| reply.map do |arr| { 'entry_id' => arr[0], 'consumer' => arr[1], 'elapsed' => arr[2], 'count' => arr[3] } end }
- HashifyClusterNodeInfo =
lambda { |str| arr = str.split { 'node_id' => arr[0], 'ip_port' => arr[1], 'flags' => arr[2].split(','), 'master_node_id' => arr[3], 'ping_sent' => arr[4], 'pong_recv' => arr[5], 'config_epoch' => arr[6], 'link_state' => arr[7], 'slots' => arr[8].nil? ? nil : Range.new(*arr[8].split('-')) } }
- HashifyClusterSlots =
lambda { |reply| reply.map do |arr| first_slot, last_slot = arr[0..1] master = { 'ip' => arr[2][0], 'port' => arr[2][1], 'node_id' => arr[2][2] } replicas = arr[3..].map { |r| { 'ip' => r[0], 'port' => r[1], 'node_id' => r[2] } } { 'start_slot' => first_slot, 'end_slot' => last_slot, 'master' => master, 'replicas' => replicas } end }
- HashifyClusterNodes =
lambda { |reply| reply.split(/[\r\n]+/).map { |str| HashifyClusterNodeInfo.call(str) } }
- HashifyClusterSlaves =
lambda { |reply| reply.map { |str| HashifyClusterNodeInfo.call(str) } }
- Noop =
->(reply) { reply }
Class Method Summary collapse
Class Method Details
.parse_redis_url(url) ⇒ Object
249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 |
# File 'lib/valkey/utils.rb', line 249 def self.parse_redis_url(url) return {} unless url.is_a?(String) && !url.empty? # Redact userinfo before it ends up in error messages, logs, or trackers. # The regex greedily consumes up to the LAST `@` after `://`, so passwords # containing `/` or `@` are still fully redacted (RFC 3986 allows both in # percent-decoded form; either can appear raw in a user-supplied URL). redacted = url.sub(%r{(\A[^:]+://).*@}, '\1[REDACTED]@').inspect uri = begin URI.parse(url) rescue URI::InvalidURIError raise ArgumentError, "Invalid Valkey URL #{redacted}: URI could not be parsed" end unless ALLOWED_URL_SCHEMES.include?(uri.scheme) raise ArgumentError, "Invalid Valkey URL #{redacted}: scheme must be one of " \ "#{ALLOWED_URL_SCHEMES.join(', ')}, got #{uri.scheme.inspect}" end host = uri.hostname raise ArgumentError, "Invalid Valkey URL #{redacted}: missing host" if host.to_s.empty? result = { host: host, port: uri.port || 6379, ssl: TLS_URL_SCHEMES.include?(uri.scheme) } db_segment = uri.path.to_s.delete_prefix('/') unless db_segment.empty? unless db_segment.match?(/\A\d+\z/) raise ArgumentError, "Invalid Valkey URL #{redacted}: database must be a non-negative integer" end result[:db] = db_segment.to_i end # `URI.parse` returns userinfo *percent-encoded* (RFC 3986 §3.2.1); we # want the raw credential value. Callers (`Valkey#initialize`) will # re-encode with `URI::RFC2396_PARSER.escape` when composing the URI # they hand to the FFI, so leaving the encoded form here would # double-encode (`p%40ss` → `p%2540ss`). See valkey-glide/issues/6659. result[:username] = URI::RFC2396_PARSER.unescape(uri.user) if uri.user && !uri.user.empty? result[:password] = URI::RFC2396_PARSER.unescape(uri.password) if uri.password && !uri.password.empty? result end |