cmdx 1.20.0 → 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.
Files changed (195) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +131 -1
  3. data/README.md +37 -24
  4. data/lib/cmdx/.DS_Store +0 -0
  5. data/lib/cmdx/callbacks.rb +179 -0
  6. data/lib/cmdx/chain.rb +78 -175
  7. data/lib/cmdx/coercions/array.rb +19 -33
  8. data/lib/cmdx/coercions/big_decimal.rb +12 -29
  9. data/lib/cmdx/coercions/boolean.rb +25 -45
  10. data/lib/cmdx/coercions/coerce.rb +32 -0
  11. data/lib/cmdx/coercions/complex.rb +12 -27
  12. data/lib/cmdx/coercions/date.rb +29 -33
  13. data/lib/cmdx/coercions/date_time.rb +29 -33
  14. data/lib/cmdx/coercions/float.rb +8 -29
  15. data/lib/cmdx/coercions/hash.rb +17 -43
  16. data/lib/cmdx/coercions/integer.rb +8 -32
  17. data/lib/cmdx/coercions/rational.rb +12 -33
  18. data/lib/cmdx/coercions/string.rb +6 -24
  19. data/lib/cmdx/coercions/symbol.rb +12 -26
  20. data/lib/cmdx/coercions/time.rb +31 -35
  21. data/lib/cmdx/coercions.rb +174 -0
  22. data/lib/cmdx/configuration.rb +45 -225
  23. data/lib/cmdx/context.rb +263 -242
  24. data/lib/cmdx/deprecation.rb +67 -0
  25. data/lib/cmdx/deprecators/error.rb +22 -0
  26. data/lib/cmdx/deprecators/log.rb +22 -0
  27. data/lib/cmdx/deprecators/warn.rb +21 -0
  28. data/lib/cmdx/deprecators.rb +101 -0
  29. data/lib/cmdx/errors.rb +145 -79
  30. data/lib/cmdx/executors/fiber.rb +42 -0
  31. data/lib/cmdx/executors/thread.rb +36 -0
  32. data/lib/cmdx/executors.rb +95 -0
  33. data/lib/cmdx/fault.rb +85 -78
  34. data/lib/cmdx/i18n_proxy.rb +104 -0
  35. data/lib/cmdx/input.rb +294 -0
  36. data/lib/cmdx/inputs.rb +218 -0
  37. data/lib/cmdx/log_formatters/json.rb +9 -20
  38. data/lib/cmdx/log_formatters/key_value.rb +10 -21
  39. data/lib/cmdx/log_formatters/line.rb +7 -19
  40. data/lib/cmdx/log_formatters/logstash.rb +8 -21
  41. data/lib/cmdx/log_formatters/raw.rb +8 -20
  42. data/lib/cmdx/logger_proxy.rb +30 -0
  43. data/lib/cmdx/mergers/deep_merge.rb +23 -0
  44. data/lib/cmdx/mergers/last_write_wins.rb +23 -0
  45. data/lib/cmdx/mergers/no_merge.rb +20 -0
  46. data/lib/cmdx/mergers.rb +95 -0
  47. data/lib/cmdx/middlewares.rb +128 -0
  48. data/lib/cmdx/output.rb +115 -0
  49. data/lib/cmdx/outputs.rb +66 -0
  50. data/lib/cmdx/pipeline.rb +144 -131
  51. data/lib/cmdx/railtie.rb +10 -36
  52. data/lib/cmdx/result.rb +252 -473
  53. data/lib/cmdx/retriers/bounded_random.rb +24 -0
  54. data/lib/cmdx/retriers/decorrelated_jitter.rb +28 -0
  55. data/lib/cmdx/retriers/exponential.rb +23 -0
  56. data/lib/cmdx/retriers/fibonacci.rb +39 -0
  57. data/lib/cmdx/retriers/full_random.rb +23 -0
  58. data/lib/cmdx/retriers/half_random.rb +24 -0
  59. data/lib/cmdx/retriers/linear.rb +23 -0
  60. data/lib/cmdx/retriers.rb +106 -0
  61. data/lib/cmdx/retry.rb +117 -138
  62. data/lib/cmdx/runtime.rb +251 -0
  63. data/lib/cmdx/settings.rb +68 -196
  64. data/lib/cmdx/signal.rb +165 -0
  65. data/lib/cmdx/task.rb +443 -336
  66. data/lib/cmdx/telemetry.rb +108 -0
  67. data/lib/cmdx/util.rb +73 -0
  68. data/lib/cmdx/validators/absence.rb +10 -39
  69. data/lib/cmdx/validators/exclusion.rb +33 -52
  70. data/lib/cmdx/validators/format.rb +19 -49
  71. data/lib/cmdx/validators/inclusion.rb +33 -54
  72. data/lib/cmdx/validators/length.rb +125 -127
  73. data/lib/cmdx/validators/numeric.rb +123 -123
  74. data/lib/cmdx/validators/presence.rb +10 -39
  75. data/lib/cmdx/validators/validate.rb +31 -0
  76. data/lib/cmdx/validators.rb +161 -0
  77. data/lib/cmdx/version.rb +2 -4
  78. data/lib/cmdx/workflow.rb +74 -82
  79. data/lib/cmdx.rb +111 -42
  80. data/lib/generators/cmdx/install_generator.rb +7 -17
  81. data/lib/generators/cmdx/task_generator.rb +12 -29
  82. data/lib/generators/cmdx/templates/install.rb +128 -52
  83. data/lib/generators/cmdx/templates/task.rb.tt +1 -1
  84. data/lib/generators/cmdx/templates/workflow.rb.tt +1 -2
  85. data/lib/generators/cmdx/workflow_generator.rb +12 -29
  86. data/lib/locales/en.yml +9 -6
  87. data/mkdocs.yml +25 -23
  88. metadata +39 -138
  89. data/lib/cmdx/attribute.rb +0 -440
  90. data/lib/cmdx/attribute_registry.rb +0 -185
  91. data/lib/cmdx/attribute_value.rb +0 -252
  92. data/lib/cmdx/callback_registry.rb +0 -169
  93. data/lib/cmdx/coercion_registry.rb +0 -138
  94. data/lib/cmdx/deprecator.rb +0 -77
  95. data/lib/cmdx/exception.rb +0 -46
  96. data/lib/cmdx/executor.rb +0 -374
  97. data/lib/cmdx/identifier.rb +0 -30
  98. data/lib/cmdx/locale.rb +0 -78
  99. data/lib/cmdx/middleware_registry.rb +0 -148
  100. data/lib/cmdx/middlewares/correlate.rb +0 -140
  101. data/lib/cmdx/middlewares/runtime.rb +0 -62
  102. data/lib/cmdx/middlewares/timeout.rb +0 -78
  103. data/lib/cmdx/parallelizer.rb +0 -100
  104. data/lib/cmdx/utils/call.rb +0 -53
  105. data/lib/cmdx/utils/condition.rb +0 -71
  106. data/lib/cmdx/utils/format.rb +0 -82
  107. data/lib/cmdx/utils/normalize.rb +0 -52
  108. data/lib/cmdx/utils/wrap.rb +0 -38
  109. data/lib/cmdx/validator_registry.rb +0 -143
  110. data/lib/generators/cmdx/locale_generator.rb +0 -39
  111. data/lib/locales/af.yml +0 -53
  112. data/lib/locales/ar.yml +0 -53
  113. data/lib/locales/az.yml +0 -53
  114. data/lib/locales/be.yml +0 -53
  115. data/lib/locales/bg.yml +0 -53
  116. data/lib/locales/bn.yml +0 -53
  117. data/lib/locales/bs.yml +0 -53
  118. data/lib/locales/ca.yml +0 -53
  119. data/lib/locales/cnr.yml +0 -53
  120. data/lib/locales/cs.yml +0 -53
  121. data/lib/locales/cy.yml +0 -53
  122. data/lib/locales/da.yml +0 -53
  123. data/lib/locales/de.yml +0 -53
  124. data/lib/locales/dz.yml +0 -53
  125. data/lib/locales/el.yml +0 -53
  126. data/lib/locales/eo.yml +0 -53
  127. data/lib/locales/es.yml +0 -53
  128. data/lib/locales/et.yml +0 -53
  129. data/lib/locales/eu.yml +0 -53
  130. data/lib/locales/fa.yml +0 -53
  131. data/lib/locales/fi.yml +0 -53
  132. data/lib/locales/fr.yml +0 -53
  133. data/lib/locales/fy.yml +0 -53
  134. data/lib/locales/gd.yml +0 -53
  135. data/lib/locales/gl.yml +0 -53
  136. data/lib/locales/he.yml +0 -53
  137. data/lib/locales/hi.yml +0 -53
  138. data/lib/locales/hr.yml +0 -53
  139. data/lib/locales/hu.yml +0 -53
  140. data/lib/locales/hy.yml +0 -53
  141. data/lib/locales/id.yml +0 -53
  142. data/lib/locales/is.yml +0 -53
  143. data/lib/locales/it.yml +0 -53
  144. data/lib/locales/ja.yml +0 -53
  145. data/lib/locales/ka.yml +0 -53
  146. data/lib/locales/kk.yml +0 -53
  147. data/lib/locales/km.yml +0 -53
  148. data/lib/locales/kn.yml +0 -53
  149. data/lib/locales/ko.yml +0 -53
  150. data/lib/locales/lb.yml +0 -53
  151. data/lib/locales/lo.yml +0 -53
  152. data/lib/locales/lt.yml +0 -53
  153. data/lib/locales/lv.yml +0 -53
  154. data/lib/locales/mg.yml +0 -53
  155. data/lib/locales/mk.yml +0 -53
  156. data/lib/locales/ml.yml +0 -53
  157. data/lib/locales/mn.yml +0 -53
  158. data/lib/locales/mr-IN.yml +0 -53
  159. data/lib/locales/ms.yml +0 -53
  160. data/lib/locales/nb.yml +0 -53
  161. data/lib/locales/ne.yml +0 -53
  162. data/lib/locales/nl.yml +0 -53
  163. data/lib/locales/nn.yml +0 -53
  164. data/lib/locales/oc.yml +0 -53
  165. data/lib/locales/or.yml +0 -53
  166. data/lib/locales/pa.yml +0 -53
  167. data/lib/locales/pl.yml +0 -53
  168. data/lib/locales/pt.yml +0 -53
  169. data/lib/locales/rm.yml +0 -53
  170. data/lib/locales/ro.yml +0 -53
  171. data/lib/locales/ru.yml +0 -53
  172. data/lib/locales/sc.yml +0 -53
  173. data/lib/locales/sk.yml +0 -53
  174. data/lib/locales/sl.yml +0 -53
  175. data/lib/locales/sq.yml +0 -53
  176. data/lib/locales/sr.yml +0 -53
  177. data/lib/locales/st.yml +0 -53
  178. data/lib/locales/sv.yml +0 -53
  179. data/lib/locales/sw.yml +0 -53
  180. data/lib/locales/ta.yml +0 -53
  181. data/lib/locales/te.yml +0 -53
  182. data/lib/locales/th.yml +0 -53
  183. data/lib/locales/tl.yml +0 -53
  184. data/lib/locales/tr.yml +0 -53
  185. data/lib/locales/tt.yml +0 -53
  186. data/lib/locales/ug.yml +0 -53
  187. data/lib/locales/uk.yml +0 -53
  188. data/lib/locales/ur.yml +0 -53
  189. data/lib/locales/uz.yml +0 -53
  190. data/lib/locales/vi.yml +0 -53
  191. data/lib/locales/wo.yml +0 -53
  192. data/lib/locales/zh-CN.yml +0 -53
  193. data/lib/locales/zh-HK.yml +0 -53
  194. data/lib/locales/zh-TW.yml +0 -53
  195. data/lib/locales/zh-YUE.yml +0 -53
@@ -1,80 +1,67 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CMDx
4
- module Validators
5
- # Validates the length of a value against various constraints.
6
- #
7
- # This validator supports multiple length validation strategies including exact length,
8
- # minimum/maximum bounds, and range-based validation. It can be used to ensure
9
- # values meet specific length requirements for strings, arrays, and other
10
- # enumerable objects.
4
+ class Validators
5
+ # Validates the `#length` of `value` against one of: `:within` /
6
+ # `:not_within` / `:in` / `:not_in` (Range), `:min` + `:max`,
7
+ # `:gt` / `:lt` (strict comparison), or `:is` / `:is_not` (exact
8
+ # match). `:gte`, `:lte`, `:eq`, `:not_eq` are accepted as aliases
9
+ # of `:min`, `:max`, `:is`, `:is_not` respectively (with matching
10
+ # `_message` overrides). Values without `#length` fail with the
11
+ # `:nil_message` override or a default.
11
12
  module Length
12
13
 
13
14
  extend self
14
15
 
15
- # Validates a value's length against specified constraints.
16
- #
17
- # @param value [String, Array, Hash, Object] The value to validate (must respond to #length)
18
- # @param options [Hash] Validation options
19
- # @option options [Range] :within Range that the length must fall within (inclusive)
20
- # @option options [Range] :not_within Range that the length must not fall within
21
- # @option options [Range] :in Alias for :within
22
- # @option options [Range] :not_in Range that the length must not fall within
23
- # @option options [Integer] :min Minimum allowed length
24
- # @option options [Integer] :max Maximum allowed length
25
- # @option options [Integer] :is Exact required length
26
- # @option options [Integer] :is_not Length that is not allowed
27
- # @option options [String] :message Custom error message for all validations
28
- # @option options [String] :within_message Custom message for within/range validations
29
- # @option options [String] :in_message Custom message for :in validation
30
- # @option options [String] :not_within_message Custom message for not_within validation
31
- # @option options [String] :not_in_message Custom message for not_in validation
32
- # @option options [String] :min_message Custom message for minimum length validation
33
- # @option options [String] :max_message Custom message for maximum length validation
34
- # @option options [String] :is_message Custom message for exact length validation
35
- # @option options [String] :is_not_message Custom message for is_not validation
36
- #
37
- # @return [nil] Returns nil if validation passes
38
- #
39
- # @raise [ValidationError] When validation fails
40
- # @raise [ArgumentError] When unknown validation options are provided
41
- #
42
- # @example Exact length validation
43
- # Length.call("hello", is: 5)
44
- # # => nil (validation passes)
45
- # @example Range-based validation
46
- # Length.call("test", within: 3..6)
47
- # # => nil (validation passes - length 4 is within range)
48
- # @example Min/max validation
49
- # Length.call("username", min: 3, max: 20)
50
- # # => nil (validation passes - length 8 is between 3 and 20)
51
- # @example Exclusion validation
52
- # Length.call("short", not_in: 1..3)
53
- # # => nil (validation passes - length 5 is not in excluded range)
54
- #
55
- # @rbs (untyped value, Hash[Symbol, untyped] options) -> nil
16
+ ALIASES = {
17
+ gte: :min,
18
+ lte: :max,
19
+ eq: :is,
20
+ not_eq: :is_not,
21
+ gte_message: :min_message,
22
+ lte_message: :max_message,
23
+ eq_message: :is_message,
24
+ not_eq_message: :is_not_message
25
+ }.freeze
26
+ private_constant :ALIASES
27
+
28
+ # @param value [#length, nil]
29
+ # @param options [Hash{Symbol => Object}] see module summary
30
+ # @option options [String] :message global failure-message override
31
+ # @option options [String] :nil_message override when `value` lacks `#length`
32
+ # @option options [String] :within_message, :in_message, :not_within_message,
33
+ # :not_in_message, :min_message, :max_message, :gt_message, :lt_message,
34
+ # :is_message, :is_not_message
35
+ # @return [Validators::Failure, nil]
36
+ # @raise [ArgumentError] when no recognized length option is given
56
37
  def call(value, options = EMPTY_HASH)
57
- length = value&.length
38
+ return nil_failure(options) unless value.respond_to?(:length)
58
39
 
59
- case options
40
+ length = value.length
41
+
42
+ case options = options.transform_keys(ALIASES)
60
43
  in within:
61
- raise_within_validation_error!(within.begin, within.end, options) unless within&.cover?(length)
44
+ within_failure(within.begin, within.end, options) unless within.cover?(length)
62
45
  in not_within:
63
- raise_not_within_validation_error!(not_within.begin, not_within.end, options) if not_within&.cover?(length)
46
+ not_within_failure(not_within.begin, not_within.end, options) if not_within.cover?(length)
64
47
  in in: xin
65
- raise_within_validation_error!(xin.begin, xin.end, options) unless xin&.cover?(length)
48
+ within_failure(xin.begin, xin.end, options) unless xin.cover?(length)
66
49
  in not_in:
67
- raise_not_within_validation_error!(not_in.begin, not_in.end, options) if not_in&.cover?(length)
50
+ not_within_failure(not_in.begin, not_in.end, options) if not_in.cover?(length)
68
51
  in min:, max:
69
- raise_within_validation_error!(min, max, options) unless length&.between?(min, max)
52
+ within_failure(min, max, options) unless length.between?(min, max)
70
53
  in min:
71
- raise_min_validation_error!(min, options) unless !length.nil? && (min <= length)
54
+ min_failure(min, options) unless min <= length
72
55
  in max:
73
- raise_max_validation_error!(max, options) unless !length.nil? && (length <= max)
56
+ max_failure(max, options) unless length <= max
57
+ in gt:
58
+ gt_failure(gt, options) unless gt < length
59
+ in lt:
60
+ lt_failure(lt, options) unless length < lt
74
61
  in is:
75
- raise_is_validation_error!(is, options) unless !length.nil? && (length == is)
62
+ is_failure(is, options) unless length == is
76
63
  in is_not:
77
- raise_is_not_validation_error!(is_not, options) if !length.nil? && (length == is_not)
64
+ is_not_failure(is_not, options) if length == is_not
78
65
  else
79
66
  raise ArgumentError, "unknown length validator options given"
80
67
  end
@@ -82,102 +69,113 @@ module CMDx
82
69
 
83
70
  private
84
71
 
85
- # Raises validation error for within/range validations.
86
- #
87
- # @param min [Integer] Minimum length value
88
- # @param max [Integer] Maximum length value
89
- # @param options [Hash] Validation options containing custom messages
90
- # @option options [Object] :* Any validation option key-value pairs
91
- #
92
- # @raise [ValidationError] Always raised with appropriate message
93
- #
94
- # @rbs (Integer min, Integer max, Hash[Symbol, untyped] options) -> void
95
- def raise_within_validation_error!(min, max, options)
72
+ # @param options [Hash{Symbol => Object}]
73
+ # @option options [String] :nil_message
74
+ # @option options [String] :message
75
+ # @return [Validators::Failure]
76
+ def nil_failure(options)
77
+ message = options[:nil_message] || options[:message]
78
+ Failure.new(message || I18nProxy.t("cmdx.validators.length.nil_value"))
79
+ end
80
+
81
+ # @param min [Object]
82
+ # @param max [Object]
83
+ # @param options [Hash{Symbol => Object}]
84
+ # @option options [String] :within_message
85
+ # @option options [String] :in_message
86
+ # @option options [String] :message
87
+ # @return [Validators::Failure]
88
+ def within_failure(min, max, options)
96
89
  message = options[:within_message] || options[:in_message] || options[:message]
97
90
  message %= { min:, max: } unless message.nil?
98
91
 
99
- raise ValidationError, message || Locale.t("cmdx.validators.length.within", min:, max:)
92
+ Failure.new(message || I18nProxy.t("cmdx.validators.length.within", min:, max:))
100
93
  end
101
94
 
102
- # Raises validation error for not_within validations.
103
- #
104
- # @param min [Integer] Minimum length value
105
- # @param max [Integer] Maximum length value
106
- # @param options [Hash] Validation options containing custom messages
107
- # @option options [Object] :* Any validation option key-value pairs
108
- #
109
- # @raise [ValidationError] Always raised with appropriate message
110
- #
111
- # @rbs (Integer min, Integer max, Hash[Symbol, untyped] options) -> void
112
- def raise_not_within_validation_error!(min, max, options)
95
+ # @param min [Object]
96
+ # @param max [Object]
97
+ # @param options [Hash{Symbol => Object}]
98
+ # @option options [String] :not_within_message
99
+ # @option options [String] :not_in_message
100
+ # @option options [String] :message
101
+ # @return [Validators::Failure]
102
+ def not_within_failure(min, max, options)
113
103
  message = options[:not_within_message] || options[:not_in_message] || options[:message]
114
104
  message %= { min:, max: } unless message.nil?
115
105
 
116
- raise ValidationError, message || Locale.t("cmdx.validators.length.not_within", min:, max:)
106
+ Failure.new(message || I18nProxy.t("cmdx.validators.length.not_within", min:, max:))
117
107
  end
118
108
 
119
- # Raises validation error for minimum length validation.
120
- #
121
- # @param min [Integer] Minimum required length
122
- # @param options [Hash] Validation options containing custom messages
123
- # @option options [Object] :* Any validation option key-value pairs
124
- #
125
- # @raise [ValidationError] Always raised with appropriate message
126
- #
127
- # @rbs (Integer min, Hash[Symbol, untyped] options) -> void
128
- def raise_min_validation_error!(min, options)
109
+ # @param min [Object]
110
+ # @param options [Hash{Symbol => Object}]
111
+ # @option options [String] :min_message
112
+ # @option options [String] :message
113
+ # @return [Validators::Failure]
114
+ def min_failure(min, options)
129
115
  message = options[:min_message] || options[:message]
130
116
  message %= { min: } unless message.nil?
131
117
 
132
- raise ValidationError, message || Locale.t("cmdx.validators.length.min", min:)
118
+ Failure.new(message || I18nProxy.t("cmdx.validators.length.min", min:))
133
119
  end
134
120
 
135
- # Raises validation error for maximum length validation.
136
- #
137
- # @param max [Integer] Maximum allowed length
138
- # @param options [Hash] Validation options containing custom messages
139
- # @option options [Object] :* Any validation option key-value pairs
140
- #
141
- # @raise [ValidationError] Always raised with appropriate message
142
- #
143
- # @rbs (Integer max, Hash[Symbol, untyped] options) -> void
144
- def raise_max_validation_error!(max, options)
121
+ # @param max [Object]
122
+ # @param options [Hash{Symbol => Object}]
123
+ # @option options [String] :max_message
124
+ # @option options [String] :message
125
+ # @return [Validators::Failure]
126
+ def max_failure(max, options)
145
127
  message = options[:max_message] || options[:message]
146
128
  message %= { max: } unless message.nil?
147
129
 
148
- raise ValidationError, message || Locale.t("cmdx.validators.length.max", max:)
130
+ Failure.new(message || I18nProxy.t("cmdx.validators.length.max", max:))
131
+ end
132
+
133
+ # @param gt [Object]
134
+ # @param options [Hash{Symbol => Object}]
135
+ # @option options [String] :gt_message
136
+ # @option options [String] :message
137
+ # @return [Validators::Failure]
138
+ def gt_failure(gt, options)
139
+ message = options[:gt_message] || options[:message]
140
+ message %= { gt: } unless message.nil?
141
+
142
+ Failure.new(message || I18nProxy.t("cmdx.validators.length.gt", gt:))
143
+ end
144
+
145
+ # @param lt [Object]
146
+ # @param options [Hash{Symbol => Object}]
147
+ # @option options [String] :lt_message
148
+ # @option options [String] :message
149
+ # @return [Validators::Failure]
150
+ def lt_failure(lt, options)
151
+ message = options[:lt_message] || options[:message]
152
+ message %= { lt: } unless message.nil?
153
+
154
+ Failure.new(message || I18nProxy.t("cmdx.validators.length.lt", lt:))
149
155
  end
150
156
 
151
- # Raises validation error for exact length validation.
152
- #
153
- # @param is [Integer] Required exact length
154
- # @param options [Hash] Validation options containing custom messages
155
- # @option options [Object] :* Any validation option key-value pairs
156
- #
157
- # @raise [ValidationError] Always raised with appropriate message
158
- #
159
- # @rbs (Integer is, Hash[Symbol, untyped] options) -> void
160
- def raise_is_validation_error!(is, options)
157
+ # @param is [Object]
158
+ # @param options [Hash{Symbol => Object}]
159
+ # @option options [String] :is_message
160
+ # @option options [String] :message
161
+ # @return [Validators::Failure]
162
+ def is_failure(is, options) # rubocop:disable Naming/PredicatePrefix
161
163
  message = options[:is_message] || options[:message]
162
164
  message %= { is: } unless message.nil?
163
165
 
164
- raise ValidationError, message || Locale.t("cmdx.validators.length.is", is:)
166
+ Failure.new(message || I18nProxy.t("cmdx.validators.length.is", is:))
165
167
  end
166
168
 
167
- # Raises validation error for is_not length validation.
168
- #
169
- # @param is_not [Integer] Length that is not allowed
170
- # @param options [Hash] Validation options containing custom messages
171
- # @option options [Object] :* Any validation option key-value pairs
172
- #
173
- # @raise [ValidationError] Always raised with appropriate message
174
- #
175
- # @rbs (Integer is_not, Hash[Symbol, untyped] options) -> void
176
- def raise_is_not_validation_error!(is_not, options)
169
+ # @param is_not [Object]
170
+ # @param options [Hash{Symbol => Object}]
171
+ # @option options [String] :is_not_message
172
+ # @option options [String] :message
173
+ # @return [Validators::Failure]
174
+ def is_not_failure(is_not, options) # rubocop:disable Naming/PredicatePrefix
177
175
  message = options[:is_not_message] || options[:message]
178
176
  message %= { is_not: } unless message.nil?
179
177
 
180
- raise ValidationError, message || Locale.t("cmdx.validators.length.is_not", is_not:)
178
+ Failure.new(message || I18nProxy.t("cmdx.validators.length.is_not", is_not:))
181
179
  end
182
180
 
183
181
  end
@@ -1,75 +1,64 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CMDx
4
- module Validators
5
- # Validates numeric values against various constraints and ranges
6
- #
7
- # This validator ensures that numeric values meet specified criteria such as
8
- # minimum/maximum bounds, exact matches, or range inclusions. It supports
9
- # both inclusive and exclusive range validations with customizable error messages.
4
+ class Validators
5
+ # Validates a numeric `value` against one of: `:within` / `:not_within`
6
+ # / `:in` / `:not_in` (Range), `:min` + `:max`, `:gt` / `:lt` (strict
7
+ # comparison), or `:is` / `:is_not` (exact match). `:gte`, `:lte`,
8
+ # `:eq`, `:not_eq` are accepted as aliases of `:min`, `:max`, `:is`,
9
+ # `:is_not` respectively (with matching `_message` overrides). `nil`
10
+ # fails with `:nil_message` override or default.
10
11
  module Numeric
11
12
 
12
13
  extend self
13
14
 
14
- # Validates a numeric value against the specified options
15
- #
16
- # @param value [Numeric] The numeric value to validate
17
- # @param options [Hash] Validation configuration options
18
- # @option options [Range] :within Range that the value must fall within (inclusive)
19
- # @option options [Range] :not_within Range that the value must not fall within
20
- # @option options [Range] :in Alias for :within option
21
- # @option options [Range] :not_in Alias for :not_within option
22
- # @option options [Numeric] :min Minimum allowed value (inclusive)
23
- # @option options [Numeric] :max Maximum allowed value (inclusive)
24
- # @option options [Numeric] :is Exact value that must match
25
- # @option options [Numeric] :is_not Value that must not match
26
- # @option options [String] :message Custom error message template
27
- # @option options [String] :within_message Custom message for range validations
28
- # @option options [String] :not_within_message Custom message for exclusion validations
29
- # @option options [String] :min_message Custom message for minimum validation
30
- # @option options [String] :max_message Custom message for maximum validation
31
- # @option options [String] :is_message Custom message for exact match validation
32
- # @option options [String] :is_not_message Custom message for exclusion validation
33
- #
34
- # @return [nil] Returns nil if validation passes
35
- #
36
- # @raise [ValidationError] When the value fails validation
37
- # @raise [ArgumentError] When unknown validator options are provided
38
- #
39
- # @example Validate value within a range
40
- # Numeric.call(5, within: 1..10)
41
- # # => nil (validation passes)
42
- # @example Validate minimum and maximum bounds
43
- # Numeric.call(15, min: 10, max: 20)
44
- # # => nil (validation passes)
45
- # @example Validate exact value match
46
- # Numeric.call(42, is: 42)
47
- # # => nil (validation passes)
48
- # @example Validate value exclusion
49
- # Numeric.call(5, not_in: 1..10)
50
- # # => nil (validation passes - 5 is not in 1..10)
51
- #
52
- # @rbs (Numeric value, Hash[Symbol, untyped] options) -> nil
15
+ ALIASES = {
16
+ gte: :min,
17
+ lte: :max,
18
+ eq: :is,
19
+ not_eq: :is_not,
20
+ gte_message: :min_message,
21
+ lte_message: :max_message,
22
+ eq_message: :is_message,
23
+ not_eq_message: :is_not_message
24
+ }.freeze
25
+ private_constant :ALIASES
26
+
27
+ # @param value [Numeric, nil]
28
+ # @param options [Hash{Symbol => Object}] see module summary
29
+ # @option options [String] :message global failure-message override
30
+ # @option options [String] :nil_message override when `value` is nil
31
+ # @option options [String] :within_message, :in_message, :not_within_message,
32
+ # :not_in_message, :min_message, :max_message, :gt_message, :lt_message,
33
+ # :is_message, :is_not_message
34
+ # @return [Validators::Failure, nil]
35
+ # @raise [ArgumentError] when no recognized numeric option is given
53
36
  def call(value, options = EMPTY_HASH)
54
- case options
37
+ return nil_failure(options) if value.nil?
38
+
39
+ case options = options.transform_keys(ALIASES)
55
40
  in within:
56
- raise_within_validation_error!(within.begin, within.end, options) unless within&.cover?(value)
41
+ within_failure(within.begin, within.end, options) unless within.cover?(value)
57
42
  in not_within:
58
- raise_not_within_validation_error!(not_within.begin, not_within.end, options) if not_within&.cover?(value)
43
+ not_within_failure(not_within.begin, not_within.end, options) if not_within.cover?(value)
59
44
  in in: xin
60
- raise_within_validation_error!(xin.begin, xin.end, options) unless xin&.cover?(value)
45
+ within_failure(xin.begin, xin.end, options) unless xin.cover?(value)
61
46
  in not_in:
62
- raise_not_within_validation_error!(not_in.begin, not_in.end, options) if not_in&.cover?(value)
47
+ not_within_failure(not_in.begin, not_in.end, options) if not_in.cover?(value)
63
48
  in min:, max:
64
- raise_within_validation_error!(min, max, options) unless value&.between?(min, max)
49
+ within_failure(min, max, options) unless value.between?(min, max)
65
50
  in min:
66
- raise_min_validation_error!(min, options) unless !value.nil? && (min <= value)
51
+ min_failure(min, options) unless min <= value
67
52
  in max:
68
- raise_max_validation_error!(max, options) unless !value.nil? && (value <= max)
53
+ max_failure(max, options) unless value <= max
54
+ in gt:
55
+ gt_failure(gt, options) unless gt < value
56
+ in lt:
57
+ lt_failure(lt, options) unless value < lt
69
58
  in is:
70
- raise_is_validation_error!(is, options) unless !value.nil? && (value == is)
59
+ is_failure(is, options) unless value == is
71
60
  in is_not:
72
- raise_is_not_validation_error!(is_not, options) if !value.nil? && (value == is_not)
61
+ is_not_failure(is_not, options) if value == is_not
73
62
  else
74
63
  raise ArgumentError, "unknown numeric validator options given"
75
64
  end
@@ -77,102 +66,113 @@ module CMDx
77
66
 
78
67
  private
79
68
 
80
- # Raises validation error for range inclusion validation
81
- #
82
- # @param min [Numeric] The minimum value of the allowed range
83
- # @param max [Numeric] The maximum value of the allowed range
84
- # @param options [Hash] Validation options containing custom messages
85
- # @option options [Object] :* Any validation option key-value pairs
86
- #
87
- # @raise [ValidationError] With appropriate error message
88
- #
89
- # @rbs (Numeric min, Numeric max, Hash[Symbol, untyped] options) -> void
90
- def raise_within_validation_error!(min, max, options)
69
+ # @param options [Hash{Symbol => Object}]
70
+ # @option options [String] :nil_message
71
+ # @option options [String] :message
72
+ # @return [Validators::Failure]
73
+ def nil_failure(options)
74
+ message = options[:nil_message] || options[:message]
75
+ Failure.new(message || I18nProxy.t("cmdx.validators.numeric.nil_value"))
76
+ end
77
+
78
+ # @param min [Object]
79
+ # @param max [Object]
80
+ # @param options [Hash{Symbol => Object}]
81
+ # @option options [String] :within_message
82
+ # @option options [String] :in_message
83
+ # @option options [String] :message
84
+ # @return [Validators::Failure]
85
+ def within_failure(min, max, options)
91
86
  message = options[:within_message] || options[:in_message] || options[:message]
92
87
  message %= { min:, max: } unless message.nil?
93
88
 
94
- raise ValidationError, message || Locale.t("cmdx.validators.numeric.within", min:, max:)
89
+ Failure.new(message || I18nProxy.t("cmdx.validators.numeric.within", min:, max:))
95
90
  end
96
91
 
97
- # Raises validation error for range exclusion validation
98
- #
99
- # @param min [Numeric] The minimum value of the excluded range
100
- # @param max [Numeric] The maximum value of the excluded range
101
- # @param options [Hash] Validation options containing custom messages
102
- # @option options [Object] :* Any validation option key-value pairs
103
- #
104
- # @raise [ValidationError] With appropriate error message
105
- #
106
- # @rbs (Numeric min, Numeric max, Hash[Symbol, untyped] options) -> void
107
- def raise_not_within_validation_error!(min, max, options)
92
+ # @param min [Object]
93
+ # @param max [Object]
94
+ # @param options [Hash{Symbol => Object}]
95
+ # @option options [String] :not_within_message
96
+ # @option options [String] :not_in_message
97
+ # @option options [String] :message
98
+ # @return [Validators::Failure]
99
+ def not_within_failure(min, max, options)
108
100
  message = options[:not_within_message] || options[:not_in_message] || options[:message]
109
101
  message %= { min:, max: } unless message.nil?
110
102
 
111
- raise ValidationError, message || Locale.t("cmdx.validators.numeric.not_within", min:, max:)
103
+ Failure.new(message || I18nProxy.t("cmdx.validators.numeric.not_within", min:, max:))
112
104
  end
113
105
 
114
- # Raises validation error for minimum value validation
115
- #
116
- # @param min [Numeric] The minimum allowed value
117
- # @param options [Hash] Validation options containing custom messages
118
- # @option options [Object] :* Any validation option key-value pairs
119
- #
120
- # @raise [ValidationError] With appropriate error message
121
- #
122
- # @rbs (Numeric min, Hash[Symbol, untyped] options) -> void
123
- def raise_min_validation_error!(min, options)
106
+ # @param min [Object]
107
+ # @param options [Hash{Symbol => Object}]
108
+ # @option options [String] :min_message
109
+ # @option options [String] :message
110
+ # @return [Validators::Failure]
111
+ def min_failure(min, options)
124
112
  message = options[:min_message] || options[:message]
125
113
  message %= { min: } unless message.nil?
126
114
 
127
- raise ValidationError, message || Locale.t("cmdx.validators.numeric.min", min:)
115
+ Failure.new(message || I18nProxy.t("cmdx.validators.numeric.min", min:))
128
116
  end
129
117
 
130
- # Raises validation error for maximum value validation
131
- #
132
- # @param max [Numeric] The maximum allowed value
133
- # @param options [Hash] Validation options containing custom messages
134
- # @option options [Object] :* Any validation option key-value pairs
135
- #
136
- # @raise [ValidationError] With appropriate error message
137
- #
138
- # @rbs (Numeric max, Hash[Symbol, untyped] options) -> void
139
- def raise_max_validation_error!(max, options)
118
+ # @param max [Object]
119
+ # @param options [Hash{Symbol => Object}]
120
+ # @option options [String] :max_message
121
+ # @option options [String] :message
122
+ # @return [Validators::Failure]
123
+ def max_failure(max, options)
140
124
  message = options[:max_message] || options[:message]
141
125
  message %= { max: } unless message.nil?
142
126
 
143
- raise ValidationError, message || Locale.t("cmdx.validators.numeric.max", max:)
127
+ Failure.new(message || I18nProxy.t("cmdx.validators.numeric.max", max:))
128
+ end
129
+
130
+ # @param gt [Object]
131
+ # @param options [Hash{Symbol => Object}]
132
+ # @option options [String] :gt_message
133
+ # @option options [String] :message
134
+ # @return [Validators::Failure]
135
+ def gt_failure(gt, options)
136
+ message = options[:gt_message] || options[:message]
137
+ message %= { gt: } unless message.nil?
138
+
139
+ Failure.new(message || I18nProxy.t("cmdx.validators.numeric.gt", gt:))
140
+ end
141
+
142
+ # @param lt [Object]
143
+ # @param options [Hash{Symbol => Object}]
144
+ # @option options [String] :lt_message
145
+ # @option options [String] :message
146
+ # @return [Validators::Failure]
147
+ def lt_failure(lt, options)
148
+ message = options[:lt_message] || options[:message]
149
+ message %= { lt: } unless message.nil?
150
+
151
+ Failure.new(message || I18nProxy.t("cmdx.validators.numeric.lt", lt:))
144
152
  end
145
153
 
146
- # Raises validation error for exact value match validation
147
- #
148
- # @param is [Numeric] The exact value that was expected
149
- # @param options [Hash] Validation options containing custom messages
150
- # @option options [Object] :* Any validation option key-value pairs
151
- #
152
- # @raise [ValidationError] With appropriate error message
153
- #
154
- # @rbs (Numeric is, Hash[Symbol, untyped] options) -> void
155
- def raise_is_validation_error!(is, options)
154
+ # @param is [Object]
155
+ # @param options [Hash{Symbol => Object}]
156
+ # @option options [String] :is_message
157
+ # @option options [String] :message
158
+ # @return [Validators::Failure]
159
+ def is_failure(is, options) # rubocop:disable Naming/PredicatePrefix
156
160
  message = options[:is_message] || options[:message]
157
161
  message %= { is: } unless message.nil?
158
162
 
159
- raise ValidationError, message || Locale.t("cmdx.validators.numeric.is", is:)
163
+ Failure.new(message || I18nProxy.t("cmdx.validators.numeric.is", is:))
160
164
  end
161
165
 
162
- # Raises validation error for value exclusion validation
163
- #
164
- # @param is_not [Numeric] The value that was not allowed
165
- # @param options [Hash] Validation options containing custom messages
166
- # @option options [Object] :* Any validation option key-value pairs
167
- #
168
- # @raise [ValidationError] With appropriate error message
169
- #
170
- # @rbs (Numeric is_not, Hash[Symbol, untyped] options) -> void
171
- def raise_is_not_validation_error!(is_not, options)
166
+ # @param is_not [Object]
167
+ # @param options [Hash{Symbol => Object}]
168
+ # @option options [String] :is_not_message
169
+ # @option options [String] :message
170
+ # @return [Validators::Failure]
171
+ def is_not_failure(is_not, options) # rubocop:disable Naming/PredicatePrefix
172
172
  message = options[:is_not_message] || options[:message]
173
173
  message %= { is_not: } unless message.nil?
174
174
 
175
- raise ValidationError, message || Locale.t("cmdx.validators.numeric.is_not", is_not:)
175
+ Failure.new(message || I18nProxy.t("cmdx.validators.numeric.is_not", is_not:))
176
176
  end
177
177
 
178
178
  end