cmdx 0.5.0 → 1.0.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/rules/cursor-instructions.mdc +6 -0
- data/.rubocop.yml +19 -1
- data/.ruby-version +1 -1
- data/CHANGELOG.md +95 -28
- data/README.md +73 -25
- data/docs/ai_prompts.md +319 -0
- data/docs/basics/call.md +234 -14
- data/docs/basics/chain.md +280 -0
- data/docs/basics/context.md +241 -33
- data/docs/basics/setup.md +85 -12
- data/docs/callbacks.md +283 -0
- data/docs/configuration.md +155 -30
- data/docs/getting_started.md +145 -22
- data/docs/internationalization.md +148 -0
- data/docs/interruptions/exceptions.md +198 -11
- data/docs/interruptions/faults.md +196 -44
- data/docs/interruptions/halt.md +188 -35
- data/docs/logging.md +204 -53
- data/docs/middlewares.md +745 -0
- data/docs/outcomes/result.md +305 -10
- data/docs/outcomes/states.md +212 -31
- data/docs/outcomes/statuses.md +284 -30
- data/docs/parameters/coercions.md +411 -29
- data/docs/parameters/defaults.md +258 -25
- data/docs/parameters/definitions.md +247 -72
- data/docs/parameters/namespacing.md +259 -27
- data/docs/parameters/validations.md +173 -168
- data/docs/testing.md +560 -0
- data/docs/tips_and_tricks.md +103 -42
- data/docs/workflows.md +329 -0
- data/lib/cmdx/.DS_Store +0 -0
- data/lib/cmdx/callback.rb +69 -0
- data/lib/cmdx/callback_registry.rb +106 -0
- data/lib/cmdx/chain.rb +190 -0
- data/lib/cmdx/chain_inspector.rb +149 -0
- data/lib/cmdx/chain_serializer.rb +175 -0
- data/lib/cmdx/coercions/array.rb +37 -0
- data/lib/cmdx/coercions/big_decimal.rb +33 -0
- data/lib/cmdx/coercions/boolean.rb +41 -1
- data/lib/cmdx/coercions/complex.rb +31 -0
- data/lib/cmdx/coercions/date.rb +39 -0
- data/lib/cmdx/coercions/date_time.rb +39 -0
- data/lib/cmdx/coercions/float.rb +31 -0
- data/lib/cmdx/coercions/hash.rb +42 -0
- data/lib/cmdx/coercions/integer.rb +32 -0
- data/lib/cmdx/coercions/rational.rb +31 -0
- data/lib/cmdx/coercions/string.rb +31 -0
- data/lib/cmdx/coercions/time.rb +39 -0
- data/lib/cmdx/coercions/virtual.rb +31 -0
- data/lib/cmdx/configuration.rb +217 -9
- data/lib/cmdx/context.rb +173 -2
- data/lib/cmdx/core_ext/hash.rb +72 -0
- data/lib/cmdx/core_ext/module.rb +94 -0
- data/lib/cmdx/core_ext/object.rb +105 -0
- data/lib/cmdx/correlator.rb +217 -0
- data/lib/cmdx/error.rb +210 -8
- data/lib/cmdx/errors.rb +256 -1
- data/lib/cmdx/fault.rb +177 -2
- data/lib/cmdx/faults.rb +158 -2
- data/lib/cmdx/immutator.rb +121 -2
- data/lib/cmdx/lazy_struct.rb +261 -18
- data/lib/cmdx/log_formatters/json.rb +46 -0
- data/lib/cmdx/log_formatters/key_value.rb +46 -0
- data/lib/cmdx/log_formatters/line.rb +54 -0
- data/lib/cmdx/log_formatters/logstash.rb +64 -0
- data/lib/cmdx/log_formatters/pretty_json.rb +57 -0
- data/lib/cmdx/log_formatters/pretty_key_value.rb +51 -0
- data/lib/cmdx/log_formatters/pretty_line.rb +60 -0
- data/lib/cmdx/log_formatters/raw.rb +54 -0
- data/lib/cmdx/logger.rb +85 -0
- data/lib/cmdx/logger_ansi.rb +93 -7
- data/lib/cmdx/logger_serializer.rb +116 -0
- data/lib/cmdx/middleware.rb +74 -0
- data/lib/cmdx/middleware_registry.rb +106 -0
- data/lib/cmdx/middlewares/correlate.rb +266 -0
- data/lib/cmdx/middlewares/timeout.rb +232 -0
- data/lib/cmdx/parameter.rb +228 -1
- data/lib/cmdx/parameter_inspector.rb +61 -0
- data/lib/cmdx/parameter_registry.rb +125 -0
- data/lib/cmdx/parameter_serializer.rb +83 -0
- data/lib/cmdx/parameter_validator.rb +62 -0
- data/lib/cmdx/parameter_value.rb +109 -1
- data/lib/cmdx/parameters_inspector.rb +59 -0
- data/lib/cmdx/parameters_serializer.rb +102 -0
- data/lib/cmdx/railtie.rb +123 -3
- data/lib/cmdx/result.rb +367 -25
- data/lib/cmdx/result_ansi.rb +105 -9
- data/lib/cmdx/result_inspector.rb +76 -0
- data/lib/cmdx/result_logger.rb +90 -3
- data/lib/cmdx/result_serializer.rb +137 -0
- data/lib/cmdx/rspec/result_matchers.rb +917 -0
- data/lib/cmdx/rspec/task_matchers.rb +570 -0
- data/lib/cmdx/task.rb +405 -37
- data/lib/cmdx/task_serializer.rb +74 -2
- data/lib/cmdx/utils/ansi_color.rb +95 -0
- data/lib/cmdx/utils/log_timestamp.rb +48 -0
- data/lib/cmdx/utils/monotonic_runtime.rb +71 -4
- data/lib/cmdx/utils/name_affix.rb +78 -0
- data/lib/cmdx/validators/custom.rb +82 -0
- data/lib/cmdx/validators/exclusion.rb +94 -0
- data/lib/cmdx/validators/format.rb +102 -8
- data/lib/cmdx/validators/inclusion.rb +104 -0
- data/lib/cmdx/validators/length.rb +128 -0
- data/lib/cmdx/validators/numeric.rb +128 -0
- data/lib/cmdx/validators/presence.rb +93 -7
- data/lib/cmdx/version.rb +7 -1
- data/lib/cmdx/workflow.rb +394 -0
- data/lib/cmdx.rb +25 -64
- data/lib/generators/cmdx/install_generator.rb +37 -1
- data/lib/generators/cmdx/task_generator.rb +69 -1
- data/lib/generators/cmdx/templates/install.rb +43 -15
- data/lib/generators/cmdx/workflow_generator.rb +109 -0
- data/lib/locales/ar.yml +36 -0
- data/lib/locales/cs.yml +36 -0
- data/lib/locales/da.yml +36 -0
- data/lib/locales/de.yml +36 -0
- data/lib/locales/el.yml +36 -0
- data/lib/locales/en.yml +20 -20
- data/lib/locales/es.yml +20 -20
- data/lib/locales/fi.yml +36 -0
- data/lib/locales/fr.yml +36 -0
- data/lib/locales/he.yml +36 -0
- data/lib/locales/hi.yml +36 -0
- data/lib/locales/it.yml +36 -0
- data/lib/locales/ja.yml +36 -0
- data/lib/locales/ko.yml +36 -0
- data/lib/locales/nl.yml +36 -0
- data/lib/locales/no.yml +36 -0
- data/lib/locales/pl.yml +36 -0
- data/lib/locales/pt.yml +36 -0
- data/lib/locales/ru.yml +36 -0
- data/lib/locales/sv.yml +36 -0
- data/lib/locales/th.yml +36 -0
- data/lib/locales/tr.yml +36 -0
- data/lib/locales/vi.yml +36 -0
- data/lib/locales/zh.yml +36 -0
- metadata +77 -15
- data/docs/basics/run.md +0 -34
- data/docs/batch.md +0 -53
- data/docs/example.md +0 -82
- data/docs/hooks.md +0 -62
- data/lib/cmdx/batch.rb +0 -43
- data/lib/cmdx/parameters.rb +0 -35
- data/lib/cmdx/run.rb +0 -39
- data/lib/cmdx/run_inspector.rb +0 -26
- data/lib/cmdx/run_serializer.rb +0 -20
- data/lib/cmdx/task_hook.rb +0 -18
- data/lib/generators/cmdx/batch_generator.rb +0 -30
- /data/lib/generators/cmdx/templates/{batch.rb.tt → workflow.rb.tt} +0 -0
@@ -2,8 +2,59 @@
|
|
2
2
|
|
3
3
|
module CMDx
|
4
4
|
module Utils
|
5
|
+
# Utility for adding ANSI color codes to terminal output.
|
6
|
+
#
|
7
|
+
# AnsiColor provides methods to colorize text output in terminal
|
8
|
+
# environments, supporting various colors and text modes for
|
9
|
+
# enhanced readability of logs and console output. Used extensively
|
10
|
+
# by CMDx's pretty formatters to provide visual distinction between
|
11
|
+
# different log levels, statuses, and metadata.
|
12
|
+
#
|
13
|
+
# @example Basic color usage
|
14
|
+
# Utils::AnsiColor.call("Error", color: :red)
|
15
|
+
# # => "\e[0;31;49mError\e[0m"
|
16
|
+
#
|
17
|
+
# @example Color with text modes
|
18
|
+
# Utils::AnsiColor.call("Warning", color: :yellow, mode: :bold)
|
19
|
+
# Utils::AnsiColor.call("Info", color: :blue, mode: :underline)
|
20
|
+
#
|
21
|
+
# @example Log severity coloring
|
22
|
+
# Utils::AnsiColor.call("ERROR", color: :red, mode: :bold)
|
23
|
+
# Utils::AnsiColor.call("WARN", color: :yellow)
|
24
|
+
# Utils::AnsiColor.call("INFO", color: :blue)
|
25
|
+
# Utils::AnsiColor.call("DEBUG", color: :light_black)
|
26
|
+
#
|
27
|
+
# @example Status indicator coloring
|
28
|
+
# Utils::AnsiColor.call("success", color: :green, mode: :bold)
|
29
|
+
# Utils::AnsiColor.call("failed", color: :red, mode: :bold)
|
30
|
+
# Utils::AnsiColor.call("skipped", color: :yellow)
|
31
|
+
#
|
32
|
+
# @example Available colors
|
33
|
+
# Utils::AnsiColor.call("Text", color: :red)
|
34
|
+
# Utils::AnsiColor.call("Text", color: :green)
|
35
|
+
# Utils::AnsiColor.call("Text", color: :blue)
|
36
|
+
# Utils::AnsiColor.call("Text", color: :light_cyan)
|
37
|
+
# Utils::AnsiColor.call("Text", color: :magenta)
|
38
|
+
#
|
39
|
+
# @example Available text modes
|
40
|
+
# Utils::AnsiColor.call("Bold", color: :white, mode: :bold)
|
41
|
+
# Utils::AnsiColor.call("Italic", color: :white, mode: :italic)
|
42
|
+
# Utils::AnsiColor.call("Underline", color: :white, mode: :underline)
|
43
|
+
# Utils::AnsiColor.call("Strikethrough", color: :white, mode: :strike)
|
44
|
+
#
|
45
|
+
# @see CMDx::ResultAnsi Uses this for result status coloring
|
46
|
+
# @see CMDx::LoggerAnsi Uses this for log severity coloring
|
47
|
+
# @see CMDx::LogFormatters::PrettyLine Uses this for colorized log output
|
48
|
+
# @see CMDx::LogFormatters::PrettyKeyValue Uses this for colorized key-value pairs
|
5
49
|
module AnsiColor
|
6
50
|
|
51
|
+
# Available color codes for text coloring.
|
52
|
+
#
|
53
|
+
# Maps color names to their corresponding ANSI escape code numbers.
|
54
|
+
# Includes both standard and light variants of common colors for
|
55
|
+
# flexible visual styling in terminal environments.
|
56
|
+
#
|
57
|
+
# @return [Hash<Symbol, Integer>] mapping of color names to ANSI codes
|
7
58
|
COLOR_CODES = {
|
8
59
|
black: 30,
|
9
60
|
red: 31,
|
@@ -23,6 +74,14 @@ module CMDx
|
|
23
74
|
light_cyan: 96,
|
24
75
|
light_white: 97
|
25
76
|
}.freeze
|
77
|
+
|
78
|
+
# Available text mode codes for formatting.
|
79
|
+
#
|
80
|
+
# Maps text formatting mode names to their corresponding ANSI escape
|
81
|
+
# code numbers. Provides various text styling options including bold,
|
82
|
+
# italic, underline, and other visual effects.
|
83
|
+
#
|
84
|
+
# @return [Hash<Symbol, Integer>] mapping of mode names to ANSI codes
|
26
85
|
MODE_CODES = {
|
27
86
|
default: 0,
|
28
87
|
bold: 1,
|
@@ -42,6 +101,42 @@ module CMDx
|
|
42
101
|
|
43
102
|
module_function
|
44
103
|
|
104
|
+
# Apply ANSI color and mode formatting to text.
|
105
|
+
#
|
106
|
+
# Wraps the provided text with ANSI escape codes to apply the specified
|
107
|
+
# color and formatting mode. The resulting string will display with the
|
108
|
+
# requested styling in ANSI-compatible terminals and will gracefully
|
109
|
+
# degrade in non-ANSI environments.
|
110
|
+
#
|
111
|
+
# @param value [String] text to colorize
|
112
|
+
# @param color [Symbol] color name from COLOR_CODES
|
113
|
+
# @param mode [Symbol] text mode from MODE_CODES (defaults to :default)
|
114
|
+
# @return [String] text wrapped with ANSI escape codes
|
115
|
+
# @raise [KeyError] if color or mode is not found in the respective code maps
|
116
|
+
#
|
117
|
+
# @example Success message with green bold text
|
118
|
+
# AnsiColor.call("Success", color: :green, mode: :bold)
|
119
|
+
# # => "\e[1;32;49mSuccess\e[0m"
|
120
|
+
#
|
121
|
+
# @example Error message with red text
|
122
|
+
# AnsiColor.call("Error", color: :red)
|
123
|
+
# # => "\e[0;31;49mError\e[0m"
|
124
|
+
#
|
125
|
+
# @example Warning with yellow underlined text
|
126
|
+
# AnsiColor.call("Warning", color: :yellow, mode: :underline)
|
127
|
+
# # => "\e[4;33;49mWarning\e[0m"
|
128
|
+
#
|
129
|
+
# @example Debug info with dimmed light text
|
130
|
+
# AnsiColor.call("Debug info", color: :light_black, mode: :dim)
|
131
|
+
# # => "\e[2;90;49mDebug info\e[0m"
|
132
|
+
#
|
133
|
+
# @example Invalid color raises KeyError
|
134
|
+
# AnsiColor.call("Text", color: :invalid_color)
|
135
|
+
# # => KeyError: key not found: :invalid_color
|
136
|
+
#
|
137
|
+
# @note The escape sequence format is: \e[{mode};{color};49m{text}\e[0m
|
138
|
+
# @note The "49" represents the default background color
|
139
|
+
# @note The final "\e[0m" resets all formatting to default
|
45
140
|
def call(value, color:, mode: :default)
|
46
141
|
color_code = COLOR_CODES.fetch(color)
|
47
142
|
mode_code = MODE_CODES.fetch(mode)
|
@@ -2,12 +2,60 @@
|
|
2
2
|
|
3
3
|
module CMDx
|
4
4
|
module Utils
|
5
|
+
# Utility for formatting timestamps in CMDx log entries.
|
6
|
+
#
|
7
|
+
# LogTimestamp provides consistent timestamp formatting across all CMDx
|
8
|
+
# log formatters, ensuring uniform time representation in logs regardless
|
9
|
+
# of the chosen output format. Uses ISO 8601 format with microsecond precision.
|
10
|
+
#
|
11
|
+
# @example Basic timestamp formatting
|
12
|
+
# Utils::LogTimestamp.call(Time.now)
|
13
|
+
# # => "2022-07-17T18:43:15.123456"
|
14
|
+
#
|
15
|
+
# @example Usage in log formatters
|
16
|
+
# timestamp = Utils::LogTimestamp.call(time.utc)
|
17
|
+
# log_entry = "#{severity} [#{timestamp}] #{message}"
|
18
|
+
#
|
19
|
+
# @example Consistent formatting across formatters
|
20
|
+
# # JSON formatter
|
21
|
+
# { "timestamp": Utils::LogTimestamp.call(time.utc) }
|
22
|
+
#
|
23
|
+
# # Line formatter
|
24
|
+
# "[#{Utils::LogTimestamp.call(time.utc)} ##{Process.pid}]"
|
25
|
+
#
|
26
|
+
# @see CMDx::LogFormatters::Json Uses this for JSON timestamp field
|
27
|
+
# @see CMDx::LogFormatters::Line Uses this for traditional log format
|
28
|
+
# @see CMDx::LogFormatters::Logstash Uses this for @timestamp field
|
5
29
|
module LogTimestamp
|
6
30
|
|
31
|
+
# ISO 8601 datetime format with microsecond precision
|
32
|
+
# @return [String] strftime format string for consistent timestamp formatting
|
7
33
|
DATETIME_FORMAT = "%Y-%m-%dT%H:%M:%S.%6N"
|
8
34
|
|
9
35
|
module_function
|
10
36
|
|
37
|
+
# Formats a Time object as an ISO 8601 timestamp string.
|
38
|
+
#
|
39
|
+
# Converts the given time to a standardized string representation
|
40
|
+
# using ISO 8601 format with microsecond precision. This ensures
|
41
|
+
# consistent timestamp formatting across all CMDx log outputs.
|
42
|
+
#
|
43
|
+
# @param time [Time] Time object to format
|
44
|
+
# @return [String] ISO 8601 formatted timestamp with microseconds
|
45
|
+
#
|
46
|
+
# @example Current time formatting
|
47
|
+
# LogTimestamp.call(Time.now)
|
48
|
+
# # => "2022-07-17T18:43:15.123456"
|
49
|
+
#
|
50
|
+
# @example UTC time formatting for logs
|
51
|
+
# LogTimestamp.call(Time.now.utc)
|
52
|
+
# # => "2022-07-17T18:43:15.123456"
|
53
|
+
#
|
54
|
+
# @example Integration with log formatters
|
55
|
+
# def format_log_entry(severity, time, message)
|
56
|
+
# timestamp = LogTimestamp.call(time.utc)
|
57
|
+
# "#{severity} [#{timestamp}] #{message}"
|
58
|
+
# end
|
11
59
|
def call(time)
|
12
60
|
time.strftime(DATETIME_FORMAT)
|
13
61
|
end
|
@@ -2,16 +2,83 @@
|
|
2
2
|
|
3
3
|
module CMDx
|
4
4
|
module Utils
|
5
|
+
# Utility for measuring execution time using monotonic clock.
|
6
|
+
#
|
7
|
+
# MonotonicRuntime provides accurate execution time measurement that is
|
8
|
+
# unaffected by system clock adjustments, leap seconds, or other time
|
9
|
+
# synchronization events. Uses Ruby's Process.clock_gettime with
|
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
|
5
39
|
module MonotonicRuntime
|
6
40
|
|
7
41
|
module_function
|
8
42
|
|
43
|
+
# Measures the execution time of a given block using monotonic clock.
|
44
|
+
#
|
45
|
+
# Executes the provided block and returns the elapsed time in milliseconds.
|
46
|
+
# Uses Process.clock_gettime with CLOCK_MONOTONIC to ensure accurate
|
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"
|
57
|
+
#
|
58
|
+
# @example Database query timing
|
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"
|
63
|
+
#
|
64
|
+
# @example API call timing with error handling
|
65
|
+
# api_time = MonotonicRuntime.call do
|
66
|
+
# begin
|
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
|
74
|
+
#
|
75
|
+
# @note The block's return value is discarded; only execution time is returned
|
76
|
+
# @note Uses millisecond precision for practical performance monitoring
|
77
|
+
# @note Monotonic clock ensures accurate timing regardless of system clock changes
|
9
78
|
def call(&)
|
10
|
-
|
79
|
+
now = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond)
|
11
80
|
yield
|
12
|
-
|
13
|
-
|
14
|
-
finish - start
|
81
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond) - now
|
15
82
|
end
|
16
83
|
|
17
84
|
end
|
@@ -2,14 +2,92 @@
|
|
2
2
|
|
3
3
|
module CMDx
|
4
4
|
module Utils
|
5
|
+
# Utility for generating method names with prefixes and suffixes.
|
6
|
+
#
|
7
|
+
# NameAffix provides flexible method name generation for dynamic method
|
8
|
+
# creation, delegation, and metaprogramming scenarios. Supports custom
|
9
|
+
# prefixes, suffixes, and complete name overrides for method naming
|
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
|
5
38
|
module NameAffix
|
6
39
|
|
40
|
+
# Proc for handling affix logic with boolean or custom values
|
41
|
+
# @return [Proc] processor for affix options that handles true/false and custom strings
|
7
42
|
AFFIX = proc do |o, &block|
|
8
43
|
o == true ? block.call : o
|
9
44
|
end.freeze
|
10
45
|
|
11
46
|
module_function
|
12
47
|
|
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
|
66
|
+
#
|
67
|
+
# @example Custom prefix
|
68
|
+
# NameAffix.call(:method, "user", prefix: "get_")
|
69
|
+
# # => :get_method
|
70
|
+
#
|
71
|
+
# @example Default suffix generation
|
72
|
+
# NameAffix.call(:method, "user", suffix: true)
|
73
|
+
# # => :method_user
|
74
|
+
#
|
75
|
+
# @example Custom suffix
|
76
|
+
# NameAffix.call(:method, "user", suffix: "_count")
|
77
|
+
# # => :method_count
|
78
|
+
#
|
79
|
+
# @example Combined prefix and suffix
|
80
|
+
# NameAffix.call(:name, "user", prefix: "get_", suffix: "_value")
|
81
|
+
# # => :get_name_value
|
82
|
+
#
|
83
|
+
# @example Complete name override (ignores prefix/suffix)
|
84
|
+
# NameAffix.call(:original, "user", prefix: true, as: :custom)
|
85
|
+
# # => :custom
|
86
|
+
#
|
87
|
+
# @example Parameter method generation
|
88
|
+
# # CMDx internally uses this for parameter methods:
|
89
|
+
# NameAffix.call(:email, "user", suffix: "?") # => :email?
|
90
|
+
# NameAffix.call(:process, "order", prefix: "can_") # => :can_process
|
13
91
|
def call(method_name, source, options = {})
|
14
92
|
options[:as] || begin
|
15
93
|
prefix = AFFIX.call(options[:prefix]) { "#{source}_" }
|
@@ -2,10 +2,92 @@
|
|
2
2
|
|
3
3
|
module CMDx
|
4
4
|
module Validators
|
5
|
+
# Custom validator for parameter validation using user-defined validation logic.
|
6
|
+
#
|
7
|
+
# The Custom validator allows you to implement your own validation logic by
|
8
|
+
# providing a callable validator class or object. This enables complex business
|
9
|
+
# rule validation that goes beyond the built-in validators.
|
10
|
+
#
|
11
|
+
# @example Basic custom validator
|
12
|
+
# class EmailDomainValidator
|
13
|
+
# def self.call(value, options)
|
14
|
+
# allowed_domains = options.dig(:custom, :domains) || ['example.com']
|
15
|
+
# domain = value.split('@').last
|
16
|
+
# allowed_domains.include?(domain)
|
17
|
+
# end
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# class ProcessUserTask < CMDx::Task
|
21
|
+
# required :email, custom: { validator: EmailDomainValidator }
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# @example Custom validator with options
|
25
|
+
# class ProcessUserTask < CMDx::Task
|
26
|
+
# required :email, custom: {
|
27
|
+
# validator: EmailDomainValidator,
|
28
|
+
# domains: ['company.com', 'partner.org'],
|
29
|
+
# message: "must be from an approved domain"
|
30
|
+
# }
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
# @example Complex business logic validator
|
34
|
+
# class AgeValidator
|
35
|
+
# def self.call(value, options)
|
36
|
+
# min_age = options.dig(:custom, :min_age) || 18
|
37
|
+
# max_age = options.dig(:custom, :max_age) || 120
|
38
|
+
# value.between?(min_age, max_age)
|
39
|
+
# end
|
40
|
+
# end
|
41
|
+
#
|
42
|
+
# @example Proc-based validator
|
43
|
+
# class ProcessOrderTask < CMDx::Task
|
44
|
+
# required :discount, custom: {
|
45
|
+
# validator: ->(value, options) { value <= 50 },
|
46
|
+
# message: "cannot exceed 50%"
|
47
|
+
# }
|
48
|
+
# end
|
49
|
+
#
|
50
|
+
# @see CMDx::Parameter Parameter validation integration
|
51
|
+
# @see CMDx::ValidationError Raised when validation fails
|
5
52
|
module Custom
|
6
53
|
|
7
54
|
module_function
|
8
55
|
|
56
|
+
# Validates a parameter value using a custom validator.
|
57
|
+
#
|
58
|
+
# Calls the provided validator with the value and options, expecting
|
59
|
+
# a truthy return value for successful validation. If validation fails,
|
60
|
+
# raises a ValidationError with the configured or default message.
|
61
|
+
#
|
62
|
+
# @param value [Object] The parameter value to validate
|
63
|
+
# @param options [Hash] Validation configuration options
|
64
|
+
# @option options [Hash] :custom Custom validation configuration
|
65
|
+
# @option options [#call] :custom.validator Callable validator object/class
|
66
|
+
# @option options [String] :custom.message Custom error message
|
67
|
+
# @option options [Hash] :custom Additional options passed to validator
|
68
|
+
#
|
69
|
+
# @return [void]
|
70
|
+
# @raise [ValidationError] If the custom validator returns falsy
|
71
|
+
#
|
72
|
+
# @example Successful validation
|
73
|
+
# validator = ->(value, options) { value.length > 5 }
|
74
|
+
# Custom.call("hello world", custom: { validator: validator })
|
75
|
+
# # => passes without error
|
76
|
+
#
|
77
|
+
# @example Failed validation with default message
|
78
|
+
# validator = ->(value, options) { value > 100 }
|
79
|
+
# Custom.call(50, custom: { validator: validator })
|
80
|
+
# # => raises ValidationError: "is not valid"
|
81
|
+
#
|
82
|
+
# @example Failed validation with custom message
|
83
|
+
# validator = ->(value, options) { value.even? }
|
84
|
+
# Custom.call(7, custom: { validator: validator, message: "must be even" })
|
85
|
+
# # => raises ValidationError: "must be even"
|
86
|
+
#
|
87
|
+
# @example Validator with additional options
|
88
|
+
# validator = ->(value, opts) { value >= opts.dig(:custom, :minimum) }
|
89
|
+
# Custom.call(10, custom: { validator: validator, minimum: 5 })
|
90
|
+
# # => passes without error
|
9
91
|
def call(value, options = {})
|
10
92
|
return if options.dig(:custom, :validator).call(value, options)
|
11
93
|
|
@@ -2,10 +2,93 @@
|
|
2
2
|
|
3
3
|
module CMDx
|
4
4
|
module Validators
|
5
|
+
# Exclusion validator for parameter validation against forbidden values.
|
6
|
+
#
|
7
|
+
# The Exclusion validator ensures that parameter values are NOT within a
|
8
|
+
# specified set of forbidden values. It supports both array-based exclusion
|
9
|
+
# (specific values) and range-based exclusion (value ranges).
|
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
|
5
47
|
module Exclusion
|
6
48
|
|
7
49
|
extend self
|
8
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.
|
56
|
+
#
|
57
|
+
# @param value [Object] The parameter value to validate
|
58
|
+
# @param options [Hash] Validation configuration options
|
59
|
+
# @option options [Hash] :exclusion Exclusion validation configuration
|
60
|
+
# @option options [Array, Range] :exclusion.in Values/range to exclude
|
61
|
+
# @option options [Array, Range] :exclusion.within Alias for :in
|
62
|
+
# @option options [String] :exclusion.of_message Error message for array exclusion
|
63
|
+
# @option options [String] :exclusion.in_message Error message for range exclusion
|
64
|
+
# @option options [String] :exclusion.within_message Alias for :in_message
|
65
|
+
# @option options [String] :exclusion.message General error message override
|
66
|
+
#
|
67
|
+
# @return [void]
|
68
|
+
# @raise [ValidationError] If value is found in the exclusion set
|
69
|
+
#
|
70
|
+
# @example Array exclusion validation
|
71
|
+
# Exclusion.call("pending", exclusion: { in: ['cancelled', 'failed'] })
|
72
|
+
# # => passes without error
|
73
|
+
#
|
74
|
+
# @example Failed array exclusion
|
75
|
+
# Exclusion.call("cancelled", exclusion: { in: ['cancelled', 'failed'] })
|
76
|
+
# # => raises ValidationError: "must not be one of: \"cancelled\", \"failed\""
|
77
|
+
#
|
78
|
+
# @example Range exclusion validation
|
79
|
+
# Exclusion.call(25, exclusion: { in: 0..17 })
|
80
|
+
# # => passes without error
|
81
|
+
#
|
82
|
+
# @example Failed range exclusion
|
83
|
+
# Exclusion.call(15, exclusion: { in: 0..17 })
|
84
|
+
# # => raises ValidationError: "must not be within 0 and 17"
|
85
|
+
#
|
86
|
+
# @example Custom error messages
|
87
|
+
# Exclusion.call("admin", exclusion: {
|
88
|
+
# in: ['admin', 'root'],
|
89
|
+
# of_message: "role is restricted"
|
90
|
+
# })
|
91
|
+
# # => raises ValidationError: "role is restricted"
|
9
92
|
def call(value, options = {})
|
10
93
|
values = options.dig(:exclusion, :in) ||
|
11
94
|
options.dig(:exclusion, :within)
|
@@ -19,6 +102,11 @@ module CMDx
|
|
19
102
|
|
20
103
|
private
|
21
104
|
|
105
|
+
# Raises validation error for array-based exclusion violations.
|
106
|
+
#
|
107
|
+
# @param values [Array] The excluded values array
|
108
|
+
# @param options [Hash] Validation options containing error messages
|
109
|
+
# @raise [ValidationError] With formatted error message
|
22
110
|
def raise_of_validation_error!(values, options)
|
23
111
|
values = values.map(&:inspect).join(", ")
|
24
112
|
message = options.dig(:exclusion, :of_message) ||
|
@@ -32,6 +120,12 @@ module CMDx
|
|
32
120
|
)
|
33
121
|
end
|
34
122
|
|
123
|
+
# Raises validation error for range-based exclusion violations.
|
124
|
+
#
|
125
|
+
# @param min [Object] Range minimum value
|
126
|
+
# @param max [Object] Range maximum value
|
127
|
+
# @param options [Hash] Validation options containing error messages
|
128
|
+
# @raise [ValidationError] With formatted error message
|
35
129
|
def raise_within_validation_error!(min, max, options)
|
36
130
|
message = options.dig(:exclusion, :in_message) ||
|
37
131
|
options.dig(:exclusion, :within_message) ||
|