vericite_api 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,360 @@
1
+ =begin
2
+ VeriCiteV1
3
+
4
+ =end
5
+
6
+ require 'date'
7
+ require 'json'
8
+ require 'logger'
9
+ require 'tempfile'
10
+ require 'net/https'
11
+ require 'uri'
12
+
13
+ module VeriCiteClient
14
+ class ApiClient
15
+ # The Configuration object holding settings to be used in the API client.
16
+ attr_accessor :config
17
+
18
+ # Defines the headers to be used in HTTP requests of all API calls by default.
19
+ #
20
+ # @return [Hash]
21
+ attr_accessor :default_headers
22
+
23
+ def initialize(config = Configuration.default)
24
+ @config = config
25
+ @user_agent = "ruby-vericite-#{VERSION}"
26
+ @default_headers = {
27
+ 'Content-Type' => "application/json",
28
+ 'User-Agent' => @user_agent
29
+ }
30
+ end
31
+
32
+ def self.default
33
+ @@default ||= ApiClient.new
34
+ end
35
+
36
+ # Call an API with given options.
37
+ #
38
+ # @return [Array<(Object, Fixnum, Hash)>] an array of 3 elements:
39
+ # the data deserialized from response body (could be nil), response status code and response headers.
40
+ def call_api(http_method, path, opts = {})
41
+ response = build_request(http_method, path, opts)
42
+
43
+ if @config.debugging
44
+ @config.logger.debug "HTTP response body ~BEGIN~\n#{response.body}\n~END~\n"
45
+ end
46
+
47
+ unless response.kind_of? Net::HTTPSuccess
48
+ fail ApiError.new(:code => response.code,
49
+ :response_headers => response.to_hash,
50
+ :response_body => response.body),
51
+ response.message
52
+ end
53
+
54
+ if opts[:return_type]
55
+ data = deserialize(response, opts[:return_type])
56
+ else
57
+ data = nil
58
+ end
59
+ return data, response.code, response.to_hash
60
+ end
61
+
62
+ def build_request(http_method, path, opts = {})
63
+ url = build_request_url(path)
64
+ http_method = http_method.to_sym.downcase
65
+
66
+ header_params = @default_headers.merge(opts[:header_params] || {})
67
+ query_params = opts[:query_params] || {}
68
+ form_params = opts[:form_params] || {}
69
+
70
+ if [:post, :patch, :put, :delete].include?(http_method)
71
+ req_body = build_request_body(header_params, form_params, opts[:body])
72
+ if @config.debugging
73
+ @config.logger.debug "HTTP request body param ~BEGIN~\n#{req_body}\n~END~\n"
74
+ end
75
+ end
76
+ uri = URI("#{url}?#{URI.encode_www_form(query_params)}")
77
+ https = Net::HTTP.new(uri.host,uri.port)
78
+ https.use_ssl = true
79
+ rootCA = '/etc/ssl/certs'
80
+ if File.directory? rootCA
81
+ https.ca_path = rootCA
82
+ https.verify_mode = OpenSSL::SSL::VERIFY_PEER
83
+ https.verify_depth = 5
84
+ else
85
+ https.verify_mode = OpenSSL::SSL::VERIFY_NONE
86
+ end
87
+
88
+ path = "#{uri.path}?#{uri.query}"
89
+ case http_method
90
+ when :put
91
+ req = Net::HTTP::Put.new(uri)
92
+ when :post
93
+ req = Net::HTTP::Post.new(uri)
94
+ when :get
95
+ req = Net::HTTP::Get.new(uri)
96
+ when :delete
97
+ req = Net::HTTP::Delete.new(uri)
98
+ end
99
+ req.body = req_body
100
+ # Headers
101
+ header_params.each do |key, value|
102
+ if key == :consumer
103
+ key = "consumer"
104
+ elsif key == :consumerSecret
105
+ key = "consumerSecret"
106
+ end
107
+ req.add_field(key, value)
108
+ end
109
+ res = https.start{|con|
110
+ con.request(req)
111
+ }
112
+ end
113
+
114
+ # Check if the given MIME is a JSON MIME.
115
+ # JSON MIME examples:
116
+ # application/json
117
+ # application/json; charset=UTF8
118
+ # APPLICATION/JSON
119
+ def json_mime?(mime)
120
+ !!(mime =~ /\Aapplication\/json(;.*)?\z/i)
121
+ end
122
+
123
+ # Deserialize the response to the given return type.
124
+ #
125
+ # @param [String] return_type some examples: "User", "Array[User]", "Hash[String,Integer]"
126
+ def deserialize(response, return_type)
127
+ body = response.body
128
+ return nil if body.nil? || body.empty?
129
+
130
+ # return response body directly for String return type
131
+ return body if return_type == 'String'
132
+
133
+ # handle file downloading - save response body into a tmp file and return the File instance
134
+ return download_file(response) if return_type == 'File'
135
+
136
+ # ensuring a default content type
137
+ content_type = response.to_hash['Content-Type'] || 'application/json'
138
+
139
+ fail "Content-Type is not supported: #{content_type}" unless json_mime?(content_type)
140
+
141
+ begin
142
+ data = JSON.parse("[#{body}]", :symbolize_names => true)[0]
143
+ rescue JSON::ParserError => e
144
+ if %w(String Date DateTime).include?(return_type)
145
+ data = body
146
+ else
147
+ raise e
148
+ end
149
+ end
150
+
151
+ convert_to_type data, return_type
152
+ end
153
+
154
+ # Convert data to the given return type.
155
+ def convert_to_type(data, return_type)
156
+ return nil if data.nil?
157
+ case return_type
158
+ when 'String'
159
+ data.to_s
160
+ when 'Integer'
161
+ data.to_i
162
+ when 'Float'
163
+ data.to_f
164
+ when 'BOOLEAN'
165
+ data == true
166
+ when 'DateTime'
167
+ # parse date time (expecting ISO 8601 format)
168
+ DateTime.parse data
169
+ when 'Date'
170
+ # parse date time (expecting ISO 8601 format)
171
+ Date.parse data
172
+ when 'Object'
173
+ # generic object (usually a Hash), return directly
174
+ data
175
+ when /\AArray<(.+)>\z/
176
+ # e.g. Array<Pet>
177
+ sub_type = $1
178
+ data.map {|item| convert_to_type(item, sub_type) }
179
+ when /\AHash\<String, (.+)\>\z/
180
+ # e.g. Hash<String, Integer>
181
+ sub_type = $1
182
+ {}.tap do |hash|
183
+ data.each {|k, v| hash[k] = convert_to_type(v, sub_type) }
184
+ end
185
+ else
186
+ # models, e.g. Pet
187
+ VeriCiteClient.const_get(return_type).new.tap do |model|
188
+ model.build_from_hash data
189
+ end
190
+ end
191
+ end
192
+
193
+ # Save response body into a file in (the defined) temporary folder, using the filename
194
+ # from the "Content-Disposition" header if provided, otherwise a random filename.
195
+ #
196
+ # @see Configuration#temp_folder_path
197
+ # @return [Tempfile] the file downloaded
198
+ def download_file(response)
199
+ content_disposition = response.to_hash['Content-Disposition']
200
+ if content_disposition
201
+ filename = content_disposition[/filename=['"]?([^'"\s]+)['"]?/, 1]
202
+ prefix = sanitize_filename(filename)
203
+ else
204
+ prefix = 'download-'
205
+ end
206
+ prefix = prefix + '-' unless prefix.end_with?('-')
207
+
208
+ tempfile = nil
209
+ encoding = response.body.encoding
210
+ Tempfile.open(prefix, @config.temp_folder_path, encoding: encoding) do |file|
211
+ file.write(response.body)
212
+ tempfile = file
213
+ end
214
+ @config.logger.info "Temp file written to #{tempfile.path}, please copy the file to a proper folder "\
215
+ "with e.g. `FileUtils.cp(tempfile.path, '/new/file/path')` otherwise the temp file "\
216
+ "will be deleted automatically with GC. It's also recommended to delete the temp file "\
217
+ "explicitly with `tempfile.delete`"
218
+ tempfile
219
+ end
220
+
221
+ # Sanitize filename by removing path.
222
+ # e.g. ../../sun.gif becomes sun.gif
223
+ #
224
+ # @param [String] filename the filename to be sanitized
225
+ # @return [String] the sanitized filename
226
+ def sanitize_filename(filename)
227
+ filename.gsub /.*[\/\\]/, ''
228
+ end
229
+
230
+ def build_request_url(path)
231
+ # Add leading and trailing slashes to path
232
+ path = "/#{path}".gsub(/\/+/, '/')
233
+ URI.encode(@config.base_url + path)
234
+ end
235
+
236
+ def build_request_body(header_params, form_params, body)
237
+ # http form
238
+ if header_params['Content-Type'] == 'application/x-www-form-urlencoded' ||
239
+ header_params['Content-Type'] == 'multipart/form-data'
240
+ data = {}
241
+ form_params.each do |key, value|
242
+ case value
243
+ when File, Array, nil
244
+ # let typhoeus handle File, Array and nil parameters
245
+ data[key] = value
246
+ else
247
+ data[key] = value.to_s
248
+ end
249
+ end
250
+ elsif body
251
+ data = body.is_a?(String) ? body : body.to_json
252
+ else
253
+ data = nil
254
+ end
255
+ data
256
+ end
257
+
258
+ # Update hearder and query params based on authentication settings.
259
+ def update_params_for_auth!(header_params, query_params, auth_names)
260
+ Array(auth_names).each do |auth_name|
261
+ auth_setting = @config.auth_settings[auth_name]
262
+ next unless auth_setting
263
+ case auth_setting[:in]
264
+ when 'header' then header_params[auth_setting[:key]] = auth_setting[:value]
265
+ when 'query' then query_params[auth_setting[:key]] = auth_setting[:value]
266
+ else fail ArgumentError, 'Authentication token must be in `query` of `header`'
267
+ end
268
+ end
269
+ end
270
+
271
+ def user_agent=(user_agent)
272
+ @user_agent = user_agent
273
+ @default_headers['User-Agent'] = @user_agent
274
+ end
275
+
276
+ # Return Accept header based on an array of accepts provided.
277
+ # @param [Array] accepts array for Accept
278
+ # @return [String] the Accept header (e.g. application/json)
279
+ def select_header_accept(accepts)
280
+ return nil if accepts.nil? || accepts.empty?
281
+ # use JSON when present, otherwise use all of the provided
282
+ json_accept = accepts.find { |s| json_mime?(s) }
283
+ return json_accept || accepts.join(',')
284
+ end
285
+
286
+ # Return Content-Type header based on an array of content types provided.
287
+ # @param [Array] content_types array for Content-Type
288
+ # @return [String] the Content-Type header (e.g. application/json)
289
+ def select_header_content_type(content_types)
290
+ # use application/json by default
291
+ return 'application/json' if content_types.nil? || content_types.empty?
292
+ # use JSON when present, otherwise use the first one
293
+ json_content_type = content_types.find { |s| json_mime?(s) }
294
+ return json_content_type || content_types.first
295
+ end
296
+
297
+ # Convert object (array, hash, object, etc) to JSON string.
298
+ # @param [Object] model object to be converted into JSON string
299
+ # @return [String] JSON string representation of the object
300
+ def object_to_http_body(model)
301
+ return model if model.nil? || model.is_a?(String)
302
+ _body = nil
303
+ if model.is_a?(Array)
304
+ _body = model.map{|m| object_to_hash(m) }
305
+ else
306
+ _body = object_to_hash(model)
307
+ end
308
+ _body.to_json
309
+ end
310
+
311
+ # Convert object(non-array) to hash.
312
+ # @param [Object] obj object to be converted into JSON string
313
+ # @return [String] JSON string representation of the object
314
+ def object_to_hash(obj)
315
+ if obj.respond_to?(:to_hash)
316
+ obj.to_hash
317
+ else
318
+ obj
319
+ end
320
+ end
321
+
322
+ # Build parameter value according to the given collection format.
323
+ # @param [String] collection_format one of :csv, :ssv, :tsv, :pipes and :multi
324
+ def build_collection_param(param, collection_format)
325
+ case collection_format
326
+ when :csv
327
+ param.join(',')
328
+ when :ssv
329
+ param.join(' ')
330
+ when :tsv
331
+ param.join("\t")
332
+ when :pipes
333
+ param.join('|')
334
+ when :multi
335
+ # return the array directly as typhoeus will handle it as expected
336
+ param
337
+ else
338
+ fail "unknown collection format: #{collection_format.inspect}"
339
+ end
340
+ end
341
+
342
+ def uploadfile(path, file)
343
+ url = URI.parse(path)
344
+
345
+ response = Net::HTTP.start(url.host) do |http|
346
+ http.send_request("PUT", url.request_uri, (file.is_a?(String) ? file : file.read), {
347
+ # This is required, or Net::HTTP will add a default unsigned content-type.
348
+ "content-type" => "",
349
+ })
350
+ end
351
+ unless response.kind_of? Net::HTTPSuccess
352
+ fail ApiError.new(:code => response.code,
353
+ :response_headers => response.to_hash,
354
+ :response_body => response.body),
355
+ response.message
356
+ end
357
+ response
358
+ end
359
+ end
360
+ end
@@ -0,0 +1,28 @@
1
+ =begin
2
+ VeriCiteV1
3
+ =end
4
+
5
+ module VeriCiteClient
6
+ class ApiError < StandardError
7
+ attr_reader :code, :response_headers, :response_body
8
+
9
+ # Usage examples:
10
+ # ApiError.new
11
+ # ApiError.new("message")
12
+ # ApiError.new(:code => 500, :response_headers => {}, :response_body => "")
13
+ # ApiError.new(:code => 404, :message => "Not Found")
14
+ def initialize(arg = nil)
15
+ if arg.is_a? Hash
16
+ arg.each do |k, v|
17
+ if k.to_s == 'message'
18
+ super v
19
+ else
20
+ instance_variable_set "@#{k}", v
21
+ end
22
+ end
23
+ else
24
+ super arg
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,163 @@
1
+ require 'uri'
2
+
3
+ module VeriCiteClient
4
+ class Configuration
5
+ # Defines url scheme
6
+ attr_accessor :scheme
7
+
8
+ # Defines url host
9
+ attr_accessor :host
10
+
11
+ # Defines url base path
12
+ attr_accessor :base_path
13
+
14
+ # Defines API keys used with API Key authentications.
15
+ #
16
+ # @return [Hash] key: parameter name, value: parameter value (API key)
17
+ #
18
+ # @example parameter name is "api_key", API key is "xxx" (e.g. "api_key=xxx" in query string)
19
+ # config.api_key['api_key'] = 'xxx'
20
+ attr_accessor :api_key
21
+
22
+ # Defines API key prefixes used with API Key authentications.
23
+ #
24
+ # @return [Hash] key: parameter name, value: API key prefix
25
+ #
26
+ # @example parameter name is "Authorization", API key prefix is "Token" (e.g. "Authorization: Token xxx" in headers)
27
+ # config.api_key_prefix['api_key'] = 'Token'
28
+ attr_accessor :api_key_prefix
29
+
30
+ # Defines the username used with HTTP basic authentication.
31
+ #
32
+ # @return [String]
33
+ attr_accessor :username
34
+
35
+ # Defines the password used with HTTP basic authentication.
36
+ #
37
+ # @return [String]
38
+ attr_accessor :password
39
+
40
+ # Defines the access token (Bearer) used with OAuth2.
41
+ attr_accessor :access_token
42
+
43
+ # Set this to enable/disable debugging. When enabled (set to true), HTTP request/response
44
+ # details will be logged with `logger.debug` (see the `logger` attribute).
45
+ # Default to false.
46
+ #
47
+ # @return [true, false]
48
+ attr_accessor :debugging
49
+
50
+ # Defines the logger used for debugging.
51
+ # Default to `Rails.logger` (when in Rails) or logging to STDOUT.
52
+ #
53
+ # @return [#debug]
54
+ attr_accessor :logger
55
+
56
+ # Defines the temporary folder to store downloaded files
57
+ # (for API endpoints that have file response).
58
+ # Default to use `Tempfile`.
59
+ #
60
+ # @return [String]
61
+ attr_accessor :temp_folder_path
62
+
63
+ # The time limit for HTTP request in seconds.
64
+ # Default to 0 (never times out).
65
+ attr_accessor :timeout
66
+
67
+ ### TLS/SSL
68
+ # Set this to false to skip verifying SSL certificate when calling API from https server.
69
+ # Default to true.
70
+ #
71
+ # @note Do NOT set it to false in production code, otherwise you would face multiple types of cryptographic attacks.
72
+ #
73
+ # @return [true, false]
74
+ attr_accessor :verify_ssl
75
+
76
+ # Set this to customize the certificate file to verify the peer.
77
+ #
78
+ # @return [String] the path to the certificate file
79
+ #
80
+ # @see The `cainfo` option of Typhoeus, `--cert` option of libcurl. Related source code:
81
+ # https://github.com/typhoeus/typhoeus/blob/master/lib/typhoeus/easy_factory.rb#L145
82
+ attr_accessor :ssl_ca_cert
83
+
84
+ # Client certificate file (for client certificate)
85
+ attr_accessor :cert_file
86
+
87
+ # Client private key file (for client certificate)
88
+ attr_accessor :key_file
89
+
90
+ attr_accessor :inject_format
91
+
92
+ attr_accessor :force_ending_format
93
+
94
+ def initialize
95
+ @scheme = 'https'
96
+ @host = ''
97
+ @base_path = '/v1'
98
+ @api_key = {}
99
+ @api_key_prefix = {}
100
+ @timeout = 0
101
+ @verify_ssl = true
102
+ @cert_file = nil
103
+ @key_file = nil
104
+ @debugging = false
105
+ @inject_format = false
106
+ @force_ending_format = false
107
+ @logger = defined?(Rails) ? Rails.logger : Logger.new(STDOUT)
108
+
109
+ yield(self) if block_given?
110
+ end
111
+
112
+ # The default Configuration object.
113
+ def self.default
114
+ @@default ||= Configuration.new
115
+ end
116
+
117
+ def configure
118
+ yield(self) if block_given?
119
+ end
120
+
121
+ def scheme=(scheme)
122
+ # remove :// from scheme
123
+ @scheme = scheme.sub(/:\/\//, '')
124
+ end
125
+
126
+ def host=(host)
127
+ # remove http(s):// and anything after a slash
128
+ @host = host.sub(/https?:\/\//, '').split('/').first
129
+ end
130
+
131
+ def base_path=(base_path)
132
+ # Add leading and trailing slashes to base_path
133
+ @base_path = "/#{base_path}".gsub(/\/+/, '/')
134
+ @base_path = "" if @base_path == "/"
135
+ end
136
+
137
+ def base_url
138
+ url = "#{scheme}://#{[host, base_path].join('/').gsub(/\/+/, '/')}".sub(/\/+\z/, '')
139
+ URI.encode(url)
140
+ end
141
+
142
+ # Gets API key (with prefix if set).
143
+ # @param [String] param_name the parameter name of API key auth
144
+ def api_key_with_prefix(param_name)
145
+ if @api_key_prefix[param_name]
146
+ "#{@api_key_prefix[param_name]} #{@api_key[param_name]}"
147
+ else
148
+ @api_key[param_name]
149
+ end
150
+ end
151
+
152
+ # Gets Basic Auth token string
153
+ def basic_auth_token
154
+ 'Basic ' + ["#{username}:#{password}"].pack('m').delete("\r\n")
155
+ end
156
+
157
+ # Returns Auth Settings hash for api client.
158
+ def auth_settings
159
+ {
160
+ }
161
+ end
162
+ end
163
+ end