treaty 0.0.1 → 0.2.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/README.md +106 -17
- data/Rakefile +4 -2
- data/config/locales/en.yml +96 -0
- data/lib/treaty/attribute/base.rb +174 -0
- data/lib/treaty/attribute/builder/base.rb +143 -0
- data/lib/treaty/attribute/collection.rb +65 -0
- data/lib/treaty/attribute/helper_mapper.rb +72 -0
- data/lib/treaty/attribute/option/base.rb +160 -0
- data/lib/treaty/attribute/option/modifiers/as_modifier.rb +88 -0
- data/lib/treaty/attribute/option/modifiers/default_modifier.rb +103 -0
- data/lib/treaty/attribute/option/registry.rb +128 -0
- data/lib/treaty/attribute/option/registry_initializer.rb +90 -0
- data/lib/treaty/attribute/option/validators/inclusion_validator.rb +80 -0
- data/lib/treaty/attribute/option/validators/required_validator.rb +92 -0
- data/lib/treaty/attribute/option/validators/type_validator.rb +159 -0
- data/lib/treaty/attribute/option_normalizer.rb +151 -0
- data/lib/treaty/attribute/option_orchestrator.rb +187 -0
- data/lib/treaty/attribute/validation/attribute_validator.rb +144 -0
- data/lib/treaty/attribute/validation/base.rb +92 -0
- data/lib/treaty/attribute/validation/nested_array_validator.rb +199 -0
- data/lib/treaty/attribute/validation/nested_object_validator.rb +103 -0
- data/lib/treaty/attribute/validation/nested_transformer.rb +246 -0
- data/lib/treaty/attribute/validation/orchestrator/base.rb +194 -0
- data/lib/treaty/base.rb +9 -0
- data/lib/treaty/configuration.rb +17 -0
- data/lib/treaty/context/callable.rb +24 -0
- data/lib/treaty/context/dsl.rb +12 -0
- data/lib/treaty/context/workspace.rb +28 -0
- data/lib/treaty/controller/dsl.rb +38 -0
- data/lib/treaty/engine.rb +37 -0
- data/lib/treaty/exceptions/base.rb +47 -0
- data/lib/treaty/exceptions/class_name.rb +50 -0
- data/lib/treaty/exceptions/deprecated.rb +54 -0
- data/lib/treaty/exceptions/execution.rb +66 -0
- data/lib/treaty/exceptions/method_name.rb +55 -0
- data/lib/treaty/exceptions/nested_attributes.rb +65 -0
- data/lib/treaty/exceptions/not_implemented.rb +32 -0
- data/lib/treaty/exceptions/strategy.rb +63 -0
- data/lib/treaty/exceptions/unexpected.rb +70 -0
- data/lib/treaty/exceptions/validation.rb +97 -0
- data/lib/treaty/info/builder.rb +122 -0
- data/lib/treaty/info/dsl.rb +26 -0
- data/lib/treaty/info/result.rb +13 -0
- data/lib/treaty/request/attribute/attribute.rb +24 -0
- data/lib/treaty/request/attribute/builder.rb +22 -0
- data/lib/treaty/request/attribute/validation/orchestrator.rb +27 -0
- data/lib/treaty/request/attribute/validator.rb +50 -0
- data/lib/treaty/request/factory.rb +32 -0
- data/lib/treaty/request/scope/collection.rb +21 -0
- data/lib/treaty/request/scope/factory.rb +42 -0
- data/lib/treaty/response/attribute/attribute.rb +24 -0
- data/lib/treaty/response/attribute/builder.rb +22 -0
- data/lib/treaty/response/attribute/validation/orchestrator.rb +27 -0
- data/lib/treaty/response/attribute/validator.rb +44 -0
- data/lib/treaty/response/factory.rb +38 -0
- data/lib/treaty/response/scope/collection.rb +21 -0
- data/lib/treaty/response/scope/factory.rb +42 -0
- data/lib/treaty/result.rb +22 -0
- data/lib/treaty/strategy.rb +31 -0
- data/lib/treaty/support/loader.rb +24 -0
- data/lib/treaty/version.rb +8 -1
- data/lib/treaty/versions/collection.rb +15 -0
- data/lib/treaty/versions/dsl.rb +30 -0
- data/lib/treaty/versions/execution/request.rb +147 -0
- data/lib/treaty/versions/executor.rb +14 -0
- data/lib/treaty/versions/factory.rb +92 -0
- data/lib/treaty/versions/resolver.rb +69 -0
- data/lib/treaty/versions/semantic.rb +22 -0
- data/lib/treaty/versions/workspace.rb +40 -0
- data/lib/treaty.rb +3 -3
- metadata +200 -27
- data/.standard.yml +0 -3
- data/CHANGELOG.md +0 -5
- data/CODE_OF_CONDUCT.md +0 -84
- data/LICENSE.txt +0 -21
- data/sig/treaty.rbs +0 -4
- data/treaty.gemspec +0 -35
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Treaty
|
|
4
|
+
module Attribute
|
|
5
|
+
# Maps DSL helper symbols to their simple mode option equivalents.
|
|
6
|
+
#
|
|
7
|
+
# ## Purpose
|
|
8
|
+
#
|
|
9
|
+
# Helpers provide the most concise syntax for common options.
|
|
10
|
+
# They are syntactic sugar that gets converted to simple mode options.
|
|
11
|
+
#
|
|
12
|
+
# ## Available Helpers
|
|
13
|
+
#
|
|
14
|
+
# - `:required` → `required: true`
|
|
15
|
+
# - `:optional` → `required: false`
|
|
16
|
+
#
|
|
17
|
+
# ## Usage Examples
|
|
18
|
+
#
|
|
19
|
+
# Helper mode (most concise):
|
|
20
|
+
# string :title, :required
|
|
21
|
+
# string :bio, :optional
|
|
22
|
+
#
|
|
23
|
+
# Equivalent to simple mode:
|
|
24
|
+
# string :title, required: true
|
|
25
|
+
# string :bio, required: false
|
|
26
|
+
#
|
|
27
|
+
# ## Processing Flow
|
|
28
|
+
#
|
|
29
|
+
# 1. Helper mode: `string :title, :required`
|
|
30
|
+
# 2. HelperMapper: `:required` → `required: true`
|
|
31
|
+
# 3. OptionNormalizer: `required: true` → `{ is: true, message: nil }`
|
|
32
|
+
# 4. Final: Advanced mode used internally
|
|
33
|
+
#
|
|
34
|
+
# ## Adding New Helpers
|
|
35
|
+
#
|
|
36
|
+
# To add a new helper:
|
|
37
|
+
# ```ruby
|
|
38
|
+
# HELPER_MAPPINGS = {
|
|
39
|
+
# required: { required: true },
|
|
40
|
+
# optional: { required: false },
|
|
41
|
+
# my_helper: { my_option: :smth } # New helper example
|
|
42
|
+
# }.freeze
|
|
43
|
+
# ```
|
|
44
|
+
class HelperMapper
|
|
45
|
+
HELPER_MAPPINGS = {
|
|
46
|
+
required: { required: true },
|
|
47
|
+
optional: { required: false }
|
|
48
|
+
}.freeze
|
|
49
|
+
|
|
50
|
+
class << self
|
|
51
|
+
# Maps helper symbols to their simple mode equivalents
|
|
52
|
+
#
|
|
53
|
+
# @param helpers [Array<Symbol>] Array of helper symbols
|
|
54
|
+
# @return [Hash] Simple mode options hash
|
|
55
|
+
def map(helpers)
|
|
56
|
+
helpers.each_with_object({}) do |helper, result|
|
|
57
|
+
mapping = HELPER_MAPPINGS.fetch(helper)
|
|
58
|
+
result.merge!(mapping) if mapping.present?
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Checks if a symbol is a registered helper
|
|
63
|
+
#
|
|
64
|
+
# @param symbol [Symbol] Symbol to check
|
|
65
|
+
# @return [Boolean] True if symbol is a helper
|
|
66
|
+
def helper?(symbol)
|
|
67
|
+
HELPER_MAPPINGS.key?(symbol)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Treaty
|
|
4
|
+
module Attribute
|
|
5
|
+
module Option
|
|
6
|
+
# Base class for all option processors (validators and modifiers).
|
|
7
|
+
#
|
|
8
|
+
# ## Option Modes
|
|
9
|
+
#
|
|
10
|
+
# Treaty supports two modes for defining options:
|
|
11
|
+
#
|
|
12
|
+
# 1. **Simple Mode** - Concise syntax for common cases:
|
|
13
|
+
# - `required: true`
|
|
14
|
+
# - `as: :value`
|
|
15
|
+
# - `default: 12`
|
|
16
|
+
# - `in: %w[twitter linkedin]`
|
|
17
|
+
#
|
|
18
|
+
# 2. **Advanced Mode** - Extended syntax with custom messages:
|
|
19
|
+
# - `required: { is: true, message: "Custom error" }`
|
|
20
|
+
# - `as: { is: :value, message: nil }`
|
|
21
|
+
# - `inclusion: { in: %w[...], message: "Must be one of..." }`
|
|
22
|
+
#
|
|
23
|
+
# ## Helpers
|
|
24
|
+
#
|
|
25
|
+
# Helpers are shortcuts in DSL that map to simple mode options:
|
|
26
|
+
# - `:required` → `required: true`
|
|
27
|
+
# - `:optional` → `required: false`
|
|
28
|
+
#
|
|
29
|
+
# ## Advanced Mode Keys
|
|
30
|
+
#
|
|
31
|
+
# Each option in advanced mode has a value key:
|
|
32
|
+
# - Default key: `:is` (used by most options)
|
|
33
|
+
# - Special key: `:in` (used by inclusion validator)
|
|
34
|
+
#
|
|
35
|
+
# The value key is defined by overriding `value_key` method in subclasses.
|
|
36
|
+
#
|
|
37
|
+
# ## Processing Phases
|
|
38
|
+
#
|
|
39
|
+
# Each option processor can participate in three phases:
|
|
40
|
+
# - Phase 1: Schema validation (validate DSL definition correctness)
|
|
41
|
+
# - Phase 2: Value validation (validate runtime data values)
|
|
42
|
+
# - Phase 3: Value transformation (transform values: defaults, renaming, etc.)
|
|
43
|
+
class Base
|
|
44
|
+
# Creates a new option processor instance
|
|
45
|
+
#
|
|
46
|
+
# @param attribute_name [Symbol] The name of the attribute
|
|
47
|
+
# @param attribute_type [Symbol] The type of the attribute
|
|
48
|
+
# @param option_schema [Object] The option schema (simple or advanced mode)
|
|
49
|
+
def initialize(attribute_name:, attribute_type:, option_schema:)
|
|
50
|
+
@attribute_name = attribute_name
|
|
51
|
+
@attribute_type = attribute_type
|
|
52
|
+
@option_schema = option_schema
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Phase 1: Validates schema (DSL definition)
|
|
56
|
+
# Override in subclasses if validation is needed
|
|
57
|
+
#
|
|
58
|
+
# @raise [Treaty::Exceptions::Validation] If schema is invalid
|
|
59
|
+
# @return [void]
|
|
60
|
+
def validate_schema!
|
|
61
|
+
# No-op by default
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Phase 2: Validates value (runtime data)
|
|
65
|
+
# Override in subclasses if validation is needed
|
|
66
|
+
#
|
|
67
|
+
# @param value [Object] The value to validate
|
|
68
|
+
# @raise [Treaty::Exceptions::Validation] If value is invalid
|
|
69
|
+
# @return [void]
|
|
70
|
+
def validate_value!(value)
|
|
71
|
+
# No-op by default
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Phase 3: Transforms value
|
|
75
|
+
# Returns transformed value or original if no transformation needed
|
|
76
|
+
# Override in subclasses if transformation is needed
|
|
77
|
+
#
|
|
78
|
+
# @param value [Object] The value to transform
|
|
79
|
+
# @return [Object] Transformed value
|
|
80
|
+
def transform_value(value)
|
|
81
|
+
value
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Indicates if this option processor transforms attribute names
|
|
85
|
+
# Override in subclasses if needed (e.g., AsModifier)
|
|
86
|
+
#
|
|
87
|
+
# @return [Boolean] True if this processor transforms names
|
|
88
|
+
def transforms_name?
|
|
89
|
+
false
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Returns the target name for the attribute if this processor transforms names
|
|
93
|
+
# Override in subclasses if needed (e.g., AsModifier)
|
|
94
|
+
#
|
|
95
|
+
# @return [Symbol] The target attribute name
|
|
96
|
+
def target_name
|
|
97
|
+
@attribute_name
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
protected
|
|
101
|
+
|
|
102
|
+
# Returns the value key for this option in advanced mode
|
|
103
|
+
# Default is :is, but can be overridden (e.g., :in for inclusion)
|
|
104
|
+
#
|
|
105
|
+
# @return [Symbol] The key used to store the value in advanced mode
|
|
106
|
+
def value_key
|
|
107
|
+
:is
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Checks if option is enabled
|
|
111
|
+
# Handles both simple mode (boolean) and advanced mode (hash with value key)
|
|
112
|
+
#
|
|
113
|
+
# @return [Boolean] Whether the option is enabled
|
|
114
|
+
def option_enabled?
|
|
115
|
+
return false if @option_schema.nil?
|
|
116
|
+
return @option_schema if @option_schema.is_a?(TrueClass) || @option_schema.is_a?(FalseClass)
|
|
117
|
+
|
|
118
|
+
@option_schema.fetch(value_key, false)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Extracts the actual value from normalized schema
|
|
122
|
+
# Works with both simple mode and advanced mode
|
|
123
|
+
#
|
|
124
|
+
# In simple mode: returns the value directly
|
|
125
|
+
# In advanced mode: extracts value using the appropriate key (is/in)
|
|
126
|
+
#
|
|
127
|
+
# @return [Object] The actual value from the option schema
|
|
128
|
+
def option_value
|
|
129
|
+
return @option_schema unless @option_schema.is_a?(Hash)
|
|
130
|
+
|
|
131
|
+
@option_schema.fetch(value_key, nil)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Gets custom error message from advanced mode schema
|
|
135
|
+
# Returns nil if no custom message, which triggers I18n default message
|
|
136
|
+
#
|
|
137
|
+
# @return [String, nil] Custom error message or nil for default message
|
|
138
|
+
def custom_message
|
|
139
|
+
return nil unless @option_schema.is_a?(Hash)
|
|
140
|
+
|
|
141
|
+
@option_schema.fetch(:message, nil)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Checks if schema is in advanced mode
|
|
145
|
+
#
|
|
146
|
+
# @return [Boolean] True if schema is in advanced mode (hash with value key)
|
|
147
|
+
def advanced_mode?
|
|
148
|
+
@option_schema.is_a?(Hash) && @option_schema.key?(value_key)
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# Checks if schema is in simple mode
|
|
152
|
+
#
|
|
153
|
+
# @return [Boolean] True if schema is in simple mode (not a hash or no value key)
|
|
154
|
+
def simple_mode?
|
|
155
|
+
!advanced_mode?
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Treaty
|
|
4
|
+
module Attribute
|
|
5
|
+
module Option
|
|
6
|
+
module Modifiers
|
|
7
|
+
# Transforms attribute names during data processing.
|
|
8
|
+
#
|
|
9
|
+
# ## Usage Examples
|
|
10
|
+
#
|
|
11
|
+
# Simple mode:
|
|
12
|
+
# # Request: expects "handle", outputs as "value"
|
|
13
|
+
# string :handle, as: :value
|
|
14
|
+
#
|
|
15
|
+
# Advanced mode:
|
|
16
|
+
# string :handle, as: { is: :value, message: nil }
|
|
17
|
+
#
|
|
18
|
+
# ## Use Cases
|
|
19
|
+
#
|
|
20
|
+
# 1. **Request to Service mapping**:
|
|
21
|
+
# ```ruby
|
|
22
|
+
# request do
|
|
23
|
+
# string :user_id, as: :id
|
|
24
|
+
# end
|
|
25
|
+
# # Input: { user_id: "123" }
|
|
26
|
+
# # Service receives: { id: "123" }
|
|
27
|
+
# ```
|
|
28
|
+
#
|
|
29
|
+
# 2. **Service to Response mapping**:
|
|
30
|
+
# ```ruby
|
|
31
|
+
# response 200 do
|
|
32
|
+
# string :id, as: :user_id
|
|
33
|
+
# end
|
|
34
|
+
# # Service returns: { id: "123" }
|
|
35
|
+
# # Output: { user_id: "123" }
|
|
36
|
+
# ```
|
|
37
|
+
#
|
|
38
|
+
# ## How It Works
|
|
39
|
+
#
|
|
40
|
+
# AsModifier doesn't transform values - it transforms attribute names.
|
|
41
|
+
# The orchestrator uses `target_name` to map source name to target name.
|
|
42
|
+
#
|
|
43
|
+
# ## Advanced Mode
|
|
44
|
+
#
|
|
45
|
+
# Schema format: `{ is: :symbol, message: nil }`
|
|
46
|
+
class AsModifier < Treaty::Attribute::Option::Base
|
|
47
|
+
# Validates that target name is a Symbol
|
|
48
|
+
#
|
|
49
|
+
# @raise [Treaty::Exceptions::Validation] If target is not a Symbol
|
|
50
|
+
# @return [void]
|
|
51
|
+
def validate_schema!
|
|
52
|
+
target = option_value
|
|
53
|
+
|
|
54
|
+
return if target.is_a?(Symbol)
|
|
55
|
+
|
|
56
|
+
raise Treaty::Exceptions::Validation,
|
|
57
|
+
I18n.t("treaty.attributes.modifiers.as.invalid_type",
|
|
58
|
+
attribute: @attribute_name,
|
|
59
|
+
type: target.class)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Indicates that AsModifier transforms attribute names
|
|
63
|
+
#
|
|
64
|
+
# @return [Boolean] Always returns true
|
|
65
|
+
def transforms_name?
|
|
66
|
+
true
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Returns the target name for the attribute
|
|
70
|
+
#
|
|
71
|
+
# @return [Symbol] The target attribute name
|
|
72
|
+
def target_name
|
|
73
|
+
option_value
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# AsModifier doesn't modify the value itself, only the name
|
|
77
|
+
# The renaming is handled by the orchestrator using target_name
|
|
78
|
+
#
|
|
79
|
+
# @param value [Object] The value to transform
|
|
80
|
+
# @return [Object] Unchanged value
|
|
81
|
+
def transform_value(value)
|
|
82
|
+
value
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Treaty
|
|
4
|
+
module Attribute
|
|
5
|
+
module Option
|
|
6
|
+
module Modifiers
|
|
7
|
+
# Sets default values for attributes when value is nil.
|
|
8
|
+
#
|
|
9
|
+
# ## Usage Examples
|
|
10
|
+
#
|
|
11
|
+
# Simple mode with static value:
|
|
12
|
+
# integer :limit, default: 12
|
|
13
|
+
# string :status, default: "pending"
|
|
14
|
+
# boolean :active, default: false
|
|
15
|
+
#
|
|
16
|
+
# Simple mode with dynamic value (Proc):
|
|
17
|
+
# datetime :created_at, default: -> { Time.current }
|
|
18
|
+
# string :uuid, default: -> { SecureRandom.uuid }
|
|
19
|
+
#
|
|
20
|
+
# Advanced mode:
|
|
21
|
+
# integer :limit, default: { is: 12, message: nil }
|
|
22
|
+
#
|
|
23
|
+
# ## Use Cases
|
|
24
|
+
#
|
|
25
|
+
# 1. **Response defaults** (most common):
|
|
26
|
+
# ```ruby
|
|
27
|
+
# response 200 do
|
|
28
|
+
# scope :meta do
|
|
29
|
+
# integer :limit, default: 12
|
|
30
|
+
# integer :page, default: 1
|
|
31
|
+
# end
|
|
32
|
+
# end
|
|
33
|
+
# # Service returns: { meta: { page: 1 } }
|
|
34
|
+
# # Output: { meta: { page: 1, limit: 12 } }
|
|
35
|
+
# ```
|
|
36
|
+
#
|
|
37
|
+
# 2. **Request defaults**:
|
|
38
|
+
# ```ruby
|
|
39
|
+
# request do
|
|
40
|
+
# string :format, default: "json"
|
|
41
|
+
# end
|
|
42
|
+
# # Input: {}
|
|
43
|
+
# # Service receives: { format: "json" }
|
|
44
|
+
# ```
|
|
45
|
+
#
|
|
46
|
+
# ## Important Notes
|
|
47
|
+
#
|
|
48
|
+
# - Default is applied ONLY when value is nil
|
|
49
|
+
# - Empty strings, empty arrays, false are NOT replaced
|
|
50
|
+
# - Proc defaults are called at transformation time
|
|
51
|
+
# - Procs receive no arguments
|
|
52
|
+
#
|
|
53
|
+
# ## Array and Object Types
|
|
54
|
+
#
|
|
55
|
+
# NOTE: DO NOT use `default: []` or `default: {}` for array/object types!
|
|
56
|
+
# Array and object types automatically represent empty collections.
|
|
57
|
+
#
|
|
58
|
+
# Incorrect:
|
|
59
|
+
# array :tags, default: [] # Wrong! Redundant
|
|
60
|
+
# object :meta, default: {} # Wrong! Redundant
|
|
61
|
+
#
|
|
62
|
+
# Correct:
|
|
63
|
+
# array :tags # Automatically handles empty array
|
|
64
|
+
# object :meta # Automatically handles empty object
|
|
65
|
+
#
|
|
66
|
+
# ## Advanced Mode
|
|
67
|
+
#
|
|
68
|
+
# Schema format: `{ is: value_or_proc, message: nil }`
|
|
69
|
+
class DefaultModifier < Treaty::Attribute::Option::Base
|
|
70
|
+
# Validates schema (no validation needed)
|
|
71
|
+
# Default value can be any type
|
|
72
|
+
#
|
|
73
|
+
# @return [void]
|
|
74
|
+
def validate_schema!
|
|
75
|
+
# Schema structure is already normalized by OptionNormalizer.
|
|
76
|
+
# Default value can be any type, so nothing specific to validate here.
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Applies default value if current value is nil
|
|
80
|
+
# Empty strings, empty arrays, and false are NOT replaced
|
|
81
|
+
#
|
|
82
|
+
# @param value [Object] The current value
|
|
83
|
+
# @param _context [Hash] Unused context parameter
|
|
84
|
+
# @return [Object] Default value if original is nil, otherwise original value
|
|
85
|
+
def transform_value(value, _context = {})
|
|
86
|
+
# Only apply default if value is nil
|
|
87
|
+
# Empty strings, empty arrays, false are NOT replaced
|
|
88
|
+
return value unless value.nil?
|
|
89
|
+
|
|
90
|
+
default_value = option_value
|
|
91
|
+
|
|
92
|
+
# If default value is a Proc, call it to get the value
|
|
93
|
+
if default_value.is_a?(Proc)
|
|
94
|
+
default_value.call
|
|
95
|
+
else
|
|
96
|
+
default_value
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Treaty
|
|
4
|
+
module Attribute
|
|
5
|
+
module Option
|
|
6
|
+
# Central registry for all option processors (validators and modifiers).
|
|
7
|
+
#
|
|
8
|
+
# ## Purpose
|
|
9
|
+
#
|
|
10
|
+
# Provides a centralized registry pattern for managing all option processors.
|
|
11
|
+
# Enables dynamic discovery and extensibility of the option system.
|
|
12
|
+
#
|
|
13
|
+
# ## Responsibilities
|
|
14
|
+
#
|
|
15
|
+
# 1. **Registration** - Stores option processor classes
|
|
16
|
+
# 2. **Retrieval** - Provides access to registered processors
|
|
17
|
+
# 3. **Categorization** - Organizes processors by category (validator/modifier)
|
|
18
|
+
# 4. **Validation** - Checks if options are registered
|
|
19
|
+
#
|
|
20
|
+
# ## Registered Options
|
|
21
|
+
#
|
|
22
|
+
# ### Validators
|
|
23
|
+
# - `:required` → RequiredValidator
|
|
24
|
+
# - `:type` → TypeValidator
|
|
25
|
+
# - `:inclusion` → InclusionValidator
|
|
26
|
+
#
|
|
27
|
+
# ### Modifiers
|
|
28
|
+
# - `:as` → AsModifier
|
|
29
|
+
# - `:default` → DefaultModifier
|
|
30
|
+
#
|
|
31
|
+
# ## Usage
|
|
32
|
+
#
|
|
33
|
+
# Registration (done in RegistryInitializer):
|
|
34
|
+
# Registry.register(:required, RequiredValidator, category: :validator)
|
|
35
|
+
#
|
|
36
|
+
# Retrieval (done in OptionOrchestrator):
|
|
37
|
+
# processor_class = Registry.processor_for(:required)
|
|
38
|
+
# processor = processor_class.new(...)
|
|
39
|
+
#
|
|
40
|
+
# ## Extensibility
|
|
41
|
+
#
|
|
42
|
+
# To add a new option:
|
|
43
|
+
# 1. Create processor class inheriting from Option::Base
|
|
44
|
+
# 2. Register it: `Registry.register(:my_option, MyProcessor, category: :validator)`
|
|
45
|
+
# 3. Option becomes available in DSL immediately
|
|
46
|
+
#
|
|
47
|
+
# ## Architecture
|
|
48
|
+
#
|
|
49
|
+
# Works with:
|
|
50
|
+
# - RegistryInitializer - Populates registry with built-in options
|
|
51
|
+
# - OptionOrchestrator - Uses registry to build processors
|
|
52
|
+
# - Option::Base - Base class for all registered processors
|
|
53
|
+
class Registry
|
|
54
|
+
class << self
|
|
55
|
+
# Register an option processor
|
|
56
|
+
#
|
|
57
|
+
# @param option_name [Symbol] The name of the option (e.g., :required, :as, :default)
|
|
58
|
+
# @param processor_class [Class] The processor class
|
|
59
|
+
# @param category [Symbol] The category (:validator or :modifier)
|
|
60
|
+
def register(option_name, processor_class, category:)
|
|
61
|
+
registry[option_name] = {
|
|
62
|
+
processor_class:,
|
|
63
|
+
category:
|
|
64
|
+
}
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Get processor class for an option
|
|
68
|
+
#
|
|
69
|
+
# @param option_name [Symbol] The name of the option
|
|
70
|
+
# @return [Class, nil] The processor class or nil if not found
|
|
71
|
+
def processor_for(option_name)
|
|
72
|
+
registry.dig(option_name, :processor_class)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Get category for an option
|
|
76
|
+
#
|
|
77
|
+
# @param option_name [Symbol] The name of the option
|
|
78
|
+
# @return [Symbol, nil] The category (:validator or :modifier) or nil if not found
|
|
79
|
+
def category_for(option_name)
|
|
80
|
+
registry.dig(option_name, :category)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Check if an option is registered
|
|
84
|
+
#
|
|
85
|
+
# @param option_name [Symbol] The name of the option
|
|
86
|
+
# @return [Boolean]
|
|
87
|
+
def registered?(option_name)
|
|
88
|
+
registry.key?(option_name)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Get all registered option names
|
|
92
|
+
#
|
|
93
|
+
# @return [Array<Symbol>]
|
|
94
|
+
def all_options
|
|
95
|
+
registry.keys
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Get all validators
|
|
99
|
+
#
|
|
100
|
+
# @return [Hash] Hash of option_name => processor_class for validators
|
|
101
|
+
def validators
|
|
102
|
+
registry.select { |_, info| info[:category] == :validator }
|
|
103
|
+
.transform_values { |info| info[:processor_class] }
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Get all modifiers
|
|
107
|
+
#
|
|
108
|
+
# @return [Hash] Hash of option_name => processor_class for modifiers
|
|
109
|
+
def modifiers
|
|
110
|
+
registry.select { |_, info| info[:category] == :modifier }
|
|
111
|
+
.transform_values { |info| info[:processor_class] }
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Reset registry (mainly for testing)
|
|
115
|
+
def reset!
|
|
116
|
+
@registry = nil
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
private
|
|
120
|
+
|
|
121
|
+
def registry
|
|
122
|
+
@registry ||= {}
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Treaty
|
|
4
|
+
module Attribute
|
|
5
|
+
module Option
|
|
6
|
+
# Initializes and registers all built-in option processors with the Registry.
|
|
7
|
+
#
|
|
8
|
+
# ## Purpose
|
|
9
|
+
#
|
|
10
|
+
# Centralized registration point for all option processors (validators and modifiers).
|
|
11
|
+
# Automatically registers all built-in options when loaded.
|
|
12
|
+
#
|
|
13
|
+
# ## Responsibilities
|
|
14
|
+
#
|
|
15
|
+
# 1. **Validator Registration** - Registers all built-in validators
|
|
16
|
+
# 2. **Modifier Registration** - Registers all built-in modifiers
|
|
17
|
+
# 3. **Auto-Loading** - Executes automatically when file is loaded
|
|
18
|
+
#
|
|
19
|
+
# ## Built-in Validators
|
|
20
|
+
#
|
|
21
|
+
# - `:required` → RequiredValidator - Validates required/optional attributes
|
|
22
|
+
# - `:type` → TypeValidator - Validates value types
|
|
23
|
+
# - `:inclusion` → InclusionValidator - Validates value is in allowed set
|
|
24
|
+
#
|
|
25
|
+
# ## Built-in Modifiers
|
|
26
|
+
#
|
|
27
|
+
# - `:as` → AsModifier - Renames attributes
|
|
28
|
+
# - `:default` → DefaultModifier - Provides default values
|
|
29
|
+
#
|
|
30
|
+
# ## Auto-Registration
|
|
31
|
+
#
|
|
32
|
+
# This file calls `register_all!` when loaded, ensuring all processors
|
|
33
|
+
# are available immediately.
|
|
34
|
+
#
|
|
35
|
+
# ## Adding New Options
|
|
36
|
+
#
|
|
37
|
+
# To add a new option processor:
|
|
38
|
+
#
|
|
39
|
+
# 1. Create the processor class (inherit from Option::Base)
|
|
40
|
+
# 2. Add registration call here:
|
|
41
|
+
# ```ruby
|
|
42
|
+
# def register_validators!
|
|
43
|
+
# Registry.register(:required, Validators::RequiredValidator, category: :validator)
|
|
44
|
+
# Registry.register(:my_option, Validators::MyValidator, category: :validator)
|
|
45
|
+
# end
|
|
46
|
+
# ```
|
|
47
|
+
#
|
|
48
|
+
# ## Architecture
|
|
49
|
+
#
|
|
50
|
+
# Works with:
|
|
51
|
+
# - Registry - Stores processor registrations
|
|
52
|
+
# - Option::Base - Base class for all processors
|
|
53
|
+
# - OptionOrchestrator - Uses registered processors
|
|
54
|
+
module RegistryInitializer
|
|
55
|
+
class << self
|
|
56
|
+
# Registers all built-in option processors
|
|
57
|
+
# Called automatically when this file is loaded
|
|
58
|
+
#
|
|
59
|
+
# @return [void]
|
|
60
|
+
def register_all!
|
|
61
|
+
register_validators!
|
|
62
|
+
register_modifiers!
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
private
|
|
66
|
+
|
|
67
|
+
# Registers all built-in validators
|
|
68
|
+
#
|
|
69
|
+
# @return [void]
|
|
70
|
+
def register_validators!
|
|
71
|
+
Registry.register(:required, Validators::RequiredValidator, category: :validator)
|
|
72
|
+
Registry.register(:type, Validators::TypeValidator, category: :validator)
|
|
73
|
+
Registry.register(:inclusion, Validators::InclusionValidator, category: :validator)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Registers all built-in modifiers
|
|
77
|
+
#
|
|
78
|
+
# @return [void]
|
|
79
|
+
def register_modifiers!
|
|
80
|
+
Registry.register(:as, Modifiers::AsModifier, category: :modifier)
|
|
81
|
+
Registry.register(:default, Modifiers::DefaultModifier, category: :modifier)
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Auto-register all options when this file is loaded
|
|
90
|
+
Treaty::Attribute::Option::RegistryInitializer.register_all!
|