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.respond_to?(:each_slice)
    value.each_slice(2).to_h
  else
    value
  end
}
Pairify =
lambda { |value|
  if value.respond_to?(:each_slice)
    value.each_slice(2).to_a
  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)

  value.each_slice(2).map(&FloatifyPair)
}
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



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
# File 'lib/valkey/utils.rb', line 218

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