servactory 3.0.0 → 3.1.0.rc2
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/option_helpers/option_helpers_collection.rb +60 -2
- data/lib/servactory/context/warehouse/inputs.rb +2 -2
- data/lib/servactory/context/workspace/inputs.rb +22 -10
- data/lib/servactory/context/workspace/internals.rb +35 -13
- data/lib/servactory/context/workspace/outputs.rb +12 -6
- 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 +12 -18
- data/lib/servactory/tool_kit/dynamic_options/must.rb +2 -2
- data/lib/servactory/tool_kit/dynamic_options/schema.rb +5 -5
- data/lib/servactory/utils.rb +0 -8
- data/lib/servactory/version.rb +2 -2
- metadata +16 -13
- 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
|
@@ -3,81 +3,56 @@
|
|
|
3
3
|
module Servactory
|
|
4
4
|
module Inputs
|
|
5
5
|
module Tools
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
break if @first_error.present?
|
|
6
|
+
module Validation
|
|
7
|
+
extend self
|
|
8
|
+
|
|
9
|
+
def validate!(context, collection_of_inputs) # rubocop:disable Metrics/MethodLength
|
|
10
|
+
warehouse = context.send(:servactory_service_warehouse)
|
|
11
|
+
first_error = nil
|
|
12
|
+
failed_input = nil
|
|
13
|
+
|
|
14
|
+
collection_of_inputs.each do |input|
|
|
15
|
+
first_error = process_input(context, warehouse, input)
|
|
16
|
+
if first_error.present?
|
|
17
|
+
failed_input = input
|
|
18
|
+
break
|
|
19
|
+
end
|
|
21
20
|
end
|
|
22
21
|
|
|
23
|
-
|
|
22
|
+
return if first_error.nil?
|
|
23
|
+
|
|
24
|
+
context.fail_input!(failed_input.name, message: first_error)
|
|
24
25
|
end
|
|
25
26
|
|
|
26
27
|
private
|
|
27
28
|
|
|
28
|
-
def process_input(input)
|
|
29
|
+
def process_input(context, warehouse, input)
|
|
30
|
+
value = warehouse.fetch_input(input.name)
|
|
31
|
+
|
|
29
32
|
input.options_for_checks.each do |check_key, check_options|
|
|
30
|
-
process_option(check_key, check_options
|
|
31
|
-
|
|
33
|
+
error = process_option(context, input, value, check_key, check_options)
|
|
34
|
+
return error if error.present?
|
|
32
35
|
end
|
|
36
|
+
|
|
37
|
+
nil
|
|
33
38
|
end
|
|
34
39
|
|
|
35
|
-
def process_option(check_key, check_options
|
|
36
|
-
validation_classes =
|
|
40
|
+
def process_option(context, input, value, check_key, check_options) # rubocop:disable Metrics/MethodLength
|
|
41
|
+
validation_classes = input.collection_of_options.validation_classes
|
|
37
42
|
return if validation_classes.empty?
|
|
38
43
|
|
|
39
44
|
validation_classes.each do |validation_class|
|
|
40
|
-
error_message =
|
|
41
|
-
|
|
42
|
-
input
|
|
45
|
+
error_message = validation_class.check(
|
|
46
|
+
context:,
|
|
47
|
+
attribute: input,
|
|
48
|
+
value:,
|
|
43
49
|
check_key:,
|
|
44
50
|
check_options:
|
|
45
51
|
)
|
|
46
|
-
|
|
47
|
-
next if error_message.blank?
|
|
48
|
-
|
|
49
|
-
@first_error = error_message
|
|
50
|
-
break
|
|
52
|
+
return error_message if error_message.present?
|
|
51
53
|
end
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
def process_validation_class(
|
|
55
|
-
validation_class:,
|
|
56
|
-
input:,
|
|
57
|
-
check_key:,
|
|
58
|
-
check_options:
|
|
59
|
-
)
|
|
60
|
-
validation_class.check(
|
|
61
|
-
context: @context,
|
|
62
|
-
attribute: input,
|
|
63
|
-
value: @context.send(:servactory_service_warehouse).fetch_input(input.name),
|
|
64
|
-
check_key:,
|
|
65
|
-
check_options:
|
|
66
|
-
)
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
########################################################################
|
|
70
|
-
|
|
71
|
-
def validation_classes_from(input)
|
|
72
|
-
@validation_classes_cache ||= input.collection_of_options.validation_classes # rubocop:disable Naming/MemoizedInstanceVariableName
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
########################################################################
|
|
76
|
-
|
|
77
|
-
def raise_errors
|
|
78
|
-
return if @first_error.nil?
|
|
79
54
|
|
|
80
|
-
|
|
55
|
+
nil
|
|
81
56
|
end
|
|
82
57
|
end
|
|
83
58
|
end
|
|
@@ -22,7 +22,7 @@ module Servactory
|
|
|
22
22
|
# end
|
|
23
23
|
# ```
|
|
24
24
|
class Required
|
|
25
|
-
extend Servactory::Maintenance::
|
|
25
|
+
extend Servactory::Maintenance::Validations::Concerns::ErrorBuilder
|
|
26
26
|
|
|
27
27
|
# Validates that a required input has a present value.
|
|
28
28
|
#
|
|
@@ -30,8 +30,9 @@ module Servactory
|
|
|
30
30
|
# @param attribute [Inputs::Input] Input attribute to validate
|
|
31
31
|
# @param value [Object] Value to check for presence
|
|
32
32
|
# @param check_key [Symbol] Must be :required to trigger validation
|
|
33
|
+
# @param check_options [Hash, nil] Unused, accepted for uniform checker interface
|
|
33
34
|
# @return [String, nil] Error message on failure, nil on success
|
|
34
|
-
def self.check(context:, attribute:, value:, check_key:,
|
|
35
|
+
def self.check(context:, attribute:, value:, check_key:, check_options: nil) # rubocop:disable Lint/UnusedMethodArgument
|
|
35
36
|
return unless should_be_checked_for?(attribute, check_key)
|
|
36
37
|
return if Servactory::Utils.value_present?(value)
|
|
37
38
|
|
|
@@ -2,30 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
module Servactory
|
|
4
4
|
module Internals
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
def initialize(collection = Set.new)
|
|
11
|
-
@collection = collection
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
def only(*names)
|
|
15
|
-
Collection.new(filter { |internal| names.include?(internal.name) })
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
def except(*names)
|
|
19
|
-
Collection.new(filter { |internal| names.exclude?(internal.name) })
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
def names
|
|
23
|
-
map(&:name)
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
def find_by(name:)
|
|
27
|
-
find { |internal| internal.name == name }
|
|
28
|
-
end
|
|
5
|
+
# Specialized collection for Internal attributes.
|
|
6
|
+
#
|
|
7
|
+
# Inherits all behavior from the base Attributes::Collection
|
|
8
|
+
# without any overrides.
|
|
9
|
+
class Collection < Servactory::Maintenance::Attributes::Collection
|
|
29
10
|
end
|
|
30
11
|
end
|
|
31
12
|
end
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
module Servactory
|
|
4
4
|
module Internals
|
|
5
|
-
class Internal
|
|
5
|
+
class Internal < Servactory::Maintenance::Attributes::Base
|
|
6
6
|
class Actor
|
|
7
7
|
attr_reader :name,
|
|
8
8
|
:types,
|
|
@@ -26,138 +26,30 @@ module Servactory
|
|
|
26
26
|
# The methods below are required to support the internal work.
|
|
27
27
|
|
|
28
28
|
def input?
|
|
29
|
-
|
|
29
|
+
@attribute.input?
|
|
30
30
|
end
|
|
31
31
|
|
|
32
32
|
def internal?
|
|
33
|
-
|
|
33
|
+
@attribute.internal?
|
|
34
34
|
end
|
|
35
35
|
|
|
36
36
|
def output?
|
|
37
|
-
|
|
37
|
+
@attribute.output?
|
|
38
38
|
end
|
|
39
39
|
end
|
|
40
40
|
|
|
41
|
-
attr_reader :name,
|
|
42
|
-
:collection_of_options,
|
|
43
|
-
:options
|
|
44
|
-
|
|
45
|
-
def initialize(
|
|
46
|
-
name,
|
|
47
|
-
*helpers,
|
|
48
|
-
option_helpers:,
|
|
49
|
-
**options
|
|
50
|
-
)
|
|
51
|
-
@name = name
|
|
52
|
-
@option_helpers = option_helpers
|
|
53
|
-
|
|
54
|
-
register_options(helpers:, options:)
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
def method_missing(name, *args, &block)
|
|
58
|
-
option = @collection_of_options.find_by(name:)
|
|
59
|
-
return super if option.nil?
|
|
60
|
-
|
|
61
|
-
option.body
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
def respond_to_missing?(name, *)
|
|
65
|
-
@collection_of_options.names.include?(name) || super
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
def register_options(helpers:, options:)
|
|
69
|
-
merged_options = augment_options_with_helpers(helpers:, options:)
|
|
70
|
-
options_registrar = create_options_registrar(options: merged_options)
|
|
71
|
-
|
|
72
|
-
@options = merged_options
|
|
73
|
-
@collection_of_options = options_registrar.collection
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
def options_for_checks
|
|
77
|
-
@collection_of_options.options_for_checks
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
def system_name
|
|
81
|
-
self.class.name.demodulize.downcase.to_sym
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
def i18n_name
|
|
85
|
-
system_name.to_s.pluralize
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
def input?
|
|
89
|
-
false
|
|
90
|
-
end
|
|
91
|
-
|
|
92
41
|
def internal?
|
|
93
42
|
true
|
|
94
43
|
end
|
|
95
44
|
|
|
96
|
-
def output?
|
|
97
|
-
false
|
|
98
|
-
end
|
|
99
|
-
|
|
100
45
|
private
|
|
101
46
|
|
|
102
|
-
def create_options_registrar(options:)
|
|
103
|
-
Servactory::Maintenance::Attributes::Options::Registrar.register(
|
|
104
|
-
attribute: self,
|
|
105
|
-
options:,
|
|
106
|
-
features: available_feature_options
|
|
107
|
-
)
|
|
108
|
-
end
|
|
109
|
-
|
|
110
47
|
def available_feature_options
|
|
111
48
|
{
|
|
112
49
|
types: true,
|
|
113
50
|
must: true
|
|
114
51
|
}
|
|
115
52
|
end
|
|
116
|
-
|
|
117
|
-
def augment_options_with_helpers(helpers:, options:)
|
|
118
|
-
result_options = options.dup
|
|
119
|
-
merge_standard_helpers_into(target_options: result_options, helpers:) if helpers.present?
|
|
120
|
-
merge_advanced_helpers_into(target_options: result_options, source_options: options)
|
|
121
|
-
result_options
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
def merge_standard_helpers_into(target_options:, helpers:)
|
|
125
|
-
standard_helpers_result = transform_helpers_to_options(helpers:)
|
|
126
|
-
target_options.deep_merge!(standard_helpers_result)
|
|
127
|
-
end
|
|
128
|
-
|
|
129
|
-
def merge_advanced_helpers_into(target_options:, source_options:)
|
|
130
|
-
advanced_helpers = filter_advanced_helpers(options: source_options)
|
|
131
|
-
return if advanced_helpers.blank?
|
|
132
|
-
|
|
133
|
-
advanced_helpers_result = transform_helpers_to_options(helpers: advanced_helpers)
|
|
134
|
-
target_options.deep_merge!(advanced_helpers_result)
|
|
135
|
-
end
|
|
136
|
-
|
|
137
|
-
def filter_advanced_helpers(options:)
|
|
138
|
-
reserved_options = Servactory::Maintenance::Attributes::Options::Registrar::RESERVED_OPTIONS
|
|
139
|
-
options.except(*reserved_options)
|
|
140
|
-
end
|
|
141
|
-
|
|
142
|
-
def transform_helpers_to_options(helpers:)
|
|
143
|
-
helpers.each_with_object({}) do |(helper_name, values), result|
|
|
144
|
-
helper = @option_helpers.find_by(name: helper_name)
|
|
145
|
-
next if helper.blank?
|
|
146
|
-
|
|
147
|
-
transformed_option = transform_helper_to_option(helper:, values:)
|
|
148
|
-
result.deep_merge!(transformed_option) if transformed_option.present?
|
|
149
|
-
end
|
|
150
|
-
end
|
|
151
|
-
|
|
152
|
-
def transform_helper_to_option(helper:, values:)
|
|
153
|
-
return helper.equivalent unless helper.equivalent.is_a?(Proc)
|
|
154
|
-
|
|
155
|
-
if values.is_a?(Hash)
|
|
156
|
-
helper.equivalent.call(**values)
|
|
157
|
-
else
|
|
158
|
-
helper.equivalent.call(values)
|
|
159
|
-
end
|
|
160
|
-
end
|
|
161
53
|
end
|
|
162
54
|
end
|
|
163
55
|
end
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Servactory
|
|
4
|
+
module Maintenance
|
|
5
|
+
module Attributes
|
|
6
|
+
class Base
|
|
7
|
+
attr_reader :name,
|
|
8
|
+
:collection_of_options,
|
|
9
|
+
:options
|
|
10
|
+
|
|
11
|
+
def initialize(
|
|
12
|
+
name,
|
|
13
|
+
*helpers,
|
|
14
|
+
option_helpers:,
|
|
15
|
+
**options
|
|
16
|
+
)
|
|
17
|
+
@name = name
|
|
18
|
+
@option_helpers = option_helpers
|
|
19
|
+
|
|
20
|
+
register_options(helpers:, options:)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def method_missing(name, *args, &block)
|
|
24
|
+
option = @collection_of_options.find_by(name:)
|
|
25
|
+
return super if option.nil?
|
|
26
|
+
|
|
27
|
+
option.body_for_access
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def respond_to_missing?(name, *)
|
|
31
|
+
@collection_of_options.names.include?(name) || super
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def register_options(helpers:, options:)
|
|
35
|
+
merged_options = augment_options_with_helpers(helpers:, options:)
|
|
36
|
+
options_registrar = create_options_registrar(options: merged_options)
|
|
37
|
+
|
|
38
|
+
@options = merged_options
|
|
39
|
+
@collection_of_options = options_registrar.collection
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def options_for_checks
|
|
43
|
+
@collection_of_options.options_for_checks
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def system_name
|
|
47
|
+
@system_name ||= self.class.name.demodulize.downcase.to_sym
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def i18n_name
|
|
51
|
+
@i18n_name ||= system_name.to_s.pluralize
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def actor
|
|
55
|
+
@actor ||= self.class::Actor.new(self)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def typed_actor_kwargs
|
|
59
|
+
@typed_actor_kwargs ||= { system_name => actor }.freeze
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def input?
|
|
63
|
+
false
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def internal?
|
|
67
|
+
false
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def output?
|
|
71
|
+
false
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
private
|
|
75
|
+
|
|
76
|
+
def create_options_registrar(options:)
|
|
77
|
+
Servactory::Maintenance::Options::Registrar.register(
|
|
78
|
+
attribute: self,
|
|
79
|
+
options:,
|
|
80
|
+
features: available_feature_options
|
|
81
|
+
)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def available_feature_options
|
|
85
|
+
raise NotImplementedError, "Subclass must implement available_feature_options"
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def augment_options_with_helpers(helpers:, options:)
|
|
89
|
+
result_options = options.dup
|
|
90
|
+
merge_standard_helpers_into(target_options: result_options, helpers:) if helpers.present?
|
|
91
|
+
merge_advanced_helpers_into(target_options: result_options, source_options: options)
|
|
92
|
+
result_options
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def merge_standard_helpers_into(target_options:, helpers:)
|
|
96
|
+
standard_helpers_result = transform_helpers_to_options(helpers:)
|
|
97
|
+
target_options.deep_merge!(standard_helpers_result)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def merge_advanced_helpers_into(target_options:, source_options:)
|
|
101
|
+
advanced_helpers = filter_advanced_helpers(options: source_options)
|
|
102
|
+
return if advanced_helpers.blank?
|
|
103
|
+
|
|
104
|
+
advanced_helpers_result = transform_helpers_to_options(helpers: advanced_helpers)
|
|
105
|
+
target_options.deep_merge!(advanced_helpers_result)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def filter_advanced_helpers(options:)
|
|
109
|
+
reserved_options = Servactory::Maintenance::Options::Registrar::RESERVED_OPTIONS
|
|
110
|
+
options.except(*reserved_options)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def transform_helpers_to_options(helpers:)
|
|
114
|
+
helpers.each_with_object({}) do |(helper_name, values), result|
|
|
115
|
+
helper = @option_helpers.find_by(name: helper_name)
|
|
116
|
+
next if helper.blank?
|
|
117
|
+
|
|
118
|
+
transformed_option = transform_helper_to_option(helper:, values:)
|
|
119
|
+
result.deep_merge!(transformed_option) if transformed_option.present?
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def transform_helper_to_option(helper:, values:)
|
|
124
|
+
return helper.equivalent unless helper.equivalent.is_a?(Proc)
|
|
125
|
+
|
|
126
|
+
if values.is_a?(Hash)
|
|
127
|
+
helper.equivalent.call(**values)
|
|
128
|
+
else
|
|
129
|
+
helper.equivalent.call(values)
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
@@ -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
|