pastore 0.0.3 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.vscode/launch.json +22 -0
- data/CHANGELOG.md +19 -2
- data/Gemfile +8 -5
- data/Gemfile.lock +10 -4
- data/README.md +20 -205
- data/docs/Guards.md +203 -0
- data/docs/Params.md +122 -0
- data/lib/pastore/guards/settings.rb +6 -4
- data/lib/pastore/params/action_param.rb +158 -0
- data/lib/pastore/params/settings.rb +117 -0
- data/lib/pastore/params/validation.rb +118 -0
- data/lib/pastore/params/validations/boolean_validation.rb +48 -0
- data/lib/pastore/params/validations/date_validation.rb +74 -0
- data/lib/pastore/params/validations/number_validation.rb +66 -0
- data/lib/pastore/params/validations/object_validation.rb +49 -0
- data/lib/pastore/params/validations/string_validation.rb +50 -0
- data/lib/pastore/params.rb +66 -0
- data/lib/pastore/version.rb +1 -1
- data/lib/pastore.rb +1 -1
- metadata +14 -3
- data/lib/pastore/params_validators.rb +0 -7
data/docs/Params.md
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
# `Pastore::Params`
|
2
|
+
|
3
|
+
`Pastore::Params` is the module that provides the features for params validation in Rails controllers. It allows you to define the params with their data type (`string`, `number`, `boolean`, `date` and `object`), specify which params are mandatory and cannot be blank.
|
4
|
+
|
5
|
+
**Table of Contents**
|
6
|
+
|
7
|
+
- [`Pastore::Params`](#pastoreparams)
|
8
|
+
- [Setup](#setup)
|
9
|
+
- [Specifying params](#specifying-params)
|
10
|
+
- [Available param types](#available-param-types)
|
11
|
+
- [Avaliable options](#avaliable-options)
|
12
|
+
|
13
|
+
## Setup
|
14
|
+
|
15
|
+
To start using `Pastore::Params` you just need to include `Pastore::Params` module in your controller. If you plan to use it in all your controllers, just add the following to your `ApplicationController` class:
|
16
|
+
|
17
|
+
```ruby
|
18
|
+
# app/controllers/application_controller.rb
|
19
|
+
|
20
|
+
class ApplicationController < ActionController::API
|
21
|
+
include Pastore::Params
|
22
|
+
|
23
|
+
# Specify response status code to use for invalid params (default: unprocessable_entity)
|
24
|
+
invalid_params_status :bad_request
|
25
|
+
|
26
|
+
# Here you can customize the response to return on invalid params
|
27
|
+
on_invalid_params do
|
28
|
+
render json: { message: 'Invalid params' }
|
29
|
+
end
|
30
|
+
|
31
|
+
# ...
|
32
|
+
end
|
33
|
+
```
|
34
|
+
|
35
|
+
## Specifying params
|
36
|
+
|
37
|
+
Once you have configured `Pastore::Params` in your controller you can use the `param` method to define your params.
|
38
|
+
|
39
|
+
`param` method has the following signature:
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
param PARAM_NAME, **OPTIONS
|
43
|
+
```
|
44
|
+
|
45
|
+
`PARAM_NAME` can be a `String` or a `Symbol`, while `OPTIONS` is a `Hash`.
|
46
|
+
|
47
|
+
Below you can find some examples of params definition using `param` method:
|
48
|
+
|
49
|
+
```ruby
|
50
|
+
class UsersController < ApplicationController
|
51
|
+
# specify that :query param is a string, so that it will automatically be converted to string
|
52
|
+
param :query, type: :string
|
53
|
+
# specify that :page param is a number, which will be defaulted to 1 and will have 1 as lower limit
|
54
|
+
param :page, type: :number, default: 1, min: 1
|
55
|
+
# specify that :per_page param is a number, which will be defaulted to 15 and will enforce the value to be in a range between 1 and 200
|
56
|
+
param :per_page, type: :number, default: 15, clamp: 1..200
|
57
|
+
def index
|
58
|
+
# ... your code ...
|
59
|
+
end
|
60
|
+
|
61
|
+
# specify that :id param is a number and cannot be missing or blank
|
62
|
+
param :id, type: :number, allow_blank: false
|
63
|
+
def show
|
64
|
+
# ... your code ...
|
65
|
+
end
|
66
|
+
|
67
|
+
param :id, type: :number, allow_blank: false
|
68
|
+
# Sometimes you may want to specify a scope for the parameters, because you might have
|
69
|
+
# nested params, like `params[:user][:name]`
|
70
|
+
scope :user do
|
71
|
+
param :name, type: :string, allow_blank: false
|
72
|
+
# For string params you can set a format regexp validation, which will be automatically applied
|
73
|
+
param :email, type: :string, required: true, format: URI::MailTo::EMAIL_REGEXP,
|
74
|
+
modifier: -> { |v| v.strip.downcase }
|
75
|
+
param :birth_date, type: :date, required: true, max: DateTime.now
|
76
|
+
end
|
77
|
+
# You can also specify the scope inline
|
78
|
+
param :preferences, scope: :user, type: :object, default: {}, allow_blank: false
|
79
|
+
def update
|
80
|
+
# ... your code ...
|
81
|
+
end
|
82
|
+
end
|
83
|
+
```
|
84
|
+
|
85
|
+
### Available param types
|
86
|
+
|
87
|
+
`Pastore::Params` supports the following param types:
|
88
|
+
|
89
|
+
| Param type | Aliases | Description |
|
90
|
+
|------------|---------|-------------|
|
91
|
+
| `:string` | | Accepts string values or converts other value types to string. |
|
92
|
+
| `:number` | `integer`, `float` | Accepts integer values or tries to convert string to number. |
|
93
|
+
| `:boolean` | | Accepts boolean values or tries to convert string to boolean. |
|
94
|
+
| `:date` | | Accepts date values or tries to convert string or number (unix time) to date. |
|
95
|
+
| `:object` | | Accepts object (`Hash`) values or tries to convert JSON string to object. |
|
96
|
+
| `:any` | | Accepts any value. |
|
97
|
+
|
98
|
+
### Avaliable options
|
99
|
+
|
100
|
+
There're several generic options that can be used with all param types, which are listed below:
|
101
|
+
|
102
|
+
| Option | Value type | Default | Description |
|
103
|
+
|--------|------------|---------|-------------|
|
104
|
+
| `:type` | `symbol`, `string`, `Class` | `:any` | Specifies the type of the parameter. |
|
105
|
+
| `:scope` | `symbol`, `string`, `symbol[]`, `string[]` | `nil` | Specifies the scope of the parameter, which is necessary for nested params definition like `params[:user][:email]`. |
|
106
|
+
| `:required` | `boolean` | `false` | When `true`, requires the parameter to be passed by client. |
|
107
|
+
| `:allow_blank` | `boolean` | `true` | When `false`, expects parameter's value not to be `nil` or empty. |
|
108
|
+
| `:default` | Depends on param type | `nil` | Allows to specify default value to set on parameter when parameter have not been sent by client. |
|
109
|
+
| `:modifier` | Lambda block | `nil` | Allows to specify a modifier lambda block, which will be used to modify parameter value. |
|
110
|
+
||
|
111
|
+
| **String** |
|
112
|
+
| `format` | `RegExp` | `nil` | Allows to use a custom `RegExp` to validate parameter value. |
|
113
|
+
||
|
114
|
+
| **Number** |
|
115
|
+
| `min` | `integer`, `float` | `nil` | Allows to specify a minimum value for the parameter. |
|
116
|
+
| `max` | `integer`, `float` | `nil` | Allows to specify a maximum value for the parameter. |
|
117
|
+
| `clamp` | `Range`, `integer[2]`, `float[2]` | `[-Float::INFINITY, Float::INFINITY]` | Allows to specify a lower and upper bound for the param value, so that a value outside the bounds will be forced to the nearest bound value. |
|
118
|
+
||
|
119
|
+
| **Date** |
|
120
|
+
| `min` | `Date`, `DateTime`, `Time`, `Integer`, `Float` | `nil` | Allows to specify a minimum value for the parameter. |
|
121
|
+
| `max` | `Date`, `DateTime`, `Time`, `Integer`, `Float` | `nil` | Allows to specify a maximum value for the parameter. |
|
122
|
+
| `clamp` | `Range`, `Date[2]`, `DateTime[2]`, `Time[2]`, `Integer[2]`, `Float[2]` | `nil` | Allows to specify a lower and upper bound for the param value, so that a value outside the bounds will be forced to the nearest bound value. |
|
@@ -4,9 +4,7 @@ module Pastore
|
|
4
4
|
module Guards
|
5
5
|
# Implements a structure where to store the settings for the guards.
|
6
6
|
class Settings # rubocop:disable Metrics/ClassLength
|
7
|
-
|
8
7
|
attr_writer :role_detector, :forbidden_cbk
|
9
|
-
attr_reader :strategy
|
10
8
|
|
11
9
|
def initialize(superklass)
|
12
10
|
@super_guards = superklass.pastore_guards if superklass.respond_to?(:pastore_guards)
|
@@ -15,7 +13,7 @@ module Pastore
|
|
15
13
|
end
|
16
14
|
|
17
15
|
def reset!
|
18
|
-
@strategy =
|
16
|
+
@strategy = nil
|
19
17
|
@role_detector = nil
|
20
18
|
@forbidden_cbk = nil
|
21
19
|
@actions = {}
|
@@ -40,6 +38,10 @@ module Pastore
|
|
40
38
|
@strategy = :deny
|
41
39
|
end
|
42
40
|
|
41
|
+
def strategy
|
42
|
+
@strategy || @super_guards&.strategy || :deny
|
43
|
+
end
|
44
|
+
|
43
45
|
def permit_role(*roles)
|
44
46
|
new_roles = [roles].flatten.compact.uniq.map(&:to_s)
|
45
47
|
conflicts = @buffer.fetch(:denied_roles, []) & new_roles
|
@@ -130,7 +132,7 @@ module Pastore
|
|
130
132
|
|
131
133
|
return false if action&.dig(:denied_roles)&.include?(role)
|
132
134
|
|
133
|
-
|
135
|
+
strategy == :allow || (strategy == :deny && action&.dig(:permitted_roles)&.include?(role)) || false
|
134
136
|
end
|
135
137
|
|
136
138
|
private
|
@@ -0,0 +1,158 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Pastore
|
4
|
+
module Params
|
5
|
+
# Stores data about action parameters
|
6
|
+
class ActionParam
|
7
|
+
AVAILABLE_TYPES = %w[string number boolean date object array any].freeze
|
8
|
+
TYPE_ALIASES = {
|
9
|
+
'text' => 'string',
|
10
|
+
'integer' => 'number',
|
11
|
+
'float' => 'number',
|
12
|
+
'hash' => 'object'
|
13
|
+
}.freeze
|
14
|
+
|
15
|
+
attr_reader :name, :type, :modifier, :options, :scope
|
16
|
+
|
17
|
+
def initialize(name, **options)
|
18
|
+
@name = name
|
19
|
+
@options = options
|
20
|
+
@scope = [@options&.fetch(:scope, nil)].flatten.compact
|
21
|
+
@array = @options&.fetch(:array, false) == true
|
22
|
+
@modifier = @options&.fetch(:modifier, nil)
|
23
|
+
@type = @options&.fetch(:type, :any)
|
24
|
+
|
25
|
+
check_options!
|
26
|
+
end
|
27
|
+
|
28
|
+
def validate(value)
|
29
|
+
Pastore::Params::Validation.validate!(name, type, value, modifier, **options)
|
30
|
+
end
|
31
|
+
|
32
|
+
def array?
|
33
|
+
@array
|
34
|
+
end
|
35
|
+
|
36
|
+
def name_with_scope
|
37
|
+
[@scope, name].flatten.compact.join('.')
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def check_options!
|
43
|
+
check_type!
|
44
|
+
check_modifier!
|
45
|
+
check_required!
|
46
|
+
check_default!
|
47
|
+
check_min_max!
|
48
|
+
check_format!
|
49
|
+
check_clamp!
|
50
|
+
end
|
51
|
+
|
52
|
+
def check_type!
|
53
|
+
@type = @type.to_s.strip.downcase
|
54
|
+
|
55
|
+
@type = TYPE_ALIASES[@type] unless TYPE_ALIASES[@type].nil?
|
56
|
+
|
57
|
+
valid_type = AVAILABLE_TYPES.include?(@type)
|
58
|
+
raise Pastore::Params::InvalidParamTypeError, "Invalid param type: #{@type.inspect}" unless valid_type
|
59
|
+
end
|
60
|
+
|
61
|
+
def check_modifier!
|
62
|
+
return if @modifier.nil? || @modifier.is_a?(Proc)
|
63
|
+
|
64
|
+
raise "Invalid modifier, lambda or Proc expected, got #{@modifier.class}"
|
65
|
+
end
|
66
|
+
|
67
|
+
def check_required!
|
68
|
+
options[:required] = (options[:required] == true)
|
69
|
+
end
|
70
|
+
|
71
|
+
def check_default!
|
72
|
+
return if options[:default].nil?
|
73
|
+
|
74
|
+
validation = Pastore::Params::Validation.validate!(name, type, options[:default], modifier, **options)
|
75
|
+
return if validation.valid?
|
76
|
+
|
77
|
+
raise Pastore::Params::InvalidValueError, "Invalid default value: #{validation.errors.join(",")}"
|
78
|
+
end
|
79
|
+
|
80
|
+
def check_min_max!
|
81
|
+
check_min!
|
82
|
+
check_max!
|
83
|
+
|
84
|
+
return if options[:min].nil? || options[:max].nil?
|
85
|
+
|
86
|
+
raise 'Invalid min-max range' unless options[:min] <= options[:max]
|
87
|
+
end
|
88
|
+
|
89
|
+
def check_min!
|
90
|
+
min = options[:min]
|
91
|
+
return if min.nil?
|
92
|
+
|
93
|
+
raise 'Invalid minimum' unless min.is_a?(Integer) || min.is_a?(Float)
|
94
|
+
end
|
95
|
+
|
96
|
+
def check_max!
|
97
|
+
max = options[:max]
|
98
|
+
return if max.nil?
|
99
|
+
|
100
|
+
raise 'Invalid maximum' unless max.is_a?(Integer) || max.is_a?(Float)
|
101
|
+
end
|
102
|
+
|
103
|
+
def check_format!
|
104
|
+
return if options[:format].nil?
|
105
|
+
|
106
|
+
raise 'Invalid format' unless options[:format].is_a?(Regexp)
|
107
|
+
end
|
108
|
+
|
109
|
+
def check_clamp!
|
110
|
+
return if options[:clamp].nil?
|
111
|
+
|
112
|
+
check_clamp_type!
|
113
|
+
convert_clamp_to_array!
|
114
|
+
normalize_datetime_clamp!
|
115
|
+
check_clamp_bounds!
|
116
|
+
end
|
117
|
+
|
118
|
+
def check_clamp_type!
|
119
|
+
return if options[:clamp].nil?
|
120
|
+
return if [Array, Range].include?(options[:clamp].class)
|
121
|
+
|
122
|
+
raise Pastore::Params::InvalidValueError, "Invalid clamp value: #{options[:clamp].inspect}"
|
123
|
+
end
|
124
|
+
|
125
|
+
def convert_clamp_to_array!
|
126
|
+
clamp = options[:clamp]
|
127
|
+
|
128
|
+
options[:clamp] = clamp.is_a?(Array) ? [clamp.first, clamp.last] : [clamp.begin, clamp.end]
|
129
|
+
end
|
130
|
+
|
131
|
+
def check_clamp_bounds!
|
132
|
+
clamp = options[:clamp]
|
133
|
+
return if clamp.first.nil? || clamp.last.nil?
|
134
|
+
|
135
|
+
raise Pastore::Params::InvalidValueError, "Invalid clamp range: #{clamp.inspect}" if clamp.first > clamp.last
|
136
|
+
end
|
137
|
+
|
138
|
+
def normalize_datetime_clamp!
|
139
|
+
return unless @type == 'date'
|
140
|
+
|
141
|
+
options[:clamp] = options[:clamp].map do |d|
|
142
|
+
return d if d.nil?
|
143
|
+
|
144
|
+
case d
|
145
|
+
when Date then d
|
146
|
+
when String then DateTime.parse(d.to_s)
|
147
|
+
when Integer, Float then Time.at(d).to_datetime
|
148
|
+
when Time then d.to_datetime
|
149
|
+
else
|
150
|
+
raise Pastore::Params::InvalidValueError, "Invalid clamp value: #{options[:clamp].inspect}"
|
151
|
+
end
|
152
|
+
end
|
153
|
+
rescue Date::Error
|
154
|
+
raise Pastore::Params::InvalidValueError, "Invalid clamp value: #{options[:clamp].inspect}"
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'action_param'
|
4
|
+
require_relative 'validation'
|
5
|
+
|
6
|
+
module Pastore
|
7
|
+
module Params
|
8
|
+
# Implements the logic for params settings storage for a controller.
|
9
|
+
class Settings
|
10
|
+
attr_writer :invalid_params_cbk, :response_status
|
11
|
+
|
12
|
+
def initialize(superklass)
|
13
|
+
@super_params = superklass.pastore_params if superklass.respond_to?(:pastore_params)
|
14
|
+
reset!
|
15
|
+
end
|
16
|
+
|
17
|
+
def reset!
|
18
|
+
@actions = {}
|
19
|
+
@invalid_params_cbk = nil
|
20
|
+
@response_status = nil
|
21
|
+
|
22
|
+
reset_buffer!
|
23
|
+
reset_scope!
|
24
|
+
end
|
25
|
+
|
26
|
+
def invalid_params_cbk
|
27
|
+
@invalid_params_cbk || @super_params&.invalid_params_cbk
|
28
|
+
end
|
29
|
+
|
30
|
+
def response_status
|
31
|
+
@response_status || @super_params&.response_status || :unprocessable_entity
|
32
|
+
end
|
33
|
+
|
34
|
+
def reset_buffer!
|
35
|
+
@buffer = []
|
36
|
+
end
|
37
|
+
|
38
|
+
def set_scope(*keys)
|
39
|
+
@scope = [keys].flatten.compact.map(&:to_sym)
|
40
|
+
end
|
41
|
+
|
42
|
+
def reset_scope!
|
43
|
+
@scope = nil
|
44
|
+
end
|
45
|
+
|
46
|
+
def add(name, **options)
|
47
|
+
options = { scope: @scope }.merge(options.symbolize_keys)
|
48
|
+
|
49
|
+
raise ParamAlreadyDefinedError, "Param #{name} already defined" if @buffer.any? { |p| p.name == name }
|
50
|
+
|
51
|
+
if @scope.present? && options[:scope].present? && @scope != options[:scope]
|
52
|
+
error = "Scope overwrite attempt detected (current_scope: #{@scope}, param_scope: #{options[:scope]}) for param #{name}"
|
53
|
+
raise ScopeConflictError, error
|
54
|
+
end
|
55
|
+
|
56
|
+
param = ActionParam.new(name, **options)
|
57
|
+
|
58
|
+
@buffer << param
|
59
|
+
end
|
60
|
+
|
61
|
+
def save_for(action_name)
|
62
|
+
@actions[action_name.to_sym] = @buffer
|
63
|
+
reset_buffer!
|
64
|
+
end
|
65
|
+
|
66
|
+
def validate(params, action_name)
|
67
|
+
action_params = @actions[action_name.to_sym]
|
68
|
+
return {} if action_params.blank?
|
69
|
+
|
70
|
+
action_params.each_with_object({}) do |validator, errors|
|
71
|
+
value = safe_dig(params, *validator.scope, validator.name)
|
72
|
+
validation = validator.validate(value)
|
73
|
+
|
74
|
+
if validation.valid?
|
75
|
+
update_param_value!(params, validator, validation)
|
76
|
+
|
77
|
+
next if validation.errors.empty?
|
78
|
+
end
|
79
|
+
|
80
|
+
errors[validator.name_with_scope] = validation.errors
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
private
|
85
|
+
|
86
|
+
def update_param_value!(params, validator, validation)
|
87
|
+
if validator.scope.empty?
|
88
|
+
params[validator.name] = validation.value
|
89
|
+
return
|
90
|
+
end
|
91
|
+
|
92
|
+
# Try to create missing scope keys
|
93
|
+
key_path = []
|
94
|
+
validator.scope.each do |key|
|
95
|
+
params[key] ||= {}
|
96
|
+
key_path << key
|
97
|
+
|
98
|
+
if params[key].is_a?(ActionController::Parameters)
|
99
|
+
params = params[key]
|
100
|
+
next
|
101
|
+
end
|
102
|
+
|
103
|
+
# if for some reason the scope key is not a hash, we need to add the error to validation errors
|
104
|
+
return validation.add_error(:bad_schema, "Invalid param schema at #{key_path.join(".").inspect}")
|
105
|
+
end
|
106
|
+
|
107
|
+
params[validator.name] = validation.value
|
108
|
+
end
|
109
|
+
|
110
|
+
def safe_dig(params, *keys)
|
111
|
+
[keys].flatten.reduce(params) do |acc, key|
|
112
|
+
acc.respond_to?(:key?) ? acc[key] : nil
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Pastore
|
4
|
+
module Params
|
5
|
+
# Implements the logic of a single param validation
|
6
|
+
class Validation
|
7
|
+
require_relative 'validations/string_validation'
|
8
|
+
require_relative 'validations/number_validation'
|
9
|
+
require_relative 'validations/boolean_validation'
|
10
|
+
require_relative 'validations/object_validation'
|
11
|
+
require_relative 'validations/date_validation'
|
12
|
+
|
13
|
+
# Validates the value based on the given type and values with the appropriate validator.
|
14
|
+
def self.validate!(name, type, value, modifier = nil, **options)
|
15
|
+
case type
|
16
|
+
when 'string' then Pastore::Params::StringValidation.new(name, value, modifier, **options)
|
17
|
+
when 'number' then Pastore::Params::NumberValidation.new(name, value, modifier, **options)
|
18
|
+
when 'boolean' then Pastore::Params::BooleanValidation.new(name, value, modifier, **options)
|
19
|
+
when 'object' then Pastore::Params::ObjectValidation.new(name, value, modifier, **options)
|
20
|
+
when 'date' then Pastore::Params::DateValidation.new(name, value, modifier, **options)
|
21
|
+
when 'any' then Validation.new(name, 'any', value, modifier, **options)
|
22
|
+
else
|
23
|
+
raise Pastore::Params::InvalidValidationTypeError, "Invalid validation type: #{type}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
attr_reader :value, :errors
|
28
|
+
|
29
|
+
def initialize(name, type, value, modifier = nil, **options)
|
30
|
+
@name = name
|
31
|
+
@type = type
|
32
|
+
@modifier = modifier
|
33
|
+
@value = value.nil? ? options[:default] : value
|
34
|
+
@required = (options[:required] == true) # default: false
|
35
|
+
@allow_blank = (options[:allow_blank].nil? || options[:allow_blank]) # default: true
|
36
|
+
@allowed_values = options[:in]
|
37
|
+
@exclude_values = options[:exclude]
|
38
|
+
|
39
|
+
@errors = []
|
40
|
+
|
41
|
+
validate!
|
42
|
+
end
|
43
|
+
|
44
|
+
# Returns true if the value is valid, false otherwise.
|
45
|
+
def valid?
|
46
|
+
@errors.empty?
|
47
|
+
end
|
48
|
+
|
49
|
+
# Returns true if the value is required, false otherwise.
|
50
|
+
def required?
|
51
|
+
@required
|
52
|
+
end
|
53
|
+
|
54
|
+
# Adds an error to the list of errors.
|
55
|
+
def add_error(error_type, message)
|
56
|
+
@errors << { type: 'param', name: @name, value: @value, error: error_type, message: message }
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
# Performs a basic validation of the value and applies the modifier.
|
62
|
+
def validate!
|
63
|
+
# check for value presence and if it's allowed to be blank
|
64
|
+
check_presence!
|
65
|
+
apply_modifier!
|
66
|
+
end
|
67
|
+
|
68
|
+
# Checks if the value is present (not nil) and if it's allowed to be blank.
|
69
|
+
def check_presence!
|
70
|
+
valid = true
|
71
|
+
|
72
|
+
# required options ensures that value is present (not nil)
|
73
|
+
valid = false if required? && value.nil?
|
74
|
+
|
75
|
+
# allow_blank option ensures that value is not blank (not empty)
|
76
|
+
valid = false if !@allow_blank && value.to_s.strip == ''
|
77
|
+
|
78
|
+
add_error(:is_blank, "#{@name} cannot be blank") unless valid
|
79
|
+
|
80
|
+
valid
|
81
|
+
end
|
82
|
+
|
83
|
+
# Applies the modifier to the value.
|
84
|
+
def apply_modifier!
|
85
|
+
return if @modifier.nil?
|
86
|
+
|
87
|
+
@value = @modifier.call(@value)
|
88
|
+
end
|
89
|
+
|
90
|
+
# check if value is in the list of allowed values
|
91
|
+
def check_allowed_values!
|
92
|
+
check_inclusion!
|
93
|
+
check_exclusion!
|
94
|
+
end
|
95
|
+
|
96
|
+
def check_inclusion!
|
97
|
+
return if @allowed_values.nil?
|
98
|
+
return if @allowed_values.include?(value)
|
99
|
+
|
100
|
+
add_error(:not_allowed, "#{@name} has invalid value: #{value}")
|
101
|
+
end
|
102
|
+
|
103
|
+
def check_exclusion!
|
104
|
+
return if @exclude_values.nil?
|
105
|
+
return unless @exclude_values.include?(value)
|
106
|
+
|
107
|
+
add_error(:not_allowed, "#{@name} has invalid value: #{value}")
|
108
|
+
end
|
109
|
+
|
110
|
+
# check if value is a number
|
111
|
+
def numeric?
|
112
|
+
!Float(value).nil?
|
113
|
+
rescue ArgumentError
|
114
|
+
false
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Pastore
|
4
|
+
module Params
|
5
|
+
# Implements the validation logic for object parameters.
|
6
|
+
class BooleanValidation < Validation
|
7
|
+
def initialize(name, value, modifier, **options)
|
8
|
+
super(name, 'boolean', value, modifier, **options)
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def validate!
|
14
|
+
# check for value presence and if it's allowed to be blank
|
15
|
+
check_presence!
|
16
|
+
|
17
|
+
# don't go further if value is blank
|
18
|
+
return if value.to_s.strip == ''
|
19
|
+
|
20
|
+
# check if value is a boolean
|
21
|
+
return unless check_if_boolean!
|
22
|
+
|
23
|
+
# check if value is in the list of allowed values
|
24
|
+
check_allowed_values!
|
25
|
+
|
26
|
+
# apply the modifier
|
27
|
+
apply_modifier!
|
28
|
+
end
|
29
|
+
|
30
|
+
def check_if_boolean!
|
31
|
+
return true if [true, false].any?(value)
|
32
|
+
|
33
|
+
if value.is_a?(String) && boolean?
|
34
|
+
@value = %w[t true y yes].any?(value.strip.downcase)
|
35
|
+
return true
|
36
|
+
end
|
37
|
+
|
38
|
+
add_error(:invalid_type, "#{@name} has invalid type: #{@type} expected")
|
39
|
+
|
40
|
+
false
|
41
|
+
end
|
42
|
+
|
43
|
+
def boolean?
|
44
|
+
%w[t true y yes f false n no].any?(value.strip.downcase)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Pastore
|
4
|
+
module Params
|
5
|
+
# Implements the validation logic for date parameters.
|
6
|
+
class DateValidation < Validation
|
7
|
+
def initialize(name, value, modifier, **options)
|
8
|
+
@min = options[:min]
|
9
|
+
@max = options[:max]
|
10
|
+
@clamp = options[:clamp]
|
11
|
+
|
12
|
+
super(name, 'date', value, modifier, **options)
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def validate!
|
18
|
+
# check for value presence and if it's allowed to be blank
|
19
|
+
check_presence!
|
20
|
+
|
21
|
+
# don't go further if value is blank
|
22
|
+
return if value.to_s.strip == ''
|
23
|
+
|
24
|
+
# check if value is a boolean
|
25
|
+
return unless check_if_date! && check_min_max! && check_clamp!
|
26
|
+
|
27
|
+
# check if value is in the list of allowed values
|
28
|
+
check_allowed_values!
|
29
|
+
|
30
|
+
# apply the modifier
|
31
|
+
apply_modifier!
|
32
|
+
end
|
33
|
+
|
34
|
+
def check_if_date!
|
35
|
+
return true if [Date, Time, DateTime].include?(value.class)
|
36
|
+
|
37
|
+
if numeric?
|
38
|
+
@value = Time.at(value.to_f).to_datetime
|
39
|
+
return true
|
40
|
+
end
|
41
|
+
|
42
|
+
# When value is a string, try to parse it as a DateTime object
|
43
|
+
if value.is_a?(String)
|
44
|
+
begin
|
45
|
+
@value = DateTime.parse(value)
|
46
|
+
return true
|
47
|
+
rescue Date::Error
|
48
|
+
# Do nothing
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
add_error(:invalid_type, "#{@name} has invalid type: #{@type} expected")
|
53
|
+
|
54
|
+
false
|
55
|
+
end
|
56
|
+
|
57
|
+
def check_min_max!
|
58
|
+
min_invalid = @min && value < @min
|
59
|
+
max_invalid = @max && value > @max
|
60
|
+
|
61
|
+
add_error(:too_small, "#{@name} should be greater than #{@min}") if min_invalid
|
62
|
+
add_error(:too_large, "#{@name} should be smaller than #{@max}") if max_invalid
|
63
|
+
|
64
|
+
min_invalid || max_invalid ? false : true
|
65
|
+
end
|
66
|
+
|
67
|
+
def check_clamp!
|
68
|
+
return true if @clamp.nil?
|
69
|
+
|
70
|
+
@value = @value.clamp(@clamp.first, @clamp.last)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|