easyship 0.1.5 → 0.3.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.
- checksums.yaml +4 -4
- data/.rubocop.yml +5 -1
- data/CHANGELOG.md +14 -0
- data/README.md +56 -2
- data/lib/easyship/configuration.rb +3 -1
- data/lib/easyship/errors/easyship_error.rb +10 -2
- data/lib/easyship/middleware/error_handler_middleware.rb +22 -5
- data/lib/easyship/pagination/cursor.rb +21 -2
- data/lib/easyship/rate_limiting/rate_limiter.rb +12 -0
- data/lib/easyship/rate_limiting/window_rate_limiter.rb +50 -0
- data/lib/easyship/version.rb +1 -1
- data/lib/easyship.rb +2 -0
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0e185adc98f576e3cf15ffc1ca38edf2dca8a0ffa580f68a731c900c3632392b
|
|
4
|
+
data.tar.gz: e15d52b3f530c58883fb8f50b086db537939ceed2fc1afff3579ea6aa82373c1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 707860b193dbc25e916f8d0e5e68a2a3c8e34498e523871e243cf487f1e5c4c2469ed274099899b1b50c22598dc085906eca132f9e537515c43bf03428040c02
|
|
7
|
+
data.tar.gz: ec38670ff9d6626bc62485d8c836d00c1efb8e4a8218214c5c6b36b01570ad3b71906511c2459dc4a74b3a5ed91e3d3f91bd5f07586e75d481dc2ebf142a4d9a
|
data/.rubocop.yml
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [v0.3.0](https://github.com/mmarusyk/easyship/tree/v0.3.0) - 2025-11-29
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- Rate limiting for cursor by @mmarusyk in https://github.com/mmarusyk/easyship/pull/13
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
## [v0.2.0](https://github.com/mmarusyk/easyship/tree/v0.2.0) - 2025-05-04
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- Add response_body and response_header to Easyship::Error
|
|
13
|
+
- Deprecate body_error by @mmarusyk in https://github.com/mmarusyk/easyship/pull/9
|
|
14
|
+
- Add badges by @mmarusyk in https://github.com/mmarusyk/easyship/pull/10
|
|
15
|
+
|
|
16
|
+
|
|
3
17
|
## [v0.1.5](https://github.com/mmarusyk/easyship/tree/v0.1.5) - 2025-05-03
|
|
4
18
|
|
|
5
19
|
### Fixed
|
data/README.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# Easyship
|
|
2
2
|
|
|
3
|
+
[](https://badge.fury.io/rb/easyship)
|
|
4
|
+
[](https://github.com/mmarusyk/easyship/actions?query=workflow%3ARuby)
|
|
5
|
+
[](https://opensource.org/licenses/MIT)
|
|
6
|
+
|
|
3
7
|
This gem provides a simple client for Easyship, offering accessing to Easyship's
|
|
4
8
|
shipping, tracking, and logistics services directly from Ruby applications.
|
|
5
9
|
|
|
@@ -54,7 +58,7 @@ Easyship.configure do |config|
|
|
|
54
58
|
end
|
|
55
59
|
```
|
|
56
60
|
|
|
57
|
-
Configuration supports the next keys: `url`, `api_key`, `per_page`.
|
|
61
|
+
Configuration supports the next keys: `url`, `api_key`, `per_page`, `requests_per_second`, `requests_per_minute`.
|
|
58
62
|
|
|
59
63
|
### Making Requests
|
|
60
64
|
`Easyship::Client` supports the next methods: `get`, `post`, `put`, `delete`.
|
|
@@ -103,6 +107,15 @@ rescue Easyship::Errors::RateLimitError => e
|
|
|
103
107
|
end
|
|
104
108
|
```
|
|
105
109
|
|
|
110
|
+
Each error instance provides these methods:
|
|
111
|
+
|
|
112
|
+
- `message`: Returns the error message text.
|
|
113
|
+
- `body_error`: Returns an error details **(Deprecated)**.
|
|
114
|
+
- `error`: Returns a hash containing original detailed error object from Easyship.
|
|
115
|
+
- `response_body`: Returns the full response body from the API.
|
|
116
|
+
- `response_headers`: Returns the HTTP headers from the API response.
|
|
117
|
+
|
|
118
|
+
|
|
106
119
|
### Pagination
|
|
107
120
|
The `get` method in the `Easyship::Client` class is designed to support pagination seamlessly when interacting with the Easyship API by passing block of code. This method abstracts the complexity of managing pagination logic, allowing you to retrieve all items across multiple pages with a single method call.
|
|
108
121
|
|
|
@@ -118,7 +131,48 @@ end
|
|
|
118
131
|
shipments # Returns all shipments from all pages
|
|
119
132
|
```
|
|
120
133
|
|
|
121
|
-
To setup items
|
|
134
|
+
To setup items per page, use the key `per_page` in your configuration.
|
|
135
|
+
|
|
136
|
+
For Example:
|
|
137
|
+
|
|
138
|
+
```ruby
|
|
139
|
+
# Global defaults
|
|
140
|
+
Easyship.configure do |config|
|
|
141
|
+
config.per_page = 100
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Per-call overrides (any of these are supported)
|
|
145
|
+
shipments = []
|
|
146
|
+
Easyship::Client.instance.get('/2023-01/shipments', {
|
|
147
|
+
per_page: 50,
|
|
148
|
+
}) do |page|
|
|
149
|
+
shipments.concat(page[:shipments])
|
|
150
|
+
end
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Rate limiting during pagination:
|
|
154
|
+
- The cursor has no default rate limiting; it uses nil to indicate that rate limiting is disabled.
|
|
155
|
+
- Use [Rate limiting documentation](https://developers.easyship.com/reference/rate-limit) for more details which values to set.
|
|
156
|
+
- You can override the limits per call, by passing `requests_per_second` and `requests_per_minute`.
|
|
157
|
+
|
|
158
|
+
Examples:
|
|
159
|
+
|
|
160
|
+
```ruby
|
|
161
|
+
# Global defaults
|
|
162
|
+
Easyship.configure do |config|
|
|
163
|
+
config.requests_per_second = 10
|
|
164
|
+
config.requests_per_minute = 60
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Per-call overrides (any of these are supported)
|
|
168
|
+
shipments = []
|
|
169
|
+
Easyship::Client.instance.get('/2023-01/shipments', {
|
|
170
|
+
requests_per_second: 5,
|
|
171
|
+
requests_per_minute: 40,
|
|
172
|
+
}) do |page|
|
|
173
|
+
shipments.concat(page[:shipments])
|
|
174
|
+
end
|
|
175
|
+
```
|
|
122
176
|
|
|
123
177
|
## Development
|
|
124
178
|
|
|
@@ -3,12 +3,14 @@
|
|
|
3
3
|
module Easyship
|
|
4
4
|
# Represents the configuration settings for the Easyship client.
|
|
5
5
|
class Configuration
|
|
6
|
-
attr_accessor :url, :api_key, :per_page
|
|
6
|
+
attr_accessor :url, :api_key, :per_page, :requests_per_second, :requests_per_minute
|
|
7
7
|
|
|
8
8
|
def initialize
|
|
9
9
|
@url = nil
|
|
10
10
|
@api_key = nil
|
|
11
11
|
@per_page = 100 # Maximum possible number of items per page
|
|
12
|
+
@requests_per_second = nil
|
|
13
|
+
@requests_per_minute = nil
|
|
12
14
|
end
|
|
13
15
|
end
|
|
14
16
|
end
|
|
@@ -4,12 +4,20 @@ module Easyship
|
|
|
4
4
|
module Errors
|
|
5
5
|
# Represents an error that is raised when an error occurs in the Easyship API.
|
|
6
6
|
class EasyshipError < StandardError
|
|
7
|
-
attr_reader :message, :
|
|
7
|
+
attr_reader :message, :error, :response_body, :response_headers
|
|
8
8
|
|
|
9
|
-
def initialize(message: '', body_error: {})
|
|
9
|
+
def initialize(message: '', body_error: {}, error: {}, response_body: nil, response_headers: {})
|
|
10
10
|
super(message)
|
|
11
|
+
@error = error
|
|
11
12
|
@message = message
|
|
12
13
|
@body_error = body_error
|
|
14
|
+
@response_body = response_body
|
|
15
|
+
@response_headers = response_headers
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def body_error
|
|
19
|
+
warn '[DEPRECATION] `body_error` is deprecated. Please use `error` instead.'
|
|
20
|
+
@body_error
|
|
13
21
|
end
|
|
14
22
|
end
|
|
15
23
|
end
|
|
@@ -9,20 +9,33 @@ module Easyship
|
|
|
9
9
|
def on_complete(env)
|
|
10
10
|
status_code = env[:status].to_i
|
|
11
11
|
body = response_body(env[:body])
|
|
12
|
+
headers = env[:response_headers]
|
|
12
13
|
|
|
13
|
-
handle_status_code(status_code, body)
|
|
14
|
+
handle_status_code(status_code, headers, body)
|
|
14
15
|
end
|
|
15
16
|
|
|
16
17
|
private
|
|
17
18
|
|
|
18
|
-
def handle_status_code(status_code, body)
|
|
19
|
+
def handle_status_code(status_code, headers, body)
|
|
19
20
|
error_class = Easyship::Error.for_status(status_code)
|
|
20
21
|
|
|
21
|
-
raise_error(error_class, body) if error_class
|
|
22
|
+
raise_error(error_class, headers, body) if error_class
|
|
22
23
|
end
|
|
23
24
|
|
|
24
|
-
def raise_error(class_error, body)
|
|
25
|
-
raise class_error.new(
|
|
25
|
+
def raise_error(class_error, headers, body)
|
|
26
|
+
raise class_error.new(
|
|
27
|
+
message: message(body),
|
|
28
|
+
body_error: body_error(body),
|
|
29
|
+
error: build_error(body),
|
|
30
|
+
response_body: body,
|
|
31
|
+
response_headers: headers
|
|
32
|
+
)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def build_error(body)
|
|
36
|
+
return default_error unless body.is_a?(Hash)
|
|
37
|
+
|
|
38
|
+
body[:error]
|
|
26
39
|
end
|
|
27
40
|
|
|
28
41
|
def body_error(body)
|
|
@@ -55,6 +68,10 @@ module Easyship
|
|
|
55
68
|
{ details: body, message: 'Something went wrong.' }
|
|
56
69
|
end
|
|
57
70
|
|
|
71
|
+
def default_error
|
|
72
|
+
{ details: [], message: 'Something went wrong.' }
|
|
73
|
+
end
|
|
74
|
+
|
|
58
75
|
def response_body(body)
|
|
59
76
|
JSON.parse(body, symbolize_names: true)
|
|
60
77
|
rescue JSON::ParserError
|
|
@@ -4,20 +4,26 @@ module Easyship
|
|
|
4
4
|
module Pagination
|
|
5
5
|
# Represents a pagination object
|
|
6
6
|
class Cursor
|
|
7
|
-
|
|
7
|
+
CONFIGURATION_VARIABLES = %i[requests_per_second requests_per_minute].freeze
|
|
8
|
+
|
|
9
|
+
attr_reader :client, :path, :params, :key, :per_page, :requests_per_second, :requests_per_minute
|
|
8
10
|
|
|
9
11
|
def initialize(client, path, params)
|
|
10
12
|
@client = client
|
|
11
13
|
@path = path
|
|
12
14
|
@params = params
|
|
13
15
|
@per_page = params[:per_page] || Easyship.configuration.per_page
|
|
16
|
+
@requests_per_second = params[:requests_per_second] || Easyship.configuration.requests_per_second
|
|
17
|
+
@requests_per_minute = params[:requests_per_minute] || Easyship.configuration.requests_per_minute
|
|
14
18
|
end
|
|
15
19
|
|
|
16
20
|
def all
|
|
17
21
|
page = 1
|
|
18
22
|
|
|
19
23
|
loop do
|
|
20
|
-
|
|
24
|
+
limiter.throttle!
|
|
25
|
+
|
|
26
|
+
body = client.get(path, build_request_params(page: page))
|
|
21
27
|
|
|
22
28
|
break if body.nil? || body.empty?
|
|
23
29
|
|
|
@@ -28,6 +34,19 @@ module Easyship
|
|
|
28
34
|
page += 1
|
|
29
35
|
end
|
|
30
36
|
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def build_request_params(page:)
|
|
41
|
+
params.merge(page: page, per_page: per_page).except(*CONFIGURATION_VARIABLES)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def limiter
|
|
45
|
+
@limiter ||= Easyship::RateLimiting::WindowRateLimiter.new(
|
|
46
|
+
requests_per_second: requests_per_second,
|
|
47
|
+
requests_per_minute: requests_per_minute
|
|
48
|
+
)
|
|
49
|
+
end
|
|
31
50
|
end
|
|
32
51
|
end
|
|
33
52
|
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Easyship
|
|
4
|
+
module RateLimiting
|
|
5
|
+
# Represents WindowRateLimiter
|
|
6
|
+
class WindowRateLimiter < RateLimiter
|
|
7
|
+
attr_reader :timestamps, :requests_per_second, :requests_per_minute
|
|
8
|
+
|
|
9
|
+
def initialize(requests_per_second:, requests_per_minute:)
|
|
10
|
+
super()
|
|
11
|
+
@requests_per_second = requests_per_second
|
|
12
|
+
@requests_per_minute = requests_per_minute
|
|
13
|
+
@timestamps = []
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def throttle!
|
|
17
|
+
now = Time.now
|
|
18
|
+
|
|
19
|
+
timestamps << now
|
|
20
|
+
|
|
21
|
+
# Remove timestamps older than a minute
|
|
22
|
+
timestamps.reject! { |t| t < now - 60 }
|
|
23
|
+
|
|
24
|
+
check_second_window(now)
|
|
25
|
+
check_minute_window(now)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def check_second_window(now)
|
|
31
|
+
second_requests = timestamps.count { |t| t > now - 1 }
|
|
32
|
+
|
|
33
|
+
return if !requests_per_second || second_requests < requests_per_second
|
|
34
|
+
|
|
35
|
+
first_in_seconds = timestamps.find { |t| t > now - 1 }
|
|
36
|
+
sleep_time = (first_in_seconds + 1) - now
|
|
37
|
+
|
|
38
|
+
sleep(sleep_time) if sleep_time.positive?
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def check_minute_window(now)
|
|
42
|
+
return if !requests_per_minute || timestamps.size < requests_per_minute
|
|
43
|
+
|
|
44
|
+
sleep_time = (timestamps.first + 60) - now
|
|
45
|
+
|
|
46
|
+
sleep(sleep_time) if sleep_time.positive?
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
data/lib/easyship/version.rb
CHANGED
data/lib/easyship.rb
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
require_relative 'easyship/version'
|
|
4
4
|
require_relative 'easyship/configuration'
|
|
5
5
|
require_relative 'easyship/client'
|
|
6
|
+
require_relative 'easyship/rate_limiting/rate_limiter'
|
|
7
|
+
require_relative 'easyship/rate_limiting/window_rate_limiter'
|
|
6
8
|
|
|
7
9
|
# Provides configuration options for the Easyship gem.
|
|
8
10
|
module Easyship
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: easyship
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Michael Marusyk
|
|
@@ -56,6 +56,8 @@ files:
|
|
|
56
56
|
- lib/easyship/handlers/response_body_handler.rb
|
|
57
57
|
- lib/easyship/middleware/error_handler_middleware.rb
|
|
58
58
|
- lib/easyship/pagination/cursor.rb
|
|
59
|
+
- lib/easyship/rate_limiting/rate_limiter.rb
|
|
60
|
+
- lib/easyship/rate_limiting/window_rate_limiter.rb
|
|
59
61
|
- lib/easyship/version.rb
|
|
60
62
|
- sig/easyship.rbs
|
|
61
63
|
homepage: https://rubygems.org/gems/easyship
|
|
@@ -81,7 +83,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
81
83
|
- !ruby/object:Gem::Version
|
|
82
84
|
version: '0'
|
|
83
85
|
requirements: []
|
|
84
|
-
rubygems_version: 3.6.
|
|
86
|
+
rubygems_version: 3.6.9
|
|
85
87
|
specification_version: 4
|
|
86
88
|
summary: A Ruby client for integrating with Easyship's API for shipping and logistics
|
|
87
89
|
management.
|