module GoCardless::Utils
Public Instance Methods
String Helpers
# File lib/gocardless/utils.rb, line 8 def camelize(str) str.split('_').map(&:capitalize).join end
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
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
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 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
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
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
# 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
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
Hash Helpers
# File lib/gocardless/utils.rb, line 22 def symbolize_keys(hash) symbolize_keys! hash.dup end
# 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
# File lib/gocardless/utils.rb, line 12 def underscore(str) str.gsub(/(.)([A-Z])/) { "#{$1}_#{$2.downcase}" }.downcase end