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.
- checksums.yaml +4 -4
- data/.DS_Store +0 -0
- data/.cursor/prompts/docs.md +4 -1
- data/.cursor/prompts/llms.md +20 -0
- data/.cursor/prompts/rspec.md +4 -1
- data/.cursor/prompts/yardoc.md +3 -2
- data/.cursor/rules/cursor-instructions.mdc +55 -1
- data/.irbrc +6 -0
- data/.rubocop.yml +29 -18
- data/CHANGELOG.md +11 -132
- data/LLM.md +3317 -0
- data/README.md +68 -44
- data/docs/attributes/coercions.md +162 -0
- data/docs/attributes/defaults.md +90 -0
- data/docs/attributes/definitions.md +281 -0
- data/docs/attributes/naming.md +78 -0
- data/docs/attributes/validations.md +309 -0
- data/docs/basics/chain.md +56 -249
- data/docs/basics/context.md +56 -289
- data/docs/basics/execution.md +114 -0
- data/docs/basics/setup.md +37 -334
- data/docs/callbacks.md +89 -467
- data/docs/deprecation.md +91 -174
- data/docs/getting_started.md +212 -202
- data/docs/internationalization.md +11 -647
- data/docs/interruptions/exceptions.md +23 -198
- data/docs/interruptions/faults.md +71 -151
- data/docs/interruptions/halt.md +109 -186
- data/docs/logging.md +44 -256
- data/docs/middlewares.md +113 -426
- data/docs/outcomes/result.md +81 -228
- data/docs/outcomes/states.md +33 -221
- data/docs/outcomes/statuses.md +21 -311
- data/docs/tips_and_tricks.md +120 -70
- data/docs/workflows.md +99 -283
- data/lib/cmdx/.DS_Store +0 -0
- data/lib/cmdx/attribute.rb +229 -0
- data/lib/cmdx/attribute_registry.rb +94 -0
- data/lib/cmdx/attribute_value.rb +193 -0
- data/lib/cmdx/callback_registry.rb +69 -77
- data/lib/cmdx/chain.rb +56 -73
- data/lib/cmdx/coercion_registry.rb +52 -68
- data/lib/cmdx/coercions/array.rb +19 -18
- data/lib/cmdx/coercions/big_decimal.rb +20 -24
- data/lib/cmdx/coercions/boolean.rb +26 -25
- data/lib/cmdx/coercions/complex.rb +21 -22
- data/lib/cmdx/coercions/date.rb +25 -23
- data/lib/cmdx/coercions/date_time.rb +24 -25
- data/lib/cmdx/coercions/float.rb +25 -22
- data/lib/cmdx/coercions/hash.rb +31 -32
- data/lib/cmdx/coercions/integer.rb +30 -24
- data/lib/cmdx/coercions/rational.rb +29 -24
- data/lib/cmdx/coercions/string.rb +19 -22
- data/lib/cmdx/coercions/symbol.rb +37 -0
- data/lib/cmdx/coercions/time.rb +26 -25
- data/lib/cmdx/configuration.rb +49 -108
- data/lib/cmdx/context.rb +222 -44
- data/lib/cmdx/deprecator.rb +61 -0
- data/lib/cmdx/errors.rb +42 -252
- data/lib/cmdx/exceptions.rb +39 -0
- data/lib/cmdx/faults.rb +78 -39
- data/lib/cmdx/freezer.rb +51 -0
- data/lib/cmdx/identifier.rb +30 -0
- data/lib/cmdx/locale.rb +52 -0
- data/lib/cmdx/log_formatters/json.rb +21 -22
- data/lib/cmdx/log_formatters/key_value.rb +20 -22
- data/lib/cmdx/log_formatters/line.rb +15 -22
- data/lib/cmdx/log_formatters/logstash.rb +22 -23
- data/lib/cmdx/log_formatters/raw.rb +16 -22
- data/lib/cmdx/middleware_registry.rb +70 -74
- data/lib/cmdx/middlewares/correlate.rb +90 -54
- data/lib/cmdx/middlewares/runtime.rb +58 -0
- data/lib/cmdx/middlewares/timeout.rb +48 -68
- data/lib/cmdx/railtie.rb +12 -45
- data/lib/cmdx/result.rb +229 -314
- data/lib/cmdx/task.rb +194 -366
- data/lib/cmdx/utils/call.rb +49 -0
- data/lib/cmdx/utils/condition.rb +71 -0
- data/lib/cmdx/utils/format.rb +61 -0
- data/lib/cmdx/validator_registry.rb +63 -72
- data/lib/cmdx/validators/exclusion.rb +38 -67
- data/lib/cmdx/validators/format.rb +48 -49
- data/lib/cmdx/validators/inclusion.rb +43 -74
- data/lib/cmdx/validators/length.rb +101 -162
- data/lib/cmdx/validators/numeric.rb +95 -170
- data/lib/cmdx/validators/presence.rb +37 -50
- data/lib/cmdx/version.rb +1 -1
- data/lib/cmdx/worker.rb +178 -0
- data/lib/cmdx/workflow.rb +85 -81
- data/lib/cmdx.rb +19 -13
- data/lib/generators/cmdx/install_generator.rb +14 -13
- data/lib/generators/cmdx/task_generator.rb +25 -50
- data/lib/generators/cmdx/templates/install.rb +11 -46
- data/lib/generators/cmdx/templates/task.rb.tt +3 -2
- data/lib/locales/en.yml +18 -4
- data/src/cmdx-logo.png +0 -0
- metadata +32 -116
- data/docs/ai_prompts.md +0 -393
- data/docs/basics/call.md +0 -317
- data/docs/configuration.md +0 -344
- data/docs/parameters/coercions.md +0 -396
- data/docs/parameters/defaults.md +0 -335
- data/docs/parameters/definitions.md +0 -446
- data/docs/parameters/namespacing.md +0 -378
- data/docs/parameters/validations.md +0 -405
- data/docs/testing.md +0 -553
- data/lib/cmdx/callback.rb +0 -53
- data/lib/cmdx/chain_inspector.rb +0 -56
- data/lib/cmdx/chain_serializer.rb +0 -63
- data/lib/cmdx/coercion.rb +0 -57
- data/lib/cmdx/coercions/virtual.rb +0 -29
- data/lib/cmdx/core_ext/hash.rb +0 -83
- data/lib/cmdx/core_ext/module.rb +0 -98
- data/lib/cmdx/core_ext/object.rb +0 -125
- data/lib/cmdx/correlator.rb +0 -122
- data/lib/cmdx/error.rb +0 -67
- data/lib/cmdx/fault.rb +0 -140
- data/lib/cmdx/immutator.rb +0 -52
- data/lib/cmdx/lazy_struct.rb +0 -246
- data/lib/cmdx/log_formatters/pretty_json.rb +0 -40
- data/lib/cmdx/log_formatters/pretty_key_value.rb +0 -38
- data/lib/cmdx/log_formatters/pretty_line.rb +0 -41
- data/lib/cmdx/logger.rb +0 -49
- data/lib/cmdx/logger_ansi.rb +0 -68
- data/lib/cmdx/logger_serializer.rb +0 -116
- data/lib/cmdx/middleware.rb +0 -70
- data/lib/cmdx/parameter.rb +0 -312
- data/lib/cmdx/parameter_evaluator.rb +0 -231
- data/lib/cmdx/parameter_inspector.rb +0 -66
- data/lib/cmdx/parameter_registry.rb +0 -106
- data/lib/cmdx/parameter_serializer.rb +0 -59
- data/lib/cmdx/result_ansi.rb +0 -71
- data/lib/cmdx/result_inspector.rb +0 -71
- data/lib/cmdx/result_logger.rb +0 -59
- data/lib/cmdx/result_serializer.rb +0 -104
- data/lib/cmdx/rspec/matchers.rb +0 -28
- data/lib/cmdx/rspec/result_matchers/be_executed.rb +0 -42
- data/lib/cmdx/rspec/result_matchers/be_failed_task.rb +0 -94
- data/lib/cmdx/rspec/result_matchers/be_skipped_task.rb +0 -94
- data/lib/cmdx/rspec/result_matchers/be_state_matchers.rb +0 -59
- data/lib/cmdx/rspec/result_matchers/be_status_matchers.rb +0 -57
- data/lib/cmdx/rspec/result_matchers/be_successful_task.rb +0 -87
- data/lib/cmdx/rspec/result_matchers/have_bad_outcome.rb +0 -51
- data/lib/cmdx/rspec/result_matchers/have_caused_failure.rb +0 -58
- data/lib/cmdx/rspec/result_matchers/have_chain_index.rb +0 -59
- data/lib/cmdx/rspec/result_matchers/have_context.rb +0 -86
- data/lib/cmdx/rspec/result_matchers/have_empty_metadata.rb +0 -54
- data/lib/cmdx/rspec/result_matchers/have_good_outcome.rb +0 -52
- data/lib/cmdx/rspec/result_matchers/have_metadata.rb +0 -114
- data/lib/cmdx/rspec/result_matchers/have_preserved_context.rb +0 -66
- data/lib/cmdx/rspec/result_matchers/have_received_thrown_failure.rb +0 -64
- data/lib/cmdx/rspec/result_matchers/have_runtime.rb +0 -78
- data/lib/cmdx/rspec/result_matchers/have_thrown_failure.rb +0 -76
- data/lib/cmdx/rspec/task_matchers/be_well_formed_task.rb +0 -62
- data/lib/cmdx/rspec/task_matchers/have_callback.rb +0 -85
- data/lib/cmdx/rspec/task_matchers/have_cmd_setting.rb +0 -68
- data/lib/cmdx/rspec/task_matchers/have_executed_callbacks.rb +0 -92
- data/lib/cmdx/rspec/task_matchers/have_middleware.rb +0 -46
- data/lib/cmdx/rspec/task_matchers/have_parameter.rb +0 -181
- data/lib/cmdx/task_deprecator.rb +0 -58
- data/lib/cmdx/task_processor.rb +0 -246
- data/lib/cmdx/task_serializer.rb +0 -57
- data/lib/cmdx/utils/ansi_color.rb +0 -73
- data/lib/cmdx/utils/log_timestamp.rb +0 -36
- data/lib/cmdx/utils/monotonic_runtime.rb +0 -34
- data/lib/cmdx/utils/name_affix.rb +0 -52
- data/lib/cmdx/validator.rb +0 -57
- data/lib/generators/cmdx/templates/workflow.rb.tt +0 -7
- data/lib/generators/cmdx/workflow_generator.rb +0 -84
- data/lib/locales/ar.yml +0 -35
- data/lib/locales/cs.yml +0 -35
- data/lib/locales/da.yml +0 -35
- data/lib/locales/de.yml +0 -35
- data/lib/locales/el.yml +0 -35
- data/lib/locales/es.yml +0 -35
- data/lib/locales/fi.yml +0 -35
- data/lib/locales/fr.yml +0 -35
- data/lib/locales/he.yml +0 -35
- data/lib/locales/hi.yml +0 -35
- data/lib/locales/it.yml +0 -35
- data/lib/locales/ja.yml +0 -35
- data/lib/locales/ko.yml +0 -35
- data/lib/locales/nl.yml +0 -35
- data/lib/locales/no.yml +0 -35
- data/lib/locales/pl.yml +0 -35
- data/lib/locales/pt.yml +0 -35
- data/lib/locales/ru.yml +0 -35
- data/lib/locales/sv.yml +0 -35
- data/lib/locales/th.yml +0 -35
- data/lib/locales/tr.yml +0 -35
- data/lib/locales/vi.yml +0 -35
- 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
|
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
|
-
|
8
|
+
extend Forwardable
|
9
|
+
|
14
10
|
attr_reader :registry
|
11
|
+
alias to_h registry
|
15
12
|
|
16
|
-
|
17
|
-
|
18
|
-
#
|
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
|
-
# @
|
17
|
+
# @param registry [Hash, nil] Optional hash mapping validator names to validator classes
|
24
18
|
#
|
25
|
-
# @
|
26
|
-
|
27
|
-
|
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
|
-
#
|
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
|
-
# @
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
#
|
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
|
-
# @
|
55
|
-
#
|
40
|
+
# @param name [String, Symbol] The name to register the validator under
|
41
|
+
# @param validator [Class] The validator class to register
|
56
42
|
#
|
57
|
-
# @
|
58
|
-
# registry.register(:positive, ->(value, options) { value > 0 })
|
43
|
+
# @return [ValidatorRegistry] Returns self for method chaining
|
59
44
|
#
|
60
|
-
# @example
|
61
|
-
# registry.register(:
|
62
|
-
#
|
63
|
-
def register(
|
64
|
-
registry[
|
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
|
-
#
|
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
|
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 [
|
57
|
+
# @return [ValidatorRegistry] Returns self for method chaining
|
81
58
|
#
|
82
|
-
# @
|
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
|
-
# @
|
85
|
-
#
|
86
|
-
#
|
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
|
-
# @
|
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
|
93
|
-
# registry.
|
94
|
-
#
|
95
|
-
def
|
96
|
-
raise
|
97
|
-
|
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
|
-
|
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
|
-
#
|
5
|
+
# Validates that a value is not included in a specified set or range
|
6
6
|
#
|
7
|
-
# This validator ensures that
|
8
|
-
# of forbidden values
|
9
|
-
#
|
10
|
-
|
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
|
-
|
13
|
-
|
14
|
-
#
|
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
|
-
# @
|
33
|
-
#
|
34
|
-
#
|
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
|
-
# @
|
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
|
41
|
-
#
|
42
|
-
# # raises ValidationError
|
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 }
|
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
|
47
|
+
# Raises validation error for discrete value exclusions
|
56
48
|
#
|
57
|
-
# @param values [Array]
|
58
|
-
# @param options [Hash]
|
49
|
+
# @param values [Array] The forbidden values that caused the error
|
50
|
+
# @param options [Hash] Validation options containing custom messages
|
59
51
|
#
|
60
|
-
# @
|
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
|
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 ||
|
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
|
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
|
-
# @
|
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
|
-
# @
|
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 ||
|
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
|
-
#
|
5
|
+
# Validates that a value matches a specified format pattern
|
6
6
|
#
|
7
|
-
# This validator ensures that
|
8
|
-
# regular
|
9
|
-
#
|
10
|
-
|
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
|
-
|
14
|
-
|
15
|
-
#
|
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
|
-
# @
|
31
|
-
#
|
32
|
-
#
|
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
|
-
# @
|
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
|
-
# @
|
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
|
43
|
-
#
|
44
|
-
# #
|
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
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
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
|
58
|
+
return if match
|
58
59
|
|
59
|
-
|
60
|
-
|
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
|