easypost 3.4.0 → 4.1.0

Sign up to get free protection for your applications and to get access to all the features.
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