usps-imis-api 0.11.23 → 1.0.0.pre.rc.1
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.
- checksums.yaml +4 -4
- data/.github/workflows/main.yml +57 -0
- data/.gitignore +4 -0
- data/.rspec +2 -0
- data/.rubocop.yml +88 -0
- data/.ruby-version +1 -0
- data/.simplecov +8 -0
- data/Gemfile +12 -0
- data/Gemfile.lock +95 -0
- data/Rakefile +12 -0
- data/Readme.md +191 -19
- data/bin/console +21 -0
- data/bin/setup +8 -0
- data/lib/ext/hash.rb +10 -0
- data/lib/usps/imis/api.rb +138 -177
- data/lib/usps/imis/config.rb +10 -68
- data/lib/usps/imis/error/api.rb +26 -0
- data/lib/usps/imis/error/mapper.rb +9 -0
- data/lib/usps/imis/{errors/response_error.rb → error/response.rb} +7 -34
- data/lib/usps/imis/mapper.rb +21 -90
- data/lib/usps/imis/panel/base_panel.rb +42 -0
- data/lib/usps/imis/panel/education.rb +111 -0
- data/lib/usps/imis/panel/vsc.rb +109 -0
- data/lib/usps/imis/version.rb +1 -1
- data/lib/usps/imis.rb +17 -32
- data/spec/lib/usps/imis/api_spec.rb +143 -0
- data/spec/lib/usps/imis/config_spec.rb +33 -0
- data/spec/lib/usps/imis/error/api_spec.rb +17 -0
- data/spec/lib/usps/imis/error/response_spec.rb +107 -0
- data/spec/lib/usps/imis/mapper_spec.rb +31 -0
- data/spec/lib/usps/imis/panel/base_panel_spec.rb +32 -0
- data/spec/lib/usps/imis/panel/education_spec.rb +55 -0
- data/spec/lib/usps/imis/panel/vsc_spec.rb +38 -0
- data/spec/lib/usps/imis_spec.rb +11 -0
- data/spec/spec_helper.rb +35 -0
- data/usps-imis-api.gemspec +18 -0
- metadata +33 -94
- data/bin/imis +0 -8
- data/lib/usps/imis/business_object.rb +0 -209
- data/lib/usps/imis/command_line/interface.rb +0 -204
- data/lib/usps/imis/command_line/options_parser.rb +0 -136
- data/lib/usps/imis/command_line.rb +0 -15
- data/lib/usps/imis/data.rb +0 -76
- data/lib/usps/imis/error.rb +0 -55
- data/lib/usps/imis/errors/api_error.rb +0 -11
- data/lib/usps/imis/errors/command_line_error.rb +0 -11
- data/lib/usps/imis/errors/config_error.rb +0 -11
- data/lib/usps/imis/errors/locked_id_error.rb +0 -15
- data/lib/usps/imis/errors/mapper_error.rb +0 -29
- data/lib/usps/imis/errors/missing_id_error.rb +0 -15
- data/lib/usps/imis/errors/not_found_error.rb +0 -11
- data/lib/usps/imis/errors/panel_unimplemented_error.rb +0 -34
- data/lib/usps/imis/errors/unexpected_property_type_error.rb +0 -31
- data/lib/usps/imis/logger.rb +0 -19
- data/lib/usps/imis/logger_formatter.rb +0 -32
- data/lib/usps/imis/logger_helpers.rb +0 -17
- data/lib/usps/imis/mocks/business_object.rb +0 -47
- data/lib/usps/imis/mocks.rb +0 -11
- data/lib/usps/imis/panels/base_panel.rb +0 -158
- data/lib/usps/imis/panels/education.rb +0 -33
- data/lib/usps/imis/panels/vsc.rb +0 -32
- data/lib/usps/imis/panels.rb +0 -25
- data/lib/usps/imis/properties.rb +0 -50
- data/lib/usps/imis/query.rb +0 -153
- data/lib/usps/imis/requests.rb +0 -68
- data/spec/support/usps/vcr/config.rb +0 -47
- data/spec/support/usps/vcr/filters.rb +0 -89
- data/spec/support/usps/vcr.rb +0 -8
data/lib/usps/imis/api.rb
CHANGED
|
@@ -1,246 +1,207 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative 'requests'
|
|
4
|
-
require_relative 'business_object'
|
|
5
|
-
require_relative 'mapper'
|
|
6
|
-
require_relative 'query'
|
|
7
|
-
|
|
8
3
|
module Usps
|
|
9
4
|
module Imis
|
|
10
|
-
# The core API wrapper
|
|
11
|
-
#
|
|
12
5
|
class Api
|
|
13
|
-
include Requests
|
|
14
|
-
|
|
15
|
-
# Endpoint for (re-)authentication requests
|
|
16
|
-
#
|
|
17
6
|
AUTHENTICATION_PATH = 'Token'
|
|
7
|
+
API_PATH = 'api'
|
|
8
|
+
QUERY_PATH = 'api/Query'
|
|
9
|
+
PANELS = Struct.new(:vsc, :education)
|
|
18
10
|
|
|
19
|
-
|
|
20
|
-
#
|
|
21
|
-
attr_reader :token
|
|
22
|
-
|
|
23
|
-
# Expiration time for the API bearer token
|
|
24
|
-
#
|
|
25
|
-
# Used to automatically re-authenticate as needed
|
|
26
|
-
#
|
|
27
|
-
attr_reader :token_expiration
|
|
28
|
-
|
|
29
|
-
# Currently selected iMIS ID for API requests
|
|
30
|
-
#
|
|
31
|
-
attr_reader :imis_id
|
|
32
|
-
|
|
33
|
-
# Whether to lock changes to the selected iMIS ID
|
|
34
|
-
#
|
|
35
|
-
attr_reader :lock_imis_id
|
|
11
|
+
attr_reader :token, :token_expiration, :imis_id
|
|
36
12
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
attr_reader :logger
|
|
40
|
-
|
|
41
|
-
# Create a new instance of +Api+ and provide an existing auth token
|
|
42
|
-
#
|
|
43
|
-
# @param token [String] Auth token
|
|
44
|
-
#
|
|
45
|
-
def self.with_token(token)
|
|
46
|
-
new.tap do |api|
|
|
47
|
-
api.instance_variable_set(:@token, token)
|
|
48
|
-
api.instance_variable_set(:@token_expiration, Time.now + 3600) # Greater than the actual lifetime of the token
|
|
49
|
-
end
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
# A new instance of +Api+
|
|
53
|
-
#
|
|
54
|
-
# @param imis_id [Integer, String] iMIS ID to select immediately on initialization
|
|
55
|
-
#
|
|
56
|
-
def initialize(imis_id: nil)
|
|
13
|
+
def initialize(skip_authentication: false, imis_id: nil)
|
|
14
|
+
authenticate unless skip_authentication
|
|
57
15
|
self.imis_id = imis_id if imis_id
|
|
58
|
-
@logger ||= Imis.logger('Api')
|
|
59
|
-
Imis.config.validate!
|
|
60
16
|
end
|
|
61
17
|
|
|
62
18
|
# Manually set the current ID, if you already have it for a given member
|
|
63
19
|
#
|
|
64
|
-
# @param id [Integer, String] iMIS ID to select for future requests
|
|
65
|
-
#
|
|
66
|
-
# @return [Integer] iMIS ID
|
|
67
|
-
#
|
|
68
20
|
def imis_id=(id)
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
@imis_id = id&.to_i
|
|
21
|
+
@imis_id = id.to_i.to_s
|
|
72
22
|
end
|
|
73
23
|
|
|
74
24
|
# Convert a member's certificate number into an iMIS ID number
|
|
75
25
|
#
|
|
76
|
-
# @param certificate [String] Certificate number to lookup the corresponding iMIS ID for
|
|
77
|
-
#
|
|
78
|
-
# @return [Integer] Corresponding iMIS ID
|
|
79
|
-
#
|
|
80
26
|
def imis_id_for(certificate)
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
begin
|
|
86
|
-
result = query(Imis.configuration.imis_id_query_name, { certificate: })
|
|
87
|
-
page = result.page.tap { logger.tagged('Response').debug it }
|
|
88
|
-
self.imis_id = page.first['ID'].to_i
|
|
89
|
-
rescue StandardError
|
|
90
|
-
raise Errors::NotFoundError, 'Member not found'
|
|
91
|
-
end
|
|
27
|
+
result = query(Imis.configuration.imis_id_query_name, { certificate: })
|
|
28
|
+
@imis_id = result['Items']['$values'][0]['ID']
|
|
29
|
+
rescue StandardError
|
|
30
|
+
raise Error::Api, 'Member not found'
|
|
92
31
|
end
|
|
93
32
|
|
|
94
33
|
# Run requests as DSL, with specific iMIS ID only maintained for this scope
|
|
95
34
|
#
|
|
96
|
-
#
|
|
97
|
-
#
|
|
98
|
-
# While in this block, changes to the value of +imis_id+ are not allowed
|
|
99
|
-
#
|
|
100
|
-
# If no block is given, this sets the iMIS ID and returns self.
|
|
101
|
-
#
|
|
102
|
-
# @param id [Integer, String] iMIS ID to select for requests within the block
|
|
103
|
-
# @param certificate [String] Certificate number to convert to iMIS ID and select for requests within the block
|
|
35
|
+
# This should be used with methods that do not change the value of `imis_id`
|
|
104
36
|
#
|
|
105
|
-
|
|
106
|
-
# with(12345) do
|
|
107
|
-
# update(mm: 15)
|
|
108
|
-
# end
|
|
109
|
-
#
|
|
110
|
-
# @return [Usps::Imis::Api]
|
|
111
|
-
#
|
|
112
|
-
def with(id = nil, certificate: nil, &)
|
|
113
|
-
raise ArgumentError, 'Must provide id or certificate' unless id || certificate
|
|
114
|
-
|
|
37
|
+
def with(id, &)
|
|
115
38
|
old_id = imis_id
|
|
116
|
-
|
|
117
|
-
id.nil? ? imis_id_for(certificate) : self.imis_id = id
|
|
118
|
-
return self unless block_given?
|
|
119
|
-
|
|
120
|
-
@lock_imis_id = true
|
|
39
|
+
self.imis_id = id
|
|
121
40
|
instance_eval(&)
|
|
122
41
|
ensure
|
|
123
|
-
|
|
124
|
-
@lock_imis_id = false
|
|
125
|
-
self.imis_id = old_id
|
|
126
|
-
end
|
|
42
|
+
self.imis_id = old_id
|
|
127
43
|
end
|
|
128
44
|
|
|
129
|
-
#
|
|
130
|
-
#
|
|
131
|
-
# Works with IQA queries and Business Objects
|
|
132
|
-
#
|
|
133
|
-
# @param query_name [String] Full path of the query, e.g. +$/_ABC/Fiander/iMIS_ID+
|
|
134
|
-
# @query_params [Hash] Conforms to pattern +{ param_name => param_value }+
|
|
45
|
+
# Get a business object for the current member
|
|
135
46
|
#
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
47
|
+
def get(business_object_name, url_id: nil)
|
|
48
|
+
uri = uri_for(business_object_name, url_id:)
|
|
49
|
+
request = Net::HTTP::Get.new(uri)
|
|
50
|
+
result = submit(uri, authorize(request))
|
|
51
|
+
JSON.parse(result.body)
|
|
52
|
+
end
|
|
139
53
|
|
|
140
|
-
#
|
|
54
|
+
# Update only specific fields on a business object for the current member
|
|
141
55
|
#
|
|
142
|
-
#
|
|
56
|
+
# fields - hash of shape: { field_name => new_value }
|
|
143
57
|
#
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
# @return [Usps::Imis::BusinessObject]
|
|
148
|
-
#
|
|
149
|
-
def on(business_object_name, ordinal: nil, &)
|
|
150
|
-
object = BusinessObject.new(self, business_object_name, ordinal:)
|
|
151
|
-
return object unless block_given?
|
|
152
|
-
|
|
153
|
-
object.instance_eval(&)
|
|
58
|
+
def put_fields(business_object_name, fields, url_id: nil)
|
|
59
|
+
updated = filter_fields(business_object_name, fields)
|
|
60
|
+
put(business_object_name, updated, url_id:)
|
|
154
61
|
end
|
|
155
62
|
|
|
156
|
-
#
|
|
63
|
+
# Update a business object for the current member
|
|
157
64
|
#
|
|
158
|
-
def
|
|
159
|
-
|
|
65
|
+
def put(business_object_name, body, url_id: nil)
|
|
66
|
+
uri = uri_for(business_object_name, url_id:)
|
|
67
|
+
request = Net::HTTP::Put.new(uri)
|
|
68
|
+
request.body = JSON.dump(body)
|
|
69
|
+
result = submit(uri, authorize(request))
|
|
70
|
+
JSON.parse(result.body)
|
|
160
71
|
end
|
|
161
72
|
|
|
162
|
-
#
|
|
163
|
-
#
|
|
164
|
-
# @return Value of the specified field
|
|
73
|
+
# Create a business object for the current member
|
|
165
74
|
#
|
|
166
|
-
def
|
|
167
|
-
|
|
75
|
+
def post(business_object_name, body, url_id: nil)
|
|
76
|
+
uri = uri_for(business_object_name, url_id:)
|
|
77
|
+
request = Net::HTTP::Post.new(uri)
|
|
78
|
+
request.body = JSON.dump(body)
|
|
79
|
+
result = submit(uri, authorize(request))
|
|
80
|
+
JSON.parse(result.body)
|
|
81
|
+
end
|
|
168
82
|
|
|
169
|
-
#
|
|
83
|
+
# Remove a business object for the current member
|
|
170
84
|
#
|
|
171
|
-
#
|
|
85
|
+
# Returns empty string on success
|
|
172
86
|
#
|
|
173
|
-
def
|
|
87
|
+
def delete(business_object_name, url_id: nil)
|
|
88
|
+
uri = uri_for(business_object_name, url_id:)
|
|
89
|
+
request = Net::HTTP::Delete.new(uri)
|
|
90
|
+
result = submit(uri, authorize(request))
|
|
91
|
+
result.body
|
|
92
|
+
end
|
|
174
93
|
|
|
175
|
-
#
|
|
94
|
+
# Run an IQA Query
|
|
176
95
|
#
|
|
177
|
-
#
|
|
96
|
+
# query_name - the full path of the query in IQA, e.g. `$/_ABC/Fiander/iMIS_ID`
|
|
97
|
+
# query_params - hash of shape: { param_name => param_value }
|
|
178
98
|
#
|
|
179
|
-
def
|
|
180
|
-
|
|
99
|
+
def query(query_name, query_params = {})
|
|
100
|
+
query_params[:QueryName] = query_name
|
|
101
|
+
path = "#{QUERY_PATH}?#{query_params.to_query}"
|
|
102
|
+
uri = URI(File.join(imis_hostname, path))
|
|
103
|
+
request = Net::HTTP::Get.new(uri)
|
|
104
|
+
result = submit(uri, authorize(request))
|
|
105
|
+
JSON.parse(result.body)
|
|
106
|
+
end
|
|
181
107
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
#
|
|
186
|
-
def update(data) = mapper.update(data)
|
|
108
|
+
def mapper
|
|
109
|
+
@mapper ||= Mapper.new(self)
|
|
110
|
+
end
|
|
187
111
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
112
|
+
def panels
|
|
113
|
+
@panels ||= PANELS.new(
|
|
114
|
+
Panel::Vsc.new(self),
|
|
115
|
+
Panel::Education.new(self)
|
|
116
|
+
)
|
|
117
|
+
end
|
|
194
118
|
|
|
195
|
-
|
|
119
|
+
def update(data)
|
|
120
|
+
mapper.update(data)
|
|
196
121
|
end
|
|
197
122
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
123
|
+
def instance_variables_to_inspect = %i[@token_expiration @imis_id]
|
|
124
|
+
|
|
125
|
+
private
|
|
126
|
+
|
|
127
|
+
def client(uri)
|
|
128
|
+
Net::HTTP.new(uri.host, uri.port).tap do |http|
|
|
129
|
+
http.use_ssl = true
|
|
130
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
|
131
|
+
end
|
|
203
132
|
end
|
|
204
133
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
authenticate
|
|
134
|
+
def imis_hostname
|
|
135
|
+
Imis.configuration.hostname
|
|
136
|
+
end
|
|
209
137
|
|
|
210
|
-
|
|
138
|
+
# Authorize a request prior to submitting
|
|
139
|
+
#
|
|
140
|
+
# If the current token is missing/expired, request a new one
|
|
141
|
+
#
|
|
142
|
+
def authorize(request)
|
|
143
|
+
authenticate if token_expiration < Time.now
|
|
144
|
+
request.tap { |r| r.add_field('Authorization', "Bearer #{token}") }
|
|
211
145
|
end
|
|
212
146
|
|
|
213
|
-
#
|
|
147
|
+
# Construct a business object API endpoint address
|
|
214
148
|
#
|
|
215
|
-
def
|
|
149
|
+
def uri_for(business_object_name, url_id: nil)
|
|
150
|
+
url_id ||= imis_id
|
|
151
|
+
url_id = CGI.escape(url_id)
|
|
152
|
+
URI(File.join(imis_hostname, "#{API_PATH}/#{business_object_name}/#{url_id}"))
|
|
153
|
+
end
|
|
216
154
|
|
|
217
|
-
|
|
155
|
+
def submit(uri, request)
|
|
156
|
+
client(uri).request(request).tap do |result|
|
|
157
|
+
raise Error::Response.from(result) unless result.is_a?(Net::HTTPSuccess)
|
|
158
|
+
end
|
|
159
|
+
end
|
|
218
160
|
|
|
219
161
|
# Authenticate to the iMIS API, and store the access token and expiration time
|
|
220
162
|
#
|
|
221
163
|
def authenticate
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
)
|
|
233
|
-
result = submit(uri, request)
|
|
234
|
-
json = JSON.parse(result.body)
|
|
235
|
-
end
|
|
164
|
+
uri = URI(File.join(imis_hostname, AUTHENTICATION_PATH))
|
|
165
|
+
req = Net::HTTP::Post.new(uri)
|
|
166
|
+
authentication_data = {
|
|
167
|
+
grant_type: 'password',
|
|
168
|
+
username: Imis.configuration.username,
|
|
169
|
+
password: Imis.configuration.password
|
|
170
|
+
}
|
|
171
|
+
req.body = URI.encode_www_form(authentication_data)
|
|
172
|
+
result = submit(uri, req)
|
|
173
|
+
json = JSON.parse(result.body)
|
|
236
174
|
|
|
237
175
|
@token = json['access_token']
|
|
238
|
-
@token_expiration = Time.
|
|
176
|
+
@token_expiration = Time.parse(json['.expires'])
|
|
239
177
|
end
|
|
240
178
|
|
|
241
|
-
#
|
|
179
|
+
# Manually assemble the matching data structure, with fields in the correct order
|
|
242
180
|
#
|
|
243
|
-
def
|
|
181
|
+
def filter_fields(business_object_name, fields)
|
|
182
|
+
existing = get(business_object_name)
|
|
183
|
+
|
|
184
|
+
JSON.parse(JSON.dump(existing)).tap do |updated|
|
|
185
|
+
# The first property is always the iMIS ID again
|
|
186
|
+
updated['Properties']['$values'] = [existing['Properties']['$values'][0]]
|
|
187
|
+
|
|
188
|
+
# Iterate through all existing fields
|
|
189
|
+
existing['Properties']['$values'].each do |value|
|
|
190
|
+
next unless fields.keys.include?(value['Name'])
|
|
191
|
+
|
|
192
|
+
# Strings are not wrapped in the type definition structure
|
|
193
|
+
new_value = fields[value['Name']]
|
|
194
|
+
if new_value.is_a?(String)
|
|
195
|
+
value['Value'] = new_value
|
|
196
|
+
else
|
|
197
|
+
value['Value']['$value'] = new_value
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# Add the completed field with the updated value
|
|
201
|
+
updated['Properties']['$values'] << value
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
end
|
|
244
205
|
end
|
|
245
206
|
end
|
|
246
207
|
end
|
data/lib/usps/imis/config.rb
CHANGED
|
@@ -1,87 +1,29 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative 'logger'
|
|
4
|
-
require_relative 'logger_formatter'
|
|
5
|
-
require_relative 'logger_helpers'
|
|
6
|
-
|
|
7
3
|
module Usps
|
|
8
4
|
module Imis
|
|
9
|
-
# API Configuration
|
|
10
|
-
#
|
|
11
5
|
class Config
|
|
12
6
|
IMIS_ROOT_URL_PROD = 'https://portal.americasboatingclub.org'
|
|
13
7
|
IMIS_ROOT_URL_DEV = 'https://abcdev.imiscloud.com'
|
|
14
|
-
REQUIRED_CONFIGS = %w[imis_id_query_name username password].freeze
|
|
15
8
|
|
|
16
|
-
attr_accessor :imis_id_query_name, :username, :password
|
|
17
|
-
attr_reader :environment, :logger, :logger_level, :logger_file
|
|
9
|
+
attr_accessor :environment, :imis_id_query_name, :username, :password
|
|
18
10
|
|
|
19
11
|
def initialize
|
|
20
|
-
@environment = default_environment
|
|
21
|
-
@imis_id_query_name = ENV.fetch('IMIS_ID_QUERY_NAME', nil)
|
|
22
|
-
@username = ENV.fetch('IMIS_USERNAME', nil)
|
|
23
|
-
@password = ENV.fetch('IMIS_PASSWORD', nil)
|
|
24
|
-
@base_logger = Logger.new($stdout, level: :info)
|
|
25
|
-
@logger = ActiveSupport::TaggedLogging.new(@base_logger)
|
|
26
|
-
|
|
27
12
|
yield self if block_given?
|
|
28
|
-
|
|
29
|
-
@logger_level = logger.class::SEV_LABEL[logger.level].downcase.to_sym
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
def environment=(env)
|
|
33
|
-
@environment = ActiveSupport::StringInquirer.new(env.to_s)
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def logger=(logger)
|
|
37
|
-
@base_logger = logger.tap { it.formatter = LoggerFormatter.new }
|
|
38
|
-
@base_logger.singleton_class.include(LoggerHelpers)
|
|
39
|
-
@logger = ActiveSupport::TaggedLogging.new(@base_logger)
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
def logger_file=(path)
|
|
43
|
-
@logger_file = path
|
|
44
|
-
@base_logger = Logger.new(@logger_file.nil? ? $stdout : @logger_file, level: logger.level)
|
|
45
|
-
@logger = ActiveSupport::TaggedLogging.new(@base_logger)
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
def silence!
|
|
49
|
-
self.logger = Logger.new(nil)
|
|
50
13
|
end
|
|
51
14
|
|
|
52
|
-
# Environment-specific API endpoint hostname
|
|
53
|
-
#
|
|
54
|
-
# @return The API hostname for the current environment
|
|
55
|
-
#
|
|
56
15
|
def hostname
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
def instance_variables_to_inspect = instance_variables - %i[@password @base_logger @logger]
|
|
66
|
-
|
|
67
|
-
# Parameters to filter out of logging
|
|
68
|
-
#
|
|
69
|
-
def filtered_parameters = %i[password]
|
|
70
|
-
|
|
71
|
-
def validate!
|
|
72
|
-
missing_config = REQUIRED_CONFIGS.filter_map { it if public_send(it).nil? }
|
|
73
|
-
return if missing_config.empty?
|
|
74
|
-
|
|
75
|
-
raise Errors::ConfigError, "Missing required configuration: #{missing_config.join(', ')}"
|
|
16
|
+
case environment.to_sym
|
|
17
|
+
when :production
|
|
18
|
+
IMIS_ROOT_URL_PROD
|
|
19
|
+
when :development
|
|
20
|
+
IMIS_ROOT_URL_DEV
|
|
21
|
+
else
|
|
22
|
+
raise Error::Api, "Unexpected API environment: #{environment}"
|
|
23
|
+
end
|
|
76
24
|
end
|
|
77
25
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
def default_environment
|
|
81
|
-
return ::Rails.env if defined?(::Rails)
|
|
82
|
-
|
|
83
|
-
ActiveSupport::StringInquirer.new(ENV.fetch('IMIS_ENVIRONMENT', 'development'))
|
|
84
|
-
end
|
|
26
|
+
def instance_variables_to_inspect = %i[@environment @imis_id_query_name @username]
|
|
85
27
|
end
|
|
86
28
|
end
|
|
87
29
|
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Usps
|
|
4
|
+
module Imis
|
|
5
|
+
module Error
|
|
6
|
+
class Api < StandardError
|
|
7
|
+
attr_accessor :metadata
|
|
8
|
+
|
|
9
|
+
def initialize(message, metadata = {})
|
|
10
|
+
super(message)
|
|
11
|
+
@metadata = metadata
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def bugsnag_meta_data
|
|
15
|
+
metadata == {} ? {} : base_metadata
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def base_metadata
|
|
21
|
+
{ api: metadata }
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -2,51 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
module Usps
|
|
4
4
|
module Imis
|
|
5
|
-
module
|
|
6
|
-
|
|
7
|
-
#
|
|
8
|
-
class ResponseError < Error
|
|
9
|
-
# [Net::HTTPResponse] The response received from the API
|
|
10
|
-
#
|
|
5
|
+
module Error
|
|
6
|
+
class Response < Api
|
|
11
7
|
attr_reader :response
|
|
12
|
-
|
|
13
|
-
# [Hash] Additional call-specific metadata to pass through to Bugsnag
|
|
14
|
-
#
|
|
15
8
|
attr_accessor :metadata
|
|
16
9
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
#
|
|
21
|
-
def self.from(response) = new(response)
|
|
10
|
+
def self.from(response)
|
|
11
|
+
new(nil, response)
|
|
12
|
+
end
|
|
22
13
|
|
|
23
|
-
|
|
24
|
-
#
|
|
25
|
-
# @param response [Net::HTTPResponse] The response received from the API
|
|
26
|
-
# @param metadata [Hash] Additional call-specific metadata to pass through to Bugsnag
|
|
27
|
-
#
|
|
28
|
-
def initialize(response, metadata = {})
|
|
14
|
+
def initialize(_message, response, metadata = {})
|
|
29
15
|
@response = response
|
|
30
16
|
super(message, metadata)
|
|
31
17
|
end
|
|
32
18
|
|
|
33
|
-
# Additional metadata to include in Bugsnag reports
|
|
34
|
-
#
|
|
35
|
-
# Can include fields at the top level, which will be shows on the +custom+ tab
|
|
36
|
-
#
|
|
37
|
-
# Can include fields nested under a top-level key, which will be shown on a tab with the
|
|
38
|
-
# top-level key as its name
|
|
39
|
-
#
|
|
40
|
-
# @return [Hash]
|
|
41
|
-
#
|
|
42
19
|
def bugsnag_meta_data
|
|
43
|
-
base_metadata.tap {
|
|
20
|
+
base_metadata.tap { |m| m[:api].merge!(metadata) }
|
|
44
21
|
end
|
|
45
22
|
|
|
46
|
-
# Auto-formatted exception message, based on the provided API response
|
|
47
|
-
#
|
|
48
|
-
# @return [String] The exception message
|
|
49
|
-
#
|
|
50
23
|
def message
|
|
51
24
|
[
|
|
52
25
|
"#{self.class.name}: [#{status.to_s.upcase}] The iMIS API returned an error.",
|