|
1 |
| -require 'logger' |
| 1 | +require 'date' |
2 | 2 | require 'json'
|
| 3 | +require 'logger' |
| 4 | +require 'tempfile' |
| 5 | +require 'typhoeus' |
| 6 | +require 'uri' |
3 | 7 |
|
4 | 8 | module {{moduleName}}
|
5 | 9 | class ApiClient
|
6 |
| - attr_accessor :scheme, :host, :base_path, :user_agent, :format, :auth_token, :inject_format, :force_ending_format |
| 10 | + attr_accessor :scheme, :host, :base_path, :user_agent |
7 | 11 |
|
8 | 12 | # Defines the username used with HTTP basic authentication.
|
9 | 13 | #
|
@@ -82,7 +86,6 @@ module {{moduleName}}
|
82 | 86 | # client.api_key['api_key'] = 'your key' # api key authentication
|
83 | 87 | # client.username = 'your username' # username for http basic authentication
|
84 | 88 | # client.password = 'your password' # password for http basic authentication
|
85 |
| - # client.format = 'json' # optional, defaults to 'json' |
86 | 89 | # end
|
87 | 90 | def initialize(&block)
|
88 | 91 | @format = 'json'
|
@@ -131,9 +134,255 @@ module {{moduleName}}
|
131 | 134 | @base_path = "" if @base_path == "/"
|
132 | 135 | end
|
133 | 136 |
|
| 137 | + def call_api(http_method, path, opts = {}) |
| 138 | + request = build_request(http_method, path, opts) |
| 139 | + response = request.run |
| 140 | + |
| 141 | + # record as last response |
| 142 | + @last_response = response |
| 143 | + |
| 144 | + if debugging |
| 145 | + logger.debug "HTTP response body ~BEGIN~\n#{response.body}\n~END~\n" |
| 146 | + end |
| 147 | + |
| 148 | + unless response.success? |
| 149 | + fail ApiError.new(:code => response.code, |
| 150 | + :response_headers => response.headers, |
| 151 | + :response_body => response.body), |
| 152 | + response.status_message |
| 153 | + end |
| 154 | + |
| 155 | + if opts[:return_type] |
| 156 | + return deserialize(response, opts[:return_type]) |
| 157 | + else |
| 158 | + return nil |
| 159 | + end |
| 160 | + end |
| 161 | + |
| 162 | + def build_request(http_method, path, opts = {}) |
| 163 | + url = build_request_url(path) |
| 164 | + http_method = http_method.to_sym.downcase |
| 165 | + |
| 166 | + header_params = @default_headers.merge(opts[:header_params] || {}) |
| 167 | + query_params = opts[:query_params] || {} |
| 168 | + form_params = opts[:form_params] || {} |
| 169 | + |
| 170 | + {{#hasAuthMethods}} |
| 171 | + update_params_for_auth! header_params, query_params, opts[:auth_names] |
| 172 | + {{/hasAuthMethods}} |
| 173 | + |
| 174 | + req_opts = { |
| 175 | + :method => http_method, |
| 176 | + :headers => header_params, |
| 177 | + :params => query_params, |
| 178 | + :ssl_verifypeer => @verify_ssl, |
| 179 | + :cainfo => @ssl_ca_cert, |
| 180 | + :verbose => @debugging |
| 181 | + } |
| 182 | + |
| 183 | + if [:post, :patch, :put, :delete].include?(http_method) |
| 184 | + req_body = build_request_body(header_params, form_params, opts[:body]) |
| 185 | + req_opts.update :body => req_body |
| 186 | + if debugging |
| 187 | + logger.debug "HTTP request body param ~BEGIN~\n#{req_body}\n~END~\n" |
| 188 | + end |
| 189 | + end |
| 190 | + |
| 191 | + Typhoeus::Request.new(url, req_opts) |
| 192 | + end |
| 193 | + |
| 194 | + # Deserialize the response to the given return type. |
| 195 | + # |
| 196 | + # @param [String] return_type some examples: "User", "Array[User]", "Hash[String,Integer]" |
| 197 | + def deserialize(response, return_type) |
| 198 | + body = response.body |
| 199 | + return nil if body.nil? || body.empty? |
| 200 | + |
| 201 | + # handle file downloading - save response body into a tmp file and return the File instance |
| 202 | + return download_file(response) if return_type == 'File' |
| 203 | + |
| 204 | + # ensuring a default content type |
| 205 | + content_type = response.headers['Content-Type'] || 'application/json' |
| 206 | + |
| 207 | + unless content_type.start_with?('application/json') |
| 208 | + fail "Content-Type is not supported: #{content_type}" |
| 209 | + end |
| 210 | + |
| 211 | + begin |
| 212 | + data = JSON.parse("[#{body}]", :symbolize_names => true)[0] |
| 213 | + rescue JSON::ParserError => e |
| 214 | + if %w(String Date DateTime).include?(return_type) |
| 215 | + data = body |
| 216 | + else |
| 217 | + raise e |
| 218 | + end |
| 219 | + end |
| 220 | + |
| 221 | + convert_to_type data, return_type |
| 222 | + end |
| 223 | + |
| 224 | + # Convert data to the given return type. |
| 225 | + def convert_to_type(data, return_type) |
| 226 | + return nil if data.nil? |
| 227 | + case return_type |
| 228 | + when 'String' |
| 229 | + data.to_s |
| 230 | + when 'Integer' |
| 231 | + data.to_i |
| 232 | + when 'Float' |
| 233 | + data.to_f |
| 234 | + when 'BOOLEAN' |
| 235 | + data == true |
| 236 | + when 'DateTime' |
| 237 | + # parse date time (expecting ISO 8601 format) |
| 238 | + DateTime.parse data |
| 239 | + when 'Date' |
| 240 | + # parse date time (expecting ISO 8601 format) |
| 241 | + Date.parse data |
| 242 | + when 'Object' |
| 243 | + # generic object, return directly |
| 244 | + data |
| 245 | + when /\AArray<(.+)>\z/ |
| 246 | + # e.g. Array<Pet> |
| 247 | + sub_type = $1 |
| 248 | + data.map {|item| convert_to_type(item, sub_type) } |
| 249 | + when /\AHash\<String, (.+)\>\z/ |
| 250 | + # e.g. Hash<String, Integer> |
| 251 | + sub_type = $1 |
| 252 | + {}.tap do |hash| |
| 253 | + data.each {|k, v| hash[k] = convert_to_type(v, sub_type) } |
| 254 | + end |
| 255 | + else |
| 256 | + # models, e.g. Pet |
| 257 | + {{moduleName}}.const_get(return_type).new.tap do |model| |
| 258 | + model.build_from_hash data |
| 259 | + end |
| 260 | + end |
| 261 | + end |
| 262 | + |
| 263 | + # Save response body into a file in (the defined) temporary folder, using the filename |
| 264 | + # from the "Content-Disposition" header if provided, otherwise a random filename. |
| 265 | + # |
| 266 | + # @see Configuration#temp_folder_path |
| 267 | + # @return [File] the file downloaded |
| 268 | + def download_file(response) |
| 269 | + tmp_file = Tempfile.new '', @temp_folder_path |
| 270 | + content_disposition = response.headers['Content-Disposition'] |
| 271 | + if content_disposition |
| 272 | + filename = content_disposition[/filename=['"]?([^'"\s]+)['"]?/, 1] |
| 273 | + path = File.join File.dirname(tmp_file), filename |
| 274 | + else |
| 275 | + path = tmp_file.path |
| 276 | + end |
| 277 | + # close and delete temp file |
| 278 | + tmp_file.close! |
| 279 | + |
| 280 | + File.open(path, 'w') { |file| file.write(response.body) } |
| 281 | + logger.info "File written to #{path}. Please move the file to a proper folder for further processing and delete the temp afterwards" |
| 282 | + return File.new(path) |
| 283 | + end |
| 284 | + |
| 285 | + def build_request_url(path) |
| 286 | + url = [host, base_path, path].join('/').gsub(/\/+/, '/') |
| 287 | + url = "#{scheme}://#{url}" |
| 288 | + URI.encode(url) |
| 289 | + end |
| 290 | + |
| 291 | + def build_request_body(header_params, form_params, body) |
| 292 | + # http form |
| 293 | + if header_params['Content-Type'] == 'application/x-www-form-urlencoded' |
| 294 | + data = form_params.dup |
| 295 | + data.each do |key, value| |
| 296 | + data[key] = value.to_s if value && !value.is_a?(File) |
| 297 | + end |
| 298 | + elsif body |
| 299 | + data = body.is_a?(String) ? body : body.to_json |
| 300 | + else |
| 301 | + data = nil |
| 302 | + end |
| 303 | + return data |
| 304 | + end |
| 305 | + |
| 306 | + {{#hasAuthMethods}} |
| 307 | + # Update hearder and query params based on authentication settings. |
| 308 | + def update_params_for_auth!(header_params, query_params, auth_names) |
| 309 | + return unless auth_names |
| 310 | + auth_names.each do |auth_name| |
| 311 | + case auth_name |
| 312 | + {{#authMethods}}when '{{name}}' |
| 313 | + {{#isApiKey}}{{#isKeyInHeader}}header_params ||= {} |
| 314 | + header_params['{{keyParamName}}'] = get_api_key_with_prefix('{{keyParamName}}'){{/isKeyInHeader}}{{#isKeyInQuery}}query_params['{{keyParamName}}'] = get_api_key_with_prefix('{{keyParamName}}'){{/isKeyInQuery}}{{/isApiKey}}{{#isBasic}}http_auth_header = 'Basic ' + ["#{@username}:#{@password}"].pack('m').delete("\r\n") |
| 315 | + header_params['Authorization'] = http_auth_header{{/isBasic}}{{#isOAuth}}# TODO: support oauth{{/isOAuth}} |
| 316 | + {{/authMethods}} |
| 317 | + end |
| 318 | + end |
| 319 | + end |
| 320 | + |
| 321 | + # Get API key (with prefix if set). |
| 322 | + # @param [String] param_name the parameter name of API key auth |
| 323 | + def get_api_key_with_prefix(param_name) |
| 324 | + if @api_key_prefix[param_name] |
| 325 | + "#{@api_key_prefix[param_name]} #{@api_key[param_name]}" |
| 326 | + else |
| 327 | + @api_key[param_name] |
| 328 | + end |
| 329 | + end |
| 330 | + {{/hasAuthMethods}} |
| 331 | + |
134 | 332 | def user_agent=(user_agent)
|
135 | 333 | @user_agent = user_agent
|
136 | 334 | @default_headers['User-Agent'] = @user_agent
|
137 | 335 | end
|
| 336 | + |
| 337 | + # Return Accept header based on an array of accepts provided. |
| 338 | + # @param [Array] accepts array for Accept |
| 339 | + # @return [String] the Accept header (e.g. application/json) |
| 340 | + def select_header_accept(accepts) |
| 341 | + if accepts.empty? |
| 342 | + return |
| 343 | + elsif accepts.any?{ |s| s.casecmp('application/json') == 0 } |
| 344 | + 'application/json' # look for json data by default |
| 345 | + else |
| 346 | + accepts.join(',') |
| 347 | + end |
| 348 | + end |
| 349 | + |
| 350 | + # Return Content-Type header based on an array of content types provided. |
| 351 | + # @param [Array] content_types array for Content-Type |
| 352 | + # @return [String] the Content-Type header (e.g. application/json) |
| 353 | + def select_header_content_type(content_types) |
| 354 | + if content_types.empty? |
| 355 | + 'application/json' # use application/json by default |
| 356 | + elsif content_types.any?{ |s| s.casecmp('application/json')==0 } |
| 357 | + 'application/json' # use application/json if it's included |
| 358 | + else |
| 359 | + content_types[0] # otherwise, use the first one |
| 360 | + end |
| 361 | + end |
| 362 | + |
| 363 | + # Convert object (array, hash, object, etc) to JSON string. |
| 364 | + # @param [Object] model object to be converted into JSON string |
| 365 | + # @return [String] JSON string representation of the object |
| 366 | + def object_to_http_body(model) |
| 367 | + return if model.nil? |
| 368 | + _body = nil |
| 369 | + if model.is_a?(Array) |
| 370 | + _body = model.map{|m| object_to_hash(m) } |
| 371 | + else |
| 372 | + _body = object_to_hash(model) |
| 373 | + end |
| 374 | + _body.to_json |
| 375 | + end |
| 376 | + |
| 377 | + # Convert object(non-array) to hash. |
| 378 | + # @param [Object] obj object to be converted into JSON string |
| 379 | + # @return [String] JSON string representation of the object |
| 380 | + def object_to_hash(obj) |
| 381 | + if obj.respond_to?(:to_hash) |
| 382 | + obj.to_hash |
| 383 | + else |
| 384 | + obj |
| 385 | + end |
| 386 | + end |
138 | 387 | end
|
139 | 388 | end
|
0 commit comments