fulfil-io 0.8.1 → 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: 4c465ec839e9c594b602b0e70f3058214630c38e6b8544ed456a16e5c518aeac
4
- data.tar.gz: 22d0df9435f0f5432bf16193b40f25cd6f681c796c163f5aac6c6439e940c8c5
3
+ metadata.gz: df8251caaf1131da686f385b8ccfa716259b5af29859f84d1236d469cc88b5b9
4
+ data.tar.gz: 695fb3d9c099d2c43d44e23cfc41cd21405be94bef8d1181383526f487ed7660
5
5
  SHA512:
6
- metadata.gz: dfa7f4dde51c08ff6f4b33520817db32a374b99dc41543926256277cb70ead338cd24987c1cefde9100a53492c848e6c87ce7e19308be7d2e5188b9b26060f9e
7
- data.tar.gz: 4462c5a7b4526802270e1eb80941077faa956b6822e5fe034b84cbdb72021f50d610e461b22419ea4b8d3aa9d1c90be529ab3ad36fbb9b91308de084b4853414
6
+ metadata.gz: 69dc9272022c7d312b3bfcc701cc8fa9814bb1d1a64da76c0e5e938676810a46df5f50ed25f573e05260a221faf968cabe6bbe095eee50463f2cd9c9a263bdb0
7
+ data.tar.gz: e5f4b528beea3306899269b647fc34c6981a7950fc664d536eca4458963eb92c01039e101a48ac692ac45e574a27a79bf9ea417103d5c31abbaabc82dac7bd0b
data/CHANGELOG.md CHANGED
@@ -1,3 +1,13 @@
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
+
1
11
  ## 0.8.1
2
12
 
3
13
  * Add logger to configuration by @cdmwebs in https://github.com/knowndecimal/fulfil/pull/49
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
@@ -177,7 +179,8 @@ module Fulfil
177
179
  def client
178
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,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.1'
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.1
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-12-10 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.7
82
+ rubygems_version: 3.4.10
83
83
  signing_key:
84
84
  specification_version: 4
85
85
  summary: Interact with the Fulfil.io API