doi_extractor 1.0.5

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.
Files changed (70) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +54 -0
  3. data/Capfile +34 -0
  4. data/Gemfile +6 -0
  5. data/README.md +50 -0
  6. data/Rakefile +43 -0
  7. data/bin/doi_extractor +18 -0
  8. data/config/deploy.rb +34 -0
  9. data/config/deploy/production.rb +65 -0
  10. data/doi_extractor.gemspec +29 -0
  11. data/lib/doi_extractor.rb +25 -0
  12. data/lib/doi_extractor/cancel_command.rb +24 -0
  13. data/lib/doi_extractor/command.rb +81 -0
  14. data/lib/doi_extractor/command_line_parser.rb +154 -0
  15. data/lib/doi_extractor/create_command.rb +32 -0
  16. data/lib/doi_extractor/download_command.rb +109 -0
  17. data/lib/doi_extractor/download_location.rb +86 -0
  18. data/lib/doi_extractor/errors.rb +34 -0
  19. data/lib/doi_extractor/ipums_client.rb +159 -0
  20. data/lib/doi_extractor/ipums_uri_builder.rb +51 -0
  21. data/lib/doi_extractor/old_ruby_patch.rb +25 -0
  22. data/lib/doi_extractor/options.rb +132 -0
  23. data/lib/doi_extractor/secrets.rb +18 -0
  24. data/lib/doi_extractor/status_command.rb +62 -0
  25. data/lib/doi_extractor/version.rb +3 -0
  26. data/spec/fixtures/api_creds.yml +2 -0
  27. data/spec/reports/SPEC-DoiExtractor-CancelCommand-when-successful.xml +7 -0
  28. data/spec/reports/SPEC-DoiExtractor-CancelCommand.xml +3 -0
  29. data/spec/reports/SPEC-DoiExtractor-CommandLineParser-with-a-valid-create-command-with-email.xml +14 -0
  30. data/spec/reports/SPEC-DoiExtractor-CommandLineParser-with-a-valid-create-command.xml +9 -0
  31. data/spec/reports/SPEC-DoiExtractor-CommandLineParser-with-a-valid-download-command.xml +9 -0
  32. data/spec/reports/SPEC-DoiExtractor-CommandLineParser-with-a-valid-status-command.xml +9 -0
  33. data/spec/reports/SPEC-DoiExtractor-CommandLineParser-with-an-invalid-command.xml +9 -0
  34. data/spec/reports/SPEC-DoiExtractor-CommandLineParser-with-an-unknown-option.xml +5 -0
  35. data/spec/reports/SPEC-DoiExtractor-CommandLineParser.xml +3 -0
  36. data/spec/reports/SPEC-DoiExtractor-CreateCommand-when-an-existing-extract-is-processing.xml +7 -0
  37. data/spec/reports/SPEC-DoiExtractor-CreateCommand-when-successful.xml +7 -0
  38. data/spec/reports/SPEC-DoiExtractor-CreateCommand.xml +3 -0
  39. data/spec/reports/SPEC-DoiExtractor-DownloadCommand-user-cancels-download.xml +7 -0
  40. data/spec/reports/SPEC-DoiExtractor-DownloadCommand-when-an-extract-is-available-when-force-is-not-set.xml +5 -0
  41. data/spec/reports/SPEC-DoiExtractor-DownloadCommand-when-an-extract-is-available.xml +13 -0
  42. data/spec/reports/SPEC-DoiExtractor-DownloadCommand.xml +3 -0
  43. data/spec/reports/SPEC-DoiExtractor-DownloadLocation.xml +11 -0
  44. data/spec/reports/SPEC-DoiExtractor-IpumsClient.xml +7 -0
  45. data/spec/reports/SPEC-DoiExtractor-IpumsUriBuilder-internal-environment.xml +5 -0
  46. data/spec/reports/SPEC-DoiExtractor-IpumsUriBuilder-live-environment.xml +5 -0
  47. data/spec/reports/SPEC-DoiExtractor-IpumsUriBuilder-local-environment.xml +5 -0
  48. data/spec/reports/SPEC-DoiExtractor-IpumsUriBuilder.xml +3 -0
  49. data/spec/reports/SPEC-DoiExtractor-Options-for-command-with-create-command-with-invalid-doi-version.xml +5 -0
  50. data/spec/reports/SPEC-DoiExtractor-Options-for-command-with-create-command.xml +7 -0
  51. data/spec/reports/SPEC-DoiExtractor-Options-for-command-with-download-command.xml +7 -0
  52. data/spec/reports/SPEC-DoiExtractor-Options-for-command-with-status-command.xml +7 -0
  53. data/spec/reports/SPEC-DoiExtractor-Options-for-command.xml +3 -0
  54. data/spec/reports/SPEC-DoiExtractor-Options-when-setting-path-values.xml +9 -0
  55. data/spec/reports/SPEC-DoiExtractor-Options.xml +5 -0
  56. data/spec/reports/SPEC-DoiExtractor-Secrets.xml +7 -0
  57. data/spec/reports/SPEC-DoiExtractor-StatusCommand.xml +5 -0
  58. data/spec/spec_helper.rb +20 -0
  59. data/spec/support/test_input.rb +36 -0
  60. data/spec/unit/cancel_command_spec.rb +28 -0
  61. data/spec/unit/command_line_parser_spec.rb +68 -0
  62. data/spec/unit/create_command_spec.rb +44 -0
  63. data/spec/unit/download_command_spec.rb +139 -0
  64. data/spec/unit/download_location_spec.rb +71 -0
  65. data/spec/unit/ipums_client_spec.rb +23 -0
  66. data/spec/unit/ipums_uri_builder_spec.rb +26 -0
  67. data/spec/unit/options_spec.rb +86 -0
  68. data/spec/unit/secrets_spec.rb +14 -0
  69. data/spec/unit/status_command_spec.rb +46 -0
  70. metadata +282 -0
@@ -0,0 +1,154 @@
1
+ module DoiExtractor
2
+ class CommandLineParser
3
+
4
+ attr_reader :top_level_parser, :command_parsers, :command_parser, :options, :error
5
+
6
+ def initialize
7
+ @command_parsers = {
8
+ 'status' => OptionParser.new do |opts|
9
+ opts.banner = "Usage: #{File.basename($0)} status [options]"
10
+ opts.version = VERSION
11
+ default_opts(opts)
12
+ api_opts(opts)
13
+ path_opts(opts)
14
+ extract_group_opts(opts)
15
+ end,
16
+
17
+ 'create' => OptionParser.new do |opts|
18
+ opts.banner = "Usage: #{File.basename($0)} create [options]"
19
+ opts.version = VERSION
20
+ default_opts(opts)
21
+ api_opts(opts)
22
+
23
+ opts.on(:REQUIRED, '-v', '--doi-version DOI_VERSION', 'DOI version') do |v|
24
+ self.options.doi_version = v
25
+ end
26
+
27
+ opts.on('-y', '--year YEAR', 'Specify year (defaults to current year)') do |v|
28
+ self.options.year = v
29
+ end
30
+
31
+ opts.on('-E', '--email EMAIL', 'Specify email address to notify when all extracts are complete') do |v|
32
+ self.options.email = v
33
+ end
34
+ end,
35
+
36
+ 'download' => OptionParser.new do |opts|
37
+ opts.banner = "Usage: #{File.basename($0)} download [options]"
38
+ opts.version = VERSION
39
+ default_opts(opts)
40
+ api_opts(opts)
41
+ path_opts(opts)
42
+ extract_group_opts(opts)
43
+ force_opts(opts, 'Downloads')
44
+ end,
45
+
46
+ 'cancel' => OptionParser.new do |opts|
47
+ opts.banner = "Usage: #{File.basename($0)} cancel [options]"
48
+ opts.version = VERSION
49
+ default_opts(opts)
50
+ api_opts(opts)
51
+ extract_group_opts(opts)
52
+ force_opts(opts, 'Cancels')
53
+ end
54
+ }
55
+
56
+ @top_level_parser = OptionParser.new do |opts|
57
+ opts.version = VERSION
58
+ opts.banner = "Usage: #{File.basename($0)} <COMMAND> [options]\nAvailable Commands: #{@command_parsers.keys.join(', ')}\n\nSee '#{File.basename($0)} <COMMAND> --help' for more information on a specific command"
59
+ end
60
+ end
61
+
62
+ def parse(args)
63
+ begin
64
+ @top_level_parser.order!(args)
65
+
66
+ command = args.shift.to_s.downcase
67
+ @command_parser = @command_parsers[command]
68
+
69
+ if @command_parser
70
+ @options = Options.for_command(command)
71
+ @command_parser.order!(args)
72
+ if @options.valid?
73
+ @valid = true
74
+ else
75
+ @valid = false
76
+ @error = @options.errors.join("\n")
77
+ end
78
+ else
79
+ @valid = false
80
+ @error = 'Invalid Command'
81
+ end
82
+ rescue OptionParser::ParseError => err
83
+ @error = err
84
+ @valid = false
85
+ end
86
+ end
87
+
88
+ def valid?
89
+ @valid
90
+ end
91
+
92
+ def to_s
93
+ [error, @command_parser || @top_level_parser].compact.join("\n\n")
94
+ end
95
+
96
+ private
97
+
98
+ def default_opts(opts)
99
+ opts.on('-e', '--environment ENVIRONMENT', Options::ENVIRONMENTS, "Select environment (defaults to live; values: #{Options::ENVIRONMENTS.join(', ')})") do |v|
100
+ self.options.environment = v
101
+ end
102
+
103
+ opts.on('-p', '--project PROJECT', Options::PROJECTS, "Select project (required; values: #{Options::PROJECTS.join(', ')})") do |v|
104
+ self.options.project = v
105
+ end
106
+
107
+ opts.on('-V', '--[no-]verbose', 'Run verbosely') do |v|
108
+ self.options.verbose = v
109
+ end
110
+ end
111
+
112
+ def api_opts(opts)
113
+ opts.on('-a', '--api-path URI', URI, 'API path (calculated from env and project by default)') do |v|
114
+ self.options.api_uri = v
115
+ end
116
+
117
+ opts.on('--api-user USER', 'API username (Reads config from /pkg by default)') do |v|
118
+ self.options.api_username = v
119
+ end
120
+
121
+ opts.on('--api-password PASSWORD', 'API password (Reads config from /pkg by default)') do |v|
122
+ self.options.api_password = v
123
+ end
124
+ end
125
+
126
+ def path_opts(opts)
127
+ opts.on('-b', '--download-base-path PATH', 'Override download path') do |v|
128
+ self.options.download_base_path = v
129
+ end
130
+ end
131
+
132
+ def extract_group_opts(opts)
133
+ opts.on('-i', '--id ID', Integer, 'DOI Extract ID') do |v|
134
+ self.options.extract_group_id = v
135
+ end
136
+ end
137
+
138
+ def force_opts(opts, verb)
139
+ opts.on('-f', '--force', "#{verb} without prompting") do |v|
140
+ self.options.force = v
141
+ end
142
+ end
143
+
144
+ class << self
145
+ private :new
146
+
147
+ def parse(args)
148
+ opts = new
149
+ opts.parse(args.dup)
150
+ opts
151
+ end
152
+ end
153
+ end
154
+ end
@@ -0,0 +1,32 @@
1
+ module DoiExtractor
2
+ class CreateCommand < Command
3
+
4
+ def _execute
5
+
6
+ say('checking existing DOI extracts', true)
7
+ if any_current_doi_extracts?
8
+ fail('A DOI Extract is already in progress. Nothing created.')
9
+ end
10
+
11
+ say('Creating DOI extract group...')
12
+ ex = ipums_client.create_doi_extract(options.doi_version, options.year, options.email)
13
+
14
+ fail('Server did not return any data; extract status is unknown') unless ex
15
+ fail("Server created DOI extract with ID #{ex.id} and invalid status #{ex.status}") unless ex.status == 'empty'
16
+
17
+ say("DOI Extract with ID #{ex.id} submitted. To check status run the following command:\n doi_extractor status -p #{options.project} -e #{options.environment} -i #{ex.id}\n")
18
+
19
+ if options.email
20
+ say("An email will be sent to #{options.email} after the last extract has finished. At that time you will be able to download and archive the extracts.")
21
+ end
22
+
23
+ end
24
+
25
+ def any_current_doi_extracts?
26
+ in_progress_status = ['empty', 'waiting_to_populate', 'populating', 'unsubmitted', 'waiting_to_submit', 'submitting', 'submitted']
27
+ recent = ipums_client.recent_doi_extracts
28
+ recent.any? { |e| in_progress_status.include?(e.status) }
29
+ end
30
+
31
+ end
32
+ end
@@ -0,0 +1,109 @@
1
+ module DoiExtractor
2
+ class DownloadCommand < Command
3
+
4
+ def _execute
5
+ say('retrieving DOI extract', true)
6
+ doi_extract = ipums_client.get_doi_extract(options.extract_group_id)
7
+
8
+ unless doi_extract
9
+ fail("no DOI extract with ID #{id} found")
10
+ end
11
+
12
+ unless doi_extract.all_data_available
13
+ fail('The specified DOI extract group is not ready to download')
14
+ end
15
+
16
+ location = DownloadLocation.new(options.download_base_path, doi_extract)
17
+
18
+ if options.force
19
+ say('skipping user confirmation', true)
20
+ else
21
+ i = ask("download DOI extract files to #{location.path}?")
22
+ if i.to_s.downcase[0...1] != 'y'
23
+ fail('download cancelled')
24
+ end
25
+ end
26
+
27
+ location.ensure!
28
+
29
+ doi_extract.extract_requests.each_with_index do |er, idx|
30
+
31
+ if location.complete_extract_request?(er)
32
+ say("ER #{er.id} already downloaded")
33
+ else
34
+ say_nb("ER #{er.id} downloading (#{idx + 1} of #{doi_extract.extract_requests.count})...")
35
+ location.clean_tmp!(er)
36
+ er.files.each do |f|
37
+ save_path = File.join(location.extract_request_tmp_path(er), f.filename)
38
+ ipums_client.download_extract_file(save_path, er.id, f.ext)
39
+
40
+ # Special handling of ddi files
41
+ if f.ext.downcase == 'xml'
42
+ FileUtils.cp(save_path, location.ddi_path)
43
+ end
44
+
45
+ end
46
+ say(location.package_tmp_to_extract(er), true)
47
+ say("Complete")
48
+ end
49
+ end
50
+
51
+ location.clean_tmp!
52
+
53
+ codebook_er = doi_extract.codebook_extract
54
+ full_ddi = codebook_er ? codebook_er.files.detect { |f| f.ext == 'xml' } : nil
55
+
56
+ if full_ddi.nil?
57
+ fail('Missing full DDI codebook')
58
+ end
59
+
60
+ ipums_client.download_extract_file(location.complete_ddi_path, codebook_er.id, 'xml')
61
+
62
+
63
+ # Write summary file to output location
64
+ File.open(location.summary_file_path, 'w') do |f|
65
+ f.puts "File generated on #{Time.now.strftime('%c')}"
66
+ f.puts
67
+ f.puts "Extract Request Group ID #{doi_extract.id}"
68
+ f.puts "DOI Version v#{doi_extract.doi_version}"
69
+ f.puts
70
+ f.puts "Citation:"
71
+ f.puts doi_extract.citation
72
+ f.puts
73
+ f.puts 'Included Extracts:'
74
+
75
+ write_extract_summary(f, location, doi_extract)
76
+ end
77
+
78
+ location.remove_tmp
79
+
80
+ end
81
+
82
+ def write_extract_summary(file, location, doi_extract)
83
+
84
+ headers = ['ID', 'Sample', 'Archive Filename', 'Data Filename']
85
+ spacer = 4
86
+
87
+ extract_data = doi_extract.extract_requests.map do |er|
88
+ [er.id.to_s, location.extract_request_name(er), location.extract_request_filename(er), er.extract_file_name]
89
+ end
90
+
91
+ max_lengths = (extract_data + [headers]).reduce([0, 0, 0, 0]) do |lengths, data|
92
+ lengths.zip(data).map { |l, d| [l, d.length].max }
93
+ end
94
+
95
+ fmt = "%-#{max_lengths.first + spacer}s"
96
+ max_lengths[1..-1].each do |l|
97
+ fmt << "%#{l + spacer}s"
98
+ end
99
+
100
+ file.puts sprintf(fmt, *headers)
101
+
102
+ extract_data.each do |d|
103
+ file.puts sprintf(fmt, *d)
104
+ end
105
+
106
+ end
107
+
108
+ end
109
+ end
@@ -0,0 +1,86 @@
1
+ module DoiExtractor
2
+ class DownloadLocation
3
+
4
+ attr_reader :path
5
+
6
+ def initialize(base_path, extract_group)
7
+ @extract_group = extract_group
8
+ @base_path = base_path
9
+ if extract_group.nil? || extract_group.year.nil? || extract_group.doi_version.nil?
10
+ raise 'Cannot calculate path: missing doi extract data'
11
+ end
12
+ @path = File.join(base_path, "#{extract_group.year}_v#{extract_group.doi_version}")
13
+ end
14
+
15
+ def exist?
16
+ Dir.exist? path
17
+ end
18
+
19
+ def ensure!
20
+ FileUtils.mkdir_p path
21
+ FileUtils.mkdir_p ddi_path
22
+ end
23
+
24
+ def tmp_path
25
+ File.join(path, 'tmp')
26
+ end
27
+
28
+ def remove_tmp
29
+ if Dir.exist?(tmp_path)
30
+ FileUtils.rm_rf tmp_path
31
+ end
32
+ end
33
+
34
+ def clean_tmp!(er = nil)
35
+ remove_tmp
36
+
37
+ FileUtils.mkdir_p tmp_path
38
+ if er
39
+ FileUtils.mkdir_p extract_request_tmp_path(er)
40
+ end
41
+ end
42
+
43
+ def summary_file_path
44
+ File.join(path, 'summary.txt')
45
+ end
46
+
47
+ def complete_ddi_path
48
+ File.join(ddi_path, 'complete.xml')
49
+ end
50
+
51
+ def ddi_path
52
+ File.join(path, 'ddi')
53
+ end
54
+
55
+ def package_tmp_to_extract(er)
56
+ cmd = %[tar -czvf "#{extract_request_path(er)}" -C "#{tmp_path}" "#{extract_request_name(er)}"]
57
+ output = `#{cmd} 2>&1`
58
+ raise "error running command: #{cmd}\n#{output}" unless $?.success?
59
+ output
60
+ end
61
+
62
+ def extract_request_tmp_path(er)
63
+ File.join(tmp_path, extract_request_name(er))
64
+ end
65
+
66
+
67
+ def complete_extract_request?(er)
68
+ file = extract_request_path(er)
69
+ File.exist?(file)
70
+ end
71
+
72
+ def extract_request_path(er)
73
+ File.join(path, extract_request_filename(er))
74
+ end
75
+
76
+ def extract_request_filename(er)
77
+ "#{extract_request_name(er)}.tar.gz"
78
+ end
79
+
80
+ def extract_request_name(er)
81
+ s = er.samples.first
82
+ [s.name, s.long_description].compact.join(' - ')
83
+ end
84
+
85
+ end
86
+ end
@@ -0,0 +1,34 @@
1
+ module DoiExtractor
2
+
3
+ class CommandFailError < StandardError
4
+
5
+ attr_reader :inner_exception, :exit_code
6
+
7
+ def initialize(message, inner_exception = nil, exit_code = 1)
8
+ super(message)
9
+ @inner_exception = inner_exception
10
+ @exit_code = exit_code
11
+ end
12
+
13
+ def error_report
14
+ report = []
15
+
16
+ report << "Failed: #{self.message}"
17
+
18
+ if inner_exception
19
+ report << "caused by: #{inner_exception.class}: #{inner_exception.message}"
20
+ inner_exception.backtrace.each do |l|
21
+ report << " #{l}"
22
+ end
23
+ end
24
+
25
+ report << ' '
26
+
27
+ report.join("\n")
28
+ end
29
+ end
30
+
31
+ class ApiError < StandardError
32
+ end
33
+
34
+ end
@@ -0,0 +1,159 @@
1
+ module DoiExtractor
2
+ class IpumsClient
3
+
4
+ DEFAULT_READ_TIMEOUT = 120
5
+
6
+ attr_reader :uri, :username, :password
7
+
8
+ def initialize(uri, username, password)
9
+ case uri
10
+ when String
11
+ @uri = URI(uri)
12
+ when URI
13
+ @uri = uri
14
+ else
15
+ raise 'Invalid uri'
16
+ end
17
+
18
+ @username = username
19
+ @password = password
20
+ end
21
+
22
+ def create_doi_extract(doi_version, year, email)
23
+ res = post('api/extracts/v1/extract_request_groups', {type: 'doi', year: year, doi_version: doi_version, email: email}, 600)
24
+ parse_body(res)
25
+ end
26
+
27
+ def submit_doi_extract(id)
28
+ res = post("api/extracts/v1/extract_request_groups/#{id}/submit")
29
+ parse_body(res)
30
+ end
31
+
32
+ def recent_doi_extracts
33
+ res = get('api/extracts/v1/extract_request_groups/recent', {type: 'doi'})
34
+ parse_body(res)
35
+ end
36
+
37
+ def get_doi_extract(id)
38
+ res = get("api/extracts/v1/extract_request_groups/#{id}")
39
+ parse_body(res)
40
+ end
41
+
42
+ def cancel_doi_extract(id)
43
+ res = post("api/extracts/v1/extract_request_groups/#{id}/cancel")
44
+ parse_body(res)
45
+ end
46
+
47
+ def submit_extract(id)
48
+ res = post("api/extracts/v1/extract_requests/#{id}/submit")
49
+ parse_body(res)
50
+ end
51
+
52
+ def download_extract_file(file_path, extract_request_id, extension)
53
+ path = "api/extracts/v1/extract_requests/#{extract_request_id}/file/#{extension}"
54
+ File.open(file_path, 'w') do |f|
55
+ perform_request('get', path, nil, f, 600)
56
+ end
57
+ end
58
+
59
+ def get(path, params = nil)
60
+ io = StringIO.new
61
+ perform_request('get', path, params, io)
62
+ io.string
63
+ end
64
+
65
+ def post(path, params = nil, read_timeout = DEFAULT_READ_TIMEOUT)
66
+ io = StringIO.new
67
+ perform_request('post', path, params, io, read_timeout)
68
+ io.string
69
+ end
70
+
71
+ def perform_request(method, path, params, response_io, read_timeout = DEFAULT_READ_TIMEOUT, redirect_limit = 5)
72
+ raise ApiError, 'too many HTTP redirects' if redirect_limit == 0
73
+
74
+ request_uri = self.uri.dup
75
+ request_uri.path = '/' + combine_uri_paths(request_uri.path, path)
76
+
77
+ Net::HTTP.start(uri.host, uri.port, {use_ssl: uri.scheme == 'https'}) do |http|
78
+ # in seconds
79
+ http.read_timeout = read_timeout
80
+ request = build_request(method, request_uri, params)
81
+
82
+ request.basic_auth(username, password)
83
+ request.add_field('Accept', 'application/json')
84
+
85
+ redirect_location = nil
86
+
87
+ http.request(request) do |response|
88
+ case response
89
+ when Net::HTTPSuccess
90
+ response.read_body do |chunk|
91
+ response_io.write chunk
92
+ end
93
+ when Net::HTTPRedirection
94
+ redirect_location = response['location']
95
+ when Net::HTTPUnprocessableEntity
96
+ body = response.body
97
+ body = body && body != '' ? JSON.parse(body) : nil
98
+ msg = body.map { |k, v| "#{k}: #{v}" }.join(', ')
99
+ raise ApiError, "API Server returned an error: #{msg}"
100
+ else
101
+ raise ApiError, "Invalid API Server Response from [#{method.to_s.upcase} #{path}]: #{response.code} - #{response.message}"
102
+ end
103
+ end
104
+
105
+ if redirect_location
106
+ perform_request('get', redirect_location, nil, response_io, read_timeout, redirect_limit - 1)
107
+ end
108
+
109
+ end
110
+ end
111
+
112
+ def combine_uri_paths(*parts)
113
+ parts.compact.map(&:to_s).map(&:strip).map { |p| p.sub(/^\/+/, '').sub(/\/+$/, '') }.join('/')
114
+ end
115
+
116
+ def build_request(method, request_uri, params)
117
+ case method.to_s.downcase
118
+ when 'get'
119
+ case params
120
+ when Hash
121
+ request_uri.query = URI.encode_www_form(params)
122
+ when String
123
+ request_uri.query = params
124
+ when nil
125
+ # Do nothing
126
+ else
127
+ raise ApiError, "Invalid GET params: #{params}"
128
+ end
129
+ Net::HTTP::Get.new(request_uri.request_uri)
130
+ when 'post'
131
+ r = Net::HTTP::Post.new(request_uri.request_uri)
132
+ case params
133
+ when Hash
134
+ r.set_content_type('application/json')
135
+ r.body = JSON.generate(params)
136
+ when String
137
+ r.set_content_type('application/json')
138
+ r.body = params
139
+ when nil
140
+ # Do nothing
141
+ else
142
+ raise ApiError, "Invalid POST params: #{params}"
143
+ end
144
+ r
145
+ else
146
+ raise ApiError, "HTTP method #{method} not implemented"
147
+ end
148
+ end
149
+
150
+ def parse_body(body)
151
+ if body && body != ''
152
+ JSON.parse(body, object_class: OpenStruct)
153
+ else
154
+ nil
155
+ end
156
+ end
157
+
158
+ end
159
+ end