easypost 3.4.0 → 4.1.0

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 +149 -138
  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 +13 -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 +67 -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 +6 -0
  39. data/lib/easypost/tracker.rb +9 -4
  40. data/lib/easypost/user.rb +31 -10
  41. data/lib/easypost/util.rb +22 -17
  42. data/lib/easypost/version.rb +3 -1
  43. data/lib/easypost/webhook.rb +18 -12
  44. data/lib/easypost.rb +99 -107
  45. metadata +80 -22
  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,38 +1,46 @@
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
2
- def self.create(params={}, api_key=nil)
3
- response = EasyPost.make_request(:post, self.url, api_key, {self.class_name.to_sym => params})
4
- return EasyPost::Util.convert_to_easypost_object(response, api_key)
5
+ # Create a child User.
6
+ def self.create(params = {}, api_key = nil)
7
+ response = EasyPost.make_request(:post, url, api_key, { class_name.to_sym => params })
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)
16
21
  end
17
- return self
22
+ self
18
23
  end
19
24
 
25
+ # Retrieve the authenticated User.
20
26
  def self.retrieve_me
21
- self.all
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
 
31
- if api_keys.id == self.id
39
+ if api_keys.id == id
32
40
  my_api_keys = api_keys.keys
33
41
  else
34
- for child in api_keys.children
35
- if child.id == self.id
42
+ api_keys.children.each do |child|
43
+ if child.id == id
36
44
  my_api_keys = child.keys
37
45
  break
38
46
  end
@@ -41,4 +49,17 @@ class EasyPost::User < EasyPost::Resource
41
49
 
42
50
  my_api_keys
43
51
  end
52
+
53
+ # Update the Brand of a User.
54
+ def update_brand(**attrs)
55
+ brand = EasyPost::Brand.new
56
+ data = { object: 'Brand', user_id: id, **attrs }
57
+ # Add accessors manually because there's no API to retrieve a brand
58
+ brand.add_accessors(data.keys)
59
+ # Assigning values with accessors defined above
60
+ data.each do |key, val|
61
+ brand.send("#{key}=", val)
62
+ end
63
+ brand.save
64
+ end
44
65
  end
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,25 +41,25 @@ 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,
42
47
  'Report' => EasyPost::Report,
43
48
  'ScanForm' => EasyPost::ScanForm,
44
49
  'Shipment' => EasyPost::Shipment,
50
+ 'TaxIdentifier' => EasyPost::TaxIdentifier,
45
51
  'ShipmentInvoiceReport' => EasyPost::Report,
46
52
  'ShipmentReport' => EasyPost::Report,
47
53
  'Tracker' => EasyPost::Tracker,
48
54
  'TrackerReport' => EasyPost::Report,
49
55
  'User' => EasyPost::User,
50
- 'Webhook' => EasyPost::Webhook
56
+ 'Webhook' => EasyPost::Webhook,
51
57
  }
52
58
 
53
59
  prefixes = {
54
60
  'adr' => EasyPost::Address,
55
61
  'batch' => EasyPost::Batch,
62
+ 'brd' => EasyPost::Brand,
56
63
  'ca' => EasyPost::CarrierAccount,
57
64
  'cstinfo' => EasyPost::CustomsInfo,
58
65
  'cstitem' => EasyPost::CustomsItem,
@@ -65,8 +72,6 @@ module EasyPost::Util
65
72
  'pl' => EasyPost::PostageLabel,
66
73
  'plrep' => EasyPost::Report,
67
74
  'prcl' => EasyPost::Parcel,
68
- 'printer' => EasyPost::Printer,
69
- 'printjob' => EasyPost::PrintJob,
70
75
  'rate' => EasyPost::Rate,
71
76
  'refrep' => EasyPost::Report,
72
77
  'rfnd' => EasyPost::Refund,
@@ -76,33 +81,33 @@ module EasyPost::Util
76
81
  'shprep' => EasyPost::Report,
77
82
  'trk' => EasyPost::Tracker,
78
83
  'trkrep' => EasyPost::Report,
79
- 'user' => EasyPost::User
84
+ 'user' => EasyPost::User,
80
85
  }
81
86
 
82
87
  case response
83
88
  when Array
84
- return response.map { |i| convert_to_easypost_object(i, api_key, parent) }
89
+ response.map { |i| convert_to_easypost_object(i, api_key, parent) }
85
90
  when Hash
86
- if cls_name = response[:object]
91
+ if (cls_name = response[:object])
87
92
  cls = types[cls_name]
88
93
  elsif response[:id]
89
94
  if response[:id].index('_').nil?
90
95
  cls = EasyPost::EasyPostObject
91
- elsif cls_prefix = response[:id][0..response[:id].index('_')]
96
+ elsif (cls_prefix = response[:id][0..response[:id].index('_')])
92
97
  cls = prefixes[cls_prefix[0..-2]]
93
98
  end
94
99
  elsif response['id']
95
100
  if response['id'].index('_').nil?
96
101
  cls = EasyPost::EasyPostObject
97
- elsif cls_prefix = response['id'][0..response['id'].index('_')]
102
+ elsif (cls_prefix = response['id'][0..response['id'].index('_')])
98
103
  cls = prefixes[cls_prefix[0..-2]]
99
104
  end
100
105
  end
101
106
 
102
107
  cls ||= EasyPost::EasyPostObject
103
- return cls.construct_from(response, api_key, parent, name)
108
+ cls.construct_from(response, api_key, parent, name)
104
109
  else
105
- return response
110
+ response
106
111
  end
107
112
  end
108
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,148 +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/carrier_account"
16
- require "easypost/carrier_type"
17
- require "easypost/customs_info"
18
- require "easypost/customs_item"
19
- require "easypost/event"
20
- require "easypost/insurance"
21
- require "easypost/order"
22
- require "easypost/parcel"
23
- require "easypost/pickup_rate"
24
- require "easypost/pickup"
25
- require "easypost/postage_label"
26
- require "easypost/print_job"
27
- require "easypost/printer"
28
- require "easypost/rate"
29
- require "easypost/refund"
30
- require "easypost/report"
31
- require "easypost/scan_form"
32
- require "easypost/shipment"
33
- require "easypost/tracker"
34
- require "easypost/user"
35
- 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'
36
39
 
37
40
  module EasyPost
38
- @api_key = nil
39
- @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}"
40
43
 
41
- def self.api_key=(api_key)
42
- @api_key = api_key
44
+ class << self
45
+ attr_accessor :api_key, :api_base
46
+ attr_writer :default_connection
43
47
  end
44
48
 
45
- def self.api_key
46
- @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
+ }
47
56
  end
48
57
 
49
- def self.api_base=(api_base)
50
- @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
+ )
51
63
  end
52
64
 
53
- def self.api_base
54
- @api_base
65
+ def self.authorization(key)
66
+ "Basic #{Base64.strict_encode64("#{key}:")}"
55
67
  end
56
68
 
69
+ # Reset the HTTP config.
57
70
  def self.reset_http_config
58
- @http_config = {
71
+ http_config.clear
72
+ self.default_connection = nil
73
+ end
74
+
75
+ def self.default_http_config
76
+ http_config = {
59
77
  timeout: 60,
60
78
  open_timeout: 30,
61
79
  verify_ssl: OpenSSL::SSL::VERIFY_PEER,
62
80
  }
63
81
 
64
82
  ruby_version = Gem::Version.new(RUBY_VERSION)
65
- if ruby_version >= Gem::Version.new("2.5.0")
66
- @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
67
85
  else
68
- @http_config[:ssl_version] = :TLSv1_2
86
+ http_config[:ssl_version] = :TLSv1_2 # rubocop:disable Naming/VariableNumber
69
87
  end
70
88
 
71
- @http_config
89
+ http_config
72
90
  end
73
91
 
92
+ # Get the HTTP config.
74
93
  def self.http_config
75
- @http_config ||= reset_http_config
94
+ @http_config ||= default_http_config
76
95
  end
77
96
 
97
+ # Set the HTTP config.
78
98
  def self.http_config=(http_config_params)
79
99
  http_config.merge!(http_config_params)
80
- end
81
-
82
- def self.make_client(uri)
83
- client = if http_config[:proxy]
84
- proxy_uri = URI(http_config[:proxy])
85
- Net::HTTP.new(
86
- uri.host,
87
- uri.port,
88
- proxy_uri.host,
89
- proxy_uri.port,
90
- proxy_uri.user,
91
- proxy_uri.password
92
- )
93
- else
94
- Net::HTTP.new(uri.host, uri.port)
95
- end
96
- client.use_ssl = true
97
-
98
- http_config.each do |name, value|
99
- # Discrepancies between RestClient and Net::HTTP.
100
- if name == :verify_ssl
101
- name = :verify_mode
102
- elsif name == :timeout
103
- name = :read_timeout
104
- end
105
-
106
- # Handled in the creation of the client.
107
- if name == :proxy
108
- next
109
- end
110
-
111
- client.send("#{name}=", value)
112
- end
113
100
 
114
- client
101
+ self.default_connection = nil
115
102
  end
116
103
 
117
- def self.make_request(method, path, api_key=nil, body=nil)
118
- client = make_client(URI(@api_base))
119
-
120
- request = Net::HTTP.const_get(method.capitalize).new(path)
121
- if body
122
- request.body = JSON.dump(EasyPost::Util.objects_to_ids(body))
123
- end
124
-
125
- request["Content-Type"] = "application/json"
126
- request["User-Agent"] = "EasyPost/v2 RubyClient/#{VERSION} Ruby/#{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}"
127
- if api_key = api_key || @api_key
128
- request["Authorization"] = "Basic #{Base64.strict_encode64("#{api_key}:")}"
129
- 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
130
110
 
131
- 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
132
122
 
133
- if (400..599).include? response.code.to_i
134
- error = JSON.parse(response.body)["error"]
135
- 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
+ )
136
134
  end
137
135
 
138
- if response["Content-Type"].include? "application/json"
139
- JSON.parse(response.body)
140
- else
141
- response.body
142
- end
136
+ json ? JSON.parse(body) : body
143
137
  rescue JSON::ParserError
144
- raise RuntimeError.new(
145
- "Invalid response object from API, unable to decode.\n#{response.body}"
146
- )
138
+ raise "Invalid response object from API, unable to decode.\n#{body}"
147
139
  end
148
140
  end