cmdx 1.1.2 → 1.5.1

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 (192) hide show
  1. checksums.yaml +4 -4
  2. data/.DS_Store +0 -0
  3. data/.cursor/prompts/docs.md +4 -1
  4. data/.cursor/prompts/llms.md +20 -0
  5. data/.cursor/prompts/rspec.md +4 -1
  6. data/.cursor/prompts/yardoc.md +3 -2
  7. data/.cursor/rules/cursor-instructions.mdc +55 -1
  8. data/.irbrc +6 -0
  9. data/.rubocop.yml +29 -18
  10. data/CHANGELOG.md +11 -132
  11. data/LLM.md +3317 -0
  12. data/README.md +68 -44
  13. data/docs/attributes/coercions.md +162 -0
  14. data/docs/attributes/defaults.md +90 -0
  15. data/docs/attributes/definitions.md +281 -0
  16. data/docs/attributes/naming.md +78 -0
  17. data/docs/attributes/validations.md +309 -0
  18. data/docs/basics/chain.md +56 -249
  19. data/docs/basics/context.md +56 -289
  20. data/docs/basics/execution.md +114 -0
  21. data/docs/basics/setup.md +37 -334
  22. data/docs/callbacks.md +89 -467
  23. data/docs/deprecation.md +91 -174
  24. data/docs/getting_started.md +212 -202
  25. data/docs/internationalization.md +11 -647
  26. data/docs/interruptions/exceptions.md +23 -198
  27. data/docs/interruptions/faults.md +71 -151
  28. data/docs/interruptions/halt.md +109 -186
  29. data/docs/logging.md +44 -256
  30. data/docs/middlewares.md +113 -426
  31. data/docs/outcomes/result.md +81 -228
  32. data/docs/outcomes/states.md +33 -221
  33. data/docs/outcomes/statuses.md +21 -311
  34. data/docs/tips_and_tricks.md +120 -70
  35. data/docs/workflows.md +99 -283
  36. data/lib/cmdx/.DS_Store +0 -0
  37. data/lib/cmdx/attribute.rb +229 -0
  38. data/lib/cmdx/attribute_registry.rb +94 -0
  39. data/lib/cmdx/attribute_value.rb +193 -0
  40. data/lib/cmdx/callback_registry.rb +69 -77
  41. data/lib/cmdx/chain.rb +56 -73
  42. data/lib/cmdx/coercion_registry.rb +52 -68
  43. data/lib/cmdx/coercions/array.rb +19 -18
  44. data/lib/cmdx/coercions/big_decimal.rb +20 -24
  45. data/lib/cmdx/coercions/boolean.rb +26 -25
  46. data/lib/cmdx/coercions/complex.rb +21 -22
  47. data/lib/cmdx/coercions/date.rb +25 -23
  48. data/lib/cmdx/coercions/date_time.rb +24 -25
  49. data/lib/cmdx/coercions/float.rb +25 -22
  50. data/lib/cmdx/coercions/hash.rb +31 -32
  51. data/lib/cmdx/coercions/integer.rb +30 -24
  52. data/lib/cmdx/coercions/rational.rb +29 -24
  53. data/lib/cmdx/coercions/string.rb +19 -22
  54. data/lib/cmdx/coercions/symbol.rb +37 -0
  55. data/lib/cmdx/coercions/time.rb +26 -25
  56. data/lib/cmdx/configuration.rb +49 -108
  57. data/lib/cmdx/context.rb +222 -44
  58. data/lib/cmdx/deprecator.rb +61 -0
  59. data/lib/cmdx/errors.rb +42 -252
  60. data/lib/cmdx/exceptions.rb +39 -0
  61. data/lib/cmdx/faults.rb +78 -39
  62. data/lib/cmdx/freezer.rb +51 -0
  63. data/lib/cmdx/identifier.rb +30 -0
  64. data/lib/cmdx/locale.rb +52 -0
  65. data/lib/cmdx/log_formatters/json.rb +21 -22
  66. data/lib/cmdx/log_formatters/key_value.rb +20 -22
  67. data/lib/cmdx/log_formatters/line.rb +15 -22
  68. data/lib/cmdx/log_formatters/logstash.rb +22 -23
  69. data/lib/cmdx/log_formatters/raw.rb +16 -22
  70. data/lib/cmdx/middleware_registry.rb +70 -74
  71. data/lib/cmdx/middlewares/correlate.rb +90 -54
  72. data/lib/cmdx/middlewares/runtime.rb +58 -0
  73. data/lib/cmdx/middlewares/timeout.rb +48 -68
  74. data/lib/cmdx/railtie.rb +12 -45
  75. data/lib/cmdx/result.rb +229 -314
  76. data/lib/cmdx/task.rb +194 -366
  77. data/lib/cmdx/utils/call.rb +49 -0
  78. data/lib/cmdx/utils/condition.rb +71 -0
  79. data/lib/cmdx/utils/format.rb +61 -0
  80. data/lib/cmdx/validator_registry.rb +63 -72
  81. data/lib/cmdx/validators/exclusion.rb +38 -67
  82. data/lib/cmdx/validators/format.rb +48 -49
  83. data/lib/cmdx/validators/inclusion.rb +43 -74
  84. data/lib/cmdx/validators/length.rb +101 -162
  85. data/lib/cmdx/validators/numeric.rb +95 -170
  86. data/lib/cmdx/validators/presence.rb +37 -50
  87. data/lib/cmdx/version.rb +1 -1
  88. data/lib/cmdx/worker.rb +178 -0
  89. data/lib/cmdx/workflow.rb +85 -81
  90. data/lib/cmdx.rb +19 -13
  91. data/lib/generators/cmdx/install_generator.rb +14 -13
  92. data/lib/generators/cmdx/task_generator.rb +25 -50
  93. data/lib/generators/cmdx/templates/install.rb +11 -46
  94. data/lib/generators/cmdx/templates/task.rb.tt +3 -2
  95. data/lib/locales/en.yml +18 -4
  96. data/src/cmdx-logo.png +0 -0
  97. metadata +32 -116
  98. data/docs/ai_prompts.md +0 -393
  99. data/docs/basics/call.md +0 -317
  100. data/docs/configuration.md +0 -344
  101. data/docs/parameters/coercions.md +0 -396
  102. data/docs/parameters/defaults.md +0 -335
  103. data/docs/parameters/definitions.md +0 -446
  104. data/docs/parameters/namespacing.md +0 -378
  105. data/docs/parameters/validations.md +0 -405
  106. data/docs/testing.md +0 -553
  107. data/lib/cmdx/callback.rb +0 -53
  108. data/lib/cmdx/chain_inspector.rb +0 -56
  109. data/lib/cmdx/chain_serializer.rb +0 -63
  110. data/lib/cmdx/coercion.rb +0 -57
  111. data/lib/cmdx/coercions/virtual.rb +0 -29
  112. data/lib/cmdx/core_ext/hash.rb +0 -83
  113. data/lib/cmdx/core_ext/module.rb +0 -98
  114. data/lib/cmdx/core_ext/object.rb +0 -125
  115. data/lib/cmdx/correlator.rb +0 -122
  116. data/lib/cmdx/error.rb +0 -67
  117. data/lib/cmdx/fault.rb +0 -140
  118. data/lib/cmdx/immutator.rb +0 -52
  119. data/lib/cmdx/lazy_struct.rb +0 -246
  120. data/lib/cmdx/log_formatters/pretty_json.rb +0 -40
  121. data/lib/cmdx/log_formatters/pretty_key_value.rb +0 -38
  122. data/lib/cmdx/log_formatters/pretty_line.rb +0 -41
  123. data/lib/cmdx/logger.rb +0 -49
  124. data/lib/cmdx/logger_ansi.rb +0 -68
  125. data/lib/cmdx/logger_serializer.rb +0 -116
  126. data/lib/cmdx/middleware.rb +0 -70
  127. data/lib/cmdx/parameter.rb +0 -312
  128. data/lib/cmdx/parameter_evaluator.rb +0 -231
  129. data/lib/cmdx/parameter_inspector.rb +0 -66
  130. data/lib/cmdx/parameter_registry.rb +0 -106
  131. data/lib/cmdx/parameter_serializer.rb +0 -59
  132. data/lib/cmdx/result_ansi.rb +0 -71
  133. data/lib/cmdx/result_inspector.rb +0 -71
  134. data/lib/cmdx/result_logger.rb +0 -59
  135. data/lib/cmdx/result_serializer.rb +0 -104
  136. data/lib/cmdx/rspec/matchers.rb +0 -28
  137. data/lib/cmdx/rspec/result_matchers/be_executed.rb +0 -42
  138. data/lib/cmdx/rspec/result_matchers/be_failed_task.rb +0 -94
  139. data/lib/cmdx/rspec/result_matchers/be_skipped_task.rb +0 -94
  140. data/lib/cmdx/rspec/result_matchers/be_state_matchers.rb +0 -59
  141. data/lib/cmdx/rspec/result_matchers/be_status_matchers.rb +0 -57
  142. data/lib/cmdx/rspec/result_matchers/be_successful_task.rb +0 -87
  143. data/lib/cmdx/rspec/result_matchers/have_bad_outcome.rb +0 -51
  144. data/lib/cmdx/rspec/result_matchers/have_caused_failure.rb +0 -58
  145. data/lib/cmdx/rspec/result_matchers/have_chain_index.rb +0 -59
  146. data/lib/cmdx/rspec/result_matchers/have_context.rb +0 -86
  147. data/lib/cmdx/rspec/result_matchers/have_empty_metadata.rb +0 -54
  148. data/lib/cmdx/rspec/result_matchers/have_good_outcome.rb +0 -52
  149. data/lib/cmdx/rspec/result_matchers/have_metadata.rb +0 -114
  150. data/lib/cmdx/rspec/result_matchers/have_preserved_context.rb +0 -66
  151. data/lib/cmdx/rspec/result_matchers/have_received_thrown_failure.rb +0 -64
  152. data/lib/cmdx/rspec/result_matchers/have_runtime.rb +0 -78
  153. data/lib/cmdx/rspec/result_matchers/have_thrown_failure.rb +0 -76
  154. data/lib/cmdx/rspec/task_matchers/be_well_formed_task.rb +0 -62
  155. data/lib/cmdx/rspec/task_matchers/have_callback.rb +0 -85
  156. data/lib/cmdx/rspec/task_matchers/have_cmd_setting.rb +0 -68
  157. data/lib/cmdx/rspec/task_matchers/have_executed_callbacks.rb +0 -92
  158. data/lib/cmdx/rspec/task_matchers/have_middleware.rb +0 -46
  159. data/lib/cmdx/rspec/task_matchers/have_parameter.rb +0 -181
  160. data/lib/cmdx/task_deprecator.rb +0 -58
  161. data/lib/cmdx/task_processor.rb +0 -246
  162. data/lib/cmdx/task_serializer.rb +0 -57
  163. data/lib/cmdx/utils/ansi_color.rb +0 -73
  164. data/lib/cmdx/utils/log_timestamp.rb +0 -36
  165. data/lib/cmdx/utils/monotonic_runtime.rb +0 -34
  166. data/lib/cmdx/utils/name_affix.rb +0 -52
  167. data/lib/cmdx/validator.rb +0 -57
  168. data/lib/generators/cmdx/templates/workflow.rb.tt +0 -7
  169. data/lib/generators/cmdx/workflow_generator.rb +0 -84
  170. data/lib/locales/ar.yml +0 -35
  171. data/lib/locales/cs.yml +0 -35
  172. data/lib/locales/da.yml +0 -35
  173. data/lib/locales/de.yml +0 -35
  174. data/lib/locales/el.yml +0 -35
  175. data/lib/locales/es.yml +0 -35
  176. data/lib/locales/fi.yml +0 -35
  177. data/lib/locales/fr.yml +0 -35
  178. data/lib/locales/he.yml +0 -35
  179. data/lib/locales/hi.yml +0 -35
  180. data/lib/locales/it.yml +0 -35
  181. data/lib/locales/ja.yml +0 -35
  182. data/lib/locales/ko.yml +0 -35
  183. data/lib/locales/nl.yml +0 -35
  184. data/lib/locales/no.yml +0 -35
  185. data/lib/locales/pl.yml +0 -35
  186. data/lib/locales/pt.yml +0 -35
  187. data/lib/locales/ru.yml +0 -35
  188. data/lib/locales/sv.yml +0 -35
  189. data/lib/locales/th.yml +0 -35
  190. data/lib/locales/tr.yml +0 -35
  191. data/lib/locales/vi.yml +0 -35
  192. data/lib/locales/zh.yml +0 -35
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CMDx
4
+ module Utils
5
+ # Utility module for invoking callable objects with different invocation strategies.
6
+ #
7
+ # This module provides a unified interface for calling methods, procs, and other
8
+ # callable objects on target objects, handling the appropriate invocation method
9
+ # based on the callable type.
10
+ module Call
11
+
12
+ extend self
13
+
14
+ # Invokes a callable object on the target with the given arguments.
15
+ #
16
+ # @param target [Object] The target object to invoke the callable on
17
+ # @param callable [Symbol, Proc, #call] The callable to invoke
18
+ # @param args [Array] Positional arguments to pass to the callable
19
+ # @param kwargs [Hash] Keyword arguments to pass to the callable
20
+ # @param &block [Proc, nil] Block to pass to the callable
21
+ #
22
+ # @return [Object] The result of invoking the callable
23
+ #
24
+ # @raise [RuntimeError] When the callable cannot be invoked
25
+ #
26
+ # @example Invoking a method by symbol
27
+ # Call.invoke(user, :name)
28
+ # Call.invoke(user, :update, { name: 'John' })
29
+ # @example Invoking a proc
30
+ # proc = ->(name) { "Hello #{name}" }
31
+ # Call.invoke(user, proc, 'John')
32
+ # @example Invoking a callable object
33
+ # callable = MyCallable.new
34
+ # Call.invoke(user, callable, 'data')
35
+ def invoke(target, callable, *args, **kwargs, &)
36
+ if callable.is_a?(Symbol)
37
+ target.send(callable, *args, **kwargs, &)
38
+ elsif callable.is_a?(Proc)
39
+ target.instance_exec(*args, **kwargs, &callable)
40
+ elsif callable.respond_to?(:call)
41
+ callable.call(*args, **kwargs, &)
42
+ else
43
+ raise "cannot invoke #{callable}"
44
+ end
45
+ end
46
+
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CMDx
4
+ module Utils
5
+ # Provides conditional evaluation utilities for CMDx tasks and workflows.
6
+ #
7
+ # This module handles conditional logic evaluation with support for `if` and `unless`
8
+ # conditions using various callable types including symbols, procs, and objects
9
+ # responding to `call`.
10
+ module Condition
11
+
12
+ extend self
13
+
14
+ EVAL = proc do |target, callable, *args, **kwargs, &block|
15
+ case callable
16
+ when NilClass, FalseClass, TrueClass then !!callable
17
+ when Symbol then target.send(callable, *args, **kwargs, &block)
18
+ when Proc then target.instance_exec(*args, **kwargs, &callable)
19
+ else
20
+ raise "cannot evaluate #{callable.inspect}" unless callable.respond_to?(:call)
21
+
22
+ callable.call(*args, **kwargs, &block)
23
+ end
24
+ end.freeze
25
+ private_constant :EVAL
26
+
27
+ # Evaluates conditional logic based on provided options.
28
+ #
29
+ # Supports both `if` and `unless` conditions, with `unless` taking precedence
30
+ # when both are specified. Returns true if no conditions are provided.
31
+ #
32
+ # @param target [Object] The target object to evaluate conditions against
33
+ # @param options [Hash] Conditional options hash
34
+ # @option options [Object] :if Condition that must be true for evaluation to succeed
35
+ # @option options [Object] :unless Condition that must be false for evaluation to succeed
36
+ # @param args [Array] Additional arguments passed to condition evaluation
37
+ # @param kwargs [Hash] Additional keyword arguments passed to condition evaluation
38
+ # @param block [Proc, nil] Optional block passed to condition evaluation
39
+ #
40
+ # @return [Boolean] true if conditions are met, false otherwise
41
+ #
42
+ # @raise [RuntimeError] When a callable cannot be evaluated
43
+ #
44
+ # @example Basic if condition
45
+ # Condition.evaluate(user, if: :active?)
46
+ # # => true if user.active? returns true
47
+ # @example Unless condition
48
+ # Condition.evaluate(user, unless: :blocked?)
49
+ # # => true if user.blocked? returns false
50
+ # @example Combined conditions
51
+ # Condition.evaluate(user, if: :verified?, unless: :suspended?)
52
+ # # => true if user.verified? is true AND user.suspended? is false
53
+ # @example With arguments and block
54
+ # Condition.evaluate(user, if: ->(u) { u.has_permission?(:admin) }, :admin)
55
+ # # => true if the proc returns true when called with user and :admin
56
+ def evaluate(target, options, ...)
57
+ case options
58
+ in if: if_cond, unless: unless_cond
59
+ EVAL.call(target, if_cond, ...) && !EVAL.call(target, unless_cond, ...)
60
+ in if: if_cond
61
+ EVAL.call(target, if_cond, ...)
62
+ in unless: unless_cond
63
+ !EVAL.call(target, unless_cond, ...)
64
+ else
65
+ true
66
+ end
67
+ end
68
+
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CMDx
4
+ module Utils
5
+ # Utility module for formatting data structures into log-friendly strings
6
+ # and converting messages to appropriate formats for logging
7
+ module Format
8
+
9
+ extend self
10
+
11
+ FORMATTER = proc do |key, value|
12
+ "#{key}=#{value.inspect}"
13
+ end.freeze
14
+ private_constant :FORMATTER
15
+
16
+ # Converts a message to a format suitable for logging
17
+ #
18
+ # @param message [Object] The message to format
19
+ #
20
+ # @return [Hash, Object] Returns a hash if the message responds to to_h and is a CMDx object, otherwise returns the original message
21
+ #
22
+ # @example Hash like objects
23
+ # Format.to_log({user_id: 123, action: "login"})
24
+ # # => {user_id: 123, action: "login"}
25
+ # @example Simple message
26
+ # Format.to_log("simple message")
27
+ # # => "simple message"
28
+ # @example CMDx object
29
+ # Format.to_log(CMDx::Task.new(name: "task1"))
30
+ # # => {name: "task1"}
31
+ def to_log(message)
32
+ if message.respond_to?(:to_h) && message.class.ancestors.any? { |a| a.to_s.start_with?("CMDx") }
33
+ message.to_h
34
+ else
35
+ message
36
+ end
37
+ end
38
+
39
+ # Converts a hash to a formatted string using a custom formatter
40
+ #
41
+ # @param hash [Hash] The hash to convert to string
42
+ # @param block [Proc, nil] Optional custom formatter block
43
+ # @option block [String] :key The hash key
44
+ # @option block [Object] :value The hash value
45
+ #
46
+ # @return [String] Space-separated formatted key-value pairs
47
+ #
48
+ # @example Default formatter
49
+ # Format.to_str({user_id: 123, status: "active"})
50
+ # # => "user_id=123 status=\"active\""
51
+ # @example Custom formatter
52
+ # Format.to_str({count: 5, total: 100}) { |k, v| "#{k}:#{v}" }
53
+ # # => "count:5 total:100"
54
+ def to_str(hash, &block)
55
+ block ||= FORMATTER
56
+ hash.map(&block).join(" ")
57
+ end
58
+
59
+ end
60
+ end
61
+ end
@@ -1,32 +1,24 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CMDx
4
- # Registry for parameter validation handlers in the CMDx framework.
5
- #
6
- # ValidatorRegistry manages the collection of validator implementations
7
- # that can be used for parameter validation in tasks. It provides a
8
- # centralized registry where validators can be registered by type and
9
- # invoked during parameter processing. The registry comes pre-loaded
10
- # with built-in validators for common validation scenarios.
4
+ # Registry for managing validation rules and their corresponding validator classes.
5
+ # Provides methods to register, deregister, and execute validators against task values.
11
6
  class ValidatorRegistry
12
7
 
13
- # @return [Hash] internal hash storing validator implementations by type
8
+ extend Forwardable
9
+
14
10
  attr_reader :registry
11
+ alias to_h registry
15
12
 
16
- # Creates a new validator registry with built-in validators.
17
- #
18
- # The registry is initialized with standard validators including
19
- # exclusion, format, inclusion, length, numeric, and presence validation.
20
- # These built-in validators provide common validation functionality
21
- # that can be immediately used without additional registration.
13
+ def_delegators :registry, :keys
14
+
15
+ # Initialize a new validator registry with default validators.
22
16
  #
23
- # @return [ValidatorRegistry] a new registry instance with built-in validators
17
+ # @param registry [Hash, nil] Optional hash mapping validator names to validator classes
24
18
  #
25
- # @example Create a new validator registry
26
- # registry = ValidatorRegistry.new
27
- # registry.registry.keys #=> [:exclusion, :format, :inclusion, :length, :numeric, :presence]
28
- def initialize
29
- @registry = {
19
+ # @return [ValidatorRegistry] A new validator registry instance
20
+ def initialize(registry = nil)
21
+ @registry = registry || {
30
22
  exclusion: Validators::Exclusion,
31
23
  format: Validators::Format,
32
24
  inclusion: Validators::Inclusion,
@@ -36,72 +28,71 @@ module CMDx
36
28
  }
37
29
  end
38
30
 
39
- # Registers a new validator implementation for the specified type.
40
- #
41
- # This method allows custom validators to be added to the registry,
42
- # enabling extended validation functionality beyond the built-in
43
- # validators. The validator can be a class, symbol, string, or proc
44
- # that implements the validation logic.
31
+ # Create a duplicate of the registry with copied internal state.
45
32
  #
46
- # @param type [Symbol] the validator type identifier
47
- # @param validator [Class, Symbol, String, Proc] the validator implementation
48
- #
49
- # @return [ValidatorRegistry] returns self for method chaining
50
- #
51
- # @example Register a custom validator class
52
- # registry.register(:email, EmailValidator)
33
+ # @return [ValidatorRegistry] A new validator registry with duplicated registry hash
34
+ def dup
35
+ self.class.new(registry.dup)
36
+ end
37
+
38
+ # Register a new validator class with the given name.
53
39
  #
54
- # @example Register a symbol validator
55
- # registry.register(:zipcode, :validate_zipcode)
40
+ # @param name [String, Symbol] The name to register the validator under
41
+ # @param validator [Class] The validator class to register
56
42
  #
57
- # @example Register a proc validator
58
- # registry.register(:positive, ->(value, options) { value > 0 })
43
+ # @return [ValidatorRegistry] Returns self for method chaining
59
44
  #
60
- # @example Method chaining
61
- # registry.register(:email, EmailValidator)
62
- # .register(:phone, PhoneValidator)
63
- def register(type, validator)
64
- registry[type] = validator
45
+ # @example
46
+ # registry.register(:custom, CustomValidator)
47
+ # registry.register("email", EmailValidator)
48
+ def register(name, validator)
49
+ registry[name.to_sym] = validator
65
50
  self
66
51
  end
67
52
 
68
- # Executes validation for a parameter value using the specified validator type.
69
- #
70
- # This method performs validation by looking up the registered validator
71
- # for the given type and executing it with the provided value and options.
72
- # The validation is only performed if the task's evaluation of the options
73
- # returns a truthy value, allowing for conditional validation.
53
+ # Remove a validator from the registry by name.
74
54
  #
75
- # @param task [Task] the task instance performing validation
76
- # @param type [Symbol] the validator type to use
77
- # @param value [Object] the value to validate
78
- # @param options [Hash] validation options and configuration
55
+ # @param name [String, Symbol] The name of the validator to remove
79
56
  #
80
- # @return [Object, nil] the validation result or nil if validation was skipped
57
+ # @return [ValidatorRegistry] Returns self for method chaining
81
58
  #
82
- # @raise [UnknownValidatorError] if the specified validator type is not registered
59
+ # @example
60
+ # registry.deregister(:format)
61
+ # registry.deregister("presence")
62
+ def deregister(name)
63
+ registry.delete(name.to_sym)
64
+ self
65
+ end
66
+
67
+ # Validate a value using the specified validator type and options.
83
68
  #
84
- # @example Validate with a built-in validator
85
- # registry.call(task, :presence, "", {})
86
- # #=> may raise ValidationError if value is blank
69
+ # @param type [Symbol] The type of validator to use
70
+ # @param task [Task] The task context for validation
71
+ # @param value [Object] The value to validate
72
+ # @param options [Hash, Object] Validation options or condition
73
+ # @option options [Boolean] :allow_nil Whether to allow nil values
87
74
  #
88
- # @example Validate with options
89
- # registry.call(task, :length, "hello", minimum: 3, maximum: 10)
90
- # #=> validates string length is between 3 and 10 characters
75
+ # @raise [TypeError] When the validator type is not registered
91
76
  #
92
- # @example Conditional validation that gets skipped
93
- # registry.call(task, :presence, "", if: -> { false })
94
- # #=> returns nil without performing validation
95
- def call(task, type, value, options = {})
96
- raise UnknownValidatorError, "unknown validator #{type}" unless registry.key?(type)
97
- return unless task.cmdx_eval(options)
77
+ # @example
78
+ # registry.validate(:presence, task, user.name, presence: true)
79
+ # registry.validate(:length, task, password, { min: 8, allow_nil: false })
80
+ def validate(type, task, value, options = {})
81
+ raise TypeError, "unknown validator type #{type.inspect}" unless registry.key?(type)
82
+
83
+ match =
84
+ if options.is_a?(Hash)
85
+ case options
86
+ in allow_nil: then allow_nil && value.nil?
87
+ else Utils::Condition.evaluate(task, options, value)
88
+ end
89
+ else
90
+ options
91
+ end
92
+
93
+ return unless match
98
94
 
99
- case validator = registry[type]
100
- when Symbol, String, Proc
101
- task.cmdx_try(validator, value, options)
102
- else
103
- validator.call(value, options)
104
- end
95
+ Utils::Call.invoke(task, registry[type], value, options)
105
96
  end
106
97
 
107
98
  end
@@ -2,103 +2,74 @@
2
2
 
3
3
  module CMDx
4
4
  module Validators
5
- # Validator class for excluding values from a specified set.
5
+ # Validates that a value is not included in a specified set or range
6
6
  #
7
- # This validator ensures that a value is not included in a given array or range
8
- # of forbidden values. It supports both discrete value exclusion and range-based
9
- # exclusion validation.
10
- class Exclusion < Validator
7
+ # This validator ensures that the given value is excluded from a collection
8
+ # of forbidden values or falls outside a specified range. It supports both
9
+ # discrete value lists and range-based exclusions.
10
+ module Exclusion
11
11
 
12
- # Validates that the given value is not included in the exclusion set.
13
- #
14
- # @param value [Object] the value to validate
15
- # @param options [Hash] validation options containing exclusion configuration
16
- # @option options [Hash] :exclusion exclusion validation configuration
17
- # @option options [Array, Range] :exclusion.in the values to exclude
18
- # @option options [Array, Range] :exclusion.within alias for :in
19
- # @option options [String] :exclusion.message custom error message
20
- # @option options [String] :exclusion.of_message custom error message for array exclusion
21
- # @option options [String] :exclusion.in_message custom error message for range exclusion
22
- # @option options [String] :exclusion.within_message alias for :in_message
23
- #
24
- # @return [void]
25
- #
26
- # @raise [ValidationError] if the value is found in the exclusion set
27
- #
28
- # @example Excluding from an array
29
- # Validators::Exclusion.call("admin", exclusion: { in: ["admin", "root"] })
30
- # # raises ValidationError: "must not be one of: \"admin\", \"root\""
12
+ extend self
13
+
14
+ # Validates that a value is excluded from the specified options
31
15
  #
32
- # @example Excluding from a range
33
- # Validators::Exclusion.call(5, exclusion: { in: 1..10 })
34
- # # raises ValidationError: "must not be within 1 and 10"
16
+ # @param value [Object] The value to validate for exclusion
17
+ # @param options [Hash] Validation configuration options
18
+ # @option options [Array, Range] :in The collection of forbidden values or range
19
+ # @option options [Array, Range] :within Alias for :in option
20
+ # @option options [String] :message Custom error message template
21
+ # @option options [String] :of_message Custom message for discrete value exclusions
22
+ # @option options [String] :in_message Custom message for range-based exclusions
23
+ # @option options [String] :within_message Custom message for range-based exclusions
35
24
  #
36
- # @example Valid exclusion
37
- # Validators::Exclusion.call("user", exclusion: { in: ["admin", "root"] })
38
- # #=> nil (no error raised)
25
+ # @raise [ValidationError] When the value is found in the forbidden collection
39
26
  #
40
- # @example Using a custom message
41
- # Validators::Exclusion.call("admin", exclusion: { in: ["admin", "root"], message: "Reserved username not allowed" })
42
- # # raises ValidationError: "Reserved username not allowed"
27
+ # @example Exclude specific values
28
+ # Exclusion.call("admin", in: ["admin", "root", "superuser"])
29
+ # # => raises ValidationError if value is "admin"
30
+ # @example Exclude values within a range
31
+ # Exclusion.call(5, in: 1..10)
32
+ # # => raises ValidationError if value is 5 (within 1..10)
33
+ # @example Exclude with custom message
34
+ # Exclusion.call("test", in: ["test", "demo"], message: "value %{values} is forbidden")
43
35
  def call(value, options = {})
44
36
  values = options[:in] || options[:within]
45
37
 
46
38
  if values.is_a?(Range)
47
39
  raise_within_validation_error!(values.begin, values.end, options) if values.cover?(value)
48
- elsif Array(values).any? { |v| v === value } # rubocop:disable Style/CaseEquality
40
+ elsif Array(values).any? { |v| v === value }
49
41
  raise_of_validation_error!(values, options)
50
42
  end
51
43
  end
52
44
 
53
45
  private
54
46
 
55
- # Raises a validation error for array-based exclusion.
47
+ # Raises validation error for discrete value exclusions
56
48
  #
57
- # @param values [Array] the excluded values
58
- # @param options [Hash] validation options
49
+ # @param values [Array] The forbidden values that caused the error
50
+ # @param options [Hash] Validation options containing custom messages
59
51
  #
60
- # @return [void]
61
- #
62
- # @raise [ValidationError] always raised with appropriate message
63
- #
64
- # @example
65
- # raise_of_validation_error!(["admin", "root"], {})
66
- # # raises ValidationError: "must not be one of: \"admin\", \"root\""
52
+ # @raise [ValidationError] With appropriate error message
67
53
  def raise_of_validation_error!(values, options)
68
- values = values.map(&:inspect).join(", ") unless values.nil?
54
+ values = values.map(&:inspect).join(", ") unless values.nil?
69
55
  message = options[:of_message] || options[:message]
70
56
  message %= { values: } unless message.nil?
71
57
 
72
- raise ValidationError, message || I18n.t(
73
- "cmdx.validators.exclusion.of",
74
- values:,
75
- default: "must not be one of: #{values}"
76
- )
58
+ raise ValidationError, message || Locale.t("cmdx.validators.exclusion.of", values:)
77
59
  end
78
60
 
79
- # Raises a validation error for range-based exclusion.
80
- #
81
- # @param min [Object] the minimum value of the range
82
- # @param max [Object] the maximum value of the range
83
- # @param options [Hash] validation options
84
- #
85
- # @return [void]
61
+ # Raises validation error for range-based exclusions
86
62
  #
87
- # @raise [ValidationError] always raised with appropriate message
63
+ # @param min [Object] The minimum value of the forbidden range
64
+ # @param max [Object] The maximum value of the forbidden range
65
+ # @param options [Hash] Validation options containing custom messages
88
66
  #
89
- # @example
90
- # raise_within_validation_error!(1, 10, {})
91
- # # raises ValidationError: "must not be within 1 and 10"
67
+ # @raise [ValidationError] With appropriate error message
92
68
  def raise_within_validation_error!(min, max, options)
93
69
  message = options[:in_message] || options[:within_message] || options[:message]
94
70
  message %= { min:, max: } unless message.nil?
95
71
 
96
- raise ValidationError, message || I18n.t(
97
- "cmdx.validators.exclusion.within",
98
- min:,
99
- max:,
100
- default: "must not be within #{min} and #{max}"
101
- )
72
+ raise ValidationError, message || Locale.t("cmdx.validators.exclusion.within", min:, max:)
102
73
  end
103
74
 
104
75
  end
@@ -2,64 +2,63 @@
2
2
 
3
3
  module CMDx
4
4
  module Validators
5
- # Validator class for format validation using regular expressions.
5
+ # Validates that a value matches a specified format pattern
6
6
  #
7
- # This validator ensures that a value matches or doesn't match specified
8
- # regular expression patterns. It supports both positive matching (with)
9
- # and negative matching (without) patterns, which can be used independently
10
- # or in combination.
11
- class Format < Validator
7
+ # This validator ensures that the given value conforms to a specific format
8
+ # using regular expressions. It supports both direct regex matching and
9
+ # conditional matching with inclusion/exclusion patterns.
10
+ module Format
12
11
 
13
- # Validates that the given value matches the specified format pattern(s).
14
- #
15
- # @param value [Object] the value to validate
16
- # @param options [Hash] validation options containing format configuration
17
- # @option options [Hash] :format format validation configuration
18
- # @option options [Regexp] :format.with pattern the value must match
19
- # @option options [Regexp] :format.without pattern the value must not match
20
- # @option options [String] :format.message custom error message
21
- #
22
- # @return [void]
23
- #
24
- # @raise [ValidationError] if the value doesn't match the format requirements
25
- #
26
- # @example Validating with a positive pattern
27
- # Validators::Format.call("user123", format: { with: /\A[a-z]+\d+\z/ })
28
- # #=> nil (no error raised)
12
+ extend self
13
+
14
+ # Validates that a value matches the specified format pattern
29
15
  #
30
- # @example Validating with a negative pattern
31
- # Validators::Format.call("admin", format: { without: /admin|root/ })
32
- # # raises ValidationError: "is an invalid format"
16
+ # @param value [Object] The value to validate for format compliance
17
+ # @param options [Hash, Regexp] Validation configuration options or direct regex pattern
18
+ # @option options [Regexp] :with Required pattern that the value must match
19
+ # @option options [Regexp] :without Pattern that the value must not match
20
+ # @option options [String] :message Custom error message
33
21
  #
34
- # @example Validating with both patterns
35
- # Validators::Format.call("user123", format: { with: /\A[a-z]+\d+\z/, without: /admin|root/ })
36
- # #=> nil (no error raised)
22
+ # @return [nil] Returns nil if validation passes
37
23
  #
38
- # @example Invalid format with positive pattern
39
- # Validators::Format.call("123abc", format: { with: /\A[a-z]+\d+\z/ })
40
- # # raises ValidationError: "is an invalid format"
24
+ # @raise [ValidationError] When the value doesn't match the required format
41
25
  #
42
- # @example Using a custom message
43
- # Validators::Format.call("123abc", format: { with: /\A[a-z]+\d+\z/, message: "Username must start with letters" })
44
- # # raises ValidationError: "Username must start with letters"
26
+ # @example Direct regex validation
27
+ # Format.call("user@example.com", /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i)
28
+ # # => nil (validation passes)
29
+ # @example Validate with required pattern
30
+ # Format.call("ABC123", with: /\A[A-Z]{3}\d{3}\z/)
31
+ # # => nil (validation passes)
32
+ # @example Validate with exclusion pattern
33
+ # Format.call("hello", without: /\d/)
34
+ # # => nil (validation passes - no digits)
35
+ # @example Validate with both patterns
36
+ # Format.call("test123", with: /\A\w+\z/, without: /\A\d+\z/)
37
+ # # => nil (validation passes - alphanumeric but not all digits)
38
+ # @example Validate with custom message
39
+ # Format.call("invalid", with: /\A\d+\z/, message: "Must contain only digits")
40
+ # # => raises ValidationError with custom message
45
41
  def call(value, options = {})
46
- valid = case options
47
- in { with: with, without: without }
48
- value.match?(with) && !value.match?(without)
49
- in { with: with }
50
- value.match?(with)
51
- in { without: without }
52
- !value.match?(without)
53
- else
54
- false
55
- end
42
+ match =
43
+ if options.is_a?(Regexp)
44
+ value&.match?(options)
45
+ else
46
+ case options
47
+ in with:, without:
48
+ value&.match?(with) && !value&.match?(without)
49
+ in with:
50
+ value&.match?(with)
51
+ in without:
52
+ !value&.match?(without)
53
+ else
54
+ false
55
+ end
56
+ end
56
57
 
57
- return if valid
58
+ return if match
58
59
 
59
- raise ValidationError, options[:message] || I18n.t(
60
- "cmdx.validators.format",
61
- default: "is an invalid format"
62
- )
60
+ message = options[:message] if options.is_a?(Hash)
61
+ raise ValidationError, message || Locale.t("cmdx.validators.format")
63
62
  end
64
63
 
65
64
  end