rasti-form 3.1.2 → 6.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +36 -0
  3. data/README.md +6 -61
  4. data/lib/rasti/form/errors.rb +6 -52
  5. data/lib/rasti/form/validable.rb +10 -7
  6. data/lib/rasti/form.rb +40 -131
  7. data/rasti-form.gemspec +2 -12
  8. data/spec/coverage_helper.rb +1 -3
  9. data/spec/minitest_helper.rb +1 -7
  10. data/spec/validations_spec.rb +333 -0
  11. metadata +19 -48
  12. data/.travis.yml +0 -20
  13. data/lib/rasti/form/castable.rb +0 -25
  14. data/lib/rasti/form/types/array.rb +0 -54
  15. data/lib/rasti/form/types/boolean.rb +0 -42
  16. data/lib/rasti/form/types/enum.rb +0 -48
  17. data/lib/rasti/form/types/float.rb +0 -33
  18. data/lib/rasti/form/types/form.rb +0 -40
  19. data/lib/rasti/form/types/hash.rb +0 -39
  20. data/lib/rasti/form/types/integer.rb +0 -21
  21. data/lib/rasti/form/types/io.rb +0 -23
  22. data/lib/rasti/form/types/regexp.rb +0 -23
  23. data/lib/rasti/form/types/string.rb +0 -40
  24. data/lib/rasti/form/types/symbol.rb +0 -23
  25. data/lib/rasti/form/types/time.rb +0 -40
  26. data/lib/rasti/form/types/uuid.rb +0 -7
  27. data/lib/rasti/form/version.rb +0 -5
  28. data/spec/form_spec.rb +0 -411
  29. data/spec/types/array_spec.rb +0 -54
  30. data/spec/types/boolean_spec.rb +0 -24
  31. data/spec/types/enum_spec.rb +0 -28
  32. data/spec/types/float_spec.rb +0 -18
  33. data/spec/types/form_spec.rb +0 -41
  34. data/spec/types/hash_spec.rb +0 -20
  35. data/spec/types/integer_spec.rb +0 -18
  36. data/spec/types/io_spec.rb +0 -18
  37. data/spec/types/regexp_spec.rb +0 -18
  38. data/spec/types/string_formatted_spec.rb +0 -20
  39. data/spec/types/string_spec.rb +0 -16
  40. data/spec/types/symbol_spec.rb +0 -16
  41. data/spec/types/time_spec.rb +0 -25
  42. data/spec/types/uuid_spec.rb +0 -18
@@ -0,0 +1,333 @@
1
+ require 'minitest_helper'
2
+
3
+ describe Rasti::Form, 'Validations' do
4
+
5
+ def build_form(&block)
6
+ Class.new(Rasti::Form) do
7
+ class_eval(&block)
8
+ end
9
+ end
10
+
11
+ def assert_validation_error(expected_errors, &block)
12
+ error = proc { block.call }.must_raise Rasti::Form::ValidationError
13
+ error.errors.must_equal expected_errors
14
+ end
15
+
16
+ it 'Validation error message' do
17
+ scope = Object.new
18
+ errors = {
19
+ x: ['not present'],
20
+ y: ['error 1', 'error 2']
21
+ }
22
+
23
+ error = Rasti::Form::ValidationError.new scope, errors
24
+
25
+ error.scope.must_equal scope
26
+ error.errors.must_equal errors
27
+ error.message.must_equal "Validation errors:\n- x: [\"not present\"]\n- y: [\"error 1\", \"error 2\"]"
28
+ end
29
+
30
+ it 'Invalid attributes' do
31
+ assert_validation_error(z: ['unexpected attribute']) do
32
+ Rasti::Form[:x, :y].new z: 3
33
+ end
34
+ end
35
+
36
+ it 'Not error' do
37
+ form = build_form do
38
+ attribute :text, T::String
39
+
40
+ def validate
41
+ assert_not_error :text do
42
+ raise 'invalid text' unless assigned? :text
43
+ end
44
+ end
45
+ end
46
+
47
+ proc { form.new text: 'text' }.must_be_silent
48
+
49
+ assert_validation_error(text: ['invalid text']) do
50
+ form.new
51
+ end
52
+ end
53
+
54
+ describe 'Present' do
55
+
56
+ let :form do
57
+ build_form do
58
+ attribute :number, T::Integer
59
+
60
+ def validate
61
+ assert_present :number
62
+ end
63
+ end
64
+ end
65
+
66
+ it 'Success' do
67
+ proc { form.new number: 1 }.must_be_silent
68
+ end
69
+
70
+ it 'Not assigned' do
71
+ assert_validation_error(number: ['not present']) do
72
+ form.new
73
+ end
74
+ end
75
+
76
+ it 'Nil' do
77
+ assert_validation_error(number: ['not present']) do
78
+ form.new number: nil
79
+ end
80
+ end
81
+
82
+ it 'Invalid cast' do
83
+ assert_validation_error(number: ['Invalid cast: true -> Rasti::Types::Integer']) do
84
+ form.new number: true
85
+ end
86
+ end
87
+
88
+ it 'Invalid nested cast' do
89
+ range = build_form do
90
+ attribute :min, T::Integer
91
+ attribute :max, T::Integer
92
+
93
+ def validate
94
+ assert_range :min, :max
95
+ end
96
+ end
97
+
98
+ form = build_form do
99
+ attribute :range, T::Model[range]
100
+
101
+ def validate
102
+ assert_present :range
103
+ end
104
+ end
105
+
106
+ assert_validation_error('range.max' => ['not present']) do
107
+ form.new range: {min: 1}
108
+ end
109
+ end
110
+
111
+ it 'With default' do
112
+ form = build_form do
113
+ attribute :number, T::Integer, default: 1
114
+
115
+ def validate
116
+ assert_present :number
117
+ end
118
+ end
119
+
120
+ proc { form.new }.must_be_silent
121
+
122
+ assert_validation_error(number: ['not present']) do
123
+ form.new number: nil
124
+ end
125
+ end
126
+
127
+ end
128
+
129
+ describe 'Not present' do
130
+
131
+ let :form do
132
+ range = build_form do
133
+ attribute :min, T::Integer
134
+ attribute :max, T::Integer
135
+
136
+ def validate
137
+ assert_range :min, :max
138
+ end
139
+ end
140
+
141
+ build_form do
142
+ attribute :range, T::Model[range]
143
+
144
+ def validate
145
+ assert_not_present :range
146
+ end
147
+ end
148
+ end
149
+
150
+ it 'Success not assigned' do
151
+ proc { form.new }.must_be_silent
152
+ end
153
+
154
+ it 'Success with nil' do
155
+ proc { form.new range: nil }.must_be_silent
156
+ end
157
+
158
+ it 'Assigned' do
159
+ assert_validation_error(range: ['is present']) do
160
+ form.new range: {min: 1, max: 2}
161
+ end
162
+ end
163
+
164
+ it 'Invalid nested cast' do
165
+ assert_validation_error('range.max' => ['not present']) do
166
+ form.new range: {min: 1}
167
+ end
168
+ end
169
+
170
+ end
171
+
172
+ describe 'Not empty' do
173
+
174
+ it 'Must be present' do
175
+ form = build_form do
176
+ attribute :text, T::String
177
+
178
+ def validate
179
+ assert_not_empty :text
180
+ end
181
+ end
182
+
183
+ proc { form.new text: 'text' }.must_be_silent
184
+
185
+ assert_validation_error(text: ['not present']) do
186
+ form.new text: nil
187
+ end
188
+
189
+ assert_validation_error(text: ['not present']) do
190
+ form.new
191
+ end
192
+ end
193
+
194
+ it 'String' do
195
+ form = build_form do
196
+ attribute :text, T::String
197
+
198
+ def validate
199
+ assert_not_empty :text
200
+ end
201
+ end
202
+
203
+ proc { form.new text: 'text' }.must_be_silent
204
+
205
+ assert_validation_error(text: ['is empty']) do
206
+ form.new text: ' '
207
+ end
208
+ end
209
+
210
+ it 'Array' do
211
+ form = build_form do
212
+ attribute :array, T::Array[T::String]
213
+
214
+ def validate
215
+ assert_not_empty :array
216
+ end
217
+ end
218
+
219
+ proc { form.new array: ['text'] }.must_be_silent
220
+
221
+ assert_validation_error(array: ['is empty']) do
222
+ form.new array: []
223
+ end
224
+ end
225
+
226
+ it 'Hash' do
227
+ form = build_form do
228
+ attribute :hash, T::Hash[T::String, T::String]
229
+
230
+ def validate
231
+ assert_not_empty :hash
232
+ end
233
+ end
234
+
235
+ proc { form.new hash: {key: 'value'} }.must_be_silent
236
+
237
+ assert_validation_error(hash: ['is empty']) do
238
+ form.new hash: {}
239
+ end
240
+ end
241
+
242
+ end
243
+
244
+ it 'Included in values list' do
245
+ form = build_form do
246
+ attribute :text, T::String
247
+
248
+ def validate
249
+ assert_included_in :text, %w(value_1 value_2)
250
+ end
251
+ end
252
+
253
+ proc { form.new text: 'value_1' }.must_be_silent
254
+
255
+ assert_validation_error(text: ['not included in "value_1", "value_2"']) do
256
+ form.new text: 'xyz'
257
+ end
258
+ end
259
+
260
+ it 'Time range' do
261
+ form = build_form do
262
+ attribute :from, T::Time['%Y-%m-%d %H:%M:%S']
263
+ attribute :to, T::Time['%Y-%m-%d %H:%M:%S']
264
+
265
+ def validate
266
+ assert_range :from, :to
267
+ end
268
+ end
269
+
270
+ from = '2018-01-01 03:10:00'
271
+ to = '2018-01-01 15:30:00'
272
+
273
+ proc { form.new from: from, to: to }.must_be_silent
274
+
275
+ assert_validation_error(from: ['invalid range']) do
276
+ form.new from: to, to: from
277
+ end
278
+ end
279
+
280
+ it 'Nested validation' do
281
+ range = build_form do
282
+ attribute :min, T::Integer
283
+ attribute :max, T::Integer
284
+
285
+ def validate
286
+ assert_range :min, :max
287
+ end
288
+ end
289
+
290
+ form = build_form do
291
+ attribute :range, T::Model[range]
292
+ end
293
+
294
+ proc { form.new range: {min: 1, max: 2} }.must_be_silent
295
+
296
+ assert_validation_error('range.min' => ['invalid range']) do
297
+ form.new range: {min: 2, max: 1}
298
+ end
299
+ end
300
+
301
+ describe 'Validations Precedence' do
302
+
303
+ let(:form) do
304
+ build_form do
305
+ attribute :limit, T::Integer
306
+
307
+ def validate
308
+ assert :limit, limit < 10, 'invalid limit'
309
+ end
310
+ end
311
+ end
312
+
313
+ it '1) Validate unexpected attributes' do
314
+ assert_validation_error(id: ["unexpected attribute"]) do
315
+ form.new id: '123'
316
+ end
317
+ end
318
+
319
+ it '2) Validate attributes casting' do
320
+ assert_validation_error(limit: ["Invalid cast: 'pepe' -> Rasti::Types::Integer"]) do
321
+ form.new limit: 'pepe'
322
+ end
323
+ end
324
+
325
+ it '3) Run custom validations' do
326
+ assert_validation_error(limit: ["invalid limit"]) do
327
+ form.new limit: 500
328
+ end
329
+ end
330
+
331
+ end
332
+
333
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rasti-form
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.1.2
4
+ version: 6.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gabriel Naiman
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-10-05 00:00:00.000000000 Z
11
+ date: 2022-04-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: multi_require
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '1.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rasti-model
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.0'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: rake
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -136,51 +150,22 @@ extensions: []
136
150
  extra_rdoc_files: []
137
151
  files:
138
152
  - ".coveralls.yml"
153
+ - ".github/workflows/ci.yml"
139
154
  - ".gitignore"
140
155
  - ".ruby-gemset"
141
156
  - ".ruby-version"
142
- - ".travis.yml"
143
157
  - Gemfile
144
158
  - LICENSE.txt
145
159
  - README.md
146
160
  - Rakefile
147
161
  - lib/rasti-form.rb
148
162
  - lib/rasti/form.rb
149
- - lib/rasti/form/castable.rb
150
163
  - lib/rasti/form/errors.rb
151
- - lib/rasti/form/types/array.rb
152
- - lib/rasti/form/types/boolean.rb
153
- - lib/rasti/form/types/enum.rb
154
- - lib/rasti/form/types/float.rb
155
- - lib/rasti/form/types/form.rb
156
- - lib/rasti/form/types/hash.rb
157
- - lib/rasti/form/types/integer.rb
158
- - lib/rasti/form/types/io.rb
159
- - lib/rasti/form/types/regexp.rb
160
- - lib/rasti/form/types/string.rb
161
- - lib/rasti/form/types/symbol.rb
162
- - lib/rasti/form/types/time.rb
163
- - lib/rasti/form/types/uuid.rb
164
164
  - lib/rasti/form/validable.rb
165
- - lib/rasti/form/version.rb
166
165
  - rasti-form.gemspec
167
166
  - spec/coverage_helper.rb
168
- - spec/form_spec.rb
169
167
  - spec/minitest_helper.rb
170
- - spec/types/array_spec.rb
171
- - spec/types/boolean_spec.rb
172
- - spec/types/enum_spec.rb
173
- - spec/types/float_spec.rb
174
- - spec/types/form_spec.rb
175
- - spec/types/hash_spec.rb
176
- - spec/types/integer_spec.rb
177
- - spec/types/io_spec.rb
178
- - spec/types/regexp_spec.rb
179
- - spec/types/string_formatted_spec.rb
180
- - spec/types/string_spec.rb
181
- - spec/types/symbol_spec.rb
182
- - spec/types/time_spec.rb
183
- - spec/types/uuid_spec.rb
168
+ - spec/validations_spec.rb
184
169
  homepage: https://github.com/gabynaiman/rasti-form
185
170
  licenses:
186
171
  - MIT
@@ -206,19 +191,5 @@ specification_version: 4
206
191
  summary: Forms validations and type casting
207
192
  test_files:
208
193
  - spec/coverage_helper.rb
209
- - spec/form_spec.rb
210
194
  - spec/minitest_helper.rb
211
- - spec/types/array_spec.rb
212
- - spec/types/boolean_spec.rb
213
- - spec/types/enum_spec.rb
214
- - spec/types/float_spec.rb
215
- - spec/types/form_spec.rb
216
- - spec/types/hash_spec.rb
217
- - spec/types/integer_spec.rb
218
- - spec/types/io_spec.rb
219
- - spec/types/regexp_spec.rb
220
- - spec/types/string_formatted_spec.rb
221
- - spec/types/string_spec.rb
222
- - spec/types/symbol_spec.rb
223
- - spec/types/time_spec.rb
224
- - spec/types/uuid_spec.rb
195
+ - spec/validations_spec.rb
data/.travis.yml DELETED
@@ -1,20 +0,0 @@
1
- language: ruby
2
-
3
- rvm:
4
- - 2.0
5
- - 2.1
6
- - 2.2
7
- - 2.3
8
- - 2.4
9
- - 2.5
10
- - 2.6
11
- - 2.7
12
- - jruby-9.2.9.0
13
- - ruby-head
14
- - jruby-head
15
-
16
- matrix:
17
- fast_finish: true
18
- allow_failures:
19
- - rvm: ruby-head
20
- - rvm: jruby-head
@@ -1,25 +0,0 @@
1
- module Rasti
2
- class Form
3
- module Castable
4
-
5
- def cast(value)
6
- if valid? value
7
- transform! value
8
- else
9
- raise CastError.new self, value
10
- end
11
- end
12
-
13
- private
14
-
15
- def transform!(value)
16
- transform value
17
- rescue MultiCastError, ValidationError => ex
18
- raise ex
19
- rescue
20
- raise CastError.new self, value
21
- end
22
-
23
- end
24
- end
25
- end
@@ -1,54 +0,0 @@
1
- module Rasti
2
- class Form
3
- module Types
4
- class Array
5
-
6
- include Castable
7
-
8
- attr_reader :type
9
-
10
- def self.[](type)
11
- new type
12
- end
13
-
14
- def to_s
15
- "#{self.class}[#{type}]"
16
- end
17
- alias_method :inspect, :to_s
18
-
19
- private
20
-
21
- def initialize(type)
22
- @type = type
23
- end
24
-
25
- def valid?(value)
26
- value.is_a? ::Array
27
- end
28
-
29
- def transform(value)
30
- result = []
31
- errors = {}
32
-
33
- value.each_with_index do |e,i|
34
- index = i + 1
35
- begin
36
- result << type.cast(e)
37
- rescue ValidationError => error
38
- error.errors.each do |k,v|
39
- errors["#{index}.#{k}"] = v
40
- end
41
- rescue => error
42
- errors[index] = [error.message]
43
- end
44
- end
45
-
46
- raise MultiCastError.new(self, value, errors) unless errors.empty?
47
-
48
- result
49
- end
50
-
51
- end
52
- end
53
- end
54
- end
@@ -1,42 +0,0 @@
1
- module Rasti
2
- class Form
3
- module Types
4
- class Boolean
5
- class << self
6
-
7
- include Castable
8
-
9
- TRUE_FORMAT = /^t(rue)?$/i
10
- FALSE_FORMAT = /^f(alse)?$/i
11
-
12
- private
13
-
14
- def valid?(value)
15
- boolean?(value) || valid_string?(value)
16
- end
17
-
18
- def transform(value)
19
- boolean?(value) ? value : true_string?(value)
20
- end
21
-
22
- def boolean?(value)
23
- value == true || value == false
24
- end
25
-
26
- def valid_string?(value)
27
- value.is_a?(::String) && (true_string?(value) || false_string?(value))
28
- end
29
-
30
- def true_string?(value)
31
- !!value.match(TRUE_FORMAT)
32
- end
33
-
34
- def false_string?(value)
35
- !!value.match(FALSE_FORMAT)
36
- end
37
-
38
- end
39
- end
40
- end
41
- end
42
- end
@@ -1,48 +0,0 @@
1
- module Rasti
2
- class Form
3
- module Types
4
- class Enum
5
-
6
- include Castable
7
-
8
- attr_reader :values
9
-
10
- def self.[](*values)
11
- new values
12
- end
13
-
14
- def to_s
15
- "#{self.class}[#{values.map(&:inspect).join(', ')}]"
16
- end
17
- alias_method :inspect, :to_s
18
-
19
- private
20
-
21
- def initialize(values)
22
- @values = values.map(&:to_s)
23
- define_getters
24
- end
25
-
26
- def valid?(value)
27
- values.include? String.cast(value)
28
- rescue
29
- false
30
- end
31
-
32
- def transform(value)
33
- String.cast value
34
- end
35
-
36
- def define_getters
37
- values.each do |value|
38
- getter_name = value.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2').gsub(/([a-z])([A-Z])/, '\1_\2').downcase
39
- define_singleton_method getter_name do
40
- value
41
- end
42
- end
43
- end
44
-
45
- end
46
- end
47
- end
48
- end
@@ -1,33 +0,0 @@
1
- module Rasti
2
- class Form
3
- module Types
4
- class Float
5
- class << self
6
-
7
- include Castable
8
-
9
- FORMAT = /^(\d+\.)?\d+$/
10
-
11
- private
12
-
13
- def valid?(value)
14
- !value.nil? && (valid_string?(value) || transformable?(value))
15
- end
16
-
17
- def transform(value)
18
- value.to_f
19
- end
20
-
21
- def valid_string?(value)
22
- value.is_a?(::String) && value.match(FORMAT)
23
- end
24
-
25
- def transformable?(value)
26
- !value.is_a?(::String) && value.respond_to?(:to_f)
27
- end
28
-
29
- end
30
- end
31
- end
32
- end
33
- end
@@ -1,40 +0,0 @@
1
- module Rasti
2
- class Form
3
- module Types
4
- class Form
5
-
6
- include Castable
7
-
8
- attr_reader :form_class
9
-
10
- def self.[](*args)
11
- new *args
12
- end
13
-
14
- def to_s
15
- "#{self.class}[#{form_class}]"
16
- end
17
- alias_method :inspect, :to_s
18
-
19
- private
20
-
21
- def initialize(form)
22
- @form_class = case
23
- when form.is_a?(::Hash) then Rasti::Form[form]
24
- when form.is_a?(Class) && form.ancestors.include?(Rasti::Form) then form
25
- else raise ArgumentError, "Invalid form specification: #{form.inspect}"
26
- end
27
- end
28
-
29
- def valid?(value)
30
- value.is_a? ::Hash
31
- end
32
-
33
- def transform(value)
34
- form_class.new value
35
- end
36
-
37
- end
38
- end
39
- end
40
- end