usps-imis-api 1.0.0.pre.rc.5 → 1.0.0.pre.rc.7
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/.rubocop.yml +1 -1
- data/Gemfile.lock +36 -30
- data/Readme.md +140 -32
- data/lib/usps/imis/api.rb +27 -39
- data/lib/usps/imis/business_object.rb +87 -47
- data/lib/usps/imis/config.rb +14 -3
- data/lib/usps/imis/data.rb +72 -0
- data/lib/usps/imis/error.rb +51 -0
- data/lib/usps/imis/errors/api_error.rb +11 -0
- data/lib/usps/imis/errors/config_error.rb +11 -0
- data/lib/usps/imis/errors/locked_id_error.rb +15 -0
- data/lib/usps/imis/errors/mapper_error.rb +29 -0
- data/lib/usps/imis/errors/not_found_error.rb +11 -0
- data/lib/usps/imis/errors/panel_unimplemented_error.rb +34 -0
- data/lib/usps/imis/{error → errors}/response_error.rb +5 -8
- data/lib/usps/imis/errors/unexpected_property_type_error.rb +31 -0
- data/lib/usps/imis/mapper.rb +28 -20
- data/lib/usps/imis/mocks/business_object.rb +47 -0
- data/lib/usps/imis/mocks.rb +11 -0
- data/lib/usps/imis/panels/base_panel.rb +144 -0
- data/lib/usps/imis/{panel → panels}/education.rb +2 -2
- data/lib/usps/imis/{panel → panels}/vsc.rb +2 -2
- data/lib/usps/imis/panels.rb +25 -0
- data/lib/usps/imis/properties.rb +50 -0
- data/lib/usps/imis/query.rb +94 -0
- data/lib/usps/imis/requests.rb +27 -3
- data/lib/usps/imis/version.rb +1 -1
- data/lib/usps/imis.rb +15 -15
- data/spec/lib/usps/imis/api_spec.rb +26 -13
- data/spec/lib/usps/imis/business_object_spec.rb +44 -20
- data/spec/lib/usps/imis/config_spec.rb +2 -2
- data/spec/lib/usps/imis/data_spec.rb +66 -0
- data/spec/lib/usps/imis/{error/api_error_spec.rb → error_spec.rb} +1 -1
- data/spec/lib/usps/imis/{error → errors}/response_error_spec.rb +4 -4
- data/spec/lib/usps/imis/mapper_spec.rb +27 -3
- data/spec/lib/usps/imis/mocks/business_object_spec.rb +65 -0
- data/spec/lib/usps/imis/panels/base_panel_spec.rb +33 -0
- data/spec/lib/usps/imis/panels/education_spec.rb +70 -0
- data/spec/lib/usps/imis/{panel → panels}/vsc_spec.rb +6 -7
- data/spec/lib/usps/imis/properties_spec.rb +19 -0
- data/spec/spec_helper.rb +2 -0
- data/usps-imis-api.gemspec +1 -1
- metadata +28 -16
- data/lib/ext/hash.rb +0 -10
- data/lib/usps/imis/error/api_error.rb +0 -44
- data/lib/usps/imis/error/mapper_error.rb +0 -11
- data/lib/usps/imis/panel/base_panel.rb +0 -101
- data/lib/usps/imis/panel/panel_properties.rb +0 -52
- data/spec/lib/usps/imis/panel/base_panel_spec.rb +0 -32
- data/spec/lib/usps/imis/panel/education_spec.rb +0 -55
- data/spec/lib/usps/imis/panel/panel_properties_spec.rb +0 -19
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative 'requests'
|
|
4
|
+
require_relative 'data'
|
|
5
|
+
|
|
3
6
|
module Usps
|
|
4
7
|
module Imis
|
|
5
8
|
# DEV
|
|
@@ -18,112 +21,127 @@ module Usps
|
|
|
18
21
|
#
|
|
19
22
|
attr_reader :business_object_name
|
|
20
23
|
|
|
21
|
-
#
|
|
24
|
+
# Ordinal to build override ID param of the URL (e.g. used for Panels)
|
|
22
25
|
#
|
|
23
|
-
attr_reader :
|
|
26
|
+
attr_reader :ordinal
|
|
24
27
|
|
|
25
28
|
# A new instance of +BusinessObject+
|
|
26
29
|
#
|
|
27
|
-
def initialize(api, business_object_name,
|
|
30
|
+
def initialize(api, business_object_name, ordinal: nil)
|
|
28
31
|
@api = api
|
|
29
32
|
@business_object_name = business_object_name
|
|
30
|
-
@
|
|
33
|
+
@ordinal = ordinal
|
|
31
34
|
end
|
|
32
35
|
|
|
33
36
|
# Get a business object for the current member
|
|
34
37
|
#
|
|
35
|
-
#
|
|
38
|
+
# If +fields+ is provided, will return only those field values
|
|
36
39
|
#
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
# @param fields [String] Field names to return
|
|
41
|
+
#
|
|
42
|
+
# @return [Usps::Imis::Data, Array<Usps::Imis::Data>] Response data from the API
|
|
43
|
+
#
|
|
44
|
+
def get(*fields) = fields.any? ? get_fields(*fields) : raw_object
|
|
45
|
+
alias read get
|
|
42
46
|
|
|
43
47
|
# Get a single named field from a business object for the current member
|
|
44
48
|
#
|
|
45
|
-
# @param
|
|
49
|
+
# @param field [String] Field name to return
|
|
46
50
|
#
|
|
47
|
-
# @return
|
|
51
|
+
# @return Response data field value from the API
|
|
48
52
|
#
|
|
49
|
-
def get_field(
|
|
50
|
-
|
|
51
|
-
|
|
53
|
+
def get_field(field) = raw_object[field]
|
|
54
|
+
alias fetch get_field
|
|
55
|
+
alias [] get_field
|
|
52
56
|
|
|
53
|
-
|
|
57
|
+
# Get named fields from a business object for the current member
|
|
58
|
+
#
|
|
59
|
+
# @param names [Array<String>] Field names to return
|
|
60
|
+
#
|
|
61
|
+
# @return [Array] Response data from the API
|
|
62
|
+
#
|
|
63
|
+
def get_fields(*fields)
|
|
64
|
+
values = raw_object
|
|
65
|
+
fields.map { values[it] }
|
|
54
66
|
end
|
|
67
|
+
alias fetch_all get_fields
|
|
55
68
|
|
|
56
69
|
# Update only specific fields on a business object for the current member
|
|
57
70
|
#
|
|
58
71
|
# @param fields [Hash] Conforms to pattern +{ field_key => value }+
|
|
59
72
|
#
|
|
60
|
-
# @return [
|
|
73
|
+
# @return [Usps::Imis::Data] Response data from the API
|
|
61
74
|
#
|
|
62
|
-
def put_fields(fields)
|
|
63
|
-
|
|
64
|
-
put(updated)
|
|
65
|
-
end
|
|
75
|
+
def put_fields(fields) = put(filter_fields(fields))
|
|
76
|
+
alias patch put_fields
|
|
66
77
|
|
|
67
78
|
# Update a business object for the current member
|
|
68
79
|
#
|
|
80
|
+
# Any properties not included will be left unmodified
|
|
81
|
+
#
|
|
69
82
|
# @param body [Hash] Full raw API object data
|
|
70
83
|
#
|
|
71
|
-
# @return [
|
|
84
|
+
# @return [Usps::Imis::Data] Response data from the API
|
|
72
85
|
#
|
|
73
|
-
def put(body)
|
|
74
|
-
|
|
75
|
-
request.body = JSON.dump(body)
|
|
76
|
-
result = submit(uri, authorize(request))
|
|
77
|
-
JSON.parse(result.body)
|
|
78
|
-
end
|
|
86
|
+
def put(body) = put_object(Net::HTTP::Put.new(uri), body)
|
|
87
|
+
alias update put
|
|
79
88
|
|
|
80
89
|
# Create a business object for the current member
|
|
81
90
|
#
|
|
82
91
|
# @param body [Hash] Full raw API object data
|
|
83
92
|
#
|
|
84
|
-
# @return [
|
|
93
|
+
# @return [Usps::Imis::Data] Response data from the API
|
|
85
94
|
#
|
|
86
|
-
def post(body)
|
|
87
|
-
|
|
88
|
-
request.body = JSON.dump(body)
|
|
89
|
-
result = submit(uri, authorize(request))
|
|
90
|
-
JSON.parse(result.body)
|
|
91
|
-
end
|
|
95
|
+
def post(body) = put_object(Net::HTTP::Post.new(uri(id: '')), body)
|
|
96
|
+
alias create post
|
|
92
97
|
|
|
93
98
|
# Remove a business object for the current member
|
|
94
99
|
#
|
|
95
|
-
# @return [
|
|
100
|
+
# @return [true] Only on success response (i.e. blank string from the API)
|
|
96
101
|
#
|
|
97
|
-
def delete
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
+
def delete = submit(uri, authorize(Net::HTTP::Delete.new(uri))).body == '' # rubocop:disable Naming/PredicateMethod
|
|
103
|
+
alias destroy delete
|
|
104
|
+
|
|
105
|
+
# Ruby 3.5 instance variable filter
|
|
106
|
+
#
|
|
107
|
+
def instance_variables_to_inspect = instance_variables - %i[@api]
|
|
102
108
|
|
|
103
109
|
private
|
|
104
110
|
|
|
105
111
|
def token = api.token
|
|
106
112
|
def token_expiration = api.token_expiration
|
|
107
113
|
|
|
114
|
+
def logger = Imis.logger('BusinessObject')
|
|
115
|
+
|
|
108
116
|
# Construct a business object API endpoint address
|
|
109
117
|
#
|
|
110
|
-
def uri
|
|
111
|
-
|
|
112
|
-
full_path = "#{API_PATH}/#{business_object_name}/#{id_for_url}"
|
|
118
|
+
def uri(id: nil)
|
|
119
|
+
full_path = "#{API_PATH}/#{business_object_name}/#{id_for_uri(id)}"
|
|
113
120
|
URI(File.join(Imis.configuration.hostname, full_path))
|
|
114
121
|
end
|
|
115
122
|
|
|
123
|
+
# Select the correct ID to use in the URI
|
|
124
|
+
#
|
|
125
|
+
def id_for_uri(id = nil)
|
|
126
|
+
return CGI.escape(id) unless id.nil?
|
|
127
|
+
return CGI.escape("~#{api.imis_id}|#{ordinal}") if ordinal
|
|
128
|
+
|
|
129
|
+
api.imis_id.to_s
|
|
130
|
+
end
|
|
131
|
+
|
|
116
132
|
# Manually assemble the matching data structure, with fields in the correct order
|
|
117
133
|
#
|
|
118
134
|
def filter_fields(fields)
|
|
119
135
|
existing = get
|
|
120
136
|
|
|
121
137
|
JSON.parse(JSON.dump(existing)).tap do |updated|
|
|
122
|
-
#
|
|
123
|
-
updated['Properties']['$values'] =
|
|
138
|
+
# Preserve the iMIS ID, as well as the Ordinal (if present)
|
|
139
|
+
updated['Properties']['$values'], properties =
|
|
140
|
+
existing.raw['Properties']['$values'].partition { %w[ID Ordinal].include?(it['Name']) }
|
|
124
141
|
|
|
125
142
|
# Iterate through all existing fields
|
|
126
|
-
|
|
143
|
+
properties.each do |value|
|
|
144
|
+
# Skip unmodified fields
|
|
127
145
|
next unless fields.keys.include?(value['Name'])
|
|
128
146
|
|
|
129
147
|
# Strings are not wrapped in the type definition structure
|
|
@@ -139,6 +157,28 @@ module Usps
|
|
|
139
157
|
end
|
|
140
158
|
end
|
|
141
159
|
end
|
|
160
|
+
|
|
161
|
+
# Get a raw object response from the API
|
|
162
|
+
#
|
|
163
|
+
# Useful for stubbing data in tests
|
|
164
|
+
#
|
|
165
|
+
def raw_object
|
|
166
|
+
request = Net::HTTP::Get.new(uri)
|
|
167
|
+
result = submit(uri, authorize(request))
|
|
168
|
+
result = Data.from_json(result.body)
|
|
169
|
+
JSON.pretty_generate(result).split("\n").each { logger.debug " -> #{it}" }
|
|
170
|
+
result
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# Upload an object to the API
|
|
174
|
+
#
|
|
175
|
+
def put_object(request, body)
|
|
176
|
+
request.body = JSON.dump(body)
|
|
177
|
+
result = submit(uri, authorize(request))
|
|
178
|
+
result = Data.from_json(result.body)
|
|
179
|
+
JSON.pretty_generate(result).split("\n").each { logger.debug " -> #{it}" }
|
|
180
|
+
result
|
|
181
|
+
end
|
|
142
182
|
end
|
|
143
183
|
end
|
|
144
184
|
end
|
data/lib/usps/imis/config.rb
CHANGED
|
@@ -9,21 +9,28 @@ module Usps
|
|
|
9
9
|
IMIS_ROOT_URL_DEV = 'https://abcdev.imiscloud.com'
|
|
10
10
|
|
|
11
11
|
attr_accessor :imis_id_query_name, :username, :password
|
|
12
|
-
attr_reader :environment
|
|
12
|
+
attr_reader :environment, :logger, :logger_level
|
|
13
13
|
|
|
14
14
|
def initialize
|
|
15
15
|
@environment = defined?(Rails) ? Rails.env : ActiveSupport::StringInquirer.new('development')
|
|
16
16
|
@imis_id_query_name = ENV.fetch('IMIS_ID_QUERY_NAME', nil)
|
|
17
17
|
@username = ENV.fetch('IMIS_USERNAME', nil)
|
|
18
18
|
@password = ENV.fetch('IMIS_PASSWORD', nil)
|
|
19
|
+
@logger = ActiveSupport::TaggedLogging.new(Logger.new($stdout, level: :info))
|
|
19
20
|
|
|
20
21
|
yield self if block_given?
|
|
22
|
+
|
|
23
|
+
@logger_level = logger.class::SEV_LABEL[logger.level].downcase.to_sym
|
|
21
24
|
end
|
|
22
25
|
|
|
23
26
|
def environment=(env)
|
|
24
27
|
@environment = ActiveSupport::StringInquirer.new(env.to_s)
|
|
25
28
|
end
|
|
26
29
|
|
|
30
|
+
def logger=(logger)
|
|
31
|
+
@logger = ActiveSupport::TaggedLogging.new(logger)
|
|
32
|
+
end
|
|
33
|
+
|
|
27
34
|
# Environment-specific API endpoint hostname
|
|
28
35
|
#
|
|
29
36
|
# @return The API hostname for the current environment
|
|
@@ -32,12 +39,16 @@ module Usps
|
|
|
32
39
|
return IMIS_ROOT_URL_PROD if environment.production?
|
|
33
40
|
return IMIS_ROOT_URL_DEV if environment.development?
|
|
34
41
|
|
|
35
|
-
raise
|
|
42
|
+
raise Errors::ConfigError, "Unexpected API environment: #{environment}"
|
|
36
43
|
end
|
|
37
44
|
|
|
38
45
|
# Ruby 3.5 instance variable filter
|
|
39
46
|
#
|
|
40
|
-
def instance_variables_to_inspect = %i[@environment @imis_id_query_name @username]
|
|
47
|
+
def instance_variables_to_inspect = %i[@environment @imis_id_query_name @username @logger_level]
|
|
48
|
+
|
|
49
|
+
# Parameters to filter out of logging
|
|
50
|
+
#
|
|
51
|
+
def filtered_parameters = %i[password]
|
|
41
52
|
end
|
|
42
53
|
end
|
|
43
54
|
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'pp'
|
|
4
|
+
require 'stringio'
|
|
5
|
+
|
|
6
|
+
module Usps
|
|
7
|
+
module Imis
|
|
8
|
+
# Convenience wrapper for accessing specific properties within an API data response
|
|
9
|
+
#
|
|
10
|
+
class Data < Hash
|
|
11
|
+
# Load raw API response JSON to access properties
|
|
12
|
+
#
|
|
13
|
+
# @param json [String] Raw API response JSON
|
|
14
|
+
#
|
|
15
|
+
def self.from_json(json) = self[JSON.parse(json)]
|
|
16
|
+
|
|
17
|
+
alias raw to_h
|
|
18
|
+
|
|
19
|
+
# The Business Object or Panel name
|
|
20
|
+
#
|
|
21
|
+
def entity = raw['EntityTypeName']
|
|
22
|
+
|
|
23
|
+
# Access the iMIS ID property
|
|
24
|
+
#
|
|
25
|
+
def imis_id = self['ID'].to_i
|
|
26
|
+
alias id imis_id
|
|
27
|
+
|
|
28
|
+
# Access the Ordinal identifier property (if present)
|
|
29
|
+
#
|
|
30
|
+
def ordinal = self['Ordinal']&.to_i
|
|
31
|
+
|
|
32
|
+
# Access an individual property value by name
|
|
33
|
+
#
|
|
34
|
+
def [](property_name)
|
|
35
|
+
property = raw['Properties']['$values'].find { it['Name'] == property_name }
|
|
36
|
+
return if property.nil?
|
|
37
|
+
|
|
38
|
+
value = property['Value']
|
|
39
|
+
value.is_a?(String) ? value : value['$value']
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Hash of all property names to values
|
|
43
|
+
#
|
|
44
|
+
# @param include_ids [Boolean] Whether to include the iMIS ID and Ordinal
|
|
45
|
+
#
|
|
46
|
+
def properties(include_ids: false)
|
|
47
|
+
raw['Properties']['$values']
|
|
48
|
+
.map { it['Name'] }
|
|
49
|
+
.select { include_ids || !%w[ID Ordinal].include?(it) }
|
|
50
|
+
.index_with { self[it] }
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def inspect
|
|
54
|
+
stringio = StringIO.new
|
|
55
|
+
PP.pp(self, stringio)
|
|
56
|
+
stringio.string.delete("\n")
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def pretty_print(pp)
|
|
60
|
+
data = { entity:, imis_id:, ordinal: }.compact
|
|
61
|
+
|
|
62
|
+
pp.group(1, "#<#{self.class}", '>') do
|
|
63
|
+
data.each do |key, value|
|
|
64
|
+
pp.breakable
|
|
65
|
+
pp.text "#{key}="
|
|
66
|
+
pp.pp value
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Usps
|
|
4
|
+
module Imis
|
|
5
|
+
# Base error class for all internal exceptions
|
|
6
|
+
#
|
|
7
|
+
class Error < StandardError
|
|
8
|
+
# Additional call-specific metadata to pass through to Bugsnag
|
|
9
|
+
#
|
|
10
|
+
attr_accessor :metadata
|
|
11
|
+
|
|
12
|
+
# A new instance of +ApiError+
|
|
13
|
+
#
|
|
14
|
+
# @param message [String] The base exception message
|
|
15
|
+
# @param metadata [Hash] Additional call-specific metadata to pass through to Bugsnag
|
|
16
|
+
#
|
|
17
|
+
def initialize(message, metadata = {})
|
|
18
|
+
super(message)
|
|
19
|
+
@metadata = metadata
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Additional metadata to include in Bugsnag reports
|
|
23
|
+
#
|
|
24
|
+
# Can include fields at the top level, which will be shows on the custom tab
|
|
25
|
+
#
|
|
26
|
+
# Can include fields nested under a top-level key, which will be shown on a tab with the
|
|
27
|
+
# top-level key as its name
|
|
28
|
+
#
|
|
29
|
+
# @return [Hash]
|
|
30
|
+
#
|
|
31
|
+
def bugsnag_meta_data
|
|
32
|
+
metadata == {} ? {} : base_metadata
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def base_metadata
|
|
38
|
+
{ api: metadata }
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
require_relative 'errors/api_error'
|
|
45
|
+
require_relative 'errors/config_error'
|
|
46
|
+
require_relative 'errors/locked_id_error'
|
|
47
|
+
require_relative 'errors/mapper_error'
|
|
48
|
+
require_relative 'errors/not_found_error'
|
|
49
|
+
require_relative 'errors/response_error'
|
|
50
|
+
require_relative 'errors/panel_unimplemented_error'
|
|
51
|
+
require_relative 'errors/unexpected_property_type_error'
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Usps
|
|
4
|
+
module Imis
|
|
5
|
+
module Errors
|
|
6
|
+
# Exception raised when attempting to change the iMIS ID while it is locked
|
|
7
|
+
#
|
|
8
|
+
class LockedIdError < Error
|
|
9
|
+
def initialize = super(message)
|
|
10
|
+
|
|
11
|
+
def message = 'Cannot change iMIS ID while locked'
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Usps
|
|
4
|
+
module Imis
|
|
5
|
+
module Errors
|
|
6
|
+
# Exception raised by a +Mapper+
|
|
7
|
+
#
|
|
8
|
+
class MapperError < Error
|
|
9
|
+
# Create a new instance of +MapperError+
|
|
10
|
+
#
|
|
11
|
+
# @param metadata [Hash] Additional call-specific metadata to pass through to Bugsnag
|
|
12
|
+
#
|
|
13
|
+
def initialize(metadata = {})
|
|
14
|
+
@metadata = metadata
|
|
15
|
+
super(message, metadata)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Exception message including the unrecognized field
|
|
19
|
+
#
|
|
20
|
+
def message
|
|
21
|
+
<<~MESSAGE.chomp
|
|
22
|
+
Mapper does not recognize field: "#{metadata[:field_key]}".
|
|
23
|
+
Please report what data you are attempting to work with to ITCom leadership.
|
|
24
|
+
MESSAGE
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Usps
|
|
4
|
+
module Imis
|
|
5
|
+
module Errors
|
|
6
|
+
# Exception raised when a panel is missing required method definitions
|
|
7
|
+
#
|
|
8
|
+
class PanelUnimplementedError < Error
|
|
9
|
+
# Create a new instance of +PanelUnimplementedError+ from the class name and missing method
|
|
10
|
+
#
|
|
11
|
+
# @param class_name [String] Name of the Panel class that is missing a method definition
|
|
12
|
+
# @param method [String] Method definition that is not defined on the Panel
|
|
13
|
+
#
|
|
14
|
+
def self.from(class_name, method) = new(class_name, method)
|
|
15
|
+
|
|
16
|
+
# Create a new instance of +PanelUnimplementedError+
|
|
17
|
+
#
|
|
18
|
+
# @param class_name [String] Name of the Panel class that is missing a method definition
|
|
19
|
+
# @param method [String] Method definition that is not defined on the Panel
|
|
20
|
+
# @param metadata [Hash] Additional call-specific metadata to pass through to Bugsnag
|
|
21
|
+
#
|
|
22
|
+
def initialize(class_name, method, metadata = {})
|
|
23
|
+
@class_name = class_name
|
|
24
|
+
@method = method
|
|
25
|
+
super(message, metadata)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Exception message including the undefined method
|
|
29
|
+
#
|
|
30
|
+
def message = "#{@class_name} must implement ##{@method}"
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
module Usps
|
|
4
4
|
module Imis
|
|
5
|
-
module
|
|
5
|
+
module Errors
|
|
6
6
|
# Exception raised due to receiving an error response from the API
|
|
7
7
|
#
|
|
8
|
-
class ResponseError <
|
|
8
|
+
class ResponseError < Error
|
|
9
9
|
# [Net::HTTPResponse] The response received from the API
|
|
10
10
|
#
|
|
11
11
|
attr_reader :response
|
|
@@ -18,17 +18,14 @@ module Usps
|
|
|
18
18
|
#
|
|
19
19
|
# @param response [Net::HTTPResponse] The response received from the API
|
|
20
20
|
#
|
|
21
|
-
def self.from(response)
|
|
22
|
-
new(nil, response)
|
|
23
|
-
end
|
|
21
|
+
def self.from(response) = new(response)
|
|
24
22
|
|
|
25
23
|
# Create a new instance of +ResponseError+
|
|
26
24
|
#
|
|
27
|
-
# @param _message Ignored
|
|
28
25
|
# @param response [Net::HTTPResponse] The response received from the API
|
|
29
26
|
# @param metadata [Hash] Additional call-specific metadata to pass through to Bugsnag
|
|
30
27
|
#
|
|
31
|
-
def initialize(
|
|
28
|
+
def initialize(response, metadata = {})
|
|
32
29
|
@response = response
|
|
33
30
|
super(message, metadata)
|
|
34
31
|
end
|
|
@@ -43,7 +40,7 @@ module Usps
|
|
|
43
40
|
# @return [Hash]
|
|
44
41
|
#
|
|
45
42
|
def bugsnag_meta_data
|
|
46
|
-
base_metadata.tap {
|
|
43
|
+
base_metadata.tap { it[:api].merge!(metadata) }
|
|
47
44
|
end
|
|
48
45
|
|
|
49
46
|
# Auto-formatted exception message, based on the provided API response
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Usps
|
|
4
|
+
module Imis
|
|
5
|
+
module Errors
|
|
6
|
+
# Exception raised when attempting to wrap an unexpected property type
|
|
7
|
+
#
|
|
8
|
+
class UnexpectedPropertyTypeError < Error
|
|
9
|
+
# Create a new instance of +UnexpectedPropertyTypeError+ from an unexpected value
|
|
10
|
+
#
|
|
11
|
+
# @param value Unexpected value
|
|
12
|
+
#
|
|
13
|
+
def self.from(value) = new(value)
|
|
14
|
+
|
|
15
|
+
# Create a new instance of +UnexpectedPropertyTypeError+
|
|
16
|
+
#
|
|
17
|
+
# @param value Unexpected value
|
|
18
|
+
# @param metadata [Hash] Additional call-specific metadata to pass through to Bugsnag
|
|
19
|
+
#
|
|
20
|
+
def initialize(value, metadata = {})
|
|
21
|
+
@value = value
|
|
22
|
+
super(message, metadata)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Exception message including the undefined method
|
|
26
|
+
#
|
|
27
|
+
def message = "Unexpected property type: #{@value.inspect}"
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
data/lib/usps/imis/mapper.rb
CHANGED
|
@@ -28,6 +28,14 @@ module Usps
|
|
|
28
28
|
@api.imis_id = imis_id if imis_id
|
|
29
29
|
end
|
|
30
30
|
|
|
31
|
+
def fetch(field_key)
|
|
32
|
+
missing_mapping!(field_key) unless FIELD_MAPPING.key?(field_key.to_sym)
|
|
33
|
+
|
|
34
|
+
business_object_name, field = FIELD_MAPPING[field_key]
|
|
35
|
+
api.on(business_object_name)[field]
|
|
36
|
+
end
|
|
37
|
+
alias [] fetch
|
|
38
|
+
|
|
31
39
|
# Update a member's data on multiple affected business objects by arbitrary field names
|
|
32
40
|
#
|
|
33
41
|
# Does not require knowing which business object / iMIS-specific field name to use
|
|
@@ -36,7 +44,7 @@ module Usps
|
|
|
36
44
|
#
|
|
37
45
|
# @param data [Hash] Conforms to pattern +{ field_key => value }+
|
|
38
46
|
#
|
|
39
|
-
# @return [Array<
|
|
47
|
+
# @return [Array<Usps::Imis::Data>] Response data from the API for each internal update request
|
|
40
48
|
#
|
|
41
49
|
def update(data)
|
|
42
50
|
updates = data.each_with_object({}) do |(field_key, value), hash|
|
|
@@ -47,37 +55,37 @@ module Usps
|
|
|
47
55
|
end
|
|
48
56
|
|
|
49
57
|
updates.map do |business_object_name, field_updates|
|
|
50
|
-
api.
|
|
58
|
+
api.on(business_object_name).put_fields(field_updates)
|
|
51
59
|
end
|
|
52
60
|
end
|
|
53
61
|
|
|
62
|
+
# Ruby 3.5 instance variable filter
|
|
63
|
+
#
|
|
64
|
+
def instance_variables_to_inspect = instance_variables - %i[@api]
|
|
65
|
+
|
|
54
66
|
private
|
|
55
67
|
|
|
56
|
-
def
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
68
|
+
def logger = Imis.logger('Mapper')
|
|
69
|
+
|
|
70
|
+
def map_update(field_key)
|
|
71
|
+
missing_mapping!(field_key) unless FIELD_MAPPING.key?(field_key.to_sym)
|
|
72
|
+
|
|
73
|
+
business_object_name, field = FIELD_MAPPING[field_key.to_sym]
|
|
74
|
+
yield(business_object_name, field)
|
|
63
75
|
end
|
|
64
76
|
|
|
65
|
-
def missing_mapping(
|
|
77
|
+
def missing_mapping!(field_key)
|
|
66
78
|
unless ENV['TESTING']
|
|
67
79
|
# :nocov:
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
"
|
|
72
|
-
|
|
80
|
+
"Mapper does not know how to handle field \"#{field_key}\".\n\n" \
|
|
81
|
+
'You can use api.put_fields(business_object_name, { field_name => value }) ' \
|
|
82
|
+
"if you know the business object and iMIS-specific field name.\n\n"
|
|
83
|
+
.split("\n")
|
|
84
|
+
.each { logger.warn(it) }
|
|
73
85
|
# :nocov:
|
|
74
86
|
end
|
|
75
87
|
|
|
76
|
-
raise(
|
|
77
|
-
Error::MapperError,
|
|
78
|
-
"Unrecognized field: \"#{field_name}\". " \
|
|
79
|
-
'Please report what data you are attempting to work with to ITCom leadership.'
|
|
80
|
-
)
|
|
88
|
+
raise Errors::MapperError.new({ field_key: })
|
|
81
89
|
end
|
|
82
90
|
end
|
|
83
91
|
end
|