module Stripe::Util

Constants

COLOR_CODES

private

OPTS_COPYABLE

Options that should be copyable from one StripeObject to another including options that may be internal.

OPTS_PERSISTABLE

Options that should be persisted between API requests. This includes client, which is an object containing an HTTP client to reuse.

OPTS_USER_SPECIFIED

Options that a user is allowed to specify.

Public Class Methods

check_api_key!(key) click to toggle source
# File lib/stripe/util.rb, line 225
def self.check_api_key!(key)
  raise TypeError, "api_key must be a string" unless key.is_a?(String)

  key
end
check_string_argument!(key) click to toggle source
# File lib/stripe/util.rb, line 219
def self.check_string_argument!(key)
  raise TypeError, "argument must be a string" unless key.is_a?(String)

  key
end
convert_to_stripe_object(data, opts = {}) click to toggle source

Converts a hash of fields or an array of hashes into a StripeObject or array of +StripeObject+s. These new objects will be created as a concrete type as dictated by their `object` field (e.g. an `object` value of `charge` would create an instance of Charge), but if `object` is not present or of an unknown type, the newly created instance will fall back to being a StripeObject.

Attributes

  • data - Hash of fields and values to be converted into a StripeObject.

  • opts - Options for StripeObject like an API key that will be reused on subsequent API calls.

# File lib/stripe/util.rb, line 62
def self.convert_to_stripe_object(data, opts = {})
  opts = normalize_opts(opts)

  case data
  when Array
    data.map { |i| convert_to_stripe_object(i, opts) }
  when Hash
    # Try converting to a known object class.  If none available, fall back
    # to generic StripeObject
    object_classes.fetch(data[:object], StripeObject)
                  .construct_from(data, opts)
  else
    data
  end
end
encode_parameters(params) click to toggle source

Encodes a hash of parameters in a way that's suitable for use as query parameters in a URI or as form parameters in a request body. This mainly involves escaping special characters from parameter keys and values (e.g. `&`).

# File lib/stripe/util.rb, line 132
def self.encode_parameters(params)
  Util.flatten_params(params)
      .map { |k, v| "#{url_encode(k)}=#{url_encode(v)}" }.join("&")
end
flatten_params(params, parent_key = nil) click to toggle source
# File lib/stripe/util.rb, line 148
def self.flatten_params(params, parent_key = nil)
  result = []

  # do not sort the final output because arrays (and arrays of hashes
  # especially) can be order sensitive, but do sort incoming parameters
  params.each do |key, value|
    calculated_key = parent_key ? "#{parent_key}[#{key}]" : key.to_s
    if value.is_a?(Hash)
      result += flatten_params(value, calculated_key)
    elsif value.is_a?(Array)
      result += flatten_params_array(value, calculated_key)
    else
      result << [calculated_key, value]
    end
  end

  result
end
flatten_params_array(value, calculated_key) click to toggle source
# File lib/stripe/util.rb, line 167
def self.flatten_params_array(value, calculated_key)
  result = []
  value.each_with_index do |elem, i|
    if elem.is_a?(Hash)
      result += flatten_params(elem, "#{calculated_key}[#{i}]")
    elsif elem.is_a?(Array)
      result += flatten_params_array(elem, calculated_key)
    else
      result << ["#{calculated_key}[#{i}]", elem]
    end
  end
  result
end
log_debug(message, data = {}) click to toggle source
# File lib/stripe/util.rb, line 98
def self.log_debug(message, data = {})
  config = data.delete(:config) || Stripe.config
  logger = config.logger || Stripe.logger
  if !logger.nil? ||
     !config.log_level.nil? && config.log_level <= Stripe::LEVEL_DEBUG
    log_internal(message, data, color: :blue, level: Stripe::LEVEL_DEBUG,
                                logger: Stripe.logger, out: $stdout)
  end
end
log_error(message, data = {}) click to toggle source
# File lib/stripe/util.rb, line 78
def self.log_error(message, data = {})
  config = data.delete(:config) || Stripe.config
  logger = config.logger || Stripe.logger
  if !logger.nil? ||
     !config.log_level.nil? && config.log_level <= Stripe::LEVEL_ERROR
    log_internal(message, data, color: :cyan, level: Stripe::LEVEL_ERROR,
                                logger: Stripe.logger, out: $stderr)
  end
end
log_info(message, data = {}) click to toggle source
# File lib/stripe/util.rb, line 88
def self.log_info(message, data = {})
  config = data.delete(:config) || Stripe.config
  logger = config.logger || Stripe.logger
  if !logger.nil? ||
     !config.log_level.nil? && config.log_level <= Stripe::LEVEL_INFO
    log_internal(message, data, color: :cyan, level: Stripe::LEVEL_INFO,
                                logger: Stripe.logger, out: $stdout)
  end
end
monotonic_time() click to toggle source

`Time.now` can be unstable in cases like an administrator manually updating its value or a reconcilation via NTP. For this reason, prefer the use of the system's monotonic clock especially where comparing times to calculate an elapsed duration.

Shortcut for getting monotonic time, mostly for purposes of line length and test stubbing. Returns time in seconds since the event used for monotonic reference purposes by the platform (e.g. system boot time).

# File lib/stripe/util.rb, line 189
def self.monotonic_time
  Process.clock_gettime(Process::CLOCK_MONOTONIC)
end
normalize_headers(headers) click to toggle source

Normalizes header keys so that they're all lower case and each hyphen-delimited section starts with a single capitalized letter. For example, `request-id` becomes `Request-Id`. This is useful for extracting certain key values when the user could have set them with a variety of diffent naming schemes.

# File lib/stripe/util.rb, line 236
def self.normalize_headers(headers)
  headers.each_with_object({}) do |(k, v), new_headers|
    k = k.to_s.tr("_", "-") if k.is_a?(Symbol)
    k = k.split("-").reject(&:empty?).map(&:capitalize).join("-")

    new_headers[k] = v
  end
end
normalize_id(id) click to toggle source
# File lib/stripe/util.rb, line 193
def self.normalize_id(id)
  if id.is_a?(Hash) # overloaded id
    params_hash = id.dup
    id = params_hash.delete(:id)
  else
    params_hash = {}
  end
  [id, params_hash]
end
normalize_opts(opts) click to toggle source

The secondary opts argument can either be a string or hash Turn this value into an api_key and a set of headers

# File lib/stripe/util.rb, line 205
def self.normalize_opts(opts)
  case opts
  when String
    { api_key: opts }
  when Hash
    check_api_key!(opts.fetch(:api_key)) if opts.key?(:api_key)
    # Explicitly use dup here instead of clone to avoid preserving freeze
    # state on input params.
    opts.dup
  else
    raise TypeError, "normalize_opts expects a string or a hash"
  end
end
object_classes() click to toggle source
# File lib/stripe/util.rb, line 42
def self.object_classes
  @object_classes ||= Stripe::ObjectTypes.object_names_to_classes
end
object_name_matches_class?(object_name, klass) click to toggle source
# File lib/stripe/util.rb, line 46
def self.object_name_matches_class?(object_name, klass)
  Util.object_classes[object_name] == klass
end
objects_to_ids(obj) click to toggle source
# File lib/stripe/util.rb, line 27
def self.objects_to_ids(obj)
  case obj
  when APIResource
    obj.id
  when Hash
    res = {}
    obj.each { |k, v| res[k] = objects_to_ids(v) unless v.nil? }
    res
  when Array
    obj.map { |v| objects_to_ids(v) }
  else
    obj
  end
end
request_id_dashboard_url(request_id, api_key) click to toggle source

Generates a Dashboard link to inspect a request ID based off of a request ID value and an API key, which is used to attempt to extract whether the environment is livemode or testmode.

# File lib/stripe/util.rb, line 248
def self.request_id_dashboard_url(request_id, api_key)
  env = !api_key.nil? && api_key.start_with?("sk_live") ? "live" : "test"
  "https://dashboard.stripe.com/#{env}/logs/#{request_id}"
end
secure_compare(str_a, str_b) click to toggle source

Constant time string comparison to prevent timing attacks Code borrowed from ActiveSupport

# File lib/stripe/util.rb, line 255
def self.secure_compare(str_a, str_b)
  return false unless str_a.bytesize == str_b.bytesize

  l = str_a.unpack "C#{str_a.bytesize}"

  res = 0
  str_b.each_byte { |byte| res |= byte ^ l.shift }
  res.zero?
end
symbolize_names(object) click to toggle source
# File lib/stripe/util.rb, line 108
def self.symbolize_names(object)
  case object
  when Hash
    new_hash = {}
    object.each do |key, value|
      key = (begin
               key.to_sym
             rescue StandardError
               key
             end) || key
      new_hash[key] = symbolize_names(value)
    end
    new_hash
  when Array
    object.map { |value| symbolize_names(value) }
  else
    object
  end
end
url_encode(key) click to toggle source

Encodes a string in a way that makes it suitable for use in a set of query parameters in a URI or in a set of form parameters in a request body.

# File lib/stripe/util.rb, line 140
def self.url_encode(key)
  CGI.escape(key.to_s).
    # Don't use strict form encoding by changing the square bracket control
    # characters back to their literals. This is fine by the server, and
    # makes these parameter strings easier to read.
    gsub("%5B", "[").gsub("%5D", "]")
end

Private Class Methods

colorize(val, color, isatty) click to toggle source

Uses an ANSI escape code to colorize text if it's going to be sent to a TTY.

# File lib/stripe/util.rb, line 284
def self.colorize(val, color, isatty)
  return val unless isatty

  mode = 0 # default
  foreground = 30 + COLOR_CODES.fetch(color)
  background = 40 + COLOR_CODES.fetch(:default)

  "\033[#{mode};#{foreground};#{background}m#{val}\033[0m"
end
level_name(level) click to toggle source

Turns an integer log level into a printable name.

# File lib/stripe/util.rb, line 296
def self.level_name(level)
  case level
  when LEVEL_DEBUG then "debug"
  when LEVEL_ERROR then "error"
  when LEVEL_INFO  then "info"
  else level
  end
end
log_internal(message, data = {}, color:, level:, logger:, out:) click to toggle source
# File lib/stripe/util.rb, line 306
def self.log_internal(message, data = {}, color:, level:, logger:, out:)
  data_str = data.reject { |_k, v| v.nil? }
                 .map do |(k, v)|
    format("%<key>s=%<value>s",
           key: colorize(k, color, logger.nil? && !out.nil? && out.isatty),
           value: wrap_logfmt_value(v))
  end.join(" ")

  if !logger.nil?
    # the library's log levels are mapped to the same values as the
    # standard library's logger
    logger.log(level,
               format("message=%<message>s %<data_str>s",
                      message: wrap_logfmt_value(message),
                      data_str: data_str))
  elsif out.isatty
    out.puts format("%<level>s %<message>s %<data_str>s",
                    level: colorize(level_name(level)[0, 4].upcase,
                                    color, out.isatty),
                    message: message,
                    data_str: data_str)
  else
    out.puts format("message=%<message>s level=%<level>s %<data_str>s",
                    message: wrap_logfmt_value(message),
                    level: level_name(level),
                    data_str: data_str)
  end
end
wrap_logfmt_value(val) click to toggle source

Wraps a value in double quotes if it looks sufficiently complex so that it can be read by logfmt parsers.

# File lib/stripe/util.rb, line 338
def self.wrap_logfmt_value(val)
  # If value is any kind of number, just allow it to be formatted directly
  # to a string (this will handle integers or floats).
  return val if val.is_a?(Numeric)

  # Hopefully val is a string, but protect in case it's not.
  val = val.to_s

  if %r{[^\w\-/]} =~ val
    # If the string contains any special characters, escape any double
    # quotes it has, remove newlines, and wrap the whole thing in quotes.
    format(%("%<value>s"), value: val.gsub('"', '\"').delete("\n"))
  else
    # Otherwise use the basic value if it looks like a standard set of
    # characters (and allow a few special characters like hyphens, and
    # slashes)
    val
  end
end