pastore 0.0.4 → 0.2.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/.vscode/launch.json +22 -0
- data/CHANGELOG.md +27 -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.rb +4 -0
- 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. |
|
data/lib/pastore/guards.rb
CHANGED
@@ -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
|