fulfil-io 0.5.0 → 0.6.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/README.md +36 -12
- data/lib/fulfil/client.rb +14 -8
- data/lib/fulfil/error.rb +2 -0
- data/lib/fulfil/rate_limit.rb +28 -0
- data/lib/fulfil/rate_limit_headers.rb +48 -0
- data/lib/fulfil/response_handler.rb +11 -2
- data/lib/fulfil/version.rb +1 -1
- data/lib/fulfil.rb +7 -0
- metadata +34 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0c58e4104ae9106550bca7c000cbf3f576f551c9e4512cee93dc74dd0aa5604f
|
4
|
+
data.tar.gz: ee0644f88360b5356efa034833772a54d207ce4be7e81040219e8d66019ff301
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d697a2153dcbcd379621b59bc7ef8af428c4f6ecc2445d454afc38eddb34247430279136f758fe52e538abcda923c386dbc4d7f2af00c27e9e41c26f71a4c5e2
|
7
|
+
data.tar.gz: e6e891f839dd472f24f1dc89ce1189cb80b9218ac09c35c4141c0dd0b575097ecb0d238d47de8e688bc69c3298da0f4e1b2478ebfed1010d139c3fb4087f0ccf
|
data/README.md
CHANGED
@@ -1,36 +1,40 @@
|
|
1
|
-
|
1
|
+
[](https://github.com/knowndecimal/fulfil/actions/workflows/tests.yml)
|
2
2
|
|
3
|
-
|
3
|
+
# Fulfil.io Rubygem
|
4
4
|
|
5
|
-
[Fulfil.io](https://fulfil.io)
|
5
|
+
A Ruby library for the [Fulfil.io](https://fulfil.io) API.
|
6
6
|
|
7
7
|
## Installation
|
8
8
|
|
9
9
|
Add this line to your application's Gemfile:
|
10
10
|
|
11
11
|
```ruby
|
12
|
-
gem 'fulfil-io', require: 'fulfil'
|
12
|
+
gem 'fulfil-io', require: 'fulfil'
|
13
13
|
```
|
14
14
|
|
15
15
|
And then execute:
|
16
16
|
|
17
|
-
|
17
|
+
```shell
|
18
|
+
$ bundle install
|
19
|
+
```
|
18
20
|
|
19
21
|
Or install it yourself as:
|
20
22
|
|
21
|
-
|
23
|
+
```shell
|
24
|
+
$ gem install fulfil-io
|
25
|
+
```
|
22
26
|
|
23
27
|
## Usage
|
24
28
|
|
25
29
|
Environment variables:
|
26
30
|
|
27
|
-
- FULFIL_SUBDOMAIN - required to
|
28
|
-
-
|
29
|
-
- FULFIL_API_KEY
|
31
|
+
- **FULFIL_SUBDOMAIN:** - always required to use the gem.
|
32
|
+
- **FULFIL_OAUTH_TOKEN:** required for oauth bearer authentication
|
33
|
+
- **FULFIL_API_KEY:** required for authentication via the `X-API-KEY` request header
|
30
34
|
|
31
|
-
**Note:** When
|
35
|
+
> **Note:** When `FULFIL_OAUTH_TOKEN` is present, the `FULFIL_API_KEY` will be ignored. So,
|
32
36
|
if oauth doesn't work, returning an Unauthorized error, to use the
|
33
|
-
FULFIL_API_KEY
|
37
|
+
`FULFIL_API_KEY`, the `FULFIL_OAUTH_TOKEN` shouldn't be specified.
|
34
38
|
|
35
39
|
```ruby
|
36
40
|
require 'fulfil' # this is necessary only in case of running without bundler
|
@@ -123,6 +127,26 @@ report = Fulfil::Report.new(client: fulfil, report_name: 'account.tax.summary.ir
|
|
123
127
|
report.execute(start_date: Date.new(2020, 12, 1), end_date: Date.new(2020, 12, 31))
|
124
128
|
```
|
125
129
|
|
130
|
+
## Rate limits
|
131
|
+
|
132
|
+
Fulfil's API applies rate limits to the API requests that it receives. Every request is subject to throttling under the general limits. In addition, there are resource-based rate limits and throttles.
|
133
|
+
|
134
|
+
This gem exposes an API for checking your current rate limits (note: the gem only knows about the rate limit after a request to Fulfil's API has been made).
|
135
|
+
|
136
|
+
Whenever you reached the rate limit, the `Fulfil::RateLimitExceeded` exception is being raised. You can use the information on the `Fulfil.rate_limit` to find out what to do next.
|
137
|
+
|
138
|
+
```ruby
|
139
|
+
$ Fulfil.rate_limit.requests_left?
|
140
|
+
=> true
|
141
|
+
|
142
|
+
# The maximum number of requests you're permitted to make per second.
|
143
|
+
$ Fulfil.rate_limit.limit
|
144
|
+
=> 9
|
145
|
+
|
146
|
+
# The time at which the current rate limit window resets in UTC epoch seconds.
|
147
|
+
$ Fulfil.rate_limit.resets_at
|
148
|
+
=> #<DateTime: 2022-01-21T16:36:01-04:00 />
|
149
|
+
```
|
126
150
|
## Development
|
127
151
|
|
128
152
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run
|
@@ -145,7 +169,7 @@ For non-client tests, create the test class or case.
|
|
145
169
|
|
146
170
|
For client tests, you'll need to add a couple steps. If running against a real
|
147
171
|
backend, you'll need to provide a couple of environment variables:
|
148
|
-
`FULFIL_SUBDOMAIN` and `
|
172
|
+
`FULFIL_SUBDOMAIN` and `FULFIL_OAUTH_TOKEN`. Additionally, pass `debug: true` to the
|
149
173
|
client instance in the test. This will output the response body. Webmock will
|
150
174
|
probably complain that real requests aren't allowed at this point, offering you
|
151
175
|
the stub. We don't need most of that.
|
data/lib/fulfil/client.rb
CHANGED
@@ -7,7 +7,6 @@ require 'fulfil/response_parser'
|
|
7
7
|
module Fulfil
|
8
8
|
SUBDOMAIN = ENV['FULFIL_SUBDOMAIN']
|
9
9
|
API_KEY = ENV['FULFIL_API_KEY']
|
10
|
-
OAUTH_TOKEN = ENV['FULFIL_TOKEN']
|
11
10
|
|
12
11
|
class Client
|
13
12
|
class InvalidClientError < StandardError
|
@@ -24,7 +23,7 @@ module Fulfil
|
|
24
23
|
|
25
24
|
class ResponseError < StandardError; end
|
26
25
|
|
27
|
-
def initialize(subdomain: SUBDOMAIN, token:
|
26
|
+
def initialize(subdomain: SUBDOMAIN, token: oauth_token, headers: { 'X-API-KEY' => API_KEY }, debug: false)
|
28
27
|
@subdomain = subdomain
|
29
28
|
@token = token
|
30
29
|
@debug = debug
|
@@ -112,6 +111,15 @@ module Fulfil
|
|
112
111
|
|
113
112
|
private
|
114
113
|
|
114
|
+
def oauth_token
|
115
|
+
if ENV['FULFIL_TOKEN']
|
116
|
+
puts "You're using an deprecated environment variable. Please update your " \
|
117
|
+
'FULFIL_TOKEN to FULFIL_OAUTH_TOKEN.'
|
118
|
+
end
|
119
|
+
|
120
|
+
ENV['FULFIL_OAUTH_TOKEN'] || ENV['FULFIL_TOKEN']
|
121
|
+
end
|
122
|
+
|
115
123
|
def parse(result: nil, results: [])
|
116
124
|
if result
|
117
125
|
parse_single(result: result)
|
@@ -159,12 +167,10 @@ module Fulfil
|
|
159
167
|
end
|
160
168
|
|
161
169
|
def client
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
@client = @client.headers(@headers)
|
167
|
-
@client
|
170
|
+
client = HTTP.use(logging: @debug ? { logger: Logger.new(STDOUT) } : {})
|
171
|
+
client = client.auth("Bearer #{@token}") if @token
|
172
|
+
client = client.headers(@headers)
|
173
|
+
client
|
168
174
|
end
|
169
175
|
end
|
170
176
|
end
|
data/lib/fulfil/error.rb
CHANGED
@@ -3,6 +3,8 @@
|
|
3
3
|
module Fulfil
|
4
4
|
class Error < StandardError; end
|
5
5
|
|
6
|
+
class RateLimitExceeded < Error; end
|
7
|
+
|
6
8
|
# The `Fulfil::HttpError` is raised whenever an API request returns a 400+ HTTP status code.
|
7
9
|
# See `Fulfil::ResponseHandler` for more information.
|
8
10
|
class HttpError < Error
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Fulfil
|
4
|
+
# The `Fulfil::RateLimit` allows clients to keep track of their API usage and
|
5
|
+
# to analyze Fulfil's response HTTP headers.
|
6
|
+
class RateLimit
|
7
|
+
attr_accessor :limit, :requests_left, :resets_at
|
8
|
+
|
9
|
+
# Analyses the rate limit based on the response headers from Fulfil.
|
10
|
+
# @param headers [HTTP::Headers] The HTTP response headers from Fulfil.
|
11
|
+
# @return [Fulfil::RateLimit]
|
12
|
+
def analyse!(headers)
|
13
|
+
rate_limit_headers = RateLimitHeaders.new(headers)
|
14
|
+
|
15
|
+
self.limit = rate_limit_headers.limit
|
16
|
+
self.requests_left = rate_limit_headers.requests_left
|
17
|
+
self.resets_at = rate_limit_headers.resets_at
|
18
|
+
|
19
|
+
raise Fulfil::RateLimitExceeded unless requests_left?
|
20
|
+
end
|
21
|
+
|
22
|
+
# Returns whether there are any requests left in the current rate limit window.
|
23
|
+
# @return [Boolean]
|
24
|
+
def requests_left?
|
25
|
+
requests_left&.positive?
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Fulfil
|
4
|
+
# The `Fulfil::RateLimitHeaders` parses Fulfil HTTP rate limit headers and
|
5
|
+
# formats them to a more usable format.
|
6
|
+
class RateLimitHeaders
|
7
|
+
# Test suites might mock (or at least should mock) the requests to Fulfil.
|
8
|
+
# However, most of these test suites will not mock the response headers.
|
9
|
+
# To make sure those test suites don't break, we're setting some defaults for them.
|
10
|
+
DEFAULT_REQUEST_LIMIT = 10
|
11
|
+
DEFAULT_REQUESTS_LEFT = 9
|
12
|
+
DEFAULT_RESETS_AT = nil
|
13
|
+
|
14
|
+
attr_reader :limit, :requests_left, :resets_at
|
15
|
+
|
16
|
+
def initialize(headers = {})
|
17
|
+
self.limit = headers['X-RateLimit-Limit'] || DEFAULT_REQUEST_LIMIT
|
18
|
+
self.requests_left = headers['X-RateLimit-Remaining'] || DEFAULT_REQUESTS_LEFT
|
19
|
+
self.resets_at = headers['X-RateLimit-Reset'] || DEFAULT_RESETS_AT
|
20
|
+
end
|
21
|
+
|
22
|
+
# Sets the maximum number of requests you're permitted to make per second.
|
23
|
+
# @param value [String] The maximum number of requests per second.
|
24
|
+
# @return [Integer] The maximum number of requests per second.
|
25
|
+
def limit=(value)
|
26
|
+
@limit = value.to_i
|
27
|
+
end
|
28
|
+
|
29
|
+
# Sets number of requests remaining in the current rate limit window.
|
30
|
+
# @param value [String] The remaining number of requests for the current time window.
|
31
|
+
# @return [Integer] The remaining number of requests for the current time window.
|
32
|
+
def requests_left=(value)
|
33
|
+
@requests_left = value.to_i
|
34
|
+
end
|
35
|
+
|
36
|
+
# Sets the time at which the current rate limit window resets in UTC epoch seconds.
|
37
|
+
# @param value [Integer|nil] Time as an integer in UTC epoch seconds.
|
38
|
+
# @return [DataTime|nil] The moment the rate limit resets.
|
39
|
+
def resets_at=(value)
|
40
|
+
@resets_at =
|
41
|
+
if value.nil?
|
42
|
+
nil
|
43
|
+
else
|
44
|
+
Time.at(value.to_i).utc.to_datetime
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -31,6 +31,17 @@ module Fulfil
|
|
31
31
|
end
|
32
32
|
|
33
33
|
def verify!
|
34
|
+
verify_rate_limits!
|
35
|
+
verify_http_status_code!
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def verify_rate_limits!
|
41
|
+
Fulfil.rate_limit.analyse!(@response.headers)
|
42
|
+
end
|
43
|
+
|
44
|
+
def verify_http_status_code!
|
34
45
|
return true unless @status_code >= 400
|
35
46
|
|
36
47
|
raise HTTP_ERROR_CODES.fetch(@status_code, Fulfil::HttpError).new(
|
@@ -43,8 +54,6 @@ module Fulfil
|
|
43
54
|
)
|
44
55
|
end
|
45
56
|
|
46
|
-
private
|
47
|
-
|
48
57
|
def response_body
|
49
58
|
@response_body ||= @response.parse
|
50
59
|
end
|
data/lib/fulfil/version.rb
CHANGED
data/lib/fulfil.rb
CHANGED
@@ -8,5 +8,12 @@ require 'fulfil/interactive_report'
|
|
8
8
|
require 'fulfil/response_handler'
|
9
9
|
require 'fulfil/response_parser'
|
10
10
|
|
11
|
+
# Rate limiting
|
12
|
+
require 'fulfil/rate_limit'
|
13
|
+
require 'fulfil/rate_limit_headers'
|
14
|
+
|
11
15
|
module Fulfil
|
16
|
+
def self.rate_limit
|
17
|
+
@rate_limit ||= RateLimit.new
|
18
|
+
end
|
12
19
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fulfil-io
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Chris Moore
|
@@ -9,22 +9,28 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2022-03-23 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: http
|
16
16
|
requirement: !ruby/object:Gem::Requirement
|
17
17
|
requirements:
|
18
|
-
- - "
|
18
|
+
- - ">="
|
19
19
|
- !ruby/object:Gem::Version
|
20
20
|
version: 4.4.1
|
21
|
+
- - "<"
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 5.1.0
|
21
24
|
type: :runtime
|
22
25
|
prerelease: false
|
23
26
|
version_requirements: !ruby/object:Gem::Requirement
|
24
27
|
requirements:
|
25
|
-
- - "
|
28
|
+
- - ">="
|
26
29
|
- !ruby/object:Gem::Version
|
27
30
|
version: 4.4.1
|
31
|
+
- - "<"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 5.1.0
|
28
34
|
- !ruby/object:Gem::Dependency
|
29
35
|
name: bundler
|
30
36
|
requirement: !ruby/object:Gem::Requirement
|
@@ -109,6 +115,26 @@ dependencies:
|
|
109
115
|
- - ">="
|
110
116
|
- !ruby/object:Gem::Version
|
111
117
|
version: '0'
|
118
|
+
- !ruby/object:Gem::Dependency
|
119
|
+
name: dotenv
|
120
|
+
requirement: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '2.7'
|
125
|
+
- - ">="
|
126
|
+
- !ruby/object:Gem::Version
|
127
|
+
version: 2.7.6
|
128
|
+
type: :development
|
129
|
+
prerelease: false
|
130
|
+
version_requirements: !ruby/object:Gem::Requirement
|
131
|
+
requirements:
|
132
|
+
- - "~>"
|
133
|
+
- !ruby/object:Gem::Version
|
134
|
+
version: '2.7'
|
135
|
+
- - ">="
|
136
|
+
- !ruby/object:Gem::Version
|
137
|
+
version: 2.7.6
|
112
138
|
description:
|
113
139
|
email:
|
114
140
|
- chris@knowndecimal.com
|
@@ -129,6 +155,8 @@ files:
|
|
129
155
|
- lib/fulfil/interactive_report.rb
|
130
156
|
- lib/fulfil/model.rb
|
131
157
|
- lib/fulfil/query.rb
|
158
|
+
- lib/fulfil/rate_limit.rb
|
159
|
+
- lib/fulfil/rate_limit_headers.rb
|
132
160
|
- lib/fulfil/response_handler.rb
|
133
161
|
- lib/fulfil/response_parser.rb
|
134
162
|
- lib/fulfil/version.rb
|
@@ -144,14 +172,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
144
172
|
requirements:
|
145
173
|
- - ">="
|
146
174
|
- !ruby/object:Gem::Version
|
147
|
-
version: 2.
|
175
|
+
version: '2.4'
|
148
176
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
149
177
|
requirements:
|
150
178
|
- - ">="
|
151
179
|
- !ruby/object:Gem::Version
|
152
180
|
version: '0'
|
153
181
|
requirements: []
|
154
|
-
rubygems_version: 3.
|
182
|
+
rubygems_version: 3.3.7
|
155
183
|
signing_key:
|
156
184
|
specification_version: 4
|
157
185
|
summary: Interact with the Fulfil.io API
|