easypost 3.5.0 → 4.1.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.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +19 -5
  3. data/.gitignore +2 -0
  4. data/.rubocop.yml +5 -0
  5. data/CHANGELOG.md +151 -141
  6. data/Gemfile +2 -0
  7. data/README.md +51 -8
  8. data/Rakefile +2 -1
  9. data/UPGRADE_GUIDE.md +62 -0
  10. data/VERSION +1 -1
  11. data/bin/easypost-irb +5 -3
  12. data/easycop.yml +180 -0
  13. data/easypost.gemspec +21 -19
  14. data/lib/easypost/address.rb +26 -26
  15. data/lib/easypost/api_key.rb +3 -0
  16. data/lib/easypost/batch.rb +31 -30
  17. data/lib/easypost/brand.rb +4 -0
  18. data/lib/easypost/carrier_account.rb +4 -0
  19. data/lib/easypost/carrier_type.rb +3 -0
  20. data/lib/easypost/connection.rb +68 -0
  21. data/lib/easypost/customs_info.rb +5 -1
  22. data/lib/easypost/customs_item.rb +5 -1
  23. data/lib/easypost/error.rb +7 -7
  24. data/lib/easypost/event.rb +5 -1
  25. data/lib/easypost/insurance.rb +4 -0
  26. data/lib/easypost/object.rb +44 -28
  27. data/lib/easypost/order.rb +15 -11
  28. data/lib/easypost/parcel.rb +7 -0
  29. data/lib/easypost/pickup.rb +15 -9
  30. data/lib/easypost/pickup_rate.rb +3 -1
  31. data/lib/easypost/postage_label.rb +3 -0
  32. data/lib/easypost/rate.rb +7 -0
  33. data/lib/easypost/refund.rb +3 -0
  34. data/lib/easypost/report.rb +9 -16
  35. data/lib/easypost/resource.rb +55 -25
  36. data/lib/easypost/scan_form.rb +8 -3
  37. data/lib/easypost/shipment.rb +47 -51
  38. data/lib/easypost/tax_identifier.rb +4 -0
  39. data/lib/easypost/tracker.rb +9 -4
  40. data/lib/easypost/user.rb +14 -5
  41. data/lib/easypost/util.rb +21 -17
  42. data/lib/easypost/version.rb +3 -1
  43. data/lib/easypost/webhook.rb +18 -12
  44. data/lib/easypost.rb +99 -109
  45. metadata +75 -19
  46. data/lib/easypost/print_job.rb +0 -2
  47. data/lib/easypost/printer.rb +0 -24
@@ -1,7 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ # A Tracker object contains all of the tracking information for a package.
1
4
  class EasyPost::Tracker < EasyPost::Resource
2
- def self.create_list(params={}, api_key=nil)
3
- url = self.url + '/create_list'
4
- response = EasyPost.make_request(:post, url, api_key, params)
5
- return true
5
+ # Create multiple Tracker objects in bulk.
6
+ def self.create_list(params = {}, api_key = nil)
7
+ url = "#{self.url}/create_list"
8
+ new_params = { 'trackers' => params }
9
+ EasyPost.make_request(:post, url, api_key, new_params)
10
+ true # This endpoint does not return a response so we return true here instead
6
11
  end
7
12
  end
data/lib/easypost/user.rb CHANGED
@@ -1,15 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ # The User object can be used to manage your own account and to create child accounts.
1
4
  class EasyPost::User < EasyPost::Resource
5
+ # Create a child User.
2
6
  def self.create(params = {}, api_key = nil)
3
- response = EasyPost.make_request(:post, url, api_key, {class_name.to_sym => params})
7
+ response = EasyPost.make_request(:post, url, api_key, { class_name.to_sym => params })
4
8
  EasyPost::Util.convert_to_easypost_object(response, api_key)
5
9
  end
6
10
 
11
+ # Save (update) a User.
7
12
  def save
8
- if @unsaved_values.length > 0
13
+ if @unsaved_values.length.positive?
9
14
  values = {}
10
15
  @unsaved_values.each { |k| values[k] = @values[k] }
11
16
 
12
- wrapped_params = {user: values}
17
+ wrapped_params = { user: values }
13
18
 
14
19
  response = EasyPost.make_request(:put, url, @api_key, wrapped_params)
15
20
  refresh_from(response, api_key)
@@ -17,14 +22,17 @@ class EasyPost::User < EasyPost::Resource
17
22
  self
18
23
  end
19
24
 
25
+ # Retrieve the authenticated User.
20
26
  def self.retrieve_me
21
27
  all
22
28
  end
23
29
 
30
+ # Retrieve a list of ApiKey objects.
24
31
  def self.all_api_keys
25
32
  EasyPost::ApiKey.all
26
33
  end
27
34
 
35
+ # Retrieve a list of ApiKey objects of a child User.
28
36
  def api_keys
29
37
  api_keys = EasyPost::User.all_api_keys
30
38
 
@@ -41,10 +49,11 @@ class EasyPost::User < EasyPost::Resource
41
49
 
42
50
  my_api_keys
43
51
  end
44
-
52
+
53
+ # Update the Brand of a User.
45
54
  def update_brand(**attrs)
46
55
  brand = EasyPost::Brand.new
47
- data = {object: "Brand", user_id: id, **attrs}
56
+ data = { object: 'Brand', user_id: id, **attrs }
48
57
  # Add accessors manually because there's no API to retrieve a brand
49
58
  brand.add_accessors(data.keys)
50
59
  # Assigning values with accessors defined above
data/lib/easypost/util.rb CHANGED
@@ -1,28 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Internal utilities helpful for this libraries operation.
1
4
  module EasyPost::Util
5
+ # Converts an object to an object ID.
2
6
  def self.objects_to_ids(obj)
3
7
  case obj
4
8
  when EasyPost::Resource
5
- return {:id => obj.id}
9
+ { id: obj.id }
6
10
  when Hash
7
11
  result = {}
8
12
  obj.each { |k, v| result[k] = objects_to_ids(v) unless v.nil? }
9
- return result
13
+ result
10
14
  when Array
11
- return obj.map { |v| objects_to_ids(v) }
15
+ obj.map { |v| objects_to_ids(v) }
12
16
  else
13
- return obj
17
+ obj
14
18
  end
15
19
  end
16
20
 
21
+ # Normalizes a list of strings.
17
22
  def self.normalize_string_list(lst)
18
23
  lst = lst.is_a?(String) ? lst.split(',') : Array(lst)
19
24
  lst.map(&:to_s).map(&:downcase).map(&:strip)
20
25
  end
21
26
 
22
- def self.convert_to_easypost_object(response, api_key, parent=nil, name=nil)
27
+ # Convert data to an EasyPost Object.
28
+ def self.convert_to_easypost_object(response, api_key, parent = nil, name = nil)
23
29
  types = {
24
30
  'Address' => EasyPost::Address,
25
31
  'Batch' => EasyPost::Batch,
32
+ 'Brand' => EasyPost::Brand,
26
33
  'CarrierAccount' => EasyPost::CarrierAccount,
27
34
  'CustomsInfo' => EasyPost::CustomsInfo,
28
35
  'CustomsItem' => EasyPost::CustomsItem,
@@ -34,8 +41,6 @@ module EasyPost::Util
34
41
  'Pickup' => EasyPost::Pickup,
35
42
  'PickupRate' => EasyPost::PickupRate,
36
43
  'PostageLabel' => EasyPost::PostageLabel,
37
- 'Printer' => EasyPost::Printer,
38
- 'PrintJob' => EasyPost::PrintJob,
39
44
  'Rate' => EasyPost::Rate,
40
45
  'Refund' => EasyPost::Refund,
41
46
  'RefundReport' => EasyPost::Report,
@@ -48,12 +53,13 @@ module EasyPost::Util
48
53
  'Tracker' => EasyPost::Tracker,
49
54
  'TrackerReport' => EasyPost::Report,
50
55
  'User' => EasyPost::User,
51
- 'Webhook' => EasyPost::Webhook
56
+ 'Webhook' => EasyPost::Webhook,
52
57
  }
53
58
 
54
59
  prefixes = {
55
60
  'adr' => EasyPost::Address,
56
61
  'batch' => EasyPost::Batch,
62
+ 'brd' => EasyPost::Brand,
57
63
  'ca' => EasyPost::CarrierAccount,
58
64
  'cstinfo' => EasyPost::CustomsInfo,
59
65
  'cstitem' => EasyPost::CustomsItem,
@@ -66,8 +72,6 @@ module EasyPost::Util
66
72
  'pl' => EasyPost::PostageLabel,
67
73
  'plrep' => EasyPost::Report,
68
74
  'prcl' => EasyPost::Parcel,
69
- 'printer' => EasyPost::Printer,
70
- 'printjob' => EasyPost::PrintJob,
71
75
  'rate' => EasyPost::Rate,
72
76
  'refrep' => EasyPost::Report,
73
77
  'rfnd' => EasyPost::Refund,
@@ -77,33 +81,33 @@ module EasyPost::Util
77
81
  'shprep' => EasyPost::Report,
78
82
  'trk' => EasyPost::Tracker,
79
83
  'trkrep' => EasyPost::Report,
80
- 'user' => EasyPost::User
84
+ 'user' => EasyPost::User,
81
85
  }
82
86
 
83
87
  case response
84
88
  when Array
85
- return response.map { |i| convert_to_easypost_object(i, api_key, parent) }
89
+ response.map { |i| convert_to_easypost_object(i, api_key, parent) }
86
90
  when Hash
87
- if cls_name = response[:object]
91
+ if (cls_name = response[:object])
88
92
  cls = types[cls_name]
89
93
  elsif response[:id]
90
94
  if response[:id].index('_').nil?
91
95
  cls = EasyPost::EasyPostObject
92
- elsif cls_prefix = response[:id][0..response[:id].index('_')]
96
+ elsif (cls_prefix = response[:id][0..response[:id].index('_')])
93
97
  cls = prefixes[cls_prefix[0..-2]]
94
98
  end
95
99
  elsif response['id']
96
100
  if response['id'].index('_').nil?
97
101
  cls = EasyPost::EasyPostObject
98
- elsif cls_prefix = response['id'][0..response['id'].index('_')]
102
+ elsif (cls_prefix = response['id'][0..response['id'].index('_')])
99
103
  cls = prefixes[cls_prefix[0..-2]]
100
104
  end
101
105
  end
102
106
 
103
107
  cls ||= EasyPost::EasyPostObject
104
- return cls.construct_from(response, api_key, parent, name)
108
+ cls.construct_from(response, api_key, parent, name)
105
109
  else
106
- return response
110
+ response
107
111
  end
108
112
  end
109
113
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module EasyPost
2
- VERSION = File.open(File.expand_path("../../../VERSION", __FILE__)).read().strip
4
+ VERSION = File.open(File.expand_path('../../VERSION', __dir__)).read.strip
3
5
  end
@@ -1,29 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Each Webhook contains the url which EasyPost will notify whenever an object in our system updates. Several types of objects are processed
4
+ # asynchronously in the EasyPost system, so whenever an object updates, an Event is sent via HTTP POST to each configured webhook URL.
1
5
  class EasyPost::Webhook < EasyPost::Resource
2
- def update(params={})
3
- # NOTE: This method is redefined here since the "url" method conflicts
4
- # with the objects field
5
- unless self.id
6
- raise EasyPost::Error.new("Could not determine which URL to request: #{self.class} instance has invalid ID: #{self.id.inspect}")
6
+ # Update a Webhook.
7
+ def update(params = {})
8
+ # NOTE: This method is redefined here since the "url" method conflicts with the objects field
9
+ unless id
10
+ raise EasyPost::Error.new("Could not determine which URL to request: #{self.class} instance has invalid ID: #{id.inspect}")
7
11
  end
12
+
8
13
  instance_url = "#{self.class.url}/#{CGI.escape(id)}"
9
14
 
10
15
  response = EasyPost.make_request(:put, instance_url, @api_key, params)
11
- self.refresh_from(response, api_key, true)
16
+ refresh_from(response, api_key, true)
12
17
 
13
- return self
18
+ self
14
19
  end
15
20
 
21
+ # Delete a Webhook.
16
22
  def delete
17
- # NOTE: This method is redefined here since the "url" method conflicts
18
- # with the objects field
19
- unless self.id
20
- raise EasyPost::Error.new("Could not determine which URL to request: #{self.class} instance has invalid ID: #{self.id.inspect}")
23
+ # NOTE: This method is redefined here since the "url" method conflicts with the objects field
24
+ unless id
25
+ raise EasyPost::Error.new("Could not determine which URL to request: #{self.class} instance has invalid ID: #{id.inspect}")
21
26
  end
27
+
22
28
  instance_url = "#{self.class.url}/#{CGI.escape(id)}"
23
29
 
24
30
  response = EasyPost.make_request(:delete, instance_url, @api_key)
25
31
  refresh_from(response, api_key)
26
32
 
27
- return self
33
+ self
28
34
  end
29
35
  end
data/lib/easypost.rb CHANGED
@@ -1,150 +1,140 @@
1
- require "base64"
2
- require "cgi"
3
- require "net/http"
1
+ # frozen_string_literal: true
4
2
 
5
- require "easypost/version"
6
- require "easypost/util"
7
- require "easypost/object"
8
- require "easypost/resource"
9
- require "easypost/error"
3
+ require 'base64'
4
+ require 'cgi'
5
+ require 'net/http'
6
+
7
+ require 'easypost/version'
8
+ require 'easypost/util'
9
+ require 'easypost/object'
10
+ require 'easypost/resource'
11
+ require 'easypost/error'
12
+ require 'easypost/connection'
10
13
 
11
14
  # Resources
12
- require "easypost/address"
13
- require "easypost/api_key"
14
- require "easypost/batch"
15
- require "easypost/brand"
16
- require "easypost/carrier_account"
17
- require "easypost/carrier_type"
18
- require "easypost/customs_info"
19
- require "easypost/customs_item"
20
- require "easypost/event"
21
- require "easypost/insurance"
22
- require "easypost/order"
23
- require "easypost/parcel"
24
- require "easypost/pickup_rate"
25
- require "easypost/pickup"
26
- require "easypost/postage_label"
27
- require "easypost/print_job"
28
- require "easypost/printer"
29
- require "easypost/rate"
30
- require "easypost/refund"
31
- require "easypost/report"
32
- require "easypost/scan_form"
33
- require "easypost/shipment"
34
- require "easypost/tax_identifier"
35
- require "easypost/tracker"
36
- require "easypost/user"
37
- require "easypost/webhook"
15
+ require 'easypost/address'
16
+ require 'easypost/api_key'
17
+ require 'easypost/batch'
18
+ require 'easypost/brand'
19
+ require 'easypost/carrier_account'
20
+ require 'easypost/carrier_type'
21
+ require 'easypost/customs_info'
22
+ require 'easypost/customs_item'
23
+ require 'easypost/event'
24
+ require 'easypost/insurance'
25
+ require 'easypost/order'
26
+ require 'easypost/parcel'
27
+ require 'easypost/pickup_rate'
28
+ require 'easypost/pickup'
29
+ require 'easypost/postage_label'
30
+ require 'easypost/rate'
31
+ require 'easypost/refund'
32
+ require 'easypost/report'
33
+ require 'easypost/scan_form'
34
+ require 'easypost/shipment'
35
+ require 'easypost/tax_identifier'
36
+ require 'easypost/tracker'
37
+ require 'easypost/user'
38
+ require 'easypost/webhook'
38
39
 
39
40
  module EasyPost
40
- @api_key = nil
41
- @api_base = "https://api.easypost.com"
41
+ DEFAULT_API_BASE = 'https://api.easypost.com'
42
+ DEFAULT_USER_AGENT = "EasyPost/v2 RubyClient/#{EasyPost::VERSION} Ruby/#{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}"
42
43
 
43
- def self.api_key=(api_key)
44
- @api_key = api_key
44
+ class << self
45
+ attr_accessor :api_key, :api_base
46
+ attr_writer :default_connection
45
47
  end
46
48
 
47
- def self.api_key
48
- @api_key
49
+ self.api_base = DEFAULT_API_BASE
50
+
51
+ def self.default_headers
52
+ @default_headers ||= {
53
+ 'Content-Type' => 'application/json',
54
+ 'User-Agent' => EasyPost::DEFAULT_USER_AGENT,
55
+ }
49
56
  end
50
57
 
51
- def self.api_base=(api_base)
52
- @api_base = api_base
58
+ def self.default_connection
59
+ @default_connection ||= EasyPost::Connection.new(
60
+ uri: URI(api_base),
61
+ config: http_config,
62
+ )
53
63
  end
54
64
 
55
- def self.api_base
56
- @api_base
65
+ def self.authorization(key)
66
+ "Basic #{Base64.strict_encode64("#{key}:")}"
57
67
  end
58
68
 
69
+ # Reset the HTTP config.
59
70
  def self.reset_http_config
60
- @http_config = {
71
+ http_config.clear
72
+ self.default_connection = nil
73
+ end
74
+
75
+ def self.default_http_config
76
+ http_config = {
61
77
  timeout: 60,
62
78
  open_timeout: 30,
63
79
  verify_ssl: OpenSSL::SSL::VERIFY_PEER,
64
80
  }
65
81
 
66
82
  ruby_version = Gem::Version.new(RUBY_VERSION)
67
- if ruby_version >= Gem::Version.new("2.5.0")
68
- @http_config[:min_version] = OpenSSL::SSL::TLS1_2_VERSION
83
+ if ruby_version >= Gem::Version.new('2.5.0')
84
+ http_config[:min_version] = OpenSSL::SSL::TLS1_2_VERSION
69
85
  else
70
- @http_config[:ssl_version] = :TLSv1_2
86
+ http_config[:ssl_version] = :TLSv1_2 # rubocop:disable Naming/VariableNumber
71
87
  end
72
88
 
73
- @http_config
89
+ http_config
74
90
  end
75
91
 
92
+ # Get the HTTP config.
76
93
  def self.http_config
77
- @http_config ||= reset_http_config
94
+ @http_config ||= default_http_config
78
95
  end
79
96
 
97
+ # Set the HTTP config.
80
98
  def self.http_config=(http_config_params)
81
99
  http_config.merge!(http_config_params)
82
- end
83
-
84
- def self.make_client(uri)
85
- client = if http_config[:proxy]
86
- proxy_uri = URI(http_config[:proxy])
87
- Net::HTTP.new(
88
- uri.host,
89
- uri.port,
90
- proxy_uri.host,
91
- proxy_uri.port,
92
- proxy_uri.user,
93
- proxy_uri.password
94
- )
95
- else
96
- Net::HTTP.new(uri.host, uri.port)
97
- end
98
- client.use_ssl = true
99
-
100
- http_config.each do |name, value|
101
- # Discrepancies between RestClient and Net::HTTP.
102
- if name == :verify_ssl
103
- name = :verify_mode
104
- elsif name == :timeout
105
- name = :read_timeout
106
- end
107
-
108
- # Handled in the creation of the client.
109
- if name == :proxy
110
- next
111
- end
112
-
113
- client.send("#{name}=", value)
114
- end
115
100
 
116
- client
101
+ self.default_connection = nil
117
102
  end
118
103
 
119
- def self.make_request(method, path, api_key=nil, body=nil)
120
- client = make_client(URI(@api_base))
121
-
122
- request = Net::HTTP.const_get(method.capitalize).new(path)
123
- if body
124
- request.body = JSON.dump(EasyPost::Util.objects_to_ids(body))
125
- end
126
-
127
- request["Content-Type"] = "application/json"
128
- request["User-Agent"] = "EasyPost/v2 RubyClient/#{VERSION} Ruby/#{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}"
129
- if api_key = api_key || @api_key
130
- request["Authorization"] = "Basic #{Base64.strict_encode64("#{api_key}:")}"
131
- end
104
+ # Create an EasyPost Client.
105
+ #
106
+ # @deprecated
107
+ def self.make_client(url)
108
+ EasyPost::Connection.new(uri: URI(url), config: http_config).create
109
+ end
132
110
 
133
- response = client.request(request)
111
+ # Make an HTTP request against the {default_connection}
112
+ #
113
+ # @param method [Symbol] the HTTP Verb (get, method, put, post, etc.)
114
+ # @param path [String] URI path of the resource
115
+ # @param requested_api_key [String] ({EasyPost.api_key}) key set Authorization header.
116
+ # @param body [Object] (nil) object to be dumped to JSON
117
+ # @raise [EasyPost::Error] if the response has a non-2xx status code
118
+ # @return [Hash] JSON object parsed from the response body
119
+ def self.make_request(method, path, api_key = nil, body = nil)
120
+ default_connection.call(method, path, api_key || EasyPost.api_key, body)
121
+ end
134
122
 
135
- if (400..599).include? response.code.to_i
136
- error = JSON.parse(response.body)["error"]
137
- raise EasyPost::Error.new(error["message"], response.code.to_i, error["code"], error["errors"], response.body)
123
+ def self.parse_response(status:, body:, json:)
124
+ if status >= 400
125
+ error = JSON.parse(body)['error']
126
+
127
+ raise EasyPost::Error.new(
128
+ error['message'],
129
+ status,
130
+ error['code'],
131
+ error['errors'],
132
+ body,
133
+ )
138
134
  end
139
135
 
140
- if response["Content-Type"].include? "application/json"
141
- JSON.parse(response.body)
142
- else
143
- response.body
144
- end
136
+ json ? JSON.parse(body) : body
145
137
  rescue JSON::ParserError
146
- raise RuntimeError.new(
147
- "Invalid response object from API, unable to decode.\n#{response.body}"
148
- )
138
+ raise "Invalid response object from API, unable to decode.\n#{body}"
149
139
  end
150
140
  end