usps-imis-api 0.11.40 → 0.11.41

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '09b163c4f02496fa2205d3ed3056f2e0e7bf7306b63f2176390a498503ca32d3'
4
- data.tar.gz: c5e9dd8def7221e9f63f71e277ebbdbd3d6b8f080d93af5a5494b50149b649cd
3
+ metadata.gz: ac3e7c47ae3555e7cb4d83bf24585b5b4ffac669c0f60dab014996566a4ad366
4
+ data.tar.gz: 547b0943b75e78b4ba4b968e3a780194cfe783eaef2301a4f5cacdee6e298c0e
5
5
  SHA512:
6
- metadata.gz: 2cf04565727807685ef016b823bdd798ac453ed9510d32b430c4b6e286b9f71d01ee6d2d08d15bcdfc3c55dc8168e42f224c9c2fddec2e9933c4e03ee34e03ed
7
- data.tar.gz: b0a7eeb21ce375a205b1783f839eba1c02aa2040091319845729d33680e49cb43a373a256e3ac002037222516ef111bf8d08903181458c0e8a5fcc1180b30772
6
+ metadata.gz: 5f7009001491dd4177d883335bdd63a2049bf157bbac66295d22c789dd1755b9672d096a760a000883f6397e4ee9c74d48cebae0c1868895e46c7b63577aaae6
7
+ data.tar.gz: 8509266846dab5d93389ee7486afb8f987202d1cfbda01b2e59894e2e5fe6caddf7e820b2c822335c7123535fac1b0d3ada220d9deaf20bb3ab56a3284fccd37
data/lib/usps/imis/api.rb CHANGED
@@ -62,7 +62,7 @@ module Usps
62
62
  def initialize(imis_id: nil, record_id: nil)
63
63
  self.imis_id = imis_id if imis_id
64
64
  self.record_id = record_id if record_id
65
- @logger ||= Imis.logger('Api')
65
+ @logger = Imis.logger('Api')
66
66
  Imis.config.validate!
67
67
  end
68
68
 
@@ -80,10 +80,11 @@ module Usps
80
80
  hex = '[0-9a-fA-F]'
81
81
  uuid_pattern = /^#{hex}{8}-#{hex}{4}-#{hex}{4}-#{hex}{4}-#{hex}{12}$/
82
82
  @imis_id =
83
- if id.to_s.match?(uuid_pattern)
84
- id
85
- elsif id.to_i.to_s == id.to_s.gsub(' ', '')
86
- id.to_i
83
+ if id.to_s.match?(uuid_pattern) then id
84
+ elsif id.to_i.to_s == id.to_s.gsub(' ', '') then id.to_i
85
+ elsif id.nil? then nil
86
+ else
87
+ raise ArgumentError, "Invalid id: #{id.inspect}"
87
88
  end
88
89
  end
89
90
 
@@ -118,13 +119,13 @@ module Usps
118
119
 
119
120
  logger.debug "Fetching iMIS ID for #{certificate}"
120
121
 
121
- begin
122
- result = query(Imis.configuration.imis_id_query_name, { certificate: })
123
- page = result.page.tap { logger.tagged('Response').debug it }
124
- self.imis_id = page.first['ID'].to_i
125
- rescue StandardError
126
- raise Errors::NotFoundError, 'Member not found'
127
- end
122
+ result = query(Imis.configuration.imis_id_query_name, { certificate: })
123
+ page = result.page.tap { logger.tagged('Response').debug it }
124
+ raise Errors::NotFoundError, 'Member not found' if page.empty?
125
+
126
+ self.imis_id = page.first['ID'].to_i
127
+ rescue Errors::ResponseError
128
+ raise Errors::NotFoundError, 'Member not found'
128
129
  end
129
130
 
130
131
  # Run requests as DSL, with specific iMIS ID only maintained for this scope
@@ -175,7 +176,7 @@ module Usps
175
176
  #
176
177
  # @return [Usps::Imis::Query] Query wrapper
177
178
  #
178
- def query(query_name, query_params = nil) = Query.new(self, query_name, **query_params)
179
+ def query(query_name, query_params = {}) = Query.new(self, query_name, **query_params)
179
180
 
180
181
  # Run requests as DSL, with specific +BusinessObject+ only maintained for this scope
181
182
  #
@@ -250,12 +251,27 @@ module Usps
250
251
  { token: @token, token_expiration: @token_expiration }
251
252
  end
252
253
 
254
+ # Persistent HTTP connection for reuse across requests
255
+ #
256
+ def http_connection
257
+ return @http_connection if @http_connection&.started?
258
+
259
+ hostname = URI(Imis.configuration.hostname)
260
+ @http_connection = Net::HTTP.new(hostname.host, hostname.port).tap do |http|
261
+ http.use_ssl = true
262
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER
263
+ http.open_timeout = 10
264
+ http.read_timeout = 30
265
+ http.write_timeout = 30
266
+ http.keep_alive_timeout = 30
267
+ http.start
268
+ end
269
+ end
270
+
253
271
  # Ruby 3.5 instance variable filter
254
272
  #
255
273
  def instance_variables_to_inspect = %i[@token_expiration @imis_id]
256
274
 
257
- private
258
-
259
275
  # Authenticate to the iMIS API, and store the access token and expiration time
260
276
  #
261
277
  def authenticate
@@ -270,7 +286,7 @@ module Usps
270
286
  username: Imis.configuration.username,
271
287
  password: Imis.configuration.password
272
288
  )
273
- result = submit(uri, request)
289
+ result = submit(request)
274
290
  json = JSON.parse(result.body)
275
291
  end
276
292
 
@@ -278,6 +294,8 @@ module Usps
278
294
  @token_expiration = Time.now + json['expires_in'] - 60
279
295
  end
280
296
 
297
+ private
298
+
281
299
  # URI for the authentication endpoint
282
300
  #
283
301
  def uri(...) = URI(File.join(Imis.configuration.hostname, AUTHENTICATION_PATH))
@@ -7,14 +7,28 @@ module Usps
7
7
  module Imis
8
8
  # Base class for API data response wrappers
9
9
  #
10
- class BaseData < Hash
10
+ class BaseData
11
+ # The raw API response hash
12
+ #
13
+ attr_reader :raw
14
+
15
+ # Create an instance from a hash
16
+ #
17
+ def self.[](hash) = new(hash)
18
+
11
19
  # Load raw API response JSON to access properties
12
20
  #
13
21
  # @param json [String] Raw API response JSON
14
22
  #
15
- def self.from_json(json) = self[JSON.parse(json)]
23
+ def self.from_json(json) = new(JSON.parse(json))
24
+
25
+ def initialize(raw = {})
26
+ @raw = raw
27
+ end
28
+
29
+ alias to_h raw
16
30
 
17
- alias raw to_h
31
+ def to_json(*) = raw.to_json(*)
18
32
 
19
33
  # Access the iMIS ID property
20
34
  #
@@ -48,6 +62,14 @@ module Usps
48
62
  )
49
63
  end
50
64
 
65
+ def ==(other)
66
+ case other
67
+ when BaseData then raw == other.raw
68
+ when Hash then raw == other
69
+ else super
70
+ end
71
+ end
72
+
51
73
  def inspect
52
74
  stringio = StringIO.new
53
75
  PP.pp(self, stringio)
@@ -37,7 +37,7 @@ module Usps
37
37
  @api = api
38
38
  @business_object_name = business_object_name.to_s
39
39
  @ordinal = ordinal
40
- @logger ||= Imis.logger('BusinessObject')
40
+ @logger = Imis.logger('BusinessObject')
41
41
  end
42
42
 
43
43
  # Run a query on the entire business object
@@ -134,7 +134,7 @@ module Usps
134
134
  #
135
135
  # @return [true] Only on success response (i.e. blank string from the API)
136
136
  #
137
- def delete = submit(uri, authorize(http_delete)).body == '' # rubocop:disable Naming/PredicateMethod
137
+ def delete = submit(authorize(http_delete)).body == '' # rubocop:disable Naming/PredicateMethod
138
138
  alias destroy delete
139
139
 
140
140
  # Build a blank object for the current iMIS ID
@@ -179,7 +179,7 @@ module Usps
179
179
 
180
180
  existing = get
181
181
 
182
- JSON.parse(JSON.dump(existing)).tap do |updated|
182
+ JSON.parse(JSON.dump(existing.raw)).tap do |updated|
183
183
  # Preserve the iMIS ID, as well as the Ordinal (if present)
184
184
  updated['Properties']['$values'], properties =
185
185
  existing.raw['Properties']['$values'].partition { %w[ID Ordinal].include?(it['Name']) }
@@ -204,7 +204,7 @@ module Usps
204
204
  def raw_object
205
205
  raise Errors::MissingIdError if api.imis_id.nil?
206
206
 
207
- response = submit(uri, authorize(http_get))
207
+ response = submit(authorize(http_get))
208
208
  klass = business_object_name == 'Party' ? PartyData : Data
209
209
  result = klass.from_json(response.body)
210
210
  logger.json result
@@ -217,7 +217,7 @@ module Usps
217
217
  raise Errors::MissingIdError if api.imis_id.nil?
218
218
 
219
219
  request.body = JSON.dump(body)
220
- response = submit(uri, authorize(request))
220
+ response = submit(authorize(request))
221
221
  klass = business_object_name == 'Party' ? PartyData : Data
222
222
  result = klass.from_json(response.body)
223
223
  logger.json result
@@ -50,7 +50,7 @@ module Usps
50
50
 
51
51
  configure!
52
52
  logging!
53
- @logger ||= Imis.logger('CommandLine')
53
+ @logger = Imis.logger('CommandLine')
54
54
  end
55
55
 
56
56
  # Run the configured action on the API
@@ -111,16 +111,15 @@ module Usps
111
111
 
112
112
  def simplify(data)
113
113
  return data.to_a if data.is_a?(Query)
114
- return data if options[:raw] || RAW_HASH_RESPONSE_OPTIONS.any? { options[it] }
114
+ return data.is_a?(BaseData) ? data.raw : data if force_raw_output?
115
115
 
116
116
  if data.is_a?(PartyData)
117
117
  logger.debug 'Returning simplified PartyData#properties'
118
118
  data.properties
119
- elsif data.is_a?(Hash)
120
- # Hash includes Usps::Imis::Data
119
+ elsif data.is_a?(Data)
121
120
  logger.debug 'Returning simplified Data#properties'
122
121
  data.properties(include_ids: options[:include_ids])
123
- elsif data.is_a?(Array) && data.all?(Hash)
122
+ elsif data.is_a?(Array) && data.all?(Data)
124
123
  logger.debug 'Returning simplified Array<Data#properties>'
125
124
  data.map { it.properties(include_ids: options[:include_ids]) }
126
125
  else
@@ -128,6 +127,8 @@ module Usps
128
127
  end
129
128
  end
130
129
 
130
+ def force_raw_output? = options[:raw] || RAW_HASH_RESPONSE_OPTIONS.any? { options[it] }
131
+
131
132
  # :nocov:
132
133
  def output(&)
133
134
  if ENV.fetch('SUPPRESS_OUTPUT', false)
@@ -158,6 +159,8 @@ module Usps
158
159
  global_log!(config)
159
160
 
160
161
  config_data&.each do |key, value|
162
+ next unless Config::SETTABLE.include?(key)
163
+
161
164
  config.public_send("#{key}=", value)
162
165
  end
163
166
  end
@@ -6,83 +6,46 @@ module Usps
6
6
  # Command line options parser
7
7
  #
8
8
  class OptionsParser
9
- OPTIONS = {
10
- # IDs
11
- certificate: ['Member certificate number', { type: :string }],
12
- id: ['Member iMIS ID', { type: :integer }],
13
- record_id: ['Specific Record ID', { type: :integer, short: :I }],
14
- uuid: ['Record UUID', { type: :string }],
15
-
16
- # Primary interactions
17
- on: ['Business Object name', { type: :string }],
18
- panel: ['Panel name', { type: :string }],
19
- query: ['IQA Query or Business Object name to query', { type: :string, short: :Q }],
20
- mapper: ['Interact with mapped fields', { short: :M }],
21
- map: ["Shorthand for #{'-Mf'.green} to access a single mapped field", { type: :string }],
22
- business_objects: ['List available Business Objects'],
23
-
24
- # Alternate verbs
25
- create: ["Send a #{'POST'.cyan} request", { short: :P }],
26
- delete: ["Send a #{'DELETE'.cyan} request", { short: :D }],
27
-
28
- # Data
29
- ordinal: ['Ordinal ID within a Panel', { type: :integer }],
30
- field: ['Specific field to return or update', { type: :string }],
31
- fields: ['Specific field(s) to return', { type: :strings, short: :F }],
32
- data: ["JSON string input -- #{'STDIN'.red} takes priority", { type: :string }],
33
-
34
- # Iteractions for supporting other language wrappers
35
- auth_token: ['Return an auth token for other language wrappers', { short: :T }],
36
- token: ['Provide an existing auth token', { type: :string }],
37
-
38
- # General config
39
- config: [
40
- 'Path to the JSON/YAML config file to use, or one of the following preset options: ' \
41
- "`#{'local'.yellow}`, " \
42
- "`#{'local_dot_config'.yellow}`, " \
43
- "`#{'local_config'.yellow}`, " \
44
- "`#{'user'.yellow}`, " \
45
- "`#{'system'.yellow}`. " \
46
- 'If no option is provided, the first matching preset option will be automatically used.',
47
- { type: :string, short: :C }
48
- ],
49
- show_config: ['Return the active config file path', { short: :X }],
50
- raw: ['Return raw JSON output, rather than simplified data', { short: :R }],
51
- include_ids: ["Include any #{'iMIS ID'.yellow} and #{'Ordinal'.yellow} properties in returned data"],
52
- jsonl: ['Format array output as JSONL', { short: :j }],
53
- quiet: ["Suppress logging to #{'STDERR'.red}"],
54
- log: ["Redirect logging to #{'STDOUT'.red}"],
55
- log_level: ['Set the logging level', { type: :string, default: 'info', short: :L }]
56
- }.freeze
57
-
9
+ SOLO_FLAGS = %i[show_config auth_token business_objects].freeze
58
10
  CONFLICTING_OPTION_GROUPS = [
59
11
  %i[certificate id uuid],
60
12
  %i[record_id uuid],
61
- %i[on panel query mapper map business_objects auth_token show_config],
13
+ %i[on panel query mapper map] + SOLO_FLAGS,
62
14
  %i[field fields map query],
63
15
  %i[raw include_ids],
64
16
  %i[quiet log_level],
65
17
  %i[quiet log],
66
18
 
67
19
  %i[create delete],
20
+ %i[create ordinal],
21
+ %i[data fields],
68
22
 
69
- %i[create mapper],
70
- %i[create query],
71
- %i[create map],
72
- %i[create field],
73
- %i[create fields],
74
-
75
- %i[delete mapper],
76
- %i[delete query],
77
- %i[delete map],
78
- %i[delete field],
79
- %i[delete fields],
80
- %i[delete data],
81
- %i[delete raw]
23
+ *(SOLO_FLAGS + %i[mapper query map field fields certificate]).map { [:create, it] },
24
+ *(SOLO_FLAGS + %i[mapper query map field fields certificate data raw]).map { [:delete, it] },
25
+ *(SOLO_FLAGS + %i[mapper query map on]).map { [:ordinal, it] },
26
+ *(SOLO_FLAGS + %i[certificate]).map { [:data, it] }
82
27
  ].freeze
83
28
 
84
29
  attr_reader :arguments, :options
85
30
 
31
+ def self.options
32
+ return @options if @options
33
+
34
+ raw_yaml_erb = File.read("#{File.dirname(__FILE__)}/options.yml.erb")
35
+ rendered_yaml = ERB.new(raw_yaml_erb).result.gsub("\x1b", '\u001b')
36
+ options = YAML.safe_load(rendered_yaml)
37
+
38
+ @options = options.transform_keys(&:to_sym).transform_values do |description, details|
39
+ next [description] if details.nil?
40
+
41
+ details = details.transform_keys(&:to_sym).each_with_object({}) do |(key, value), hash|
42
+ hash[key] = key == :default ? value : value.to_sym
43
+ end
44
+
45
+ [description, details]
46
+ end
47
+ end
48
+
86
49
  def self.banner_header(version)
87
50
  <<~BANNER
88
51
  #{version.bold.blue}
@@ -147,7 +110,7 @@ module Usps
147
110
  banner OptionsParser.banner_header(version)
148
111
  banner OptionsParser.banner_contents
149
112
 
150
- OPTIONS.each { |option, data| opt(option, *data) }
113
+ OptionsParser.options.each { |option, data| opt(option, *data) }
151
114
  CONFLICTING_OPTION_GROUPS.each { |group| conflicts(*group) }
152
115
 
153
116
  educate_on_error
@@ -160,7 +123,7 @@ module Usps
160
123
  # :nocov:
161
124
 
162
125
  def defaults?
163
- options_with_defaults = OPTIONS.select { |_, data| data[1]&.key?(:default) }
126
+ options_with_defaults = OptionsParser.options.select { |_, data| data[1]&.key?(:default) }
164
127
 
165
128
  options_with_defaults.all? { |option, data| data[1].nil? || options[option] == data[1][:default] } &&
166
129
  options.except(*options_with_defaults.keys).values.none?
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'optimist'
4
4
  require 'colorize'
5
+ require 'erb'
5
6
 
6
7
  module Usps
7
8
  module Imis
@@ -13,7 +13,10 @@ module Usps
13
13
  class Config
14
14
  IMIS_ROOT_URL_PROD = 'https://portal.americasboatingclub.org'
15
15
  IMIS_ROOT_URL_DEV = 'https://abcdev.imiscloud.com'
16
- REQUIRED_CONFIGS = %w[imis_id_query_name username password].freeze
16
+ DEFAULT_GLOBAL_LOG_PATH = '/var/log/imis/imis.log'
17
+ REQUIRED = %w[imis_id_query_name username password].freeze
18
+ OPTIONAL = %w[environment logger_file global_log_path].freeze
19
+ SETTABLE = (REQUIRED + OPTIONAL).freeze
17
20
 
18
21
  attr_accessor :imis_id_query_name, :username, :password
19
22
  attr_reader :environment, :logger, :logger_level, :logger_file
@@ -55,13 +58,14 @@ module Usps
55
58
  end
56
59
 
57
60
  def global_log_path
58
- defined?(@global_log_path) ? @global_log_path : self.global_log_path = '/var/log/imis/imis.log'
61
+ return @global_log_path if defined?(@global_log_path)
62
+
63
+ self.global_log_path = DEFAULT_GLOBAL_LOG_PATH
59
64
  end
60
65
 
61
66
  def global_log_path=(path)
62
67
  @global_log_path = path
63
68
  @global_logger = build_global_logger
64
- global_log_path
65
69
  end
66
70
 
67
71
  def silence!
@@ -88,7 +92,7 @@ module Usps
88
92
  def filtered_parameters = %i[password]
89
93
 
90
94
  def validate!
91
- missing_config = REQUIRED_CONFIGS.filter_map { it if public_send(it).nil? }
95
+ missing_config = REQUIRED.filter_map { it if public_send(it).nil? }
92
96
  return if missing_config.empty?
93
97
 
94
98
  raise Errors::ConfigError, "Missing required configuration: #{missing_config.join(', ')}"
@@ -95,7 +95,7 @@ module Usps
95
95
  response_body['error_description']
96
96
  else
97
97
  # Unknown error type: just use the raw response
98
- JSON.pretty_generate(response.body)
98
+ JSON.pretty_generate(response_body)
99
99
  end
100
100
  end
101
101
  end
@@ -30,7 +30,7 @@ module Usps
30
30
  def initialize(api = nil, imis_id: nil)
31
31
  @api = api || Api.new
32
32
  @api.imis_id = imis_id if imis_id
33
- @logger ||= Imis.logger('Mapper')
33
+ @logger = Imis.logger('Mapper')
34
34
  end
35
35
 
36
36
  # Get a member's data for a specific field by arbitrary field name
@@ -68,8 +68,8 @@ module Usps
68
68
  business_objects[business_object_name] << field
69
69
  end
70
70
 
71
- business_objects.flat_map do |business_object_name, fields|
72
- api.on(business_object_name).get_fields(*fields)
71
+ business_objects.flat_map do |business_object_name, object_fields|
72
+ api.on(business_object_name).get_fields(*object_fields)
73
73
  end
74
74
  end
75
75
  alias fetch_all get_fields
@@ -18,7 +18,7 @@ module Usps
18
18
  @api = api || Api.new
19
19
  @api.imis_id = imis_id if imis_id
20
20
  @api.record_id = record_id if record_id
21
- @logger ||= Imis.logger('Panel')
21
+ @logger = Imis.logger('Panel')
22
22
  end
23
23
 
24
24
  # Get a specific object from the Panel
@@ -19,7 +19,7 @@ module Usps
19
19
  case value
20
20
  when String then value
21
21
  when Symbol then value.to_s
22
- when Time, DateTime then value.strftime('%Y-%m-%dT%H:%I:%S')
22
+ when Time, DateTime then value.strftime('%Y-%m-%dT%H:%M:%S')
23
23
  when Integer then { '$type' => 'System.Int32', '$value' => value }
24
24
  when true, false then { '$type' => 'System.Boolean', '$value' => value }
25
25
  else
@@ -59,7 +59,7 @@ module Usps
59
59
  @page_size = page_size
60
60
  @offset = offset
61
61
  @count = 0
62
- @logger ||= Imis.logger('Query', query_type)
62
+ @logger = Imis.logger('Query', query_type)
63
63
 
64
64
  logger.tagged('Name').debug query_name
65
65
  logger.tagged('Params').json query_params
@@ -113,7 +113,7 @@ module Usps
113
113
 
114
114
  # Fetch a raw query page
115
115
  #
116
- def fetch = JSON.parse(submit(uri, authorize(http_get)).body)
116
+ def fetch = JSON.parse(submit(authorize(http_get)).body)
117
117
 
118
118
  # Reset query paging progress
119
119
  #
@@ -7,23 +7,18 @@ module Usps
7
7
  module Requests
8
8
  private
9
9
 
10
- def logger = raise("#{self.class.name} must implement #logger")
10
+ def logger = raise(Errors::ApiError, "#{self.class.name} must implement #logger")
11
11
 
12
12
  def token = api.token
13
13
  def token_expiration = api.token_expiration
14
- def authenticate = api.send(:authenticate)
14
+ def authenticate = api.authenticate
15
15
 
16
16
  def http_get = Net::HTTP::Get.new(uri)
17
17
  def http_put = Net::HTTP::Put.new(uri)
18
18
  def http_post = Net::HTTP::Post.new(uri(id: ''))
19
19
  def http_delete = Net::HTTP::Delete.new(uri)
20
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
21
+ def client = respond_to?(:http_connection) ? http_connection : api.http_connection
27
22
 
28
23
  # Authorize a request prior to submitting
29
24
  #
@@ -37,13 +32,13 @@ module Usps
37
32
  request.tap { it.add_field('Authorization', "Bearer #{token}") }
38
33
  end
39
34
 
40
- def submit(uri, request)
35
+ def submit(request)
41
36
  logger.info 'Submitting request to iMIS'
42
37
  logger.tagged('Request') do
43
38
  logger.debug "#{request.class.name.demodulize.upcase} #{uri}"
44
39
  logger.json sanitized_request_body(request)
45
40
 
46
- client(uri).request(request).tap do |result|
41
+ client.request(request).tap do |result|
47
42
  raise Errors::ResponseError.from(result) unless result.is_a?(Net::HTTPSuccess)
48
43
 
49
44
  logger.info 'Request succeeded'
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Usps
4
4
  module Imis
5
- VERSION = '0.11.40'
5
+ VERSION = '0.11.41'
6
6
  end
7
7
  end
data/lib/usps/imis.rb CHANGED
@@ -12,7 +12,7 @@ require 'active_support/core_ext/object/to_query'
12
12
  require 'active_support/core_ext/enumerable'
13
13
  require 'active_support/string_inquirer'
14
14
  require 'logger'
15
- require 'active_support/isolated_execution_state' # Fix costant loading issue with TaggedLogging
15
+ require 'active_support/isolated_execution_state' # Fix constant loading issue with TaggedLogging
16
16
  require 'active_support/tagged_logging'
17
17
 
18
18
  require 'dotenv/load'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: usps-imis-api
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.11.40
4
+ version: 0.11.41
5
5
  platform: ruby
6
6
  authors:
7
7
  - Julian Fiander