hive_api 0.1.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 +7 -0
- data/CHANGELOG.md +19 -0
- data/LICENSE.txt +21 -0
- data/README.md +161 -0
- data/lib/hive_api/client.rb +58 -0
- data/lib/hive_api/errors.rb +29 -0
- data/lib/hive_api/object.rb +79 -0
- data/lib/hive_api/objects/pagination_response.rb +7 -0
- data/lib/hive_api/objects/return_list_response.rb +16 -0
- data/lib/hive_api/objects/return_response.rb +7 -0
- data/lib/hive_api/objects/return_rules_response.rb +7 -0
- data/lib/hive_api/resource.rb +111 -0
- data/lib/hive_api/resources/return_rules_resource.rb +9 -0
- data/lib/hive_api/resources/returns_resource.rb +67 -0
- data/lib/hive_api/version.rb +5 -0
- data/lib/hive_api.rb +25 -0
- metadata +160 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 07111f77209d4cbfd69880e122f8d2972210928e22a11165a9522218ed389bca
|
|
4
|
+
data.tar.gz: 184d9565e9976454e373ec36e6d881377864a72616245827fd3a94ec463151e4
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 5725404f0d899889a8cf46a0375df662434906b39d1c4fe7e89407ec4b96b63fc8199b20c4d3a56871575b2e69f9ed0ecd49e68ffd5974c530825bf0095ca77d
|
|
7
|
+
data.tar.gz: 0adf09a3cd8ef6ea8db79c9cbc833966533c39f03bfbd70fe5a36644d9db2c078ef0f516bfc20fbe622e4b079d3cc242e68fe90f2ceb106ff8877e59353dbb3c
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [Unreleased]
|
|
4
|
+
|
|
5
|
+
## [0.1.0] - 2026-09-15
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Rails-independent Hive API client foundation with fixed production and staging environments, defaulting safely to staging.
|
|
10
|
+
- Bounded Faraday connections with injectable adapters and safe JSON middleware.
|
|
11
|
+
- Shared resource and response-object foundations.
|
|
12
|
+
- Merchant-scoped bearer authentication and typed, credential-safe API errors.
|
|
13
|
+
- Hive rate-limit metadata and transport-error wrapping without automatic retries or logging.
|
|
14
|
+
- Return rules, paginated return listing, safe explicit page traversal, and individual return retrieval.
|
|
15
|
+
- Immutable, dedicated return response objects with deeply frozen provider evidence.
|
|
16
|
+
|
|
17
|
+
### Out of scope
|
|
18
|
+
|
|
19
|
+
- Webhook handling, automatic polling, retries, caching, persistence, and business workflows remain caller-owned.
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 PostCo
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# HiveAPI
|
|
2
|
+
|
|
3
|
+
Rails-independent Ruby client for the [Hive Merchant API](https://developers.hive.app/).
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Add the gem to your Gemfile:
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
gem "hive_api", "~> 0.1.0"
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Then run `bundle install`.
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
### Initialize a client
|
|
18
|
+
|
|
19
|
+
Require the gem and build a client. It defaults to Hive's staging API:
|
|
20
|
+
|
|
21
|
+
```ruby
|
|
22
|
+
require "hive_api"
|
|
23
|
+
|
|
24
|
+
client = HiveAPI::Client.new(
|
|
25
|
+
api_token: ENV.fetch("HIVE_API_TOKEN"),
|
|
26
|
+
sandbox: true # Staging (the default)
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
# Select production explicitly:
|
|
30
|
+
production_client = HiveAPI::Client.new(
|
|
31
|
+
api_token: ENV.fetch("HIVE_API_TOKEN"),
|
|
32
|
+
sandbox: false
|
|
33
|
+
)
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
The client does not accept a custom base URL and it does not use global configuration. Connections use bounded connect and request timeouts, defaulting to 5 and 15 seconds respectively:
|
|
37
|
+
|
|
38
|
+
```ruby
|
|
39
|
+
HiveAPI::Client.new(
|
|
40
|
+
api_token: ENV.fetch("HIVE_API_TOKEN"),
|
|
41
|
+
sandbox: true,
|
|
42
|
+
open_timeout: 2,
|
|
43
|
+
timeout: 10
|
|
44
|
+
)
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Every request sends the API token as a bearer credential in the `Authorization` header. The gem
|
|
48
|
+
does not acquire, refresh, rotate, log, or retry credentials or requests automatically.
|
|
49
|
+
|
|
50
|
+
### Response objects
|
|
51
|
+
|
|
52
|
+
Response objects expose provider keys as snake-case Ruby methods, including nested hashes and arrays. The original provider payload remains available as a deeply frozen snapshot through `raw`, while `to_h` returns plain recursive hashes and arrays.
|
|
53
|
+
|
|
54
|
+
```ruby
|
|
55
|
+
response = HiveAPI::Base.new(
|
|
56
|
+
"returnId" => "return-123",
|
|
57
|
+
"lineItems" => [{"merchantSKU" => "SKU-1"}]
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
response.return_id # => "return-123"
|
|
61
|
+
response.line_items.first.merchant_sku # => "SKU-1"
|
|
62
|
+
response.raw.frozen? # => true
|
|
63
|
+
response.to_h # => {"return_id" => "return-123", ...}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Returns
|
|
67
|
+
|
|
68
|
+
The client exposes Hive's return rules and returns without applying merchant policy or traversing
|
|
69
|
+
pages automatically:
|
|
70
|
+
|
|
71
|
+
```ruby
|
|
72
|
+
# GET /return_rules
|
|
73
|
+
rules = client.return_rules.get
|
|
74
|
+
rules.send_back_address.postal_code
|
|
75
|
+
rules.default_rules.a
|
|
76
|
+
rules.sku_rules.first.sku.sku_code
|
|
77
|
+
|
|
78
|
+
# GET /returns
|
|
79
|
+
page = client.returns.list(
|
|
80
|
+
sales_channel_id_in: [101, 202],
|
|
81
|
+
created_at_gt: "2026-09-01T00:00:00Z",
|
|
82
|
+
created_at_lt: "2026-10-01T00:00:00Z",
|
|
83
|
+
created_at_gte: "2026-09-02T00:00:00Z",
|
|
84
|
+
created_at_lte: "2026-09-30T23:59:59Z",
|
|
85
|
+
limit: 50
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
page.data.each do |hive_return|
|
|
89
|
+
hive_return.order.merchant_order_id
|
|
90
|
+
hive_return.announced_items
|
|
91
|
+
hive_return.handled_items
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# GET /returns/{id}
|
|
95
|
+
hive_return = client.returns.find(id: 5555)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
`sales_channel_id_in:` accepts either an array or a comma-separated value. The timestamp filters
|
|
99
|
+
are passed to Hive unchanged, so callers should provide ISO 8601 values with an explicit timezone.
|
|
100
|
+
Nil filters are omitted.
|
|
101
|
+
|
|
102
|
+
List responses expose `pagination.first_page_url`, `pagination.limit`, and
|
|
103
|
+
`pagination.next_page_url`. Fetch another page explicitly when Hive provides one:
|
|
104
|
+
|
|
105
|
+
```ruby
|
|
106
|
+
next_page = client.returns.list_page(url: page.pagination.next_page_url)
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
`list_page` only follows URLs on the client's selected Hive environment and exact Returns path.
|
|
110
|
+
All response objects are immutable and retain their deeply frozen provider payload through `raw`.
|
|
111
|
+
|
|
112
|
+
### Error handling
|
|
113
|
+
|
|
114
|
+
Unsuccessful responses raise a typed `HiveAPI::Error` subclass:
|
|
115
|
+
|
|
116
|
+
```ruby
|
|
117
|
+
begin
|
|
118
|
+
client.returns.find(id: 5555)
|
|
119
|
+
rescue HiveAPI::AuthenticationError => error
|
|
120
|
+
# 401/403 responses
|
|
121
|
+
puts error.status_code
|
|
122
|
+
rescue HiveAPI::ValidationError => error
|
|
123
|
+
# 400 responses
|
|
124
|
+
puts error.status_code
|
|
125
|
+
rescue HiveAPI::NotFoundError => error
|
|
126
|
+
# 404 responses
|
|
127
|
+
puts error.status_code
|
|
128
|
+
rescue HiveAPI::RateLimitError => error
|
|
129
|
+
# 429 responses
|
|
130
|
+
puts error.retry_after
|
|
131
|
+
rescue HiveAPI::ServerError => error
|
|
132
|
+
# 500-599 responses
|
|
133
|
+
puts error.status_code
|
|
134
|
+
rescue HiveAPI::APIError => error
|
|
135
|
+
# Other HTTP responses and transport failures
|
|
136
|
+
warn error.message
|
|
137
|
+
end
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Errors expose the HTTP status and Hive's `X-Rate-Limit-Used`, `X-Rate-Limit-Max`, and
|
|
141
|
+
`Retry-After` metadata when present. Transport failures retain the original Faraday exception as
|
|
142
|
+
their cause. Error messages redact bearer credentials and the configured token.
|
|
143
|
+
|
|
144
|
+
### Caller responsibilities
|
|
145
|
+
|
|
146
|
+
This gem is deliberately a thin API client. Webhook handling, automatic polling, retries, caching,
|
|
147
|
+
persistence, and business workflows remain caller-owned. In particular, callers decide when and
|
|
148
|
+
how to traverse subsequent pages; the gem never retries or polls automatically.
|
|
149
|
+
|
|
150
|
+
## Development
|
|
151
|
+
|
|
152
|
+
```sh
|
|
153
|
+
bundle install
|
|
154
|
+
bundle exec rspec
|
|
155
|
+
bundle exec standardrb
|
|
156
|
+
gem build hive_api.gemspec
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## License
|
|
160
|
+
|
|
161
|
+
MIT License. See [LICENSE.txt](LICENSE.txt).
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "faraday"
|
|
4
|
+
|
|
5
|
+
module HiveAPI
|
|
6
|
+
class Client
|
|
7
|
+
LIVE_BASE_URL = "https://app.hive.app/merchant_api/v2/"
|
|
8
|
+
TEST_BASE_URL = "https://staging.app.hive.app/merchant_api/v2/"
|
|
9
|
+
DEFAULT_OPEN_TIMEOUT = 5
|
|
10
|
+
DEFAULT_TIMEOUT = 15
|
|
11
|
+
|
|
12
|
+
attr_reader :adapter, :api_token, :open_timeout, :timeout
|
|
13
|
+
|
|
14
|
+
def initialize(api_token:, sandbox: true, adapter: Faraday.default_adapter,
|
|
15
|
+
open_timeout: DEFAULT_OPEN_TIMEOUT, timeout: DEFAULT_TIMEOUT)
|
|
16
|
+
validate_api_token!(api_token)
|
|
17
|
+
|
|
18
|
+
@api_token = api_token
|
|
19
|
+
@sandbox = sandbox
|
|
20
|
+
@adapter = adapter
|
|
21
|
+
@open_timeout = open_timeout
|
|
22
|
+
@timeout = timeout
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def connection
|
|
26
|
+
@connection ||= Faraday.new do |connection|
|
|
27
|
+
connection.url_prefix = sandbox? ? TEST_BASE_URL : LIVE_BASE_URL
|
|
28
|
+
connection.options.open_timeout = open_timeout
|
|
29
|
+
connection.options.timeout = timeout
|
|
30
|
+
connection.headers["Authorization"] = "Bearer #{api_token}"
|
|
31
|
+
connection.headers["Accept"] = "application/json"
|
|
32
|
+
connection.request :json
|
|
33
|
+
connection.response :json, content_type: /\bjson/
|
|
34
|
+
connection.adapter adapter
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def return_rules
|
|
39
|
+
@return_rules ||= ReturnRulesResource.new(self)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def returns
|
|
43
|
+
@returns ||= ReturnsResource.new(self)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
def validate_api_token!(api_token)
|
|
49
|
+
return if api_token.is_a?(String) && !api_token.strip.empty?
|
|
50
|
+
|
|
51
|
+
raise ArgumentError, "api_token must be a non-empty String"
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def sandbox?
|
|
55
|
+
@sandbox
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module HiveAPI
|
|
4
|
+
class Error < StandardError
|
|
5
|
+
attr_reader :response, :status_code, :rate_limit_used, :rate_limit_max, :retry_after
|
|
6
|
+
|
|
7
|
+
def initialize(message = nil, response: nil, status_code: nil, rate_limit_used: nil,
|
|
8
|
+
rate_limit_max: nil, retry_after: nil)
|
|
9
|
+
super(message)
|
|
10
|
+
@response = response
|
|
11
|
+
@status_code = status_code
|
|
12
|
+
@rate_limit_used = rate_limit_used
|
|
13
|
+
@rate_limit_max = rate_limit_max
|
|
14
|
+
@retry_after = retry_after
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
class APIError < Error; end
|
|
19
|
+
|
|
20
|
+
class AuthenticationError < Error; end
|
|
21
|
+
|
|
22
|
+
class ValidationError < Error; end
|
|
23
|
+
|
|
24
|
+
class NotFoundError < Error; end
|
|
25
|
+
|
|
26
|
+
class RateLimitError < Error; end
|
|
27
|
+
|
|
28
|
+
class ServerError < Error; end
|
|
29
|
+
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "ostruct"
|
|
4
|
+
require "active_support/core_ext/string/inflections"
|
|
5
|
+
|
|
6
|
+
module HiveAPI
|
|
7
|
+
class Base < OpenStruct
|
|
8
|
+
attr_reader :original_response
|
|
9
|
+
|
|
10
|
+
def self.new(attributes)
|
|
11
|
+
return attributes.map { |item| super(item) }.freeze if attributes.is_a?(Array)
|
|
12
|
+
|
|
13
|
+
super
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def initialize(attributes)
|
|
17
|
+
@original_response = deep_freeze(attributes)
|
|
18
|
+
super(to_ostruct(attributes))
|
|
19
|
+
freeze
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def to_hash
|
|
23
|
+
ostruct_to_hash(self)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
alias_method :to_h, :to_hash
|
|
27
|
+
|
|
28
|
+
def raw
|
|
29
|
+
@original_response
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def to_ostruct(object, key: nil)
|
|
35
|
+
case object
|
|
36
|
+
when Hash
|
|
37
|
+
object_class = nested_object_class(key)
|
|
38
|
+
return object_class.new(object) if object_class
|
|
39
|
+
|
|
40
|
+
OpenStruct.new(
|
|
41
|
+
object.transform_keys { |key| key.to_s.underscore }
|
|
42
|
+
.to_h { |nested_key, value| [nested_key, to_ostruct(value, key: nested_key)] }
|
|
43
|
+
).freeze
|
|
44
|
+
when Array
|
|
45
|
+
object.map { |value| to_ostruct(value, key: key) }.freeze
|
|
46
|
+
else
|
|
47
|
+
object.respond_to?(:freeze) ? object.freeze : object
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def nested_object_class(_key)
|
|
52
|
+
nil
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def deep_freeze(object)
|
|
56
|
+
case object
|
|
57
|
+
when Hash then object.transform_values { |value| deep_freeze(value) }.freeze
|
|
58
|
+
when Array then object.map { |item| deep_freeze(item) }.freeze
|
|
59
|
+
else object.respond_to?(:freeze) ? object.freeze : object
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def ostruct_to_hash(object)
|
|
64
|
+
case object
|
|
65
|
+
when OpenStruct
|
|
66
|
+
object.each_pair.to_h
|
|
67
|
+
.transform_keys(&:to_s)
|
|
68
|
+
.transform_values { |value| ostruct_to_hash(value) }
|
|
69
|
+
when Array
|
|
70
|
+
object.map { |value| ostruct_to_hash(value) }
|
|
71
|
+
when Hash
|
|
72
|
+
object.transform_keys(&:to_s)
|
|
73
|
+
.transform_values { |value| ostruct_to_hash(value) }
|
|
74
|
+
else
|
|
75
|
+
object
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module HiveAPI
|
|
4
|
+
module Objects
|
|
5
|
+
class ReturnListResponse < Base
|
|
6
|
+
private
|
|
7
|
+
|
|
8
|
+
def nested_object_class(key)
|
|
9
|
+
case key.to_s
|
|
10
|
+
when "data" then ReturnResponse
|
|
11
|
+
when "pagination" then PaginationResponse
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module HiveAPI
|
|
6
|
+
class Resource
|
|
7
|
+
attr_reader :client
|
|
8
|
+
|
|
9
|
+
def initialize(client)
|
|
10
|
+
@client = client
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
def get_request(path, params: {}, headers: {})
|
|
16
|
+
handle_response(client.connection.get(path, params, headers))
|
|
17
|
+
rescue Faraday::Error => error
|
|
18
|
+
raise_transport_error(error)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def post_request(path, body:, headers: {})
|
|
22
|
+
handle_response(client.connection.post(path, body, headers))
|
|
23
|
+
rescue Faraday::Error => error
|
|
24
|
+
raise_transport_error(error)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def handle_response(response)
|
|
28
|
+
body = parse_body(response.body)
|
|
29
|
+
return body if response.status.between?(200, 299)
|
|
30
|
+
|
|
31
|
+
error_class, prefix = error_mapping(response.status)
|
|
32
|
+
message = "#{prefix} (HTTP #{response.status}): #{extract_error_message(body)}"
|
|
33
|
+
raise error_class.new(
|
|
34
|
+
message,
|
|
35
|
+
response: response,
|
|
36
|
+
status_code: response.status,
|
|
37
|
+
rate_limit_used: numeric_header(response, "x-rate-limit-used"),
|
|
38
|
+
rate_limit_max: numeric_header(response, "x-rate-limit-max"),
|
|
39
|
+
retry_after: numeric_header(response, "retry-after")
|
|
40
|
+
)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def parse_body(body)
|
|
44
|
+
return body unless body.is_a?(String)
|
|
45
|
+
|
|
46
|
+
JSON.parse(body)
|
|
47
|
+
rescue JSON::ParserError
|
|
48
|
+
body
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def extract_error_message(body)
|
|
52
|
+
message = case body
|
|
53
|
+
when Hash
|
|
54
|
+
approved_error_message(body)
|
|
55
|
+
when String
|
|
56
|
+
body
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
redact_credentials(message || "Unknown error")
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def approved_error_message(body)
|
|
63
|
+
[body["message"], body[:message], body["error"], body[:error]]
|
|
64
|
+
.find { |value| value.is_a?(String) } || errors_message(body)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def errors_message(body)
|
|
68
|
+
errors = body["errors"] || body[:errors]
|
|
69
|
+
return errors if errors.is_a?(String)
|
|
70
|
+
return unless errors.is_a?(Array)
|
|
71
|
+
|
|
72
|
+
messages = errors.select { |error| error.is_a?(String) }
|
|
73
|
+
messages.join(", ") unless messages.empty?
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def redact_credentials(message)
|
|
77
|
+
redacted = message.dup
|
|
78
|
+
redacted.gsub!(/\bAuthorization\s*:\s*[^,;]+/i, "[REDACTED]")
|
|
79
|
+
redacted.gsub!(/\bBearer\s+[^\s,;]+/i, "[REDACTED]")
|
|
80
|
+
redacted.gsub!(client.api_token, "[REDACTED]")
|
|
81
|
+
redacted
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def error_mapping(status)
|
|
85
|
+
case status
|
|
86
|
+
when 400 then [ValidationError, "Bad request"]
|
|
87
|
+
when 401, 403 then [AuthenticationError, "Authentication failed"]
|
|
88
|
+
when 404 then [NotFoundError, "Resource not found"]
|
|
89
|
+
when 429 then [RateLimitError, "Rate limited"]
|
|
90
|
+
when 500..599 then [ServerError, "Server error"]
|
|
91
|
+
else [APIError, "API error"]
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def numeric_header(response, name)
|
|
96
|
+
value = response.headers[name]
|
|
97
|
+
return if value.nil?
|
|
98
|
+
|
|
99
|
+
Integer(value, exception: false) || Float(value, exception: false)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def raise_transport_error(error)
|
|
103
|
+
wrapped_error = APIError.new(
|
|
104
|
+
"Network request failed",
|
|
105
|
+
response: error.response,
|
|
106
|
+
status_code: error.response_status
|
|
107
|
+
)
|
|
108
|
+
raise wrapped_error, cause: error
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "uri"
|
|
4
|
+
|
|
5
|
+
module HiveAPI
|
|
6
|
+
class ReturnsResource < Resource
|
|
7
|
+
INVALID_PAGE_URL_MESSAGE = "url must be a Returns page URL for the selected Hive environment"
|
|
8
|
+
|
|
9
|
+
def list(
|
|
10
|
+
sales_channel_id_in: nil,
|
|
11
|
+
created_at_gt: nil,
|
|
12
|
+
created_at_lt: nil,
|
|
13
|
+
created_at_gte: nil,
|
|
14
|
+
created_at_lte: nil,
|
|
15
|
+
limit: nil
|
|
16
|
+
)
|
|
17
|
+
params = {
|
|
18
|
+
"sales_channel_id[in]" => serialize_sales_channel_ids(sales_channel_id_in),
|
|
19
|
+
"created_at[gt]" => created_at_gt,
|
|
20
|
+
"created_at[lt]" => created_at_lt,
|
|
21
|
+
"created_at[gte]" => created_at_gte,
|
|
22
|
+
"created_at[lte]" => created_at_lte,
|
|
23
|
+
"limit" => limit
|
|
24
|
+
}.compact
|
|
25
|
+
|
|
26
|
+
build_list_response(get_request("returns", params: params))
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def list_page(url:)
|
|
30
|
+
validate_page_url!(url)
|
|
31
|
+
build_list_response(get_request(url))
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def find(id:)
|
|
35
|
+
Objects::ReturnResponse.new(get_request("returns/#{id}"))
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def build_list_response(data)
|
|
41
|
+
Objects::ReturnListResponse.new(data)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def serialize_sales_channel_ids(value)
|
|
45
|
+
value.is_a?(Array) ? value.join(",") : value
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def validate_page_url!(url)
|
|
49
|
+
page_uri = URI.parse(url)
|
|
50
|
+
base_uri = URI.parse(client.connection.url_prefix.to_s)
|
|
51
|
+
returns_path = URI.join(base_uri.to_s, "returns").path
|
|
52
|
+
|
|
53
|
+
valid = url.is_a?(String) &&
|
|
54
|
+
page_uri.absolute? &&
|
|
55
|
+
page_uri.userinfo.nil? &&
|
|
56
|
+
page_uri.fragment.nil? &&
|
|
57
|
+
page_uri.scheme == base_uri.scheme &&
|
|
58
|
+
page_uri.host == base_uri.host &&
|
|
59
|
+
page_uri.port == base_uri.port &&
|
|
60
|
+
page_uri.path == returns_path
|
|
61
|
+
|
|
62
|
+
raise ArgumentError, INVALID_PAGE_URL_MESSAGE unless valid
|
|
63
|
+
rescue URI::Error, TypeError
|
|
64
|
+
raise ArgumentError, INVALID_PAGE_URL_MESSAGE
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
data/lib/hive_api.rb
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "hive_api/version"
|
|
4
|
+
|
|
5
|
+
module HiveAPI
|
|
6
|
+
autoload :Client, "hive_api/client"
|
|
7
|
+
autoload :Base, "hive_api/object"
|
|
8
|
+
autoload :Resource, "hive_api/resource"
|
|
9
|
+
autoload :Error, "hive_api/errors"
|
|
10
|
+
autoload :APIError, "hive_api/errors"
|
|
11
|
+
autoload :AuthenticationError, "hive_api/errors"
|
|
12
|
+
autoload :ValidationError, "hive_api/errors"
|
|
13
|
+
autoload :NotFoundError, "hive_api/errors"
|
|
14
|
+
autoload :RateLimitError, "hive_api/errors"
|
|
15
|
+
autoload :ServerError, "hive_api/errors"
|
|
16
|
+
autoload :ReturnRulesResource, "hive_api/resources/return_rules_resource"
|
|
17
|
+
autoload :ReturnsResource, "hive_api/resources/returns_resource"
|
|
18
|
+
|
|
19
|
+
module Objects
|
|
20
|
+
autoload :PaginationResponse, "hive_api/objects/pagination_response"
|
|
21
|
+
autoload :ReturnListResponse, "hive_api/objects/return_list_response"
|
|
22
|
+
autoload :ReturnResponse, "hive_api/objects/return_response"
|
|
23
|
+
autoload :ReturnRulesResponse, "hive_api/objects/return_rules_response"
|
|
24
|
+
end
|
|
25
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: hive_api
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- PostCo
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-09-15 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: faraday
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '2.0'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '2.0'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: faraday-net_http
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - ">="
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '2.0'
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - ">="
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '2.0'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: activesupport
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - ">="
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '7.0'
|
|
48
|
+
type: :runtime
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - ">="
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '7.0'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: rake
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - ">="
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '0'
|
|
62
|
+
type: :development
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - ">="
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '0'
|
|
69
|
+
- !ruby/object:Gem::Dependency
|
|
70
|
+
name: rspec
|
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - "~>"
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: '3.0'
|
|
76
|
+
type: :development
|
|
77
|
+
prerelease: false
|
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - "~>"
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '3.0'
|
|
83
|
+
- !ruby/object:Gem::Dependency
|
|
84
|
+
name: webmock
|
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
|
86
|
+
requirements:
|
|
87
|
+
- - "~>"
|
|
88
|
+
- !ruby/object:Gem::Version
|
|
89
|
+
version: '3.0'
|
|
90
|
+
type: :development
|
|
91
|
+
prerelease: false
|
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
93
|
+
requirements:
|
|
94
|
+
- - "~>"
|
|
95
|
+
- !ruby/object:Gem::Version
|
|
96
|
+
version: '3.0'
|
|
97
|
+
- !ruby/object:Gem::Dependency
|
|
98
|
+
name: standard
|
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
|
100
|
+
requirements:
|
|
101
|
+
- - ">="
|
|
102
|
+
- !ruby/object:Gem::Version
|
|
103
|
+
version: '0'
|
|
104
|
+
type: :development
|
|
105
|
+
prerelease: false
|
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
107
|
+
requirements:
|
|
108
|
+
- - ">="
|
|
109
|
+
- !ruby/object:Gem::Version
|
|
110
|
+
version: '0'
|
|
111
|
+
description: A small Ruby client foundation for integrating with Hive without depending
|
|
112
|
+
on Rails.
|
|
113
|
+
email:
|
|
114
|
+
- engineering@postco.co
|
|
115
|
+
executables: []
|
|
116
|
+
extensions: []
|
|
117
|
+
extra_rdoc_files: []
|
|
118
|
+
files:
|
|
119
|
+
- CHANGELOG.md
|
|
120
|
+
- LICENSE.txt
|
|
121
|
+
- README.md
|
|
122
|
+
- lib/hive_api.rb
|
|
123
|
+
- lib/hive_api/client.rb
|
|
124
|
+
- lib/hive_api/errors.rb
|
|
125
|
+
- lib/hive_api/object.rb
|
|
126
|
+
- lib/hive_api/objects/pagination_response.rb
|
|
127
|
+
- lib/hive_api/objects/return_list_response.rb
|
|
128
|
+
- lib/hive_api/objects/return_response.rb
|
|
129
|
+
- lib/hive_api/objects/return_rules_response.rb
|
|
130
|
+
- lib/hive_api/resource.rb
|
|
131
|
+
- lib/hive_api/resources/return_rules_resource.rb
|
|
132
|
+
- lib/hive_api/resources/returns_resource.rb
|
|
133
|
+
- lib/hive_api/version.rb
|
|
134
|
+
homepage: https://github.com/PostCo/hive_api
|
|
135
|
+
licenses:
|
|
136
|
+
- MIT
|
|
137
|
+
metadata:
|
|
138
|
+
source_code_uri: https://github.com/PostCo/hive_api
|
|
139
|
+
changelog_uri: https://github.com/PostCo/hive_api/blob/main/CHANGELOG.md
|
|
140
|
+
rubygems_mfa_required: 'true'
|
|
141
|
+
post_install_message:
|
|
142
|
+
rdoc_options: []
|
|
143
|
+
require_paths:
|
|
144
|
+
- lib
|
|
145
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
146
|
+
requirements:
|
|
147
|
+
- - ">="
|
|
148
|
+
- !ruby/object:Gem::Version
|
|
149
|
+
version: 3.3.0
|
|
150
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
151
|
+
requirements:
|
|
152
|
+
- - ">="
|
|
153
|
+
- !ruby/object:Gem::Version
|
|
154
|
+
version: '0'
|
|
155
|
+
requirements: []
|
|
156
|
+
rubygems_version: 3.5.22
|
|
157
|
+
signing_key:
|
|
158
|
+
specification_version: 4
|
|
159
|
+
summary: Rails-independent Ruby client for the Hive Merchant API
|
|
160
|
+
test_files: []
|