easypost 5.0.1 → 5.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: be1297ea7c20b21f3f959cb281001d7435902ede623ca47ac981dba2b19309c6
4
- data.tar.gz: 73e65fb3a6dc6a00635d0ae1f4351edcdaf427d5d29c7cf8254f43a770208033
3
+ metadata.gz: 05dcc6dfaedaea7f8e6bbe28ab7bf510db31211cf664fcef76b42736929eeda6
4
+ data.tar.gz: 4c70e841f3fc6c6542928cfed6bbb848f53200e80a77157cabcfd42438a72d3c
5
5
  SHA512:
6
- metadata.gz: 99d4ded392e0e2b5a3a30c3eb58c9b9dff59997d479ae0174fa59a6b0d8dec1260f3861fd3d3a965545fc910c48cd137ccd1b24704db0f56d0287e15890c4f94
7
- data.tar.gz: c31978176d4f25898021b9306180171e86826e48409de7a007cd0aeff371e528dfe8b690963c9cc97df3288ba61db27658b8780add0e29943db13a99d095f6df
6
+ metadata.gz: 85306184880d3bd91c1232c7e5a360f21755de6889f075aa0155b91b1a8601abda4951ff4f9ac4f879a7159da805b40093e1f2625274606773b4feb868ec2f0b
7
+ data.tar.gz: 88d77f2ec9743baaecdeba5556ff95161aa1817384043132bfb8a1334bb03adab3699f4375d37d75625a86bb41161e082c3dc0d8d40459c32a040cf138021510
@@ -48,8 +48,6 @@ jobs:
48
48
  run: make install-styleguide
49
49
  - name: Lint Project
50
50
  run: make lint
51
- - name: Run security analysis
52
- run: make scan
53
51
  docs:
54
52
  if: github.ref == 'refs/heads/master'
55
53
  runs-on: ubuntu-latest
data/.gitignore CHANGED
@@ -25,4 +25,15 @@ test/version_tmp
25
25
  tmp
26
26
  vendor/
27
27
  /easycop.yml
28
- /.rubocop.yml
28
+ /.rubocop.yml
29
+ [._]*.s[a-v][a-z]
30
+ [._]*.sw[a-p]
31
+ [._]s[a-rt-v][a-z]
32
+ [._]ss[a-gi-z]
33
+ [._]sw[a-p]
34
+ Session.vim
35
+ Sessionx.vim
36
+ .netrwhist
37
+ *~
38
+ tags
39
+ [._]*.un~
data/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## v5.1.1 (2023-09-05)
4
+
5
+ - Fix endpoint for creating a FedEx Smartpost carrier account
6
+
7
+ ## v5.1.0 (2023-07-28)
8
+
9
+ - Adds hooks to introspect the request and response of API calls (see `HTTP Hooks` section in the README for more details)
10
+ - Maps 400 status code responses to the new `BadRequestError` class
11
+
3
12
  ## v5.0.1 (2023-06-20)
4
13
 
5
14
  - Fixes a bug where the params of a `customs_info` on create weren't wrapped properly which led to an empty set of `customs_items`
data/Makefile CHANGED
@@ -20,10 +20,6 @@ coverage:
20
20
  docs:
21
21
  bundle exec rdoc lib -o docs --title "EasyPost Ruby Docs"
22
22
 
23
- ## format - Fix Rubocop errors
24
- format:
25
- bundle exec rubocop -a
26
-
27
23
  ## install-styleguide - Import the style guides (Unix only)
28
24
  install-styleguide: | update-examples-submodule
29
25
  sh examples/symlink_directory_files.sh examples/style_guides/ruby .
@@ -32,14 +28,11 @@ install-styleguide: | update-examples-submodule
32
28
  install: | update-examples-submodule
33
29
  bundle install
34
30
 
35
- ## update-examples-submodule - Update the examples submodule
36
- update-examples-submodule:
37
- git submodule init
38
- git submodule update --remote
39
-
40
31
  ## lint - Lint the project
41
- lint:
42
- bundle exec rubocop
32
+ lint: rubocop scan
33
+
34
+ ## lint-fix - Fix Rubocop errors
35
+ lint-fix: rubocop-fix
43
36
 
44
37
  ## publish - Publishes the built gem to Rubygems
45
38
  publish:
@@ -50,6 +43,14 @@ publish:
50
43
  release:
51
44
  gh release create ${tag} dist/*
52
45
 
46
+ ## rubocop - lints the project with rubocop
47
+ rubocop:
48
+ bundle exec rubocop
49
+
50
+ ## rubocop-fix - fix rubocop errors
51
+ rubocop-fix:
52
+ bundle exec rubocop -a
53
+
53
54
  ## scan - Runs security analysis on the project with Brakeman
54
55
  scan:
55
56
  bundle exec brakeman lib --force
@@ -61,4 +62,9 @@ test:
61
62
  ## update - Updates dependencies
62
63
  update: | update-examples-submodule
63
64
 
64
- .PHONY: help build clean coverage docs format install install-styleguide lint publish release scan test update update-examples-submodule
65
+ ## update-examples-submodule - Update the examples submodule
66
+ update-examples-submodule:
67
+ git submodule init
68
+ git submodule update --remote
69
+
70
+ .PHONY: help build clean coverage docs install install-styleguide lint lint-fix publish release rubocop rubocop-fix scan test update update-examples-submodule
data/README.md CHANGED
@@ -61,6 +61,7 @@ puts bought_shipment
61
61
  ### Custom Connections
62
62
 
63
63
  Pass in a lambda function as `custom_client_exec` when initializing a client that responds to `call(method, uri, headers, open_timeout, read_timeout, body = nil)` where:
64
+
64
65
  - `uri` is the fully-qualified URL of the EasyPost endpoint, including query parameters (`Uri` object)
65
66
  - `method` is the lowercase name of the HTTP method being used for the request (e.g. `:get`, `:post`, `:put`, `:delete`)
66
67
  - `headers` is a hash with all headers needed for the request pre-populated, including authorization (`Hash` object)
@@ -69,7 +70,8 @@ Pass in a lambda function as `custom_client_exec` when initializing a client tha
69
70
  - `body` is a string of the body data to be included in the request, or nil (e.g. GET or DELETE request) (string or `nil`)
70
71
 
71
72
  The lambda function should return an object with `code` and `body` attributes, where:
72
- - `code` is the HTTP response status code (integer)
73
+
74
+ - `code` is the HTTP response status code (integer)
73
75
  - `body` is the response body (string)
74
76
 
75
77
  #### Faraday
@@ -116,6 +118,49 @@ my_client = described_class.new(
116
118
  )
117
119
  ```
118
120
 
121
+ ### HTTP Hooks
122
+
123
+ Users can audit HTTP requests and responses being made by the library by subscribing to request and response events. To do so, pass a block to the `subscribe_request_hook` and `subscribe_response_hook` methods of an instance of `EasyPost::Client`:
124
+
125
+ ```ruby
126
+ require 'easypost'
127
+
128
+ client = EasyPost::Client.new(api_key: ENV['EASYPOST_API_KEY'])
129
+
130
+ # Returns a randomly-generated symbol which you can use to later unsubscribe the request hook
131
+ client.subscribe_request_hook do |request_data|
132
+ # Your code goes here
133
+ end
134
+ # Returns a randomly-generated symbol which you can use to later unsubscribe the response hook
135
+ client.subscribe_response_hook do |response_data|
136
+ # Your code goes here
137
+ end
138
+ ```
139
+
140
+ You can also name your hook subscriptions by providing an optional parameter to the methods above:
141
+
142
+ ```ruby
143
+ require 'easypost'
144
+
145
+ client = EasyPost::Client.new(api_key: ENV['EASYPOST_API_KEY'])
146
+
147
+ request_hook = client.subscribe_request_hook(:my_request_hook) do |request_data|
148
+ # Your code goes here
149
+ end
150
+ response_hook = client.subscribe_response_hook(:my_response_hook) do |response_data|
151
+ # Your code goes here
152
+ end
153
+
154
+ puts request_hook # :my_request_hook
155
+ puts response_hook # :my_response_hook
156
+ ```
157
+
158
+ Keep in mind that subscribing a hook with the same name of an existing hook will replace the existing hook with the new one. A request hook and a response hook can share the same name.
159
+
160
+ #### Custom HTTP Connections with HTTP Hooks
161
+
162
+ If you're using a custom HTTP connection, keep in mind that the `response_data` parameter that a response hook receives *will not be hydrated* with all the response data. You will have to inspect the `client_response_object` property in `response_data` to inspect the response code, response headers and response body.
163
+
119
164
  ## Documentation
120
165
 
121
166
  API documentation can be found at: <https://easypost.com/docs/api>.
@@ -141,9 +186,7 @@ make install-style
141
186
 
142
187
  # Lint project
143
188
  make lint
144
-
145
- # Fix linting errors
146
- make format
189
+ make lint-fix
147
190
 
148
191
  # Run tests (coverage is generated on a successful test suite run)
149
192
  EASYPOST_TEST_API_KEY=123... EASYPOST_PROD_API_KEY=123... make test
@@ -155,8 +198,7 @@ make scan
155
198
  make docs
156
199
 
157
200
  # Update submodules
158
- git submodule init
159
- git submodule update --remote
201
+ make update-examples-submodule
160
202
  ```
161
203
 
162
204
  ### Testing
data/VERSION CHANGED
@@ -1 +1 @@
1
- 5.0.1
1
+ 5.1.1
@@ -4,6 +4,7 @@ require_relative 'services'
4
4
  require_relative 'http_client'
5
5
  require_relative 'internal_utilities'
6
6
  require 'json'
7
+ require 'securerandom'
7
8
 
8
9
  class EasyPost::Client
9
10
  attr_reader :open_timeout, :read_timeout, :api_base
@@ -90,6 +91,54 @@ class EasyPost::Client
90
91
  EasyPost::InternalUtilities::Json.convert_json_to_object(response.body, cls)
91
92
  end
92
93
 
94
+ # Subscribe a request hook
95
+ #
96
+ # @param name [Symbol] the name of the hook. Defaults ot a ranom hexadecimal-based symbol
97
+ # @param block [Block] a code block that will be executed before a request is made
98
+ # @return [Symbol] the name of the request hook
99
+ def subscribe_request_hook(name = SecureRandom.hex.to_sym, &block)
100
+ EasyPost::Hooks.subscribe(:request, name, block)
101
+ end
102
+
103
+ # Unsubscribe a request hook
104
+ #
105
+ # @param name [Symbol] the name of the hook
106
+ # @return [Block] the hook code block
107
+ def unsubscribe_request_hook(name)
108
+ EasyPost::Hooks.unsubscribe(:request, name)
109
+ end
110
+
111
+ # Unsubscribe all request hooks
112
+ #
113
+ # @return [Hash] a hash containing all request hook subscriptions
114
+ def unsubscribe_all_request_hooks
115
+ EasyPost::Hooks.unsubscribe_all(:request)
116
+ end
117
+
118
+ # Subscribe a response hook
119
+ #
120
+ # @param name [Symbol] the name of the hook. Defaults ot a ranom hexadecimal-based symbol
121
+ # @param block [Block] a code block that will be executed upon receiving the response from a request
122
+ # @return [Symbol] the name of the response hook
123
+ def subscribe_response_hook(name = SecureRandom.hex.to_sym, &block)
124
+ EasyPost::Hooks.subscribe(:response, name, block)
125
+ end
126
+
127
+ # Unsubscribe a response hook
128
+ #
129
+ # @param name [Symbol] the name of the hook
130
+ # @return [Block] the hook code block
131
+ def unsubscribe_response_hook(name)
132
+ EasyPost::Hooks.unsubscribe(:response, name)
133
+ end
134
+
135
+ # Unsubscribe all response hooks
136
+ #
137
+ # @return [Hash] a hash containing all response hook subscriptions
138
+ def unsubscribe_all_response_hooks
139
+ EasyPost::Hooks.unsubscribe_all(:response)
140
+ end
141
+
93
142
  private
94
143
 
95
144
  def http_config
@@ -77,6 +77,8 @@ class EasyPost::Errors::ApiError < EasyPost::Errors::EasyPostError
77
77
  EasyPost::Errors::ConnectionError
78
78
  when 300, 301, 302, 303, 304, 305, 306, 307, 308
79
79
  EasyPost::Errors::RedirectError
80
+ when 400
81
+ EasyPost::Errors::BadRequestError
80
82
  when 401
81
83
  EasyPost::Errors::UnauthorizedError
82
84
  when 402
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'api_error'
4
+
5
+ class EasyPost::Errors::BadRequestError < EasyPost::Errors::ApiError
6
+ end
@@ -12,6 +12,7 @@ require_relative 'errors/invalid_parameter_error'
12
12
  require_relative 'errors/missing_parameter_error'
13
13
  require_relative 'errors/signature_verification_error'
14
14
  require_relative 'errors/api/api_error'
15
+ require_relative 'errors/api/bad_request_error'
15
16
  require_relative 'errors/api/connection_error'
16
17
  require_relative 'errors/api/forbidden_error'
17
18
  require_relative 'errors/api/gateway_timeout_error'
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ class EasyPost::Hooks::RequestContext
4
+ attr_reader :method, :path, :headers, :request_body, :request_timestamp, :request_uuid
5
+
6
+ def initialize(method:, path:, headers:, request_body:, request_timestamp:, request_uuid:)
7
+ @method = method
8
+ @path = path
9
+ @headers = headers
10
+ @request_body = request_body
11
+ @request_timestamp = request_timestamp
12
+ @request_uuid = request_uuid
13
+
14
+ freeze
15
+ end
16
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ class EasyPost::Hooks::ResponseContext
4
+ attr_reader :http_status, :method, :path, :headers, :response_body,
5
+ :request_timestamp, :response_timestamp, :request_uuid,
6
+ :client_response_object
7
+
8
+ def initialize(http_status:, method:, path:, headers:, response_body:,
9
+ request_timestamp:, response_timestamp:, request_uuid:,
10
+ client_response_object:)
11
+ @http_status = http_status
12
+ @method = method
13
+ @path = path
14
+ @headers = headers
15
+ @response_body = response_body
16
+ @request_timestamp = request_timestamp
17
+ @response_timestamp = response_timestamp
18
+ @request_uuid = request_uuid
19
+ @client_response_object = client_response_object
20
+
21
+ freeze
22
+ end
23
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EasyPost::Hooks
4
+ def self.subscribe(type, name, block)
5
+ subscribers[type][name] = block
6
+
7
+ name
8
+ end
9
+
10
+ def self.unsubscribe(type, name)
11
+ subscribers[type].delete(name)
12
+ end
13
+
14
+ def self.unsubscribe_all(type)
15
+ subscribers.delete(type)
16
+ end
17
+
18
+ def self.notify(type, context)
19
+ subscribers[type].each_value { |subscriber| subscriber.call(context) }
20
+ end
21
+
22
+ def self.any_subscribers?(type)
23
+ !subscribers[type].empty?
24
+ end
25
+
26
+ def self.subscribers
27
+ @subscribers ||= Hash.new { |hash, key| hash[key] = {} }
28
+ end
29
+
30
+ private_class_method :subscribers
31
+ end
32
+
33
+ require_relative 'hooks/request_context'
34
+ require_relative 'hooks/response_context'
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'securerandom'
4
+
3
5
  class EasyPost::HttpClient
4
6
  def initialize(base_url, config, custom_client_exec = nil)
5
7
  @base_url = base_url
@@ -20,17 +22,67 @@ class EasyPost::HttpClient
20
22
 
21
23
  uri = URI.parse("#{@base_url}/#{api_version}/#{path}")
22
24
  headers = @config[:headers].merge(headers || {})
23
- body = JSON.dump(EasyPost::InternalUtilities.objects_to_ids(body)) if body
25
+ serialized_body = JSON.dump(EasyPost::InternalUtilities.objects_to_ids(body)) if body
24
26
  open_timeout = @config[:open_timeout]
25
27
  read_timeout = @config[:read_timeout]
28
+ request_timestamp = Time.now
29
+ request_uuid = SecureRandom.uuid
30
+
31
+ if EasyPost::Hooks.any_subscribers?(:request)
32
+ request_context = EasyPost::Hooks::RequestContext.new(
33
+ method: method,
34
+ path: uri.to_s,
35
+ headers: headers,
36
+ request_body: body,
37
+ request_timestamp: request_timestamp,
38
+ request_uuid: request_uuid,
39
+ )
40
+ EasyPost::Hooks.notify(:request, request_context)
41
+ end
26
42
 
27
43
  # Execute the request, return the response.
28
44
 
29
- if @custom_client_exec
30
- @custom_client_exec.call(method, uri, headers, open_timeout, read_timeout, body)
31
- else
32
- default_request_execute(method, uri, headers, open_timeout, read_timeout, body)
45
+ response = if @custom_client_exec
46
+ @custom_client_exec.call(method, uri, headers, open_timeout, read_timeout, serialized_body)
47
+ else
48
+ default_request_execute(method, uri, headers, open_timeout, read_timeout, serialized_body)
49
+ end
50
+ response_timestamp = Time.now
51
+
52
+ if EasyPost::Hooks.any_subscribers?(:response)
53
+ response_context = {
54
+ http_status: nil,
55
+ method: method,
56
+ path: uri.to_s,
57
+ headers: nil,
58
+ response_body: nil,
59
+ request_timestamp: request_timestamp,
60
+ response_timestamp: response_timestamp,
61
+ client_response_object: response,
62
+ request_uuid: request_uuid,
63
+ }
64
+
65
+ # If using a custom HTTP client, the user will have to infer these from the raw
66
+ # client_response_object attribute
67
+ if response.is_a?(Net::HTTPResponse)
68
+ response_body = begin
69
+ JSON.parse(response.body)
70
+ rescue JSON::ParseError
71
+ response.body
72
+ end
73
+ response_context.merge!(
74
+ {
75
+ http_status: response.code.to_i,
76
+ headers: response.each_header.to_h,
77
+ response_body: response_body,
78
+ },
79
+ )
80
+ end
81
+
82
+ EasyPost::Hooks.notify(:response, EasyPost::Hooks::ResponseContext.new(**response_context))
33
83
  end
84
+
85
+ response
34
86
  end
35
87
 
36
88
  def default_request_execute(method, uri, headers, open_timeout, read_timeout, body = nil)
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class EasyPost::Services::CarrierAccount < EasyPost::Services::Service
4
- CUSTOM_WORKFLOW_CARRIER_TYPES = %w[UpsAccount FedexAccount].freeze
4
+ CUSTOM_WORKFLOW_CARRIER_TYPES = %w[UpsAccount FedexAccount FedexSmartpostAccount].freeze
5
5
  MODEL_CLASS = EasyPost::Models::CarrierAccount
6
6
 
7
7
  # Create a carrier account
data/lib/easypost.rb CHANGED
@@ -24,5 +24,8 @@ require 'easypost/errors'
24
24
  # Internal Utilities
25
25
  require 'easypost/internal_utilities'
26
26
 
27
+ # Hooks
28
+ require 'easypost/hooks'
29
+
27
30
  module EasyPost
28
31
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: easypost
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.0.1
4
+ version: 5.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - EasyPost Developers
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-06-20 00:00:00.000000000 Z
11
+ date: 2023-09-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: brakeman
@@ -237,6 +237,7 @@ files:
237
237
  - lib/easypost/constants.rb
238
238
  - lib/easypost/errors.rb
239
239
  - lib/easypost/errors/api/api_error.rb
240
+ - lib/easypost/errors/api/bad_request_error.rb
240
241
  - lib/easypost/errors/api/connection_error.rb
241
242
  - lib/easypost/errors/api/external_api_error.rb
242
243
  - lib/easypost/errors/api/forbidden_error.rb
@@ -262,6 +263,9 @@ files:
262
263
  - lib/easypost/errors/invalid_parameter_error.rb
263
264
  - lib/easypost/errors/missing_parameter_error.rb
264
265
  - lib/easypost/errors/signature_verification_error.rb
266
+ - lib/easypost/hooks.rb
267
+ - lib/easypost/hooks/request_context.rb
268
+ - lib/easypost/hooks/response_context.rb
265
269
  - lib/easypost/http_client.rb
266
270
  - lib/easypost/internal_utilities.rb
267
271
  - lib/easypost/models.rb