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?

  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) ⇒ Hash

Parse Redis URL format: redis://[:password@]host[/db] Also supports: rediss:// (SSL)

Examples:

parse_redis_url('redis://:secret@localhost:6379/15')
# => { host: 'localhost', port: 6379, password: 'secret', db: 15, ssl: false }

parse_redis_url('rediss://user:secret@localhost:6380/0')
# => { host: 'localhost', port: 6380, username: 'user', password: 'secret', db: 0, ssl: true }

Parameters:

  • url (String)

    Redis URL to parse

Returns:

  • (Hash)

    Parsed connection options with keys: :host, :port, :password, :username, :db, :ssl



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 232

def self.parse_redis_url(url)
  return {} unless url.is_a?(String) && !url.empty?

  # Match redis:// or rediss:// URLs
  # Format: redis[s]://[username:password@]host[:port][/db][?param=value]
  # Supports: redis://host, redis://user:pass@host, redis://:pass@host
  # The regex handles:
  # - No auth: redis://host...
  # - Username and password: redis://user:pass@host...
  # - Password only: redis://:pass@host...
  match = url.match(%r{\A(redis|rediss)://(?:([^:@]*):([^@]+)@)?([^:/]+)(?::(\d+))?(?:/(\d+))?(?:\?.*)?\z})

  return {} unless match

  scheme = match[1]
  username = match[2]
  password = match[3]
  host = match[4]
  port = match[5]&.to_i
  db = match[6]&.to_i
  ssl = scheme == "rediss"

  result = {
    host: host,
    port: port || 6379,
    ssl: ssl
  }

  result[:username] = username if username && !username.empty?
  result[:password] = password if password && !password.empty?
  result[:db] = db if db

  result
end