node-red 0.1.0 → 0.2.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: 2a448f70ca26bba12cb8a9d4f6a25f93b2956e3144c70f2b215a5b0d1efa320f
4
- data.tar.gz: abd51032ec4f34f15f1ef1e5b00dea2c71a7bc4135b519f1838145bb00be42cb
3
+ metadata.gz: 9cc4a2ebc55b4f440b05fdb7209304381bafee347ef54257306bdf74b196963d
4
+ data.tar.gz: 151b1bb1140a55798308f499077ea575826010b8c2587a19efe277bbc6371ae0
5
5
  SHA512:
6
- metadata.gz: 1421fecb786a1a74a5db7f094717a28070468a904ad8b67f43c97250c928424a831d76a30fb340f11c369dabb631f55cc0f129edaf9d9cd62604337bbb6ec92b
7
- data.tar.gz: 9dcd66078d904b340be8f97faae4730112fc8f41eecf5b975e74efffd1af96033a79bc1efce9be944519724c144841d02308403d9561b198971ed27050d55bac
6
+ metadata.gz: 80f109e87d03189d1513944d074b02b7292988229a16c1b285f87e1abfbfb402a9f648a09d2458e5d07bd6cbd6e3168e31cda8c5897d007363f2cb10a643860e
7
+ data.tar.gz: 61951643190f2f562b81836600e84e81c843353a1798413eb1f8007a3d46b4be1bd3dbcf815728261bb51a699cf483a8bbcb9236b0c163ff1db4edae737d30f7
data/CHANGELOG.md CHANGED
@@ -1,3 +1,14 @@
1
+ ## [0.2.0] - 2025-10-28
2
+
3
+ ### Added
4
+ - Structured error classes with contextual information (`status`, `code`, `details`)
5
+ - Extensive WebMock-backed RSpec coverage for every Admin API endpoint and error path
6
+
7
+ ### Changed
8
+ - Improved error handling to map Node-RED response codes (400/401/404/409/5xx) to dedicated exceptions
9
+ - Enhanced JSON parsing safeguards on responses and errors for clearer diagnostics
10
+ - Updated README with advanced error-handling guidance
11
+
1
12
  ## [0.1.0] - 2025-10-28
2
13
 
3
14
  - Initial release
data/README.md CHANGED
@@ -140,6 +140,16 @@ rescue Node::Red::ApiError => e
140
140
  end
141
141
  ```
142
142
 
143
+ Each error object carries contextual information to help with debugging:
144
+
145
+ ```ruby
146
+ rescue Node::Red::ApiError => e
147
+ puts e.status # => 400, 401, 404, 409, 500, ...
148
+ puts e.code # => "invalid_request", "module_already_loaded", ... (may be nil)
149
+ puts e.details # => full parsed response body, if available
150
+ end
151
+ ```
152
+
143
153
  ## Development
144
154
 
145
155
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -56,44 +56,6 @@ begin
56
56
  puts "=== Deploying Complete Flow Configuration ==="
57
57
 
58
58
  # Create a simple flow with an inject and debug node
59
- complete_flows = [
60
- {
61
- id: "flow1",
62
- type: "tab",
63
- label: "Test Flow"
64
- },
65
- {
66
- id: "inject1",
67
- type: "inject",
68
- z: "flow1",
69
- name: "Test Inject",
70
- props: [{ p: "payload" }],
71
- repeat: "",
72
- crontab: "",
73
- once: false,
74
- onceDelay: 0.1,
75
- topic: "",
76
- payload: "Hello from Ruby!",
77
- payloadType: "str",
78
- x: 150,
79
- y: 100
80
- },
81
- {
82
- id: "debug1",
83
- type: "debug",
84
- z: "flow1",
85
- name: "Test Debug",
86
- active: true,
87
- tosidebar: true,
88
- console: false,
89
- tostatus: false,
90
- complete: "payload",
91
- targetType: "msg",
92
- x: 350,
93
- y: 100,
94
- wires: []
95
- }
96
- ]
97
59
 
98
60
  # NOTE: Be careful with deploy_flows as it replaces ALL flows
99
61
  # Uncomment the following lines to deploy
@@ -286,34 +286,70 @@ module Node
286
286
  def handle_response(response)
287
287
  case response
288
288
  when Net::HTTPSuccess
289
- response.body && !response.body.empty? ? JSON.parse(response.body) : {}
289
+ parse_response_body(response)
290
+ when Net::HTTPBadRequest
291
+ raise_api_error(BadRequestError, response)
290
292
  when Net::HTTPUnauthorized
291
- raise AuthenticationError, "Authentication failed: #{response.code} #{response.message}"
293
+ raise_api_error(AuthenticationError, response)
292
294
  when Net::HTTPNotFound
293
- raise NotFoundError, "Resource not found: #{response.code} #{response.message}"
295
+ raise_api_error(NotFoundError, response)
296
+ when Net::HTTPConflict
297
+ raise_api_error(ConflictError, response)
294
298
  when Net::HTTPClientError
295
- error_message = parse_error_message(response)
296
- raise ApiError, "API error: #{response.code} #{error_message}"
299
+ raise_api_error(ApiError, response)
297
300
  when Net::HTTPServerError
298
- raise ServerError, "Server error: #{response.code} #{response.message}"
301
+ raise_api_error(ServerError, response)
299
302
  else
300
- raise ApiError, "Unexpected response: #{response.code} #{response.message}"
303
+ raise_api_error(UnexpectedResponseError, response)
301
304
  end
302
305
  end
303
306
 
304
- # Parse error message from response
307
+ # Parse a successful HTTP response body, ensuring JSON content
305
308
  #
306
309
  # @param response [Net::HTTPResponse] The HTTP response
307
- # @return [String] Error message
308
- def parse_error_message(response)
309
- return response.message unless response.body && !response.body.empty?
310
-
311
- begin
312
- error_data = JSON.parse(response.body)
313
- error_data["message"] || error_data["error"] || response.message
314
- rescue JSON::ParserError
315
- response.message
316
- end
310
+ # @return [Hash, Array] Parsed JSON response or empty hash for blank body
311
+ def parse_response_body(response)
312
+ body = response.body
313
+ return {} if body.nil? || body.strip.empty?
314
+
315
+ JSON.parse(body)
316
+ rescue JSON::ParserError => e
317
+ raise UnexpectedResponseError.new(
318
+ "Invalid JSON response: #{e.message}",
319
+ status: response.code.to_i,
320
+ details: { body: body }
321
+ )
322
+ end
323
+
324
+ # Raise a structured API error based on the HTTP response
325
+ #
326
+ # @param error_class [Class<ApiError>] The error class to raise
327
+ # @param response [Net::HTTPResponse] The HTTP response
328
+ # @raise [ApiError]
329
+ def raise_api_error(error_class, response)
330
+ message, error_code, details = extract_error_details(response)
331
+ raise error_class.new(
332
+ message,
333
+ status: response.code.to_i,
334
+ code: error_code,
335
+ details: details
336
+ )
337
+ end
338
+
339
+ # Extract error details from a failed HTTP response
340
+ #
341
+ # @param response [Net::HTTPResponse] The HTTP response
342
+ # @return [Array(String, String, Hash, nil)] message, error code and raw details
343
+ def extract_error_details(response)
344
+ body = response.body
345
+ return [response.message, nil, nil] if body.nil? || body.strip.empty?
346
+
347
+ data = JSON.parse(body)
348
+ message = data["message"] || data["error"] || response.message
349
+ error_code = data["code"]
350
+ [message, error_code, data]
351
+ rescue JSON::ParserError
352
+ [response.message, nil, { raw_body: body }]
317
353
  end
318
354
  end
319
355
  end
@@ -2,16 +2,34 @@
2
2
 
3
3
  module Node
4
4
  module Red
5
- # Base error class for Node-RED API errors
6
- class ApiError < StandardError; end
5
+ # Base error class for Node-RED API errors that keeps rich context
6
+ class ApiError < StandardError
7
+ attr_reader :status, :code, :details
7
8
 
8
- # Raised when authentication fails
9
+ def initialize(message, status:, code: nil, details: nil)
10
+ super(message)
11
+ @status = status
12
+ @code = code
13
+ @details = details
14
+ end
15
+ end
16
+
17
+ # Raised when the request is malformed (HTTP 400)
18
+ class BadRequestError < ApiError; end
19
+
20
+ # Raised when authentication fails (HTTP 401)
9
21
  class AuthenticationError < ApiError; end
10
22
 
11
- # Raised when a resource is not found
23
+ # Raised when a resource is not found (HTTP 404)
12
24
  class NotFoundError < ApiError; end
13
25
 
26
+ # Raised when there is a version mismatch (HTTP 409)
27
+ class ConflictError < ApiError; end
28
+
14
29
  # Raised when the server returns a 5xx error
15
30
  class ServerError < ApiError; end
31
+
32
+ # Raised when the response type is unexpected
33
+ class UnexpectedResponseError < ApiError; end
16
34
  end
17
35
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Node
4
4
  module Red
5
- VERSION = "0.1.0"
5
+ VERSION = "0.2.0"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: node-red
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Davide Santangelo