rails-flow-map 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.
@@ -0,0 +1,240 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsFlowMap
4
+ # Base error class for all RailsFlowMap errors
5
+ #
6
+ # This class provides a common interface for all errors raised by the
7
+ # RailsFlowMap gem, including error categorization, context preservation,
8
+ # and structured error reporting.
9
+ class Error < StandardError
10
+ attr_reader :context, :error_code, :category
11
+
12
+ # Initialize a new error with context and categorization
13
+ #
14
+ # @param message [String] The error message
15
+ # @param context [Hash] Additional context about the error
16
+ # @param error_code [String] Unique error code for programmatic handling
17
+ # @param category [Symbol] Error category for classification
18
+ def initialize(message, context: {}, error_code: nil, category: :general)
19
+ super(message)
20
+ @context = context
21
+ @error_code = error_code || self.class.name.split('::').last.downcase
22
+ @category = category
23
+ end
24
+
25
+ # @return [Hash] Full error information including context
26
+ def to_h
27
+ {
28
+ error_class: self.class.name,
29
+ message: message,
30
+ error_code: error_code,
31
+ category: category,
32
+ context: context,
33
+ backtrace: backtrace&.first(10)
34
+ }
35
+ end
36
+
37
+ # @return [String] JSON representation of the error
38
+ def to_json(*args)
39
+ require 'json'
40
+ to_h.to_json(*args)
41
+ end
42
+ end
43
+
44
+ # Raised when graph parsing fails
45
+ class GraphParseError < Error
46
+ def initialize(message, context: {})
47
+ super(message, context: context, category: :parsing)
48
+ end
49
+ end
50
+
51
+ # Raised when graph validation fails
52
+ class GraphValidationError < Error
53
+ def initialize(message, context: {})
54
+ super(message, context: context, category: :validation)
55
+ end
56
+ end
57
+
58
+ # Raised when formatter encounters an error
59
+ class FormatterError < Error
60
+ def initialize(message, context: {})
61
+ super(message, context: context, category: :formatting)
62
+ end
63
+ end
64
+
65
+ # Raised when file operations fail
66
+ class FileOperationError < Error
67
+ def initialize(message, context: {})
68
+ super(message, context: context, category: :file_operation)
69
+ end
70
+ end
71
+
72
+ # Raised when security validation fails
73
+ class SecurityError < Error
74
+ def initialize(message, context: {})
75
+ super(message, context: context, category: :security)
76
+ end
77
+ end
78
+
79
+ # Raised when configuration is invalid
80
+ class ConfigurationError < Error
81
+ def initialize(message, context: {})
82
+ super(message, context: context, category: :configuration)
83
+ end
84
+ end
85
+
86
+ # Raised when a feature is not implemented
87
+ class NotImplementedError < Error
88
+ def initialize(message = "Feature not yet implemented", context: {})
89
+ super(message, context: context, category: :not_implemented)
90
+ end
91
+ end
92
+
93
+ # Raised when invalid input is provided
94
+ class InvalidInputError < Error
95
+ def initialize(message, context: {})
96
+ super(message, context: context, category: :invalid_input)
97
+ end
98
+ end
99
+
100
+ # Raised when resource limits are exceeded
101
+ class ResourceLimitError < Error
102
+ def initialize(message, context: {})
103
+ super(message, context: context, category: :resource_limit)
104
+ end
105
+ end
106
+
107
+ # Error handling utilities
108
+ module ErrorHandler
109
+ extend self
110
+
111
+ # Maximum number of retry attempts for transient errors
112
+ MAX_RETRIES = 3
113
+
114
+ # Errors that can be retried
115
+ RETRYABLE_ERRORS = [
116
+ Errno::ENOENT,
117
+ Errno::EACCES,
118
+ Errno::EAGAIN,
119
+ Errno::EWOULDBLOCK
120
+ ].freeze
121
+
122
+ # Execute a block with comprehensive error handling
123
+ #
124
+ # @param operation [String] Description of the operation
125
+ # @param context [Hash] Additional context for error reporting
126
+ # @param retries [Integer] Number of retry attempts for transient errors
127
+ # @yield The block to execute
128
+ # @return The result of the block
129
+ # @raise [RailsFlowMap::Error] Re-raised as appropriate RailsFlowMap error
130
+ def with_error_handling(operation, context: {}, retries: MAX_RETRIES)
131
+ attempt = 0
132
+
133
+ begin
134
+ Logging.with_context(operation: operation, **context) do
135
+ yield
136
+ end
137
+ rescue *RETRYABLE_ERRORS => e
138
+ attempt += 1
139
+ if attempt <= retries
140
+ Logging.logger.warn("Retrying #{operation} (attempt #{attempt}/#{retries}): #{e.message}")
141
+ sleep(0.1 * attempt) # Exponential backoff
142
+ retry
143
+ else
144
+ handle_error(e, operation, context.merge(max_retries_exceeded: true))
145
+ end
146
+ rescue RailsFlowMap::Error => e
147
+ # Already a RailsFlowMap error, just log and re-raise
148
+ Logging.log_error(e, context: context.merge(operation: operation))
149
+ raise
150
+ rescue => e
151
+ handle_error(e, operation, context)
152
+ end
153
+ end
154
+
155
+ # Convert standard errors to RailsFlowMap errors with context
156
+ #
157
+ # @param error [Exception] The original error
158
+ # @param operation [String] Description of the operation that failed
159
+ # @param context [Hash] Additional context
160
+ # @raise [RailsFlowMap::Error] Appropriate RailsFlowMap error type
161
+ def handle_error(error, operation, context = {})
162
+ error_context = context.merge(
163
+ operation: operation,
164
+ original_error: error.class.name
165
+ )
166
+
167
+ rails_flow_map_error = case error
168
+ when JSON::ParserError, YAML::SyntaxError
169
+ GraphParseError.new("Failed to parse data during #{operation}: #{error.message}", context: error_context)
170
+ when Errno::ENOENT, Errno::EACCES
171
+ FileOperationError.new("File operation failed during #{operation}: #{error.message}", context: error_context)
172
+ when ArgumentError
173
+ InvalidInputError.new("Invalid input during #{operation}: #{error.message}", context: error_context)
174
+ when SystemStackError
175
+ ResourceLimitError.new("Stack overflow during #{operation}, input too complex", context: error_context)
176
+ when NoMemoryError
177
+ ResourceLimitError.new("Out of memory during #{operation}", context: error_context)
178
+ else
179
+ Error.new("Unexpected error during #{operation}: #{error.message}", context: error_context)
180
+ end
181
+
182
+ Logging.log_error(rails_flow_map_error, context: error_context)
183
+ raise rails_flow_map_error
184
+ end
185
+
186
+ # Validate input parameters and raise errors if invalid
187
+ #
188
+ # @param validations [Hash] Hash of parameter_name => validation_proc pairs
189
+ # @param context [Hash] Additional context for error reporting
190
+ # @raise [InvalidInputError] If any validation fails
191
+ def validate_input!(validations, context: {})
192
+ validations.each do |param_name, validation|
193
+ begin
194
+ next if validation.call
195
+ rescue => e
196
+ raise InvalidInputError.new(
197
+ "Validation failed for parameter '#{param_name}': #{e.message}",
198
+ context: context.merge(parameter: param_name)
199
+ )
200
+ end
201
+
202
+ raise InvalidInputError.new(
203
+ "Invalid value for parameter '#{param_name}'",
204
+ context: context.merge(parameter: param_name)
205
+ )
206
+ end
207
+ end
208
+
209
+ # Check if an error is retryable
210
+ #
211
+ # @param error [Exception] The error to check
212
+ # @return [Boolean] True if the error can be retried
213
+ def retryable?(error)
214
+ RETRYABLE_ERRORS.any? { |retryable_class| error.is_a?(retryable_class) }
215
+ end
216
+
217
+ # Extract user-friendly error message from any error
218
+ #
219
+ # @param error [Exception] The error to extract message from
220
+ # @return [String] User-friendly error message
221
+ def user_friendly_message(error)
222
+ case error
223
+ when RailsFlowMap::GraphParseError
224
+ "Failed to parse the graph data. Please check your input format."
225
+ when RailsFlowMap::FileOperationError
226
+ "File operation failed. Please check file permissions and paths."
227
+ when RailsFlowMap::InvalidInputError
228
+ "Invalid input provided. #{error.message}"
229
+ when RailsFlowMap::SecurityError
230
+ "Security validation failed. Operation blocked for safety."
231
+ when RailsFlowMap::ResourceLimitError
232
+ "Operation exceeded resource limits. Please try with smaller input."
233
+ when RailsFlowMap::Error
234
+ error.message
235
+ else
236
+ "An unexpected error occurred. Please try again."
237
+ end
238
+ end
239
+ end
240
+ end