generatorlabs 2.0.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/LICENSE +21 -0
- data/README.md +546 -0
- data/lib/generatorlabs/cert.rb +220 -0
- data/lib/generatorlabs/client.rb +128 -0
- data/lib/generatorlabs/config.rb +85 -0
- data/lib/generatorlabs/contact.rb +192 -0
- data/lib/generatorlabs/rate_limit_info.rb +30 -0
- data/lib/generatorlabs/rbl.rb +297 -0
- data/lib/generatorlabs/request_handler.rb +213 -0
- data/lib/generatorlabs/response.rb +62 -0
- data/lib/generatorlabs/version.rb +15 -0
- data/lib/generatorlabs/webhook.rb +70 -0
- data/lib/generatorlabs.rb +26 -0
- metadata +90 -0
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
#
|
|
4
|
+
# This file is part of the Generator Labs Ruby SDK package.
|
|
5
|
+
#
|
|
6
|
+
# (c) Generator Labs <support@generatorlabs.com>
|
|
7
|
+
#
|
|
8
|
+
# For the full copyright and license information, please view the LICENSE
|
|
9
|
+
# file that was distributed with this source code.
|
|
10
|
+
#
|
|
11
|
+
|
|
12
|
+
module GeneratorLabs
|
|
13
|
+
# RBL monitoring API namespace
|
|
14
|
+
class RBL
|
|
15
|
+
def initialize(handler)
|
|
16
|
+
@handler = handler
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Access host management operations
|
|
20
|
+
# @return [RBLHosts]
|
|
21
|
+
def hosts
|
|
22
|
+
@hosts ||= RBLHosts.new(@handler)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Access profile management operations
|
|
26
|
+
# @return [RBLProfiles]
|
|
27
|
+
def profiles
|
|
28
|
+
@profiles ||= RBLProfiles.new(@handler)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Access source management operations
|
|
32
|
+
# @return [RBLSources]
|
|
33
|
+
def sources
|
|
34
|
+
@sources ||= RBLSources.new(@handler)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Access manual RBL check operations
|
|
38
|
+
# @return [RBLCheck]
|
|
39
|
+
def check
|
|
40
|
+
@check ||= RBLCheck.new(@handler)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Get current RBL listings for monitored hosts
|
|
44
|
+
# @return [Hash] Listing information
|
|
45
|
+
def listings
|
|
46
|
+
@handler.get('rbl/listings')
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Manual RBL check operations
|
|
51
|
+
class RBLCheck
|
|
52
|
+
def initialize(handler)
|
|
53
|
+
@handler = handler
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Start a manual RBL check
|
|
57
|
+
# @param params [Hash] Check parameters (host, callback, details)
|
|
58
|
+
# @return [Hash] Check result with check ID
|
|
59
|
+
def start(params)
|
|
60
|
+
@handler.post('rbl/check/start', params)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Get the status of a manual RBL check
|
|
64
|
+
# @param id [String] Check ID
|
|
65
|
+
# @param params [Hash] Optional parameters (details)
|
|
66
|
+
# @return [Hash] Check status
|
|
67
|
+
def status(id, params = {})
|
|
68
|
+
@handler.get("rbl/check/status/#{id}", params)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# RBL host management
|
|
73
|
+
class RBLHosts
|
|
74
|
+
def initialize(handler)
|
|
75
|
+
@handler = handler
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Get hosts (all, by ID, or by array of IDs)
|
|
79
|
+
# @param ids [nil, String, Integer, Hash, Array] Optional ID(s) or parameters
|
|
80
|
+
# @return [Hash] Host data
|
|
81
|
+
def get(*ids)
|
|
82
|
+
return @handler.get('rbl/hosts') if ids.empty?
|
|
83
|
+
|
|
84
|
+
# Check if first argument is a hash (parameters)
|
|
85
|
+
return @handler.get('rbl/hosts', ids.first) if ids.first.is_a?(Hash)
|
|
86
|
+
|
|
87
|
+
return @handler.get("rbl/hosts/#{ids.first}") if ids.length == 1
|
|
88
|
+
|
|
89
|
+
@handler.get('rbl/hosts', { ids: ids.join(',') })
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Get all hosts with automatic pagination
|
|
93
|
+
# @param params [Hash] Optional parameters (e.g., page_size)
|
|
94
|
+
# @return [Array] All hosts across all pages
|
|
95
|
+
def get_all(params = {})
|
|
96
|
+
all_items = []
|
|
97
|
+
page = 1
|
|
98
|
+
params = params.dup
|
|
99
|
+
|
|
100
|
+
loop do
|
|
101
|
+
params[:page] = page
|
|
102
|
+
response = get(params)
|
|
103
|
+
hosts = response['hosts'] || []
|
|
104
|
+
|
|
105
|
+
all_items.concat(hosts)
|
|
106
|
+
|
|
107
|
+
break unless page < (response['total_pages'] || 1) && !hosts.empty?
|
|
108
|
+
|
|
109
|
+
page += 1
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
all_items
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Create a new monitored host
|
|
116
|
+
# @param params [Hash] Host parameters
|
|
117
|
+
# @return [Hash] Created host data
|
|
118
|
+
def create(params)
|
|
119
|
+
@handler.post('rbl/hosts', params)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Update a monitored host
|
|
123
|
+
# @param id [String, Integer] Host ID
|
|
124
|
+
# @param params [Hash] Updated parameters
|
|
125
|
+
# @return [Hash] Updated host data
|
|
126
|
+
def update(id, params)
|
|
127
|
+
@handler.put("rbl/hosts/#{id}", params)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Delete a monitored host
|
|
131
|
+
# @param id [String, Integer] Host ID
|
|
132
|
+
# @return [Hash] Deletion confirmation
|
|
133
|
+
def delete(id)
|
|
134
|
+
@handler.delete("rbl/hosts/#{id}")
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Pause monitoring for a host
|
|
138
|
+
# @param id [String, Integer] Host ID
|
|
139
|
+
# @return [Hash] Response
|
|
140
|
+
def pause(id)
|
|
141
|
+
@handler.post("rbl/hosts/#{id}/pause")
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Resume monitoring for a host
|
|
145
|
+
# @param id [String, Integer] Host ID
|
|
146
|
+
# @return [Hash] Response
|
|
147
|
+
def resume(id)
|
|
148
|
+
@handler.post("rbl/hosts/#{id}/resume")
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# RBL profile management
|
|
153
|
+
class RBLProfiles
|
|
154
|
+
def initialize(handler)
|
|
155
|
+
@handler = handler
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# Get profiles (all, by ID, or by array of IDs)
|
|
159
|
+
# @param ids [nil, String, Integer, Hash, Array] Optional ID(s) or parameters
|
|
160
|
+
# @return [Hash] Profile data
|
|
161
|
+
def get(*ids)
|
|
162
|
+
return @handler.get('rbl/profiles') if ids.empty?
|
|
163
|
+
|
|
164
|
+
# Check if first argument is a hash (parameters)
|
|
165
|
+
return @handler.get('rbl/profiles', ids.first) if ids.first.is_a?(Hash)
|
|
166
|
+
|
|
167
|
+
return @handler.get("rbl/profiles/#{ids.first}") if ids.length == 1
|
|
168
|
+
|
|
169
|
+
@handler.get('rbl/profiles', { ids: ids.join(',') })
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Get all profiles with automatic pagination
|
|
173
|
+
# @param params [Hash] Optional parameters (e.g., page_size)
|
|
174
|
+
# @return [Array] All profiles across all pages
|
|
175
|
+
def get_all(params = {})
|
|
176
|
+
all_items = []
|
|
177
|
+
page = 1
|
|
178
|
+
params = params.dup
|
|
179
|
+
|
|
180
|
+
loop do
|
|
181
|
+
params[:page] = page
|
|
182
|
+
response = get(params)
|
|
183
|
+
profiles = response['profiles'] || []
|
|
184
|
+
|
|
185
|
+
all_items.concat(profiles)
|
|
186
|
+
|
|
187
|
+
break unless page < (response['total_pages'] || 1) && !profiles.empty?
|
|
188
|
+
|
|
189
|
+
page += 1
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
all_items
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# Create a new monitoring profile
|
|
196
|
+
# @param params [Hash] Profile parameters
|
|
197
|
+
# @return [Hash] Created profile data
|
|
198
|
+
def create(params)
|
|
199
|
+
@handler.post('rbl/profiles', params)
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# Update a monitoring profile
|
|
203
|
+
# @param id [String, Integer] Profile ID
|
|
204
|
+
# @param params [Hash] Updated parameters
|
|
205
|
+
# @return [Hash] Updated profile data
|
|
206
|
+
def update(id, params)
|
|
207
|
+
@handler.put("rbl/profiles/#{id}", params)
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
# Delete a monitoring profile
|
|
211
|
+
# @param id [String, Integer] Profile ID
|
|
212
|
+
# @return [Hash] Deletion confirmation
|
|
213
|
+
def delete(id)
|
|
214
|
+
@handler.delete("rbl/profiles/#{id}")
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
# RBL source management
|
|
219
|
+
class RBLSources
|
|
220
|
+
def initialize(handler)
|
|
221
|
+
@handler = handler
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
# Get sources (all, by ID, or by array of IDs)
|
|
225
|
+
# @param ids [nil, String, Integer, Hash, Array] Optional ID(s) or parameters
|
|
226
|
+
# @return [Hash] Source data
|
|
227
|
+
def get(*ids)
|
|
228
|
+
return @handler.get('rbl/sources') if ids.empty?
|
|
229
|
+
|
|
230
|
+
# Check if first argument is a hash (parameters)
|
|
231
|
+
return @handler.get('rbl/sources', ids.first) if ids.first.is_a?(Hash)
|
|
232
|
+
|
|
233
|
+
return @handler.get("rbl/sources/#{ids.first}") if ids.length == 1
|
|
234
|
+
|
|
235
|
+
@handler.get('rbl/sources', { ids: ids.join(',') })
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
# Get all sources with automatic pagination
|
|
239
|
+
# @param params [Hash] Optional parameters (e.g., page_size)
|
|
240
|
+
# @return [Array] All sources across all pages
|
|
241
|
+
def get_all(params = {})
|
|
242
|
+
all_items = []
|
|
243
|
+
page = 1
|
|
244
|
+
params = params.dup
|
|
245
|
+
|
|
246
|
+
loop do
|
|
247
|
+
params[:page] = page
|
|
248
|
+
response = get(params)
|
|
249
|
+
sources = response['sources'] || []
|
|
250
|
+
|
|
251
|
+
all_items.concat(sources)
|
|
252
|
+
|
|
253
|
+
break unless page < (response['total_pages'] || 1) && !sources.empty?
|
|
254
|
+
|
|
255
|
+
page += 1
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
all_items
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
# Create a new RBL source
|
|
262
|
+
# @param params [Hash] Source parameters
|
|
263
|
+
# @return [Hash] Created source data
|
|
264
|
+
def create(params)
|
|
265
|
+
@handler.post('rbl/sources', params)
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
# Update an RBL source
|
|
269
|
+
# @param id [String, Integer] Source ID
|
|
270
|
+
# @param params [Hash] Updated parameters
|
|
271
|
+
# @return [Hash] Updated source data
|
|
272
|
+
def update(id, params)
|
|
273
|
+
@handler.put("rbl/sources/#{id}", params)
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
# Delete an RBL source
|
|
277
|
+
# @param id [String, Integer] Source ID
|
|
278
|
+
# @return [Hash] Deletion confirmation
|
|
279
|
+
def delete(id)
|
|
280
|
+
@handler.delete("rbl/sources/#{id}")
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
# Pause an RBL source
|
|
284
|
+
# @param id [String, Integer] Source ID
|
|
285
|
+
# @return [Hash] Response
|
|
286
|
+
def pause(id)
|
|
287
|
+
@handler.post("rbl/sources/#{id}/pause")
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
# Resume an RBL source
|
|
291
|
+
# @param id [String, Integer] Source ID
|
|
292
|
+
# @return [Hash] Response
|
|
293
|
+
def resume(id)
|
|
294
|
+
@handler.post("rbl/sources/#{id}/resume")
|
|
295
|
+
end
|
|
296
|
+
end
|
|
297
|
+
end
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
#
|
|
4
|
+
# This file is part of the Generator Labs Ruby SDK package.
|
|
5
|
+
#
|
|
6
|
+
# (c) Generator Labs <support@generatorlabs.com>
|
|
7
|
+
#
|
|
8
|
+
# For the full copyright and license information, please view the LICENSE
|
|
9
|
+
# file that was distributed with this source code.
|
|
10
|
+
#
|
|
11
|
+
|
|
12
|
+
require 'faraday'
|
|
13
|
+
require 'faraday/retry'
|
|
14
|
+
require 'json'
|
|
15
|
+
|
|
16
|
+
module GeneratorLabs
|
|
17
|
+
# Handles HTTP requests to the Generator Labs API.
|
|
18
|
+
#
|
|
19
|
+
# This class manages HTTP client configuration, authentication, retry logic,
|
|
20
|
+
# and error handling for all API requests. It automatically retries failed
|
|
21
|
+
# requests using exponential backoff on connection errors, 5xx server errors,
|
|
22
|
+
# and 429 rate limit errors.
|
|
23
|
+
class RequestHandler
|
|
24
|
+
# Initialize request handler with authentication and retry logic.
|
|
25
|
+
#
|
|
26
|
+
# This creates a Faraday connection with:
|
|
27
|
+
# - Automatic retries on connection errors, 5xx errors, and 429 rate limits
|
|
28
|
+
# - Exponential backoff based on config.retry_backoff
|
|
29
|
+
# - Configurable timeouts from config.timeout and config.connect_timeout
|
|
30
|
+
# - HTTP Basic Authentication using account_sid and auth_token
|
|
31
|
+
#
|
|
32
|
+
# @param account_sid [String] Account SID for authentication
|
|
33
|
+
# @param auth_token [String] Auth token for authentication
|
|
34
|
+
# @param config [Config] Configuration object with timeouts and retry settings
|
|
35
|
+
def initialize(account_sid, auth_token, config)
|
|
36
|
+
@account_sid = account_sid
|
|
37
|
+
@auth_token = auth_token
|
|
38
|
+
@config = config
|
|
39
|
+
|
|
40
|
+
# Create Faraday connection with retry middleware
|
|
41
|
+
@connection = Faraday.new(url: config.base_url) do |faraday_config|
|
|
42
|
+
faraday_config.request :url_encoded
|
|
43
|
+
faraday_config.request :authorization, :basic, account_sid, auth_token
|
|
44
|
+
# Retry with exponential backoff; faraday-retry respects Retry-After
|
|
45
|
+
# headers automatically on rate limit (429) responses
|
|
46
|
+
faraday_config.request :retry,
|
|
47
|
+
max: config.max_retries,
|
|
48
|
+
interval: 1.0,
|
|
49
|
+
backoff_factor: config.retry_backoff,
|
|
50
|
+
retry_statuses: [429, 500, 502, 503, 504],
|
|
51
|
+
methods: %i[get post put delete]
|
|
52
|
+
|
|
53
|
+
faraday_config.adapter Faraday.default_adapter
|
|
54
|
+
faraday_config.options.timeout = config.timeout
|
|
55
|
+
faraday_config.options.open_timeout = config.connect_timeout
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Make a GET request to the API.
|
|
60
|
+
#
|
|
61
|
+
# Parameters are sent as query string parameters. The request includes
|
|
62
|
+
# automatic retry logic for failures.
|
|
63
|
+
#
|
|
64
|
+
# @param path [String] API endpoint path (e.g., 'rbl/hosts')
|
|
65
|
+
# @param params [Hash, nil] Query parameters (e.g., {status: 'active'})
|
|
66
|
+
# @return [Response] Response wrapper with parsed data and rate limit info
|
|
67
|
+
# @raise [Error] if request fails after all retries
|
|
68
|
+
#
|
|
69
|
+
# @example
|
|
70
|
+
# handler.get('rbl/hosts', { status: 'active' })
|
|
71
|
+
def get(path, params = nil)
|
|
72
|
+
make_request(:get, path, params)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Make a POST request to the API.
|
|
76
|
+
#
|
|
77
|
+
# Parameters are sent as application/x-www-form-urlencoded data.
|
|
78
|
+
# The request includes automatic retry logic for failures.
|
|
79
|
+
#
|
|
80
|
+
# @param path [String] API endpoint path (e.g., 'rbl/hosts')
|
|
81
|
+
# @param params [Hash, nil] Request parameters (e.g., {name: 'My Host', host: '1.2.3.4'})
|
|
82
|
+
# @return [Response] Response wrapper with parsed data and rate limit info
|
|
83
|
+
# @raise [Error] if request fails after all retries
|
|
84
|
+
#
|
|
85
|
+
# @example
|
|
86
|
+
# handler.post('rbl/hosts', { name: 'My Host', host: '1.2.3.4', type: 'rbl' })
|
|
87
|
+
def post(path, params = nil)
|
|
88
|
+
make_request(:post, path, params)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Make a PUT request to the API.
|
|
92
|
+
#
|
|
93
|
+
# Parameters are sent as application/x-www-form-urlencoded data.
|
|
94
|
+
# The request includes automatic retry logic for failures.
|
|
95
|
+
#
|
|
96
|
+
# @param path [String] API endpoint path (e.g., 'rbl/hosts/HTxxxxxxxx')
|
|
97
|
+
# @param params [Hash, nil] Request parameters (e.g., {name: 'Updated Name'})
|
|
98
|
+
# @return [Response] Response wrapper with parsed data and rate limit info
|
|
99
|
+
# @raise [Error] if request fails after all retries
|
|
100
|
+
#
|
|
101
|
+
# @example
|
|
102
|
+
# handler.put('rbl/hosts/HT1a2b3c4d5e6f7890abcdef1234567890', { name: 'Updated Name' })
|
|
103
|
+
def put(path, params = nil)
|
|
104
|
+
make_request(:put, path, params)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Make a DELETE request to the API.
|
|
108
|
+
#
|
|
109
|
+
# No parameters are sent with DELETE requests. The request includes
|
|
110
|
+
# automatic retry logic for failures.
|
|
111
|
+
#
|
|
112
|
+
# @param path [String] API endpoint path (e.g., 'rbl/hosts/HTxxxxxxxx')
|
|
113
|
+
# @return [Response] Response wrapper with parsed data and rate limit info
|
|
114
|
+
# @raise [Error] if request fails after all retries
|
|
115
|
+
#
|
|
116
|
+
# @example
|
|
117
|
+
# handler.delete('rbl/hosts/HT1a2b3c4d5e6f7890abcdef1234567890')
|
|
118
|
+
def delete(path)
|
|
119
|
+
make_request(:delete, path, nil)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
private
|
|
123
|
+
|
|
124
|
+
# Make HTTP request to API with automatic retries.
|
|
125
|
+
#
|
|
126
|
+
# This method handles:
|
|
127
|
+
# - Building the request URL with .json extension
|
|
128
|
+
# - Adding query parameters for GET requests
|
|
129
|
+
# - Adding form data for POST/PUT/DELETE requests
|
|
130
|
+
# - Setting authentication headers
|
|
131
|
+
# - Executing the request with retry logic
|
|
132
|
+
# - Parsing JSON responses
|
|
133
|
+
# - Checking for API errors in the response
|
|
134
|
+
#
|
|
135
|
+
# Errors are raised if:
|
|
136
|
+
# - All retry attempts fail
|
|
137
|
+
# - The response body cannot be parsed
|
|
138
|
+
# - The API returns success=false in the response
|
|
139
|
+
# - The HTTP status code is >= 400
|
|
140
|
+
#
|
|
141
|
+
# @param method [Symbol] HTTP method (:get, :post, :put, :delete)
|
|
142
|
+
# @param path [String] API endpoint path
|
|
143
|
+
# @param params [Hash, nil] Request parameters
|
|
144
|
+
# @return [Response] Response wrapper with parsed data and rate limit info
|
|
145
|
+
# @raise [Error] if request fails or response is invalid
|
|
146
|
+
def make_request(method, path, params)
|
|
147
|
+
url = "#{path}.json"
|
|
148
|
+
params = convert_params(params)
|
|
149
|
+
|
|
150
|
+
response = @connection.send(method) do |req|
|
|
151
|
+
req.url url
|
|
152
|
+
req.headers['User-Agent'] = "GeneratorLabs-Ruby/#{VERSION}"
|
|
153
|
+
req.headers['Accept'] = 'application/json'
|
|
154
|
+
apply_params(req, method, params)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
data = parse_response(response)
|
|
158
|
+
|
|
159
|
+
#
|
|
160
|
+
# parse rate limit headers
|
|
161
|
+
#
|
|
162
|
+
rate_limit_info = nil
|
|
163
|
+
if response.headers['ratelimit-limit']
|
|
164
|
+
rate_limit_info = RateLimitInfo.new(
|
|
165
|
+
limit: response.headers['ratelimit-limit'],
|
|
166
|
+
remaining: response.headers['ratelimit-remaining'].to_i,
|
|
167
|
+
reset: response.headers['ratelimit-reset'].to_i
|
|
168
|
+
)
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
Response.new(data, rate_limit_info)
|
|
172
|
+
rescue Faraday::Error => e
|
|
173
|
+
raise Error, "API request failed: #{e.message}"
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# Convert array values to comma-separated strings for form encoding.
|
|
177
|
+
def convert_params(params)
|
|
178
|
+
return unless params
|
|
179
|
+
|
|
180
|
+
params.transform_values { |v| v.is_a?(Array) ? v.join(',') : v }
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# Apply parameters to request based on HTTP method.
|
|
184
|
+
def apply_params(req, method, params)
|
|
185
|
+
return unless params
|
|
186
|
+
|
|
187
|
+
if method == :get
|
|
188
|
+
req.params = params
|
|
189
|
+
else
|
|
190
|
+
req.body = params
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# Parse JSON response and check for errors.
|
|
195
|
+
def parse_response(response)
|
|
196
|
+
data = JSON.parse(response.body)
|
|
197
|
+
|
|
198
|
+
if data.is_a?(Hash) && data['success'] == false
|
|
199
|
+
error_msg = data.dig('error', 'message') || data['message'] || 'Unknown error'
|
|
200
|
+
raise Error, "API error: #{error_msg}"
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
if response.status >= 400
|
|
204
|
+
error_msg = data.dig('error', 'message') || data['message'] || "HTTP #{response.status} error"
|
|
205
|
+
raise Error, "API error: #{error_msg}"
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
data
|
|
209
|
+
rescue JSON::ParserError => e
|
|
210
|
+
raise Error, "Failed to parse JSON response: #{e.message}"
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
#
|
|
4
|
+
# This file is part of the Generator Labs Ruby SDK package.
|
|
5
|
+
#
|
|
6
|
+
# (c) Generator Labs <support@generatorlabs.com>
|
|
7
|
+
#
|
|
8
|
+
# For the full copyright and license information, please view the LICENSE
|
|
9
|
+
# file that was distributed with this source code.
|
|
10
|
+
#
|
|
11
|
+
|
|
12
|
+
module GeneratorLabs
|
|
13
|
+
# API response wrapper providing Hash-like access to response data and rate limit info.
|
|
14
|
+
#
|
|
15
|
+
# Supports bracket notation (response['key']) for backward compatibility
|
|
16
|
+
# with raw Hash returns, while also exposing rate_limit_info.
|
|
17
|
+
class Response
|
|
18
|
+
# @return [RateLimitInfo, nil] Rate limit information from response headers
|
|
19
|
+
attr_reader :rate_limit_info
|
|
20
|
+
|
|
21
|
+
# @param data [Hash] Parsed JSON response body
|
|
22
|
+
# @param rate_limit_info [RateLimitInfo, nil] Rate limit information
|
|
23
|
+
def initialize(data, rate_limit_info = nil)
|
|
24
|
+
@data = data
|
|
25
|
+
@rate_limit_info = rate_limit_info
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Access response data by key (Hash-like access).
|
|
29
|
+
# @param key [String] The key to look up
|
|
30
|
+
# @return [Object] The value
|
|
31
|
+
def [](key)
|
|
32
|
+
@data[key]
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Dig into nested response data.
|
|
36
|
+
# @param keys [Array] Keys to dig through
|
|
37
|
+
# @return [Object] The nested value
|
|
38
|
+
def dig(*keys)
|
|
39
|
+
@data.dig(*keys)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Check if a key exists in the response data.
|
|
43
|
+
# @param key [String] The key to check
|
|
44
|
+
# @return [Boolean]
|
|
45
|
+
def key?(key)
|
|
46
|
+
@data.key?(key)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Convert to a plain Hash.
|
|
50
|
+
# @return [Hash]
|
|
51
|
+
def to_h
|
|
52
|
+
@data
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Check if the response data is a Hash (for type compatibility).
|
|
56
|
+
# @param klass [Class] The class to check
|
|
57
|
+
# @return [Boolean]
|
|
58
|
+
def is_a?(klass)
|
|
59
|
+
klass == Hash ? true : super
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
#
|
|
4
|
+
# This file is part of the Generator Labs Ruby SDK package.
|
|
5
|
+
#
|
|
6
|
+
# (c) Generator Labs <support@generatorlabs.com>
|
|
7
|
+
#
|
|
8
|
+
# For the full copyright and license information, please view the LICENSE
|
|
9
|
+
# file that was distributed with this source code.
|
|
10
|
+
#
|
|
11
|
+
|
|
12
|
+
module GeneratorLabs
|
|
13
|
+
# Current version of the SDK
|
|
14
|
+
VERSION = '2.0.0'
|
|
15
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
#
|
|
4
|
+
# This file is part of the Generator Labs Ruby SDK package.
|
|
5
|
+
#
|
|
6
|
+
# (c) Generator Labs <support@generatorlabs.com>
|
|
7
|
+
#
|
|
8
|
+
# For the full copyright and license information, please view the LICENSE
|
|
9
|
+
# file that was distributed with this source code.
|
|
10
|
+
#
|
|
11
|
+
|
|
12
|
+
require 'openssl'
|
|
13
|
+
require 'json'
|
|
14
|
+
|
|
15
|
+
module GeneratorLabs
|
|
16
|
+
# Webhook signature verification utility.
|
|
17
|
+
#
|
|
18
|
+
# Verifies that incoming webhook requests were sent by Generator Labs
|
|
19
|
+
# using HMAC-SHA256 signatures.
|
|
20
|
+
#
|
|
21
|
+
# @example
|
|
22
|
+
# payload = GeneratorLabs::Webhook.verify(body, header, signing_secret)
|
|
23
|
+
class Webhook
|
|
24
|
+
# Default tolerance in seconds for timestamp validation (5 minutes).
|
|
25
|
+
DEFAULT_TOLERANCE = 300
|
|
26
|
+
|
|
27
|
+
# Verify a webhook signature and return the decoded payload.
|
|
28
|
+
#
|
|
29
|
+
# @param body [String] The raw request body string
|
|
30
|
+
# @param header [String] The X-Webhook-Signature header value
|
|
31
|
+
# @param secret [String] Your webhook's signing secret
|
|
32
|
+
# @param tolerance [Integer] Maximum age in seconds (0 to disable, default: 300)
|
|
33
|
+
# @return [Hash] The decoded JSON payload
|
|
34
|
+
# @raise [Error] if verification fails
|
|
35
|
+
def self.verify(body, header, secret, tolerance = DEFAULT_TOLERANCE)
|
|
36
|
+
raise Error, 'Missing X-Webhook-Signature header.' if header.nil? || header.empty?
|
|
37
|
+
|
|
38
|
+
# Parse the header: t=timestamp,v1=signature
|
|
39
|
+
parts = {}
|
|
40
|
+
header.split(',').each do |part|
|
|
41
|
+
key, value = part.split('=', 2)
|
|
42
|
+
parts[key] = value
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
raise Error, 'Invalid X-Webhook-Signature header format.' unless parts.key?('t') && parts.key?('v1')
|
|
46
|
+
|
|
47
|
+
# Check timestamp tolerance
|
|
48
|
+
if tolerance.positive? && (Time.now.to_i - parts['t'].to_i).abs > tolerance
|
|
49
|
+
raise Error, 'Webhook timestamp is outside the tolerance window.'
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Compute and compare the signature
|
|
53
|
+
expected = OpenSSL::HMAC.hexdigest('sha256', secret, "#{parts['t']}.#{body}")
|
|
54
|
+
|
|
55
|
+
raise Error, 'Webhook signature verification failed.' unless secure_compare(expected, parts['v1'])
|
|
56
|
+
|
|
57
|
+
# Decode and return the payload
|
|
58
|
+
JSON.parse(body)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Constant-time string comparison.
|
|
62
|
+
def self.secure_compare(left, right)
|
|
63
|
+
return false unless left.bytesize == right.bytesize
|
|
64
|
+
|
|
65
|
+
OpenSSL.fixed_length_secure_compare(left, right)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
private_class_method :secure_compare
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
#
|
|
4
|
+
# This file is part of the Generator Labs Ruby SDK package.
|
|
5
|
+
#
|
|
6
|
+
# (c) Generator Labs <support@generatorlabs.com>
|
|
7
|
+
#
|
|
8
|
+
# For the full copyright and license information, please view the LICENSE
|
|
9
|
+
# file that was distributed with this source code.
|
|
10
|
+
#
|
|
11
|
+
|
|
12
|
+
require_relative 'generatorlabs/version'
|
|
13
|
+
require_relative 'generatorlabs/config'
|
|
14
|
+
require_relative 'generatorlabs/rate_limit_info'
|
|
15
|
+
require_relative 'generatorlabs/response'
|
|
16
|
+
require_relative 'generatorlabs/client'
|
|
17
|
+
require_relative 'generatorlabs/request_handler'
|
|
18
|
+
require_relative 'generatorlabs/webhook'
|
|
19
|
+
require_relative 'generatorlabs/rbl'
|
|
20
|
+
require_relative 'generatorlabs/contact'
|
|
21
|
+
require_relative 'generatorlabs/cert'
|
|
22
|
+
|
|
23
|
+
# Generator Labs API SDK
|
|
24
|
+
module GeneratorLabs
|
|
25
|
+
class Error < StandardError; end
|
|
26
|
+
end
|