freight_kit 0.1.18 → 0.1.20
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/Gemfile.lock +51 -52
- data/README.md +36 -0
- data/VERSION +1 -1
- data/freight_kit.gemspec +10 -7
- data/lib/freight_kit/carriers/abfs.rb +2 -2
- data/lib/freight_kit/carriers/ccyq.rb +2 -9
- data/lib/freight_kit/carriers/clni.rb +2 -2
- data/lib/freight_kit/carriers/cnwy.rb +3 -3
- data/lib/freight_kit/carriers/dphe.rb +7 -9
- data/lib/freight_kit/carriers/drrq.rb +18 -18
- data/lib/freight_kit/carriers/fwda.rb +3 -3
- data/lib/freight_kit/carriers/otcl.rb +4 -12
- data/lib/freight_kit/carriers/saia.rb +4 -6
- data/lib/freight_kit/carriers/sefl.rb +2 -2
- data/lib/freight_kit/carriers/tqyl.rb +1 -3
- data/lib/freight_kit/carriers/wrds.rb +3 -3
- data/lib/freight_kit/carriers.rb +24 -25
- data/lib/freight_kit/errors/response_error.rb +1 -1
- data/lib/freight_kit/http_client/cache_store.rb +61 -0
- data/lib/freight_kit/http_client/log_device.rb +15 -0
- data/lib/freight_kit/http_client/response.rb +40 -0
- data/lib/freight_kit/http_client/revalidation.rb +27 -0
- data/lib/freight_kit/http_client/safe_retries.rb +25 -0
- data/lib/freight_kit/http_client/zstd.rb +30 -0
- data/lib/freight_kit/http_client.rb +80 -0
- data/lib/freight_kit/package.rb +1 -1
- data/lib/freight_kit/package_item.rb +1 -1
- data/lib/freight_kit/platforms/carrier_logistics.rb +3 -3
- data/lib/freight_kit/platforms/next.rb +2 -2
- data/lib/freight_kit/platforms/the_great_information_factory.rb +1 -2
- data/lib/freight_kit.rb +12 -21
- metadata +104 -43
- data/lib/freight_kit/api_clients.rb +0 -3
- data/lib/freight_kit/helpers.rb +0 -6
- data/lib/freight_kit/platforms.rb +0 -5
|
@@ -42,9 +42,9 @@ module FreightKit
|
|
|
42
42
|
url = request_url(action)
|
|
43
43
|
|
|
44
44
|
response = if @conf.dig(:api, :methods, action) == :post
|
|
45
|
-
options[:params].blank? ?
|
|
45
|
+
options[:params].blank? ? HTTPClient.post(url) : HTTPClient.post(url, query: options[:params])
|
|
46
46
|
else
|
|
47
|
-
|
|
47
|
+
HTTPClient.get(url)
|
|
48
48
|
end
|
|
49
49
|
|
|
50
50
|
response.parsed_response if response&.parsed_response
|
|
@@ -60,7 +60,7 @@ module FreightKit
|
|
|
60
60
|
document_response = DocumentResponse.new(request: URI.parse(url))
|
|
61
61
|
|
|
62
62
|
begin
|
|
63
|
-
response =
|
|
63
|
+
response = HTTPClient.get(url)
|
|
64
64
|
rescue StandardError => e
|
|
65
65
|
document_response.error = e
|
|
66
66
|
return document_response
|
data/lib/freight_kit/carriers.rb
CHANGED
|
@@ -8,8 +8,7 @@ module FreightKit
|
|
|
8
8
|
|
|
9
9
|
@registered = []
|
|
10
10
|
|
|
11
|
-
def register(class_name
|
|
12
|
-
FreightKit.autoload(class_name, autoload_require)
|
|
11
|
+
def register(class_name)
|
|
13
12
|
registered << class_name
|
|
14
13
|
end
|
|
15
14
|
|
|
@@ -23,28 +22,28 @@ module FreightKit
|
|
|
23
22
|
end
|
|
24
23
|
end
|
|
25
24
|
|
|
26
|
-
FreightKit::Carriers.register(:ABFS
|
|
27
|
-
FreightKit::Carriers.register(:BTVP
|
|
28
|
-
FreightKit::Carriers.register(:CCYQ
|
|
29
|
-
FreightKit::Carriers.register(:CLNI
|
|
30
|
-
FreightKit::Carriers.register(:CNWY
|
|
31
|
-
FreightKit::Carriers.register(:DLDS
|
|
32
|
-
FreightKit::Carriers.register(:DPHE
|
|
33
|
-
FreightKit::Carriers.register(:DRRQ
|
|
34
|
-
FreightKit::Carriers.register(:FWDA
|
|
35
|
-
FreightKit::Carriers.register(:MTVL
|
|
36
|
-
FreightKit::Carriers.register(:NUMK
|
|
37
|
-
FreightKit::Carriers.register(:OTCL
|
|
38
|
-
FreightKit::Carriers.register(:PENS
|
|
39
|
-
FreightKit::Carriers.register(:RDFS
|
|
40
|
-
FreightKit::Carriers.register(:SAIA
|
|
41
|
-
FreightKit::Carriers.register(:SEFL
|
|
42
|
-
FreightKit::Carriers.register(:TQYL
|
|
43
|
-
FreightKit::Carriers.register(:WRDS
|
|
25
|
+
FreightKit::Carriers.register(:ABFS)
|
|
26
|
+
FreightKit::Carriers.register(:BTVP)
|
|
27
|
+
FreightKit::Carriers.register(:CCYQ)
|
|
28
|
+
FreightKit::Carriers.register(:CLNI)
|
|
29
|
+
FreightKit::Carriers.register(:CNWY)
|
|
30
|
+
FreightKit::Carriers.register(:DLDS)
|
|
31
|
+
FreightKit::Carriers.register(:DPHE)
|
|
32
|
+
FreightKit::Carriers.register(:DRRQ)
|
|
33
|
+
FreightKit::Carriers.register(:FWDA)
|
|
34
|
+
FreightKit::Carriers.register(:MTVL)
|
|
35
|
+
FreightKit::Carriers.register(:NUMK)
|
|
36
|
+
FreightKit::Carriers.register(:OTCL)
|
|
37
|
+
FreightKit::Carriers.register(:PENS)
|
|
38
|
+
FreightKit::Carriers.register(:RDFS)
|
|
39
|
+
FreightKit::Carriers.register(:SAIA)
|
|
40
|
+
FreightKit::Carriers.register(:SEFL)
|
|
41
|
+
FreightKit::Carriers.register(:TQYL)
|
|
42
|
+
FreightKit::Carriers.register(:WRDS)
|
|
44
43
|
|
|
45
44
|
# Based on Platform
|
|
46
|
-
FreightKit::Carriers.register(:CTBV
|
|
47
|
-
FreightKit::Carriers.register(:DCHA
|
|
48
|
-
FreightKit::Carriers.register(:JFJTransportation
|
|
49
|
-
FreightKit::Carriers.register(:FCSY
|
|
50
|
-
FreightKit::Carriers.register(:TOTL
|
|
45
|
+
FreightKit::Carriers.register(:CTBV)
|
|
46
|
+
FreightKit::Carriers.register(:DCHA)
|
|
47
|
+
FreightKit::Carriers.register(:JFJTransportation)
|
|
48
|
+
FreightKit::Carriers.register(:FCSY)
|
|
49
|
+
FreightKit::Carriers.register(:TOTL)
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module FreightKit
|
|
4
|
+
class HTTPClient
|
|
5
|
+
# Adapts an ActiveSupport::Cache::Store-like backend (#read, #write, #delete_matched)
|
|
6
|
+
# to the HTTPX cache store interface (#get, #set, #clear).
|
|
7
|
+
#
|
|
8
|
+
# HTTPX responses hold sockets and options, so only status, headers and body are stored.
|
|
9
|
+
class CacheStore
|
|
10
|
+
NAMESPACE = 'freight_kit/http_client'
|
|
11
|
+
VALIDATOR_HEADERS = ['if-modified-since', 'if-none-match'].freeze
|
|
12
|
+
|
|
13
|
+
def initialize(backend) = @backend = backend
|
|
14
|
+
|
|
15
|
+
# RedisCacheStore only supports glob strings, the other ActiveSupport stores only regular expressions
|
|
16
|
+
def clear
|
|
17
|
+
@backend.delete_matched(%r{\A#{Regexp.escape(NAMESPACE)}/})
|
|
18
|
+
rescue ArgumentError
|
|
19
|
+
@backend.delete_matched("#{NAMESPACE}/*")
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def get(request)
|
|
23
|
+
entry = @backend.read(key(request))
|
|
24
|
+
return if entry.blank?
|
|
25
|
+
|
|
26
|
+
response = request.options.response_class.new(request, entry[:status], entry[:version], entry[:headers])
|
|
27
|
+
response.finish!
|
|
28
|
+
response.mark_as_cached!
|
|
29
|
+
response.body.write(entry[:body])
|
|
30
|
+
response.body.rewind
|
|
31
|
+
response
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def set(request, response)
|
|
35
|
+
response.body.rewind
|
|
36
|
+
|
|
37
|
+
@backend.write(
|
|
38
|
+
key(request),
|
|
39
|
+
{
|
|
40
|
+
body: response.body.to_s,
|
|
41
|
+
headers: response.headers.to_h,
|
|
42
|
+
status: response.status,
|
|
43
|
+
version: response.version
|
|
44
|
+
},
|
|
45
|
+
)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
# Unlike HTTPX's default key, every request header (e.g. Authorization, API keys) is
|
|
51
|
+
# part of the key so that responses are never shared across credentials.
|
|
52
|
+
# Query parameters given as `params:` are only part of the request path, not its URI.
|
|
53
|
+
def key(request)
|
|
54
|
+
headers = request.headers.to_a.reject { |field, _| VALIDATOR_HEADERS.include?(field) }.sort
|
|
55
|
+
digest = Digest::SHA256.hexdigest([request.verb, request.uri.merge(request.path).to_s, headers].to_json)
|
|
56
|
+
|
|
57
|
+
"#{NAMESPACE}/#{digest}"
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module FreightKit
|
|
4
|
+
class HTTPClient
|
|
5
|
+
# HTTPX writes debug output to an IO-like object (#<<); this forwards it to a logger at debug level.
|
|
6
|
+
class LogDevice
|
|
7
|
+
def initialize(logger) = @logger = logger
|
|
8
|
+
|
|
9
|
+
def <<(message)
|
|
10
|
+
@logger.debug(message.chomp)
|
|
11
|
+
self
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module FreightKit
|
|
4
|
+
class HTTPClient
|
|
5
|
+
# Like HTTParty::Response, unknown methods (e.g. #dig, #[]) are delegated to the parsed body.
|
|
6
|
+
class Response
|
|
7
|
+
JSON_CONTENT_TYPES = ['application/hal+json', 'application/json', 'application/vnd.api+json', 'text/json'].freeze
|
|
8
|
+
XML_CONTENT_TYPES = ['application/xml', 'text/xml'].freeze
|
|
9
|
+
|
|
10
|
+
attr_reader :body, :code, :content_type, :headers
|
|
11
|
+
|
|
12
|
+
# Delegation alone would make empty bodies (parsed as nil) look present
|
|
13
|
+
delegate :blank?, to: :parsed_response
|
|
14
|
+
delegate_missing_to :parsed_response
|
|
15
|
+
|
|
16
|
+
def initialize(response)
|
|
17
|
+
@body = response.body.to_s
|
|
18
|
+
@code = response.status
|
|
19
|
+
@content_type = response.content_type.mime_type
|
|
20
|
+
@headers = response.headers
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def parsed_response
|
|
24
|
+
return @parsed_response if defined?(@parsed_response)
|
|
25
|
+
|
|
26
|
+
@parsed_response = if body.strip.empty? || body == 'null'
|
|
27
|
+
nil
|
|
28
|
+
elsif JSON_CONTENT_TYPES.include?(content_type)
|
|
29
|
+
JSON.parse(body, allow_nan: true)
|
|
30
|
+
elsif XML_CONTENT_TYPES.include?(content_type)
|
|
31
|
+
MultiXML.parse(body)
|
|
32
|
+
else
|
|
33
|
+
body
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def to_s = body
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module FreightKit
|
|
4
|
+
class HTTPClient
|
|
5
|
+
# HTTPX plugin making revalidated (304 Not Modified) responses look like the cached
|
|
6
|
+
# response they were revalidated against, so callers checking for 200 keep working.
|
|
7
|
+
#
|
|
8
|
+
# HTTPX renews freshness by mutating the cached response in memory, which serializing
|
|
9
|
+
# cache stores never see, so the refreshed response is stored again.
|
|
10
|
+
module Revalidation
|
|
11
|
+
module ResponseMethods
|
|
12
|
+
def copy_from_cached!
|
|
13
|
+
super
|
|
14
|
+
return unless (cached_response = @request.cached_response)
|
|
15
|
+
|
|
16
|
+
@status = cached_response.status
|
|
17
|
+
store = @request.options.response_cache_store
|
|
18
|
+
store.set(@request, self) if HTTPX::Plugins::ResponseCache.cacheable_response?(self)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
class << self
|
|
23
|
+
def load_dependencies(klass) = klass.plugin(:response_cache)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module FreightKit
|
|
4
|
+
class HTTPClient
|
|
5
|
+
# HTTPX plugin keeping connections open (HTTPX's persistent plugin) without risking duplicate
|
|
6
|
+
# rates, pickups or tenders.
|
|
7
|
+
#
|
|
8
|
+
# The persistent plugin retries any request failing with a connection error, but a POST may
|
|
9
|
+
# already have been processed by then, so only idempotent requests are retried.
|
|
10
|
+
# Streams refused by an HTTP/2 GOAWAY are resent by HTTPX itself, independently of retries.
|
|
11
|
+
module SafeRetries
|
|
12
|
+
IDEMPOTENT_METHODS = ['DELETE', 'GET', 'HEAD', 'OPTIONS', 'PUT'].freeze
|
|
13
|
+
|
|
14
|
+
module InstanceMethods
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
def retryable_request?(request, *) = super && IDEMPOTENT_METHODS.include?(request.verb)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
class << self
|
|
21
|
+
def load_dependencies(klass) = klass.plugin(:persistent)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module FreightKit
|
|
4
|
+
class HTTPClient
|
|
5
|
+
# HTTPX plugin decoding Zstandard ("zstd") response bodies, modeled on HTTPX's brotli plugin.
|
|
6
|
+
module Zstd
|
|
7
|
+
class Inflater
|
|
8
|
+
def initialize = @inflater = ::Zstd::StreamingDecompress.new
|
|
9
|
+
|
|
10
|
+
def call(chunk) = @inflater.decompress(chunk)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
module ResponseBodyClassMethods
|
|
14
|
+
def initialize_inflater_by_encoding(encoding, response, **) = encoding == 'zstd' ? Inflater.new : super
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
class << self
|
|
18
|
+
def load_dependencies(*) = require('zstd-ruby')
|
|
19
|
+
|
|
20
|
+
def extra_options(options)
|
|
21
|
+
supported_compression_formats = (['zstd'] + options.supported_compression_formats).freeze
|
|
22
|
+
|
|
23
|
+
headers = options.headers.merge('accept-encoding' => supported_compression_formats)
|
|
24
|
+
|
|
25
|
+
options.merge(headers: options.headers_class.new(headers), supported_compression_formats:)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module FreightKit
|
|
4
|
+
# Thin wrapper around HTTPX that negotiates Brotli/Zstandard compression, keeps connections
|
|
5
|
+
# open between requests and revalidates cached responses with conditional requests (ETag/Last-Modified).
|
|
6
|
+
#
|
|
7
|
+
# The cache and logger are hookable; they default to the host Rails/Hanami app's logger:
|
|
8
|
+
#
|
|
9
|
+
# FreightKit::HTTPClient.cache = Rails.cache
|
|
10
|
+
# FreightKit::HTTPClient.logger = Rails.logger
|
|
11
|
+
class HTTPClient
|
|
12
|
+
@sessions = Concurrent::Map.new
|
|
13
|
+
|
|
14
|
+
class << self
|
|
15
|
+
def cache = @cache ||= ActiveSupport::Cache::MemoryStore.new
|
|
16
|
+
|
|
17
|
+
def cache=(store)
|
|
18
|
+
@cache = store
|
|
19
|
+
@sessions.clear
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def logger = @logger || framework_logger || ::Logger.new($stdout, level: ENV.fetch('LOG_LEVEL', 'info'))
|
|
23
|
+
|
|
24
|
+
def logger=(logger)
|
|
25
|
+
@logger = logger
|
|
26
|
+
@sessions.clear
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def get(url, **) = request(:get, url, **)
|
|
30
|
+
|
|
31
|
+
def post(url, **) = request(:post, url, **)
|
|
32
|
+
|
|
33
|
+
def request(verb, url, body: nil, headers: {}, proxy: nil, query: nil)
|
|
34
|
+
options = { headers: }
|
|
35
|
+
options[:params] = query if query.present?
|
|
36
|
+
|
|
37
|
+
# Like HTTParty, hashes are form-encoded regardless of the Content-Type header
|
|
38
|
+
if body.is_a?(Hash)
|
|
39
|
+
options[:form] = body if body.present?
|
|
40
|
+
elsif body.present?
|
|
41
|
+
options[:body] = body
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
response = session(proxy.presence).request(verb.to_s.upcase, url, **options)
|
|
45
|
+
raise response.error if response.is_a?(HTTPX::ErrorResponse)
|
|
46
|
+
|
|
47
|
+
Response.new(response)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
def framework_logger
|
|
53
|
+
if defined?(::Rails) && ::Rails.respond_to?(:logger)
|
|
54
|
+
::Rails.logger
|
|
55
|
+
elsif defined?(::Hanami) && ::Hanami.respond_to?(:app?) && ::Hanami.app?
|
|
56
|
+
::Hanami.app['logger']
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Sessions own their connection pool, so they are built once (per proxy) and reused
|
|
61
|
+
def session(proxy)
|
|
62
|
+
@sessions.compute_if_absent(proxy) do
|
|
63
|
+
session = HTTPX
|
|
64
|
+
.plugin(:brotli)
|
|
65
|
+
.plugin(Zstd)
|
|
66
|
+
.plugin(:follow_redirects)
|
|
67
|
+
.plugin(:response_cache, response_cache_store: CacheStore.new(cache))
|
|
68
|
+
.plugin(Revalidation)
|
|
69
|
+
.plugin(SafeRetries)
|
|
70
|
+
.with(close_on_fork: true)
|
|
71
|
+
|
|
72
|
+
logger = self.logger
|
|
73
|
+
session = session.with(debug: LogDevice.new(logger), debug_level: 2) if logger.level <= ::Logger::DEBUG
|
|
74
|
+
|
|
75
|
+
proxy ? session.plugin(:proxy).with(proxy: { uri: proxy }) : session
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
data/lib/freight_kit/package.rb
CHANGED
|
@@ -304,7 +304,7 @@ module FreightKit # :nodoc:
|
|
|
304
304
|
weight
|
|
305
305
|
when :volumetric, :dimensional
|
|
306
306
|
@volumetric_weight ||= begin
|
|
307
|
-
m = Measured::Weight.new(
|
|
307
|
+
m = Measured::Weight.new(centimetres(:box_volume) / 6.0, :grams)
|
|
308
308
|
@weight_unit_system == :imperial ? m.convert_to(:oz) : m
|
|
309
309
|
end
|
|
310
310
|
when :billable
|
|
@@ -31,7 +31,7 @@ module FreightKit # :nodoc:
|
|
|
31
31
|
@weight
|
|
32
32
|
when :volumetric, :dimensional
|
|
33
33
|
@volumetric_weight ||= begin
|
|
34
|
-
m = Measured::Weight.new(
|
|
34
|
+
m = Measured::Weight.new(centimetres(:box_volume) / 6.0, :grams)
|
|
35
35
|
@unit_system == :imperial ? m.in_ounces : m
|
|
36
36
|
end
|
|
37
37
|
when :billable
|
|
@@ -58,13 +58,13 @@ module FreightKit
|
|
|
58
58
|
url = build_url(action, query:)
|
|
59
59
|
save_request(url)
|
|
60
60
|
|
|
61
|
-
|
|
61
|
+
HTTPClient.get(url)
|
|
62
62
|
end
|
|
63
63
|
|
|
64
64
|
def map_response_errors(response, not_found_error: DocumentNotFoundError)
|
|
65
65
|
return ResponseError.new('Unknown response') if response.blank?
|
|
66
66
|
|
|
67
|
-
webspeed_error = (response.is_a?(String) || response.is_a?(
|
|
67
|
+
webspeed_error = (response.is_a?(String) || response.is_a?(HTTPClient::Response)) &&
|
|
68
68
|
response.include?('WebSpeed error')
|
|
69
69
|
return ResponseError.new('Temporary error (WebSpeed error)') if webspeed_error
|
|
70
70
|
|
|
@@ -99,7 +99,7 @@ module FreightKit
|
|
|
99
99
|
end
|
|
100
100
|
|
|
101
101
|
begin
|
|
102
|
-
response =
|
|
102
|
+
response = HTTPClient.get(url)
|
|
103
103
|
rescue StandardError => e
|
|
104
104
|
document_response.error = e
|
|
105
105
|
return document_response
|
|
@@ -66,9 +66,9 @@ module FreightKit
|
|
|
66
66
|
|
|
67
67
|
response = case method
|
|
68
68
|
when :post
|
|
69
|
-
|
|
69
|
+
HTTPClient.post(url, headers:, body:)
|
|
70
70
|
else
|
|
71
|
-
|
|
71
|
+
HTTPClient.get(url, headers:)
|
|
72
72
|
end
|
|
73
73
|
|
|
74
74
|
JSON.parse(response.body) if response&.body
|
|
@@ -447,7 +447,6 @@ module FreightKit
|
|
|
447
447
|
service:,
|
|
448
448
|
shipment:
|
|
449
449
|
)
|
|
450
|
-
|
|
451
450
|
shipper_phone = shipment.origin.contact.phone.gsub(/\s+/, '').gsub(/[()-+.]/, '')
|
|
452
451
|
shipper_phone = shipper_phone[1..] if shipper_phone.length == 11
|
|
453
452
|
|
|
@@ -499,7 +498,7 @@ module FreightKit
|
|
|
499
498
|
|
|
500
499
|
url = [base_url, query_parameter].join
|
|
501
500
|
|
|
502
|
-
response =
|
|
501
|
+
response = HTTPClient.get(url)
|
|
503
502
|
|
|
504
503
|
image = response.deep_symbolize_keys.dig(:ImageRequest, :Image)
|
|
505
504
|
image = image.last if image.is_a?(Array)
|
data/lib/freight_kit.rb
CHANGED
|
@@ -4,9 +4,10 @@ require 'active_model'
|
|
|
4
4
|
require 'active_support/all'
|
|
5
5
|
require 'active_utils'
|
|
6
6
|
require 'business_time'
|
|
7
|
-
require '
|
|
7
|
+
require 'httpx'
|
|
8
8
|
require 'measured'
|
|
9
9
|
require 'mimemagic'
|
|
10
|
+
require 'multi_xml'
|
|
10
11
|
require 'nokogiri'
|
|
11
12
|
require 'open-uri'
|
|
12
13
|
require 'place_kit'
|
|
@@ -20,6 +21,11 @@ module FreightKit
|
|
|
20
21
|
|
|
21
22
|
class Inflector < Zeitwerk::Inflector
|
|
22
23
|
def camelize(basename, abspath)
|
|
24
|
+
# Carriers are named after their four-letter SCAC (e.g. abfs.rb => ABFS)
|
|
25
|
+
if File.basename(File.dirname(abspath)) == 'carriers' && basename.match?(/\A[a-z]{4}\z/)
|
|
26
|
+
return basename.upcase
|
|
27
|
+
end
|
|
28
|
+
|
|
23
29
|
if basename =~ /\Ahttp_(.*)/
|
|
24
30
|
return "HTTP#{super(::Regexp.last_match(1), abspath)}"
|
|
25
31
|
end
|
|
@@ -31,29 +37,14 @@ end
|
|
|
31
37
|
|
|
32
38
|
loader = Zeitwerk::Loader.for_gem
|
|
33
39
|
|
|
40
|
+
loader.collapse("#{__dir__}/freight_kit/api_clients")
|
|
41
|
+
loader.collapse("#{__dir__}/freight_kit/carriers")
|
|
34
42
|
loader.collapse("#{__dir__}/freight_kit/errors")
|
|
43
|
+
loader.collapse("#{__dir__}/freight_kit/helpers")
|
|
35
44
|
loader.collapse("#{__dir__}/freight_kit/models")
|
|
36
|
-
|
|
37
|
-
# Carriers, platforms, helpers, and api_clients define top-level constants
|
|
38
|
-
# under FreightKit:: (e.g. FreightKit::ABFS, FreightKit::Rateable) rather
|
|
39
|
-
# than nested under the directory name, so they're loaded manually via the
|
|
40
|
-
# explicit requires below.
|
|
41
|
-
loader.ignore("#{__dir__}/freight_kit/api_clients.rb")
|
|
42
|
-
loader.ignore("#{__dir__}/freight_kit/api_clients")
|
|
43
|
-
loader.ignore("#{__dir__}/freight_kit/carriers.rb")
|
|
44
|
-
loader.ignore("#{__dir__}/freight_kit/carriers")
|
|
45
|
-
loader.ignore("#{__dir__}/freight_kit/helpers.rb")
|
|
46
|
-
loader.ignore("#{__dir__}/freight_kit/helpers")
|
|
47
|
-
loader.ignore("#{__dir__}/freight_kit/platforms.rb")
|
|
48
|
-
loader.ignore("#{__dir__}/freight_kit/platforms")
|
|
45
|
+
loader.collapse("#{__dir__}/freight_kit/platforms")
|
|
49
46
|
|
|
50
47
|
loader.inflector = FreightKit::Inflector.new
|
|
48
|
+
loader.inflector.inflect('jfj_transportation' => 'JFJTransportation')
|
|
51
49
|
|
|
52
50
|
loader.setup
|
|
53
|
-
|
|
54
|
-
require 'rmagick'
|
|
55
|
-
|
|
56
|
-
require 'freight_kit/api_clients'
|
|
57
|
-
require 'freight_kit/helpers'
|
|
58
|
-
require 'freight_kit/platforms'
|
|
59
|
-
require 'freight_kit/carriers'
|