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.
- checksums.yaml +4 -4
- data/README.md +79 -1
- data/lib/param_param/std.rb +231 -152
- data/lib/param_param.rb +65 -141
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 73a3ff3dffbee8500b224cef365440d71cb01b5f8e8a8c8f0e7995e894892493
|
4
|
+
data.tar.gz: c958be904a5b0794c93dbae002da9a33c10a809d168d4234beb0b184e7d2cd8f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: da3253615f397f6bd04f080e9c9e32178bbfa1e59e193287c27ab7a81198194aece453a31fb71f4ef402d6b589da653436d79341a7d221eda838265f65ed7a28
|
7
|
+
data.tar.gz: 51db488247772ae2db84f6ec5883721c9b9c7108a5596f98c98b197028bf0580cba5e1f0110ad062f035ad8802bf947ded9f47afe692df2f06b8413bc28b83df
|
data/README.md
CHANGED
@@ -1,4 +1,82 @@
|
|
1
1
|
# param_param
|
2
|
-
|
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
|
+
```
|
data/lib/param_param/std.rb
CHANGED
@@ -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
|
-
#
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
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
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
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
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
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
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
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
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
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
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
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
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
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
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
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
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
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
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
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
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
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
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
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
|
-
#
|
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
|
-
#
|
11
|
-
#
|
12
|
-
#
|
13
|
-
#
|
14
|
-
#
|
15
|
-
#
|
16
|
-
#
|
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
|
-
|
34
|
-
|
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
|
-
#
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
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
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
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
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
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
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
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
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
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
|
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
|
+
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.
|
60
|
+
rubygems_version: 3.1.2
|
61
61
|
signing_key:
|
62
62
|
specification_version: 4
|
63
|
-
summary:
|
63
|
+
summary: Lambda powered pipelines for hash values
|
64
64
|
test_files: []
|