treaty 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +19 -18
- data/Rakefile +4 -2
- data/lib/treaty/attribute/base.rb +172 -0
- data/lib/treaty/attribute/builder/base.rb +142 -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 +159 -0
- data/lib/treaty/attribute/option/modifiers/as_modifier.rb +87 -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 +94 -0
- data/lib/treaty/attribute/option/validators/type_validator.rb +153 -0
- data/lib/treaty/attribute/option_normalizer.rb +150 -0
- data/lib/treaty/attribute/option_orchestrator.rb +186 -0
- data/lib/treaty/attribute/validation/attribute_validator.rb +144 -0
- data/lib/treaty/attribute/validation/base.rb +93 -0
- data/lib/treaty/attribute/validation/nested_array_validator.rb +194 -0
- data/lib/treaty/attribute/validation/nested_object_validator.rb +103 -0
- data/lib/treaty/attribute/validation/nested_transformer.rb +240 -0
- data/lib/treaty/attribute/validation/orchestrator/base.rb +196 -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 +8 -0
- data/lib/treaty/exceptions/class_name.rb +11 -0
- data/lib/treaty/exceptions/deprecated.rb +8 -0
- data/lib/treaty/exceptions/execution.rb +8 -0
- data/lib/treaty/exceptions/method_name.rb +8 -0
- data/lib/treaty/exceptions/nested_attributes.rb +8 -0
- data/lib/treaty/exceptions/strategy.rb +8 -0
- data/lib/treaty/exceptions/unexpected.rb +8 -0
- data/lib/treaty/exceptions/validation.rb +8 -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 +151 -0
- data/lib/treaty/versions/executor.rb +14 -0
- data/lib/treaty/versions/factory.rb +93 -0
- data/lib/treaty/versions/resolver.rb +72 -0
- data/lib/treaty/versions/semantic.rb +22 -0
- data/lib/treaty/versions/workspace.rb +40 -0
- data/lib/treaty.rb +3 -3
- metadata +184 -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,87 @@
|
|
|
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
|
+
# TODO: It is necessary to implement a translation system (I18n).
|
|
57
|
+
raise Treaty::Exceptions::Validation,
|
|
58
|
+
"Option 'as' for attribute '#{@attribute_name}' must be a Symbol. Got: #{target.class}"
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Indicates that AsModifier transforms attribute names
|
|
62
|
+
#
|
|
63
|
+
# @return [Boolean] Always returns true
|
|
64
|
+
def transforms_name?
|
|
65
|
+
true
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Returns the target name for the attribute
|
|
69
|
+
#
|
|
70
|
+
# @return [Symbol] The target attribute name
|
|
71
|
+
def target_name
|
|
72
|
+
option_value
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# AsModifier doesn't modify the value itself, only the name
|
|
76
|
+
# The renaming is handled by the orchestrator using target_name
|
|
77
|
+
#
|
|
78
|
+
# @param value [Object] The value to transform
|
|
79
|
+
# @return [Object] Unchanged value
|
|
80
|
+
def transform_value(value)
|
|
81
|
+
value
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
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!
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Treaty
|
|
4
|
+
module Attribute
|
|
5
|
+
module Option
|
|
6
|
+
module Validators
|
|
7
|
+
# Validates that attribute value is included in allowed set.
|
|
8
|
+
#
|
|
9
|
+
# ## Usage Examples
|
|
10
|
+
#
|
|
11
|
+
# Simple mode:
|
|
12
|
+
# string :provider, in: %w[twitter linkedin github]
|
|
13
|
+
#
|
|
14
|
+
# Advanced mode:
|
|
15
|
+
# string :provider, inclusion: { in: %w[twitter linkedin github], message: "Invalid provider" }
|
|
16
|
+
#
|
|
17
|
+
# ## Advanced Mode
|
|
18
|
+
#
|
|
19
|
+
# Uses `:in` as the value key (instead of default `:is`).
|
|
20
|
+
# Schema format: `{ in: [...], message: nil }`
|
|
21
|
+
class InclusionValidator < Treaty::Attribute::Option::Base
|
|
22
|
+
# Validates that allowed values are provided as non-empty array
|
|
23
|
+
#
|
|
24
|
+
# @raise [Treaty::Exceptions::Validation] If allowed values are not valid
|
|
25
|
+
# @return [void]
|
|
26
|
+
def validate_schema!
|
|
27
|
+
allowed_values = option_value
|
|
28
|
+
|
|
29
|
+
return if allowed_values.is_a?(Array) && !allowed_values.empty?
|
|
30
|
+
|
|
31
|
+
# TODO: It is necessary to implement a translation system (I18n).
|
|
32
|
+
raise Treaty::Exceptions::Validation,
|
|
33
|
+
"Option 'inclusion' for attribute '#{@attribute_name}' must have a non-empty array of allowed values"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Validates that value is included in allowed set
|
|
37
|
+
# Skips validation for nil values (handled by RequiredValidator)
|
|
38
|
+
#
|
|
39
|
+
# @param value [Object] The value to validate
|
|
40
|
+
# @raise [Treaty::Exceptions::Validation] If value is not in allowed set
|
|
41
|
+
# @return [void]
|
|
42
|
+
def validate_value!(value)
|
|
43
|
+
return if value.nil? # Inclusion validation doesn't check for nil, required does.
|
|
44
|
+
|
|
45
|
+
allowed_values = option_value
|
|
46
|
+
|
|
47
|
+
return if allowed_values.include?(value)
|
|
48
|
+
|
|
49
|
+
message = custom_message || default_message(allowed_values, value)
|
|
50
|
+
|
|
51
|
+
# TODO: It is necessary to implement a translation system (I18n).
|
|
52
|
+
raise Treaty::Exceptions::Validation, message
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
protected
|
|
56
|
+
|
|
57
|
+
# Returns the value key for inclusion validator
|
|
58
|
+
# Uses :in instead of default :is
|
|
59
|
+
#
|
|
60
|
+
# @return [Symbol] The value key (:in)
|
|
61
|
+
def value_key
|
|
62
|
+
:in
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
private
|
|
66
|
+
|
|
67
|
+
# Generates default error message with allowed values
|
|
68
|
+
#
|
|
69
|
+
# @param allowed_values [Array] Array of allowed values
|
|
70
|
+
# @param value [Object] The actual value that failed validation
|
|
71
|
+
# @return [String] Default error message
|
|
72
|
+
def default_message(allowed_values, value)
|
|
73
|
+
# TODO: It is necessary to implement a translation system (I18n).
|
|
74
|
+
"Attribute '#{@attribute_name}' must be one of: #{allowed_values.join(', ')}. Got: '#{value}'"
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Treaty
|
|
4
|
+
module Attribute
|
|
5
|
+
module Option
|
|
6
|
+
module Validators
|
|
7
|
+
# Validates that attribute value is present (not nil and not empty).
|
|
8
|
+
#
|
|
9
|
+
# ## Usage Examples
|
|
10
|
+
#
|
|
11
|
+
# Helper mode:
|
|
12
|
+
# string :title, :required # Maps to required: true
|
|
13
|
+
# string :bio, :optional # Maps to required: false
|
|
14
|
+
#
|
|
15
|
+
# Simple mode:
|
|
16
|
+
# string :title, required: true
|
|
17
|
+
# string :bio, required: false
|
|
18
|
+
#
|
|
19
|
+
# Advanced mode:
|
|
20
|
+
# string :title, required: { is: true, message: "Title is mandatory" }
|
|
21
|
+
#
|
|
22
|
+
# ## Default Behavior
|
|
23
|
+
#
|
|
24
|
+
# - Request attributes: required by default (required: true)
|
|
25
|
+
# - Response attributes: optional by default (required: false)
|
|
26
|
+
#
|
|
27
|
+
# ## Validation Rules
|
|
28
|
+
#
|
|
29
|
+
# A value is considered present if:
|
|
30
|
+
# - It is not nil
|
|
31
|
+
# - It is not empty (for String, Array, Hash)
|
|
32
|
+
#
|
|
33
|
+
# ## Advanced Mode
|
|
34
|
+
#
|
|
35
|
+
# Schema format: `{ is: true/false, message: nil }`
|
|
36
|
+
class RequiredValidator < Treaty::Attribute::Option::Base
|
|
37
|
+
# Validates schema (no validation needed, already normalized)
|
|
38
|
+
#
|
|
39
|
+
# @return [void]
|
|
40
|
+
def validate_schema!
|
|
41
|
+
# Schema structure is already normalized by OptionNormalizer.
|
|
42
|
+
# Nothing to validate here.
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Validates that required attribute has a present value
|
|
46
|
+
#
|
|
47
|
+
# @param value [Object] The value to validate
|
|
48
|
+
# @raise [Treaty::Exceptions::Validation] If required but value is missing/empty
|
|
49
|
+
# @return [void]
|
|
50
|
+
def validate_value!(value)
|
|
51
|
+
return unless required?
|
|
52
|
+
return if present?(value)
|
|
53
|
+
|
|
54
|
+
message = custom_message || default_message
|
|
55
|
+
|
|
56
|
+
# TODO: It is necessary to implement a translation system (I18n).
|
|
57
|
+
raise Treaty::Exceptions::Validation, message
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
private
|
|
61
|
+
|
|
62
|
+
# Checks if attribute is required
|
|
63
|
+
#
|
|
64
|
+
# @return [Boolean] True if attribute is required
|
|
65
|
+
def required?
|
|
66
|
+
return false if @option_schema.nil?
|
|
67
|
+
|
|
68
|
+
# Use option_value helper which correctly extracts value based on mode
|
|
69
|
+
option_value == true
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Checks if value is present (not nil and not empty)
|
|
73
|
+
#
|
|
74
|
+
# @param value [Object] The value to check
|
|
75
|
+
# @return [Boolean] True if value is present
|
|
76
|
+
def present?(value)
|
|
77
|
+
return false if value.nil?
|
|
78
|
+
return false if value.respond_to?(:empty?) && value.empty?
|
|
79
|
+
|
|
80
|
+
true
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Generates default error message
|
|
84
|
+
#
|
|
85
|
+
# @return [String] Default error message
|
|
86
|
+
def default_message
|
|
87
|
+
# TODO: It is necessary to implement a translation system (I18n).
|
|
88
|
+
"Attribute '#{@attribute_name}' is required but was not provided or is empty"
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|