param_param 0.0.2
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 +7 -0
- data/LICENSE +21 -0
- data/README.md +4 -0
- data/lib/param_param/result.rb +47 -0
- data/lib/param_param/std.rb +168 -0
- data/lib/param_param.rb +157 -0
- metadata +64 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 61d152a83cea632bd4e660508c5dc62d26231e9bb60a35626e1991548171e205
|
4
|
+
data.tar.gz: 28d02503d7fc9a374d1e555c3e9328aac9418194ed6520989165b66fe5d4a3cd
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: bdef7f585fc8f1050327bcdbe0946cc3255eca321c2f43b17fd42035cdc4d029f5d85011f91e01e2813af50b9e47ae70593f676078e483611efeed2e2a3f583b
|
7
|
+
data.tar.gz: '004692833ec9cefe0d4ebdc6ec02ffa1c4de71ef38995e03aa62eecf2cbe4e8fa839f75a9119ca4adcab5576742909d69955f332f5e833c01965544fdd54c504'
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2022 Mr Dev
|
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 all
|
13
|
+
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 THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ParamParam
|
4
|
+
# Defines operation result pattern.
|
5
|
+
# A result can be a success or a failure and has some value.
|
6
|
+
class Result
|
7
|
+
# Returns +false+.
|
8
|
+
def success?
|
9
|
+
false
|
10
|
+
end
|
11
|
+
|
12
|
+
# Returns +false+.
|
13
|
+
def failure?
|
14
|
+
false
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# Describes successful result.
|
19
|
+
class Success < Result
|
20
|
+
# A value related to the success.
|
21
|
+
attr_reader :value
|
22
|
+
|
23
|
+
def initialize(value)
|
24
|
+
@value = value
|
25
|
+
end
|
26
|
+
|
27
|
+
# Returns +true+.
|
28
|
+
def success?
|
29
|
+
true
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Describes failed result.
|
34
|
+
class Failure < Result
|
35
|
+
# An error related to the failure.
|
36
|
+
attr_reader :error
|
37
|
+
|
38
|
+
def initialize(error)
|
39
|
+
@error = error
|
40
|
+
end
|
41
|
+
|
42
|
+
# Returns +true+.
|
43
|
+
def failure?
|
44
|
+
true
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,168 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# It contains a collection of some useful rules.
|
4
|
+
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
|
34
|
+
|
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
|
42
|
+
|
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
|
50
|
+
|
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
|
58
|
+
|
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
|
66
|
+
|
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
|
76
|
+
|
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
|
86
|
+
|
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
|
94
|
+
|
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
|
111
|
+
|
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
|
128
|
+
|
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
|
151
|
+
|
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
|
167
|
+
end
|
168
|
+
end
|
data/lib/param_param.rb
ADDED
@@ -0,0 +1,157 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'optiomist'
|
4
|
+
require 'param_param/result'
|
5
|
+
require 'param_param/std'
|
6
|
+
|
7
|
+
# The main purpose of this module is to convert hash data
|
8
|
+
# applying a chain of rules to values in the provided hash.
|
9
|
+
#
|
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
|
32
|
+
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
|
44
|
+
end
|
45
|
+
|
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)]
|
70
|
+
end
|
71
|
+
|
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
|
117
|
+
|
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
|
134
|
+
|
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
|
147
|
+
|
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) }
|
156
|
+
end
|
157
|
+
end
|
metadata
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: param_param
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Michał Radmacher
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2023-09-11 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: optiomist
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.0.3
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.0.3
|
27
|
+
description:
|
28
|
+
email: michal@radmacher.pl
|
29
|
+
executables: []
|
30
|
+
extensions: []
|
31
|
+
extra_rdoc_files:
|
32
|
+
- README.md
|
33
|
+
- LICENSE
|
34
|
+
files:
|
35
|
+
- LICENSE
|
36
|
+
- README.md
|
37
|
+
- lib/param_param.rb
|
38
|
+
- lib/param_param/result.rb
|
39
|
+
- lib/param_param/std.rb
|
40
|
+
homepage: https://github.com/mradmacher/param_param
|
41
|
+
licenses:
|
42
|
+
- MIT
|
43
|
+
metadata:
|
44
|
+
rubygems_mfa_required: 'true'
|
45
|
+
post_install_message:
|
46
|
+
rdoc_options: []
|
47
|
+
require_paths:
|
48
|
+
- lib
|
49
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 2.7.0
|
54
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
version: '0'
|
59
|
+
requirements: []
|
60
|
+
rubygems_version: 3.3.7
|
61
|
+
signing_key:
|
62
|
+
specification_version: 4
|
63
|
+
summary: Params parser built on lambdas
|
64
|
+
test_files: []
|