easypost 3.5.0 → 4.1.1

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 +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