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
# 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
# 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
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 aStripeObject
. -
opts
- Options forStripeObject
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
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
# 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
# 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
# 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
# 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
# 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
`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
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
# 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
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
# File lib/stripe/util.rb, line 42 def self.object_classes @object_classes ||= Stripe::ObjectTypes.object_names_to_classes end
# File lib/stripe/util.rb, line 46 def self.object_name_matches_class?(object_name, klass) Util.object_classes[object_name] == klass end
# 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
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
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
# 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
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
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
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
# 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
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