freight_kit 0.1.21 → 0.1.22

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 +4 -4
  2. data/README.md +5 -3
  3. data/Rakefile +1 -3
  4. data/VERSION +1 -1
  5. data/freight_kit-0.1.21.gem +0 -0
  6. data/freight_kit.gemspec +8 -19
  7. data/lib/freight_kit/address_normalizer.rb +1 -3
  8. data/lib/freight_kit/api_clients/soap_client.rb +30 -5
  9. data/lib/freight_kit/carrier.rb +6 -7
  10. data/lib/freight_kit/carriers/abf_freight_system.rb +9 -26
  11. data/lib/freight_kit/carriers/aloha_freight_forwarders.rb +1 -5
  12. data/lib/freight_kit/carriers/amado_trucking.rb +1 -5
  13. data/lib/freight_kit/carriers/clear_lane_freight_systems.rb +6 -15
  14. data/lib/freight_kit/carriers/cpl_express.rb +1 -5
  15. data/lib/freight_kit/carriers/cross_country_freight_solutions.rb +6 -17
  16. data/lib/freight_kit/carriers/dependable_highway_express.rb +6 -18
  17. data/lib/freight_kit/carriers/forward_air.rb +4 -12
  18. data/lib/freight_kit/carriers/on_trac.rb +7 -25
  19. data/lib/freight_kit/carriers/peninsula_truck_lines.rb +4 -20
  20. data/lib/freight_kit/carriers/roadrunner_transportation_services.rb +2 -8
  21. data/lib/freight_kit/carriers/saia.rb +4 -19
  22. data/lib/freight_kit/carriers/southeastern_freight_lines.rb +4 -13
  23. data/lib/freight_kit/carriers/t_force_worldwide.rb +7 -19
  24. data/lib/freight_kit/carriers/total_quality_logistics.rb +2 -1
  25. data/lib/freight_kit/carriers/western_regional_delivery_service.rb +5 -11
  26. data/lib/freight_kit/carriers/xpo_logistics.rb +5 -17
  27. data/lib/freight_kit/errors/rate_limit_error.rb +12 -0
  28. data/lib/freight_kit/errors/unserviceable_accessorials_error.rb +1 -3
  29. data/lib/freight_kit/helpers/pickupable.rb +1 -10
  30. data/lib/freight_kit/http_client/httpi_adapter.rb +37 -0
  31. data/lib/freight_kit/http_client/safe_retries.rb +17 -1
  32. data/lib/freight_kit/http_client.rb +13 -9
  33. data/lib/freight_kit/models/credential.rb +2 -15
  34. data/lib/freight_kit/models/shipment.rb +1 -8
  35. data/lib/freight_kit/package.rb +3 -13
  36. data/lib/freight_kit/packaging.rb +1 -16
  37. data/lib/freight_kit/platforms/carrier_logistics.rb +5 -10
  38. data/lib/freight_kit/platforms/dsdc.rb +82 -0
  39. data/lib/freight_kit/platforms/next_tms/graphql_client/mutation/quote_create.rb +32 -0
  40. data/lib/freight_kit/platforms/next_tms/graphql_client/mutation/shipment_create.rb +29 -0
  41. data/lib/freight_kit/platforms/next_tms/graphql_client/query/broker_carrier.rb +37 -0
  42. data/lib/freight_kit/platforms/next_tms/graphql_client/query/broker_carriers.rb +45 -0
  43. data/lib/freight_kit/platforms/next_tms/graphql_client/query/broker_customer.rb +54 -0
  44. data/lib/freight_kit/platforms/next_tms/graphql_client/query/broker_customers.rb +62 -0
  45. data/lib/freight_kit/platforms/next_tms/graphql_client/query/carrier.rb +22 -0
  46. data/lib/freight_kit/platforms/next_tms/graphql_client/query/carriers.rb +30 -0
  47. data/lib/freight_kit/platforms/next_tms/graphql_client/query/customer.rb +20 -0
  48. data/lib/freight_kit/platforms/next_tms/graphql_client/query/customers.rb +28 -0
  49. data/lib/freight_kit/platforms/next_tms/graphql_client/query/invoice.rb +31 -0
  50. data/lib/freight_kit/platforms/next_tms/graphql_client/query/invoices.rb +39 -0
  51. data/lib/freight_kit/platforms/next_tms/graphql_client/query/shipment.rb +102 -0
  52. data/lib/freight_kit/platforms/next_tms/graphql_client/query/shipment_documents.rb +29 -0
  53. data/lib/freight_kit/platforms/next_tms/graphql_client/query/shipments.rb +110 -0
  54. data/lib/freight_kit/platforms/next_tms/graphql_client.rb +22 -0
  55. data/lib/freight_kit/platforms/next_tms/orm/address.rb +22 -0
  56. data/lib/freight_kit/platforms/next_tms/orm/base.rb +120 -0
  57. data/lib/freight_kit/platforms/next_tms/orm/broker_carrier.rb +18 -0
  58. data/lib/freight_kit/platforms/next_tms/orm/broker_customer.rb +22 -0
  59. data/lib/freight_kit/platforms/next_tms/orm/carrier.rb +14 -0
  60. data/lib/freight_kit/platforms/next_tms/orm/collection.rb +335 -0
  61. data/lib/freight_kit/platforms/next_tms/orm/customer.rb +12 -0
  62. data/lib/freight_kit/platforms/next_tms/orm/invoice.rb +21 -0
  63. data/lib/freight_kit/platforms/next_tms/orm/record_not_found_error.rb +9 -0
  64. data/lib/freight_kit/platforms/next_tms/orm/shipment.rb +50 -0
  65. data/lib/freight_kit/platforms/next_tms/orm.rb +38 -0
  66. data/lib/freight_kit/platforms/next_tms.rb +414 -55
  67. data/lib/freight_kit/platforms/the_great_information_factory.rb +4 -12
  68. data/lib/freight_kit.rb +19 -0
  69. data/schemas/next_tms.json +110710 -0
  70. metadata +83 -5
@@ -17,11 +17,7 @@ module FreightKit
17
17
 
18
18
  def contacts
19
19
  [
20
- Contact.new(
21
- department: 'Dispatch - Mira Loma, CA',
22
- id: 'dc14714f-e381-4b8b-97a6-f002c0897409',
23
- phone: '+19513615900'
24
- ),
20
+ Contact.new(department: 'Dispatch - Mira Loma, CA', id: 'dc14714f-e381-4b8b-97a6-f002c0897409', phone: '+19513615900'),
25
21
  Contact.new(department: 'Main', id: '22203680-060c-41db-ba65-9925e8fa381d', phone: '+14146151696')
26
22
  ]
27
23
  end
@@ -398,9 +394,7 @@ module FreightKit
398
394
  def parse_amount(amount)
399
395
  negative = amount.start_with?('-$') || amount.start_with?('-')
400
396
 
401
- ['$', '-', ','].each do |char|
402
- amount = amount.sub(char, '')
403
- end
397
+ ['$', '-', ','].each { |char| amount = amount.sub(char, '') }
404
398
 
405
399
  return 0 if amount.blank?
406
400
 
@@ -265,9 +265,7 @@ module FreightKit
265
265
  end
266
266
 
267
267
  if declared_value.present?
268
- request = request.deep_merge(
269
- { request: { DeclaredValue: declared_value, FullValueCoverage: declared_value } }
270
- )
268
+ request = request.deep_merge({ request: { DeclaredValue: declared_value, FullValueCoverage: declared_value } })
271
269
  end
272
270
 
273
271
  save_request(request)
@@ -281,12 +279,7 @@ module FreightKit
281
279
  end
282
280
 
283
281
  def commit(action, request)
284
- client_args = {
285
- wsdl: request_url(action),
286
- convert_request_keys_to: :none,
287
- env_namespace: :soap,
288
- element_form_default: :qualified
289
- }
282
+ client_args = { wsdl: request_url(action), convert_request_keys_to: :none, env_namespace: :soap, element_form_default: :qualified }
290
283
 
291
284
  call_args = { message: request_blueprint.deep_merge(request) }
292
285
 
@@ -307,11 +300,7 @@ module FreightKit
307
300
  end
308
301
 
309
302
  def parse_api_location(api_event)
310
- Location.new(
311
- city: api_event[:city]&.titleize,
312
- province: api_event[:state]&.upcase,
313
- country: ActiveUtils::Country.find('USA')
314
- )
303
+ Location.new(city: api_event[:city]&.titleize, province: api_event[:state]&.upcase, country: ActiveUtils::Country.find('USA'))
315
304
  end
316
305
 
317
306
  def parse_rate_response(shipment:, response:)
@@ -477,11 +466,7 @@ module FreightKit
477
466
  api_events = search_result.dig(:history, :history_item)
478
467
 
479
468
  if api_events.blank?
480
- shipment_events << ShipmentEvent.new(
481
- date_time: pickup_date,
482
- location: shipper_location,
483
- type_code: :picked_up
484
- )
469
+ shipment_events << ShipmentEvent.new(date_time: pickup_date, location: shipper_location, type_code: :picked_up)
485
470
  else
486
471
  api_events = [api_events] if api_events.is_a?(Hash)
487
472
 
@@ -160,11 +160,7 @@ module FreightKit
160
160
  methods: { get_rate: :get, rates: :post },
161
161
  use_ssl: false
162
162
  }.freeze
163
- JSON_HEADERS = {
164
- Accept: 'application/json',
165
- charset: 'utf-8',
166
- 'Content-Type' => 'application/x-www-form-urlencoded'
167
- }.freeze
163
+ JSON_HEADERS = { Accept: 'application/json', charset: 'utf-8', 'Content-Type' => 'application/x-www-form-urlencoded' }.freeze
168
164
 
169
165
  # Documents
170
166
 
@@ -296,12 +292,7 @@ module FreightKit
296
292
  headers = headers.merge(options[:headers]) if options[:headers].present?
297
293
  body = URI.encode_www_form(options[:body]) if options[:body].present?
298
294
 
299
- request = {
300
- url: options[:url].presence || build_url(action),
301
- headers:,
302
- method: API.dig(:methods, action),
303
- body:
304
- }
295
+ request = { url: options[:url].presence || build_url(action), headers:, method: API.dig(:methods, action), body: }
305
296
 
306
297
  save_request(request)
307
298
  request
@@ -317,9 +308,9 @@ module FreightKit
317
308
 
318
309
  case method
319
310
  when :post
320
- HTTPClient.post(url, headers:, body:)
311
+ HTTPClient.post(url, headers:, body:, proxy_url:)
321
312
  else
322
- HTTPClient.get(url, headers:)
313
+ HTTPClient.get(url, headers:, proxy_url:)
323
314
  end
324
315
  end
325
316
 
@@ -190,16 +190,7 @@ module FreightKit
190
190
  service:,
191
191
  shipment:
192
192
  )
193
- request = build_pickup_request(
194
- delivery_from:,
195
- delivery_to:,
196
- dispatcher:,
197
- pickup_from:,
198
- pickup_to:,
199
- scac:,
200
- service:,
201
- shipment:
202
- )
193
+ request = build_pickup_request(delivery_from:, delivery_to:, dispatcher:, pickup_from:, pickup_to:, scac:, service:, shipment:)
203
194
 
204
195
  parse_pickup_response(commit(request))
205
196
  end
@@ -399,9 +390,9 @@ module FreightKit
399
390
 
400
391
  case method
401
392
  when :post
402
- HTTPClient.post(url, headers:, body:)
393
+ HTTPClient.post(url, headers:, body:, proxy_url:)
403
394
  else
404
- HTTPClient.get(url, headers:)
395
+ HTTPClient.get(url, headers:, proxy_url:)
405
396
  end
406
397
  end
407
398
 
@@ -468,8 +459,7 @@ module FreightKit
468
459
  logout_url = 'https://rrd.mercurygate.net/MercuryGate/login/urlRedirect.jsp?Logout=true'
469
460
 
470
461
  begin
471
- browser.element(xpath: '//*[@id="__AppFrameBaseTable"]/tbody/tr[2]/td/div[4]')
472
- .click
462
+ browser.element(xpath: '//*[@id="__AppFrameBaseTable"]/tbody/tr[2]/td/div[4]').click
473
463
  rescue Selenium::WebDriver::Error::UnexpectedAlertOpenError => e
474
464
  browser.close
475
465
 
@@ -484,8 +474,7 @@ module FreightKit
484
474
  browser.iframe(name: 'AppBody').frame(id: 'Header').select(name: 'column').select('Primary Reference')
485
475
  browser.iframe(name: 'AppBody').frame(id: 'Header').select(name: 'condition').select('=')
486
476
  browser.iframe(name: 'AppBody').frame(id: 'Header').text_field(name: 'filter').set(tracking_number)
487
- browser.iframe(name: 'AppBody').frame(id: 'Header').button(value: 'Find')
488
- .click
477
+ browser.iframe(name: 'AppBody').frame(id: 'Header').button(value: 'Find').click
489
478
 
490
479
  begin
491
480
  browser.iframe(name: 'AppBody').frame(id: 'Detail').iframe(id: 'transportsWin')
@@ -505,8 +494,7 @@ module FreightKit
505
494
  return document_response
506
495
  end
507
496
 
508
- html = browser.iframe(name: 'AppBody').frame(id: 'Detail').iframes[1].element(xpath: '/html/body/table[3]')
509
- .html
497
+ html = browser.iframe(name: 'AppBody').frame(id: 'Detail').iframes[1].element(xpath: '/html/body/table[3]').html
510
498
  html = Nokogiri::HTML(html)
511
499
 
512
500
  browser.goto(logout_url)
@@ -529,7 +517,7 @@ module FreightKit
529
517
  end
530
518
 
531
519
  begin
532
- response = HTTPClient.get(url)
520
+ response = HTTPClient.get(url, proxy_url:)
533
521
  rescue StandardError => e
534
522
  document_response.error = e
535
523
  return document_response
@@ -214,7 +214,8 @@ module FreightKit
214
214
  request[:url],
215
215
  headers: request[:headers].merge(subscription_key_headers),
216
216
  query: request[:query],
217
- body: request[:body]
217
+ body: request[:body],
218
+ proxy_url:
218
219
  )
219
220
 
220
221
  parsed_response = JSON.parse(response.body)
@@ -70,9 +70,9 @@ module FreightKit
70
70
  url = request_url(action)
71
71
 
72
72
  response = if API.dig(:methods, action) == :post
73
- options[:params].blank? ? HTTPClient.post(url) : HTTPClient.post(url, query: options[:params])
73
+ options[:params].blank? ? HTTPClient.post(url, proxy_url:) : HTTPClient.post(url, proxy_url:, query: options[:params])
74
74
  else
75
- HTTPClient.get(url)
75
+ HTTPClient.get(url, proxy_url:)
76
76
  end
77
77
 
78
78
  response.parsed_response if response&.parsed_response
@@ -81,11 +81,7 @@ module FreightKit
81
81
  def parse_api_city_state(str)
82
82
  return if str.blank?
83
83
 
84
- Location.new(
85
- city: str[..-3].strip.titleize,
86
- province: str[-2..].upcase,
87
- country: ActiveUtils::Country.find('USA')
88
- )
84
+ Location.new(city: str[..-3].strip.titleize, province: str[-2..].upcase, country: ActiveUtils::Country.find('USA'))
89
85
  end
90
86
 
91
87
  def parse_api_city_state_zip(str)
@@ -118,7 +114,7 @@ module FreightKit
118
114
  document_response = DocumentResponse.new(request: URI.parse(url))
119
115
 
120
116
  begin
121
- response = HTTPClient.get(url)
117
+ response = HTTPClient.get(url, proxy_url:)
122
118
  rescue StandardError => e
123
119
  document_response.error = e
124
120
  return document_response
@@ -152,9 +148,7 @@ module FreightKit
152
148
  image_url = nil
153
149
  browser.switch_window.use do
154
150
  page_count = browser.element(xpath: '/html/body/form/div[3]/b/span').text.strip.to_i
155
- (page_count - 1).times do
156
- browser.element(xpath: '/html/body/form/div[3]/input[2]').wait_until(&:present?).click
157
- end
151
+ (page_count - 1).times { browser.element(xpath: '/html/body/form/div[3]/input[2]').wait_until(&:present?).click }
158
152
  image_url = browser.element(css: '#cphMain_imgImage').attribute_value('src')
159
153
  end
160
154
  browser.close
@@ -17,11 +17,7 @@ module FreightKit
17
17
 
18
18
  def contacts
19
19
  [
20
- Contact.new(
21
- department: 'Customer Service - Kingman, AZ',
22
- id: '5bb9caaa-ae84-4ee6-90ef-3c14f6d434c6',
23
- phone: '+17609228255'
24
- )
20
+ Contact.new(department: 'Customer Service - Kingman, AZ', id: '5bb9caaa-ae84-4ee6-90ef-3c14f6d434c6', phone: '+17609228255')
25
21
  ]
26
22
  end
27
23
 
@@ -159,11 +155,7 @@ module FreightKit
159
155
  def website_url = 'https://www.xpo.com'
160
156
  end
161
157
 
162
- API = {
163
- domain: 'api.ltl.xpo.com',
164
- endpoints: { rates: '/rating/1.0/ratequotes', token: '/token' },
165
- use_ssl: true
166
- }.freeze
158
+ API = { domain: 'api.ltl.xpo.com', endpoints: { rates: '/rating/1.0/ratequotes', token: '/token' }, use_ssl: true }.freeze
167
159
 
168
160
  # Documents
169
161
 
@@ -223,11 +215,7 @@ module FreightKit
223
215
  def build_bearer_token_request
224
216
  api_credentials = fetch_credential(:api)
225
217
 
226
- body = URI.encode_www_form(
227
- grant_type: 'password',
228
- password: api_credentials.password,
229
- username: api_credentials.username
230
- )
218
+ body = URI.encode_www_form(grant_type: 'password', password: api_credentials.password, username: api_credentials.username)
231
219
 
232
220
  {
233
221
  body:,
@@ -301,9 +289,9 @@ module FreightKit
301
289
 
302
290
  response = case method
303
291
  when :post
304
- HTTPClient.post(url, headers:, body:)
292
+ HTTPClient.post(url, headers:, body:, proxy_url:)
305
293
  else
306
- HTTPClient.get(url, headers:)
294
+ HTTPClient.get(url, headers:, proxy_url:)
307
295
  end
308
296
 
309
297
  begin
@@ -0,0 +1,12 @@
1
+ module FreightKit
2
+ # The API refused a request because the caller sent too many. Wait {#retry_after} seconds before the next one.
3
+ class RateLimitError < HTTPError
4
+ attr_reader :retry_after
5
+
6
+ def initialize(body:, code:, retry_after: nil)
7
+ @retry_after = retry_after
8
+
9
+ super(body:, code:)
10
+ end
11
+ end
12
+ end
@@ -9,9 +9,7 @@ module FreightKit
9
9
  end
10
10
 
11
11
  def message
12
- @message ||= "Unable to service #{@accessorials.map do |accessorial|
13
- accessorial.to_s.gsub('_', ' ')
14
- end.join(', ')}"
12
+ @message ||= "Unable to service #{@accessorials.map { |accessorial| accessorial.to_s.gsub('_', ' ') }.join(', ')}"
15
13
  end
16
14
  end
17
15
  end
@@ -10,16 +10,7 @@ module FreightKit
10
10
  service:,
11
11
  shipment:
12
12
  )
13
- request = build_pickup_request(
14
- delivery_from:,
15
- delivery_to:,
16
- dispatcher:,
17
- pickup_from:,
18
- pickup_to:,
19
- scac:,
20
- service:,
21
- shipment:
22
- )
13
+ request = build_pickup_request(delivery_from:, delivery_to:, dispatcher:, pickup_from:, pickup_to:, scac:, service:, shipment:)
23
14
 
24
15
  begin
25
16
  # For SOAP APIs, the :action parameter is required
@@ -0,0 +1,37 @@
1
+ module FreightKit
2
+ class HTTPClient
3
+ # HTTPI adapter sending Savon's requests through {HTTPClient}, so SOAP calls get its compression,
4
+ # connection reuse, retries, cache, logger and User-Agent:
5
+ #
6
+ # Savon.client(adapter: FreightKit::HTTPClient::HTTPIAdapter::NAME, wsdl: url)
7
+ class HTTPIAdapter < HTTPI::Adapter::Base
8
+ NAME = :freight_kit
9
+
10
+ register NAME
11
+
12
+ attr_reader :client
13
+
14
+ def initialize(request)
15
+ super
16
+ raise HTTPI::NotSupportedError, "#{NAME} does not support #{request.auth.type} authentication" if request.auth.type
17
+
18
+ @client = HTTPClient
19
+ @request = request
20
+ end
21
+
22
+ def request(method)
23
+ response = client.request(
24
+ method,
25
+ @request.url.to_s,
26
+ body: @request.body,
27
+ headers: @request.headers.to_h,
28
+ proxy_url: @request.proxy&.to_s,
29
+ read_timeout: @request.read_timeout
30
+ )
31
+
32
+ # HTTPX already decoded the body, and HTTPI would gunzip it again
33
+ HTTPI::Response.new(response.code, response.headers.to_h.except('content-encoding'), response.body)
34
+ end
35
+ end
36
+ end
37
+ end
@@ -6,13 +6,29 @@ module FreightKit
6
6
  # The persistent plugin retries any request failing with a connection error, but a POST may
7
7
  # already have been processed by then, so only idempotent requests are retried.
8
8
  # Streams refused by an HTTP/2 GOAWAY are resent by HTTPX itself, independently of retries.
9
+ #
10
+ # A 429 means the server did not process the request, so any request is retried once after its
11
+ # Retry-After seconds, unless that is longer than {MAX_RETRY_AFTER}.
9
12
  module SafeRetries
10
13
  IDEMPOTENT_METHODS = ['DELETE', 'GET', 'HEAD', 'OPTIONS', 'PUT'].freeze
14
+ # Longest wait in seconds. Longer waits return the 429, so callers can reschedule instead of blocking.
15
+ MAX_RETRY_AFTER = 60
11
16
 
12
17
  module InstanceMethods
13
18
  private
14
19
 
15
- def retryable_request?(request, *) = super && IDEMPOTENT_METHODS.include?(request.verb)
20
+ def rate_limited?(response)
21
+ response.is_a?(HTTPX::Response) && response.status == 429 && retry_after(response)&.between?(0, MAX_RETRY_AFTER)
22
+ end
23
+
24
+ def retry_after(response) = Integer(response.headers['retry-after'], 10, exception: false)
25
+
26
+ def retryable_request?(request, response, *)
27
+ rate_limited?(response) || (super && IDEMPOTENT_METHODS.include?(request.verb))
28
+ end
29
+
30
+ def retryable_response?(response, *) = super || rate_limited?(response)
31
+ def when_to_retry(request, response, *) = rate_limited?(response) ? retry_after(response) : super
16
32
  end
17
33
 
18
34
  class << self
@@ -2,7 +2,7 @@ module FreightKit
2
2
  # Thin wrapper around HTTPX that negotiates Brotli/Zstandard compression, keeps connections
3
3
  # open between requests and revalidates cached responses with conditional requests (ETag/Last-Modified).
4
4
  #
5
- # The cache and logger are hookable; they default to the host Rails/Hanami app's logger:
5
+ # The cache defaults to {FreightKit.cache}, and the logger to the host Rails/Hanami app's logger. Both are hookable:
6
6
  #
7
7
  # FreightKit::HTTPClient.cache = Rails.cache
8
8
  # FreightKit::HTTPClient.logger = Rails.logger
@@ -12,11 +12,11 @@ module FreightKit
12
12
  @sessions = Concurrent::Map.new
13
13
 
14
14
  class << self
15
- def cache = @cache ||= ActiveSupport::Cache::MemoryStore.new
15
+ def cache = @cache || FreightKit.cache
16
16
 
17
17
  def cache=(store)
18
18
  @cache = store
19
- @sessions.clear
19
+ reset
20
20
  end
21
21
 
22
22
  def get(url, **) = request(:get, url, **)
@@ -24,14 +24,15 @@ module FreightKit
24
24
 
25
25
  def logger=(logger)
26
26
  @logger = logger
27
- @sessions.clear
27
+ reset
28
28
  end
29
29
 
30
30
  def post(url, **) = request(:post, url, **)
31
31
 
32
- def request(verb, url, body: nil, headers: {}, proxy: nil, query: nil)
32
+ def request(verb, url, body: nil, headers: {}, proxy_url: nil, query: nil, read_timeout: nil)
33
33
  options = { headers: }
34
34
  options[:params] = query if query.present?
35
+ options[:timeout] = { read_timeout: } if read_timeout
35
36
 
36
37
  # Like HTTParty, hashes are form-encoded regardless of the Content-Type header
37
38
  if body.is_a?(Hash)
@@ -40,12 +41,15 @@ module FreightKit
40
41
  options[:body] = body
41
42
  end
42
43
 
43
- response = session(proxy.presence).request(verb.to_s.upcase, url, **options)
44
+ response = session(proxy_url.presence).request(verb.to_s.upcase, url, **options)
44
45
  raise response.error if response.is_a?(HTTPX::ErrorResponse)
45
46
 
46
47
  Response.new(response)
47
48
  end
48
49
 
50
+ # Rebuilds sessions on their next request, e.g. after the cache or logger changes.
51
+ def reset = @sessions.clear
52
+
49
53
  private
50
54
 
51
55
  def framework_logger
@@ -57,8 +61,8 @@ module FreightKit
57
61
  end
58
62
 
59
63
  # Sessions own their connection pool, so they are built once (per proxy) and reused
60
- def session(proxy)
61
- @sessions.compute_if_absent(proxy) do
64
+ def session(proxy_url)
65
+ @sessions.compute_if_absent(proxy_url) do
62
66
  session = HTTPX.plugin(:brotli).plugin(Zstd).plugin(:follow_redirects)
63
67
  .plugin(:response_cache, response_cache_store: CacheStore.new(cache))
64
68
  .plugin(Revalidation)
@@ -68,7 +72,7 @@ module FreightKit
68
72
  logger = self.logger
69
73
  session = session.with(debug: LogDevice.new(logger), debug_level: 2) if logger.level <= ::Logger::DEBUG
70
74
 
71
- proxy ? session.plugin(:proxy).with(proxy: { uri: proxy }) : session
75
+ proxy_url ? session.plugin(:proxy).with(proxy: { uri: proxy_url }) : session
72
76
  end
73
77
  end
74
78
  end
@@ -51,7 +51,7 @@ module FreightKit
51
51
  when :api, :website
52
52
  { password: String }
53
53
  when :api_proxy
54
- { api_key: String, proxy_url: String }
54
+ { proxy_url: String }
55
55
  when :oauth2
56
56
  { access_token: String, expires_at: ::DateTime, scope: String }
57
57
  when :selenoid
@@ -96,20 +96,7 @@ module FreightKit
96
96
  url.path = '/wd/hub/'
97
97
  url = url.to_s
98
98
 
99
- @watir_args = [
100
- browser,
101
- {
102
- options: {
103
- prefs: {
104
- download: {
105
- directory_upgrade: true,
106
- prompt_for_download: false
107
- }
108
- }
109
- },
110
- url:
111
- }
112
- ]
99
+ @watir_args = [browser, { options: { prefs: { download: { directory_upgrade: true, prompt_for_download: false } } }, url: }]
113
100
  end
114
101
  end
115
102
  end
@@ -11,14 +11,7 @@ module FreightKit
11
11
  # @!attribute po_number [String] Purchase order number (also known as PO #).
12
12
  # @!attribute pickup_at [FreightKit::DateTime] Pickup date/time.
13
13
  class Shipment < Model
14
- attr_accessor :accessorials,
15
- :declared_value_cents,
16
- :destination,
17
- :origin,
18
- :order_number,
19
- :packages,
20
- :po_number,
21
- :pro
14
+ attr_accessor :accessorials, :declared_value_cents, :destination, :origin, :order_number, :packages, :po_number, :pro
22
15
  attr_reader :pickup_at
23
16
 
24
17
  def hazmat?
@@ -64,13 +64,7 @@ module FreightKit # :nodoc:
64
64
 
65
65
  @quantity = options[:quantity] || 1
66
66
 
67
- @total_weight = attribute_from_metric_or_imperial(
68
- total_grams_or_ounces,
69
- Measured::Weight,
70
- @weight_unit_system,
71
- :grams,
72
- :ounces
73
- )
67
+ @total_weight = attribute_from_metric_or_imperial(total_grams_or_ounces, Measured::Weight, @weight_unit_system, :grams, :ounces)
74
68
 
75
69
  @each_weight = attribute_from_metric_or_imperial(
76
70
  total_grams_or_ounces / @quantity.to_f,
@@ -91,9 +85,7 @@ module FreightKit # :nodoc:
91
85
  @dimensions = @dimensions.map do |l|
92
86
  attribute_from_metric_or_imperial(l, Measured::Length, @dimensions_unit_system, :centimetres, :inches)
93
87
  end
94
- 2.downto(@dimensions.length) do |_n|
95
- @dimensions.unshift(@dimensions[0])
96
- end
88
+ 2.downto(@dimensions.length) { |_n| @dimensions.unshift(@dimensions[0]) }
97
89
  end
98
90
 
99
91
  @value = Package.cents_from(options[:value])
@@ -274,9 +266,7 @@ module FreightKit # :nodoc:
274
266
  # [1,2] => [1,1,2]
275
267
  # [5] => [5,5,5]
276
268
  # etc..
277
- 2.downto(@dimensions.length) do |_n|
278
- @dimensions.unshift(@dimensions[0])
279
- end
269
+ 2.downto(@dimensions.length) { |_n| @dimensions.unshift(@dimensions[0]) }
280
270
  end
281
271
 
282
272
  def sanitized_freight_class(freight_class)
@@ -1,22 +1,7 @@
1
1
  module FreightKit
2
2
  class Packaging
3
3
  PALLET_TYPES = %i[crate drum pallet tote].freeze
4
- VALID_TYPES = %i[
5
- box
6
- bundle
7
- container
8
- crate
9
- cylinder
10
- drum
11
- luggage
12
- pail
13
- pallet
14
- piece
15
- roll
16
- tote
17
- truckload
18
- tote
19
- ].freeze
4
+ VALID_TYPES = %i[box bundle container crate cylinder drum luggage pail pallet piece roll tote truckload tote].freeze
20
5
 
21
6
  attr_accessor :type
22
7
 
@@ -114,14 +114,13 @@ module FreightKit
114
114
  url = build_url(action, query:)
115
115
  save_request(url)
116
116
 
117
- HTTPClient.get(url)
117
+ HTTPClient.get(url, proxy_url:)
118
118
  end
119
119
 
120
120
  def map_response_errors(response, not_found_error: DocumentNotFoundError)
121
121
  return ResponseError.new('Unknown response') if response.blank?
122
122
 
123
- webspeed_error = (response.is_a?(String) || response.is_a?(HTTPClient::Response)) &&
124
- response.include?('WebSpeed error')
123
+ webspeed_error = (response.is_a?(String) || response.is_a?(HTTPClient::Response)) && response.include?('WebSpeed error')
125
124
  return ResponseError.new('Temporary error (WebSpeed error)') if webspeed_error
126
125
 
127
126
  return if response.code == 200
@@ -132,9 +131,7 @@ module FreightKit
132
131
  def parse_amount(amount)
133
132
  negative = amount.include?('-')
134
133
 
135
- ['$', ',', '-'].each do |char|
136
- amount = amount.sub(char, '')
137
- end
134
+ ['$', ',', '-'].each { |char| amount = amount.sub(char, '') }
138
135
 
139
136
  return 0 if amount.blank?
140
137
 
@@ -220,7 +217,7 @@ module FreightKit
220
217
  end
221
218
 
222
219
  begin
223
- response = HTTPClient.get(url)
220
+ response = HTTPClient.get(url, proxy_url:)
224
221
  rescue StandardError => e
225
222
  document_response.error = e
226
223
  return document_response
@@ -283,9 +280,7 @@ module FreightKit
283
280
  if self.class.overlength_fees_require_tariff?
284
281
  cents = 0
285
282
 
286
- shipment.packages.each do |package|
287
- cents += overlength_fee(tariff, package)
288
- end
283
+ shipment.packages.each { |package| cents += overlength_fee(tariff, package) }
289
284
 
290
285
  prices << Price.new(blame: :tariff, cents:, description: 'Overlength fees') if cents.nonzero?
291
286
  end