Blame view

lib/syspro/util.rb 7.11 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
  
701afa86   Samuel J Clopton   Move configuratio...
23
24
25
26
27
28
29
30
31
32
33
34
35
36
      class << self
        def objects_to_ids(h) # rubocop:disable Metrics/MethodLength, Metrics/LineLength, Naming/UncommunicativeMethodParamName
          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
db76748d   Isaac Lewis   cop a bunch of St...
37
        end
db76748d   Isaac Lewis   cop a bunch of St...
38
  
701afa86   Samuel J Clopton   Move configuratio...
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
        # 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.
        def convert_to_syspro_object(data, opts = {}) # rubocop:disable Metrics/LineLength, Metrics/MethodLength
          case data
          when Array
            data.map { |i| convert_to_syspro_object(i, opts) }
          when Hash
            # 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)
          else
            data
          end
db76748d   Isaac Lewis   cop a bunch of St...
65
        end
db76748d   Isaac Lewis   cop a bunch of St...
66
  
701afa86   Samuel J Clopton   Move configuratio...
67
68
69
70
71
72
73
74
75
76
77
        # The secondary opts argument can either be a string or hash
        # Turn this value into an api_key and a set of headers
        def normalize_opts(opts)
          case opts
          when String
            opts
          when Hash
            opts.clone
          else
            raise TypeError, 'normalize_opts expects a string or a hash'
          end
db76748d   Isaac Lewis   cop a bunch of St...
78
        end
db76748d   Isaac Lewis   cop a bunch of St...
79
  
701afa86   Samuel J Clopton   Move configuratio...
80
81
82
83
84
85
86
87
88
89
90
91
92
93
        # 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 normalize_headers(headers)
          headers.each_with_object({}) do |(k, v), new_headers|
            if k.is_a?(Symbol)
              k = titlecase_parts(k.to_s.tr('_', '-'))
            elsif k.is_a?(String)
              k = titlecase_parts(k)
            end
  
            new_headers[k] = v
db76748d   Isaac Lewis   cop a bunch of St...
94
          end
db76748d   Isaac Lewis   cop a bunch of St...
95
        end
db76748d   Isaac Lewis   cop a bunch of St...
96
  
701afa86   Samuel J Clopton   Move configuratio...
97
98
99
100
        def encode_parameters(params)
          Util.flatten_params(params)
              .map { |k, v| "#{url_encode(k)}=#{url_encode(v)}" }.join('&')
        end
db76748d   Isaac Lewis   cop a bunch of St...
101
  
701afa86   Samuel J Clopton   Move configuratio...
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
        def flatten_params(params, parent_key = nil) # rubocop:disable Metrics/LineLength, Metrics/MethodLength
          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
db76748d   Isaac Lewis   cop a bunch of St...
117
          end
701afa86   Samuel J Clopton   Move configuratio...
118
119
  
          result
db76748d   Isaac Lewis   cop a bunch of St...
120
121
        end
  
701afa86   Samuel J Clopton   Move configuratio...
122
123
124
125
126
127
128
129
130
131
132
133
        def log_error(message, data = {})
          if !logger.nil? || !log_level.nil? && 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
            )
          end
        end
db76748d   Isaac Lewis   cop a bunch of St...
134
  
701afa86   Samuel J Clopton   Move configuratio...
135
136
137
138
139
140
141
142
143
144
145
        def log_info(message, data = {})
          if !logger.nil? || !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: logger,
              out: $stdout
            )
          end
db76748d   Isaac Lewis   cop a bunch of St...
146
        end
db76748d   Isaac Lewis   cop a bunch of St...
147
  
701afa86   Samuel J Clopton   Move configuratio...
148
149
150
151
152
153
154
155
156
157
158
        def log_debug(message, data = {})
          if !logger.nil? || !log_level.nil? && log_level <= Syspro::LEVEL_DEBUG # rubocop:disable Style/GuardClause, Metrics/LineLength
            log_internal(
              message,
              data,
              color: :blue,
              level: Syspro::LEVEL_DEBUG,
              logger: logger,
              out: $stdout
            )
          end
db76748d   Isaac Lewis   cop a bunch of St...
159
        end
db76748d   Isaac Lewis   cop a bunch of St...
160
  
701afa86   Samuel J Clopton   Move configuratio...
161
162
163
164
165
166
        def 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', ']')
db76748d   Isaac Lewis   cop a bunch of St...
167
        end
db76748d   Isaac Lewis   cop a bunch of St...
168
  
701afa86   Samuel J Clopton   Move configuratio...
169
        private
dc8aa5b6   Joe Weakley   Rubocop corrections
170
  
701afa86   Samuel J Clopton   Move configuratio...
171
172
173
174
        # TODO: Make these named required arguments when we drop support for Ruby
        # 2.0.
        def 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)|
dc8aa5b6   Joe Weakley   Rubocop corrections
175
            format(
701afa86   Samuel J Clopton   Move configuratio...
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
              '%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
dc8aa5b6   Joe Weakley   Rubocop corrections
203
              wrap_logfmt_value(message),
701afa86   Samuel J Clopton   Move configuratio...
204
              level_name(level),
dc8aa5b6   Joe Weakley   Rubocop corrections
205
206
              data_str
            )
701afa86   Samuel J Clopton   Move configuratio...
207
208
209
210
211
212
213
214
215
216
          end
        end
  
  
        def logger
          Syspro.configuration.logger
        end
  
        def log_level
          Syspro.configuration.log_level
dc8aa5b6   Joe Weakley   Rubocop corrections
217
218
        end
      end
db76748d   Isaac Lewis   cop a bunch of St...
219
220
    end
  end