fulfil-io 0.8.0 → 0.9.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5e82498f50af6f27434982cbeb6bb3faec54a085e999f4d86293d4378afb801a
4
- data.tar.gz: d2596d9afedaf2f67aa3f573c139d1c5a03b706e638fdf2407fc482f25336d6f
3
+ metadata.gz: df8251caaf1131da686f385b8ccfa716259b5af29859f84d1236d469cc88b5b9
4
+ data.tar.gz: 695fb3d9c099d2c43d44e23cfc41cd21405be94bef8d1181383526f487ed7660
5
5
  SHA512:
6
- metadata.gz: 67ad033ab2a666abc04747a37e1d4859690158cfdf1c6455cdc3e3383ccfd6aa74b556def1b02e7584945120b426357ed4a85fa1f1feaa74ad118fbe6161280c
7
- data.tar.gz: 1ade1a109a650b56c76fcaea49ed71d92b87d8f87e6659748ea5c739d6e6f1715ec6099835f2faefd7c515e8482054d580c17b0fcb74342c2cc214d40fabaeee
6
+ metadata.gz: 69dc9272022c7d312b3bfcc701cc8fa9814bb1d1a64da76c0e5e938676810a46df5f50ed25f573e05260a221faf968cabe6bbe095eee50463f2cd9c9a263bdb0
7
+ data.tar.gz: e5f4b528beea3306899269b647fc34c6981a7950fc664d536eca4458963eb92c01039e101a48ac692ac45e574a27a79bf9ea417103d5c31abbaabc82dac7bd0b
data/CHANGELOG.md CHANGED
@@ -1,3 +1,44 @@
1
+ ## 0.9.0
2
+
3
+ * Drop legacy Ruby support and require Ruby 3.2+.
4
+ * Update CI matrix to supported Rubies (3.2, 3.3, 3.4).
5
+ * Migrate coverage uploads from Code Climate to Qlty.
6
+ * Update runtime and development dependencies.
7
+ * Auth cleanup: support explicit `api_key` while keeping backwards compatibility for `headers: { 'X-API-KEY' => ... }`.
8
+ * Fix auth selection to ignore blank tokens and correctly fall back to API key authentication.
9
+ * Improve HTTP error handling and rescue ordering so specific connection/response errors are raised correctly.
10
+
11
+ ## 0.8.1
12
+
13
+ * Add logger to configuration by @cdmwebs in https://github.com/knowndecimal/fulfil/pull/49
14
+
15
+ ## 0.8.0
16
+
17
+ * Add rubocop by @stefanvermaas in https://github.com/knowndecimal/fulfil/pull/35
18
+ * Remove _now from testing example by @swanny85 in https://github.com/knowndecimal/fulfil/pull/32
19
+ * Add SimpleCov for test coverage by @cdmwebs in https://github.com/knowndecimal/fulfil/pull/38
20
+ * Add support for Fulfil sale duplication by @swanny85 in https://github.com/knowndecimal/fulfil/pull/48
21
+
22
+ ## 0.6.1
23
+
24
+ * Fixes a typo in the examples of the readme by @stefanvermaas in https://github.com/knowndecimal/fulfil/pull/25
25
+ * Fix requiring gemfiles manually by @stefanvermaas in https://github.com/knowndecimal/fulfil/pull/27
26
+
27
+ ## 0.6.0
28
+
29
+ * Streamline the usage of environment variables. by @stefanvermaas in https://github.com/knowndecimal/fulfil/pull/18
30
+ * Update http requirement from ~> 4.4.1 to >= 4.4.1, < 5.1.0 by @dependabot in https://github.com/knowndecimal/fulfil/pull/12
31
+ * Remove extremely precise User-Agent from mocks. by @stefanvermaas in https://github.com/knowndecimal/fulfil/pull/20
32
+ * Use GitHub Actions instead of Travis CI and Circle CI by @stefanvermaas in https://github.com/knowndecimal/fulfil/pull/21
33
+ * Remove persistent connection and client caching by @stefanvermaas in https://github.com/knowndecimal/fulfil/pull/22
34
+ * Add rate limit detection by @stefanvermaas in https://github.com/knowndecimal/fulfil/pull/24
35
+
36
+ ## 0.5.0
37
+
38
+ * Better response parsing and error handling.
39
+ * Documentation updates.
40
+ * Add release script.
41
+
1
42
  ## 0.4.9
2
43
 
3
44
  * Add client tests and stub with Webmock.
data/README.md CHANGED
@@ -32,7 +32,8 @@ Environment variables:
32
32
 
33
33
  - **FULFIL_SUBDOMAIN:** - always required to use the gem.
34
34
  - **FULFIL_OAUTH_TOKEN:** required for oauth bearer authentication
35
- - **FULFIL_API_KEY:** required for authentication via the `X-API-KEY` request header
35
+ - **FULFIL_API_KEY:** required for authentication via the `X-API-KEY` request
36
+ header. This is used with the Personal Access Token authentication method.
36
37
 
37
38
  > **Note:** When `FULFIL_OAUTH_TOKEN` is present, the `FULFIL_API_KEY` will be ignored. So,
38
39
  if oauth doesn't work, returning an Unauthorized error, to use the
@@ -72,6 +73,24 @@ pp sales
72
73
  # "rec_name"=>""}]
73
74
  ```
74
75
 
76
+ To configure a client without using environment variables, pass them as arguments:
77
+
78
+ ```ruby
79
+ client = Fulfil::Client.new(
80
+ subdomain: 'test',
81
+ api_key: '123'
82
+ )
83
+ ```
84
+
85
+ With an OAuth token:
86
+
87
+ ```ruby
88
+ client = Fulfil::Client.new(
89
+ subdomain: 'test',
90
+ token: '123'
91
+ )
92
+ ```
93
+
75
94
  ### Count
76
95
 
77
96
  ```ruby
data/lib/fulfil/client.rb CHANGED
@@ -11,7 +11,7 @@ module Fulfil
11
11
  class Client
12
12
  class InvalidClientError < StandardError
13
13
  def message
14
- 'Client is not configured correctly.'
14
+ super || 'Client is not configured correctly.'
15
15
  end
16
16
  end
17
17
 
@@ -23,12 +23,19 @@ module Fulfil
23
23
 
24
24
  class ResponseError < StandardError; end
25
25
 
26
- def initialize(subdomain: SUBDOMAIN, token: oauth_token, headers: { 'X-API-KEY' => API_KEY }, debug: false)
26
+ def initialize(subdomain: SUBDOMAIN, token: oauth_token, api_key: API_KEY, headers: nil, debug: false)
27
27
  @subdomain = subdomain
28
- @token = token
29
28
  @debug = debug
30
- @headers = headers
31
- @headers.delete('X-API-KEY') if @token
29
+
30
+ normalized_api_key = api_key || headers&.[]('X-API-KEY') || headers&.[]('X-Api-Key')
31
+
32
+ if !token.to_s.empty?
33
+ @token = token
34
+ elsif !normalized_api_key.to_s.empty?
35
+ @api_key = normalized_api_key
36
+ else
37
+ raise InvalidClientError, 'No token or API key provided.'
38
+ end
32
39
 
33
40
  raise InvalidClientError if invalid?
34
41
  end
@@ -150,21 +157,16 @@ module Fulfil
150
157
  end
151
158
 
152
159
  def request(endpoint:, verb: :get, **args)
153
- raise InvalidClientError if invalid?
154
-
155
160
  response = client.request(verb, endpoint, args)
156
161
  Fulfil::ResponseHandler.new(response).verify!
157
162
 
158
163
  response.parse
159
- rescue HTTP::Error => e
160
- puts e
161
- raise UnknownHTTPError, 'Unhandled HTTP error encountered'
162
164
  rescue HTTP::ConnectionError => e
163
- puts "Couldn't connect"
164
165
  raise ConnectionError, "Can't connect to #{base_url}"
165
166
  rescue HTTP::ResponseError => e
166
167
  raise ResponseError, "Can't process response: #{e}"
167
- []
168
+ rescue HTTP::Error => e
169
+ raise UnknownHTTPError, 'Unhandled HTTP error encountered'
168
170
  # If configured, the client will wait whenever the `RateLimitExceeded` exception
169
171
  # is raised. Check `Fulfil::Configuration` for more details.
170
172
  rescue RateLimitExceeded => e
@@ -175,9 +177,10 @@ module Fulfil
175
177
  end
176
178
 
177
179
  def client
178
- client = HTTP.use(logging: @debug ? { logger: Logger.new($stdout) } : {})
180
+ client = HTTP.use(logging: @debug ? { logger: config.logger } : {})
179
181
  client = client.auth("Bearer #{@token}") if @token
180
- client.headers(@headers)
182
+ client = client.headers({ 'X-API-KEY': @api_key }) if @api_key
183
+ client
181
184
  end
182
185
 
183
186
  def config
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'logger'
4
+
3
5
  module Fulfil
4
6
  # The `Fulfil::Configuration` contains the available configuration options
5
7
  # for the `Fulfil` gem.
@@ -22,9 +24,21 @@ module Fulfil
22
24
  # @return [Proc, nil]
23
25
  attr_accessor :rate_limit_notification_handler
24
26
 
27
+ # Allows the client to configure a logger. Logs are output to $stderr by default.
28
+ #
29
+ # @example Use a logger to log the API rate limit hits
30
+ # Fulfil.configure do |config|
31
+ # config.logger = Logger.new($stderr)
32
+ # end
33
+ #
34
+ # @return [Logger, nil]
35
+ #
36
+ attr_accessor :logger
37
+
25
38
  def initialize
26
39
  @retry_on_rate_limit = false
27
40
  @retry_on_rate_limit_wait = 1
41
+ @logger = Logger.new($stderr)
28
42
  end
29
43
 
30
44
  def retry_on_rate_limit?
@@ -1,16 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Fulfil
4
- # The `Fulfil::ResponseHandler` is parses the HTTP response from Fulfil. If it
5
- # encounters an HTTP status code that indicates an error, it will raise an internal
4
+ # The `Fulfil::ResponseHandler` parses the HTTP response from Fulfil. If it
5
+ # encounters an HTTP status code that indicates an error, it raises an internal
6
6
  # exception that the consumer can catch.
7
- #
8
- # @example
9
- # Fulfil::ResponseHandler.new(@response).verify!
10
- # => true
11
- #
12
- # Fulfil::ResponseHandler.new(@response).verify!
13
- # => Fulfil::Error::BadRequest
14
7
  class ResponseHandler
15
8
  HTTP_ERROR_CODES = {
16
9
  400 => Fulfil::HttpError::BadRequest,
@@ -44,18 +37,61 @@ module Fulfil
44
37
  def verify_http_status_code!
45
38
  return true unless @status_code >= 400
46
39
 
40
+ payload = response_body
47
41
  raise HTTP_ERROR_CODES.fetch(@status_code, Fulfil::HttpError).new(
48
- response_body['error_description'],
49
- {
50
- body: @response.body,
51
- headers: @response.headers,
52
- status: @response.status
53
- }
42
+ response_error_message(payload),
43
+ response_error_metadata(payload)
54
44
  )
55
45
  end
56
46
 
47
+ def response_error_message(payload)
48
+ body = payload.is_a?(Hash) ? payload : {}
49
+
50
+ code = body['code']
51
+ type = body['type']
52
+ message = first_present(
53
+ body['error_description'],
54
+ body.dig('error', 'message'),
55
+ body['message'],
56
+ body['description'],
57
+ body['detail'],
58
+ body.dig('errors', 0, 'message')
59
+ )
60
+
61
+ return "Fulfil request failed (HTTP #{@status_code})" if message.nil?
62
+
63
+ message_parts = []
64
+ message_parts << "[#{code}]" if present?(code)
65
+ message_parts << "#{type}:" if present?(type)
66
+ message_parts << message
67
+ message_parts.join(' ')
68
+ end
69
+
70
+ def response_error_metadata(payload)
71
+ body = payload.is_a?(Hash) ? payload : {}
72
+
73
+ {
74
+ body: @response.body,
75
+ parsed_body: payload,
76
+ headers: @response.headers,
77
+ status: @response.status,
78
+ code: body['code'],
79
+ type: body['type'],
80
+ message: body['message'],
81
+ description: body['description']
82
+ }
83
+ end
84
+
57
85
  def response_body
58
86
  @response_body ||= @response.parse
59
87
  end
88
+
89
+ def present?(value)
90
+ !(value.nil? || value.to_s.strip.empty?)
91
+ end
92
+
93
+ def first_present(*values)
94
+ values.find { |value| present?(value) }
95
+ end
60
96
  end
61
97
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Fulfil
4
- VERSION = '0.8.0'
4
+ VERSION = '0.9.0'
5
5
  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.8.0
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Moore
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2023-04-20 00:00:00.000000000 Z
13
+ date: 2026-03-03 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: http
@@ -21,7 +21,7 @@ dependencies:
21
21
  version: 4.4.1
22
22
  - - "<"
23
23
  - !ruby/object:Gem::Version
24
- version: 5.2.0
24
+ version: '6.0'
25
25
  type: :runtime
26
26
  prerelease: false
27
27
  version_requirements: !ruby/object:Gem::Requirement
@@ -31,7 +31,7 @@ dependencies:
31
31
  version: 4.4.1
32
32
  - - "<"
33
33
  - !ruby/object:Gem::Version
34
- version: 5.2.0
34
+ version: '6.0'
35
35
  description:
36
36
  email:
37
37
  - chris@knowndecimal.com
@@ -72,14 +72,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
72
72
  requirements:
73
73
  - - ">="
74
74
  - !ruby/object:Gem::Version
75
- version: '2.6'
75
+ version: '3.2'
76
76
  required_rubygems_version: !ruby/object:Gem::Requirement
77
77
  requirements:
78
78
  - - ">="
79
79
  - !ruby/object:Gem::Version
80
80
  version: '0'
81
81
  requirements: []
82
- rubygems_version: 3.3.12
82
+ rubygems_version: 3.4.10
83
83
  signing_key:
84
84
  specification_version: 4
85
85
  summary: Interact with the Fulfil.io API