cmdx 1.0.1 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.cursor/prompts/rspec.md +20 -0
- data/.cursor/prompts/yardoc.md +8 -0
- data/.rubocop.yml +2 -0
- data/CHANGELOG.md +17 -2
- data/README.md +1 -1
- data/docs/basics/call.md +2 -2
- data/docs/basics/chain.md +1 -1
- data/docs/callbacks.md +3 -36
- data/docs/configuration.md +58 -12
- data/docs/interruptions/exceptions.md +1 -1
- data/docs/interruptions/faults.md +2 -2
- data/docs/logging.md +4 -4
- data/docs/middlewares.md +43 -43
- data/docs/parameters/coercions.md +49 -38
- data/docs/parameters/defaults.md +1 -1
- data/docs/parameters/validations.md +0 -39
- data/docs/testing.md +11 -12
- data/docs/workflows.md +4 -4
- data/lib/cmdx/.DS_Store +0 -0
- data/lib/cmdx/callback.rb +36 -56
- data/lib/cmdx/callback_registry.rb +82 -73
- data/lib/cmdx/chain.rb +65 -122
- data/lib/cmdx/chain_inspector.rb +22 -115
- data/lib/cmdx/chain_serializer.rb +17 -148
- data/lib/cmdx/coercion.rb +49 -0
- data/lib/cmdx/coercion_registry.rb +94 -0
- data/lib/cmdx/coercions/array.rb +18 -36
- data/lib/cmdx/coercions/big_decimal.rb +21 -33
- data/lib/cmdx/coercions/boolean.rb +21 -40
- data/lib/cmdx/coercions/complex.rb +18 -31
- data/lib/cmdx/coercions/date.rb +20 -39
- data/lib/cmdx/coercions/date_time.rb +22 -39
- data/lib/cmdx/coercions/float.rb +19 -32
- data/lib/cmdx/coercions/hash.rb +22 -41
- data/lib/cmdx/coercions/integer.rb +20 -33
- data/lib/cmdx/coercions/rational.rb +20 -32
- data/lib/cmdx/coercions/string.rb +23 -31
- data/lib/cmdx/coercions/time.rb +24 -40
- data/lib/cmdx/coercions/virtual.rb +14 -31
- data/lib/cmdx/configuration.rb +57 -171
- data/lib/cmdx/context.rb +22 -165
- data/lib/cmdx/core_ext/hash.rb +42 -67
- data/lib/cmdx/core_ext/module.rb +35 -79
- data/lib/cmdx/core_ext/object.rb +63 -98
- data/lib/cmdx/correlator.rb +40 -156
- data/lib/cmdx/error.rb +37 -202
- data/lib/cmdx/errors.rb +165 -202
- data/lib/cmdx/fault.rb +55 -158
- data/lib/cmdx/faults.rb +26 -137
- data/lib/cmdx/immutator.rb +22 -109
- data/lib/cmdx/lazy_struct.rb +103 -187
- data/lib/cmdx/log_formatters/json.rb +14 -40
- data/lib/cmdx/log_formatters/key_value.rb +14 -40
- data/lib/cmdx/log_formatters/line.rb +14 -48
- data/lib/cmdx/log_formatters/logstash.rb +14 -57
- data/lib/cmdx/log_formatters/pretty_json.rb +14 -50
- data/lib/cmdx/log_formatters/pretty_key_value.rb +13 -46
- data/lib/cmdx/log_formatters/pretty_line.rb +16 -54
- data/lib/cmdx/log_formatters/raw.rb +19 -49
- data/lib/cmdx/logger.rb +20 -82
- data/lib/cmdx/logger_ansi.rb +18 -75
- data/lib/cmdx/logger_serializer.rb +24 -114
- data/lib/cmdx/middleware.rb +38 -60
- data/lib/cmdx/middleware_registry.rb +81 -77
- data/lib/cmdx/middlewares/correlate.rb +41 -226
- data/lib/cmdx/middlewares/timeout.rb +46 -185
- data/lib/cmdx/parameter.rb +120 -198
- data/lib/cmdx/parameter_evaluator.rb +231 -0
- data/lib/cmdx/parameter_inspector.rb +25 -56
- data/lib/cmdx/parameter_registry.rb +59 -84
- data/lib/cmdx/parameter_serializer.rb +23 -74
- data/lib/cmdx/railtie.rb +24 -107
- data/lib/cmdx/result.rb +254 -260
- data/lib/cmdx/result_ansi.rb +19 -85
- data/lib/cmdx/result_inspector.rb +27 -68
- data/lib/cmdx/result_logger.rb +18 -81
- data/lib/cmdx/result_serializer.rb +28 -132
- data/lib/cmdx/rspec/matchers.rb +28 -0
- data/lib/cmdx/rspec/result_matchers/be_executed.rb +42 -0
- data/lib/cmdx/rspec/result_matchers/be_failed_task.rb +94 -0
- data/lib/cmdx/rspec/result_matchers/be_skipped_task.rb +94 -0
- data/lib/cmdx/rspec/result_matchers/be_state_matchers.rb +59 -0
- data/lib/cmdx/rspec/result_matchers/be_status_matchers.rb +57 -0
- data/lib/cmdx/rspec/result_matchers/be_successful_task.rb +87 -0
- data/lib/cmdx/rspec/result_matchers/have_bad_outcome.rb +51 -0
- data/lib/cmdx/rspec/result_matchers/have_caused_failure.rb +58 -0
- data/lib/cmdx/rspec/result_matchers/have_chain_index.rb +59 -0
- data/lib/cmdx/rspec/result_matchers/have_context.rb +86 -0
- data/lib/cmdx/rspec/result_matchers/have_empty_metadata.rb +54 -0
- data/lib/cmdx/rspec/result_matchers/have_good_outcome.rb +52 -0
- data/lib/cmdx/rspec/result_matchers/have_metadata.rb +114 -0
- data/lib/cmdx/rspec/result_matchers/have_preserved_context.rb +66 -0
- data/lib/cmdx/rspec/result_matchers/have_received_thrown_failure.rb +64 -0
- data/lib/cmdx/rspec/result_matchers/have_runtime.rb +78 -0
- data/lib/cmdx/rspec/result_matchers/have_thrown_failure.rb +76 -0
- data/lib/cmdx/rspec/task_matchers/be_well_formed_task.rb +62 -0
- data/lib/cmdx/rspec/task_matchers/have_callback.rb +85 -0
- data/lib/cmdx/rspec/task_matchers/have_cmd_setting.rb +68 -0
- data/lib/cmdx/rspec/task_matchers/have_executed_callbacks.rb +92 -0
- data/lib/cmdx/rspec/task_matchers/have_middleware.rb +46 -0
- data/lib/cmdx/rspec/task_matchers/have_parameter.rb +181 -0
- data/lib/cmdx/task.rb +213 -425
- data/lib/cmdx/task_deprecator.rb +55 -0
- data/lib/cmdx/task_processor.rb +245 -0
- data/lib/cmdx/task_serializer.rb +22 -70
- data/lib/cmdx/utils/ansi_color.rb +13 -89
- data/lib/cmdx/utils/log_timestamp.rb +13 -42
- data/lib/cmdx/utils/monotonic_runtime.rb +13 -63
- data/lib/cmdx/utils/name_affix.rb +21 -71
- data/lib/cmdx/validator.rb +48 -0
- data/lib/cmdx/validator_registry.rb +86 -0
- data/lib/cmdx/validators/exclusion.rb +55 -94
- data/lib/cmdx/validators/format.rb +31 -85
- data/lib/cmdx/validators/inclusion.rb +65 -110
- data/lib/cmdx/validators/length.rb +117 -133
- data/lib/cmdx/validators/numeric.rb +123 -130
- data/lib/cmdx/validators/presence.rb +38 -79
- data/lib/cmdx/version.rb +1 -7
- data/lib/cmdx/workflow.rb +46 -339
- data/lib/cmdx.rb +1 -1
- data/lib/generators/cmdx/install_generator.rb +14 -31
- data/lib/generators/cmdx/task_generator.rb +39 -55
- data/lib/generators/cmdx/templates/install.rb +24 -6
- data/lib/generators/cmdx/workflow_generator.rb +41 -66
- data/lib/locales/ar.yml +0 -1
- data/lib/locales/cs.yml +0 -1
- data/lib/locales/da.yml +0 -1
- data/lib/locales/de.yml +0 -1
- data/lib/locales/el.yml +0 -1
- data/lib/locales/en.yml +0 -1
- data/lib/locales/es.yml +0 -1
- data/lib/locales/fi.yml +0 -1
- data/lib/locales/fr.yml +0 -1
- data/lib/locales/he.yml +0 -1
- data/lib/locales/hi.yml +0 -1
- data/lib/locales/it.yml +0 -1
- data/lib/locales/ja.yml +0 -1
- data/lib/locales/ko.yml +0 -1
- data/lib/locales/nl.yml +0 -1
- data/lib/locales/no.yml +0 -1
- data/lib/locales/pl.yml +0 -1
- data/lib/locales/pt.yml +0 -1
- data/lib/locales/ru.yml +0 -1
- data/lib/locales/sv.yml +0 -1
- data/lib/locales/th.yml +0 -1
- data/lib/locales/tr.yml +0 -1
- data/lib/locales/vi.yml +0 -1
- data/lib/locales/zh.yml +0 -1
- metadata +34 -8
- data/lib/cmdx/parameter_validator.rb +0 -81
- data/lib/cmdx/parameter_value.rb +0 -244
- data/lib/cmdx/parameters_inspector.rb +0 -72
- data/lib/cmdx/parameters_serializer.rb +0 -115
- data/lib/cmdx/rspec/result_matchers.rb +0 -917
- data/lib/cmdx/rspec/task_matchers.rb +0 -570
- data/lib/cmdx/validators/custom.rb +0 -102
@@ -2,79 +2,29 @@
|
|
2
2
|
|
3
3
|
module CMDx
|
4
4
|
module Utils
|
5
|
-
# Utility for measuring execution time using monotonic clock.
|
5
|
+
# Utility module for measuring execution time using monotonic clock.
|
6
6
|
#
|
7
|
-
#
|
8
|
-
#
|
9
|
-
#
|
10
|
-
# CLOCK_MONOTONIC for reliable performance measurements.
|
11
|
-
#
|
12
|
-
# @example Basic runtime measurement
|
13
|
-
# runtime = Utils::MonotonicRuntime.call do
|
14
|
-
# sleep(1.5)
|
15
|
-
# # ... task execution code ...
|
16
|
-
# end
|
17
|
-
# # => 1500 (milliseconds)
|
18
|
-
#
|
19
|
-
# @example Task execution timing
|
20
|
-
# class ProcessOrderTask < CMDx::Task
|
21
|
-
# def call
|
22
|
-
# runtime = Utils::MonotonicRuntime.call do
|
23
|
-
# # Complex business logic
|
24
|
-
# process_payment
|
25
|
-
# update_inventory
|
26
|
-
# send_confirmation
|
27
|
-
# end
|
28
|
-
# logger.info "Order processed in #{runtime}ms"
|
29
|
-
# end
|
30
|
-
# end
|
31
|
-
#
|
32
|
-
# @example Performance benchmarking
|
33
|
-
# fast_time = Utils::MonotonicRuntime.call { fast_algorithm }
|
34
|
-
# slow_time = Utils::MonotonicRuntime.call { slow_algorithm }
|
35
|
-
# puts "Fast algorithm is #{slow_time / fast_time}x faster"
|
36
|
-
#
|
37
|
-
# @see CMDx::Task Uses this internally to measure task execution time
|
38
|
-
# @see CMDx::Result#runtime Contains the measured execution time
|
7
|
+
# This module provides functionality to measure the time taken to execute
|
8
|
+
# a block of code using the monotonic clock, which is not affected by
|
9
|
+
# system clock adjustments and provides more accurate timing measurements.
|
39
10
|
module MonotonicRuntime
|
40
11
|
|
41
12
|
module_function
|
42
13
|
|
43
14
|
# Measures the execution time of a given block using monotonic clock.
|
44
15
|
#
|
45
|
-
#
|
46
|
-
#
|
47
|
-
# timing that is immune to system clock changes.
|
48
|
-
#
|
49
|
-
# @yield Block of code to measure execution time for
|
50
|
-
# @return [Integer] Execution time in milliseconds
|
51
|
-
#
|
52
|
-
# @example Simple timing measurement
|
53
|
-
# time_taken = MonotonicRuntime.call do
|
54
|
-
# expensive_operation
|
55
|
-
# end
|
56
|
-
# puts "Operation took #{time_taken}ms"
|
16
|
+
# @param block [Proc] the block of code to measure execution time for
|
17
|
+
# @yield executes the provided block while measuring its runtime
|
57
18
|
#
|
58
|
-
# @
|
59
|
-
# query_time = MonotonicRuntime.call do
|
60
|
-
# User.joins(:orders).where(active: true).count
|
61
|
-
# end
|
62
|
-
# logger.debug "Query executed in #{query_time}ms"
|
19
|
+
# @return [Integer] the execution time in milliseconds
|
63
20
|
#
|
64
|
-
# @example
|
65
|
-
#
|
66
|
-
#
|
67
|
-
# external_api.fetch_data
|
68
|
-
# rescue => e
|
69
|
-
# logger.error "API call failed: #{e.message}"
|
70
|
-
# raise
|
71
|
-
# end
|
72
|
-
# end
|
73
|
-
# # Time is measured even if an exception occurs
|
21
|
+
# @example Basic usage
|
22
|
+
# runtime = MonotonicRuntime.call { sleep(0.1) }
|
23
|
+
# # => 100 (approximately)
|
74
24
|
#
|
75
|
-
# @
|
76
|
-
#
|
77
|
-
#
|
25
|
+
# @example Measuring database query time
|
26
|
+
# query_time = MonotonicRuntime.call { User.find(1) }
|
27
|
+
# # => 15 (milliseconds)
|
78
28
|
def call(&)
|
79
29
|
now = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond)
|
80
30
|
yield
|
@@ -2,92 +2,42 @@
|
|
2
2
|
|
3
3
|
module CMDx
|
4
4
|
module Utils
|
5
|
-
# Utility for generating method names with prefixes and suffixes.
|
5
|
+
# Utility module for generating method names with configurable prefixes and suffixes.
|
6
6
|
#
|
7
|
-
#
|
8
|
-
#
|
9
|
-
#
|
10
|
-
# conventions in CMDx's parameter and delegation systems.
|
11
|
-
#
|
12
|
-
# @example Basic prefix and suffix usage
|
13
|
-
# Utils::NameAffix.call(:name, "user", prefix: true, suffix: true)
|
14
|
-
# # => :user_name_user
|
15
|
-
#
|
16
|
-
# @example Custom prefix
|
17
|
-
# Utils::NameAffix.call(:email, "admin", prefix: "get_")
|
18
|
-
# # => :get_email
|
19
|
-
#
|
20
|
-
# @example Custom suffix
|
21
|
-
# Utils::NameAffix.call(:count, "items", suffix: "_total")
|
22
|
-
# # => :count_total
|
23
|
-
#
|
24
|
-
# @example Complete name override
|
25
|
-
# Utils::NameAffix.call(:original, "source", as: :custom_method)
|
26
|
-
# # => :custom_method
|
27
|
-
#
|
28
|
-
# @example Parameter delegation usage
|
29
|
-
# class MyTask < CMDx::Task
|
30
|
-
# required :user_id
|
31
|
-
#
|
32
|
-
# # Internally uses NameAffix for method generation
|
33
|
-
# # Creates methods like user_id, user_id?, etc.
|
34
|
-
# end
|
35
|
-
#
|
36
|
-
# @see CMDx::Parameter Uses this for parameter method name generation
|
37
|
-
# @see CMDx::CoreExt::Module Uses this for delegation method naming
|
7
|
+
# This module provides functionality to dynamically construct method names
|
8
|
+
# by applying prefixes and suffixes to a base method name, with support
|
9
|
+
# for custom naming through options.
|
38
10
|
module NameAffix
|
39
11
|
|
40
|
-
# Proc
|
41
|
-
# @return [Proc] processor for affix options that handles true/false and custom strings
|
12
|
+
# Proc that handles affix logic - returns block result if value is true, otherwise returns value as-is.
|
42
13
|
AFFIX = proc do |o, &block|
|
43
14
|
o == true ? block.call : o
|
44
15
|
end.freeze
|
45
16
|
|
46
17
|
module_function
|
47
18
|
|
48
|
-
# Generates a method name with optional prefix and suffix.
|
49
|
-
#
|
50
|
-
# Creates a method name by combining the base method name with optional
|
51
|
-
# prefixes and suffixes. Supports boolean flags for default affixes or
|
52
|
-
# custom string values for specific naming patterns.
|
53
|
-
#
|
54
|
-
# @param method_name [Symbol, String] Base method name to transform
|
55
|
-
# @param source [String] Source identifier used for default prefix/suffix generation
|
56
|
-
# @param options [Hash] Configuration options for name generation
|
57
|
-
# @option options [Boolean, String] :prefix (false) Add prefix - true for "#{source}_", string for custom
|
58
|
-
# @option options [Boolean, String] :suffix (false) Add suffix - true for "_#{source}", string for custom
|
59
|
-
# @option options [Symbol] :as Override the entire generated name
|
60
|
-
#
|
61
|
-
# @return [Symbol] Generated method name with applied affixes
|
62
|
-
#
|
63
|
-
# @example Default prefix generation
|
64
|
-
# NameAffix.call(:method, "user", prefix: true)
|
65
|
-
# # => :user_method
|
19
|
+
# Generates a method name with optional prefix and suffix based on source and options.
|
66
20
|
#
|
67
|
-
# @
|
68
|
-
#
|
69
|
-
#
|
21
|
+
# @param method_name [String, Symbol] the base method name to be affixed
|
22
|
+
# @param source [String, Symbol] the source identifier used for generating default prefixes/suffixes
|
23
|
+
# @param options [Hash] configuration options for name generation
|
24
|
+
# @option options [String, Symbol, true] :prefix custom prefix or true for default "#{source}_"
|
25
|
+
# @option options [String, Symbol, true] :suffix custom suffix or true for default "_#{source}"
|
26
|
+
# @option options [String, Symbol] :as override the entire generated name
|
70
27
|
#
|
71
|
-
# @
|
72
|
-
# NameAffix.call(:method, "user", suffix: true)
|
73
|
-
# # => :method_user
|
28
|
+
# @return [Symbol] the generated method name as a symbol
|
74
29
|
#
|
75
|
-
# @example
|
76
|
-
# NameAffix.call(
|
77
|
-
# # => :method_count
|
30
|
+
# @example Using default prefix and suffix
|
31
|
+
# NameAffix.call("process", "user", prefix: true, suffix: true) #=> :user_process_user
|
78
32
|
#
|
79
|
-
# @example
|
80
|
-
# NameAffix.call(
|
81
|
-
# # => :get_name_value
|
33
|
+
# @example Using custom prefix
|
34
|
+
# NameAffix.call("process", "user", prefix: "handle_") #=> :handle_process
|
82
35
|
#
|
83
|
-
# @example
|
84
|
-
# NameAffix.call(
|
85
|
-
# # => :custom
|
36
|
+
# @example Using custom suffix
|
37
|
+
# NameAffix.call("process", "user", suffix: "_data") #=> :process_data
|
86
38
|
#
|
87
|
-
# @example
|
88
|
-
#
|
89
|
-
# NameAffix.call(:email, "user", suffix: "?") # => :email?
|
90
|
-
# NameAffix.call(:process, "order", prefix: "can_") # => :can_process
|
39
|
+
# @example Overriding with custom name
|
40
|
+
# NameAffix.call("process", "user", as: "custom_method") #=> :custom_method
|
91
41
|
def call(method_name, source, options = {})
|
92
42
|
options[:as] || begin
|
93
43
|
prefix = AFFIX.call(options[:prefix]) { "#{source}_" }
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CMDx
|
4
|
+
# Base class for implementing validation functionality in parameter processing.
|
5
|
+
#
|
6
|
+
# Validators are used to validate parameter values during task execution to
|
7
|
+
# ensure data integrity and business rule compliance. All validator implementations
|
8
|
+
# must inherit from this class and implement the abstract call method.
|
9
|
+
class Validator
|
10
|
+
|
11
|
+
# Executes a validator by creating a new instance and calling it.
|
12
|
+
#
|
13
|
+
# @param value [Object] the value to be validated
|
14
|
+
# @param options [Hash] optional validation configuration
|
15
|
+
#
|
16
|
+
# @return [Object] the result of the validation execution
|
17
|
+
#
|
18
|
+
# @raise [UndefinedCallError] when the validator subclass doesn't implement call
|
19
|
+
#
|
20
|
+
# @example Execute a validator on a value
|
21
|
+
# MyValidator.call("example", { min_length: 5 })
|
22
|
+
def self.call(value, options = {})
|
23
|
+
new.call(value, options)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Abstract method that must be implemented by validator subclasses.
|
27
|
+
#
|
28
|
+
# This method contains the actual validation logic to be executed.
|
29
|
+
# Subclasses must override this method to provide their specific
|
30
|
+
# validation implementation.
|
31
|
+
#
|
32
|
+
# @param _value [Object] the value to be validated
|
33
|
+
# @param _options [Hash] optional validation configuration
|
34
|
+
#
|
35
|
+
# @return [Object] the result of the validation execution
|
36
|
+
#
|
37
|
+
# @raise [UndefinedCallError] always raised in the base class
|
38
|
+
#
|
39
|
+
# @example Implement in a subclass
|
40
|
+
# def call(value, options)
|
41
|
+
# raise ValidationError, "Value too short" if value.length < options[:min_length]
|
42
|
+
# end
|
43
|
+
def call(_value, _options = {})
|
44
|
+
raise UndefinedCallError, "call method not defined in #{self.class.name}"
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CMDx
|
4
|
+
# Registry for managing validator definitions and execution within tasks.
|
5
|
+
#
|
6
|
+
# This registry handles the registration and execution of validators for
|
7
|
+
# parameter validation, including built-in validators and custom validators
|
8
|
+
# that can be registered at runtime.
|
9
|
+
class ValidatorRegistry
|
10
|
+
|
11
|
+
# The internal hash storing validator definitions.
|
12
|
+
#
|
13
|
+
# @return [Hash] hash containing validator type keys and validator class values
|
14
|
+
attr_reader :registry
|
15
|
+
|
16
|
+
# Initializes a new validator registry with built-in validators.
|
17
|
+
#
|
18
|
+
# @return [ValidatorRegistry] a new validator registry instance
|
19
|
+
#
|
20
|
+
# @example Creating a validator registry
|
21
|
+
# ValidatorRegistry.new
|
22
|
+
def initialize
|
23
|
+
@registry = {
|
24
|
+
exclusion: Validators::Exclusion,
|
25
|
+
format: Validators::Format,
|
26
|
+
inclusion: Validators::Inclusion,
|
27
|
+
length: Validators::Length,
|
28
|
+
numeric: Validators::Numeric,
|
29
|
+
presence: Validators::Presence
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
# Registers a custom validator for a specific type.
|
34
|
+
#
|
35
|
+
# @param type [Symbol] the validator type to register
|
36
|
+
# @param validator [Class, Module, Symbol, Proc] the validator to register
|
37
|
+
#
|
38
|
+
# @return [ValidatorRegistry] returns self for method chaining
|
39
|
+
#
|
40
|
+
# @example Registering a custom validator class
|
41
|
+
# registry.register(:email, EmailValidator)
|
42
|
+
#
|
43
|
+
# @example Registering a Proc validator
|
44
|
+
# registry.register(:custom, ->(value, opts) { value.length > 3 })
|
45
|
+
#
|
46
|
+
# @example Registering a Symbol validator
|
47
|
+
# registry.register(:password, :validate_password_strength)
|
48
|
+
#
|
49
|
+
# @example Chaining validator registrations
|
50
|
+
# registry.register(:phone, PhoneValidator)
|
51
|
+
# .register(:zipcode, ZipcodeValidator)
|
52
|
+
def register(type, validator)
|
53
|
+
registry[type] = validator
|
54
|
+
self
|
55
|
+
end
|
56
|
+
|
57
|
+
# Executes validation for a specific type on a given value.
|
58
|
+
#
|
59
|
+
# @param task [Task] the task instance to execute validation on
|
60
|
+
# @param type [Symbol] the validator type to execute
|
61
|
+
# @param value [Object] the value to validate
|
62
|
+
# @param options [Hash] options for conditional validation execution
|
63
|
+
#
|
64
|
+
# @return [Object, nil] returns the validation result or nil if skipped
|
65
|
+
#
|
66
|
+
# @raise [UnknownValidatorError] when the validator type is not registered
|
67
|
+
#
|
68
|
+
# @example Validating with a built-in validator
|
69
|
+
# registry.call(task, :presence, "", {})
|
70
|
+
#
|
71
|
+
# @example Validating with options
|
72
|
+
# registry.call(task, :length, "test", { minimum: 5 })
|
73
|
+
def call(task, type, value, options = {})
|
74
|
+
raise UnknownValidatorError, "unknown validator #{type}" unless registry.key?(type)
|
75
|
+
return unless task.cmdx_eval(options)
|
76
|
+
|
77
|
+
case validator = registry[type]
|
78
|
+
when Symbol, String, Proc
|
79
|
+
task.cmdx_try(validator, value, options)
|
80
|
+
else
|
81
|
+
validator.call(value, options)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
end
|
@@ -2,96 +2,46 @@
|
|
2
2
|
|
3
3
|
module CMDx
|
4
4
|
module Validators
|
5
|
-
#
|
5
|
+
# Validator class for excluding values from a specified set.
|
6
6
|
#
|
7
|
-
#
|
8
|
-
#
|
9
|
-
#
|
10
|
-
|
11
|
-
# @example Basic exclusion validation with array
|
12
|
-
# class ProcessOrderTask < CMDx::Task
|
13
|
-
# required :status, exclusion: { in: ['cancelled', 'refunded'] }
|
14
|
-
# required :priority, exclusion: { in: [0, -1] }
|
15
|
-
# end
|
16
|
-
#
|
17
|
-
# @example Range-based exclusion
|
18
|
-
# class ProcessUserTask < CMDx::Task
|
19
|
-
# required :age, exclusion: { in: 0..17 } # Must be 18 or older
|
20
|
-
# required :score, exclusion: { within: 90..100 } # Cannot be in top 10%
|
21
|
-
# end
|
22
|
-
#
|
23
|
-
# @example Custom error messages
|
24
|
-
# class ProcessOrderTask < CMDx::Task
|
25
|
-
# required :status, exclusion: {
|
26
|
-
# in: ['cancelled', 'refunded'],
|
27
|
-
# of_message: "cannot be cancelled or refunded"
|
28
|
-
# }
|
29
|
-
# required :age, exclusion: {
|
30
|
-
# in: 0..17,
|
31
|
-
# in_message: "must be %{min} or older"
|
32
|
-
# }
|
33
|
-
# end
|
34
|
-
#
|
35
|
-
# @example Exclusion validation behavior
|
36
|
-
# # Array exclusion
|
37
|
-
# Exclusion.call("active", exclusion: { in: ['cancelled'] }) # passes
|
38
|
-
# Exclusion.call("cancelled", exclusion: { in: ['cancelled'] }) # raises ValidationError
|
39
|
-
#
|
40
|
-
# # Range exclusion
|
41
|
-
# Exclusion.call(25, exclusion: { in: 0..17 }) # passes
|
42
|
-
# Exclusion.call(15, exclusion: { in: 0..17 }) # raises ValidationError
|
43
|
-
#
|
44
|
-
# @see CMDx::Validators::Inclusion For validating values must be in a set
|
45
|
-
# @see CMDx::Parameter Parameter validation integration
|
46
|
-
# @see CMDx::ValidationError Raised when validation fails
|
47
|
-
module Exclusion
|
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
|
48
11
|
|
49
|
-
|
50
|
-
|
51
|
-
# Validates that a parameter value is not in the excluded set.
|
52
|
-
#
|
53
|
-
# Checks that the value is not present in the specified array or range
|
54
|
-
# of forbidden values. Raises ValidationError if the value is found
|
55
|
-
# in the exclusion set.
|
12
|
+
# Validates that the given value is not included in the exclusion set.
|
56
13
|
#
|
57
|
-
# @param value [Object]
|
58
|
-
# @param options [Hash]
|
59
|
-
# @option options [Hash] :exclusion
|
60
|
-
# @option options [Array, Range] :exclusion.in
|
61
|
-
# @option options [Array, Range] :exclusion.within
|
62
|
-
# @option options [String] :exclusion.
|
63
|
-
# @option options [String] :exclusion.
|
64
|
-
# @option options [String] :exclusion.
|
65
|
-
# @option options [String] :exclusion.
|
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
|
66
23
|
#
|
67
24
|
# @return [void]
|
68
|
-
# @raise [ValidationError] If value is found in the exclusion set
|
69
25
|
#
|
70
|
-
# @
|
71
|
-
# Exclusion.call("pending", exclusion: { in: ['cancelled', 'failed'] })
|
72
|
-
# # => passes without error
|
26
|
+
# @raise [ValidationError] if the value is found in the exclusion set
|
73
27
|
#
|
74
|
-
# @example
|
75
|
-
# Exclusion.call("
|
76
|
-
# #
|
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\""
|
77
31
|
#
|
78
|
-
# @example
|
79
|
-
# Exclusion.call(
|
80
|
-
# #
|
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"
|
81
35
|
#
|
82
|
-
# @example
|
83
|
-
# Exclusion.call(
|
84
|
-
# # =>
|
36
|
+
# @example Valid exclusion
|
37
|
+
# Validators::Exclusion.call("user", exclusion: { in: ["admin", "root"] })
|
38
|
+
# # => nil (no error raised)
|
85
39
|
#
|
86
|
-
# @example
|
87
|
-
# Exclusion.call("admin", exclusion: {
|
88
|
-
#
|
89
|
-
# of_message: "role is restricted"
|
90
|
-
# })
|
91
|
-
# # => raises ValidationError: "role is restricted"
|
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"
|
92
43
|
def call(value, options = {})
|
93
|
-
values = options
|
94
|
-
options.dig(:exclusion, :within)
|
44
|
+
values = options[:in] || options[:within]
|
95
45
|
|
96
46
|
if values.is_a?(Range)
|
97
47
|
raise_within_validation_error!(values.begin, values.end, options) if values.cover?(value)
|
@@ -102,15 +52,21 @@ module CMDx
|
|
102
52
|
|
103
53
|
private
|
104
54
|
|
105
|
-
# Raises validation error for array-based exclusion
|
55
|
+
# Raises a validation error for array-based exclusion.
|
106
56
|
#
|
107
|
-
# @param values [Array]
|
108
|
-
# @param options [Hash]
|
109
|
-
#
|
57
|
+
# @param values [Array] the excluded values
|
58
|
+
# @param options [Hash] validation options
|
59
|
+
#
|
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\""
|
110
67
|
def raise_of_validation_error!(values, options)
|
111
|
-
values = values.map(&:inspect).join(", ")
|
112
|
-
message = options
|
113
|
-
options.dig(:exclusion, :message)
|
68
|
+
values = values.map(&:inspect).join(", ") unless values.nil?
|
69
|
+
message = options[:of_message] || options[:message]
|
114
70
|
message %= { values: } unless message.nil?
|
115
71
|
|
116
72
|
raise ValidationError, message || I18n.t(
|
@@ -120,16 +76,21 @@ module CMDx
|
|
120
76
|
)
|
121
77
|
end
|
122
78
|
|
123
|
-
# Raises validation error for range-based exclusion
|
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]
|
86
|
+
#
|
87
|
+
# @raise [ValidationError] always raised with appropriate message
|
124
88
|
#
|
125
|
-
# @
|
126
|
-
#
|
127
|
-
#
|
128
|
-
# @raise [ValidationError] With formatted error message
|
89
|
+
# @example
|
90
|
+
# raise_within_validation_error!(1, 10, {})
|
91
|
+
# # raises ValidationError: "must not be within 1 and 10"
|
129
92
|
def raise_within_validation_error!(min, max, options)
|
130
|
-
message = options
|
131
|
-
options.dig(:exclusion, :within_message) ||
|
132
|
-
options.dig(:exclusion, :message)
|
93
|
+
message = options[:in_message] || options[:within_message] || options[:message]
|
133
94
|
message %= { min:, max: } unless message.nil?
|
134
95
|
|
135
96
|
raise ValidationError, message || I18n.t(
|