cmdx 1.0.1 → 1.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.
Files changed (157) hide show
  1. checksums.yaml +4 -4
  2. data/.cursor/prompts/rspec.md +20 -0
  3. data/.cursor/prompts/yardoc.md +8 -0
  4. data/.rubocop.yml +2 -0
  5. data/CHANGELOG.md +17 -2
  6. data/README.md +1 -1
  7. data/docs/basics/call.md +2 -2
  8. data/docs/basics/chain.md +1 -1
  9. data/docs/callbacks.md +3 -36
  10. data/docs/configuration.md +58 -12
  11. data/docs/interruptions/exceptions.md +1 -1
  12. data/docs/interruptions/faults.md +2 -2
  13. data/docs/logging.md +4 -4
  14. data/docs/middlewares.md +43 -43
  15. data/docs/parameters/coercions.md +49 -38
  16. data/docs/parameters/defaults.md +1 -1
  17. data/docs/parameters/validations.md +0 -39
  18. data/docs/testing.md +11 -12
  19. data/docs/workflows.md +4 -4
  20. data/lib/cmdx/.DS_Store +0 -0
  21. data/lib/cmdx/callback.rb +36 -56
  22. data/lib/cmdx/callback_registry.rb +82 -73
  23. data/lib/cmdx/chain.rb +65 -122
  24. data/lib/cmdx/chain_inspector.rb +22 -115
  25. data/lib/cmdx/chain_serializer.rb +17 -148
  26. data/lib/cmdx/coercion.rb +49 -0
  27. data/lib/cmdx/coercion_registry.rb +94 -0
  28. data/lib/cmdx/coercions/array.rb +18 -36
  29. data/lib/cmdx/coercions/big_decimal.rb +21 -33
  30. data/lib/cmdx/coercions/boolean.rb +21 -40
  31. data/lib/cmdx/coercions/complex.rb +18 -31
  32. data/lib/cmdx/coercions/date.rb +20 -39
  33. data/lib/cmdx/coercions/date_time.rb +22 -39
  34. data/lib/cmdx/coercions/float.rb +19 -32
  35. data/lib/cmdx/coercions/hash.rb +22 -41
  36. data/lib/cmdx/coercions/integer.rb +20 -33
  37. data/lib/cmdx/coercions/rational.rb +20 -32
  38. data/lib/cmdx/coercions/string.rb +23 -31
  39. data/lib/cmdx/coercions/time.rb +24 -40
  40. data/lib/cmdx/coercions/virtual.rb +14 -31
  41. data/lib/cmdx/configuration.rb +57 -171
  42. data/lib/cmdx/context.rb +22 -165
  43. data/lib/cmdx/core_ext/hash.rb +42 -67
  44. data/lib/cmdx/core_ext/module.rb +35 -79
  45. data/lib/cmdx/core_ext/object.rb +63 -98
  46. data/lib/cmdx/correlator.rb +40 -156
  47. data/lib/cmdx/error.rb +37 -202
  48. data/lib/cmdx/errors.rb +165 -202
  49. data/lib/cmdx/fault.rb +55 -158
  50. data/lib/cmdx/faults.rb +26 -137
  51. data/lib/cmdx/immutator.rb +22 -109
  52. data/lib/cmdx/lazy_struct.rb +103 -187
  53. data/lib/cmdx/log_formatters/json.rb +14 -40
  54. data/lib/cmdx/log_formatters/key_value.rb +14 -40
  55. data/lib/cmdx/log_formatters/line.rb +14 -48
  56. data/lib/cmdx/log_formatters/logstash.rb +14 -57
  57. data/lib/cmdx/log_formatters/pretty_json.rb +14 -50
  58. data/lib/cmdx/log_formatters/pretty_key_value.rb +13 -46
  59. data/lib/cmdx/log_formatters/pretty_line.rb +16 -54
  60. data/lib/cmdx/log_formatters/raw.rb +19 -49
  61. data/lib/cmdx/logger.rb +20 -82
  62. data/lib/cmdx/logger_ansi.rb +18 -75
  63. data/lib/cmdx/logger_serializer.rb +24 -114
  64. data/lib/cmdx/middleware.rb +38 -60
  65. data/lib/cmdx/middleware_registry.rb +81 -77
  66. data/lib/cmdx/middlewares/correlate.rb +41 -226
  67. data/lib/cmdx/middlewares/timeout.rb +46 -185
  68. data/lib/cmdx/parameter.rb +120 -198
  69. data/lib/cmdx/parameter_evaluator.rb +231 -0
  70. data/lib/cmdx/parameter_inspector.rb +25 -56
  71. data/lib/cmdx/parameter_registry.rb +59 -84
  72. data/lib/cmdx/parameter_serializer.rb +23 -74
  73. data/lib/cmdx/railtie.rb +24 -107
  74. data/lib/cmdx/result.rb +254 -260
  75. data/lib/cmdx/result_ansi.rb +19 -85
  76. data/lib/cmdx/result_inspector.rb +27 -68
  77. data/lib/cmdx/result_logger.rb +18 -81
  78. data/lib/cmdx/result_serializer.rb +28 -132
  79. data/lib/cmdx/rspec/matchers.rb +28 -0
  80. data/lib/cmdx/rspec/result_matchers/be_executed.rb +42 -0
  81. data/lib/cmdx/rspec/result_matchers/be_failed_task.rb +94 -0
  82. data/lib/cmdx/rspec/result_matchers/be_skipped_task.rb +94 -0
  83. data/lib/cmdx/rspec/result_matchers/be_state_matchers.rb +59 -0
  84. data/lib/cmdx/rspec/result_matchers/be_status_matchers.rb +57 -0
  85. data/lib/cmdx/rspec/result_matchers/be_successful_task.rb +87 -0
  86. data/lib/cmdx/rspec/result_matchers/have_bad_outcome.rb +51 -0
  87. data/lib/cmdx/rspec/result_matchers/have_caused_failure.rb +58 -0
  88. data/lib/cmdx/rspec/result_matchers/have_chain_index.rb +59 -0
  89. data/lib/cmdx/rspec/result_matchers/have_context.rb +86 -0
  90. data/lib/cmdx/rspec/result_matchers/have_empty_metadata.rb +54 -0
  91. data/lib/cmdx/rspec/result_matchers/have_good_outcome.rb +52 -0
  92. data/lib/cmdx/rspec/result_matchers/have_metadata.rb +114 -0
  93. data/lib/cmdx/rspec/result_matchers/have_preserved_context.rb +66 -0
  94. data/lib/cmdx/rspec/result_matchers/have_received_thrown_failure.rb +64 -0
  95. data/lib/cmdx/rspec/result_matchers/have_runtime.rb +78 -0
  96. data/lib/cmdx/rspec/result_matchers/have_thrown_failure.rb +76 -0
  97. data/lib/cmdx/rspec/task_matchers/be_well_formed_task.rb +62 -0
  98. data/lib/cmdx/rspec/task_matchers/have_callback.rb +85 -0
  99. data/lib/cmdx/rspec/task_matchers/have_cmd_setting.rb +68 -0
  100. data/lib/cmdx/rspec/task_matchers/have_executed_callbacks.rb +92 -0
  101. data/lib/cmdx/rspec/task_matchers/have_middleware.rb +46 -0
  102. data/lib/cmdx/rspec/task_matchers/have_parameter.rb +181 -0
  103. data/lib/cmdx/task.rb +213 -425
  104. data/lib/cmdx/task_deprecator.rb +55 -0
  105. data/lib/cmdx/task_processor.rb +245 -0
  106. data/lib/cmdx/task_serializer.rb +22 -70
  107. data/lib/cmdx/utils/ansi_color.rb +13 -89
  108. data/lib/cmdx/utils/log_timestamp.rb +13 -42
  109. data/lib/cmdx/utils/monotonic_runtime.rb +13 -63
  110. data/lib/cmdx/utils/name_affix.rb +21 -71
  111. data/lib/cmdx/validator.rb +48 -0
  112. data/lib/cmdx/validator_registry.rb +86 -0
  113. data/lib/cmdx/validators/exclusion.rb +55 -94
  114. data/lib/cmdx/validators/format.rb +31 -85
  115. data/lib/cmdx/validators/inclusion.rb +65 -110
  116. data/lib/cmdx/validators/length.rb +117 -133
  117. data/lib/cmdx/validators/numeric.rb +123 -130
  118. data/lib/cmdx/validators/presence.rb +38 -79
  119. data/lib/cmdx/version.rb +1 -7
  120. data/lib/cmdx/workflow.rb +46 -339
  121. data/lib/cmdx.rb +1 -1
  122. data/lib/generators/cmdx/install_generator.rb +14 -31
  123. data/lib/generators/cmdx/task_generator.rb +39 -55
  124. data/lib/generators/cmdx/templates/install.rb +24 -6
  125. data/lib/generators/cmdx/workflow_generator.rb +41 -66
  126. data/lib/locales/ar.yml +0 -1
  127. data/lib/locales/cs.yml +0 -1
  128. data/lib/locales/da.yml +0 -1
  129. data/lib/locales/de.yml +0 -1
  130. data/lib/locales/el.yml +0 -1
  131. data/lib/locales/en.yml +0 -1
  132. data/lib/locales/es.yml +0 -1
  133. data/lib/locales/fi.yml +0 -1
  134. data/lib/locales/fr.yml +0 -1
  135. data/lib/locales/he.yml +0 -1
  136. data/lib/locales/hi.yml +0 -1
  137. data/lib/locales/it.yml +0 -1
  138. data/lib/locales/ja.yml +0 -1
  139. data/lib/locales/ko.yml +0 -1
  140. data/lib/locales/nl.yml +0 -1
  141. data/lib/locales/no.yml +0 -1
  142. data/lib/locales/pl.yml +0 -1
  143. data/lib/locales/pt.yml +0 -1
  144. data/lib/locales/ru.yml +0 -1
  145. data/lib/locales/sv.yml +0 -1
  146. data/lib/locales/th.yml +0 -1
  147. data/lib/locales/tr.yml +0 -1
  148. data/lib/locales/vi.yml +0 -1
  149. data/lib/locales/zh.yml +0 -1
  150. metadata +34 -8
  151. data/lib/cmdx/parameter_validator.rb +0 -81
  152. data/lib/cmdx/parameter_value.rb +0 -244
  153. data/lib/cmdx/parameters_inspector.rb +0 -72
  154. data/lib/cmdx/parameters_serializer.rb +0 -115
  155. data/lib/cmdx/rspec/result_matchers.rb +0 -917
  156. data/lib/cmdx/rspec/task_matchers.rb +0 -570
  157. data/lib/cmdx/validators/custom.rb +0 -102
data/lib/cmdx/errors.rb CHANGED
@@ -1,151 +1,72 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CMDx
4
- ##
5
- # Errors provides a collection-like interface for managing validation and execution errors
6
- # within CMDx tasks. It offers Rails-inspired methods for adding, querying, and formatting
7
- # error messages, making it easy to accumulate and present multiple error conditions.
4
+ # Error collection and validation system for CMDx tasks.
8
5
  #
9
- # The Errors class is designed to work seamlessly with CMDx's parameter validation system,
10
- # automatically collecting validation failures and providing convenient methods for
11
- # error reporting and user feedback.
12
- #
13
- #
14
- # @example Basic error management
15
- # errors = Errors.new
16
- # errors.add(:email, "is required")
17
- # errors.add(:email, "is invalid format")
18
- # errors.add(:password, "is too short")
19
- #
20
- # errors.empty? #=> false
21
- # errors.size #=> 2 (attributes with errors)
22
- # errors[:email] #=> ["is required", "is invalid format"]
23
- # errors.full_messages #=> ["email is required", "email is invalid format", "password is too short"]
24
- #
25
- # @example Task integration
26
- # class CreateUserTask < CMDx::Task
27
- # required :email, type: :string
28
- # required :password, type: :string
29
- #
30
- # def call
31
- # validate_email
32
- # validate_password
33
- #
34
- # if errors.present?
35
- # fail!(reason: "Validation failed", validation_errors: errors.full_messages)
36
- # end
37
- #
38
- # create_user
39
- # end
40
- #
41
- # private
42
- #
43
- # def validate_email
44
- # errors.add(:email, "is required") if email.blank?
45
- # errors.add(:email, "is invalid") unless email.include?("@")
46
- # end
47
- #
48
- # def validate_password
49
- # errors.add(:password, "is too short") if password.length < 8
50
- # end
51
- # end
52
- #
53
- # @example Error querying and formatting
54
- # errors = Errors.new
55
- # errors.add(:name, "cannot be blank")
56
- # errors.add(:age, "must be a number")
57
- #
58
- # # Checking for specific errors
59
- # errors.key?(:name) #=> true
60
- # errors.added?(:name, "cannot be blank") #=> true
61
- # errors.of_kind?(:age, "must be positive") #=> false
62
- #
63
- # # Getting messages
64
- # errors.messages_for(:name) #=> ["cannot be blank"]
65
- # errors.full_messages_for(:name) #=> ["name cannot be blank"]
66
- #
67
- # # Converting to hash
68
- # errors.to_hash #=> {:name => ["cannot be blank"], :age => ["must be a number"]}
69
- # errors.to_hash(true) #=> {:name => ["name cannot be blank"], :age => ["age must be a number"]}
70
- #
71
- # @example Rails-style validation
72
- # class ValidateUserTask < CMDx::Task
73
- # required :user_data, type: :hash
74
- #
75
- # def call
76
- # user = user_data
77
- #
78
- # errors.add(:email, "can't be blank") if user[:email].blank?
79
- # errors.add(:email, "is invalid") unless valid_email?(user[:email])
80
- # errors.add(:age, "must be at least 18") if user[:age] && user[:age] < 18
81
- #
82
- # return if errors.invalid?
83
- #
84
- # context.validated_user = user
85
- # end
86
- # end
87
- #
88
- # @see Task Task base class with errors attribute
89
- # @see Parameter Parameter validation integration
90
- # @see ValidationError Individual validation errors
91
- # @since 1.0.0
6
+ # This class manages error messages associated with specific attributes,
7
+ # providing a flexible API for adding, querying, and formatting validation
8
+ # errors. It supports both individual error messages and collections of
9
+ # errors per attribute, with various convenience methods for error handling
10
+ # and display.
92
11
  class Errors
93
12
 
94
- __cmdx_attr_delegator :clear, :delete, :each, :empty?, :key?, :keys, :size, :values,
95
- to: :errors
13
+ cmdx_attr_delegator :clear, :delete, :empty?, :key?, :keys, :size, :values,
14
+ to: :errors
96
15
 
97
- ##
98
- # @!attribute [r] errors
99
- # @return [Hash] internal hash storing error messages by attribute
16
+ # @return [Hash] internal hash storing error messages by attribute
100
17
  attr_reader :errors
101
18
 
102
- ##
103
- # @!method attribute_names
104
- # @return [Array<Symbol>] list of attributes that have errors
19
+ # @return [Array<Symbol>] list of attributes that have errors
105
20
  alias attribute_names keys
106
21
 
107
- ##
108
- # @!method blank?
109
- # @return [Boolean] true if no errors are present
22
+ # @return [Boolean] true if no errors are present
110
23
  alias blank? empty?
111
24
 
112
- ##
113
- # @!method valid?
114
- # @return [Boolean] true if no errors are present
25
+ # @return [Boolean] true if no errors are present
115
26
  alias valid? empty?
116
27
 
117
- ##
118
28
  # Alias for {#key?}. Checks if an attribute has error messages.
119
29
  alias has_key? key?
120
30
 
121
- ##
122
31
  # Alias for {#key?}. Checks if an attribute has error messages.
123
32
  alias include? key?
124
33
 
125
- ##
126
- # Initializes a new Errors collection.
127
- # Creates an empty hash to store error messages by attribute.
34
+ # Creates a new error collection with an empty internal hash.
35
+ #
36
+ # @return [Errors] the newly created error collection
128
37
  #
129
- # @example
130
- # errors = Errors.new
131
- # errors.empty? #=> true
38
+ # @example Create a new error collection
39
+ # errors = CMDx::Errors.new
40
+ # errors.empty? # => true
132
41
  def initialize
133
42
  @errors = {}
134
43
  end
135
44
 
136
- ##
137
45
  # Adds an error message to the specified attribute.
138
- # Messages are stored in arrays and automatically deduplicated.
139
46
  #
140
- # @param key [Symbol, String] the attribute name
141
- # @param value [String] the error message
142
- # @return [Array<String>] the updated array of messages for the attribute
47
+ # If the attribute already has errors, the new message is appended to the
48
+ # existing array. Duplicate messages are automatically removed to ensure
49
+ # each error message appears only once per attribute.
50
+ #
51
+ # @param key [Symbol, String] the attribute name to associate the error with
52
+ # @param value [String] the error message to add
53
+ #
54
+ # @return [Array<String>] the array of error messages for the attribute
143
55
  #
144
- # @example Adding multiple errors
56
+ # @example Add an error to an attribute
57
+ # errors = CMDx::Errors.new
58
+ # errors.add(:name, "is required")
59
+ # errors[:name] # => ["is required"]
60
+ #
61
+ # @example Add multiple errors to the same attribute
145
62
  # errors.add(:email, "is required")
146
- # errors.add(:email, "is invalid format")
147
- # errors.add(:email, "is required") # Duplicate - ignored
148
- # errors[:email] #=> ["is required", "is invalid format"]
63
+ # errors.add(:email, "must be valid")
64
+ # errors[:email] # => ["is required", "must be valid"]
65
+ #
66
+ # @example Duplicate errors are automatically removed
67
+ # errors.add(:age, "must be positive")
68
+ # errors.add(:age, "must be positive")
69
+ # errors[:age] # => ["must be positive"]
149
70
  def add(key, value)
150
71
  errors[key] ||= []
151
72
  errors[key] << value
@@ -153,18 +74,21 @@ module CMDx
153
74
  end
154
75
  alias []= add
155
76
 
156
- ##
157
77
  # Checks if a specific error message has been added to an attribute.
158
78
  #
159
- # @param key [Symbol, String] the attribute name
160
- # @param val [String] the error message to check for
161
- # @return [Boolean] true if the specific error exists
79
+ # @param key [Symbol, String] the attribute name to check
80
+ # @param val [String] the error message to look for
162
81
  #
163
- # @example
82
+ # @return [Boolean] true if the specific error message exists for the attribute
83
+ #
84
+ # @example Check if a specific error exists
85
+ # errors = CMDx::Errors.new
164
86
  # errors.add(:name, "is required")
165
- # errors.added?(:name, "is required") #=> true
166
- # errors.added?(:name, "is too long") #=> false
167
- # errors.of_kind?(:name, "is required") #=> true (alias)
87
+ # errors.added?(:name, "is required") # => true
88
+ # errors.added?(:name, "is invalid") # => false
89
+ #
90
+ # @example Check error on attribute without errors
91
+ # errors.added?(:missing, "any error") # => false
168
92
  def added?(key, val)
169
93
  return false unless key?(key)
170
94
 
@@ -172,15 +96,14 @@ module CMDx
172
96
  end
173
97
  alias of_kind? added?
174
98
 
175
- ##
176
- # Iterates over each error, yielding the attribute and message.
177
- # Flattens the error structure so each message is yielded individually.
99
+ # Iterates over all error messages, yielding the attribute and message.
178
100
  #
179
- # @yieldparam key [Symbol] the attribute name
180
- # @yieldparam val [String] the error message
181
- # @return [Enumerator] if no block given
101
+ # @yield [Symbol, String] the attribute name and error message
182
102
  #
183
- # @example
103
+ # @return [void]
104
+ #
105
+ # @example Iterate over all errors
106
+ # errors = CMDx::Errors.new
184
107
  # errors.add(:name, "is required")
185
108
  # errors.add(:email, "is invalid")
186
109
  # errors.each { |attr, msg| puts "#{attr}: #{msg}" }
@@ -193,32 +116,36 @@ module CMDx
193
116
  end
194
117
  end
195
118
 
196
- ##
197
- # Generates a full error message by combining attribute name and message.
119
+ # Formats an attribute and error message into a full error message.
198
120
  #
199
121
  # @param key [Symbol, String] the attribute name
200
122
  # @param value [String] the error message
201
- # @return [String] formatted full message
202
123
  #
203
- # @example
204
- # errors.full_message(:email, "is required") #=> "email is required"
205
- # errors.full_message(:age, "must be positive") #=> "age must be positive"
124
+ # @return [String] the formatted full error message
125
+ #
126
+ # @example Format a full error message
127
+ # errors = CMDx::Errors.new
128
+ # errors.full_message(:name, "is required") # => "name is required"
129
+ #
130
+ # @example Format with different attribute types
131
+ # errors.full_message("email", "must be valid") # => "email must be valid"
206
132
  def full_message(key, value)
207
133
  "#{key} #{value}"
208
134
  end
209
135
 
210
- ##
211
- # Returns an array of all full error messages.
212
- # Combines attribute names with their error messages.
136
+ # Returns an array of all full error messages across all attributes.
213
137
  #
214
138
  # @return [Array<String>] array of formatted error messages
215
139
  #
216
- # @example
217
- # errors.add(:email, "is required")
140
+ # @example Get all full error messages
141
+ # errors = CMDx::Errors.new
142
+ # errors.add(:name, "is required")
218
143
  # errors.add(:email, "is invalid")
219
- # errors.add(:password, "is too short")
220
- # errors.full_messages
221
- # #=> ["email is required", "email is invalid", "password is too short"]
144
+ # errors.full_messages # => ["name is required", "email is invalid"]
145
+ #
146
+ # @example Empty errors return empty array
147
+ # errors = CMDx::Errors.new
148
+ # errors.full_messages # => []
222
149
  def full_messages
223
150
  errors.each_with_object([]) do |(key, arr), memo|
224
151
  arr.each { |val| memo << full_message(key, val) }
@@ -226,54 +153,89 @@ module CMDx
226
153
  end
227
154
  alias to_a full_messages
228
155
 
229
- ##
230
156
  # Returns full error messages for a specific attribute.
231
157
  #
232
- # @param key [Symbol, String] the attribute name
233
- # @return [Array<String>] array of full messages for the attribute
158
+ # @param key [Symbol, String] the attribute name to get messages for
234
159
  #
235
- # @example
236
- # errors.add(:email, "is required")
237
- # errors.add(:email, "is invalid")
238
- # errors.full_messages_for(:email)
239
- # #=> ["email is required", "email is invalid"]
240
- # errors.full_messages_for(:missing) #=> []
160
+ # @return [Array<String>] array of formatted error messages for the attribute
161
+ #
162
+ # @example Get full messages for a specific attribute
163
+ # errors = CMDx::Errors.new
164
+ # errors.add(:name, "is required")
165
+ # errors.add(:name, "is too short")
166
+ # errors.full_messages_for(:name) # => ["name is required", "name is too short"]
167
+ #
168
+ # @example Get messages for attribute without errors
169
+ # errors.full_messages_for(:missing) # => []
241
170
  def full_messages_for(key)
242
171
  return [] unless key?(key)
243
172
 
244
173
  errors[key].map { |val| full_message(key, val) }
245
174
  end
246
175
 
247
- ##
248
- # Checks if any errors are present.
176
+ # Checks if the error collection contains any errors.
249
177
  #
250
- # @return [Boolean] true if errors exist
178
+ # @return [Boolean] true if there are any errors present
251
179
  #
252
- # @example
253
- # errors = Errors.new
254
- # errors.invalid? #=> false
180
+ # @example Check if errors are present
181
+ # errors = CMDx::Errors.new
182
+ # errors.invalid? # => false
255
183
  # errors.add(:name, "is required")
256
- # errors.invalid? #=> true
184
+ # errors.invalid? # => true
257
185
  def invalid?
258
186
  !valid?
259
187
  end
260
188
 
261
- ##
189
+ # Maps over all error messages, yielding the attribute and message to a block.
190
+ #
191
+ # Similar to {#each}, but returns an array of the block's return values
192
+ # instead of iterating without collecting results.
193
+ #
194
+ # @yield [Symbol, String] the attribute name and error message
195
+ # @yieldreturn [Object] the transformed value for each error message
196
+ #
197
+ # @return [Array<Object>] array of transformed values from the block
198
+ #
199
+ # @example Transform error messages to a custom format
200
+ # errors = CMDx::Errors.new
201
+ # errors.add(:name, "is required")
202
+ # errors.add(:email, "is invalid")
203
+ # result = errors.map { |attr, msg| "#{attr.upcase}: #{msg}" }
204
+ # result # => ["NAME: is required", "EMAIL: is invalid"]
205
+ #
206
+ # @example Extract only attribute names with errors
207
+ # errors.map { |attr, _msg| attr } # => [:name, :email]
208
+ #
209
+ # @example Return empty array for no errors
210
+ # empty_errors = CMDx::Errors.new
211
+ # empty_errors.map { |attr, msg| [attr, msg] } # => []
212
+ def map
213
+ errors.each_with_object([]) do |(key, _arr), memo|
214
+ memo.concat(errors[key].map { |val| yield(key, val) })
215
+ end
216
+ end
217
+
262
218
  # Merges another hash of errors into this collection.
263
- # Combines arrays of messages for attributes that exist in both collections.
264
219
  #
265
- # @param hash [Hash] hash of attribute => messages to merge
266
- # @return [Hash] the updated errors hash
220
+ # When the same attribute exists in both collections, the error arrays
221
+ # are combined and duplicates are removed.
267
222
  #
268
- # @example
269
- # errors1 = Errors.new
270
- # errors1.add(:email, "is required")
223
+ # @param hash [Hash] hash of errors to merge, with attribute names as keys
271
224
  #
272
- # errors2 = { email: ["is invalid"], password: ["is too short"] }
273
- # errors1.merge!(errors2)
225
+ # @return [Hash] the merged errors hash
274
226
  #
275
- # errors1[:email] #=> ["is required", "is invalid"]
276
- # errors1[:password] #=> ["is too short"]
227
+ # @example Merge errors from another hash
228
+ # errors = CMDx::Errors.new
229
+ # errors.add(:name, "is required")
230
+ # other_errors = { email: ["is invalid"], name: ["is too short"] }
231
+ # errors.merge!(other_errors)
232
+ # errors[:name] # => ["is required", "is too short"]
233
+ # errors[:email] # => ["is invalid"]
234
+ #
235
+ # @example Merge with duplicate errors
236
+ # errors.add(:age, "must be positive")
237
+ # errors.merge!(age: ["must be positive", "must be an integer"])
238
+ # errors[:age] # => ["must be positive", "must be an integer"]
277
239
  def merge!(hash)
278
240
  errors.merge!(hash) do |_, arr1, arr2|
279
241
  arr3 = arr1 + arr2
@@ -282,17 +244,20 @@ module CMDx
282
244
  end
283
245
  end
284
246
 
285
- ##
286
- # Returns error messages for a specific attribute.
247
+ # Returns the raw error messages for a specific attribute.
287
248
  #
288
- # @param key [Symbol, String] the attribute name
289
- # @return [Array<String>] array of error messages for the attribute
249
+ # @param key [Symbol, String] the attribute name to get messages for
290
250
  #
291
- # @example
292
- # errors.add(:email, "is required")
293
- # errors.add(:email, "is invalid")
294
- # errors.messages_for(:email) #=> ["is required", "is invalid"]
295
- # errors[:email] #=> ["is required", "is invalid"] (alias)
251
+ # @return [Array<String>] array of raw error messages for the attribute
252
+ #
253
+ # @example Get raw messages for an attribute
254
+ # errors = CMDx::Errors.new
255
+ # errors.add(:name, "is required")
256
+ # errors.add(:name, "is too short")
257
+ # errors.messages_for(:name) # => ["is required", "is too short"]
258
+ #
259
+ # @example Get messages for attribute without errors
260
+ # errors.messages_for(:missing) # => []
296
261
  def messages_for(key)
297
262
  return [] unless key?(key)
298
263
 
@@ -300,38 +265,36 @@ module CMDx
300
265
  end
301
266
  alias [] messages_for
302
267
 
303
- ##
304
- # Checks if any errors are present.
268
+ # Checks if the error collection contains any errors.
305
269
  #
306
- # @return [Boolean] true if errors exist
270
+ # @return [Boolean] true if there are any errors present
307
271
  #
308
- # @example
309
- # errors = Errors.new
310
- # errors.present? #=> false
272
+ # @example Check if errors are present
273
+ # errors = CMDx::Errors.new
274
+ # errors.present? # => false
311
275
  # errors.add(:name, "is required")
312
- # errors.present? #=> true
276
+ # errors.present? # => true
313
277
  def present?
314
278
  !blank?
315
279
  end
316
280
 
317
- ##
318
- # Converts the errors collection to a hash representation.
281
+ # Converts the error collection to a hash representation.
319
282
  #
320
283
  # @param full_messages [Boolean] whether to include full formatted messages
284
+ #
321
285
  # @return [Hash] hash representation of errors
322
286
  #
323
- # @example Raw messages
324
- # errors.add(:email, "is required")
325
- # errors.to_hash #=> {:email => ["is required"]}
287
+ # @example Get raw error messages hash
288
+ # errors = CMDx::Errors.new
289
+ # errors.add(:name, "is required")
290
+ # errors.to_hash # => { name: ["is required"] }
326
291
  #
327
- # @example Full messages
328
- # errors.add(:email, "is required")
329
- # errors.to_hash(true) #=> {:email => ["email is required"]}
292
+ # @example Get full formatted messages hash
293
+ # errors.to_hash(true) # => { name: ["name is required"] }
330
294
  #
331
- # @example Method aliases
332
- # errors.messages #=> same as to_hash
333
- # errors.group_by_attribute #=> same as to_hash
334
- # errors.as_json #=> same as to_hash
295
+ # @example Empty errors return empty hash
296
+ # errors = CMDx::Errors.new
297
+ # errors.to_hash # => {}
335
298
  def to_hash(full_messages = false)
336
299
  return errors unless full_messages
337
300