param_param 0.0.2 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: []