module GoCardless::Utils

Public Instance Methods

camelize(str) click to toggle source

String Helpers

# File lib/gocardless/utils.rb, line 8
def camelize(str)
  str.split('_').map(&:capitalize).join
end
flatten_params(obj, ns=nil) click to toggle source

Flatten a hash containing nested hashes and arrays to a non-nested array of key-value pairs.

Examples:

flatten_params(a: 'b')
# => [['a', 'b']]

flatten_params(a: ['b', 'c'])
# => [['a[]', 'b'], ['a[]', 'c']]

flatten_params(a: {b: 'c'})
# => [['a[b]', 'c']]

@param [Hash] obj the hash to flatten @return [Array] an array of key-value pairs (arrays of two strings)

# File lib/gocardless/utils.rb, line 58
def flatten_params(obj, ns=nil)
  case obj
  when Hash
    pairs = obj.map { |k,v| flatten_params(v, ns ? "#{ns}[#{k}]" : k) }
    pairs.empty? ? [] : pairs.inject(&:+)
  when Array
    obj.map { |v| flatten_params(v, "#{ns}[]") }.inject(&:+) || []
  when Time
    [[ns.to_s, iso_format_time(obj)]]
  else
    [[ns.to_s, obj.to_s]]
  end
end
iso_format_time(time) click to toggle source

Format a Time object according to ISO 8601, and convert to UTC.

@param [Time] time the time object to format @return [String] the ISO-formatted time

# File lib/gocardless/utils.rb, line 120
def iso_format_time(time)
  return time unless time.is_a? Time or time.is_a? Date
  time = time.getutc if time.is_a? Time
  time = time.new_offset(0) if time.is_a? DateTime
  time.strftime('%Y-%m-%dT%H:%M:%SZ')
end
normalize_params(params) click to toggle source

Generate a percent-encoded query string from an object. The object may have nested arrays and objects as values. Ordinary top-level key-value pairs will be of the form “name=Bob”, arrays will result in “cars[]=BMW&cars=Fiat”, and nested objects will look like “user=Bob&user=50”. All keys and values will be percent-encoded according to RFC5849 §3.6 and parameters will be normalised according to RFC5849 §3.4.1.3.2.

# File lib/gocardless/utils.rb, line 79
def normalize_params(params)
  flatten_params(params).sort.map do |pair|
    pair.map { |item| percent_encode(item) } * '='
  end * '&'
end
percent_encode(str) click to toggle source

Percent encode a string according to RFC 5849 (section 3.6)

@param [String] str the string to encode @return [String] str the encoded string

# File lib/gocardless/utils.rb, line 38
def percent_encode(str)
  URI.encode(str, /[^a-zA-Z0-9\-\.\_\~]/)
end
secure_compare(a, b) click to toggle source

Given two strings, compare them in constant time (for the length of the string). This can avoid timing attacks when used to compare signed parameters. Borrowed from ActiveSupport::MessageVerifier. github.com/rails/rails/blob/master/activesupport/lib/active_support/message_verifier.rb

@param [String] the first string to compare @param [String] this second string to compare @return [Boolean] the result of the comparison

# File lib/gocardless/utils.rb, line 106
def secure_compare(a, b)
  return false unless a.bytesize == b.bytesize

  l = a.unpack("C#{a.bytesize}")

  res = 0
  b.each_byte { |byte| res |= byte ^ l.shift }
  res == 0
end
sign_params(params, key) click to toggle source

Given a Hash of parameters, normalize them (flatten and convert to a string), then generate the HMAC-SHA-256 signature using the provided key.

@param [Hash] params the parameters to sign @param [String] key the key to sign the params with @return [String] the resulting signature

# File lib/gocardless/utils.rb, line 91
def sign_params(params, key)
  msg = Utils.normalize_params(params)
  digest = OpenSSL::Digest.new('sha256')
  OpenSSL::HMAC.hexdigest(digest, key, msg)
end
singularize(str) click to toggle source
# File lib/gocardless/utils.rb, line 16
def singularize(str)
  # This should probably be a bit more robust
  str.sub(/s$/, '').sub(/i$/, 'us')
end
stringify_times(obj) click to toggle source

Recursively ISO format all time and date values.

@param [Hash] obj the object containing date or time objects @return [Hash] the object with ISO-formatted time stings

# File lib/gocardless/utils.rb, line 131
def stringify_times(obj)
  case obj
  when Hash
    Hash[obj.map { |k,v| [k, stringify_times(v)] }]
  when Array
    obj.map { |v| stringify_times(v) }
  else
    iso_format_time(obj)
  end
end
symbolize_keys(hash) click to toggle source

Hash Helpers

# File lib/gocardless/utils.rb, line 22
def symbolize_keys(hash)
  symbolize_keys! hash.dup
end
symbolize_keys!(hash) click to toggle source
# File lib/gocardless/utils.rb, line 26
def symbolize_keys!(hash)
  hash.keys.each do |key|
    sym_key = key.to_s.to_sym rescue key
    hash[sym_key] = hash.delete(key) unless hash.key?(sym_key)
  end
  hash
end
underscore(str) click to toggle source
# File lib/gocardless/utils.rb, line 12
def underscore(str)
  str.gsub(/(.)([A-Z])/) { "#{$1}_#{$2.downcase}" }.downcase
end