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 =
Normalizes the many shapes that stream-read replies can arrive in (Hash / [[id, pairs], ...] / flat [id, pairs, id, pairs] outer; Hash / [[k, v], ...] / flat [k, v, k, v] inner) into redis-rb's [[id, => value, ...], ...] shape. Duplicate field names in one entry collapse to last-write-wins, same as redis-rb.
lambda { |reply| entries = case reply when Hash reply when Array reply.first.is_a?(Array) ? reply : reply.each_slice(2) else return [] end entries.map { |entry_id, pairs| [entry_id, Hashify.call(pairs || [])] } }
- HashifyStreamAutoclaim =
lambda { |reply| { 'next' => reply[0], 'entries' => HashifyStreamEntries.call(reply[1]) } }
- 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
217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 |
# File 'lib/valkey/utils.rb', line 217 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 |