structured_params 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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 9c06ea969ca1b6ba0b64271bc9b168519fecbb48d754c234489e9b509cd22578
4
+ data.tar.gz: 9f10004fd987e2e5af527857860d72d6d4ccaf19ec98dec0ac673c9db20ec473
5
+ SHA512:
6
+ metadata.gz: 6354a4d7f609f46093ed55f8cab4873d2f0f15370f0ad4a1e6719e7e5e02ff12a17ce0bd34f5b82bb1129ed6d933cfa51aa6de353b7fe7cf18882d857f3daa23
7
+ data.tar.gz: ff1fe2be0beb5024f7afa1e8f41a4b32dc55aeee061d633d40ca0052802129b507962d0a38bcad826d10da98ac7c64bd3b25edb7f6bc4c65a3117449fbc8aef9
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2024 Mizuki Yamamoto
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,154 @@
1
+ # rbs_inline: enabled
2
+ # frozen_string_literal: true
3
+
4
+ module StructuredParams
5
+ # Parameter model that supports nested structures
6
+ #
7
+ # Usage example:
8
+ # class UserParameter < StructuredParams::Params
9
+ # attribute :name, :string
10
+ # attribute :address, :nested, value_class: AddressParameter
11
+ # attribute :hobbies, :array, value_class: HobbyParameter
12
+ # attribute :tags, :array, value_type: :string
13
+ # end
14
+ class Params
15
+ include ActiveModel::Model
16
+ include ActiveModel::Attributes
17
+
18
+ class << self
19
+ # Generate permitted parameter structure for Strong Parameters
20
+ #: () -> Array[untyped]
21
+ def permit_attribute_names
22
+ attribute_types.map do |name, type|
23
+ name = name.to_sym
24
+
25
+ if type.is_a?(StructuredParams::Type::Object) || type.is_a?(StructuredParams::Type::Array)
26
+ { name => type.permit_attribute_names }
27
+ else
28
+ name
29
+ end
30
+ end
31
+ end
32
+
33
+ # Get names of nested StructuredParams attributes
34
+ #: () { (String) -> void } -> void
35
+ def each_nested_attribute_name
36
+ attribute_types.each do |name, type|
37
+ yield name if structured_params_type?(type)
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ # Determine if the specified type is a nested parameter type
44
+ #: (untyped) -> bool
45
+ def structured_params_type?(type)
46
+ type.is_a?(StructuredParams::Type::Object) ||
47
+ (type.is_a?(StructuredParams::Type::Array) && type.item_type_is_structured_params_object?)
48
+ end
49
+ end
50
+
51
+ # Integrate validation of nested objects
52
+ validate :validate_nested_parameters
53
+
54
+ #: (untyped) -> void
55
+ def initialize(params)
56
+ processed_params = process_input_parameters(params)
57
+ super(**processed_params)
58
+ end
59
+
60
+ # Convert nested objects to Hash and get attributes
61
+ #: (symbolize: bool) -> Hash[untyped, untyped]
62
+ def attributes(symbolize: false)
63
+ attrs = super()
64
+
65
+ self.class.each_nested_attribute_name do |name|
66
+ value = attrs[name.to_s]
67
+ attrs[name.to_s] = serialize_nested_value(value)
68
+ end
69
+
70
+ symbolize ? attrs.deep_symbolize_keys : attrs
71
+ end
72
+
73
+ private
74
+
75
+ # Process input parameters
76
+ #: (untyped) -> Hash[untyped, untyped]
77
+ def process_input_parameters(params)
78
+ case params
79
+ when ActionController::Parameters
80
+ params.permit(self.class.permit_attribute_names).to_h
81
+ when Hash
82
+ # ActiveModel::Attributes can handle both symbol and string keys
83
+ params
84
+ else
85
+ raise ArgumentError, "params must be ActionController::Parameters or Hash, got #{params.class}"
86
+ end
87
+ end
88
+
89
+ # Execute nested parameter validation
90
+ #: () -> void
91
+ def validate_nested_parameters
92
+ self.class.each_nested_attribute_name do |attr_name|
93
+ value = attribute(attr_name)
94
+ next if value.blank?
95
+
96
+ case value
97
+ when Array
98
+ validate_nested_array(attr_name, value)
99
+ else
100
+ validate_nested_object(attr_name, value)
101
+ end
102
+ end
103
+ end
104
+
105
+ # Validate nested arrays
106
+ #: (String, Array[untyped]) -> void
107
+ def validate_nested_array(attr_name, array_value)
108
+ array_value.each_with_index do |item, index|
109
+ next if item.valid?(validation_context)
110
+
111
+ error_path = format_error_path(attr_name, index)
112
+ import_nested_errors(item.errors, error_path)
113
+ end
114
+ end
115
+
116
+ # Validate nested objects
117
+ #: (String, StructuredParams::Params) -> void
118
+ def validate_nested_object(attr_name, object_value)
119
+ return if object_value.valid?(validation_context)
120
+
121
+ error_path = format_error_path(attr_name)
122
+ import_nested_errors(object_value.errors, error_path)
123
+ end
124
+
125
+ # Format error path using dot notation (always consistent)
126
+ #: (String, Integer?) -> String
127
+ def format_error_path(attr_name, index = nil)
128
+ path_parts = [attr_name]
129
+ path_parts << index.to_s if index
130
+ path_parts.join('.')
131
+ end
132
+
133
+ # Integrate nested errors into parent errors
134
+ #: (untyped, String) -> void
135
+ def import_nested_errors(nested_errors, prefix)
136
+ nested_errors.each do |error|
137
+ errors.import(error, attribute: "#{prefix}.#{error.attribute}")
138
+ end
139
+ end
140
+
141
+ # Serialize nested values
142
+ #: (untyped) -> untyped
143
+ def serialize_nested_value(value)
144
+ case value
145
+ when Array
146
+ value.map(&:attributes)
147
+ when StructuredParams::Params
148
+ value.attributes
149
+ else
150
+ value
151
+ end
152
+ end
153
+ end
154
+ end
@@ -0,0 +1,102 @@
1
+ # rbs_inline: enabled
2
+ # frozen_string_literal: true
3
+
4
+ module StructuredParams
5
+ module Type
6
+ # Custom type for arrays of both StructuredParams::Params objects and primitive types
7
+ #
8
+ # Usage examples:
9
+ # # Array of nested objects
10
+ # attribute :hobbies, :array, value_class: HobbyParameter
11
+ #
12
+ # # Array of primitive types
13
+ # attribute :tags, :array, value_type: :string
14
+ class Array < ActiveModel::Type::Value
15
+ attr_reader :item_type #: ActiveModel::Type::Value
16
+
17
+ # value_class or value_type is required
18
+ #: (?value_class: singleton(StructuredParams::Params)?, ?value_type: Symbol?, **untyped) -> void
19
+ def initialize(value_class: nil, value_type: nil, **options)
20
+ super()
21
+ validate_parameters!(value_class, value_type)
22
+ @item_type = build_item_type(value_class, value_type, options)
23
+ end
24
+
25
+ #: () -> Symbol
26
+ def type
27
+ :array
28
+ end
29
+
30
+ # Cast value to array and convert each element to appropriate type
31
+ #: (untyped) -> ::Array[untyped]?
32
+ def cast(value)
33
+ return nil if value.nil?
34
+
35
+ ensure_array(value).map { |item| cast_item(item) }
36
+ end
37
+
38
+ # Serialize array (convert each element to Hash)
39
+ #: (::Array[untyped]?) -> ::Array[untyped]?
40
+ def serialize(value)
41
+ return nil if value.nil?
42
+ return [] unless value.is_a?(::Array)
43
+
44
+ value.map { |item| @item_type.serialize(item) }
45
+ end
46
+
47
+ # Get permitted parameter names for use with Strong Parameters
48
+ #: () -> ::Array[untyped]
49
+ def permit_attribute_names
50
+ return [] unless item_type_is_structured_params_object?
51
+
52
+ @item_type.permit_attribute_names
53
+ end
54
+
55
+ # Determine if item type is StructuredParams::Object
56
+ #: () -> bool
57
+ def item_type_is_structured_params_object?
58
+ @item_type.is_a?(StructuredParams::Type::Object)
59
+ end
60
+
61
+ private
62
+
63
+ # Cast single item (delegate to new method)
64
+ #: (untyped) -> untyped
65
+ def cast_item(item)
66
+ if item_type_is_structured_params_object?
67
+ @item_type.value_class.new(item)
68
+ else
69
+ @item_type.cast(item)
70
+ end
71
+ end
72
+
73
+ # Parameter validation
74
+ #: (singleton(StructuredParams::Params)?, Symbol?) -> void
75
+ def validate_parameters!(value_class, value_type)
76
+ if value_class && value_type
77
+ raise ArgumentError, 'Specify either value_class or value_type, not both'
78
+ elsif !value_class && !value_type
79
+ raise ArgumentError, 'Either value_class or value_type must be specified'
80
+ elsif value_class && !(value_class <= StructuredParams::Params)
81
+ raise ArgumentError, "value_class must inherit from StructuredParams::Params, got #{value_class}"
82
+ end
83
+ end
84
+
85
+ # Build item type
86
+ #: (singleton(StructuredParams::Params)?, Symbol?, Hash[untyped, untyped]) -> ActiveModel::Type::Value
87
+ def build_item_type(value_class, value_type, options)
88
+ if value_class
89
+ StructuredParams::Type::Object.new(value_class: value_class)
90
+ else
91
+ ActiveModel::Type.lookup(value_type, **options)
92
+ end
93
+ end
94
+
95
+ # Convert value to array
96
+ #: (untyped) -> ::Array[untyped]
97
+ def ensure_array(value)
98
+ value.is_a?(::Array) ? value : [value]
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,58 @@
1
+ # rbs_inline: enabled
2
+ # frozen_string_literal: true
3
+
4
+ module StructuredParams
5
+ module Type
6
+ # Custom type for single StructuredParams::Params objects
7
+ class Object < ActiveModel::Type::Value
8
+ attr_reader :value_class #: singleton(StructuredParams::Params)
9
+
10
+ # Get permitted parameter names for use with Strong Parameters
11
+ # @rbs!
12
+ # def permit_attribute_names: () -> ::Array[untyped]
13
+ delegate :permit_attribute_names, to: :value_class
14
+
15
+ #: (value_class: singleton(StructuredParams::Params), **untyped) -> void
16
+ def initialize(value_class:, **_options)
17
+ super()
18
+ validate_value_class!(value_class)
19
+ @value_class = value_class
20
+ end
21
+
22
+ #: () -> Symbol
23
+ def type
24
+ :object
25
+ end
26
+
27
+ # Cast value to StructuredParams::Params instance
28
+ #: (untyped) -> StructuredParams::Params?
29
+ def cast(value)
30
+ return nil if value.nil?
31
+ return value if value.is_a?(@value_class)
32
+
33
+ @value_class.new(value) # call new directly instead of cast_value method
34
+ end
35
+
36
+ # Serialize (convert to Hash) the object
37
+ #: (untyped) -> untyped
38
+ def serialize(value)
39
+ return nil if value.nil?
40
+
41
+ value.is_a?(@value_class) ? value.attributes : value
42
+ end
43
+
44
+ private
45
+
46
+ # Validate the class
47
+ #: (untyped) -> void
48
+ def validate_value_class!(value_class)
49
+ raise ArgumentError, 'value_class must inherit from StructuredParams::Params, got NilClass' if value_class.nil?
50
+ raise ArgumentError, "value_class must be a Class, got #{value_class.class}" unless value_class.is_a?(Class)
51
+
52
+ return if value_class < StructuredParams::Params
53
+
54
+ raise ArgumentError, "value_class must inherit from StructuredParams::Params, got #{value_class}"
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,6 @@
1
+ # rbs_inline: enabled
2
+ # frozen_string_literal: true
3
+
4
+ module StructuredParams
5
+ VERSION = '0.1.0' #: string
6
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_model'
4
+ require 'active_model/type'
5
+ require 'action_controller/metal/strong_parameters'
6
+
7
+ # version
8
+ require_relative 'structured_params/version'
9
+
10
+ # types (load first for module definition)
11
+ require_relative 'structured_params/type/object'
12
+ require_relative 'structured_params/type/array'
13
+
14
+ # params (load after type definitions)
15
+ require_relative 'structured_params/params'
16
+
17
+ # Main module
18
+ module StructuredParams
19
+ # Helper method to register types
20
+ def self.register_types
21
+ ActiveModel::Type.register(:object, StructuredParams::Type::Object)
22
+ ActiveModel::Type.register(:array, StructuredParams::Type::Array)
23
+ end
24
+
25
+ # Helper method to register types with custom names
26
+ def self.register_types_as(object_name: :object, array_name: :array)
27
+ ActiveModel::Type.register(object_name, StructuredParams::Type::Object)
28
+ ActiveModel::Type.register(array_name, StructuredParams::Type::Array)
29
+ end
30
+ end
@@ -0,0 +1,67 @@
1
+ # Generated from lib/structured_params/params.rb with RBS::Inline
2
+
3
+ module StructuredParams
4
+ # Parameter model that supports nested structures
5
+ #
6
+ # Usage example:
7
+ # class UserParameter < StructuredParams::Params
8
+ # attribute :name, :string
9
+ # attribute :address, :nested, value_class: AddressParameter
10
+ # attribute :hobbies, :array, value_class: HobbyParameter
11
+ # attribute :tags, :array, value_type: :string
12
+ # end
13
+ class Params
14
+ include ActiveModel::Model
15
+
16
+ include ActiveModel::Attributes
17
+
18
+ # Generate permitted parameter structure for Strong Parameters
19
+ # : () -> Array[untyped]
20
+ def self.permit_attribute_names: () -> Array[untyped]
21
+
22
+ # Get names of nested StructuredParams attributes
23
+ # : () { (String) -> void } -> void
24
+ def self.each_nested_attribute_name: () { (String) -> void } -> void
25
+
26
+ # Determine if the specified type is a nested parameter type
27
+ # : (untyped) -> bool
28
+ private def self.structured_params_type?: (untyped) -> bool
29
+
30
+ # : (untyped) -> void
31
+ def initialize: (untyped) -> void
32
+
33
+ # Convert nested objects to Hash and get attributes
34
+ # : (symbolize: bool) -> Hash[untyped, untyped]
35
+ def attributes: (symbolize: bool) -> Hash[untyped, untyped]
36
+
37
+ private
38
+
39
+ # Process input parameters
40
+ # : (untyped) -> Hash[untyped, untyped]
41
+ def process_input_parameters: (untyped) -> Hash[untyped, untyped]
42
+
43
+ # Execute nested parameter validation
44
+ # : () -> void
45
+ def validate_nested_parameters: () -> void
46
+
47
+ # Validate nested arrays
48
+ # : (String, Array[untyped]) -> void
49
+ def validate_nested_array: (String, Array[untyped]) -> void
50
+
51
+ # Validate nested objects
52
+ # : (String, StructuredParams::Params) -> void
53
+ def validate_nested_object: (String, StructuredParams::Params) -> void
54
+
55
+ # Format error path using dot notation (always consistent)
56
+ # : (String, Integer?) -> String
57
+ def format_error_path: (String, Integer?) -> String
58
+
59
+ # Integrate nested errors into parent errors
60
+ # : (untyped, String) -> void
61
+ def import_nested_errors: (untyped, String) -> void
62
+
63
+ # Serialize nested values
64
+ # : (untyped) -> untyped
65
+ def serialize_nested_value: (untyped) -> untyped
66
+ end
67
+ end
@@ -0,0 +1,58 @@
1
+ # Generated from lib/structured_params/type/array.rb with RBS::Inline
2
+
3
+ module StructuredParams
4
+ module Type
5
+ # Custom type for arrays of both StructuredParams::Params objects and primitive types
6
+ #
7
+ # Usage examples:
8
+ # # Array of nested objects
9
+ # attribute :hobbies, :array, value_class: HobbyParameter
10
+ #
11
+ # # Array of primitive types
12
+ # attribute :tags, :array, value_type: :string
13
+ class Array < ActiveModel::Type::Value
14
+ attr_reader item_type: ActiveModel::Type::Value
15
+
16
+ # value_class or value_type is required
17
+ # : (?value_class: singleton(StructuredParams::Params)?, ?value_type: Symbol?, **untyped) -> void
18
+ def initialize: (?value_class: singleton(StructuredParams::Params)?, ?value_type: Symbol?, **untyped) -> void
19
+
20
+ # : () -> Symbol
21
+ def type: () -> Symbol
22
+
23
+ # Cast value to array and convert each element to appropriate type
24
+ # : (untyped) -> ::Array[untyped]?
25
+ def cast: (untyped) -> ::Array[untyped]?
26
+
27
+ # Serialize array (convert each element to Hash)
28
+ # : (::Array[untyped]?) -> ::Array[untyped]?
29
+ def serialize: (::Array[untyped]?) -> ::Array[untyped]?
30
+
31
+ # Get permitted parameter names for use with Strong Parameters
32
+ # : () -> ::Array[untyped]
33
+ def permit_attribute_names: () -> ::Array[untyped]
34
+
35
+ # Determine if item type is StructuredParams::Object
36
+ # : () -> bool
37
+ def item_type_is_structured_params_object?: () -> bool
38
+
39
+ private
40
+
41
+ # Cast single item (delegate to new method)
42
+ # : (untyped) -> untyped
43
+ def cast_item: (untyped) -> untyped
44
+
45
+ # Parameter validation
46
+ # : (singleton(StructuredParams::Params)?, Symbol?) -> void
47
+ def validate_parameters!: (singleton(StructuredParams::Params)?, Symbol?) -> void
48
+
49
+ # Build item type
50
+ # : (singleton(StructuredParams::Params)?, Symbol?, Hash[untyped, untyped]) -> ActiveModel::Type::Value
51
+ def build_item_type: (singleton(StructuredParams::Params)?, Symbol?, Hash[untyped, untyped]) -> ActiveModel::Type::Value
52
+
53
+ # Convert value to array
54
+ # : (untyped) -> ::Array[untyped]
55
+ def ensure_array: (untyped) -> ::Array[untyped]
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,32 @@
1
+ # Generated from lib/structured_params/type/object.rb with RBS::Inline
2
+
3
+ module StructuredParams
4
+ module Type
5
+ # Custom type for single StructuredParams::Params objects
6
+ class Object < ActiveModel::Type::Value
7
+ attr_reader value_class: singleton(StructuredParams::Params)
8
+
9
+ def permit_attribute_names: () -> ::Array[untyped]
10
+
11
+ # : (value_class: singleton(StructuredParams::Params), **untyped) -> void
12
+ def initialize: (value_class: singleton(StructuredParams::Params), **untyped) -> void
13
+
14
+ # : () -> Symbol
15
+ def type: () -> Symbol
16
+
17
+ # Cast value to StructuredParams::Params instance
18
+ # : (untyped) -> StructuredParams::Params?
19
+ def cast: (untyped) -> StructuredParams::Params?
20
+
21
+ # Serialize (convert to Hash) the object
22
+ # : (untyped) -> untyped
23
+ def serialize: (untyped) -> untyped
24
+
25
+ private
26
+
27
+ # Validate the class
28
+ # : (untyped) -> void
29
+ def validate_value_class!: (untyped) -> void
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,5 @@
1
+ # Generated from lib/structured_params/version.rb with RBS::Inline
2
+
3
+ module StructuredParams
4
+ VERSION: string
5
+ end
metadata ADDED
@@ -0,0 +1,83 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: structured_params
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Mizuki Yamamoto
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: actionpack
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: 8.0.0
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: 8.0.0
26
+ - !ruby/object:Gem::Dependency
27
+ name: activemodel
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: 8.0.0
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: 8.0.0
40
+ description: ''
41
+ email:
42
+ - mizuki-y@syati.info
43
+ executables: []
44
+ extensions: []
45
+ extra_rdoc_files: []
46
+ files:
47
+ - LICENSE.txt
48
+ - lib/structured_params.rb
49
+ - lib/structured_params/params.rb
50
+ - lib/structured_params/type/array.rb
51
+ - lib/structured_params/type/object.rb
52
+ - lib/structured_params/version.rb
53
+ - sig/structured_params/params.rbs
54
+ - sig/structured_params/type/array.rbs
55
+ - sig/structured_params/type/object.rbs
56
+ - sig/structured_params/version.rbs
57
+ homepage: https://github.com/Syati/structured_params
58
+ licenses:
59
+ - MIT
60
+ metadata:
61
+ homepage_uri: https://github.com/Syati/structured_params#readme
62
+ changelog_uri: https://github.com/Syati/structured_params/blob/main/CHANGELOG.md
63
+ bug_tracker_uri: https://github.com/Syati/structured_params/issues
64
+ source_code_uri: https://github.com/Syati/structured_params
65
+ rubygems_mfa_required: 'true'
66
+ rdoc_options: []
67
+ require_paths:
68
+ - lib
69
+ required_ruby_version: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: 3.0.0
74
+ required_rubygems_version: !ruby/object:Gem::Requirement
75
+ requirements:
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ version: '0'
79
+ requirements: []
80
+ rubygems_version: 3.6.9
81
+ specification_version: 4
82
+ summary: StructuredParams allows you to validate pass parameter.
83
+ test_files: []