Blame view

lib/syspro/util.rb 6.77 KB
dc8aa5b6   Joe Weakley   Rubocop corrections
1
2
  # frozen_string_literal: true
  
db76748d   Isaac Lewis   cop a bunch of St...
3
  module Syspro
dc8aa5b6   Joe Weakley   Rubocop corrections
4
5
    # Utillity class
    class Util # rubocop:disable Metrics/ClassLength
db76748d   Isaac Lewis   cop a bunch of St...
6
7
      # Options that a user is allowed to specify.
      OPTS_USER_SPECIFIED = Set[
0c0af54a   Isaac Lewis   error handling; c...
8
        :user_id
db76748d   Isaac Lewis   cop a bunch of St...
9
10
      ].freeze
  
0c0af54a   Isaac Lewis   error handling; c...
11
12
13
14
15
16
17
18
19
20
21
22
      # Options that should be copyable from one StripeObject to another
      # including options that may be internal.
      OPTS_COPYABLE = (
        OPTS_USER_SPECIFIED + Set[:api_base]
      ).freeze
  
      # Options that should be persisted between API requests. This includes
      # client, which is an object containing an HTTP client to reuse.
      OPTS_PERSISTABLE = (
        OPTS_USER_SPECIFIED + Set[:client]
      ).freeze
  
dc8aa5b6   Joe Weakley   Rubocop corrections
23
      def self.objects_to_ids(h) # rubocop:disable Metrics/MethodLength, Metrics/LineLength, Naming/UncommunicativeMethodParamName
db76748d   Isaac Lewis   cop a bunch of St...
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
        case h
        when ApiResource
          h.id
        when Hash
          res = {}
          h.each { |k, v| res[k] = objects_to_ids(v) unless v.nil? }
          res
        when Array
          h.map { |v| objects_to_ids(v) }
        else
          h
        end
      end
  
      # Converts a hash of fields or an array of hashes into a +SysproObject+ or
      # array of +SysproObject+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 +SysproObject+.
      #
      # ==== Attributes
      #
      # * +data+ - Hash of fields and values to be converted into a SysproObject.
      # * +opts+ - Options for +SysproObject+ like an API key that will be reused
      #   on subsequent API calls.
dc8aa5b6   Joe Weakley   Rubocop corrections
50
      def self.convert_to_syspro_object(data, opts = {}) # rubocop:disable Metrics/LineLength, Metrics/MethodLength
db76748d   Isaac Lewis   cop a bunch of St...
51
52
53
54
        case data
        when Array
          data.map { |i| convert_to_syspro_object(i, opts) }
        when Hash
dc8aa5b6   Joe Weakley   Rubocop corrections
55
56
57
58
59
60
          # Try converting to a known object class.
          # If none available, fall back to generic SysproObject
          object_classes.fetch(
            data[:object],
            SysproObject
          ).construct_from(data, opts)
db76748d   Isaac Lewis   cop a bunch of St...
61
62
63
64
65
        else
          data
        end
      end
  
0c0af54a   Isaac Lewis   error handling; c...
66
      # The secondary opts argument can either be a string or hash
db76748d   Isaac Lewis   cop a bunch of St...
67
68
69
70
71
72
73
74
      # Turn this value into an api_key and a set of headers
      def self.normalize_opts(opts)
        case opts
        when String
          opts
        when Hash
          opts.clone
        else
dc8aa5b6   Joe Weakley   Rubocop corrections
75
          raise TypeError, 'normalize_opts expects a string or a hash'
db76748d   Isaac Lewis   cop a bunch of St...
76
77
78
79
80
81
82
83
84
85
86
        end
      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.
      def self.normalize_headers(headers)
        headers.each_with_object({}) do |(k, v), new_headers|
          if k.is_a?(Symbol)
dc8aa5b6   Joe Weakley   Rubocop corrections
87
            k = titlecase_parts(k.to_s.tr('_', '-'))
db76748d   Isaac Lewis   cop a bunch of St...
88
89
90
91
92
93
94
95
96
97
          elsif k.is_a?(String)
            k = titlecase_parts(k)
          end
  
          new_headers[k] = v
        end
      end
  
      def self.encode_parameters(params)
        Util.flatten_params(params)
dc8aa5b6   Joe Weakley   Rubocop corrections
98
            .map { |k, v| "#{url_encode(k)}=#{url_encode(v)}" }.join('&')
db76748d   Isaac Lewis   cop a bunch of St...
99
100
      end
  
dc8aa5b6   Joe Weakley   Rubocop corrections
101
      def self.flatten_params(params, parent_key = nil) # rubocop:disable Metrics/LineLength, Metrics/MethodLength
db76748d   Isaac Lewis   cop a bunch of St...
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
        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)
            check_array_of_maps_start_keys!(value)
            result += flatten_params_array(value, calculated_key)
          else
            result << [calculated_key, value]
          end
        end
  
        result
      end
  
      def self.log_error(message, data = {})
dc8aa5b6   Joe Weakley   Rubocop corrections
122
123
124
125
126
127
128
129
130
        if !Syspro.logger.nil? || !Syspro.log_level.nil? && Syspro.log_level <= Syspro::LEVEL_ERROR # rubocop:disable Style/GuardClause, Metrics/LineLength
          log_internal(
            message,
            data,
            color: :cyan,
            level: Syspro::LEVEL_ERROR,
            logger: Syspro.logger,
            out: $stderr
          )
db76748d   Isaac Lewis   cop a bunch of St...
131
132
133
134
        end
      end
  
      def self.log_info(message, data = {})
dc8aa5b6   Joe Weakley   Rubocop corrections
135
136
137
138
139
140
141
142
143
        if !Syspro.logger.nil? || !Syspro.log_level.nil? && Syspro.log_level <= Syspro::LEVEL_INFO # rubocop:disable Style/GuardClause, Metrics/LineLength
          log_internal(
            message,
            data,
            color: :cyan,
            level: Syspro::LEVEL_INFO,
            logger: Syspro.logger,
            out: $stdout
          )
db76748d   Isaac Lewis   cop a bunch of St...
144
145
146
147
        end
      end
  
      def self.log_debug(message, data = {})
dc8aa5b6   Joe Weakley   Rubocop corrections
148
149
150
151
152
153
154
155
156
        if !Syspro.logger.nil? || !Syspro.log_level.nil? && Syspro.log_level <= Syspro::LEVEL_DEBUG # rubocop:disable Style/GuardClause, Metrics/LineLength
          log_internal(
            message,
            data,
            color: :blue,
            level: Syspro::LEVEL_DEBUG,
            logger: Syspro.logger,
            out: $stdout
          )
db76748d   Isaac Lewis   cop a bunch of St...
157
158
159
        end
      end
  
49716587   Isaac Lewis   refactor object s...
160
161
162
163
164
      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.
dc8aa5b6   Joe Weakley   Rubocop corrections
165
          gsub('%5B', '[').gsub('%5D', ']')
49716587   Isaac Lewis   refactor object s...
166
      end
dc8aa5b6   Joe Weakley   Rubocop corrections
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
  
      # TODO: Make these named required arguments when we drop support for Ruby
      # 2.0.
      def self.log_internal(message, data = {}, color: nil, level: nil, logger: nil, out: nil) # rubocop:disable Metrics/LineLength, Metrics/AbcSize, Metrics/MethodLength, Metrics/ParameterLists
        data_str = data.reject { |_k, v| v.nil? }.map do |(k, v)|
          format(
            '%s=%s', # rubocop:disable Style/FormatStringToken
            colorize(k, color, !out.nil? && out.isatty),
            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=%s %s', # rubocop:disable Style/FormatStringToken
              wrap_logfmt_value(message),
              data_str
            )
          )
        elsif out.isatty
          out.puts format(
            '%s %s %s', # rubocop:disable Style/FormatStringToken
            colorize(level_name(level)[0, 4].upcase, color, out.isatty),
            message,
            data_str
          )
        else
          out.puts format(
            'message=%s level=%s %s', # rubocop:disable Style/FormatStringToken
            wrap_logfmt_value(message),
            level_name(level),
            data_str
          )
        end
      end
      private_class_method :log_internal
db76748d   Isaac Lewis   cop a bunch of St...
207
208
    end
  end