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/query.rb
DELETED
|
@@ -1,153 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Usps
|
|
4
|
-
module Imis
|
|
5
|
-
# API wrapper for IQA and Business Object Queries
|
|
6
|
-
#
|
|
7
|
-
class Query
|
|
8
|
-
include Enumerable
|
|
9
|
-
include Requests
|
|
10
|
-
|
|
11
|
-
# Endpoint for IQA query requests
|
|
12
|
-
#
|
|
13
|
-
IQA_PATH = 'api/Query'
|
|
14
|
-
|
|
15
|
-
# The parent +Api+ object
|
|
16
|
-
#
|
|
17
|
-
attr_reader :api
|
|
18
|
-
|
|
19
|
-
# Name of the Query to run
|
|
20
|
-
#
|
|
21
|
-
attr_reader :query_name
|
|
22
|
-
|
|
23
|
-
# Parameters for the Query
|
|
24
|
-
#
|
|
25
|
-
attr_reader :query_params
|
|
26
|
-
|
|
27
|
-
# Current page size for paging through the Query
|
|
28
|
-
#
|
|
29
|
-
attr_accessor :page_size
|
|
30
|
-
|
|
31
|
-
# Current offset for paging through the Query
|
|
32
|
-
#
|
|
33
|
-
attr_accessor :offset
|
|
34
|
-
|
|
35
|
-
# Count of records processed
|
|
36
|
-
#
|
|
37
|
-
attr_reader :count
|
|
38
|
-
|
|
39
|
-
# Whether the current query has a next page
|
|
40
|
-
#
|
|
41
|
-
attr_reader :next_page
|
|
42
|
-
|
|
43
|
-
# Tagged logger
|
|
44
|
-
#
|
|
45
|
-
attr_reader :logger
|
|
46
|
-
|
|
47
|
-
# A new instance of +Query+
|
|
48
|
-
#
|
|
49
|
-
# @param api [Api] Parent to use for making requests
|
|
50
|
-
# @param query_name [String] Full path of the query in IQA, e.g. +$/_ABC/Fiander/iMIS_ID+
|
|
51
|
-
# @param page_size [Integer] Number of records to return on each request page
|
|
52
|
-
# @param offset [Integer] Offset index of records to return on next request page
|
|
53
|
-
# @param query_params [Hash] Conforms to pattern +{ param_name => param_value }+
|
|
54
|
-
#
|
|
55
|
-
def initialize(api, query_name, page_size: 100, offset: nil, **query_params)
|
|
56
|
-
@api = api
|
|
57
|
-
@query_name = query_name
|
|
58
|
-
@query_params = query_params
|
|
59
|
-
@page_size = page_size
|
|
60
|
-
@offset = offset
|
|
61
|
-
@count = 0
|
|
62
|
-
@logger ||= Imis.logger('Query', query_type)
|
|
63
|
-
|
|
64
|
-
logger.tagged('Name').debug query_name
|
|
65
|
-
logger.tagged('Params').json query_params
|
|
66
|
-
logger.tagged('URI').debug uri
|
|
67
|
-
logger.tagged('Page Size').debug page_size
|
|
68
|
-
logger.tagged('Offset').debug offset.to_i
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
# Iterate through all results from the query
|
|
72
|
-
#
|
|
73
|
-
def each(&)
|
|
74
|
-
logger.info 'Running'
|
|
75
|
-
|
|
76
|
-
items = []
|
|
77
|
-
find_each { items << it }
|
|
78
|
-
items.each(&)
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
# Iterate through all results from the query, fetching one page at a time
|
|
82
|
-
#
|
|
83
|
-
def find_each(&)
|
|
84
|
-
reset!
|
|
85
|
-
page.each(&) while page?
|
|
86
|
-
nil
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
# Fetch a filtered query page, and update the current offset
|
|
90
|
-
#
|
|
91
|
-
def page = fetch_next['Items']['$values'].map { iqa? ? it.except('$type') : Imis::Data[it] }
|
|
92
|
-
|
|
93
|
-
# Fetch the next raw query page, and update the current offset
|
|
94
|
-
#
|
|
95
|
-
def fetch_next
|
|
96
|
-
return unless page?
|
|
97
|
-
|
|
98
|
-
logger.info "Fetching #{query_type} Query page"
|
|
99
|
-
|
|
100
|
-
result = fetch
|
|
101
|
-
|
|
102
|
-
@count += result['Count'] || 0
|
|
103
|
-
total = result['TotalCount']
|
|
104
|
-
logger.info "#{@count} / #{total} #{'item'.pluralize(total)}"
|
|
105
|
-
logger.debug 'Query page data:'
|
|
106
|
-
logger.json result
|
|
107
|
-
|
|
108
|
-
@offset = result['NextOffset']
|
|
109
|
-
@next_page = result['HasNext']
|
|
110
|
-
|
|
111
|
-
result
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
# Fetch a raw query page
|
|
115
|
-
#
|
|
116
|
-
def fetch = JSON.parse(submit(uri, authorize(http_get)).body)
|
|
117
|
-
|
|
118
|
-
# Reset query paging progress
|
|
119
|
-
#
|
|
120
|
-
def reset!
|
|
121
|
-
return if next_page.nil?
|
|
122
|
-
|
|
123
|
-
logger.debug 'Resetting progress'
|
|
124
|
-
|
|
125
|
-
@count = 0
|
|
126
|
-
@offset = 0
|
|
127
|
-
@next_page = nil
|
|
128
|
-
end
|
|
129
|
-
|
|
130
|
-
# Ruby 3.5 instance variable filter
|
|
131
|
-
#
|
|
132
|
-
def instance_variables_to_inspect = instance_variables - %i[@api @logger]
|
|
133
|
-
|
|
134
|
-
private
|
|
135
|
-
|
|
136
|
-
# Only skip if explicitly set
|
|
137
|
-
def page? = next_page.nil? || next_page
|
|
138
|
-
|
|
139
|
-
def iqa? = query_name.match?(/^\$/)
|
|
140
|
-
def query_type = iqa? ? :IQA : :'Business Object'
|
|
141
|
-
def path_params = query_params.merge({ Offset: offset, Limit: page_size }.compact)
|
|
142
|
-
def uri = URI(File.join(Imis.configuration.hostname, path))
|
|
143
|
-
|
|
144
|
-
def path
|
|
145
|
-
if iqa?
|
|
146
|
-
"#{IQA_PATH}?#{path_params.merge(QueryName: query_name).to_query}"
|
|
147
|
-
else
|
|
148
|
-
"#{Imis::BusinessObject::API_PATH}/#{query_name}?#{path_params.to_query}"
|
|
149
|
-
end
|
|
150
|
-
end
|
|
151
|
-
end
|
|
152
|
-
end
|
|
153
|
-
end
|
data/lib/usps/imis/requests.rb
DELETED
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Usps
|
|
4
|
-
module Imis
|
|
5
|
-
# @private
|
|
6
|
-
#
|
|
7
|
-
module Requests
|
|
8
|
-
private
|
|
9
|
-
|
|
10
|
-
def logger = raise("#{self.class.name} must implement #logger")
|
|
11
|
-
|
|
12
|
-
def token = api.token
|
|
13
|
-
def token_expiration = api.token_expiration
|
|
14
|
-
def authenticate = api.send(:authenticate)
|
|
15
|
-
|
|
16
|
-
def http_get = Net::HTTP::Get.new(uri)
|
|
17
|
-
def http_put = Net::HTTP::Put.new(uri)
|
|
18
|
-
def http_post = Net::HTTP::Post.new(uri(id: ''))
|
|
19
|
-
def http_delete = Net::HTTP::Delete.new(uri)
|
|
20
|
-
|
|
21
|
-
def client(uri)
|
|
22
|
-
Net::HTTP.new(uri.host, uri.port).tap do |http|
|
|
23
|
-
http.use_ssl = true
|
|
24
|
-
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
|
25
|
-
end
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
# Authorize a request prior to submitting
|
|
29
|
-
#
|
|
30
|
-
# If the current token is missing/expired, request a new one
|
|
31
|
-
#
|
|
32
|
-
def authorize(request)
|
|
33
|
-
if token_expiration.nil? || token_expiration < Time.now
|
|
34
|
-
logger.debug 'Token expired: re-authenticating with iMIS'
|
|
35
|
-
authenticate
|
|
36
|
-
end
|
|
37
|
-
request.tap { it.add_field('Authorization', "Bearer #{token}") }
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
def submit(uri, request)
|
|
41
|
-
logger.info 'Submitting request to iMIS'
|
|
42
|
-
logger.tagged('Request') do
|
|
43
|
-
logger.debug "#{request.class.name.demodulize.upcase} #{uri}"
|
|
44
|
-
logger.multiline sanitized_request_body(request)
|
|
45
|
-
|
|
46
|
-
client(uri).request(request).tap do |result|
|
|
47
|
-
raise Errors::ResponseError.from(result) unless result.is_a?(Net::HTTPSuccess)
|
|
48
|
-
|
|
49
|
-
logger.info 'Request succeeded'
|
|
50
|
-
end
|
|
51
|
-
end
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
def sanitized_request_body(request)
|
|
55
|
-
return '*** empty request body ***' if request.body.nil?
|
|
56
|
-
|
|
57
|
-
body = request.body.dup
|
|
58
|
-
|
|
59
|
-
Imis.config.filtered_parameters.each do |parameter|
|
|
60
|
-
body.gsub!(Imis.config.public_send(parameter), '[FILTERED]')
|
|
61
|
-
body.gsub!(CGI.escape(Imis.config.public_send(parameter)), '[FILTERED]')
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
body
|
|
65
|
-
end
|
|
66
|
-
end
|
|
67
|
-
end
|
|
68
|
-
end
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Usps
|
|
4
|
-
module Vcr
|
|
5
|
-
module Config
|
|
6
|
-
class << self
|
|
7
|
-
def configure!
|
|
8
|
-
WebMock.disable_net_connect!
|
|
9
|
-
|
|
10
|
-
VCR.configure do |config|
|
|
11
|
-
config.cassette_library_dir = 'spec/fixtures/vcr_cassettes'
|
|
12
|
-
config.hook_into :webmock
|
|
13
|
-
default_options(config)
|
|
14
|
-
config.configure_rspec_metadata!
|
|
15
|
-
apply_filters(config)
|
|
16
|
-
|
|
17
|
-
yield(config) if block_given?
|
|
18
|
-
end
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
def vcr_record_ordered
|
|
22
|
-
ENV['VCR'] == 'all' ? :defined : :random
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
private
|
|
26
|
-
|
|
27
|
-
def default_options(config)
|
|
28
|
-
config.default_cassette_options = {
|
|
29
|
-
record: ENV['VCR'] ? ENV['VCR'].to_sym : :once,
|
|
30
|
-
match_requests_on: %i[method uri]
|
|
31
|
-
}
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
def apply_filters(config)
|
|
35
|
-
Filters.apply!(
|
|
36
|
-
config,
|
|
37
|
-
*%i[
|
|
38
|
-
username password access_token bearer_token
|
|
39
|
-
ignore_response_headers cf_ray cookie date
|
|
40
|
-
issued expires
|
|
41
|
-
]
|
|
42
|
-
)
|
|
43
|
-
end
|
|
44
|
-
end
|
|
45
|
-
end
|
|
46
|
-
end
|
|
47
|
-
end
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Usps
|
|
4
|
-
module Vcr
|
|
5
|
-
module Filters
|
|
6
|
-
class << self
|
|
7
|
-
def apply!(config, *filters)
|
|
8
|
-
filters.each { public_send(it, config) }
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
def username(config)
|
|
12
|
-
value = Usps::Imis.config.username || '<USERNAME>'
|
|
13
|
-
config.filter_sensitive_data('<USERNAME>') { value }
|
|
14
|
-
config.filter_sensitive_data('<USERNAME>') { CGI.escape(value) }
|
|
15
|
-
config.filter_sensitive_data('<USERNAME>') { value.upcase }
|
|
16
|
-
config.filter_sensitive_data('<USERNAME>') { CGI.escape(value).upcase }
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
def password(config)
|
|
20
|
-
value = Usps::Imis.config.password || '<PASSWORD>'
|
|
21
|
-
config.filter_sensitive_data('<PASSWORD>') { value }
|
|
22
|
-
config.filter_sensitive_data('<PASSWORD>') { CGI.escape(value) }
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
def access_token(config)
|
|
26
|
-
filter_json_field(config, 'access_token', '<ACCESS_TOKEN>')
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
def bearer_token(config)
|
|
30
|
-
config.filter_sensitive_data('<BEARER_TOKEN>') do |interaction|
|
|
31
|
-
if interaction.request.headers['Authorization']
|
|
32
|
-
authorization = interaction.request.headers['Authorization'].first
|
|
33
|
-
if (match = authorization.match(/^Bearer\s+([^,\s]+)/))
|
|
34
|
-
match.captures.first
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
end
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
def ignore_response_headers(config)
|
|
41
|
-
config.before_record do |interaction|
|
|
42
|
-
interaction.response.headers.delete('Report-To')
|
|
43
|
-
interaction.response.headers.delete('Content-Security-Policy-Report-Only')
|
|
44
|
-
end
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
def cf_ray(config)
|
|
48
|
-
config.filter_sensitive_data('<CF_RAY>') do |interaction|
|
|
49
|
-
interaction.response.headers['Cf-Ray']&.first
|
|
50
|
-
end
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
def cookie(config)
|
|
54
|
-
config.filter_sensitive_data('<COOKIE>') do |interaction|
|
|
55
|
-
interaction.response.headers['Set-Cookie']&.first
|
|
56
|
-
end
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
def date(config)
|
|
60
|
-
config.filter_sensitive_data('<DATE>') do |interaction|
|
|
61
|
-
interaction.response.headers['Date']&.first
|
|
62
|
-
end
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
def issued(config)
|
|
66
|
-
filter_json_field(config, '.issued', '<ISSUED>')
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
def expires(config)
|
|
70
|
-
filter_json_field(config, '.expires', '<EXPIRES>')
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
private
|
|
74
|
-
|
|
75
|
-
def filter_json_field(config, field_name, placeholder)
|
|
76
|
-
config.filter_sensitive_data(placeholder) do |interaction|
|
|
77
|
-
if interaction.response.headers['Content-Type']&.first&.include?('application/json')
|
|
78
|
-
begin
|
|
79
|
-
JSON.parse(interaction.response.body)[field_name]
|
|
80
|
-
rescue JSON::ParserError
|
|
81
|
-
nil
|
|
82
|
-
end
|
|
83
|
-
end
|
|
84
|
-
end
|
|
85
|
-
end
|
|
86
|
-
end
|
|
87
|
-
end
|
|
88
|
-
end
|
|
89
|
-
end
|