rasti-form 3.1.2 → 6.0.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.
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