servactory 3.0.4 → 3.1.0.rc1
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/lib/servactory/actions/action.rb +2 -2
- data/lib/servactory/actions/collection.rb +1 -1
- data/lib/servactory/actions/stages/collection.rb +1 -1
- data/lib/servactory/configuration/config.rb +0 -16
- data/lib/servactory/configuration/configurable.rb +3 -6
- data/lib/servactory/configuration/hash_mode/class_names_collection.rb +1 -4
- data/lib/servactory/configuration/option_helpers/option_helpers_collection.rb +57 -8
- data/lib/servactory/context/warehouse/inputs.rb +28 -15
- data/lib/servactory/context/workspace/inputs.rb +27 -18
- data/lib/servactory/context/workspace/internals.rb +44 -27
- data/lib/servactory/context/workspace/outputs.rb +31 -23
- data/lib/servactory/dsl.rb +1 -1
- data/lib/servactory/info/builder.rb +2 -3
- data/lib/servactory/inputs/collection.rb +14 -21
- data/lib/servactory/inputs/input.rb +6 -103
- data/lib/servactory/inputs/tools/validation.rb +32 -57
- data/lib/servactory/inputs/validations/required.rb +3 -2
- data/lib/servactory/internals/collection.rb +5 -24
- data/lib/servactory/internals/internal.rb +4 -112
- data/lib/servactory/maintenance/attributes/base.rb +135 -0
- data/lib/servactory/maintenance/attributes/collection.rb +109 -0
- data/lib/servactory/maintenance/attributes/option_helper.rb +33 -12
- data/lib/servactory/maintenance/{attributes/options_collection.rb → options/collection.rb} +6 -7
- data/lib/servactory/maintenance/{attributes → options}/define_conflict.rb +1 -1
- data/lib/servactory/maintenance/{attributes → options}/define_method.rb +1 -1
- data/lib/servactory/maintenance/options/helper.rb +23 -0
- data/lib/servactory/maintenance/{attributes → options}/option.rb +50 -27
- data/lib/servactory/maintenance/options/registrar.rb +200 -0
- data/lib/servactory/maintenance/{attributes/validations → validations/checkers}/must.rb +25 -8
- data/lib/servactory/maintenance/{attributes/validations → validations/checkers}/type.rb +6 -5
- data/lib/servactory/maintenance/validations/concerns/error_builder.rb +50 -0
- data/lib/servactory/maintenance/validations/performer.rb +57 -0
- data/lib/servactory/maintenance/validations/support/type_validator.rb +36 -0
- data/lib/servactory/maintenance/{attributes → validations}/translator/must.rb +1 -1
- data/lib/servactory/maintenance/{attributes → validations}/translator/type.rb +1 -1
- data/lib/servactory/outputs/collection.rb +5 -24
- data/lib/servactory/outputs/output.rb +4 -112
- data/lib/servactory/result.rb +48 -39
- data/lib/servactory/tool_kit/dynamic_options/consists_of.rb +2 -4
- data/lib/servactory/tool_kit/dynamic_options/must.rb +2 -2
- data/lib/servactory/tool_kit/dynamic_options/schema.rb +7 -9
- data/lib/servactory/utils.rb +0 -8
- data/lib/servactory/version.rb +3 -3
- data/lib/servactory.rb +4 -0
- metadata +19 -22
- data/lib/servactory/maintenance/attributes/options/registrar.rb +0 -183
- data/lib/servactory/maintenance/attributes/tools/validation.rb +0 -84
- data/lib/servactory/maintenance/attributes/validations/concerns/error_builder.rb +0 -52
- data/lib/servactory/maintenance/validations/types.rb +0 -34
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Servactory
|
|
4
|
+
module Maintenance
|
|
5
|
+
module Attributes
|
|
6
|
+
# Collection wrapper for managing attribute objects (inputs, internals, outputs).
|
|
7
|
+
#
|
|
8
|
+
# ## Purpose
|
|
9
|
+
#
|
|
10
|
+
# Collection provides a polymorphic base for storing and querying attribute
|
|
11
|
+
# instances. It wraps a Set to ensure uniqueness and delegates common
|
|
12
|
+
# enumeration methods. Subclasses override `lookup_name` to customize
|
|
13
|
+
# how attributes are indexed and filtered (e.g., inputs use `internal_name`
|
|
14
|
+
# for the `as:` parameter).
|
|
15
|
+
#
|
|
16
|
+
# ## Usage
|
|
17
|
+
#
|
|
18
|
+
# Subclasses are used internally by Input, Internal, and Output DSL modules
|
|
19
|
+
# to manage their registered attributes:
|
|
20
|
+
#
|
|
21
|
+
# ```ruby
|
|
22
|
+
# collection = Collection.new
|
|
23
|
+
# collection << attribute
|
|
24
|
+
#
|
|
25
|
+
# collection.names # => [:first_name, :last_name]
|
|
26
|
+
# collection.find_by(name: :first_name) # => attribute instance
|
|
27
|
+
# collection.only(:first_name) # => filtered Collection
|
|
28
|
+
# collection.except(:last_name) # => filtered Collection
|
|
29
|
+
# ```
|
|
30
|
+
#
|
|
31
|
+
# ## Performance
|
|
32
|
+
#
|
|
33
|
+
# The collection uses memoization for frequently accessed data:
|
|
34
|
+
# - `attributes_index` - cached hash for O(1) lookups by name
|
|
35
|
+
#
|
|
36
|
+
class Collection
|
|
37
|
+
extend Forwardable
|
|
38
|
+
|
|
39
|
+
def_delegators :@collection,
|
|
40
|
+
:<<,
|
|
41
|
+
:filter,
|
|
42
|
+
:each, :each_with_object,
|
|
43
|
+
:map, :to_h,
|
|
44
|
+
:merge
|
|
45
|
+
|
|
46
|
+
# Initializes the collection with an optional pre-built Set.
|
|
47
|
+
#
|
|
48
|
+
# @param collection [Set] initial set of attributes
|
|
49
|
+
# @return [Collection]
|
|
50
|
+
def initialize(collection = Set.new)
|
|
51
|
+
@collection = collection
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Returns names of all attributes in the collection.
|
|
55
|
+
#
|
|
56
|
+
# @return [Array<Symbol>] list of attribute names
|
|
57
|
+
def names
|
|
58
|
+
map(&:name)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Returns a new collection containing only the named attributes.
|
|
62
|
+
#
|
|
63
|
+
# @param names [Array<Symbol>] attribute names to include
|
|
64
|
+
# @return [Collection] filtered collection
|
|
65
|
+
def only(*names)
|
|
66
|
+
self.class.new(filter { |attribute| names.include?(lookup_name(attribute)) })
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Returns a new collection excluding the named attributes.
|
|
70
|
+
#
|
|
71
|
+
# @param names [Array<Symbol>] attribute names to exclude
|
|
72
|
+
# @return [Collection] filtered collection
|
|
73
|
+
def except(*names)
|
|
74
|
+
self.class.new(filter { |attribute| names.exclude?(lookup_name(attribute)) })
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Finds an attribute by its name using indexed lookup.
|
|
78
|
+
#
|
|
79
|
+
# @param name [Symbol] the attribute name to find
|
|
80
|
+
# @return [Object, nil] the found attribute or nil
|
|
81
|
+
def find_by(name:)
|
|
82
|
+
attributes_index[name]
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
private
|
|
86
|
+
|
|
87
|
+
# Builds and caches a hash index for O(1) attribute lookups.
|
|
88
|
+
#
|
|
89
|
+
# @return [Hash{Symbol => Object}] attribute names mapped to attribute instances
|
|
90
|
+
def attributes_index
|
|
91
|
+
@attributes_index ||= each_with_object({}) do |attribute, index|
|
|
92
|
+
index[lookup_name(attribute)] = attribute
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Returns the name used for indexing and filtering a given attribute.
|
|
97
|
+
#
|
|
98
|
+
# Subclasses override this to use alternative name fields
|
|
99
|
+
# (e.g., `internal_name` for inputs with the `as:` parameter).
|
|
100
|
+
#
|
|
101
|
+
# @param attribute [Object] the attribute to extract a name from
|
|
102
|
+
# @return [Symbol] the lookup name
|
|
103
|
+
def lookup_name(attribute)
|
|
104
|
+
attribute.name
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
@@ -3,19 +3,40 @@
|
|
|
3
3
|
module Servactory
|
|
4
4
|
module Maintenance
|
|
5
5
|
module Attributes
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
6
|
+
# @deprecated Use {Servactory::Maintenance::Options::Helper} instead.
|
|
7
|
+
# This class is maintained for backward compatibility only and will be
|
|
8
|
+
# removed in a future major version.
|
|
9
|
+
#
|
|
10
|
+
# OptionHelper provides a simple way to define custom option helpers
|
|
11
|
+
# for service inputs, internals, and outputs configuration.
|
|
12
|
+
#
|
|
13
|
+
# @example Creating a custom option helper
|
|
14
|
+
# Servactory::Maintenance::Attributes::OptionHelper.new(
|
|
15
|
+
# name: :must_be_positive,
|
|
16
|
+
# equivalent: {
|
|
17
|
+
# must: {
|
|
18
|
+
# be_positive: {
|
|
19
|
+
# is: ->(value:, **) { value.positive? },
|
|
20
|
+
# message: "must be positive"
|
|
21
|
+
# }
|
|
22
|
+
# }
|
|
23
|
+
# }
|
|
24
|
+
# )
|
|
25
|
+
#
|
|
26
|
+
# @see Servactory::Maintenance::Options::Helper The new preferred class
|
|
27
|
+
class OptionHelper < Servactory::Maintenance::Options::Helper
|
|
28
|
+
# @deprecated This class is deprecated. Use Options::Helper instead.
|
|
29
|
+
#
|
|
30
|
+
# Creates a new OptionHelper instance.
|
|
31
|
+
#
|
|
32
|
+
# @param name [Symbol] The name of the option helper
|
|
33
|
+
# @param equivalent [Hash, Proc] The equivalent option configuration
|
|
34
|
+
# @param meta [Hash] Additional metadata for the helper
|
|
35
|
+
# @return [OptionHelper] A new instance
|
|
11
36
|
def initialize(name:, equivalent:, meta: {})
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
def dynamic_option?
|
|
18
|
-
meta[:type] == :dynamic_option
|
|
37
|
+
warn "[DEPRECATION] Servactory::Maintenance::Attributes::OptionHelper is deprecated. " \
|
|
38
|
+
"Use Servactory::Maintenance::Options::Helper instead."
|
|
39
|
+
super
|
|
19
40
|
end
|
|
20
41
|
end
|
|
21
42
|
end
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
module Servactory
|
|
4
4
|
module Maintenance
|
|
5
|
-
module
|
|
5
|
+
module Options
|
|
6
6
|
# Collection wrapper for managing Option objects.
|
|
7
7
|
#
|
|
8
8
|
# ## Purpose
|
|
9
9
|
#
|
|
10
|
-
#
|
|
10
|
+
# Collection provides a unified interface for storing and querying
|
|
11
11
|
# Option instances associated with service attributes (inputs, internals, outputs).
|
|
12
12
|
# It wraps a Set to ensure uniqueness and delegates common enumeration methods.
|
|
13
13
|
#
|
|
@@ -17,7 +17,7 @@ module Servactory
|
|
|
17
17
|
# to manage their registered options:
|
|
18
18
|
#
|
|
19
19
|
# ```ruby
|
|
20
|
-
# collection =
|
|
20
|
+
# collection = Collection.new
|
|
21
21
|
# collection << Option.new(name: :required, ...)
|
|
22
22
|
# collection << Option.new(name: :types, ...)
|
|
23
23
|
#
|
|
@@ -32,21 +32,20 @@ module Servactory
|
|
|
32
32
|
# - `options_for_checks` - cached hash for validation pipeline
|
|
33
33
|
# - `options_index` - cached hash for O(1) lookups by name
|
|
34
34
|
#
|
|
35
|
-
class
|
|
35
|
+
class Collection
|
|
36
36
|
extend Forwardable
|
|
37
37
|
|
|
38
38
|
def_delegators :@collection,
|
|
39
39
|
:<<,
|
|
40
40
|
:filter,
|
|
41
|
-
:
|
|
41
|
+
:each_with_object,
|
|
42
42
|
:map, :flat_map,
|
|
43
|
-
:find,
|
|
44
43
|
:size,
|
|
45
44
|
:empty?
|
|
46
45
|
|
|
47
46
|
# Initializes an empty collection.
|
|
48
47
|
#
|
|
49
|
-
# @return [
|
|
48
|
+
# @return [Collection]
|
|
50
49
|
def initialize
|
|
51
50
|
@collection = Set.new
|
|
52
51
|
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Servactory
|
|
4
|
+
module Maintenance
|
|
5
|
+
module Options
|
|
6
|
+
class Helper
|
|
7
|
+
attr_reader :name,
|
|
8
|
+
:equivalent,
|
|
9
|
+
:meta
|
|
10
|
+
|
|
11
|
+
def initialize(name:, equivalent:, meta: {})
|
|
12
|
+
@name = name
|
|
13
|
+
@equivalent = equivalent
|
|
14
|
+
@meta = meta
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def dynamic_option?
|
|
18
|
+
meta[:type] == :dynamic_option
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
module Servactory
|
|
4
4
|
module Maintenance
|
|
5
|
-
module
|
|
5
|
+
module Options
|
|
6
6
|
class Option # rubocop:disable Metrics/ClassLength
|
|
7
7
|
DEFAULT_BODY = ->(key:, body:, message: nil) { { key => body, message: } }
|
|
8
8
|
|
|
@@ -14,9 +14,10 @@ module Servactory
|
|
|
14
14
|
:define_conflicts,
|
|
15
15
|
:need_for_checks,
|
|
16
16
|
:body,
|
|
17
|
-
:body_key
|
|
17
|
+
:body_key,
|
|
18
|
+
:return_value_on_access
|
|
18
19
|
|
|
19
|
-
# rubocop:disable Metrics/MethodLength
|
|
20
|
+
# rubocop:disable Metrics/MethodLength, Metrics/ParameterLists
|
|
20
21
|
def initialize(
|
|
21
22
|
name:,
|
|
22
23
|
attribute:,
|
|
@@ -28,11 +29,15 @@ module Servactory
|
|
|
28
29
|
body_value: true,
|
|
29
30
|
define_methods: nil,
|
|
30
31
|
define_conflicts: nil,
|
|
31
|
-
|
|
32
|
+
detect_advanced_mode: true,
|
|
33
|
+
return_value_on_access: false,
|
|
34
|
+
normalizer: nil,
|
|
32
35
|
**options
|
|
33
36
|
)
|
|
34
37
|
@name = name.to_sym
|
|
35
38
|
@body_key = body_key
|
|
39
|
+
@return_value_on_access = return_value_on_access
|
|
40
|
+
@normalizer = normalizer
|
|
36
41
|
@validation_class = validation_class
|
|
37
42
|
@define_methods = define_methods
|
|
38
43
|
@define_conflicts = define_conflicts
|
|
@@ -44,12 +49,12 @@ module Servactory
|
|
|
44
49
|
body_key:,
|
|
45
50
|
body_value:,
|
|
46
51
|
body_fallback:,
|
|
47
|
-
|
|
52
|
+
detect_advanced_mode:
|
|
48
53
|
)
|
|
49
54
|
|
|
50
55
|
apply_dynamic_methods_to(attribute:)
|
|
51
56
|
end
|
|
52
|
-
# rubocop:enable Metrics/MethodLength
|
|
57
|
+
# rubocop:enable Metrics/MethodLength, Metrics/ParameterLists
|
|
53
58
|
|
|
54
59
|
def value
|
|
55
60
|
return body unless body.is_a?(Hash)
|
|
@@ -57,51 +62,69 @@ module Servactory
|
|
|
57
62
|
body.fetch(@body_key, body)
|
|
58
63
|
end
|
|
59
64
|
|
|
65
|
+
def body_for_access
|
|
66
|
+
@return_value_on_access ? value : body
|
|
67
|
+
end
|
|
68
|
+
|
|
60
69
|
def need_for_checks?
|
|
61
70
|
need_for_checks
|
|
62
71
|
end
|
|
63
72
|
|
|
64
73
|
private
|
|
65
74
|
|
|
75
|
+
# rubocop:disable Metrics/MethodLength
|
|
66
76
|
def construct_body(
|
|
67
77
|
original_value:,
|
|
68
78
|
options:,
|
|
69
79
|
body_key:,
|
|
70
80
|
body_value:,
|
|
71
81
|
body_fallback:,
|
|
72
|
-
|
|
82
|
+
detect_advanced_mode:
|
|
73
83
|
)
|
|
74
|
-
return original_value if original_value.present?
|
|
75
|
-
|
|
84
|
+
return wrap_and_normalize(original_value) if original_value.present?
|
|
85
|
+
|
|
86
|
+
raw_value = options.fetch(@name, body_fallback)
|
|
87
|
+
|
|
88
|
+
result = if detect_advanced_mode
|
|
89
|
+
use_advanced_mode(
|
|
90
|
+
raw_value:,
|
|
91
|
+
body_key:,
|
|
92
|
+
body_value:,
|
|
93
|
+
body_fallback:
|
|
94
|
+
)
|
|
95
|
+
else
|
|
96
|
+
wrap_value(raw_value)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
apply_normalizer(result)
|
|
100
|
+
end
|
|
101
|
+
# rubocop:enable Metrics/MethodLength
|
|
76
102
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
body_value:,
|
|
81
|
-
body_fallback:
|
|
82
|
-
)
|
|
103
|
+
def wrap_and_normalize(value)
|
|
104
|
+
result = wrap_value(value)
|
|
105
|
+
apply_normalizer(result)
|
|
83
106
|
end
|
|
84
107
|
|
|
85
|
-
def
|
|
86
|
-
|
|
87
|
-
|
|
108
|
+
def apply_normalizer(body)
|
|
109
|
+
return body unless @normalizer && body.is_a?(Hash)
|
|
110
|
+
|
|
111
|
+
body[@body_key] = @normalizer.call(body[@body_key])
|
|
112
|
+
body
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def wrap_value(value)
|
|
116
|
+
DEFAULT_BODY.call(key: @body_key, body: value)
|
|
117
|
+
end
|
|
88
118
|
|
|
119
|
+
def use_advanced_mode(raw_value:, body_key:, body_value:, body_fallback:)
|
|
89
120
|
construct_advanced_body(
|
|
90
|
-
body
|
|
121
|
+
body: raw_value,
|
|
91
122
|
body_key:,
|
|
92
123
|
body_value:,
|
|
93
124
|
body_fallback:
|
|
94
125
|
)
|
|
95
126
|
end
|
|
96
127
|
|
|
97
|
-
def create_default_body(body_key:, body_fallback:)
|
|
98
|
-
DEFAULT_BODY.call(key: body_key, body: body_fallback)
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
def extract_body_from_options(options:, default_body:)
|
|
102
|
-
options.fetch(@name, default_body)
|
|
103
|
-
end
|
|
104
|
-
|
|
105
128
|
def construct_advanced_body(body:, body_key:, body_value:, body_fallback:)
|
|
106
129
|
if body.is_a?(Hash)
|
|
107
130
|
build_hash_body(body:, body_key:, body_value:, body_fallback:)
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Servactory
|
|
4
|
+
module Maintenance
|
|
5
|
+
module Options
|
|
6
|
+
class Registrar # rubocop:disable Metrics/ClassLength
|
|
7
|
+
RESERVED_OPTIONS = %i[
|
|
8
|
+
type
|
|
9
|
+
required
|
|
10
|
+
default
|
|
11
|
+
collection
|
|
12
|
+
must
|
|
13
|
+
prepare
|
|
14
|
+
].freeze
|
|
15
|
+
|
|
16
|
+
DEFAULT_FEATURES = {
|
|
17
|
+
required: false,
|
|
18
|
+
types: false,
|
|
19
|
+
default: false,
|
|
20
|
+
must: false,
|
|
21
|
+
prepare: false
|
|
22
|
+
}.freeze
|
|
23
|
+
|
|
24
|
+
private_constant :DEFAULT_FEATURES
|
|
25
|
+
|
|
26
|
+
def self.register(...)
|
|
27
|
+
new(...).register
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def initialize(attribute:, options:, features:)
|
|
31
|
+
@attribute = attribute
|
|
32
|
+
@options = options
|
|
33
|
+
@features = DEFAULT_FEATURES.merge(features)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
########################################################################
|
|
37
|
+
|
|
38
|
+
def register
|
|
39
|
+
register_feature(:required, Servactory::Inputs::Validations::Required)
|
|
40
|
+
register_feature(:types, Servactory::Maintenance::Validations::Checkers::Type)
|
|
41
|
+
register_feature(:default, Servactory::Maintenance::Validations::Checkers::Type)
|
|
42
|
+
register_feature(:must, Servactory::Maintenance::Validations::Checkers::Must)
|
|
43
|
+
register_feature(:prepare, nil)
|
|
44
|
+
|
|
45
|
+
self
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def collection
|
|
49
|
+
@collection ||= Servactory::Maintenance::Options::Collection.new
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
def register_feature(feature_name, validation_class)
|
|
55
|
+
return unless @features.fetch(feature_name)
|
|
56
|
+
|
|
57
|
+
method_name = "register_#{feature_name}_option"
|
|
58
|
+
send(method_name, validation_class)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
########################################################################
|
|
62
|
+
|
|
63
|
+
def register_required_option(validation_class) # rubocop:disable Metrics/MethodLength
|
|
64
|
+
create_option(
|
|
65
|
+
name: :required,
|
|
66
|
+
validation_class:,
|
|
67
|
+
define_methods: required_define_methods,
|
|
68
|
+
define_conflicts: required_define_conflicts,
|
|
69
|
+
need_for_checks: true,
|
|
70
|
+
body_key: :is,
|
|
71
|
+
body_fallback: true,
|
|
72
|
+
detect_advanced_mode: true,
|
|
73
|
+
return_value_on_access: false
|
|
74
|
+
)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def register_types_option(validation_class) # rubocop:disable Metrics/MethodLength
|
|
78
|
+
create_option(
|
|
79
|
+
name: :types,
|
|
80
|
+
validation_class:,
|
|
81
|
+
original_value: extract_types_value,
|
|
82
|
+
need_for_checks: true,
|
|
83
|
+
body_key: :is,
|
|
84
|
+
body_fallback: nil,
|
|
85
|
+
detect_advanced_mode: true,
|
|
86
|
+
return_value_on_access: true,
|
|
87
|
+
normalizer: ->(v) { Array(v).uniq }
|
|
88
|
+
)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def register_default_option(validation_class) # rubocop:disable Metrics/MethodLength
|
|
92
|
+
create_option(
|
|
93
|
+
name: :default,
|
|
94
|
+
validation_class:,
|
|
95
|
+
define_methods: [
|
|
96
|
+
create_define_method(
|
|
97
|
+
name: :default_value_present?,
|
|
98
|
+
content: ->(option:) { !option[:is].nil? }
|
|
99
|
+
)
|
|
100
|
+
],
|
|
101
|
+
need_for_checks: true,
|
|
102
|
+
body_key: :is,
|
|
103
|
+
body_fallback: nil,
|
|
104
|
+
detect_advanced_mode: false,
|
|
105
|
+
return_value_on_access: true
|
|
106
|
+
)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def register_must_option(validation_class) # rubocop:disable Metrics/MethodLength
|
|
110
|
+
create_option(
|
|
111
|
+
name: :must,
|
|
112
|
+
validation_class:,
|
|
113
|
+
define_methods: [
|
|
114
|
+
create_define_method(
|
|
115
|
+
name: :must_present?,
|
|
116
|
+
content: ->(option:) { option[:rules].present? }
|
|
117
|
+
)
|
|
118
|
+
],
|
|
119
|
+
need_for_checks: true,
|
|
120
|
+
body_key: :rules,
|
|
121
|
+
body_fallback: nil,
|
|
122
|
+
detect_advanced_mode: false,
|
|
123
|
+
return_value_on_access: true
|
|
124
|
+
)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def register_prepare_option(_validation_class) # rubocop:disable Metrics/MethodLength
|
|
128
|
+
create_option(
|
|
129
|
+
name: :prepare,
|
|
130
|
+
validation_class: nil,
|
|
131
|
+
define_methods: [
|
|
132
|
+
create_define_method(
|
|
133
|
+
name: :prepare_present?,
|
|
134
|
+
content: ->(option:) { option[:in].present? }
|
|
135
|
+
)
|
|
136
|
+
],
|
|
137
|
+
need_for_checks: false,
|
|
138
|
+
body_key: :in,
|
|
139
|
+
body_fallback: false,
|
|
140
|
+
detect_advanced_mode: true,
|
|
141
|
+
return_value_on_access: false
|
|
142
|
+
)
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def extract_types_value
|
|
146
|
+
type_option = @options[:type]
|
|
147
|
+
return nil if type_option.nil?
|
|
148
|
+
|
|
149
|
+
# Advanced Mode: type: { is: String, message: "..." }
|
|
150
|
+
return nil if type_option.is_a?(Hash) && type_option.key?(:is)
|
|
151
|
+
|
|
152
|
+
# Simple Mode: type: String or type: [String, Integer]
|
|
153
|
+
Array(type_option).uniq
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
########################################################################
|
|
157
|
+
|
|
158
|
+
def required_define_methods
|
|
159
|
+
[
|
|
160
|
+
create_define_method(
|
|
161
|
+
name: :required?,
|
|
162
|
+
content: ->(option:) { Servactory::Utils.true?(option[:is]) }
|
|
163
|
+
),
|
|
164
|
+
create_define_method(
|
|
165
|
+
name: :optional?,
|
|
166
|
+
content: ->(option:) { !Servactory::Utils.true?(option[:is]) }
|
|
167
|
+
)
|
|
168
|
+
]
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def required_define_conflicts
|
|
172
|
+
[
|
|
173
|
+
Servactory::Maintenance::Options::DefineConflict.new(
|
|
174
|
+
content: -> { :required_vs_default if @attribute.required? && @attribute.default_value_present? }
|
|
175
|
+
)
|
|
176
|
+
]
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
########################################################################
|
|
180
|
+
|
|
181
|
+
def create_option(name:, validation_class:, **options)
|
|
182
|
+
collection << Servactory::Maintenance::Options::Option.new(
|
|
183
|
+
name:,
|
|
184
|
+
attribute: @attribute,
|
|
185
|
+
validation_class:,
|
|
186
|
+
**options,
|
|
187
|
+
**@options
|
|
188
|
+
)
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def create_define_method(name:, content:)
|
|
192
|
+
Servactory::Maintenance::Options::DefineMethod.new(
|
|
193
|
+
name:,
|
|
194
|
+
content:
|
|
195
|
+
)
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
end
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
module Servactory
|
|
4
4
|
module Maintenance
|
|
5
|
-
module
|
|
6
|
-
module
|
|
5
|
+
module Validations
|
|
6
|
+
module Checkers
|
|
7
7
|
# Validates custom conditions defined with `must` option.
|
|
8
8
|
#
|
|
9
9
|
# ## Purpose
|
|
@@ -82,9 +82,10 @@ module Servactory
|
|
|
82
82
|
# @param attribute [Inputs::Input, Internals::Internal, Outputs::Output] Attribute being validated
|
|
83
83
|
# @param value [Object] Value to pass to check lambda
|
|
84
84
|
# @param code [Symbol] Condition identifier (e.g., :be_adult)
|
|
85
|
-
# @param options [Hash] Condition options with :is (lambda) and :message
|
|
85
|
+
# @param options [Hash, Proc] Condition options with :is (lambda) and :message, or just a lambda
|
|
86
86
|
# @return [String, nil] Error message on failure, nil on success
|
|
87
87
|
def self.validate_condition(context:, attribute:, value:, code:, options:) # rubocop:disable Metrics/MethodLength
|
|
88
|
+
options = normalize_rule_options(options)
|
|
88
89
|
check, message = options.values_at(:is, :message)
|
|
89
90
|
|
|
90
91
|
check_result, check_result_code, meta = call_check(
|
|
@@ -111,6 +112,22 @@ module Servactory
|
|
|
111
112
|
end
|
|
112
113
|
private_class_method :validate_condition
|
|
113
114
|
|
|
115
|
+
# Normalizes rule options to always have :is and :message keys.
|
|
116
|
+
#
|
|
117
|
+
# Supports Simple Mode where only a lambda is provided:
|
|
118
|
+
# must: { be_adult: ->(value:) { value >= 18 } }
|
|
119
|
+
# Converts to Advanced Mode format:
|
|
120
|
+
# must: { be_adult: { is: lambda, message: nil } }
|
|
121
|
+
#
|
|
122
|
+
# @param options [Hash, Proc] Either { is: lambda, message: ... } or just lambda
|
|
123
|
+
# @return [Hash] Normalized options with :is and :message keys
|
|
124
|
+
def self.normalize_rule_options(options)
|
|
125
|
+
return options if options.is_a?(Hash)
|
|
126
|
+
|
|
127
|
+
{ is: options, message: nil }
|
|
128
|
+
end
|
|
129
|
+
private_class_method :normalize_rule_options
|
|
130
|
+
|
|
114
131
|
# Executes the check lambda with exception handling.
|
|
115
132
|
#
|
|
116
133
|
# Catches any StandardError from the lambda and converts it to a
|
|
@@ -124,7 +141,7 @@ module Servactory
|
|
|
124
141
|
# @return [Array] On success: [result, reason_code, meta_hash]
|
|
125
142
|
# @return [Array] On exception: [:syntax_error, error_message_string, nil]
|
|
126
143
|
def self.call_check(context:, attribute:, value:, check:, code:)
|
|
127
|
-
check.call(value:, **
|
|
144
|
+
check.call(value:, **attribute.typed_actor_kwargs)
|
|
128
145
|
rescue StandardError => e
|
|
129
146
|
# Return formatted syntax error message
|
|
130
147
|
syntax_error_message = build_syntax_error_message(
|
|
@@ -154,12 +171,12 @@ module Servactory
|
|
|
154
171
|
# @param message [String, Proc, nil] Custom error message
|
|
155
172
|
# @return [String] Processed error message
|
|
156
173
|
def self.build_error_message(context:, attribute:, value:, code:, reason:, meta:, message:)
|
|
157
|
-
message = message.presence || Servactory::Maintenance::
|
|
174
|
+
message = message.presence || Servactory::Maintenance::Validations::Translator::Must.default_message
|
|
158
175
|
|
|
159
176
|
process_message(
|
|
160
177
|
message,
|
|
161
178
|
service: context.send(:servactory_service_info),
|
|
162
|
-
**
|
|
179
|
+
**attribute.typed_actor_kwargs,
|
|
163
180
|
value:,
|
|
164
181
|
code:,
|
|
165
182
|
reason:,
|
|
@@ -180,12 +197,12 @@ module Servactory
|
|
|
180
197
|
# @param exception_message [String] Original exception message
|
|
181
198
|
# @return [String] Formatted syntax error message
|
|
182
199
|
def self.build_syntax_error_message(context:, attribute:, value:, code:, exception_message:)
|
|
183
|
-
message = Servactory::Maintenance::
|
|
200
|
+
message = Servactory::Maintenance::Validations::Translator::Must.syntax_error_message
|
|
184
201
|
|
|
185
202
|
process_message(
|
|
186
203
|
message,
|
|
187
204
|
service: context.send(:servactory_service_info),
|
|
188
|
-
**
|
|
205
|
+
**attribute.typed_actor_kwargs,
|
|
189
206
|
value:,
|
|
190
207
|
code:,
|
|
191
208
|
exception_message:
|