param_param 0.0.2 → 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.
Files changed (5) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +79 -1
  3. data/lib/param_param/std.rb +231 -152
  4. data/lib/param_param.rb +65 -141
  5. metadata +4 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 61d152a83cea632bd4e660508c5dc62d26231e9bb60a35626e1991548171e205
4
- data.tar.gz: 28d02503d7fc9a374d1e555c3e9328aac9418194ed6520989165b66fe5d4a3cd
3
+ metadata.gz: 73a3ff3dffbee8500b224cef365440d71cb01b5f8e8a8c8f0e7995e894892493
4
+ data.tar.gz: c958be904a5b0794c93dbae002da9a33c10a809d168d4234beb0b184e7d2cd8f
5
5
  SHA512:
6
- metadata.gz: bdef7f585fc8f1050327bcdbe0946cc3255eca321c2f43b17fd42035cdc4d029f5d85011f91e01e2813af50b9e47ae70593f676078e483611efeed2e2a3f583b
7
- data.tar.gz: '004692833ec9cefe0d4ebdc6ec02ffa1c4de71ef38995e03aa62eecf2cbe4e8fa839f75a9119ca4adcab5576742909d69955f332f5e833c01965544fdd54c504'
6
+ metadata.gz: da3253615f397f6bd04f080e9c9e32178bbfa1e59e193287c27ab7a81198194aece453a31fb71f4ef402d6b589da653436d79341a7d221eda838265f65ed7a28
7
+ data.tar.gz: 51db488247772ae2db84f6ec5883721c9b9c7108a5596f98c98b197028bf0580cba5e1f0110ad062f035ad8802bf947ded9f47afe692df2f06b8413bc28b83df
data/README.md CHANGED
@@ -1,4 +1,82 @@
1
1
  # param_param
2
- Params parser built on lambdas.
2
+ Lambda powered pipelines.
3
+ Define pipelines transforming hash values.
3
4
 
4
5
  Inspired by Martin Chabot's [Simple Functional Strong Parameters In Ruby](https://blog.martinosis.com/blog/simple-functional-strong-params-in-ruby) article.
6
+
7
+ # Examples
8
+ ## Validate and transform a user provided data in a web application.
9
+
10
+ ```
11
+ require 'param_param'
12
+ require 'param_param/std'
13
+
14
+ class UserParams
15
+ include ParamParam
16
+ include ParamParam::Std
17
+
18
+ # You can add your own actions
19
+ capitalized = ->(option) { Success.new(Optiomist.some(option.value.capitalize)) }
20
+
21
+ RULES = define.(
22
+ name: required.(string.(all_of.([not_blank, max_size.(50), capitalized]))),
23
+ admin: required.(bool.(any)),
24
+ age: optional.(integer.(gt.(0))),
25
+ )
26
+
27
+ def process(params)
28
+ RULES.(params)
29
+ end
30
+ end
31
+
32
+ params, errors = UserParams.new.process(
33
+ name: 'JOHN',
34
+ admin: '0',
35
+ age: '30',
36
+ race: 'It is not important',
37
+ )
38
+ params # {:name=>"John", :admin=>false, :age=>30}
39
+ errors # {}
40
+
41
+ params, errors = UserParams.new.process(admin: 'no', age: 'very old')
42
+ params # {:admin=>false}
43
+ errors # {:name=>:missing, :age=>:non_integer}
44
+ ```
45
+
46
+ ## Perform some chain of operations on provided data.
47
+ ```
48
+ require 'param_param'
49
+
50
+ module Mather
51
+ include ParamParam
52
+
53
+ def self.add
54
+ ->(value, option) { Success.new(Optiomist.some(option.value + value)) }.curry
55
+ end
56
+
57
+ def self.mul
58
+ ->(value, option) { Success.new(Optiomist.some(option.value * value)) }.curry
59
+ end
60
+
61
+ def self.sub
62
+ ->(value, option) { Success.new(Optiomist.some(option.value - value)) }.curry
63
+ end
64
+ end
65
+
66
+ rules = Mather.define.(
67
+ a: Mather.add.(5),
68
+ b: Mather.mul.(3),
69
+ c: Mather.sub.(1),
70
+ d: Mather.all_of.([Mather.add.(2), Mather.mul.(2), Mather.sub.(2)]),
71
+ )
72
+
73
+ params, _ = rules.(
74
+ a: 0,
75
+ b: 1,
76
+ c: 2,
77
+ d: 3,
78
+ )
79
+
80
+ params # {:a=>5, :b=>3, :c=>1, :d=>8}
81
+
82
+ ```
@@ -1,168 +1,247 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # It contains a collection of some useful rules.
4
3
  module ParamParam
5
- # Some string values that can be considered as +true+ (thank you dry-rb for inspiration).
6
- TRUE_VALUES = %w[1 on On ON t true True TRUE T y yes Yes YES Y].freeze
7
- # Some string values that can be considered as +false+ (thank you dry-rb for inspiration).
8
- FALSE_VALUES = %w[0 off Off OFF f false False FALSE F n no No NO N].freeze
9
-
10
- NOT_GTE = :not_gte
11
- NOT_GT = :not_gt
12
- NOT_LTE = :not_lte
13
- NOT_LT = :not_lt
14
- NOT_INCLUDED = :not_included
15
- TOO_LONG = :too_long
16
- TOO_SHORT = :too_short
17
-
18
- NON_BOOL = :non_bool
19
- NON_DECIMAL = :non_decimal
20
- NON_INTEGER = :non_integer
21
- NON_STRING = :non_string
22
-
23
- # Verifies inclusion of a value in a collection.
24
- #
25
- # Returns
26
- # lambda { |collection, option| ... }.
27
- #
28
- # Verifies if value of the +option+ is included in the provided +collection+.
29
- def self.included_in
30
- lambda { |collection, option|
31
- collection.include?(option.value) ? Success.new(option) : Failure.new(NOT_INCLUDED)
32
- }.curry
33
- end
4
+ # It defines some actions that can be useful in an everyday life.
5
+ module Std
6
+ def self.included(base)
7
+ base.include(Messages)
8
+ base.extend(Actions)
9
+ end
34
10
 
35
- # Returns
36
- # lambda { |limit, option| ... }.
37
- #
38
- # Checks if the +option+'s value is greater than or equal to the provided +limit+.
39
- def self.gte
40
- ->(limit, option) { option.value >= limit ? Success.new(option) : Failure.new(NOT_GTE) }.curry
41
- end
11
+ # Some string values that can be considered as +true+ (thank you dry-rb for inspiration).
12
+ TRUE_VALUES = %w[1 on On ON t true True TRUE T y yes Yes YES Y].freeze
13
+ # Some string values that can be considered as +false+ (thank you dry-rb for inspiration).
14
+ FALSE_VALUES = %w[0 off Off OFF f false False FALSE F n no No NO N].freeze
42
15
 
43
- # Returns
44
- # lambda { |limit, option| ... }.
45
- #
46
- # Checks if the +option+'s value is greater than the provided +limit+.
47
- def self.gt
48
- ->(limit, option) { option.value > limit ? Success.new(option) : Failure.new(NOT_GT) }.curry
49
- end
16
+ # Validation messages for rules.
17
+ module Messages
18
+ BLANK = :blank
19
+ MISSING = :missing
50
20
 
51
- # Returns
52
- # lambda { |limit, option| ... }.
53
- #
54
- # Checks if the +option+'s value is less than or equal to the provided +limit+.
55
- def self.lte
56
- ->(limit, option) { option.value <= limit ? Success.new(option) : Failure.new(NOT_LTE) }.curry
57
- end
21
+ NOT_GTE = :not_gte
22
+ NOT_GT = :not_gt
23
+ NOT_LTE = :not_lte
24
+ NOT_LT = :not_lt
25
+ NOT_INCLUDED = :not_included
26
+ TOO_LONG = :too_long
27
+ TOO_SHORT = :too_short
58
28
 
59
- # Returns
60
- # lambda { |limit, option| ... }.
61
- #
62
- # Checks if the +option+'s value is less than the provided +limit+.
63
- def self.lt
64
- ->(limit, option) { option.value < limit ? Success.new(option) : Failure.new(NOT_LT) }.curry
65
- end
29
+ NON_BOOL = :non_bool
30
+ NON_DECIMAL = :non_decimal
31
+ NON_INTEGER = :non_integer
32
+ NON_STRING = :non_string
33
+ end
66
34
 
67
- # Returns
68
- # lambda { |limit, option| ... }.
69
- #
70
- # Checks if the size of the value in +option+ does not exceed provided +limit+.
71
- def self.max_size
72
- lambda { |limit, option|
73
- option.value.size <= limit ? Success.new(option) : Failure.new(TOO_LONG)
74
- }.curry
75
- end
35
+ # Actions definitions.
36
+ module Actions
37
+ # Verifies inclusion of a value in a collection.
38
+ #
39
+ # Returns
40
+ # lambda { |collection, option| ... }.
41
+ #
42
+ # Verifies if value of the +option+ is included in the provided +collection+.
43
+ def included_in
44
+ lambda { |collection, option|
45
+ collection.include?(option.value) ? Success.new(option) : Failure.new(Messages::NOT_INCLUDED)
46
+ }.curry
47
+ end
76
48
 
77
- # Returns
78
- # lambda { |limit, option| ... }.
79
- #
80
- # Checks if the size of the value in +option+ is not lower than the provided +limit+.
81
- def self.min_size
82
- lambda { |limit, option|
83
- option.value.size >= limit ? Success.new(option) : Failure.new(TOO_SHORT)
84
- }.curry
85
- end
49
+ # Describes an optional value.
50
+ #
51
+ # Returns
52
+ # lambda { |fn, option| ... }.
53
+ #
54
+ # If +option+ is the +Optiomist::None+ it succeeds causing the parameter not to be included in the final result.
55
+ # Otherwise executes the funciton +fn+ for the option.
56
+ def optional
57
+ lambda { |fn, option|
58
+ case option
59
+ in Optiomist::None
60
+ Success.new(option)
61
+ in Optiomist::Some
62
+ fn.call(option)
63
+ end
64
+ }.curry
65
+ end
86
66
 
87
- # Returns
88
- # lambda { |option| ... }.
89
- #
90
- # Removes leading and trailing spaces from string provided in +option+'s value.
91
- def self.stripped
92
- ->(option) { Success.new(Optiomist.some(option.value.strip)) }
93
- end
67
+ # Describes a required value.
68
+ #
69
+ # Returns
70
+ # lambda { |fn, option| ... }.
71
+ #
72
+ # If +option+ is a +Optiomist::None+ it fails otherwise executes the funciton +fn+ for the option.
73
+ def required
74
+ lambda { |fn, option|
75
+ case option
76
+ in Optiomist::None
77
+ Failure.new(Messages::MISSING)
78
+ in Optiomist::Some
79
+ fn.call(option)
80
+ end
81
+ }.curry
82
+ end
94
83
 
95
- # Returns
96
- # lambda { |fn, option| ... }.
97
- #
98
- # Converts provided +option+'s value to integer.
99
- # If the conversion is not possible it fails, otherwise executes the provider function +fn+
100
- # for the converted integer value.
101
- def self.integer
102
- lambda { |fn, option|
103
- begin
104
- integer_value = Integer(option.value)
105
- rescue StandardError
106
- return Failure.new(NON_INTEGER)
107
- end
108
- fn.call(Optiomist.some(integer_value))
109
- }.curry
110
- end
84
+ # Converts blank value to nil or passes non blank value to next rule.
85
+ #
86
+ # Returns
87
+ # lambda { |fn, option| ... }.
88
+ #
89
+ # If provided +option+'s value is blank it succeeds with +nil+
90
+ # otherwise executes provided function for the +option+.
91
+ def blank_to_nil_or
92
+ lambda { |fn, option|
93
+ blank?(option.value) ? Success.new(Optiomist.some(nil)) : fn.call(option)
94
+ }.curry
95
+ end
111
96
 
112
- # Returns
113
- # lambda { |fn, option| ... }.
114
- #
115
- # Converts provided +option+'s value to float.
116
- # If the conversion is not possible it fails, otherwise executes the provider function +fn+
117
- # for the converted float value.
118
- def self.decimal
119
- lambda { |fn, option|
120
- begin
121
- float_value = Float(option.value)
122
- rescue StandardError
123
- return Failure.new(NON_DECIMAL)
124
- end
125
- fn.call(Optiomist.some(float_value))
126
- }.curry
127
- end
97
+ # Verifies if value is not blank.
98
+ #
99
+ # Returns
100
+ # lambda { |option| ... }.
101
+ #
102
+ # It fails if provided +option+ is blank, otherwise succeeds with the +option+.
103
+ def not_blank
104
+ ->(option) { blank?(option.value) ? Failure.new(Messages::BLANK) : Success.new(option) }
105
+ end
128
106
 
129
- # Returns
130
- # lambda { |fn, option| ... }.
131
- #
132
- # Converts provided +option+'s value to boolean.
133
- # If the conversion is not possible it fails, otherwise executes the provider function +fn+
134
- # for the converted boolean value.
135
- def self.bool
136
- lambda { |fn, option|
137
- case option
138
- in Optiomist::Some
139
- if [true, *TRUE_VALUES].include?(option.value)
140
- fn.call(Optiomist.some(true))
141
- elsif [false, *FALSE_VALUES].include?(option.value)
142
- fn.call(Optiomist.some(false))
143
- else
144
- Failure.new(NON_BOOL)
145
- end
146
- in Optiomist::None
147
- Failure.new(NON_BOOL)
148
- end
149
- }.curry
150
- end
107
+ # Verifies if provided value is nil, empty string or string consisting only from spaces.
108
+ def blank?(value)
109
+ value.nil? || (value.is_a?(String) && value.strip.empty?)
110
+ end
111
+
112
+ # Returns
113
+ # lambda { |limit, option| ... }.
114
+ #
115
+ # Checks if the +option+'s value is greater than or equal to the provided +limit+.
116
+ def gte
117
+ ->(limit, option) { option.value >= limit ? Success.new(option) : Failure.new(Messages::NOT_GTE) }.curry
118
+ end
151
119
 
152
- # Returns
153
- # lambda { |fn, option| ... }.
154
- #
155
- # Converts provided +option+'s value to string.
156
- # If the conversion is not possible it fails, otherwise executes the provider function +fn+
157
- # for the converted string value.
158
- def self.string
159
- lambda { |fn, option|
160
- case option
161
- in Optiomist::Some
162
- fn.call(Optiomist.some(option.value.to_s))
163
- in Optiomist::None
164
- Failure.new(NON_STRING)
165
- end
166
- }.curry
120
+ # Returns
121
+ # lambda { |limit, option| ... }.
122
+ #
123
+ # Checks if the +option+'s value is greater than the provided +limit+.
124
+ def gt
125
+ ->(limit, option) { option.value > limit ? Success.new(option) : Failure.new(Messages::NOT_GT) }.curry
126
+ end
127
+
128
+ # Returns
129
+ # lambda { |limit, option| ... }.
130
+ #
131
+ # Checks if the +option+'s value is less than or equal to the provided +limit+.
132
+ def lte
133
+ ->(limit, option) { option.value <= limit ? Success.new(option) : Failure.new(Messages::NOT_LTE) }.curry
134
+ end
135
+
136
+ # Returns
137
+ # lambda { |limit, option| ... }.
138
+ #
139
+ # Checks if the +option+'s value is less than the provided +limit+.
140
+ def lt
141
+ ->(limit, option) { option.value < limit ? Success.new(option) : Failure.new(Messages::NOT_LT) }.curry
142
+ end
143
+
144
+ # Returns
145
+ # lambda { |limit, option| ... }.
146
+ #
147
+ # Checks if the size of the value in +option+ does not exceed provided +limit+.
148
+ def max_size
149
+ lambda { |limit, option|
150
+ option.value.size <= limit ? Success.new(option) : Failure.new(Messages::TOO_LONG)
151
+ }.curry
152
+ end
153
+
154
+ # Returns
155
+ # lambda { |limit, option| ... }.
156
+ #
157
+ # Checks if the size of the value in +option+ is not lower than the provided +limit+.
158
+ def min_size
159
+ lambda { |limit, option|
160
+ option.value.size >= limit ? Success.new(option) : Failure.new(Messages::TOO_SHORT)
161
+ }.curry
162
+ end
163
+
164
+ # Returns
165
+ # lambda { |option| ... }.
166
+ #
167
+ # Removes leading and trailing spaces from string provided in +option+'s value.
168
+ def stripped
169
+ ->(option) { Success.new(Optiomist.some(option.value.strip)) }
170
+ end
171
+
172
+ # Returns
173
+ # lambda { |fn, option| ... }.
174
+ #
175
+ # Converts provided +option+'s value to integer.
176
+ # If the conversion is not possible it fails, otherwise executes the provider function +fn+
177
+ # for the converted integer value.
178
+ def integer
179
+ lambda { |fn, option|
180
+ begin
181
+ integer_value = Integer(option.value)
182
+ rescue StandardError
183
+ return Failure.new(Messages::NON_INTEGER)
184
+ end
185
+ fn.call(Optiomist.some(integer_value))
186
+ }.curry
187
+ end
188
+
189
+ # Returns
190
+ # lambda { |fn, option| ... }.
191
+ #
192
+ # Converts provided +option+'s value to float.
193
+ # If the conversion is not possible it fails, otherwise executes the provider function +fn+
194
+ # for the converted float value.
195
+ def decimal
196
+ lambda { |fn, option|
197
+ begin
198
+ float_value = Float(option.value)
199
+ rescue StandardError
200
+ return Failure.new(Messages::NON_DECIMAL)
201
+ end
202
+ fn.call(Optiomist.some(float_value))
203
+ }.curry
204
+ end
205
+
206
+ # Returns
207
+ # lambda { |fn, option| ... }.
208
+ #
209
+ # Converts provided +option+'s value to boolean.
210
+ # If the conversion is not possible it fails, otherwise executes the provider function +fn+
211
+ # for the converted boolean value.
212
+ def bool
213
+ lambda { |fn, option|
214
+ case option
215
+ in Optiomist::Some
216
+ if [true, *TRUE_VALUES].include?(option.value)
217
+ fn.call(Optiomist.some(true))
218
+ elsif [false, *FALSE_VALUES].include?(option.value)
219
+ fn.call(Optiomist.some(false))
220
+ else
221
+ Failure.new(Messages::NON_BOOL)
222
+ end
223
+ in Optiomist::None
224
+ Failure.new(Messages::NON_BOOL)
225
+ end
226
+ }.curry
227
+ end
228
+
229
+ # Returns
230
+ # lambda { |fn, option| ... }.
231
+ #
232
+ # Converts provided +option+'s value to string.
233
+ # If the conversion is not possible it fails, otherwise executes the provider function +fn+
234
+ # for the converted string value.
235
+ def string
236
+ lambda { |fn, option|
237
+ case option
238
+ in Optiomist::Some
239
+ fn.call(Optiomist.some(option.value.to_s))
240
+ in Optiomist::None
241
+ Failure.new(Messages::NON_STRING)
242
+ end
243
+ }.curry
244
+ end
245
+ end
167
246
  end
168
247
  end
data/lib/param_param.rb CHANGED
@@ -2,156 +2,80 @@
2
2
 
3
3
  require 'optiomist'
4
4
  require 'param_param/result'
5
- require 'param_param/std'
6
5
 
7
- # The main purpose of this module is to convert hash data
8
- # applying a chain of rules to values in the provided hash.
6
+ # It allows to define pipelines that transform hash values.
9
7
  #
10
- # It can be used to process form data in a web application converting
11
- # user provided data to values understood by the application and validate
12
- # it the data fulfills constraints required by the application.
13
- #
14
- # Example:
15
- #
16
- # class UserOperation
17
- # Rules = ParamParam.define.(
18
- # name: ParamParam.required.(
19
- # ParamParam.string.(ParamParam.all_of.([ParamParam.not_nil, ParamParam.stripped, ParamParam.max_size.(50)]))
20
- # ),
21
- # admin: ParamParam.required.(ParamParam.bool.(ParamParam.any)),
22
- # age: ParamParam.optional.(ParamParam.integer.(ParamParam.gt.(0))),
23
- # )
24
- #
25
- # def create(name:, age:)
26
- # params, errors = Rules.(name: name, age: age)
27
- # throw errors unless errors.empty?
28
- #
29
- # # do something with params
30
- # end
31
- # end
8
+ # Each pipeline is bound to a particular key and processes a value related to that key.
9
+ # A pipeline consists of a chain of actions that are executed one by one.
10
+ # An actions receives a value from a previous action and is expected to return successful or failed result.
11
+ # A successful result contains a new value being the result of the processing.
12
+ # The new value is passed further in the chain to the next action.
13
+ # A failed result contains a message from an action saying why a particular value can't be processed.
14
+ # Following actions in the chain are not executed.
32
15
  module ParamParam
33
- MISSING = :missing
34
- BLANK = :blank
35
-
36
- # Converts provided value to +Optiomist::Some+.
37
- # If the value is already +Optiomist::Some+ or +Optiomist::None+ returns it untouched.
38
- def self.optionize(value)
39
- if value.is_a?(Optiomist::Some) || value.is_a?(Optiomist::None)
40
- value
41
- else
42
- Optiomist.some(value)
43
- end
16
+ def self.included(base)
17
+ base.extend(Actions)
44
18
  end
45
19
 
46
- # Verifies if provided value is nil, empty string or string consisting only from spaces.
47
- def self.blank?(value)
48
- value.nil? || (value.is_a?(String) && value.strip.empty?)
49
- end
50
-
51
- # Returns lambda that allows defining a set of rules and bind them to symbols.
52
- # Later those rules can be applied to parameters provided in a for of a hash.
53
- # Each rule defined for a given key processes a value related to the same key in provided parameters.
54
- # lambda { |rules, params| ... }
55
- #
56
- # The lambda returns two hashes:
57
- # - if a value related to a key can be procesed by the rules,
58
- # the result is bound to the key and added to the first hash
59
- # - if a rule can't be applied to a value,
60
- # the error is bound to the key and added to the second hash
61
- #
62
- # Each rule needs to be a lambda taking +Optiomist+ as the only or the last parameter and returning either:
63
- # - +ParamParam::Success+ with processed option
64
- # - +ParamParam::Failure+ with an error
65
- def self.define
66
- lambda { |rules, params|
67
- results = rules.to_h do |key, fn|
68
- option = params.key?(key) ? optionize(params[key]) : Optiomist.none
69
- [key, fn.call(option)]
20
+ # Actions definitions.
21
+ module Actions
22
+ # Converts provided value to +Optiomist::Some+.
23
+ # If the value is already +Optiomist::Some+ or +Optiomist::None+ returns it untouched.
24
+ def optionize(value)
25
+ if value.is_a?(Optiomist::Some) || value.is_a?(Optiomist::None)
26
+ value
27
+ else
28
+ Optiomist.some(value)
70
29
  end
30
+ end
71
31
 
72
- errors = results.select { |_, result| result.failure? }
73
- .transform_values(&:error)
74
- params = results.select { |_, result| result.success? && result.value.some? }
75
- .transform_values { |result| result.value.value }
76
- [params, errors]
77
- }.curry
78
- end
79
-
80
- # It return lambda that allows defining a chain of rules that will be applied one by one
81
- # to value processed by a previous rule.
82
- #
83
- # Returns:
84
- # lambda { |fns, option| ... }
85
- # If some rule fails the chain is broken and value stops being processed.
86
- def self.all_of
87
- lambda { |fns, option|
88
- fns.reduce(Success.new(option)) { |result, fn| result.failure? ? result : fn.call(result.value) }
89
- }.curry
90
- end
91
-
92
- # Returns
93
- # lambda { |option| ... }.
94
- #
95
- # Always succeeds with the provided +option+.
96
- def self.any
97
- ->(option) { Success.new(option) }
98
- end
99
-
100
- # Describes an optional value.
101
- #
102
- # Returns
103
- # lambda { |fn, option| ... }.
104
- #
105
- # If +option+ is the +Optiomist::None+ it succeeds causing the parameter not to be included in the final result.
106
- # Otherwise executes the funciton +fn+ for the option.
107
- def self.optional
108
- lambda { |fn, option|
109
- case option
110
- in Optiomist::None
111
- Success.new(option)
112
- in Optiomist::Some
113
- fn.call(option)
114
- end
115
- }.curry
116
- end
32
+ # Defines pipelines and binds them to symbols.
33
+ # Pipelines are used to process parameters provided in a form of a hash.
34
+ # Each pipeline is defined for a key and processes a value related to that key in provided parameters.
35
+ # lambda { |rules, params| ... }
36
+ #
37
+ # The lambda returns two hashes:
38
+ # - if a value related to a key can be procesed by an action,
39
+ # the result is bound to the key and added to the first hash
40
+ # - if a value related to a key can't be processed by an action,
41
+ # the error is bound to the key and added to the second hash
42
+ #
43
+ # Each action needs to be a lambda taking +Optiomist+ as the only or the last parameter and returning either:
44
+ # - +ParamParam::Success+ with processed option
45
+ # - +ParamParam::Failure+ with an error
46
+ def define
47
+ lambda { |rules, params|
48
+ results = rules.to_h do |key, fn|
49
+ option = params.key?(key) ? optionize(params[key]) : Optiomist.none
50
+ [key, fn.call(option)]
51
+ end
117
52
 
118
- # Describes a required value.
119
- #
120
- # Returns
121
- # lambda { |fn, option| ... }.
122
- #
123
- # If +option+ is a +Optiomist::None+ it fails otherwise executes the funciton +fn+ for the option.
124
- def self.required
125
- lambda { |fn, option|
126
- case option
127
- in Optiomist::None
128
- Failure.new(MISSING)
129
- in Optiomist::Some
130
- fn.call(option)
131
- end
132
- }.curry
133
- end
53
+ errors = results.select { |_, result| result.failure? }
54
+ .transform_values(&:error)
55
+ params = results.select { |_, result| result.success? && result.value.some? }
56
+ .transform_values { |result| result.value.value }
57
+ [params, errors]
58
+ }.curry
59
+ end
134
60
 
135
- # Converts blank value to nil or passes non blank value to next rule.
136
- #
137
- # Returns
138
- # lambda { |fn, option| ... }.
139
- #
140
- # If provided +option+'s value is blank it succeeds with +nil+
141
- # otherwise executes provided function for the +option+.
142
- def self.blank_to_nil_or
143
- lambda { |fn, option|
144
- blank?(option.value) ? Success.new(Optiomist.some(nil)) : fn.call(option)
145
- }.curry
146
- end
61
+ # It return lambda that allows defining a chain of rules that will be applied one by one
62
+ # to value processed by a previous rule.
63
+ #
64
+ # Returns:
65
+ # lambda { |fns, option| ... }
66
+ # If some rule fails the chain is broken and value stops being processed.
67
+ def all_of
68
+ lambda { |fns, option|
69
+ fns.reduce(Success.new(option)) { |result, fn| result.failure? ? result : fn.call(result.value) }
70
+ }.curry
71
+ end
147
72
 
148
- # Verifies if value is not blank.
149
- #
150
- # Returns
151
- # lambda { |option| ... }.
152
- #
153
- # It fails if provided +option+ is blank, otherwise succeeds with the +option+.
154
- def self.not_blank
155
- ->(option) { blank?(option.value) ? Failure.new(BLANK) : Success.new(option) }
73
+ # Returns
74
+ # lambda { |option| ... }.
75
+ #
76
+ # Always succeeds with the provided +option+.
77
+ def any
78
+ ->(option) { Success.new(option) }
79
+ end
156
80
  end
157
81
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: param_param
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michał Radmacher
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-09-11 00:00:00.000000000 Z
11
+ date: 2023-09-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: optiomist
@@ -57,8 +57,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
57
57
  - !ruby/object:Gem::Version
58
58
  version: '0'
59
59
  requirements: []
60
- rubygems_version: 3.3.7
60
+ rubygems_version: 3.1.2
61
61
  signing_key:
62
62
  specification_version: 4
63
- summary: Params parser built on lambdas
63
+ summary: Lambda powered pipelines for hash values
64
64
  test_files: []